diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 466355903..b4519bcf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -406,10 +406,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and | Label name | `atom/atom` :mag_right: | `atom`‑org :mag_right: | Description | | --- | --- | --- | --- | -| `in-progress` | [search][search-atom-repo-label-in-progress] | [search][search-atom-org-label-in-progress] | Tasks which the Atom core team is working on currently. | -| `on-deck` | [search][search-atom-repo-label-on-deck] | [search][search-atom-org-label-on-deck] | Tasks which the Atom core team plans to work on next. | -| `shipping` | [search][search-atom-repo-label-shipping] | [search][search-atom-org-label-shipping] | Tasks which the Atom core team completed and will be released in one of the next releases. | -| `post-1.0-roadmap` | [search][search-atom-repo-label-post-1.0-roadmap] | [search][search-atom-org-label-post-1.0-roadmap] | The Atom core team's roadmap post version 1.0.0. | | `atom` | [search][search-atom-repo-label-atom] | [search][search-atom-org-label-atom] | Topics discussed for prioritization at the next meeting of Atom core team members. | #### Pull Request Labels @@ -498,14 +494,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and [search-atom-org-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Adeprecation-help [search-atom-repo-label-electron]: https://github.com/issues?q=is%3Aissue+repo%3Aatom%2Fatom+is%3Aopen+label%3Aelectron [search-atom-org-label-electron]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aelectron -[search-atom-repo-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aon-deck -[search-atom-org-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aon-deck -[search-atom-repo-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ain-progress -[search-atom-org-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Ain-progress -[search-atom-repo-label-shipping]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ashipping -[search-atom-org-label-shipping]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Ashipping -[search-atom-repo-label-post-1.0-roadmap]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Apost-1.0-roadmap -[search-atom-org-label-post-1.0-roadmap]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Apost-1.0-roadmap [search-atom-repo-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aatom [search-atom-org-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aatom [search-atom-repo-label-work-in-progress]: https://github.com/pulls?q=is%3Aopen+is%3Apr+repo%3Aatom%2Fatom+label%3Awork-in-progress diff --git a/build/package.json b/build/package.json index 6c41d4355..2f8d88d8a 100644 --- a/build/package.json +++ b/build/package.json @@ -25,7 +25,7 @@ "grunt-contrib-less": "~0.8.0", "grunt-cson": "0.16.0", "grunt-download-electron": "^2.1.1", - "grunt-electron-installer": "1.0.6", + "grunt-electron-installer": "1.2.2", "grunt-lesslint": "0.17.0", "grunt-peg": "~1.1.0", "grunt-shell": "~0.3.1", diff --git a/build/tasks/spec-task.coffee b/build/tasks/spec-task.coffee index e3fa105ac..40b7ad5ce 100644 --- a/build/tasks/spec-task.coffee +++ b/build/tasks/spec-task.coffee @@ -1,5 +1,6 @@ fs = require 'fs' path = require 'path' +temp = require('temp').track() _ = require 'underscore-plus' async = require 'async' @@ -94,7 +95,7 @@ module.exports = (grunt) -> if process.platform in ['darwin', 'linux'] options = cmd: appPath - args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath] + args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath, "--user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"] opts: env: _.extend({}, process.env, ATOM_INTEGRATION_TESTS_ENABLED: true diff --git a/package.json b/package.json index f5097344e..4adfb85f5 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/atom/atom/issues" }, "license": "MIT", - "electronVersion": "0.34.5", + "electronVersion": "0.36.7", "dependencies": { "async": "0.2.6", "atom-keymap": "^6.2.0", @@ -54,7 +54,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.3.0", + "text-buffer": "8.3.1", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" @@ -173,7 +173,8 @@ "runs", "spyOn", "waitsFor", - "waitsForPromise" + "waitsForPromise", + "indexedDB" ] } } diff --git a/script/clean b/script/clean index fd0aa5bfa..0c947baf2 100755 --- a/script/clean +++ b/script/clean @@ -4,15 +4,16 @@ var fs = require('fs'); var path = require('path'); var os = require('os'); -var removeCommand = process.platform === 'win32' ? 'rmdir /S /Q ' : 'rm -rf '; +var isWindows = process.platform === 'win32'; +var removeCommand = isWindows ? 'rmdir /S /Q ' : 'rm -rf '; var productName = require('../package.json').productName; process.chdir(path.dirname(__dirname)); -var home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; +var home = process.env[isWindows ? 'USERPROFILE' : 'HOME']; var tmpdir = os.tmpdir(); // Windows: Use START as a way to ignore error if Atom.exe isnt running -var killatom = process.platform === 'win32' ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true'; +var killatom = isWindows ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true'; var commands = [ killatom, @@ -39,12 +40,32 @@ var run = function() { if (Array.isArray(next)) { var pathToRemove = path.resolve.apply(path.resolve, next); - if (fs.existsSync(pathToRemove)) - next = removeCommand + pathToRemove; - else + if (fs.existsSync(pathToRemove)) { + if (isWindows) { + removeFolderRecursive(pathToRemove); + } else { + next = removeCommand + pathToRemove; + cp.safeExec(next, run); + } + } + else { return run(); + } } - - cp.safeExec(next, run); + else + cp.safeExec(next, run); }; run(); + +// Windows has a 260-char path limit for rmdir etc. Just recursively delete in Node. +var removeFolderRecursive = function(folderPath) { + fs.readdirSync(folderPath).forEach(function(entry, index) { + var entryPath = path.join(folderPath, entry); + if (fs.lstatSync(entryPath).isDirectory()) { + removeFolderRecursive(entryPath); + } else { + fs.unlinkSync(entryPath); + } + }); + fs.rmdirSync(folderPath); +}; diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee index b5b975112..561ecf3b5 100644 --- a/spec/atom-environment-spec.coffee +++ b/spec/atom-environment-spec.coffee @@ -152,6 +152,8 @@ describe "AtomEnvironment", -> atom.enablePersistence = false it "selects the state based on the current project paths", -> + jasmine.useRealClock() + [dir1, dir2] = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")] loadSettings = _.extend atom.getLoadSettings(), @@ -159,20 +161,40 @@ describe "AtomEnvironment", -> windowState: null spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings - spyOn(atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-")) + spyOn(atom, 'serialize').andReturn({stuff: 'cool'}) + spyOn(atom, 'deserialize') - atom.state.stuff = "cool" atom.project.setPaths([dir1, dir2]) - atom.saveStateSync() + # State persistence will fail if other Atom instances are running + waitsForPromise -> + atom.stateStore.connect().then (isConnected) -> + expect(isConnected).toBe true - atom.state = {} - atom.loadStateSync() - expect(atom.state.stuff).toBeUndefined() + waitsForPromise -> + atom.saveState().then -> + atom.loadState() - loadSettings.initialPaths = [dir2, dir1] - atom.state = {} - atom.loadStateSync() - expect(atom.state.stuff).toBe("cool") + runs -> + expect(atom.deserialize).not.toHaveBeenCalled() + + waitsForPromise -> + loadSettings.initialPaths = [dir2, dir1] + atom.loadState() + runs -> + expect(atom.deserialize).toHaveBeenCalledWith({stuff: 'cool'}) + + it "saves state on keydown and mousedown events", -> + spyOn(atom, 'saveState') + + keydown = new KeyboardEvent('keydown') + atom.document.dispatchEvent(keydown) + advanceClock atom.saveStateDebounceInterval + expect(atom.saveState).toHaveBeenCalled() + + mousedown = new MouseEvent('mousedown') + atom.document.dispatchEvent(mousedown) + advanceClock atom.saveStateDebounceInterval + expect(atom.saveState).toHaveBeenCalled() describe "openInitialEmptyEditorIfNecessary", -> describe "when there are no paths set", -> @@ -230,23 +252,6 @@ describe "AtomEnvironment", -> atomEnvironment.destroy() - it "saves the serialized state of the window so it can be deserialized after reload", -> - atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document}) - spyOn(atomEnvironment, 'saveStateSync') - - workspaceState = atomEnvironment.workspace.serialize() - grammarsState = {grammarOverridesByPath: atomEnvironment.grammars.grammarOverridesByPath} - projectState = atomEnvironment.project.serialize() - - atomEnvironment.unloadEditorWindow() - - expect(atomEnvironment.state.workspace).toEqual workspaceState - expect(atomEnvironment.state.grammars).toEqual grammarsState - expect(atomEnvironment.state.project).toEqual projectState - expect(atomEnvironment.saveStateSync).toHaveBeenCalled() - - atomEnvironment.destroy() - describe "::destroy()", -> it "does not throw exceptions when unsubscribing from ipc events (regression)", -> configDirPath = temp.mkdirSync() diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index 150eb9a4a..dcda4633b 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -172,7 +172,7 @@ class AtomReporter listen document, 'click', '.stack-trace', (event) -> event.currentTarget.classList.toggle('expanded') - @reloadButton.addEventListener('click', -> require('ipc').send('call-window-method', 'restart')) + @reloadButton.addEventListener('click', -> require('electron').ipcRenderer.send('call-window-method', 'restart')) updateSpecCounts: -> if @skippedCount diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee index 3c1016ad2..2939dd6ab 100644 --- a/spec/integration/helpers/start-atom.coffee +++ b/spec/integration/helpers/start-atom.coffee @@ -15,6 +15,8 @@ ChromedriverPort = 9515 ChromedriverURLBase = "/wd/hub" ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status" +userDataDir = temp.mkdirSync('atom-user-data-dir') + chromeDriverUp = (done) -> checkStatus = -> http @@ -48,7 +50,7 @@ buildAtomClient = (args, env) -> "atom-env=#{map(env, (value, key) -> "#{key}=#{value}").join(" ")}" "dev" "safe" - "user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}" + "user-data-dir=#{userDataDir}" "socket-path=#{SocketPath}" ]) @@ -124,7 +126,7 @@ buildAtomClient = (args, env) -> .addCommand "simulateQuit", (done) -> @execute -> atom.unloadEditorWindow() - .execute -> require("remote").require("app").emit("before-quit") + .execute -> require("electron").remote.app.emit("before-quit") .call(done) module.exports = (args, env, fn) -> diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 6e8a7f55a..e82d34e57 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -28,13 +28,12 @@ describe "Starting Atom", -> it "opens the parent directory and creates an empty text editor", -> runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> client - .waitForPaneItemCount(1, 1000) - .treeViewRootDirectories() .then ({value}) -> expect(value).toEqual([tempDirPath]) .waitForExist("atom-text-editor", 5000) .then (exists) -> expect(exists).toBe true + .waitForPaneItemCount(1, 1000) .click("atom-text-editor") .keys("Hello!") .execute -> atom.workspace.getActiveTextEditor().getText() @@ -153,6 +152,8 @@ describe "Starting Atom", -> .waitForPaneItemCount(0, 3000) .execute -> atom.workspace.open() .waitForPaneItemCount(1, 3000) + .keys("Hello!") + .waitUntil((-> Promise.resolve(false)), 1100) runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) -> client diff --git a/spec/jasmine-test-runner.coffee b/spec/jasmine-test-runner.coffee index 62b42d0a9..5b5d4e225 100644 --- a/spec/jasmine-test-runner.coffee +++ b/spec/jasmine-test-runner.coffee @@ -1,7 +1,7 @@ _ = require 'underscore-plus' fs = require 'fs-plus' path = require 'path' -ipc = require 'ipc' +{ipcRenderer} = require 'electron' module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) -> window[key] = value for key, value of require '../vendor/jasmine' @@ -88,7 +88,7 @@ buildTerminalReporter = (logFile, resolveWithExitCode) -> if logStream? fs.writeSync(logStream, str) else - ipc.send 'write-to-stderr', str + ipcRenderer.send 'write-to-stderr', str {TerminalReporter} = require 'jasmine-tagged' new TerminalReporter diff --git a/spec/language-mode-spec.coffee b/spec/language-mode-spec.coffee index 7ea4a1ae9..cd32e29c7 100644 --- a/spec/language-mode-spec.coffee +++ b/spec/language-mode-spec.coffee @@ -463,17 +463,6 @@ describe "LanguageMode", -> fold2 = editor.tokenizedLineForScreenRow(5).fold expect(fold2).toBeFalsy() - describe ".isFoldableAtBufferRow(bufferRow)", -> - it "returns true if the line starts a multi-line comment", -> - expect(languageMode.isFoldableAtBufferRow(1)).toBe true - expect(languageMode.isFoldableAtBufferRow(6)).toBe true - expect(languageMode.isFoldableAtBufferRow(17)).toBe false - - it "does not return true for a line in the middle of a comment that's followed by an indented line", -> - expect(languageMode.isFoldableAtBufferRow(7)).toBe false - editor.buffer.insert([8, 0], ' ') - expect(languageMode.isFoldableAtBufferRow(7)).toBe false - describe "css", -> beforeEach -> waitsForPromise -> diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 5c10ed2fc..0a2d614b3 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -446,16 +446,15 @@ describe "PackageManager", -> pack = null waitsForPromise -> atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p - runs -> expect(pack.mainModule.someNumber).not.toBe 77 pack.mainModule.someNumber = 77 atom.packages.deactivatePackage("package-with-serialization") spyOn(pack.mainModule, 'activate').andCallThrough() - waitsForPromise -> - atom.packages.activatePackage("package-with-serialization") - runs -> - expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) + waitsForPromise -> + atom.packages.activatePackage("package-with-serialization") + runs -> + expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) it "invokes ::onDidActivatePackage listeners with the activated package", -> activatedPackage = null @@ -819,6 +818,34 @@ describe "PackageManager", -> expect(atom.packages.isPackageActive("package-with-missing-provided-services")).toBe true expect(addErrorHandler.callCount).toBe 0 + describe "::serialize", -> + it "does not serialize packages that threw an error during activation", -> + spyOn(console, 'warn') + badPack = null + waitsForPromise -> + atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p + + runs -> + spyOn(badPack.mainModule, 'serialize').andCallThrough() + + atom.packages.serialize() + expect(badPack.mainModule.serialize).not.toHaveBeenCalled() + + it "absorbs exceptions that are thrown by the package module's serialize method", -> + spyOn(console, 'error') + + waitsForPromise -> + atom.packages.activatePackage('package-with-serialize-error') + + waitsForPromise -> + atom.packages.activatePackage('package-with-serialization') + + runs -> + atom.packages.serialize() + expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined() + expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1 + expect(console.error).toHaveBeenCalled() + describe "::deactivatePackage(id)", -> afterEach -> atom.packages.unloadPackages() @@ -850,33 +877,6 @@ describe "PackageManager", -> expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy() - it "does not serialize packages that have not been activated called on their main module", -> - spyOn(console, 'warn') - badPack = null - waitsForPromise -> - atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p - - runs -> - spyOn(badPack.mainModule, 'serialize').andCallThrough() - - atom.packages.deactivatePackage("package-that-throws-on-activate") - expect(badPack.mainModule.serialize).not.toHaveBeenCalled() - - it "absorbs exceptions that are thrown by the package module's serialize method", -> - spyOn(console, 'error') - - waitsForPromise -> - atom.packages.activatePackage('package-with-serialize-error') - - waitsForPromise -> - atom.packages.activatePackage('package-with-serialization') - - runs -> - atom.packages.deactivatePackages() - expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined() - expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1 - expect(console.error).toHaveBeenCalled() - it "absorbs exceptions that are thrown by the package module's deactivate method", -> spyOn(console, 'error') diff --git a/spec/state-store-spec.js b/spec/state-store-spec.js new file mode 100644 index 000000000..95fdcb71b --- /dev/null +++ b/spec/state-store-spec.js @@ -0,0 +1,61 @@ +/** @babel */ +import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers' + +const StateStore = require('../src/state-store.js') + +describe("StateStore", () => { + let databaseName = `test-database-${Date.now()}` + let version = 1 + + it("can save and load states", () => { + const store = new StateStore(databaseName, version) + return store.save('key', {foo:'bar'}) + .then(() => store.load('key')) + .then((state) => { + expect(state).toEqual({foo:'bar'}) + }) + }) + + it("resolves with null when a non-existent key is loaded", () => { + const store = new StateStore(databaseName, version) + return store.load('no-such-key').then((value) => { + expect(value).toBeNull() + }) + }) + + it("can clear the state object store", () => { + const store = new StateStore(databaseName, version) + return store.save('key', {foo:'bar'}) + .then(() => store.count()) + .then((count) => + expect(count).toBe(1) + ) + .then(() => store.clear()) + .then(() => store.count()) + .then((count) => { + expect(count).toBe(0) + }) + }) + + describe("when there is an error reading from the database", () => { + it("rejects the promise returned by load", () => { + const store = new StateStore(databaseName, version) + + const fakeErrorEvent = {target: {errorCode: "Something bad happened"}} + + spyOn(IDBObjectStore.prototype, 'get').andCallFake((key) => { + let request = {} + process.nextTick(() => request.onerror(fakeErrorEvent)) + return request + }) + + return store.load('nonexistentKey') + .then(() => { + throw new Error("Promise should have been rejected") + }) + .catch((event) => { + expect(event).toBe(fakeErrorEvent) + }) + }) + }) +}) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 9663c6222..08da07dd8 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -2151,10 +2151,11 @@ describe('TextEditorComponent', function () { item.style.height = itemHeight + 'px' wrapperNode.style.width = windowWidth + 'px' wrapperNode.style.height = windowHeight + 'px' - atom.setWindowDimensions({ + await atom.setWindowDimensions({ width: windowWidth, height: windowHeight }) + component.measureDimensions() component.measureWindowSize() await nextViewUpdatePromise() @@ -4834,7 +4835,7 @@ describe('TextEditorComponent', function () { it('pastes the previously selected text at the clicked location', async function () { let clipboardWrittenTo = false - spyOn(require('ipc'), 'send').andCallFake(function (eventName, selectedText) { + spyOn(require('electron').ipcRenderer, 'send').andCallFake(function (eventName, selectedText) { if (eventName === 'write-text-to-selection-clipboard') { require('../src/safe-clipboard').writeText(selectedText, 'selection') clipboardWrittenTo = true diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 72b292cc2..88c095f68 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -202,8 +202,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.firstInvalidRow()).toBe 3 advanceClock() - # we discover that row 2 starts a foldable region when line 3 gets tokenized - expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0) + expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0) expect(tokenizedBuffer.firstInvalidRow()).toBe 8 describe "when there is a buffer change surrounding an invalid row", -> @@ -253,7 +252,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 1, end: 2, delta: 0) + expect(event).toEqual(start: 2, end: 2, delta: 0) changeHandler.reset() advanceClock() @@ -263,8 +262,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - # we discover that row 2 starts a foldable region when line 3 gets tokenized - expect(event).toEqual(start: 2, end: 5, delta: 0) + expect(event).toEqual(start: 3, end: 5, delta: 0) it "resumes highlighting with the state of the previous line", -> buffer.insert([0, 0], '/*') @@ -292,7 +290,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false + expect(event).toEqual(start: 1, end: 3, delta: -2) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> @@ -305,7 +303,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 1, end: 3, delta: -1) + expect(event).toEqual(start: 2, end: 3, delta: -1) changeHandler.reset() advanceClock() @@ -314,8 +312,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - # we discover that row 2 starts a foldable region when line 3 gets tokenized - expect(event).toEqual(start: 2, end: 4, delta: 0) + expect(event).toEqual(start: 3, end: 4, delta: 0) describe "when lines are both updated and inserted", -> it "updates tokens to reflect the change", -> @@ -339,7 +336,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0 + expect(event).toEqual(start: 1, end: 2, delta: 2) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> @@ -350,7 +347,7 @@ describe "TokenizedBuffer", -> expect(changeHandler).toHaveBeenCalled() [event] = changeHandler.argsForCall[0] delete event.bufferChange - expect(event).toEqual(start: 1, end: 2, delta: 2) + expect(event).toEqual(start: 2, end: 2, delta: 2) expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] @@ -894,7 +891,7 @@ describe "TokenizedBuffer", -> buffer.setTextInRange([[7, 0], [8, 65]], ' ok') delete changeHandler.argsForCall[0][0].bufferChange - expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable + expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1) expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2 expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2 @@ -903,7 +900,7 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2 expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # } - describe ".foldable on tokenized lines", -> + describe "::isFoldableAtRow(row)", -> changes = null beforeEach -> @@ -915,74 +912,66 @@ describe "TokenizedBuffer", -> buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert }) fullyTokenize(tokenizedBuffer) - tokenizedBuffer.onDidChange (change) -> - delete change.bufferChange - changes.push(change) - it "sets .foldable to true on the first line of multi-line comments", -> - expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent - expect(tokenizedBuffer.tokenizedLineForRow(13).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(14).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(15).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(16).foldable).toBe false + it "includes the first line of multi-line comments", -> + expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent + expect(tokenizedBuffer.isFoldableAtRow(13)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(14)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(15)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(16)).toBe false buffer.insert([0, Infinity], '\n') - expect(changes).toEqual [{start: 0, end: 1, delta: 1}] - expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false + expect(tokenizedBuffer.isFoldableAtRow(0)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe false - changes = [] buffer.undo() - expect(changes).toEqual [{start: 0, end: 2, delta: -1}] - expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent - it "sets .foldable to true on non-comment lines that precede an increase in indentation", -> + expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent + + it "includes non-comment lines that precede an increase in indentation", -> buffer.insert([2, 0], ' ') # commented lines preceding an indent aren't foldable - expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(4).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(5).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false - changes = [] + expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(4)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(5)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + buffer.insert([7, 0], ' ') - expect(changes).toEqual [{start: 6, end: 7, delta: 0}] - expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false - changes = [] + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + buffer.undo() - expect(changes).toEqual [{start: 6, end: 7, delta: 0}] - expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false - changes = [] + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + buffer.insert([7, 0], " \n x\n") - expect(changes).toEqual [{start: 6, end: 7, delta: 2}] - expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false - changes = [] + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + buffer.insert([9, 0], " ") - expect(changes).toEqual [{start: 9, end: 9, delta: 0}] - expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true - expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false - expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false + + expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true + expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false + expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false describe "when the buffer is configured with the null grammar", -> it "uses the placeholder tokens and does not actually tokenize using the grammar", -> diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee index a988ae7de..bb7e1665b 100644 --- a/spec/window-event-handler-spec.coffee +++ b/spec/window-event-handler-spec.coffee @@ -4,7 +4,7 @@ fs = require 'fs-plus' temp = require 'temp' TextEditor = require '../src/text-editor' WindowEventHandler = require '../src/window-event-handler' -ipc = require 'ipc' +{ipcRenderer} = require 'electron' describe "WindowEventHandler", -> [projectPath, windowEventHandler] = [] @@ -53,7 +53,7 @@ describe "WindowEventHandler", -> describe "beforeunload event", -> beforeEach -> jasmine.unspy(TextEditor.prototype, "shouldPromptToSave") - spyOn(ipc, 'send') + spyOn(ipcRenderer, 'send') describe "when pane items are modified", -> editor = null @@ -65,13 +65,13 @@ describe "WindowEventHandler", -> spyOn(atom.workspace, 'confirmClose').andReturn(true) window.dispatchEvent(new CustomEvent('beforeunload')) expect(atom.workspace.confirmClose).toHaveBeenCalled() - expect(ipc.send).not.toHaveBeenCalledWith('did-cancel-window-unload') + expect(ipcRenderer.send).not.toHaveBeenCalledWith('did-cancel-window-unload') it "cancels the unload if the user selects cancel", -> spyOn(atom.workspace, 'confirmClose').andReturn(false) window.dispatchEvent(new CustomEvent('beforeunload')) expect(atom.workspace.confirmClose).toHaveBeenCalled() - expect(ipc.send).toHaveBeenCalledWith('did-cancel-window-unload') + expect(ipcRenderer.send).toHaveBeenCalledWith('did-cancel-window-unload') describe "when a link is clicked", -> it "opens the http/https links in an external application", -> diff --git a/spec/workspace-element-spec.coffee b/spec/workspace-element-spec.coffee index 883bad2fc..cf773b4ef 100644 --- a/spec/workspace-element-spec.coffee +++ b/spec/workspace-element-spec.coffee @@ -1,4 +1,4 @@ -ipc = require 'ipc' +{ipcRenderer} = require 'electron' path = require 'path' temp = require('temp').track() @@ -127,35 +127,35 @@ describe "WorkspaceElement", -> describe "the 'window:run-package-specs' command", -> it "runs the package specs for the active item's project path, or the first project path", -> workspaceElement = atom.views.getView(atom.workspace) - spyOn(ipc, 'send') + spyOn(ipcRenderer, 'send') # No project paths. Don't try to run specs. atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipc.send).not.toHaveBeenCalledWith("run-package-specs") + expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs") projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")] atom.project.setPaths(projectPaths) # No active item. Use first project directory. atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) - ipc.send.reset() + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) + ipcRenderer.send.reset() # Active item doesn't implement ::getPath(). Use first project directory. item = document.createElement("div") atom.workspace.getActivePane().activateItem(item) atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) - ipc.send.reset() + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) + ipcRenderer.send.reset() # Active item has no path. Use first project directory. item.getPath = -> null atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) - ipc.send.reset() + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) + ipcRenderer.send.reset() # Active item has path. Use project path for item path. item.getPath = -> path.join(projectPaths[1], "a-file.txt") atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec")) - ipc.send.reset() + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec")) + ipcRenderer.send.reset() diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index e89e4c6bd..ef89636a8 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -585,6 +585,22 @@ describe "Workspace", -> open = -> workspace.open('file1', workspace.getActivePane()) expect(open).toThrow() + describe "when the file is already open in pending state", -> + it "should terminate the pending state", -> + editor = null + + waitsForPromise -> + atom.workspace.open('sample.js', pending: true).then (o) -> editor = o + + runs -> + expect(editor.isPending()).toBe true + + waitsForPromise -> + atom.workspace.open('sample.js').then (o) -> editor = o + + runs -> + expect(editor.isPending()).toBe false + describe "::reopenItem()", -> it "opens the uri associated with the last closed pane that isn't currently open", -> pane = workspace.getActivePane() @@ -1532,3 +1548,14 @@ describe "Workspace", -> atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() expect(atom.close).toHaveBeenCalled() + + describe "when the core.allowPendingPaneItems option is falsey", -> + it "does not open item with `pending: true` option as pending", -> + editor = null + atom.config.set('core.allowPendingPaneItems', false) + + waitsForPromise -> + atom.workspace.open('sample.js', pending: true).then (o) -> editor = o + + runs -> + expect(editor.isPending()).toBeFalsy() diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee index 59259d223..f87827886 100644 --- a/src/application-delegate.coffee +++ b/src/application-delegate.coffee @@ -1,69 +1,67 @@ _ = require 'underscore-plus' -ipc = require 'ipc' -remote = require 'remote' -shell = require 'shell' -webFrame = require 'web-frame' +{ipcRenderer, remote, shell, webFrame} = require 'electron' +ipcHelpers = require './ipc-helpers' {Disposable} = require 'event-kit' {getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers' module.exports = class ApplicationDelegate open: (params) -> - ipc.send('open', params) + ipcRenderer.send('open', params) pickFolder: (callback) -> responseChannel = "atom-pick-folder-response" - ipc.on responseChannel, (path) -> - ipc.removeAllListeners(responseChannel) + ipcRenderer.on responseChannel, (event, path) -> + ipcRenderer.removeAllListeners(responseChannel) callback(path) - ipc.send("pick-folder", responseChannel) + ipcRenderer.send("pick-folder", responseChannel) getCurrentWindow: -> remote.getCurrentWindow() closeWindow: -> - ipc.send("call-window-method", "close") + ipcRenderer.send("call-window-method", "close") getWindowSize: -> [width, height] = remote.getCurrentWindow().getSize() {width, height} setWindowSize: (width, height) -> - remote.getCurrentWindow().setSize(width, height) + ipcHelpers.call('set-window-size', width, height) getWindowPosition: -> [x, y] = remote.getCurrentWindow().getPosition() {x, y} setWindowPosition: (x, y) -> - ipc.send("call-window-method", "setPosition", x, y) + ipcHelpers.call('set-window-position', x, y) centerWindow: -> - ipc.send("call-window-method", "center") + ipcHelpers.call('center-window') focusWindow: -> - ipc.send("call-window-method", "focus") + ipcHelpers.call('focus-window') showWindow: -> - ipc.send("call-window-method", "show") + ipcHelpers.call('show-window') hideWindow: -> - ipc.send("call-window-method", "hide") + ipcHelpers.call('hide-window') - restartWindow: -> - ipc.send("call-window-method", "restart") + reloadWindow: -> + ipcRenderer.send("call-window-method", "reload") isWindowMaximized: -> remote.getCurrentWindow().isMaximized() maximizeWindow: -> - ipc.send("call-window-method", "maximize") + ipcRenderer.send("call-window-method", "maximize") isWindowFullScreen: -> remote.getCurrentWindow().isFullScreen() setWindowFullScreen: (fullScreen=false) -> - ipc.send("call-window-method", "setFullScreen", fullScreen) + ipcRenderer.send("call-window-method", "setFullScreen", fullScreen) openWindowDevTools: -> new Promise (resolve) -> @@ -75,7 +73,7 @@ class ApplicationDelegate resolve() else remote.getCurrentWindow().once("devtools-opened", -> resolve()) - ipc.send("call-window-method", "openDevTools") + ipcRenderer.send("call-window-method", "openDevTools") closeWindowDevTools: -> new Promise (resolve) -> @@ -87,7 +85,7 @@ class ApplicationDelegate resolve() else remote.getCurrentWindow().once("devtools-closed", -> resolve()) - ipc.send("call-window-method", "closeDevTools") + ipcRenderer.send("call-window-method", "closeDevTools") toggleWindowDevTools: -> new Promise (resolve) => @@ -101,16 +99,16 @@ class ApplicationDelegate @openWindowDevTools().then(resolve) executeJavaScriptInWindowDevTools: (code) -> - ipc.send("call-window-method", "executeJavaScriptInDevTools", code) + ipcRenderer.send("execute-javascript-in-dev-tools", code) setWindowDocumentEdited: (edited) -> - ipc.send("call-window-method", "setDocumentEdited", edited) + ipcRenderer.send("call-window-method", "setDocumentEdited", edited) setRepresentedFilename: (filename) -> - ipc.send("call-window-method", "setRepresentedFilename", filename) + ipcRenderer.send("call-window-method", "setRepresentedFilename", filename) addRecentDocument: (filename) -> - ipc.send("add-recent-document", filename) + ipcRenderer.send("add-recent-document", filename) setRepresentedDirectoryPaths: (paths) -> loadSettings = getWindowLoadSettings() @@ -118,14 +116,13 @@ class ApplicationDelegate setWindowLoadSettings(loadSettings) setAutoHideWindowMenuBar: (autoHide) -> - ipc.send("call-window-method", "setAutoHideMenuBar", autoHide) + ipcRenderer.send("call-window-method", "setAutoHideMenuBar", autoHide) setWindowMenuBarVisibility: (visible) -> remote.getCurrentWindow().setMenuBarVisibility(visible) getPrimaryDisplayWorkAreaSize: -> - screen = remote.require 'screen' - screen.getPrimaryDisplay().workAreaSize + remote.screen.getPrimaryDisplay().workAreaSize confirm: ({message, detailedMessage, buttons}) -> buttons ?= {} @@ -134,8 +131,7 @@ class ApplicationDelegate else buttonLabels = Object.keys(buttons) - dialog = remote.require('dialog') - chosen = dialog.showMessageBox(remote.getCurrentWindow(), { + chosen = remote.dialog.showMessageBox(remote.getCurrentWindow(), { type: 'info' message: message detail: detailedMessage @@ -157,42 +153,47 @@ class ApplicationDelegate params = _.clone(params) params.title ?= 'Save File' params.defaultPath ?= getWindowLoadSettings().initialPaths[0] - dialog = remote.require('dialog') - dialog.showSaveDialog remote.getCurrentWindow(), params + remote.dialog.showSaveDialog remote.getCurrentWindow(), params playBeepSound: -> shell.beep() onDidOpenLocations: (callback) -> - outerCallback = (message, detail) -> + outerCallback = (event, message, detail) -> if message is 'open-locations' callback(detail) - ipc.on('message', outerCallback) + ipcRenderer.on('message', outerCallback) new Disposable -> - ipc.removeListener('message', outerCallback) + ipcRenderer.removeListener('message', outerCallback) onUpdateAvailable: (callback) -> - outerCallback = (message, detail) -> + outerCallback = (event, message, detail) -> if message is 'update-available' callback(detail) - ipc.on('message', outerCallback) + ipcRenderer.on('message', outerCallback) new Disposable -> - ipc.removeListener('message', outerCallback) + ipcRenderer.removeListener('message', outerCallback) onApplicationMenuCommand: (callback) -> - ipc.on('command', callback) + outerCallback = (event, args...) -> + callback(args...) + + ipcRenderer.on('command', outerCallback) new Disposable -> - ipc.removeListener('command', callback) + ipcRenderer.removeListener('command', outerCallback) onContextMenuCommand: (callback) -> - ipc.on('context-command', callback) + outerCallback = (event, args...) -> + callback(args...) + + ipcRenderer.on('context-command', outerCallback) new Disposable -> - ipc.removeListener('context-command', callback) + ipcRenderer.removeListener('context-command', outerCallback) didCancelWindowUnload: -> - ipc.send('did-cancel-window-unload') + ipcRenderer.send('did-cancel-window-unload') openExternal: (url) -> shell.openExternal(url) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 49fe81f0d..8c2d3e73d 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -1,16 +1,16 @@ crypto = require 'crypto' path = require 'path' -ipc = require 'ipc' +{ipcRenderer} = require 'electron' _ = require 'underscore-plus' {deprecate} = require 'grim' -{CompositeDisposable, Emitter} = require 'event-kit' +{CompositeDisposable, Disposable, Emitter} = require 'event-kit' fs = require 'fs-plus' {mapSourcePosition} = require 'source-map-support' Model = require './model' WindowEventHandler = require './window-event-handler' StylesElement = require './styles-element' -StorageFolder = require './storage-folder' +StateStore = require './state-store' {getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers' registerDefaultCommands = require './register-default-commands' @@ -111,6 +111,8 @@ class AtomEnvironment extends Model # Public: A {Workspace} instance workspace: null + saveStateDebounceInterval: 1000 + ### Section: Construction and Destruction ### @@ -119,14 +121,16 @@ class AtomEnvironment extends Model constructor: (params={}) -> {@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params - @state = {version: @constructor.version} - @loadTime = null - {devMode, safeMode, resourcePath} = @getLoadSettings() + {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() @emitter = new Emitter @disposables = new CompositeDisposable + @stateStore = new StateStore('AtomEnvironments', 1) + + @stateStore.clear() if clearWindowState + @deserializers = new DeserializerManager(this) @deserializeTimings = {} @@ -200,19 +204,28 @@ class AtomEnvironment extends Model @registerDefaultViewProviders() @installUncaughtErrorHandler() + @attachSaveStateListeners() @installWindowEventHandler() @observeAutoHideMenuBar() checkPortableHomeWritable = -> responseChannel = "check-portable-home-writable-response" - ipc.on responseChannel, (response) -> - ipc.removeAllListeners(responseChannel) + ipcRenderer.on responseChannel, (event, response) -> + ipcRenderer.removeAllListeners(responseChannel) atom.notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable - ipc.send('check-portable-home-writable', responseChannel) + ipcRenderer.send('check-portable-home-writable', responseChannel) checkPortableHomeWritable() + attachSaveStateListeners: -> + debouncedSaveState = _.debounce((=> @saveState()), @saveStateDebounceInterval) + @document.addEventListener('mousedown', debouncedSaveState, true) + @document.addEventListener('keydown', debouncedSaveState, true) + @disposables.add new Disposable => + @document.removeEventListener('mousedown', debouncedSaveState, true) + @document.removeEventListener('keydown', debouncedSaveState, true) + setConfigSchema: -> @config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))} @@ -302,9 +315,6 @@ class AtomEnvironment extends Model @views.clear() @registerDefaultViewProviders() - @state.packageStates = {} - delete @state.workspace - destroy: -> return if not @project @@ -497,7 +507,7 @@ class AtomEnvironment extends Model # Extended: Reload the current window. reload: -> - @applicationDelegate.restartWindow() + @applicationDelegate.reloadWindow() # Extended: Returns a {Boolean} that is `true` if the current window is maximized. isMaximized: -> @@ -524,16 +534,18 @@ class AtomEnvironment extends Model # Restore the window to its previous dimensions and show it. # - # Also restores the full screen and maximized state on the next tick to + # Restores the full screen and maximized state after the window has resized to # prevent resize glitches. displayWindow: -> - dimensions = @restoreWindowDimensions() - @show() - @focus() - - setImmediate => - @setFullScreen(true) if @workspace?.fullScreen - @maximize() if dimensions?.maximized and process.platform isnt 'darwin' + @restoreWindowDimensions().then (dimensions) => + steps = [ + @restoreWindowBackground(), + @show(), + @focus() + ] + steps.push(@setFullScreen(true)) if @workspace.fullScreen + steps.push(@maximize()) if dimensions?.maximized and process.platform isnt 'darwin' + Promise.all(steps) if @isFirstLoad() loadSettings = getWindowLoadSettings() @@ -566,12 +578,14 @@ class AtomEnvironment extends Model # * `width` The new width. # * `height` The new height. setWindowDimensions: ({x, y, width, height}) -> + steps = [] if width? and height? - @setSize(width, height) + steps.push(@setSize(width, height)) if x? and y? - @setPosition(x, y) + steps.push(@setPosition(x, y)) else - @center() + steps.push(@center()) + Promise.all(steps) # Returns true if the dimensions are useable, false if they should be ignored. # Work around for https://github.com/atom/atom-shell/issues/473 @@ -607,16 +621,22 @@ class AtomEnvironment extends Model # But after that, e.g., when the window's been reloaded, we want to use the # dimensions we've saved for it. if not @isFirstLoad() - dimensions = @state.windowDimensions + dimensions = @windowDimensions unless @isValidDimensions(dimensions) dimensions = @getDefaultWindowDimensions() - @setWindowDimensions(dimensions) - dimensions + @setWindowDimensions(dimensions).then -> dimensions storeWindowDimensions: -> dimensions = @getWindowDimensions() - @state.windowDimensions = dimensions if @isValidDimensions(dimensions) + @windowDimensions = dimensions if @isValidDimensions(dimensions) + + restoreWindowBackground: -> + if backgroundColor = window.localStorage.getItem('atom:window-background-color') + @backgroundStylesheet = document.createElement('style') + @backgroundStylesheet.type = 'text/css' + @backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + ' !important; }' + document.head.appendChild(@backgroundStylesheet) storeWindowBackground: -> return if @inSpecMode() @@ -642,6 +662,7 @@ class AtomEnvironment extends Model @packages.loadPackages() @document.body.appendChild(@views.getView(@workspace)) + @backgroundStylesheet?.remove() @watchProjectPath() @@ -653,17 +674,20 @@ class AtomEnvironment extends Model @openInitialEmptyEditorIfNecessary() + serialize: -> + version: @constructor.version + project: @project.serialize() + workspace: @workspace.serialize() + packageStates: @packages.serialize() + grammars: {grammarOverridesByPath: @grammars.grammarOverridesByPath} + fullScreen: @isFullScreen() + windowDimensions: @windowDimensions + unloadEditorWindow: -> return if not @project @storeWindowBackground() - @state.grammars = {grammarOverridesByPath: @grammars.grammarOverridesByPath} - @state.project = @project.serialize() - @state.workspace = @workspace.serialize() @packages.deactivatePackages() - @state.packageStates = @packages.packageStates - @state.fullScreen = @isFullScreen() - @saveStateSync() @saveBlobStoreSync() openInitialEmptyEditorIfNecessary: -> @@ -797,45 +821,54 @@ class AtomEnvironment extends Model @blobStore.save() - saveStateSync: -> - return unless @enablePersistence + saveState: -> + return Promise.resolve() unless @enablePersistence + state = @serialize() if storageKey = @getStateKey(@project?.getPaths()) - @getStorageFolder().store(storageKey, @state) + @stateStore.save(storageKey, state) else - @getCurrentWindow().loadSettings.windowState = JSON.stringify(@state) + @getCurrentWindow().loadSettings.windowState = JSON.stringify(state) + Promise.resolve() - loadStateSync: -> - return unless @enablePersistence + loadState: -> + return Promise.resolve() unless @enablePersistence startTime = Date.now() + statePromise = null if stateKey = @getStateKey(@getLoadSettings().initialPaths) - if state = @getStorageFolder().load(stateKey) - @state = state + statePromise = @stateStore.load(stateKey) - if not @state? and windowState = @getLoadSettings().windowState + if not statePromise? and windowState = @getLoadSettings().windowState try - if state = JSON.parse(@getLoadSettings().windowState) - @state = state + statePromise = Promise.resolve(JSON.parse(@getLoadSettings().windowState)) catch error console.warn "Error parsing window state: #{statePath} #{error.stack}", error - @deserializeTimings.atom = Date.now() - startTime + if statePromise? + statePromise.then (state) => + @deserializeTimings.atom = Date.now() - startTime + @deserialize(state) if state? + else + Promise.resolve() - if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath + deserialize: (state) -> + if grammarOverridesByPath = state.grammars?.grammarOverridesByPath @grammars.grammarOverridesByPath = grammarOverridesByPath - @setFullScreen(@state.fullScreen) + @setFullScreen(state.fullScreen) - @packages.packageStates = @state.packageStates ? {} + @windowDimensions = state.windowDimensions if state.windowDimensions + + @packages.packageStates = state.packageStates ? {} startTime = Date.now() - @project.deserialize(@state.project, @deserializers) if @state.project? + @project.deserialize(state.project, @deserializers) if state.project? @deserializeTimings.project = Date.now() - startTime startTime = Date.now() - @workspace.deserialize(@state.workspace, @deserializers) if @state.workspace? + @workspace.deserialize(state.workspace, @deserializers) if state.workspace? @deserializeTimings.workspace = Date.now() - startTime getStateKey: (paths) -> @@ -848,9 +881,6 @@ class AtomEnvironment extends Model getConfigDirPath: -> @configDirPath ?= process.env.ATOM_HOME - getStorageFolder: -> - @storageFolder ?= new StorageFolder(@getConfigDirPath()) - getUserInitScriptPath: -> initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee']) initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee') diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 74da80e43..b0a6e3267 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -1,6 +1,4 @@ -app = require 'app' -ipc = require 'ipc' -Menu = require 'menu' +{app, Menu} = require 'electron' _ = require 'underscore-plus' # Used to manage the global application menu. diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 44848eb72..93dd428ce 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -2,14 +2,10 @@ AtomWindow = require './atom-window' ApplicationMenu = require './application-menu' AtomProtocolHandler = require './atom-protocol-handler' AutoUpdateManager = require './auto-update-manager' -BrowserWindow = require 'browser-window' StorageFolder = require '../storage-folder' -Menu = require 'menu' -app = require 'app' -dialog = require 'dialog' -shell = require 'shell' +ipcHelpers = require '../ipc-helpers' +{BrowserWindow, Menu, app, dialog, ipcMain, shell} = require 'electron' fs = require 'fs-plus' -ipc = require 'ipc' path = require 'path' os = require 'os' net = require 'net' @@ -51,7 +47,7 @@ class AtomApplication client = net.connect {path: options.socketPath}, -> client.write JSON.stringify(options), -> client.end() - app.terminate() + app.quit() client.on 'error', createAtomApplication @@ -65,7 +61,7 @@ class AtomApplication exit: (status) -> app.exit(status) constructor: (options) -> - {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout} = options + {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout, clearWindowState} = options @socketPath = null if options.test @@ -89,16 +85,16 @@ class AtomApplication else @loadState(options) or @openPath(options) - openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout}) -> + openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState}) -> if test @runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout}) else if pathsToOpen.length > 0 - @openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup}) + @openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState}) else if urlsToOpen.length > 0 @openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen else # Always open a editor window if this is the first instance of Atom. - @openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup}) + @openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState}) # Public: Removes the {AtomWindow} from the global window list. removeWindow: (window) -> @@ -212,7 +208,6 @@ class AtomApplication @openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md')) app.on 'before-quit', => - @saveState(false) @quitting = true app.on 'will-quit', => @@ -232,7 +227,7 @@ class AtomApplication @emit('application:new-window') # A request from the associated render process to open a new render process. - ipc.on 'open', (event, options) => + ipcMain.on 'open', (event, options) => window = @windowForEvent(event) if options? if typeof options.pathsToOpen is 'string' @@ -245,44 +240,65 @@ class AtomApplication else @promptForPathToOpen('all', {window}) - ipc.on 'update-application-menu', (event, template, keystrokesByCommand) => + ipcMain.on 'update-application-menu', (event, template, keystrokesByCommand) => win = BrowserWindow.fromWebContents(event.sender) @applicationMenu.update(win, template, keystrokesByCommand) - ipc.on 'run-package-specs', (event, packageSpecPath) => + ipcMain.on 'run-package-specs', (event, packageSpecPath) => @runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false}) - ipc.on 'command', (event, command) => + ipcMain.on 'command', (event, command) => @emit(command) - ipc.on 'window-command', (event, command, args...) -> + ipcMain.on 'window-command', (event, command, args...) -> win = BrowserWindow.fromWebContents(event.sender) win.emit(command, args...) - ipc.on 'call-window-method', (event, method, args...) -> + ipcMain.on 'call-window-method', (event, method, args...) -> win = BrowserWindow.fromWebContents(event.sender) win[method](args...) - ipc.on 'pick-folder', (event, responseChannel) => + ipcMain.on 'pick-folder', (event, responseChannel) => @promptForPath "folder", (selectedPaths) -> event.sender.send(responseChannel, selectedPaths) - ipc.on 'did-cancel-window-unload', => + ipcHelpers.respondTo 'set-window-size', (win, width, height) -> + win.setSize(width, height) + + ipcHelpers.respondTo 'set-window-position', (win, x, y) -> + win.setPosition(x, y) + + ipcHelpers.respondTo 'center-window', (win) -> + win.center() + + ipcHelpers.respondTo 'focus-window', (win) -> + win.focus() + + ipcHelpers.respondTo 'show-window', (win) -> + win.show() + + ipcHelpers.respondTo 'hide-window', (win) -> + win.hide() + + ipcMain.on 'did-cancel-window-unload', => @quitting = false clipboard = require '../safe-clipboard' - ipc.on 'write-text-to-selection-clipboard', (event, selectedText) -> + ipcMain.on 'write-text-to-selection-clipboard', (event, selectedText) -> clipboard.writeText(selectedText, 'selection') - ipc.on 'write-to-stdout', (event, output) -> + ipcMain.on 'write-to-stdout', (event, output) -> process.stdout.write(output) - ipc.on 'write-to-stderr', (event, output) -> + ipcMain.on 'write-to-stderr', (event, output) -> process.stderr.write(output) - ipc.on 'add-recent-document', (event, filename) -> + ipcMain.on 'add-recent-document', (event, filename) -> app.addRecentDocument(filename) + ipcMain.on 'execute-javascript-in-dev-tools', (event, code) -> + event.sender.devToolsWebContents?.executeJavaScript(code) + setupDockMenu: -> if process.platform is 'darwin' dockMenu = Menu.buildFromTemplate [ @@ -350,7 +366,7 @@ class AtomApplication _.find @windows, (atomWindow) -> atomWindow.devMode is devMode and atomWindow.containsPaths(pathsToOpen) - # Returns the {AtomWindow} for the given ipc event. + # Returns the {AtomWindow} for the given ipcMain event. windowForEvent: ({sender}) -> window = BrowserWindow.fromWebContents(sender) _.find @windows, ({browserWindow}) -> window is browserWindow @@ -387,8 +403,8 @@ 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} = {}) -> - @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) + openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState} = {}) -> + @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState}) # Public: Opens multiple paths, in existing windows if possible. # @@ -400,9 +416,10 @@ class AtomApplication # :safeMode - Boolean to control the opened window's safe mode. # :windowDimensions - Object with height and width keys. # :window - {AtomWindow} to open file paths in. - openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window}={}) -> + openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window, clearWindowState}={}) -> devMode = Boolean(devMode) safeMode = Boolean(safeMode) + clearWindowState = Boolean(clearWindowState) locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom) for pathToOpen in pathsToOpen) pathsToOpen = (locationToOpen.pathToOpen for locationToOpen in locationsToOpen) @@ -435,7 +452,7 @@ class AtomApplication windowInitializationScript ?= require.resolve('../initialize-application-window') resourcePath ?= @resourcePath windowDimensions ?= @getDimensionsForNewWindow() - openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup}) + openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState}) if pidToKillWhenClosed? @pidsToOpenWindows[pidToKillWhenClosed] = openedWindow @@ -472,7 +489,7 @@ class AtomApplication if loadSettings = window.getLoadSettings() states.push(initialPaths: loadSettings.initialPaths) if states.length > 0 or allowEmpty - @storageFolder.store('application.json', states) + @storageFolder.storeSync('application.json', states) loadState: (options) -> if (states = @storageFolder.load('application.json'))?.length > 0 diff --git a/src/browser/atom-portable.coffee b/src/browser/atom-portable.coffee index 5f8f10cf6..ae4bb67ec 100644 --- a/src/browser/atom-portable.coffee +++ b/src/browser/atom-portable.coffee @@ -1,6 +1,6 @@ fs = require 'fs-plus' path = require 'path' -ipc = require 'ipc' +{ipcMain} = require 'electron' module.exports = class AtomPortable @@ -30,6 +30,6 @@ class AtomPortable catch error message = "Failed to use portable Atom home directory (#{@getPortableAtomHomePath()}). Using the default instead (#{defaultHome}). #{error.message}" - ipc.on 'check-portable-home-writable', (event) -> + ipcMain.on 'check-portable-home-writable', (event) -> event.sender.send 'check-portable-home-writable-response', {writable, message} writable diff --git a/src/browser/atom-protocol-handler.coffee b/src/browser/atom-protocol-handler.coffee index 0fda8095b..3967c0525 100644 --- a/src/browser/atom-protocol-handler.coffee +++ b/src/browser/atom-protocol-handler.coffee @@ -1,7 +1,6 @@ -app = require 'app' +{app, protocol} = require 'electron' fs = require 'fs' path = require 'path' -protocol = require 'protocol' # Handles requests with 'atom' protocol. # diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index 20ba2ad5c..828366c4f 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -1,6 +1,4 @@ -BrowserWindow = require 'browser-window' -app = require 'app' -dialog = require 'dialog' +{BrowserWindow, app, dialog} = require 'electron' path = require 'path' fs = require 'fs' url = require 'url' @@ -50,6 +48,7 @@ class AtomWindow loadSettings.safeMode ?= false loadSettings.atomHome = process.env.ATOM_HOME loadSettings.firstLoad = true + loadSettings.clearWindowState ?= false # Only send to the first non-spec window created if @constructor.includeShellLoadTime and not @isSpec @@ -82,7 +81,7 @@ class AtomWindow loadSettings = _.clone(loadSettingsObj) delete loadSettings['windowState'] - @browserWindow.loadUrl url.format + @browserWindow.loadURL url.format protocol: 'file' pathname: "#{@resourcePath}/static/index.html" slashes: true @@ -90,7 +89,7 @@ class AtomWindow getLoadSettings: -> if @browserWindow.webContents? and not @browserWindow.webContents.isLoading() - hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1) + hash = url.parse(@browserWindow.webContents.getURL()).hash.substr(1) JSON.parse(decodeURIComponent(hash)) hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0 @@ -122,6 +121,9 @@ class AtomWindow false handleEvents: -> + @browserWindow.on 'close', -> + global.atomApplication.saveState(false) + @browserWindow.on 'closed', => global.atomApplication.removeWindow(this) @@ -145,10 +147,10 @@ class AtomWindow detail: 'Please report this issue to https://github.com/atom/atom' switch chosen when 0 then @browserWindow.destroy() - when 1 then @browserWindow.restart() + when 1 then @browserWindow.reload() @browserWindow.webContents.on 'will-navigate', (event, url) => - unless url is @browserWindow.webContents.getUrl() + unless url is @browserWindow.webContents.getURL() event.preventDefault() @setupContextMenu() @@ -221,6 +223,6 @@ class AtomWindow isSpecWindow: -> @isSpec - reload: -> @browserWindow.restart() + reload: -> @browserWindow.reload() toggleDevTools: -> @browserWindow.toggleDevTools() diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 6a008d44d..2df338761 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -29,13 +29,13 @@ class AutoUpdateManager if process.platform is 'win32' autoUpdater = require './auto-updater-win32' else - autoUpdater = require 'auto-updater' + {autoUpdater} = require 'electron' autoUpdater.on 'error', (event, message) => @setState(ErrorState) console.error "Error Downloading Update: #{message}" - autoUpdater.setFeedUrl @feedUrl + autoUpdater.setFeedURL @feedUrl autoUpdater.on 'checking-for-update', => @setState(CheckingState) @@ -104,7 +104,7 @@ class AutoUpdateManager onUpdateNotAvailable: => autoUpdater.removeListener 'error', @onUpdateError - dialog = require 'dialog' + {dialog} = require 'electron' dialog.showMessageBox type: 'info' buttons: ['OK'] @@ -115,7 +115,7 @@ class AutoUpdateManager onUpdateError: (event, message) => autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable - dialog = require 'dialog' + {dialog} = require 'electron' dialog.showMessageBox type: 'warning' buttons: ['OK'] diff --git a/src/browser/auto-updater-win32.coffee b/src/browser/auto-updater-win32.coffee index 4d043ac4e..e31578d49 100644 --- a/src/browser/auto-updater-win32.coffee +++ b/src/browser/auto-updater-win32.coffee @@ -5,13 +5,13 @@ SquirrelUpdate = require './squirrel-update' class AutoUpdater _.extend @prototype, EventEmitter.prototype - setFeedUrl: (@updateUrl) -> + setFeedURL: (@updateUrl) -> quitAndInstall: -> if SquirrelUpdate.existsSync() - SquirrelUpdate.restartAtom(require('app')) + SquirrelUpdate.restartAtom(require('electron').app) else - require('auto-updater').quitAndInstall() + require('electron').autoUpdater.quitAndInstall() downloadUpdate: (callback) -> SquirrelUpdate.spawn ['--download', @updateUrl], (error, stdout) -> diff --git a/src/browser/context-menu.coffee b/src/browser/context-menu.coffee index 44b57cdc9..1bc9c29ba 100644 --- a/src/browser/context-menu.coffee +++ b/src/browser/context-menu.coffee @@ -1,4 +1,4 @@ -Menu = require 'menu' +{Menu} = require 'electron' module.exports = class ContextMenu diff --git a/src/browser/main.coffee b/src/browser/main.coffee index ca9d7e3ae..20f5bb046 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -4,8 +4,7 @@ process.on 'uncaughtException', (error={}) -> console.log(error.message) if error.message? console.log(error.stack) if error.stack? -crashReporter = require 'crash-reporter' -app = require 'app' +{crashReporter, app} = require 'electron' fs = require 'fs-plus' path = require 'path' yargs = require 'yargs' @@ -32,6 +31,9 @@ start = -> app.on 'open-url', addUrlToOpen app.on 'will-finish-launching', setupCrashReporter + if args.userDataDir? + app.setPath('userData', args.userDataDir) + app.on 'ready', -> app.removeListener 'open-file', addPathToOpen app.removeListener 'open-url', addUrlToOpen @@ -54,12 +56,12 @@ handleStartupEventWithSquirrel = -> SquirrelUpdate.handleStartupEvent(app, squirrelCommand) setupCrashReporter = -> - crashReporter.start(productName: 'Atom', companyName: 'GitHub') + crashReporter.start(productName: 'Atom', companyName: 'GitHub', submitURL: 'http://54.249.141.255:1127/post') setupAtomHome = ({setPortable}) -> return if process.env.ATOM_HOME - atomHome = path.join(app.getHomeDir(), '.atom') + atomHome = path.join(app.getPath('home'), '.atom') AtomPortable = require './atom-portable' if setPortable and not AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome) @@ -119,6 +121,8 @@ parseCommandLine = -> options.alias('v', 'version').boolean('v').describe('v', 'Print the version.') options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.') options.string('socket-path') + options.string('user-data-dir') + options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.') args = options.argv @@ -140,9 +144,11 @@ parseCommandLine = -> pidToKillWhenClosed = args['pid'] if args['wait'] logFile = args['log-file'] socketPath = args['socket-path'] + userDataDir = args['user-data-dir'] profileStartup = args['profile-startup'] + clearWindowState = args['clear-window-state'] urlsToOpen = [] - devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom') + devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getPath('home'), 'github', 'atom') setPortable = args.portable if args['resource-path'] @@ -164,6 +170,7 @@ parseCommandLine = -> {resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, - logFile, socketPath, profileStartup, timeout, setPortable} + logFile, socketPath, userDataDir, profileStartup, timeout, setPortable, + clearWindowState} start() diff --git a/src/browser/squirrel-update.coffee b/src/browser/squirrel-update.coffee index 3660158fc..f9684ce49 100644 --- a/src/browser/squirrel-update.coffee +++ b/src/browser/squirrel-update.coffee @@ -20,6 +20,7 @@ else fileKeyPath = 'HKCU\\Software\\Classes\\*\\shell\\Atom' directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom' backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom' +applicationsKeyPath = 'HKCU\\Software\\Classes\\Applications\\atom.exe' environmentKeyPath = 'HKCU\\Environment' # Spawn a command and invoke the callback when it completes with an error @@ -64,6 +65,10 @@ installContextMenu = (callback) -> args.push('/f') spawnReg(args, callback) + installFileHandler = (callback) -> + args = ["#{applicationsKeyPath}\\shell\\open\\command", '/ve', '/d', "\"#{process.execPath}\" \"%1\""] + addToRegistry(args, callback) + installMenu = (keyPath, arg, callback) -> args = [keyPath, '/ve', '/d', 'Open with Atom'] addToRegistry args, -> @@ -74,7 +79,8 @@ installContextMenu = (callback) -> installMenu fileKeyPath, '%1', -> installMenu directoryKeyPath, '%1', -> - installMenu(backgroundKeyPath, '%V', callback) + installMenu backgroundKeyPath, '%V', -> + installFileHandler(callback) isAscii = (text) -> index = 0 @@ -124,7 +130,8 @@ uninstallContextMenu = (callback) -> deleteFromRegistry fileKeyPath, -> deleteFromRegistry directoryKeyPath, -> - deleteFromRegistry(backgroundKeyPath, callback) + deleteFromRegistry backgroundKeyPath, -> + deleteFromRegistry(applicationsKeyPath, callback) # Add atom and apm to the PATH # diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 15e5223b8..346551ff5 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -108,6 +108,10 @@ module.exports = description: 'Automatically update Atom when a new release is available.' type: 'boolean' default: true + allowPendingPaneItems: + description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.' + type: 'boolean' + default: true editor: type: 'object' diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 1d80dec09..936a9c6b6 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -4,7 +4,7 @@ CSON = require 'season' fs = require 'fs-plus' {calculateSpecificity, validateSelector} = require 'clear-cut' {Disposable} = require 'event-kit' -remote = require 'remote' +{remote} = require 'electron' MenuHelpers = require './menu-helpers' platformContextMenu = require('../package.json')?._atomMenu?['context-menu'] diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 8b95656f9..d01ad03c9 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -413,6 +413,9 @@ class DisplayBuffer extends Model isFoldedAtScreenRow: (screenRow) -> @largestFoldContainingBufferRow(@bufferRowForScreenRow(screenRow))? + isFoldableAtBufferRow: (row) -> + @tokenizedBuffer.isFoldableAtRow(row) + # Destroys the fold with the given id destroyFoldWithId: (id) -> @foldsByMarkerId[id]?.destroy() diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 5c5e936c5..61f0c2439 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -23,12 +23,12 @@ module.exports = ({blobStore}) -> enablePersistence: true }) - atom.loadStateSync() - atom.displayWindow() - atom.startEditorWindow() + atom.loadState().then -> + atom.displayWindow().then -> + atom.startEditorWindow() - # Workaround for focus getting cleared upon window creation - windowFocused = -> - window.removeEventListener('focus', windowFocused) - setTimeout (-> document.querySelector('atom-workspace').focus()), 0 - window.addEventListener('focus', windowFocused) + # Workaround for focus getting cleared upon window creation + windowFocused = -> + window.removeEventListener('focus', windowFocused) + setTimeout (-> document.querySelector('atom-workspace').focus()), 0 + window.addEventListener('focus', windowFocused) diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee index f3507b479..690180fc8 100644 --- a/src/initialize-test-window.coffee +++ b/src/initialize-test-window.coffee @@ -4,17 +4,17 @@ cloneObject = (object) -> clone module.exports = ({blobStore}) -> + {crashReporter, remote} = require 'electron' # Start the crash reporter before anything else. - require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub') - remote = require 'remote' + crashReporter.start(productName: 'Atom', companyName: 'GitHub', submitURL: 'http://54.249.141.255:1127/post') exitWithStatusCode = (status) -> - remote.require('app').emit('will-quit') + remote.app.emit('will-quit') remote.process.exit(status) try path = require 'path' - ipc = require 'ipc' + {ipcRenderer} = require 'electron' {getWindowLoadSettings} = require './window-load-settings-helpers' AtomEnvironment = require '../src/atom-environment' ApplicationDelegate = require '../src/application-delegate' @@ -29,15 +29,15 @@ module.exports = ({blobStore}) -> handleKeydown = (event) -> # Reload: cmd-r / ctrl-r if (event.metaKey or event.ctrlKey) and event.keyCode is 82 - ipc.send('call-window-method', 'restart') + ipcRenderer.send('call-window-method', 'reload') # Toggle Dev Tools: cmd-alt-i / ctrl-alt-i if (event.metaKey or event.ctrlKey) and event.altKey and event.keyCode is 73 - ipc.send('call-window-method', 'toggleDevTools') + ipcRenderer.send('call-window-method', 'toggleDevTools') # Reload: cmd-w / ctrl-w if (event.metaKey or event.ctrlKey) and event.keyCode is 87 - ipc.send('call-window-method', 'close') + ipcRenderer.send('call-window-method', 'close') window.addEventListener('keydown', handleKeydown, true) @@ -68,7 +68,8 @@ module.exports = ({blobStore}) -> logFile, headless, testPaths, buildAtomEnvironment, buildDefaultApplicationDelegate, legacyTestRunner }) - promise.then(exitWithStatusCode) if getWindowLoadSettings().headless + promise.then (statusCode) -> + exitWithStatusCode(statusCode) if getWindowLoadSettings().headless catch error if getWindowLoadSettings().headless console.error(error.stack ? error) diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js new file mode 100644 index 000000000..c0b38c50e --- /dev/null +++ b/src/ipc-helpers.js @@ -0,0 +1,40 @@ +var ipcRenderer = null +var ipcMain = null +var BrowserWindow = null + +exports.call = function (methodName, ...args) { + if (!ipcRenderer) { + ipcRenderer = require('electron').ipcRenderer + } + + var responseChannel = getResponseChannel(methodName) + + return new Promise(function (resolve) { + ipcRenderer.on(responseChannel, function (event, result) { + ipcRenderer.removeAllListeners(responseChannel) + resolve(result) + }) + + ipcRenderer.send(methodName, ...args) + }) +} + +exports.respondTo = function (methodName, callback) { + if (!ipcMain) { + var electron = require('electron') + ipcMain = electron.ipcMain + BrowserWindow = electron.BrowserWindow + } + + var responseChannel = getResponseChannel(methodName) + + ipcMain.on(methodName, function (event, ...args) { + var browserWindow = BrowserWindow.fromWebContents(event.sender) + var result = callback(browserWindow, ...args) + event.sender.send(responseChannel, result) + }) +} + +function getResponseChannel (methodName) { + return 'ipc-helpers-' + methodName + '-response' +} diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index fa78d3cd6..67076dbfa 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -1,7 +1,7 @@ path = require 'path' _ = require 'underscore-plus' -ipc = require 'ipc' +{ipcRenderer} = require 'electron' CSON = require 'season' fs = require 'fs-plus' {Disposable} = require 'event-kit' @@ -191,7 +191,7 @@ class MenuManager sendToBrowserProcess: (template, keystrokesByCommand) -> keystrokesByCommand = @filterMultipleKeystroke(keystrokesByCommand) - ipc.send 'update-application-menu', template, keystrokesByCommand + ipcRenderer.send 'update-application-menu', template, keystrokesByCommand # Get an {Array} of {String} classes for the given element. classesForElement: (element) -> diff --git a/src/module-cache.coffee b/src/module-cache.coffee index e9245cf40..a2840a864 100644 --- a/src/module-cache.coffee +++ b/src/module-cache.coffee @@ -208,7 +208,7 @@ registerBuiltins = (devMode) -> cache.builtins[builtin] = path.join(commonRoot, "#{builtin}.js") rendererRoot = path.join(atomShellRoot, 'renderer', 'api', 'lib') - rendererBuiltins = ['ipc', 'remote'] + rendererBuiltins = ['ipc-renderer', 'remote'] for builtin in rendererBuiltins cache.builtins[builtin] = path.join(rendererRoot, "#{builtin}.js") diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 1ecdc5448..33f8f86a3 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -357,7 +357,9 @@ class PackageManager packagePaths = @getAvailablePackagePaths() packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath)) packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath) - @loadPackage(packagePath) for packagePath in packagePaths + @config.transact => + @loadPackage(packagePath) for packagePath in packagePaths + return @emitter.emit 'did-load-initial-packages' loadPackage: (nameOrPath) -> @@ -467,6 +469,14 @@ class PackageManager return unless hook? and _.isString(hook) and hook.length > 0 @activationHookEmitter.on(hook, callback) + serialize: -> + for pack in @getActivePackages() + @serializePackage(pack) + @packageStates + + serializePackage: (pack) -> + @setPackageState(pack.name, state) if state = pack.serialize?() + # Deactivate all packages deactivatePackages: -> @config.transact => @@ -478,8 +488,7 @@ class PackageManager # Deactivate the package with the given name deactivatePackage: (name) -> pack = @getLoadedPackage(name) - if @isPackageActive(name) - @setPackageState(pack.name, state) if state = pack.serialize?() + @serializePackage(pack) if @isPackageActive(pack.name) pack.deactivate() delete @activePackages[pack.name] delete @activatingPackages[pack.name] diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 32c74569b..1b1aad2cf 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -1,4 +1,4 @@ -ipc = require 'ipc' +{ipcRenderer} = require 'electron' module.exports = ({commandRegistry, commandInstaller, config}) -> commandRegistry.add 'atom-workspace', @@ -18,30 +18,30 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> 'window:increase-font-size': -> @getModel().increaseFontSize() 'window:decrease-font-size': -> @getModel().decreaseFontSize() 'window:reset-font-size': -> @getModel().resetFontSize() - 'application:about': -> ipc.send('command', 'application:about') - 'application:show-preferences': -> ipc.send('command', 'application:show-settings') - 'application:show-settings': -> ipc.send('command', 'application:show-settings') - 'application:quit': -> ipc.send('command', 'application:quit') - 'application:hide': -> ipc.send('command', 'application:hide') - 'application:hide-other-applications': -> ipc.send('command', 'application:hide-other-applications') - 'application:install-update': -> ipc.send('command', 'application:install-update') - 'application:unhide-all-applications': -> ipc.send('command', 'application:unhide-all-applications') - 'application:new-window': -> ipc.send('command', 'application:new-window') - 'application:new-file': -> ipc.send('command', 'application:new-file') - 'application:open': -> ipc.send('command', 'application:open') - 'application:open-file': -> ipc.send('command', 'application:open-file') - 'application:open-folder': -> ipc.send('command', 'application:open-folder') - 'application:open-dev': -> ipc.send('command', 'application:open-dev') - 'application:open-safe': -> ipc.send('command', 'application:open-safe') + 'application:about': -> ipcRenderer.send('command', 'application:about') + 'application:show-preferences': -> ipcRenderer.send('command', 'application:show-settings') + 'application:show-settings': -> ipcRenderer.send('command', 'application:show-settings') + 'application:quit': -> ipcRenderer.send('command', 'application:quit') + 'application:hide': -> ipcRenderer.send('command', 'application:hide') + 'application:hide-other-applications': -> ipcRenderer.send('command', 'application:hide-other-applications') + 'application:install-update': -> ipcRenderer.send('command', 'application:install-update') + 'application:unhide-all-applications': -> ipcRenderer.send('command', 'application:unhide-all-applications') + 'application:new-window': -> ipcRenderer.send('command', 'application:new-window') + 'application:new-file': -> ipcRenderer.send('command', 'application:new-file') + 'application:open': -> ipcRenderer.send('command', 'application:open') + 'application:open-file': -> ipcRenderer.send('command', 'application:open-file') + 'application:open-folder': -> ipcRenderer.send('command', 'application:open-folder') + 'application:open-dev': -> ipcRenderer.send('command', 'application:open-dev') + 'application:open-safe': -> ipcRenderer.send('command', 'application:open-safe') 'application:add-project-folder': -> atom.addProjectFolder() - 'application:minimize': -> ipc.send('command', 'application:minimize') - 'application:zoom': -> ipc.send('command', 'application:zoom') - 'application:bring-all-windows-to-front': -> ipc.send('command', 'application:bring-all-windows-to-front') - 'application:open-your-config': -> ipc.send('command', 'application:open-your-config') - 'application:open-your-init-script': -> ipc.send('command', 'application:open-your-init-script') - 'application:open-your-keymap': -> ipc.send('command', 'application:open-your-keymap') - 'application:open-your-snippets': -> ipc.send('command', 'application:open-your-snippets') - 'application:open-your-stylesheet': -> ipc.send('command', 'application:open-your-stylesheet') + 'application:minimize': -> ipcRenderer.send('command', 'application:minimize') + 'application:zoom': -> ipcRenderer.send('command', 'application:zoom') + 'application:bring-all-windows-to-front': -> ipcRenderer.send('command', 'application:bring-all-windows-to-front') + 'application:open-your-config': -> ipcRenderer.send('command', 'application:open-your-config') + 'application:open-your-init-script': -> ipcRenderer.send('command', 'application:open-your-init-script') + 'application:open-your-keymap': -> ipcRenderer.send('command', 'application:open-your-keymap') + 'application:open-your-snippets': -> ipcRenderer.send('command', 'application:open-your-snippets') + 'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet') 'application:open-license': -> @getModel().openLicense() 'window:run-package-specs': -> @runPackageSpecs() 'window:focus-next-pane': -> @getModel().activateNextPane() diff --git a/src/safe-clipboard.coffee b/src/safe-clipboard.coffee index 8301f9d54..1f91803e2 100644 --- a/src/safe-clipboard.coffee +++ b/src/safe-clipboard.coffee @@ -1,6 +1,6 @@ # Using clipboard in renderer process is not safe on Linux. module.exports = if process.platform is 'linux' and process.type is 'renderer' - require('remote').require('clipboard') + require('electron').remote.clipboard else - require('clipboard') + require('electron').clipboard diff --git a/src/selection.coffee b/src/selection.coffee index 2ba66ebb0..465d76a4c 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -755,7 +755,7 @@ class Selection extends Model # # * `otherSelection` A {Selection} to compare against compare: (otherSelection) -> - @getBufferRange().compare(otherSelection.getBufferRange()) + @marker.compare(otherSelection.marker) ### Section: Private Utilities diff --git a/src/state-store.js b/src/state-store.js new file mode 100644 index 000000000..2175bbe4c --- /dev/null +++ b/src/state-store.js @@ -0,0 +1,91 @@ +'use strict' + +module.exports = +class StateStore { + constructor (databaseName, version) { + this.dbPromise = new Promise((resolve) => { + let dbOpenRequest = indexedDB.open(databaseName, version) + dbOpenRequest.onupgradeneeded = (event) => { + let db = event.target.result + db.createObjectStore('states') + } + dbOpenRequest.onsuccess = () => { + resolve(dbOpenRequest.result) + } + dbOpenRequest.onerror = (error) => { + console.error('Could not connect to indexedDB', error) + resolve(null) + } + }) + } + + connect () { + return this.dbPromise.then(db => !!db) + } + + save (key, value) { + return this.dbPromise.then(db => { + if (!db) return + + return new Promise((resolve, reject) => { + var request = db.transaction(['states'], 'readwrite') + .objectStore('states') + .put({value: value, storedAt: new Date().toString()}, key) + + request.onsuccess = resolve + request.onerror = reject + }) + }) + } + + load (key) { + return this.dbPromise.then(db => { + if (!db) return + + return new Promise((resolve, reject) => { + var request = db.transaction(['states']) + .objectStore('states') + .get(key) + + request.onsuccess = (event) => { + let result = event.target.result + resolve(result ? result.value : null) + } + + request.onerror = (event) => reject(event) + }) + }) + } + + clear () { + return this.dbPromise.then(db => { + if (!db) return + + return new Promise((resolve, reject) => { + var request = db.transaction(['states'], 'readwrite') + .objectStore('states') + .clear() + + request.onsuccess = resolve + request.onerror = reject + }) + }) + } + + count () { + return this.dbPromise.then(db => { + if (!db) return + + return new Promise((resolve, reject) => { + var request = db.transaction(['states']) + .objectStore('states') + .count() + + request.onsuccess = () => { + resolve(request.result) + } + request.onerror = reject + }) + }) + } +} diff --git a/src/storage-folder.coffee b/src/storage-folder.coffee index da8af3f2e..06beae56a 100644 --- a/src/storage-folder.coffee +++ b/src/storage-folder.coffee @@ -6,7 +6,7 @@ class StorageFolder constructor: (containingPath) -> @path = path.join(containingPath, "storage") if containingPath? - store: (name, object) -> + storeSync: (name, object) -> return unless @path? fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8') diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 4a51badbd..bdd0befcd 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -2,7 +2,7 @@ _ = require 'underscore-plus' scrollbarStyle = require 'scrollbar-style' {Range, Point} = require 'text-buffer' {CompositeDisposable} = require 'event-kit' -ipc = require 'ipc' +{ipcRenderer} = require 'electron' TextEditorPresenter = require './text-editor-presenter' GutterContainerComponent = require './gutter-container-component' @@ -279,10 +279,10 @@ class TextEditorComponent writeSelectedTextToSelectionClipboard = => return if @editor.isDestroyed() if selectedText = @editor.getSelectedText() - # This uses ipc.send instead of clipboard.writeText because - # clipboard.writeText is a sync ipc call on Linux and that + # This uses ipcRenderer.send instead of clipboard.writeText because + # clipboard.writeText is a sync ipcRenderer call on Linux and that # will slow down selections. - ipc.send('write-text-to-selection-clipboard', selectedText) + ipcRenderer.send('write-text-to-selection-clipboard', selectedText) @disposables.add @editor.onDidChangeSelectionRange -> clearTimeout(timeoutId) timeoutId = setTimeout(writeSelectedTextToSelectionClipboard) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b13bf9036..a3504caa8 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -432,18 +432,14 @@ class TextEditorPresenter return updateCursorsState: -> - @state.content.cursors = {} - @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation - return - - updateCursorState: (cursor) -> return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? - screenRange = cursor.getScreenRange() - return unless cursor.isVisible() and @startRow <= screenRange.start.row < @endRow - pixelRect = @pixelRectForScreenRange(screenRange) - pixelRect.width = Math.round(@baseCharacterWidth) if pixelRect.width is 0 - @state.content.cursors[cursor.id] = pixelRect + @state.content.cursors = {} + for cursor in @model.cursorsForScreenRowRange(@startRow, @endRow - 1) when cursor.isVisible() + pixelRect = @pixelRectForScreenRange(cursor.getScreenRange()) + pixelRect.width = Math.round(@baseCharacterWidth) if pixelRect.width is 0 + @state.content.cursors[cursor.id] = pixelRect + return updateOverlaysState: -> return unless @hasOverlayPositionRequirements() @@ -603,7 +599,14 @@ class TextEditorPresenter if endRow > startRow bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1) + previousBufferRow = -1 + foldable = false for bufferRow, i in bufferRows + # don't compute foldability more than once per buffer row + if previousBufferRow isnt bufferRow + foldable = @model.isFoldableAtBufferRow(bufferRow) + previousBufferRow = bufferRow + if bufferRow is lastBufferRow softWrapped = true else @@ -613,7 +616,6 @@ class TextEditorPresenter screenRow = startRow + i line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) - foldable = @model.isFoldableAtScreenRow(screenRow) blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) blockDecorationsHeight = blockDecorationsBeforeCurrentScreenRowHeight if screenRow % @tileSize isnt 0 diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 2ba45a3ba..c0a6f2057 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -109,6 +109,7 @@ class TextEditor extends Model @emitter = new Emitter @disposables = new CompositeDisposable @cursors = [] + @cursorsByMarkerId = new Map @selections = [] buffer ?= new TextBuffer @@ -1944,10 +1945,18 @@ class TextEditor extends Model getCursorsOrderedByBufferPosition: -> @getCursors().sort (a, b) -> a.compare(b) + cursorsForScreenRowRange: (startScreenRow, endScreenRow) -> + cursors = [] + for marker in @selectionsMarkerLayer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) + if cursor = @cursorsByMarkerId.get(marker.id) + cursors.push(cursor) + cursors + # Add a cursor based on the given {TextEditorMarker}. addCursor: (marker) -> cursor = new Cursor(editor: this, marker: marker, config: @config) @cursors.push(cursor) + @cursorsByMarkerId.set(marker.id, cursor) @decorateMarker(marker, type: 'line-number', class: 'cursor-line') @decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true) @decorateMarker(marker, type: 'line', class: 'cursor-line', onlyEmpty: true) @@ -2446,6 +2455,7 @@ class TextEditor extends Model removeSelection: (selection) -> _.remove(@cursors, selection.cursor) _.remove(@selections, selection) + @cursorsByMarkerId.delete(selection.cursor.marker.id) @emitter.emit 'did-remove-cursor', selection.cursor @emitter.emit 'did-remove-selection', selection @@ -2929,8 +2939,7 @@ class TextEditor extends Model # # Returns a {Boolean}. isFoldableAtBufferRow: (bufferRow) -> - # @languageMode.isFoldableAtBufferRow(bufferRow) - @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow)?.foldable ? false + @displayBuffer.isFoldableAtBufferRow(bufferRow) # Extended: Determine whether the given row in screen coordinates is foldable. # diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 0fdf4eea8..5c62f9ecd 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -210,8 +210,6 @@ class TokenizedBuffer extends Model @validateRow(endRow) @invalidateRow(endRow + 1) unless filledRegion - [startRow, endRow] = @updateFoldableStatus(startRow, endRow) - event = {start: startRow, end: endRow, delta: 0} @emitter.emit 'did-change', event @@ -271,9 +269,6 @@ class TokenizedBuffer extends Model if newEndStack and not _.isEqual(newEndStack, previousEndStack) @invalidateRow(end + delta + 1) - [start, end] = @updateFoldableStatus(start, end + delta) - end -= delta - event = {start, end, delta, bufferChange: e} @emitter.emit 'did-change', event @@ -287,23 +282,6 @@ class TokenizedBuffer extends Model row - increment - updateFoldableStatus: (startRow, endRow) -> - return [startRow, endRow] if @largeFileMode - - scanStartRow = @buffer.previousNonBlankRow(startRow) ? startRow - scanStartRow-- while scanStartRow > 0 and @tokenizedLineForRow(scanStartRow).isComment() - scanEndRow = @buffer.nextNonBlankRow(endRow) ? endRow - - for row in [scanStartRow..scanEndRow] by 1 - foldable = @isFoldableAtRow(row) - line = @tokenizedLineForRow(row) - unless line.foldable is foldable - line.foldable = foldable - startRow = Math.min(startRow, row) - endRow = Math.max(endRow, row) - - [startRow, endRow] - isFoldableAtRow: (row) -> if @largeFileMode false diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index c97a621ac..bf6a6dd2b 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -33,7 +33,6 @@ class TokenizedLine endOfLineInvisibles: null lineIsWhitespaceOnly: false firstNonWhitespaceIndex: 0 - foldable: false constructor: (properties) -> @id = idCounter++ @@ -492,15 +491,20 @@ class TokenizedLine @endOfLineInvisibles.push(eol) if eol isComment: -> + return @isCommentLine if @isCommentLine? + + @isCommentLine = false iterator = @getTokenIterator() while iterator.next() scopes = iterator.getScopes() continue if scopes.length is 1 continue unless NonWhitespaceRegex.test(iterator.getText()) for scope in scopes - return true if CommentScopeRegex.test(scope) + if CommentScopeRegex.test(scope) + @isCommentLine = true + break break - false + @isCommentLine isOnlyWhitespace: -> @lineIsWhitespaceOnly diff --git a/src/window-load-settings-helpers.coffee b/src/window-load-settings-helpers.coffee index 59ee2f382..4bb514301 100644 --- a/src/window-load-settings-helpers.coffee +++ b/src/window-load-settings-helpers.coffee @@ -1,4 +1,4 @@ -remote = require 'remote' +{remote} = require 'electron' _ = require 'underscore-plus' windowLoadSettings = null diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee index f7805ed57..8f6dca48a 100644 --- a/src/workspace-element.coffee +++ b/src/workspace-element.coffee @@ -1,4 +1,4 @@ -ipc = require 'ipc' +{ipcRenderer} = require 'electron' path = require 'path' {Disposable, CompositeDisposable} = require 'event-kit' Grim = require 'grim' @@ -117,6 +117,6 @@ class WorkspaceElement extends HTMLElement [projectPath] = @project.relativizePath(activePath) else [projectPath] = @project.getPaths() - ipc.send('run-package-specs', path.join(projectPath, 'spec')) if projectPath + ipcRenderer.send('run-package-specs', path.join(projectPath, 'spec')) if projectPath module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype diff --git a/src/workspace.coffee b/src/workspace.coffee index 79cfb3e8e..0bfff7e0f 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -414,6 +414,9 @@ class Workspace extends Model split = options.split uri = @project.resolvePath(uri) + if not atom.config.get('core.allowPendingPaneItems') + options.pending = false + # Avoid adding URLs as recent documents to work-around this Spotlight crash: # https://github.com/atom/atom/issues/10071 if uri? and not url.parse(uri).protocol? @@ -473,7 +476,8 @@ class Workspace extends Model activateItem = options.activateItem ? true if uri? - item = pane.itemForURI(uri) + if item = pane.itemForURI(uri) + item.terminatePendingState?() if item.isPending?() and not options.pending item ?= opener(uri, options) for opener in @getOpeners() when not item try diff --git a/static/index.html b/static/index.html index 5fcb30ad2..0bd4a7954 100644 --- a/static/index.html +++ b/static/index.html @@ -1,7 +1,7 @@
- + diff --git a/static/index.js b/static/index.js index 6d65d3c52..c33eda67a 100644 --- a/static/index.js +++ b/static/index.js @@ -54,7 +54,7 @@ } function handleSetupError (error) { - var currentWindow = require('remote').getCurrentWindow() + var currentWindow = require('electron').remote.getCurrentWindow() currentWindow.setSize(800, 600) currentWindow.center() currentWindow.show() @@ -71,9 +71,10 @@ ModuleCache.add(loadSettings.resourcePath) // Start the crash reporter before anything else. - require('crash-reporter').start({ + require('electron').crashReporter.start({ productName: 'Atom', companyName: 'GitHub', + submitURL: 'http://54.249.141.255:1127/post', // By explicitly passing the app version here, we could save the call // of "require('remote').require('app').getVersion()". extra: {_version: loadSettings.appVersion} @@ -83,8 +84,9 @@ setupCsonCache(CompileCache.getCacheDirectory()) var initialize = require(loadSettings.windowInitializationScript) - initialize({blobStore: blobStore}) - require('ipc').sendChannel('window-command', 'window:loaded') + return initialize({blobStore: blobStore}).then(function () { + require('electron').ipcRenderer.send('window-command', 'window:loaded') + }) } function setupCsonCache (cacheDir) { @@ -112,19 +114,15 @@ function profileStartup (loadSettings, initialTime) { function profile () { console.profile('startup') - try { - var startTime = Date.now() - setupWindow(loadSettings) + var startTime = Date.now() + setupWindow(loadSettings).then(function () { setLoadTime(Date.now() - startTime + initialTime) - } catch (error) { - handleSetupError(error) - } finally { console.profileEnd('startup') console.log('Switch to the Profiles tab to view the created startup profile') - } + }) } - var currentWindow = require('remote').getCurrentWindow() + var currentWindow = require('electron').remote.getCurrentWindow() if (currentWindow.devToolsWebContents) { profile() } else { @@ -145,31 +143,6 @@ } } - function setupWindowBackground () { - if (loadSettings && loadSettings.isSpec) { - return - } - - var backgroundColor = window.localStorage.getItem('atom:window-background-color') - if (!backgroundColor) { - return - } - - var backgroundStylesheet = document.createElement('style') - backgroundStylesheet.type = 'text/css' - backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + ' !important; }' - document.head.appendChild(backgroundStylesheet) - - // Remove once the page loads - window.addEventListener('load', function loadWindow () { - window.removeEventListener('load', loadWindow, false) - setTimeout(function () { - backgroundStylesheet.remove() - backgroundStylesheet = null - }, 1000) - }, false) - } - var setupAtomHome = function () { if (process.env.ATOM_HOME) { return @@ -185,5 +158,4 @@ parseLoadSettings() setupAtomHome() - setupWindowBackground() })()