diff --git a/CHANGELOG.md b/CHANGELOG.md index e36b3f59e..8823bd9cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1 @@ See https://atom.io/releases - -## 1.4.0 - -* Switching encoding is now fast also with large files. -* Fixed an issue where disabling and re-enabling a package caused custom keymaps to be overridden. -* Fixed restoring untitled editors on restart. The new behavior never prompts to save new/changed files when closing a window or quitting Atom. - -## 1.3.0 - -* The tree-view now sorts directory entries more naturally, in a locale-sensitive way. -* Lines can now be moved up and down with multiple cursors. -* Improved the performance of marker-dependent code paths such as spell-check and find and replace. -* Fixed copying and pasting in native input fields. -* By default, windows with no pane items are now closed via the `core:close` command. The previous behavior can be restored via the `Close Empty Windows` option in settings. -* Fixed an issue where characters were inserted when toggling the settings view on some keyboard layouts. -* Modules can now temporarily override `Error.prepareStackTrace`. There is also an `Error.prototype.getRawStack()` method if you just need access to the raw v8 trace structure. -* Fixed a problem that caused blurry fonts on monitors that have a slightly higher resolution than 96 DPI. diff --git a/apm/package.json b/apm/package.json index b8dda21ea..2e6b0b8ea 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.5.0" + "atom-package-manager": "1.6.0" } } diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 26d9c2f42..60d5916b8 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -14,6 +14,8 @@ _ = require 'underscore-plus' packageJson = require '../package.json' module.exports = (grunt) -> + require('time-grunt')(grunt) + grunt.loadNpmTasks('grunt-babel') grunt.loadNpmTasks('grunt-coffeelint') grunt.loadNpmTasks('grunt-lesslint') @@ -36,7 +38,6 @@ module.exports = (grunt) -> buildDir = grunt.option('build-dir') buildDir ?= path.join(os.tmpdir(), 'atom-build') buildDir = path.resolve(buildDir) - disableAutoUpdate = grunt.option('no-auto-update') ? false channel = grunt.option('channel') releasableBranches = ['stable', 'beta'] @@ -179,7 +180,7 @@ module.exports = (grunt) -> pkg: grunt.file.readJSON('package.json') atom: { - appName, channel, metadata, disableAutoUpdate, + appName, channel, metadata, appFileName, apmFileName, appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir, } @@ -255,7 +256,7 @@ module.exports = (grunt) -> outputDir: 'electron' downloadDir: electronDownloadDir rebuild: true # rebuild native modules after electron is updated - token: process.env.ATOM_ACCESS_TOKEN + token: process.env.ATOM_ACCESS_TOKEN ? 'da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4' 'create-windows-installer': installer: diff --git a/build/package.json b/build/package.json index fd7d29d80..d5c780c08 100644 --- a/build/package.json +++ b/build/package.json @@ -37,6 +37,7 @@ "runas": "^3.1", "tello": "1.0.5", "temp": "~0.8.1", + "time-grunt": "1.2.2", "underscore-plus": "1.x", "unzip": "~0.1.9", "vm-compatibility-layer": "~0.1.0", diff --git a/build/tasks/build-task.coffee b/build/tasks/build-task.coffee index 213aa0da4..a86c7c1f4 100644 --- a/build/tasks/build-task.coffee +++ b/build/tasks/build-task.coffee @@ -186,5 +186,4 @@ module.exports = (grunt) -> dependencies = ['compile', 'generate-license:save', 'generate-module-cache', 'compile-packages-slug'] dependencies.push('copy-info-plist') if process.platform is 'darwin' dependencies.push('set-exe-icon') if process.platform is 'win32' - dependencies.push('disable-autoupdate') if grunt.config.get('atom.disableAutoUpdate') grunt.task.run(dependencies...) diff --git a/build/tasks/disable-autoupdate-task.coffee b/build/tasks/disable-autoupdate-task.coffee deleted file mode 100644 index 7517543da..000000000 --- a/build/tasks/disable-autoupdate-task.coffee +++ /dev/null @@ -1,12 +0,0 @@ -fs = require 'fs' -path = require 'path' - -module.exports = (grunt) -> - - grunt.registerTask 'disable-autoupdate', 'Set up disableAutoUpdate field in package.json file', -> - appDir = fs.realpathSync(grunt.config.get('atom.appDir')) - - metadata = grunt.file.readJSON(path.join(appDir, 'package.json')) - metadata._disableAutoUpdate = grunt.config.get('atom.disableAutoUpdate') - - grunt.file.write(path.join(appDir, 'package.json'), JSON.stringify(metadata)) diff --git a/dot-atom/.gitignore b/dot-atom/.gitignore index 81af3f689..e5c80ce23 100644 --- a/dot-atom/.gitignore +++ b/dot-atom/.gitignore @@ -1,5 +1,6 @@ -storage +blob-store compile-cache dev -.npm +storage .node-gyp +.npm diff --git a/keymaps/linux.cson b/keymaps/linux.cson index 28f43a8fc..536a1d75d 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -13,7 +13,7 @@ 'ctrl-alt-o': 'application:add-project-folder' 'ctrl-shift-pageup': 'pane:move-item-left' 'ctrl-shift-pagedown': 'pane:move-item-right' - 'F11': 'window:toggle-full-screen' + 'f11': 'window:toggle-full-screen' # Sublime Parity 'ctrl-,': 'application:show-settings' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index f052a64fc..cb291f493 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -19,7 +19,7 @@ 'ctrl-alt-o': 'application:add-project-folder' 'ctrl-shift-left': 'pane:move-item-left' 'ctrl-shift-right': 'pane:move-item-right' - 'F11': 'window:toggle-full-screen' + 'f11': 'window:toggle-full-screen' # Sublime Parity 'ctrl-,': 'application:show-settings' diff --git a/menus/darwin.cson b/menus/darwin.cson index 52b7a5bc8..23f4b6144 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -110,36 +110,9 @@ ] } - { - label: 'Selection' - submenu: [ - { label: 'Add Selection Above', command: 'editor:add-selection-above' } - { label: 'Add Selection Below', command: 'editor:add-selection-below' } - { label: 'Single Selection', command: 'editor:consolidate-selections'} - { label: 'Split into Lines', command: 'editor:split-selections-into-lines'} - { type: 'separator' } - { label: 'Select to Top', command: 'core:select-to-top' } - { label: 'Select to Bottom', command: 'core:select-to-bottom' } - { type: 'separator' } - { label: 'Select Line', command: 'editor:select-line' } - { label: 'Select Word', command: 'editor:select-word' } - { label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' } - { label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' } - { label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' } - { label: 'Select to End of Word', command: 'editor:select-to-end-of-word' } - { label: 'Select to End of Line', command: 'editor:select-to-end-of-line' } - ] - } - - { - label: 'Find' - submenu: [] - } - { label: 'View' submenu: [ - { label: 'Reload', command: 'window:reload' } { label: 'Toggle Full Screen', command: 'window:toggle-full-screen' } { label: 'Panes' @@ -164,6 +137,7 @@ label: 'Developer' submenu: [ { label: 'Open In Dev Mode…', command: 'application:open-dev' } + { label: 'Reload Window', command: 'window:reload' } { label: 'Run Package Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } ] @@ -177,6 +151,32 @@ ] } + { + label: 'Selection' + submenu: [ + { label: 'Add Selection Above', command: 'editor:add-selection-above' } + { label: 'Add Selection Below', command: 'editor:add-selection-below' } + { label: 'Single Selection', command: 'editor:consolidate-selections'} + { label: 'Split into Lines', command: 'editor:split-selections-into-lines'} + { type: 'separator' } + { label: 'Select to Top', command: 'core:select-to-top' } + { label: 'Select to Bottom', command: 'core:select-to-bottom' } + { type: 'separator' } + { label: 'Select Line', command: 'editor:select-line' } + { label: 'Select Word', command: 'editor:select-word' } + { label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' } + { label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' } + { label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' } + { label: 'Select to End of Word', command: 'editor:select-to-end-of-word' } + { label: 'Select to End of Line', command: 'editor:select-to-end-of-line' } + ] + } + + { + label: 'Find' + submenu: [] + } + { label: 'Packages' submenu: [] diff --git a/menus/linux.cson b/menus/linux.cson index fa831b4a4..9f251d67f 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -95,7 +95,6 @@ { label: '&View' submenu: [ - { label: '&Reload', command: 'window:reload' } { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } { @@ -121,6 +120,7 @@ label: 'Developer' submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } + { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] diff --git a/menus/win32.cson b/menus/win32.cson index 04da3d388..ad0461898 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -94,7 +94,6 @@ { label: '&View' submenu: [ - { label: '&Reload', command: 'window:reload' } { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } { @@ -120,6 +119,7 @@ label: 'Developer' submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } + { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] diff --git a/package.json b/package.json index 35a0bcb2d..b13313aee 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.5.0-dev", + "version": "1.6.0-dev", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { @@ -22,17 +22,17 @@ "clear-cut": "^2.0.1", "coffee-script": "1.8.0", "color": "^0.7.3", - "event-kit": "^1.3.0", + "event-kit": "^1.5.0", "find-parent-dir": "^0.3.0", "first-mate": "^5.1.1", "fs-plus": "^2.8.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", - "git-utils": "^4.0.7", + "git-utils": "^4.1.0", "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", - "jquery": "^2.1.1", + "jquery": "2.1.4", "key-path-helpers": "^0.4.0", "less-cache": "0.22", "line-top-index": "0.2.0", @@ -53,7 +53,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.1.3", + "text-buffer": "8.1.4", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" @@ -66,12 +66,12 @@ "base16-tomorrow-dark-theme": "1.1.0", "base16-tomorrow-light-theme": "1.1.1", "one-dark-ui": "1.1.9", - "one-dark-syntax": "1.1.1", - "one-light-syntax": "1.1.1", "one-light-ui": "1.1.9", + "one-dark-syntax": "1.1.2", + "one-light-syntax": "1.1.2", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", - "about": "1.1.0", + "about": "1.3.0", "archive-view": "0.61.0", "autocomplete-atom-api": "0.9.2", "autocomplete-css": "0.11.0", @@ -103,16 +103,15 @@ "notifications": "0.62.1", "open-on-github": "0.40.0", "package-generator": "0.41.0", - "release-notes": "0.53.0", - "settings-view": "0.232.1", + "settings-view": "0.232.3", "snippets": "1.0.1", - "spell-check": "0.63.0", + "spell-check": "0.65.0", "status-bar": "0.80.0", "styleguide": "0.45.0", "symbols-view": "0.110.1", "tabs": "0.88.0", "timecop": "0.33.0", - "tree-view": "0.198.0", + "tree-view": "0.198.1", "update-package-dependencies": "0.10.0", "welcome": "0.33.0", "whitespace": "0.32.1", @@ -122,24 +121,24 @@ "language-coffee-script": "0.46.0", "language-csharp": "0.11.0", "language-css": "0.36.0", - "language-gfm": "0.82.0", + "language-gfm": "0.83.0", "language-git": "0.11.0", - "language-go": "0.41.0", - "language-html": "0.43.1", + "language-go": "0.42.0", + "language-html": "0.44.0", "language-hyperlink": "0.16.0", "language-java": "0.17.0", - "language-javascript": "0.104.0", - "language-json": "0.17.2", + "language-javascript": "0.105.0", + "language-json": "0.17.3", "language-less": "0.29.0", "language-make": "0.21.0", "language-mustache": "0.13.0", "language-objective-c": "0.15.1", "language-perl": "0.32.0", - "language-php": "0.34.0", + "language-php": "0.36.0", "language-property-list": "0.8.0", - "language-python": "0.42.1", - "language-ruby": "0.65.0", - "language-ruby-on-rails": "0.24.0", + "language-python": "0.43.0", + "language-ruby": "0.68.0", + "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", "language-shellscript": "0.21.0", "language-source": "0.9.0", diff --git a/script/bootstrap b/script/bootstrap index 8314b9cb0..5f9241a3d 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -5,8 +5,16 @@ var verifyRequirements = require('./utils/verify-requirements'); var safeExec = require('./utils/child-process-wrapper.js').safeExec; var path = require('path'); +var t0, t1 + // Executes an array of commands one by one. function executeCommands(commands, done, index) { + if (index != undefined) { + t1 = Date.now() + console.log("=> Took " + (t1 - t0) + "ms."); + console.log(); + } + index = (index == undefined ? 0 : index); if (index < commands.length) { var command = commands[index]; @@ -17,6 +25,7 @@ function executeCommands(commands, done, index) { options = command.options; command = command.command; } + t0 = Date.now() safeExec(command, options, executeCommands.bind(this, commands, done, index + 1)); } else @@ -96,7 +105,10 @@ function bootstrap() { message: 'Installing apm...', options: apmInstallOptions }, - apmPath + ' clean' + apmFlags, + { + command: apmPath + ' clean' + apmFlags, + message: 'Deleting old packages...' + }, moduleInstallCommand, dedupeApmCommand + ' ' + packagesToDedupe.join(' '), ]; diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index caaaed9f2..3c4a4fe4b 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,4 +1,26 @@ +# Users may have this environment variable set. Currently, it causes babel to +# log to stderr, which causes errors on Windows. +# See https://github.com/atom/electron/issues/2033 +process.env.DEBUG='*' + +path = require('path') +temp = require('temp').track() +CompileCache = require('../src/compile-cache') + describe "Babel transpiler support", -> + originalCacheDir = null + + beforeEach -> + originalCacheDir = CompileCache.getCacheDirectory() + CompileCache.setCacheDirectory(temp.mkdirSync('compile-cache')) + for cacheKey in Object.keys(require.cache) + if cacheKey.startsWith(path.join(__dirname, 'fixtures', 'babel')) + console.log('deleting', cacheKey) + delete require.cache[cacheKey] + + afterEach -> + CompileCache.setCacheDirectory(originalCacheDir) + describe 'when a .js file starts with /** @babel */;', -> it "transpiles it using babel", -> transpiled = require('./fixtures/babel/babel-comment.js') @@ -17,3 +39,12 @@ describe "Babel transpiler support", -> describe "when a .js file does not start with 'use babel';", -> it "does not transpile it using babel", -> expect(-> require('./fixtures/babel/invalid.js')).toThrow() + + it "does not try to log to stdout or stderr while parsing the file", -> + spyOn(process.stderr, 'write') + spyOn(process.stdout, 'write') + + transpiled = require('./fixtures/babel/babel-double-quotes.js') + + expect(process.stdout.write).not.toHaveBeenCalled() + expect(process.stderr.write).not.toHaveBeenCalled() diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index eab2f6f04..c431fea5f 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -872,6 +872,26 @@ describe "Config", -> atom.config.loadUserConfig() expect(atom.config.get("foo.bar")).toBe "baz" + describe "when the config file fails to load", -> + addErrorHandler = null + + beforeEach -> + atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy() + spyOn(fs, "existsSync").andCallFake -> + error = new Error() + error.code = 'EPERM' + throw error + + it "creates a notification and does not try to save later changes to disk", -> + load = -> atom.config.loadUserConfig() + expect(load).not.toThrow() + expect(addErrorHandler.callCount).toBe 1 + + atom.config.set("foo.bar", "baz") + advanceClock(100) + expect(atom.config.save).not.toHaveBeenCalled() + expect(atom.config.get("foo.bar")).toBe "baz" + describe ".observeUserConfig()", -> updatedHandler = null diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 8c4adca44..0246008a4 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1253,6 +1253,13 @@ describe "DisplayBuffer", -> decoration.destroy() expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined() + it "does not allow destroyed markers to be decorated", -> + marker.destroy() + expect(-> + displayBuffer.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')}) + ).toThrow("Cannot decorate a destroyed marker") + expect(displayBuffer.getOverlayDecorations()).toEqual [] + describe "when a decoration is updated via Decoration::update()", -> it "emits an 'updated' event containing the new and old params", -> decoration.onDidChangeProperties updatedSpy = jasmine.createSpy() diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index 888272295..38716ab3e 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -19,9 +19,8 @@ class FakeLinesYardstick setScopedCharacterWidth: (scopeNames, character, width) -> @getScopedCharacterWidths(scopeNames)[character] = width - pixelPositionForScreenPosition: (screenPosition, clip=true) -> + pixelPositionForScreenPosition: (screenPosition) -> screenPosition = Point.fromObject(screenPosition) - screenPosition = @model.clipScreenPosition(screenPosition) if clip targetRow = screenPosition.row targetColumn = screenPosition.column diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 799c7685f..da4e08ae2 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -125,6 +125,27 @@ describe "Starting Atom", -> .treeViewRootDirectories() .then ({value}) -> expect(value).toEqual([otherTempDirPath]) + it "opens the new window offset from the other window", -> + runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> + win0Position = null + win1Position = null + client + .waitForWindowCount(1, 10000) + .execute -> atom.getPosition() + .then ({value}) -> win0Position = value + .waitForNewWindow(-> + @startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome) + , 5000) + .waitForWindowCount(2, 10000) + .execute -> atom.getPosition() + .then ({value}) -> win1Position = value + .then -> + expect(win1Position.x).toBeGreaterThan(win0Position.x) + # Ideally we'd test the y coordinate too, but if the window's + # already as tall as it can be, then OS X won't move it down outside + # the screen. + # expect(win1Position.y).toBeGreaterThan(win0Position.y) + describe "reopening a directory that was previously opened", -> it "remembers the state of the window", -> runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) -> diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index c6b8b2d76..46935510f 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -1,6 +1,7 @@ LinesYardstick = require '../src/lines-yardstick' LineTopIndex = require 'line-top-index' {toArray} = require 'underscore-plus' +{Point} = require 'text-buffer' describe "LinesYardstick", -> [editor, mockLineNodesProvider, createdLineNodes, linesYardstick, buildLineNode] = [] @@ -66,12 +67,12 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([0, 0])).toEqual({left: 0, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([0, 1])).toEqual({left: 7, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.78125, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.171875, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.171875, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 287.859375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 37.78125, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43.171875, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72.171875, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28}) it "reuses already computed pixel positions unless it is invalidated", -> atom.styles.addStyleSheet """ @@ -81,9 +82,9 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70}) atom.styles.addStyleSheet """ * { @@ -91,15 +92,15 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70}) linesYardstick.invalidateCache() - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 24, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 72, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 120, top: 70}) it "correctly handles RTL characters", -> atom.styles.addStyleSheet """ @@ -110,13 +111,13 @@ describe "LinesYardstick", -> """ editor.setText("السلام عليكم") - expect(linesYardstick.pixelPositionForScreenPosition([0, 0]).left).toBe 0 - expect(linesYardstick.pixelPositionForScreenPosition([0, 1]).left).toBe 8 - expect(linesYardstick.pixelPositionForScreenPosition([0, 2]).left).toBe 16 - expect(linesYardstick.pixelPositionForScreenPosition([0, 5]).left).toBe 33 - expect(linesYardstick.pixelPositionForScreenPosition([0, 7]).left).toBe 50 - expect(linesYardstick.pixelPositionForScreenPosition([0, 9]).left).toBe 67 - expect(linesYardstick.pixelPositionForScreenPosition([0, 11]).left).toBe 84 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0)).left).toBe 0 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1)).left).toBe 8 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 2)).left).toBe 16 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5)).left).toBe 33 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 7)).left).toBe 50 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 9)).left).toBe 67 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 11)).left).toBe 84 it "doesn't report a width greater than 0 when the character to measure is at the beginning of a text node", -> # This spec documents what seems to be a bug in Chromium, because we'd @@ -141,9 +142,9 @@ describe "LinesYardstick", -> editor.setText(text) - expect(linesYardstick.pixelPositionForScreenPosition([0, 35]).left).toBe 230.90625 - expect(linesYardstick.pixelPositionForScreenPosition([0, 36]).left).toBe 237.5 - expect(linesYardstick.pixelPositionForScreenPosition([0, 37]).left).toBe 244.09375 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375 describe "::screenPositionForPixelPosition(pixelPosition)", -> it "converts pixel positions to screen positions", -> diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index e6848ef03..46d1d11ee 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -1026,6 +1026,16 @@ describe "PackageManager", -> expect(atom.packages.enablePackage("this-doesnt-exist")).toBeNull() expect(console.warn.callCount).toBe 1 + it "does not disable an already disabled package", -> + packageName = 'package-with-main' + atom.config.pushAtKeyPath('core.disabledPackages', packageName) + atom.packages.observeDisabledPackages() + expect(atom.config.get('core.disabledPackages')).toContain packageName + + atom.packages.disablePackage(packageName) + packagesDisabled = atom.config.get('core.disabledPackages').filter((pack) -> pack is packageName) + expect(packagesDisabled.length).toEqual 1 + describe "with themes", -> didChangeActiveThemesHandler = null diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 36803bde6..5c5cd9e95 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -18,6 +18,8 @@ describe "Pane", -> onDidDestroy: (fn) -> @emitter.on('did-destroy', fn) destroy: -> @destroyed = true; @emitter.emit('did-destroy') isDestroyed: -> @destroyed + isPending: -> @pending + pending: false beforeEach -> confirm = spyOn(atom.applicationDelegate, 'confirm') @@ -153,6 +155,26 @@ describe "Pane", -> pane.activateItem(pane.itemAtIndex(1)) expect(observed).toEqual [pane.itemAtIndex(1)] + it "replaces pending items", -> + itemC = new Item("C") + itemD = new Item("D") + itemC.pending = true + itemD.pending = true + + expect(itemC.isPending()).toBe true + pane.activateItem(itemC) + expect(pane.getItems().length).toBe 3 + expect(pane.getActiveItem()).toBe pane.itemAtIndex(1) + + expect(itemD.isPending()).toBe true + pane.activateItem(itemD) + expect(pane.getItems().length).toBe 3 + expect(pane.getActiveItem()).toBe pane.itemAtIndex(1) + + pane.activateItem(pane.itemAtIndex(2)) + expect(pane.getItems().length).toBe 2 + expect(pane.getActiveItem()).toBe pane.itemAtIndex(1) + describe "::activateNextItem() and ::activatePreviousItem()", -> it "sets the active item to the next/previous item, looping around at either end", -> pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")])) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index c85e8818e..9663c6222 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -4809,6 +4809,13 @@ describe('TextEditorComponent', function () { }) }) + describe('::pixelPositionForScreenPosition()', () => { + it('returns the correct horizontal position, even if it is on a row that has not yet been rendered (regression)', () => { + editor.setTextInBufferRange([[5, 0], [6, 0]], 'hello world\n') + expect(wrapperNode.pixelPositionForScreenPosition([5, Infinity]).left).toBeGreaterThan(0) + }) + }) + describe('middle mouse paste on Linux', function () { let originalPlatform diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 02d2e4a96..ce84d2c50 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -5804,3 +5804,29 @@ describe "TextEditor", -> screenRange: marker1.getRange(), rangeIsReversed: false } + + describe "pending state", -> + editor1 = null + beforeEach -> + waitsForPromise -> + atom.workspace.open('sample.txt', pending: true).then (o) -> editor1 = o + + it "should open file in pending state if 'pending' option is true", -> + expect(editor1.isPending()).toBe true + expect(editor.isPending()).toBe false # By default pending status is false + + it "invokes ::onDidTerminatePendingState observers if pending status is terminated", -> + events = [] + editor1.onDidTerminatePendingState (event) -> events.push(event) + editor1.terminatePendingState() + expect(editor1.isPending()).toBe false + expect(events).toEqual [editor1] + + it "should terminate pending state when buffer is changed", -> + events = [] + editor1.onDidTerminatePendingState (event) -> events.push(event) + expect(editor1.isPending()).toBe true + editor1.insertText('I\'ll be back!') + advanceClock(500) + expect(editor1.isPending()).toBe false + expect(events).toEqual [editor1] diff --git a/src/babel.js b/src/babel.js index f53dbc758..1f450ff96 100644 --- a/src/babel.js +++ b/src/babel.js @@ -42,6 +42,10 @@ exports.getCachePath = function (sourceCode) { exports.compile = function (sourceCode, filePath) { if (!babel) { babel = require('babel-core') + var Logger = require('babel-core/lib/transformation/file/logger') + var noop = function () {} + Logger.prototype.debug = noop + Logger.prototype.verbose = noop } var options = {filename: filePath} diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 27b9df8e1..74da80e43 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -103,8 +103,6 @@ class ApplicationMenu downloadingUpdateItem.visible = false installUpdateItem.visible = false - return if @autoUpdateManager.isDisabled() - switch state when 'idle', 'error', 'no-update-available' checkForUpdateItem.visible = true @@ -119,9 +117,10 @@ class ApplicationMenu # # Returns an Array of menu item Objects. getDefaultTemplate: -> - template = [ + [ label: "Atom" submenu: [ + {label: "Check for Update", metadata: {autoUpdate: true}} {label: 'Reload', accelerator: 'Command+R', click: => @focusedWindow()?.reload()} {label: 'Close Window', accelerator: 'Command+Shift+W', click: => @focusedWindow()?.close()} {label: 'Toggle Dev Tools', accelerator: 'Command+Alt+I', click: => @focusedWindow()?.toggleDevTools()} @@ -129,10 +128,6 @@ class ApplicationMenu ] ] - # Add `Check for Update` button if autoUpdateManager is enabled. - template[0].submenu.unshift({label: "Check for Update", metadata: {autoUpdate: true}}) unless @autoUpdateManager.isDisabled() - template - focusedWindow: -> _.find global.atomApplication.windows, (atomWindow) -> atomWindow.isFocused() diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index e720597e3..3096fb7c0 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -74,8 +74,7 @@ class AtomApplication @pidsToOpenWindows = {} @windows = [] - disableAutoUpdate = require(path.join(@resourcePath, 'package.json'))._disableAutoUpdate ? false - @autoUpdateManager = new AutoUpdateManager(@version, options.test, disableAutoUpdate) + @autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath) @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) @@ -167,7 +166,7 @@ class AtomApplication safeMode: @focusedWindow()?.safeMode @on 'application:quit', -> app.quit() - @on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings())) + @on 'application:new-window', -> @openPath(getLoadSettings()) @on 'application:new-file', -> (@focusedWindow() ? this).openPath() @on 'application:open', -> @promptForPathToOpen('all', getLoadSettings()) @on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings()) @@ -229,7 +228,7 @@ class AtomApplication @openUrl({urlToOpen, @devMode, @safeMode}) app.on 'activate-with-no-open-windows', (event) => - event.preventDefault() + event?.preventDefault() @emit('application:new-window') # A request from the associated render process to open a new render process. @@ -360,6 +359,23 @@ class AtomApplication focusedWindow: -> _.find @windows, (atomWindow) -> atomWindow.isFocused() + # Get the platform-specific window offset for new windows. + getWindowOffsetForCurrentPlatform: -> + offsetByPlatform = + darwin: 22 + win32: 26 + offsetByPlatform[process.platform] ? 0 + + # Get the dimensions for opening a new window by cascading as appropriate to + # the platform. + getDimensionsForNewWindow: -> + dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() + offset = @getWindowOffsetForCurrentPlatform() + if dimensions? and offset? + dimensions.x += offset + dimensions.y += offset + dimensions + # Public: Opens a single path, in an existing window if possible. # # options - @@ -370,7 +386,7 @@ class AtomApplication # :safeMode - Boolean to control the opened window's safe mode. # :profileStartup - Boolean to control creating a profile of the startup time. # :window - {AtomWindow} to open file paths in. - openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) -> + openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window} = {}) -> @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) # Public: Opens multiple paths, in existing windows if possible. @@ -417,6 +433,7 @@ class AtomApplication windowInitializationScript ?= require.resolve('../initialize-application-window') resourcePath ?= @resourcePath + windowDimensions ?= @getDimensionsForNewWindow() openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup}) if pidToKillWhenClosed? diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 55ab2462b..6a008d44d 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -1,5 +1,6 @@ autoUpdater = null _ = require 'underscore-plus' +Config = require '../config' {EventEmitter} = require 'events' path = require 'path' @@ -15,10 +16,13 @@ module.exports = class AutoUpdateManager _.extend @prototype, EventEmitter.prototype - constructor: (@version, @testMode, @disabled) -> + constructor: (@version, @testMode, resourcePath) -> @state = IdleState @iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') @feedUrl = "https://atom.io/api/updates?version=#{@version}" + @config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true}) + @config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))} + @config.load() process.nextTick => @setupAutoUpdater() setupAutoUpdater: -> @@ -46,9 +50,13 @@ class AutoUpdateManager @setState(UpdateAvailableState) @emitUpdateAvailableEvent(@getWindows()...) - # Only check for updates periodically if enabled and running in release - # version. - @scheduleUpdateCheck() unless /\w{7}/.test(@version) or @disabled + @config.onDidChange 'core.automaticallyUpdate', ({newValue}) => + if newValue + @scheduleUpdateCheck() + else + @cancelScheduledUpdateCheck() + + @scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate' switch process.platform when 'win32' @@ -56,9 +64,6 @@ class AutoUpdateManager when 'linux' @setState(UnsupportedState) - isDisabled: -> - @disabled - emitUpdateAvailableEvent: (windows...) -> return unless @releaseVersion? for atomWindow in windows @@ -74,10 +79,18 @@ class AutoUpdateManager @state scheduleUpdateCheck: -> - checkForUpdates = => @check(hidePopups: true) - fourHours = 1000 * 60 * 60 * 4 - setInterval(checkForUpdates, fourHours) - checkForUpdates() + # Only schedule update check periodically if running in release version and + # and there is no existing scheduled update check. + unless /\w{7}/.test(@version) or @checkForUpdatesIntervalID + checkForUpdates = => @check(hidePopups: true) + fourHours = 1000 * 60 * 60 * 4 + @checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours) + checkForUpdates() + + cancelScheduledUpdateCheck: -> + if @checkForUpdatesIntervalID + clearInterval(@checkForUpdatesIntervalID) + @checkForUpdatesIntervalID = null check: ({hidePopups}={}) -> unless hidePopups diff --git a/src/config-schema.coffee b/src/config-schema.coffee index d9c0c1d21..88e00c71d 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -104,6 +104,10 @@ module.exports = description: 'Automatically open an empty editor on startup.' type: 'boolean' default: true + automaticallyUpdate: + description: 'Automatically update Atom when a new release is available.' + type: 'boolean' + default: true editor: type: 'object' diff --git a/src/config.coffee b/src/config.coffee index 2e4387732..2a883a57d 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -779,9 +779,14 @@ class Config loadUserConfig: -> return if @shouldNotAccessFileSystem() - unless fs.existsSync(@configFilePath) - fs.makeTreeSync(path.dirname(@configFilePath)) - CSON.writeFileSync(@configFilePath, {}) + try + unless fs.existsSync(@configFilePath) + fs.makeTreeSync(path.dirname(@configFilePath)) + CSON.writeFileSync(@configFilePath, {}) + catch error + @configFileHasErrors = true + @notifyFailure("Failed to initialize `#{path.basename(@configFilePath)}`", error.stack) + return try unless @savePending @@ -820,7 +825,7 @@ class Config @watchSubscription = null notifyFailure: (errorMessage, detail) -> - @notificationManager.addError(errorMessage, {detail, dismissable: true}) + @notificationManager?.addError(errorMessage, {detail, dismissable: true}) save: -> return if @shouldNotAccessFileSystem() diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index f5a7bd853..8b95656f9 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -812,6 +812,7 @@ class DisplayBuffer extends Model decorationsState decorateMarker: (marker, decorationParams) -> + throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed() marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id) decoration = new Decoration(marker, this, decorationParams) @decorationsByMarkerId[marker.id] ?= [] diff --git a/src/git-repository.coffee b/src/git-repository.coffee index 1663f9ad4..ee27f87a5 100644 --- a/src/git-repository.coffee +++ b/src/git-repository.coffee @@ -463,8 +463,12 @@ class GitRepository refreshStatus: -> @handlerPath ?= require.resolve('./repository-status-handler') + relativeProjectPaths = @project?.getPaths() + .map (path) => @relativize(path) + .filter (path) -> path.length > 0 + @statusTask?.terminate() - @statusTask = Task.once @handlerPath, @getPath(), ({statuses, upstream, branch, submodules}) => + @statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) => statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch) and diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 88a836c60..2373463af 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -77,10 +77,7 @@ class LinesYardstick else Point(row, column) - pixelPositionForScreenPosition: (screenPosition, clip=true) -> - screenPosition = Point.fromObject(screenPosition) - screenPosition = @model.clipScreenPosition(screenPosition) if clip - + pixelPositionForScreenPosition: (screenPosition) -> targetRow = screenPosition.row targetColumn = screenPosition.column diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 6772178af..1ecdc5448 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -199,7 +199,10 @@ class PackageManager # Returns the {Package} that was disabled or null if it isn't loaded. disablePackage: (name) -> pack = @loadPackage(name) - pack?.disable() + + unless @isPackageDisabled(name) + pack?.disable() + pack # Public: Is the package with the given name disabled? diff --git a/src/pane.coffee b/src/pane.coffee index 412fc5251..a9c4d45e2 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -337,13 +337,19 @@ class Pane extends Model # # * `index` {Number} activateItemAtIndex: (index) -> - @activateItem(@itemAtIndex(index)) + item = @itemAtIndex(index) or @getActiveItem() + @setActiveItem(item) # Public: Make the given item *active*, causing it to be displayed by # the pane's view. activateItem: (item) -> if item? - @addItem(item, @getActiveItemIndex() + 1, false) + if @activeItem?.isPending?() + index = @getActiveItemIndex() + @destroyActiveItem() unless item is @activeItem + else + index = @getActiveItemIndex() + 1 + @addItem(item, index, false) @setActiveItem(item) # Public: Add the given item to the pane. @@ -574,7 +580,6 @@ class Pane extends Model # Public: Makes this pane the *active* pane, causing it to gain focus. activate: -> throw new Error("Pane has been destroyed") if @isDestroyed() - @container?.setActivePane(this) @emitter.emit 'did-activate' @@ -721,30 +726,28 @@ class Pane extends Model message = "#{message} '#{itemPath}'" if itemPath @notificationManager.addWarning(message, options) - if error.code is 'EISDIR' or error.message?.endsWith?('is a directory') + customMessage = @getMessageForErrorCode(error.code) + if customMessage? + addWarningWithPath("Unable to save file: #{customMessage}") + else if error.code is 'EISDIR' or error.message?.endsWith?('is a directory') @notificationManager.addWarning("Unable to save file: #{error.message}") - else if error.code is 'EACCES' - addWarningWithPath('Unable to save file: Permission denied') else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN'] addWarningWithPath('Unable to save file', detail: error.message) - else if error.code is 'EROFS' - addWarningWithPath('Unable to save file: Read-only file system') - else if error.code is 'ENOSPC' - addWarningWithPath('Unable to save file: No space left on device') - else if error.code is 'ENXIO' - addWarningWithPath('Unable to save file: No such device or address') - else if error.code is 'ENOTSUP' - addWarningWithPath('Unable to save file: Operation not supported on socket') - else if error.code is 'EIO' - addWarningWithPath('Unable to save file: I/O error writing file') - else if error.code is 'EINTR' - addWarningWithPath('Unable to save file: Interrupted system call') - else if error.code is 'ECONNRESET' - addWarningWithPath('Unable to save file: Connection reset') - else if error.code is 'ESPIPE' - addWarningWithPath('Unable to save file: Invalid seek') else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message) fileName = errorMatch[1] @notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to") else throw error + + getMessageForErrorCode: (errorCode) -> + switch errorCode + when 'EACCES' then 'Permission denied' + when 'ECONNRESET' then 'Connection reset' + when 'EINTR' then 'Interrupted system call' + when 'EIO' then 'I/O error writing file' + when 'ENOSPC' then 'No space left on device' + when 'ENOTSUP' then 'Operation not supported on socket' + when 'ENXIO' then 'No such device or address' + when 'EROFS' then 'Read-only file system' + when 'ESPIPE' then 'Invalid seek' + when 'ETIMEDOUT' then 'Connection timed out' diff --git a/src/project.coffee b/src/project.coffee index d59c041cb..714511f9a 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -288,7 +288,7 @@ class Project extends Model 'atom.repository-provider', '^0.1.0', (provider) => - @repositoryProviders.push(provider) + @repositoryProviders.unshift(provider) @setPaths(@getPaths()) if null in @repositories new Disposable => @repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 159ea1abc..b4649f70e 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -169,7 +169,8 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7) 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8) 'editor:log-cursor-scope': -> @logCursorScope() - 'editor:copy-path': -> @copyPathToClipboard() + 'editor:copy-path': -> @copyPathToClipboard(false) + 'editor:copy-project-path': -> @copyPathToClipboard(true) 'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide')) 'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers')) 'editor:scroll-to-cursor': -> @scrollToCursorPosition() diff --git a/src/repository-status-handler.coffee b/src/repository-status-handler.coffee index d0763fd5a..2fda9a335 100644 --- a/src/repository-status-handler.coffee +++ b/src/repository-status-handler.coffee @@ -1,7 +1,7 @@ Git = require 'git-utils' path = require 'path' -module.exports = (repoPath) -> +module.exports = (repoPath, paths = []) -> repo = Git.open(repoPath) upstream = {} @@ -12,7 +12,8 @@ module.exports = (repoPath) -> if repo? # Statuses in main repo workingDirectoryPath = repo.getWorkingDirectory() - for filePath, status of repo.getStatus() + repoStatus = (if paths.length > 0 then repo.getStatusForPaths(paths) else repo.getStatus()) + for filePath, status of repoStatus statuses[filePath] = status # Statuses in submodules diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 1fcb21123..1576de4bf 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -445,12 +445,17 @@ class TextEditorComponent getVisibleRowRange: -> @presenter.getVisibleRowRange() - pixelPositionForScreenPosition: (screenPosition, clip) -> + pixelPositionForScreenPosition: (screenPosition, clip=true) -> + screenPosition = Point.fromObject(screenPosition) + screenPosition = @editor.clipScreenPosition(screenPosition) if clip + unless @presenter.isRowVisible(screenPosition.row) @presenter.setScreenRowsToMeasure([screenPosition.row]) + + unless @linesComponent.lineNodeForLineIdAndScreenRow(@presenter.lineIdForScreenRow(screenPosition.row), screenPosition.row)? @updateSyncPreMeasurement() - pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip) + pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition) @presenter.clearScreenRowsToMeasure() pixelPosition diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index aad660d57..b13bf9036 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -459,7 +459,7 @@ class TextEditorPresenter else screenPosition = decoration.getMarker().getHeadScreenPosition() - pixelPosition = @pixelPositionForScreenPosition(screenPosition, true) + pixelPosition = @pixelPositionForScreenPosition(screenPosition) top = pixelPosition.top + @lineHeight left = pixelPosition.left + @gutterWidth @@ -681,8 +681,10 @@ class TextEditorPresenter updateHorizontalDimensions: -> if @baseCharacterWidth? oldContentWidth = @contentWidth - clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped() - @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left + rightmostPosition = Point(@model.getLongestScreenRow(), @model.getMaxScreenLineLength()) + if @model.tokenizedLineForScreenRow(rightmostPosition.row)?.isSoftWrapped() + rightmostPosition = @model.clipScreenPosition(rightmostPosition) + @contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left @contentWidth += @scrollLeft @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width @@ -1007,9 +1009,9 @@ class TextEditorPresenter hasPixelPositionRequirements: -> @lineHeight? and @baseCharacterWidth? - pixelPositionForScreenPosition: (screenPosition, clip=true) -> + pixelPositionForScreenPosition: (screenPosition) -> position = - @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip, true) + @linesYardstick.pixelPositionForScreenPosition(screenPosition) position.top -= @getScrollTop() position.left -= @getScrollLeft() @@ -1028,14 +1030,14 @@ class TextEditorPresenter lineHeight = @model.getLineHeightInPixels() if screenRange.end.row > screenRange.start.row - top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, true).top + top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start).top left = 0 height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight width = @getScrollWidth() else - {top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, false) + {top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start) height = lineHeight - width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end, false).left - left + width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end).left - left {top, left, width, height} @@ -1217,8 +1219,8 @@ class TextEditorPresenter buildHighlightRegions: (screenRange) -> lineHeightInPixels = @lineHeight - startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, false) - endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, false) + startPixelPosition = @pixelPositionForScreenPosition(screenRange.start) + endPixelPosition = @pixelPositionForScreenPosition(screenRange.end) spannedRows = screenRange.end.row - screenRange.start.row + 1 regions = [] @@ -1520,3 +1522,6 @@ class TextEditorPresenter isRowVisible: (row) -> @startRow <= row < @endRow + + lineIdForScreenRow: (screenRow) -> + @model.tokenizedLineForScreenRow(screenRow)?.id diff --git a/src/text-editor.coffee b/src/text-editor.coffee index a3cb1c812..e132b5500 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -92,7 +92,7 @@ class TextEditor extends Model softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry, - @project, @assert, @applicationDelegate + @project, @assert, @applicationDelegate, @pending } = params throw new Error("Must pass a config parameter when constructing TextEditors") unless @config? @@ -161,6 +161,9 @@ class TextEditor extends Model @disposables.add @buffer.onDidChangeEncoding => @emitter.emit 'did-change-encoding', @getEncoding() @disposables.add @buffer.onDidDestroy => @destroy() + if @pending + @disposables.add @buffer.onDidChangeModified => + @terminatePendingState() if @buffer.isModified() @preserveCursorPositionOnBufferReload() @@ -569,6 +572,13 @@ class TextEditor extends Model getEditorWidthInChars: -> @displayBuffer.getEditorWidthInChars() + onDidTerminatePendingState: (callback) -> + @emitter.on 'did-terminate-pending-state', callback + + terminatePendingState: -> + @pending = false + @emitter.emit 'did-terminate-pending-state', this + ### Section: File Details ### @@ -652,9 +662,13 @@ class TextEditor extends Model # Essential: Returns {Boolean} `true` if this editor has no content. isEmpty: -> @buffer.isEmpty() + # Returns {Boolean} `true` if this editor is pending and `false` if it is permanent. + isPending: -> Boolean(@pending) + # Copies the current file path to the native clipboard. - copyPathToClipboard: -> + copyPathToClipboard: (relative = false) -> if filePath = @getPath() + filePath = atom.project.relativize(filePath) if relative @clipboard.write(filePath) ### @@ -3043,7 +3057,7 @@ class TextEditor extends Model # Essential: Scrolls the editor to the given screen position. # - # * `screenPosition` An object that represents a buffer position. It can be either + # * `screenPosition` An object that represents a screen position. It can be either # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} # * `options` (optional) {Object} # * `center` Center the editor around the position if possible. (default: false) diff --git a/src/workspace.coffee b/src/workspace.coffee index f64f58ee0..07e69cdcf 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -475,7 +475,7 @@ class Workspace extends Model when 'EACCES' @notificationManager.addWarning("Permission denied '#{error.path}'") return Promise.resolve() - when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR' + when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR', 'EAGAIN' @notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message) return Promise.resolve() else