From 59f6065e9b181102bee496cef4f48fbe93b0242c Mon Sep 17 00:00:00 2001 From: Steven Hobson-Campbell Date: Tue, 22 Aug 2017 19:05:12 -0700 Subject: [PATCH 01/43] Adding option to skip main process tests. Cleaning up resources in tests. --- script/package.json | 1 + script/test | 18 ++++++++++++++---- spec/git-repository-provider-spec.coffee | 3 +++ spec/project-spec.coffee | 4 ++++ src/atom-environment.coffee | 1 - 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/script/package.json b/script/package.json index 454d561aa..82500d12c 100644 --- a/script/package.json +++ b/script/package.json @@ -20,6 +20,7 @@ "legal-eagle": "0.14.0", "lodash.template": "4.4.0", "minidump": "0.9.0", + "minimist": "^1.2.0", "mkdirp": "0.5.1", "normalize-package-data": "2.3.5", "npm": "5.3.0", diff --git a/script/test b/script/test index 2f22b1e0a..1b1453341 100755 --- a/script/test +++ b/script/test @@ -3,6 +3,7 @@ 'use strict' require('colors') +const argv = require('minimist')(process.argv.slice(2)) const assert = require('assert') const async = require('async') const childProcess = require('child_process') @@ -150,17 +151,26 @@ function runBenchmarkTests (callback) { let testSuitesToRun = testSuitesForPlatform(process.platform) function testSuitesForPlatform (platform) { + let suites = []; switch (platform) { case 'darwin': - return [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites) + suites = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites) + break case 'win32': - return (process.arch === 'x64') ? [runCoreMainProcessTests, runCoreRenderProcessTests] : [runCoreMainProcessTests] + suites = (process.arch === 'x64') ? [runCoreMainProcessTests, runCoreRenderProcessTests] : [runCoreMainProcessTests] + break case 'linux': - return [runCoreMainProcessTests] + suites = [runCoreMainProcessTests] + break default: console.log(`Unrecognized platform: ${platform}`) - return [] } + + if(argv.skipMainProccessTests) { + suites = suites.filter(suite => suite !== runCoreMainProcessTests); + } + + return suites; } async.series(testSuitesToRun, function (err, exitCodes) { diff --git a/spec/git-repository-provider-spec.coffee b/spec/git-repository-provider-spec.coffee index 16ccf8938..186bc7f63 100644 --- a/spec/git-repository-provider-spec.coffee +++ b/spec/git-repository-provider-spec.coffee @@ -12,6 +12,9 @@ describe "GitRepositoryProvider", -> provider = new GitRepositoryProvider(atom.project, atom.config, atom.confirm) afterEach -> + if provider? + provider.pathToRepository[key].destroy() for key in Object.keys(provider.pathToRepository) + try temp.cleanupSync() diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 4ce84617a..6ce13c3f4 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -94,6 +94,8 @@ describe "Project", -> waitsForPromise -> atom.workspace.open('a') + notQuittingProject = null + quittingProject = null bufferA = null layerA = null markerA = null @@ -105,12 +107,14 @@ describe "Project", -> bufferA.append('!') waitsForPromise -> + notQuittingProject?.destroy() notQuittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm}) notQuittingProject.deserialize(atom.project.serialize({isUnloading: false})).then -> expect(notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).toBeUndefined() expect(notQuittingProject.getBuffers()[0].undo()).toBe(false) waitsForPromise -> + quittingProject?.destroy() quittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm}) quittingProject.deserialize(atom.project.serialize({isUnloading: true})).then -> expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).not.toBeUndefined() diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index b37acddd1..d777ecc3e 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -40,7 +40,6 @@ PaneContainer = require './pane-container' PaneAxis = require './pane-axis' Pane = require './pane' Dock = require './dock' -Project = require './project' TextEditor = require './text-editor' TextBuffer = require 'text-buffer' Gutter = require './gutter' From f00bc1e1adf4d9f776c2cbc309306bb3df0a000d Mon Sep 17 00:00:00 2001 From: Steven Hobson-Campbell Date: Tue, 5 Sep 2017 17:58:36 -0700 Subject: [PATCH 02/43] Switching from minimist -> yargs --- script/package.json | 1 - script/test | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/script/package.json b/script/package.json index 82500d12c..454d561aa 100644 --- a/script/package.json +++ b/script/package.json @@ -20,7 +20,6 @@ "legal-eagle": "0.14.0", "lodash.template": "4.4.0", "minidump": "0.9.0", - "minimist": "^1.2.0", "mkdirp": "0.5.1", "normalize-package-data": "2.3.5", "npm": "5.3.0", diff --git a/script/test b/script/test index 1b1453341..c4df0efad 100755 --- a/script/test +++ b/script/test @@ -3,7 +3,7 @@ 'use strict' require('colors') -const argv = require('minimist')(process.argv.slice(2)) +const argv = require('yargs').argv const assert = require('assert') const async = require('async') const childProcess = require('child_process') @@ -166,7 +166,7 @@ function testSuitesForPlatform (platform) { console.log(`Unrecognized platform: ${platform}`) } - if(argv.skipMainProccessTests) { + if(argv.skipMainProcessTests) { suites = suites.filter(suite => suite !== runCoreMainProcessTests); } From 80c81b9e03291c0872766d3b1c5a3e3e3dce636e Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 8 Sep 2017 15:56:48 -0700 Subject: [PATCH 03/43] PR feedback --- script/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/test b/script/test index c4df0efad..c6c3a6a61 100755 --- a/script/test +++ b/script/test @@ -166,7 +166,7 @@ function testSuitesForPlatform (platform) { console.log(`Unrecognized platform: ${platform}`) } - if(argv.skipMainProcessTests) { + if (argv.skipMainProcessTests) { suites = suites.filter(suite => suite !== runCoreMainProcessTests); } From 208e3293f3c2456f9e43a219cf719e59d675c8ca Mon Sep 17 00:00:00 2001 From: Jason Rudolph Date: Fri, 3 Nov 2017 08:14:24 -0400 Subject: [PATCH 04/43] =?UTF-8?q?=E2=98=A0=E2=98=95=20Decaffeinate=20src/t?= =?UTF-8?q?ext-utils.coffee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/text-utils.coffee | 121 --------------------------------------- src/text-utils.js | 130 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 121 deletions(-) delete mode 100644 src/text-utils.coffee create mode 100644 src/text-utils.js diff --git a/src/text-utils.coffee b/src/text-utils.coffee deleted file mode 100644 index f4d62772e..000000000 --- a/src/text-utils.coffee +++ /dev/null @@ -1,121 +0,0 @@ -isHighSurrogate = (charCode) -> - 0xD800 <= charCode <= 0xDBFF - -isLowSurrogate = (charCode) -> - 0xDC00 <= charCode <= 0xDFFF - -isVariationSelector = (charCode) -> - 0xFE00 <= charCode <= 0xFE0F - -isCombiningCharacter = (charCode) -> - 0x0300 <= charCode <= 0x036F or - 0x1AB0 <= charCode <= 0x1AFF or - 0x1DC0 <= charCode <= 0x1DFF or - 0x20D0 <= charCode <= 0x20FF or - 0xFE20 <= charCode <= 0xFE2F - -# Are the given character codes a high/low surrogate pair? -# -# * `charCodeA` The first character code {Number}. -# * `charCode2` The second character code {Number}. -# -# Return a {Boolean}. -isSurrogatePair = (charCodeA, charCodeB) -> - isHighSurrogate(charCodeA) and isLowSurrogate(charCodeB) - -# Are the given character codes a variation sequence? -# -# * `charCodeA` The first character code {Number}. -# * `charCode2` The second character code {Number}. -# -# Return a {Boolean}. -isVariationSequence = (charCodeA, charCodeB) -> - not isVariationSelector(charCodeA) and isVariationSelector(charCodeB) - -# Are the given character codes a combined character pair? -# -# * `charCodeA` The first character code {Number}. -# * `charCode2` The second character code {Number}. -# -# Return a {Boolean}. -isCombinedCharacter = (charCodeA, charCodeB) -> - not isCombiningCharacter(charCodeA) and isCombiningCharacter(charCodeB) - -# Is the character at the given index the start of high/low surrogate pair -# a variation sequence, or a combined character? -# -# * `string` The {String} to check for a surrogate pair, variation sequence, -# or combined character. -# * `index` The {Number} index to look for a surrogate pair, variation -# sequence, or combined character. -# -# Return a {Boolean}. -isPairedCharacter = (string, index=0) -> - charCodeA = string.charCodeAt(index) - charCodeB = string.charCodeAt(index + 1) - isSurrogatePair(charCodeA, charCodeB) or - isVariationSequence(charCodeA, charCodeB) or - isCombinedCharacter(charCodeA, charCodeB) - -IsJapaneseKanaCharacter = (charCode) -> - 0x3000 <= charCode <= 0x30FF - -isCJKUnifiedIdeograph = (charCode) -> - 0x4E00 <= charCode <= 0x9FFF - -isFullWidthForm = (charCode) -> - 0xFF01 <= charCode <= 0xFF5E or - 0xFFE0 <= charCode <= 0xFFE6 - -isDoubleWidthCharacter = (character) -> - charCode = character.charCodeAt(0) - - IsJapaneseKanaCharacter(charCode) or - isCJKUnifiedIdeograph(charCode) or - isFullWidthForm(charCode) - -isHalfWidthCharacter = (character) -> - charCode = character.charCodeAt(0) - - 0xFF65 <= charCode <= 0xFFDC or - 0xFFE8 <= charCode <= 0xFFEE - -isKoreanCharacter = (character) -> - charCode = character.charCodeAt(0) - - 0xAC00 <= charCode <= 0xD7A3 or - 0x1100 <= charCode <= 0x11FF or - 0x3130 <= charCode <= 0x318F or - 0xA960 <= charCode <= 0xA97F or - 0xD7B0 <= charCode <= 0xD7FF - -isCJKCharacter = (character) -> - isDoubleWidthCharacter(character) or - isHalfWidthCharacter(character) or - isKoreanCharacter(character) - -isWordStart = (previousCharacter, character) -> - (previousCharacter is ' ' or previousCharacter is '\t') and - (character isnt ' ' and character isnt '\t') - -isWrapBoundary = (previousCharacter, character) -> - isWordStart(previousCharacter, character) or isCJKCharacter(character) - -# Does the given string contain at least surrogate pair, variation sequence, -# or combined character? -# -# * `string` The {String} to check for the presence of paired characters. -# -# Returns a {Boolean}. -hasPairedCharacter = (string) -> - index = 0 - while index < string.length - return true if isPairedCharacter(string, index) - index++ - false - -module.exports = { - isPairedCharacter, hasPairedCharacter, - isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, - isWrapBoundary -} diff --git a/src/text-utils.js b/src/text-utils.js new file mode 100644 index 000000000..7dde49fd7 --- /dev/null +++ b/src/text-utils.js @@ -0,0 +1,130 @@ +const isHighSurrogate = (charCode) => + charCode >= 0xD800 && charCode <= 0xDBFF + +const isLowSurrogate = (charCode) => + charCode >= 0xDC00 && charCode <= 0xDFFF + +const isVariationSelector = (charCode) => + charCode >= 0xFE00 && charCode <= 0xFE0F + +const isCombiningCharacter = charCode => + (charCode >= 0x0300 && charCode <= 0x036F) || + (charCode >= 0x1AB0 && charCode <= 0x1AFF) || + (charCode >= 0x1DC0 && charCode <= 0x1DFF) || + (charCode >= 0x20D0 && charCode <= 0x20FF) || + (charCode >= 0xFE20 && charCode <= 0xFE2F) + +// Are the given character codes a high/low surrogate pair? +// +// * `charCodeA` The first character code {Number}. +// * `charCode2` The second character code {Number}. +// +// Return a {Boolean}. +const isSurrogatePair = (charCodeA, charCodeB) => + isHighSurrogate(charCodeA) && isLowSurrogate(charCodeB) + +// Are the given character codes a variation sequence? +// +// * `charCodeA` The first character code {Number}. +// * `charCode2` The second character code {Number}. +// +// Return a {Boolean}. +const isVariationSequence = (charCodeA, charCodeB) => + !isVariationSelector(charCodeA) && isVariationSelector(charCodeB) + +// Are the given character codes a combined character pair? +// +// * `charCodeA` The first character code {Number}. +// * `charCode2` The second character code {Number}. +// +// Return a {Boolean}. +const isCombinedCharacter = (charCodeA, charCodeB) => + !isCombiningCharacter(charCodeA) && isCombiningCharacter(charCodeB) + +// Is the character at the given index the start of high/low surrogate pair +// a variation sequence, or a combined character? +// +// * `string` The {String} to check for a surrogate pair, variation sequence, +// or combined character. +// * `index` The {Number} index to look for a surrogate pair, variation +// sequence, or combined character. +// +// Return a {Boolean}. +const isPairedCharacter = (string, index = 0) => { + const charCodeA = string.charCodeAt(index) + const charCodeB = string.charCodeAt(index + 1) + return isSurrogatePair(charCodeA, charCodeB) || + isVariationSequence(charCodeA, charCodeB) || + isCombinedCharacter(charCodeA, charCodeB) +} + +const IsJapaneseKanaCharacter = charCode => + charCode >= 0x3000 && charCode <= 0x30FF + +const isCJKUnifiedIdeograph = charCode => + charCode >= 0x4E00 && charCode <= 0x9FFF + +const isFullWidthForm = charCode => + (charCode >= 0xFF01 && charCode <= 0xFF5E) || + (charCode >= 0xFFE0 && charCode <= 0xFFE6) + +const isDoubleWidthCharacter = (character) => { + const charCode = character.charCodeAt(0) + + return IsJapaneseKanaCharacter(charCode) || + isCJKUnifiedIdeograph(charCode) || + isFullWidthForm(charCode) +} + +const isHalfWidthCharacter = (character) => { + const charCode = character.charCodeAt(0) + + return (charCode >= 0xFF65 && charCode <= 0xFFDC) || + (charCode >= 0xFFE8 && charCode <= 0xFFEE) +} + +const isKoreanCharacter = (character) => { + const charCode = character.charCodeAt(0) + + return (charCode >= 0xAC00 && charCode <= 0xD7A3) || + (charCode >= 0x1100 && charCode <= 0x11FF) || + (charCode >= 0x3130 && charCode <= 0x318F) || + (charCode >= 0xA960 && charCode <= 0xA97F) || + (charCode >= 0xD7B0 && charCode <= 0xD7FF) +} + +const isCJKCharacter = (character) => + isDoubleWidthCharacter(character) || + isHalfWidthCharacter(character) || + isKoreanCharacter(character) + +const isWordStart = (previousCharacter, character) => + ((previousCharacter === ' ') || (previousCharacter === '\t')) && + ((character !== ' ') && (character !== '\t')) + +const isWrapBoundary = (previousCharacter, character) => + isWordStart(previousCharacter, character) || isCJKCharacter(character) + +// Does the given string contain at least surrogate pair, variation sequence, +// or combined character? +// +// * `string` The {String} to check for the presence of paired characters. +// +// Returns a {Boolean}. +const hasPairedCharacter = (string) => { + let index = 0 + while (index < string.length) { + if (isPairedCharacter(string, index)) { return true } + index++ + } + return false +} + +module.exports = { + isPairedCharacter, + hasPairedCharacter, + isDoubleWidthCharacter, + isHalfWidthCharacter, + isKoreanCharacter, + isWrapBoundary +} From 3a42785678621eedb45870c5f6b24d30ffd1cf10 Mon Sep 17 00:00:00 2001 From: Jason Rudolph Date: Fri, 3 Nov 2017 08:21:01 -0400 Subject: [PATCH 05/43] =?UTF-8?q?=E2=98=A0=E2=98=95=20Decaffeinate=20spec/?= =?UTF-8?q?text-utils-spec.coffee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/text-utils-spec.coffee | 97 ------------------------------- spec/text-utils-spec.js | 110 ++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 97 deletions(-) delete mode 100644 spec/text-utils-spec.coffee create mode 100644 spec/text-utils-spec.js diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee deleted file mode 100644 index bae7f5997..000000000 --- a/spec/text-utils-spec.coffee +++ /dev/null @@ -1,97 +0,0 @@ -textUtils = require '../src/text-utils' - -describe 'text utilities', -> - describe '.hasPairedCharacter(string)', -> - it 'returns true when the string contains a surrogate pair, variation sequence, or combined character', -> - expect(textUtils.hasPairedCharacter('abc')).toBe false - expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe true - expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe true - expect(textUtils.hasPairedCharacter('\u2714\uFE0E')).toBe true - expect(textUtils.hasPairedCharacter('e\u0301')).toBe true - - expect(textUtils.hasPairedCharacter('\uD835')).toBe false - expect(textUtils.hasPairedCharacter('\uDF97')).toBe false - expect(textUtils.hasPairedCharacter('\uFE0E')).toBe false - expect(textUtils.hasPairedCharacter('\u0301')).toBe false - - expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe false - expect(textUtils.hasPairedCharacter('\u0301\u0301')).toBe false - - describe '.isPairedCharacter(string, index)', -> - it 'returns true when the index is the start of a high/low surrogate pair, variation sequence, or combined character', -> - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe false - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe true - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe false - expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe false - - expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 0)).toBe false - expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 1)).toBe true - expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 2)).toBe false - expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe false - - expect(textUtils.isPairedCharacter('\uD835')).toBe false - expect(textUtils.isPairedCharacter('\uDF97')).toBe false - expect(textUtils.isPairedCharacter('\uFE0E')).toBe false - expect(textUtils.isPairedCharacter('\uFE0E')).toBe false - - expect(textUtils.isPairedCharacter('\uFE0E\uFE0E')).toBe false - - expect(textUtils.isPairedCharacter('ae\u0301c', 0)).toBe false - expect(textUtils.isPairedCharacter('ae\u0301c', 1)).toBe true - expect(textUtils.isPairedCharacter('ae\u0301c', 2)).toBe false - expect(textUtils.isPairedCharacter('ae\u0301c', 3)).toBe false - expect(textUtils.isPairedCharacter('ae\u0301c', 4)).toBe false - - describe ".isDoubleWidthCharacter(character)", -> - it "returns true when the character is either japanese, chinese or a full width form", -> - expect(textUtils.isDoubleWidthCharacter("我")).toBe(true) - - expect(textUtils.isDoubleWidthCharacter("私")).toBe(true) - - expect(textUtils.isDoubleWidthCharacter("B")).toBe(true) - expect(textUtils.isDoubleWidthCharacter(",")).toBe(true) - expect(textUtils.isDoubleWidthCharacter("¢")).toBe(true) - - expect(textUtils.isDoubleWidthCharacter("a")).toBe(false) - - describe ".isHalfWidthCharacter(character)", -> - it "returns true when the character is an half width form", -> - expect(textUtils.isHalfWidthCharacter("ハ")).toBe(true) - expect(textUtils.isHalfWidthCharacter("ヒ")).toBe(true) - expect(textUtils.isHalfWidthCharacter("ᆲ")).toBe(true) - expect(textUtils.isHalfWidthCharacter("■")).toBe(true) - - expect(textUtils.isHalfWidthCharacter("B")).toBe(false) - - describe ".isKoreanCharacter(character)", -> - it "returns true when the character is a korean character", -> - expect(textUtils.isKoreanCharacter("우")).toBe(true) - expect(textUtils.isKoreanCharacter("가")).toBe(true) - expect(textUtils.isKoreanCharacter("ㅢ")).toBe(true) - expect(textUtils.isKoreanCharacter("ㄼ")).toBe(true) - - expect(textUtils.isKoreanCharacter("O")).toBe(false) - - describe ".isWrapBoundary(previousCharacter, character)", -> - it "returns true when the character is CJK or when the previous character is a space/tab", -> - anyCharacter = 'x' - expect(textUtils.isWrapBoundary(anyCharacter, "我")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "私")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "B")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, ",")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "¢")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "ハ")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "ヒ")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "ᆲ")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "■")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "우")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "가")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "ㅢ")).toBe(true) - expect(textUtils.isWrapBoundary(anyCharacter, "ㄼ")).toBe(true) - - expect(textUtils.isWrapBoundary(' ', 'h')).toBe(true) - expect(textUtils.isWrapBoundary('\t', 'h')).toBe(true) - expect(textUtils.isWrapBoundary('a', 'h')).toBe(false) diff --git a/spec/text-utils-spec.js b/spec/text-utils-spec.js new file mode 100644 index 000000000..3a4b29866 --- /dev/null +++ b/spec/text-utils-spec.js @@ -0,0 +1,110 @@ +const textUtils = require('../src/text-utils') + +describe('text utilities', () => { + describe('.hasPairedCharacter(string)', () => + it('returns true when the string contains a surrogate pair, variation sequence, or combined character', () => { + expect(textUtils.hasPairedCharacter('abc')).toBe(false) + expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe(true) + expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe(true) + expect(textUtils.hasPairedCharacter('\u2714\uFE0E')).toBe(true) + expect(textUtils.hasPairedCharacter('e\u0301')).toBe(true) + + expect(textUtils.hasPairedCharacter('\uD835')).toBe(false) + expect(textUtils.hasPairedCharacter('\uDF97')).toBe(false) + expect(textUtils.hasPairedCharacter('\uFE0E')).toBe(false) + expect(textUtils.hasPairedCharacter('\u0301')).toBe(false) + + expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe(false) + expect(textUtils.hasPairedCharacter('\u0301\u0301')).toBe(false) + }) + ) + + describe('.isPairedCharacter(string, index)', () => + it('returns true when the index is the start of a high/low surrogate pair, variation sequence, or combined character', () => { + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe(false) + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe(true) + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe(false) + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe(false) + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe(true) + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe(false) + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe(false) + + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 0)).toBe(false) + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 1)).toBe(true) + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 2)).toBe(false) + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe(false) + + expect(textUtils.isPairedCharacter('\uD835')).toBe(false) + expect(textUtils.isPairedCharacter('\uDF97')).toBe(false) + expect(textUtils.isPairedCharacter('\uFE0E')).toBe(false) + expect(textUtils.isPairedCharacter('\uFE0E')).toBe(false) + + expect(textUtils.isPairedCharacter('\uFE0E\uFE0E')).toBe(false) + + expect(textUtils.isPairedCharacter('ae\u0301c', 0)).toBe(false) + expect(textUtils.isPairedCharacter('ae\u0301c', 1)).toBe(true) + expect(textUtils.isPairedCharacter('ae\u0301c', 2)).toBe(false) + expect(textUtils.isPairedCharacter('ae\u0301c', 3)).toBe(false) + expect(textUtils.isPairedCharacter('ae\u0301c', 4)).toBe(false) + }) + ) + + describe('.isDoubleWidthCharacter(character)', () => + it('returns true when the character is either japanese, chinese or a full width form', () => { + expect(textUtils.isDoubleWidthCharacter('我')).toBe(true) + + expect(textUtils.isDoubleWidthCharacter('私')).toBe(true) + + expect(textUtils.isDoubleWidthCharacter('B')).toBe(true) + expect(textUtils.isDoubleWidthCharacter(',')).toBe(true) + expect(textUtils.isDoubleWidthCharacter('¢')).toBe(true) + + expect(textUtils.isDoubleWidthCharacter('a')).toBe(false) + }) + ) + + describe('.isHalfWidthCharacter(character)', () => + it('returns true when the character is an half width form', () => { + expect(textUtils.isHalfWidthCharacter('ハ')).toBe(true) + expect(textUtils.isHalfWidthCharacter('ヒ')).toBe(true) + expect(textUtils.isHalfWidthCharacter('ᆲ')).toBe(true) + expect(textUtils.isHalfWidthCharacter('■')).toBe(true) + + expect(textUtils.isHalfWidthCharacter('B')).toBe(false) + }) + ) + + describe('.isKoreanCharacter(character)', () => + it('returns true when the character is a korean character', () => { + expect(textUtils.isKoreanCharacter('우')).toBe(true) + expect(textUtils.isKoreanCharacter('가')).toBe(true) + expect(textUtils.isKoreanCharacter('ㅢ')).toBe(true) + expect(textUtils.isKoreanCharacter('ㄼ')).toBe(true) + + expect(textUtils.isKoreanCharacter('O')).toBe(false) + }) + ) + + describe('.isWrapBoundary(previousCharacter, character)', () => + it('returns true when the character is CJK or when the previous character is a space/tab', () => { + const anyCharacter = 'x' + expect(textUtils.isWrapBoundary(anyCharacter, '我')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, '私')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, 'B')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, ',')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, '¢')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, 'ハ')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, 'ヒ')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, 'ᆲ')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, '■')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, '우')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, '가')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, 'ㅢ')).toBe(true) + expect(textUtils.isWrapBoundary(anyCharacter, 'ㄼ')).toBe(true) + + expect(textUtils.isWrapBoundary(' ', 'h')).toBe(true) + expect(textUtils.isWrapBoundary('\t', 'h')).toBe(true) + expect(textUtils.isWrapBoundary('a', 'h')).toBe(false) + }) + ) +}) From a1e8d25e55a2456be91c46ec0b785a8c9a288dce Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 3 Nov 2017 09:57:29 -0600 Subject: [PATCH 06/43] Autoscroll to cursor position after folding or unfolding There were several different fold/unfold code paths, so I decided to weave simple autoscroll assertions into the existing tests. --- spec/text-editor-spec.js | 26 +++++++++++++++++++++++--- src/text-editor.js | 28 ++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 382d020d4..84eea43ef 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -115,12 +115,12 @@ describe('TextEditor', () => { editor.update({showCursorOnSelection: false}) editor.setSelectedBufferRange([[1, 2], [3, 4]]) editor.addSelectionForBufferRange([[5, 6], [7, 8]], {reversed: true}) + editor.foldBufferRow(4) + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() editor.setScrollTopRow(3) expect(editor.getScrollTopRow()).toBe(3) editor.setScrollLeftColumn(4) expect(editor.getScrollLeftColumn()).toBe(4) - editor.foldBufferRow(4) - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() const editor2 = editor.copy() const element2 = editor2.getElement() @@ -7002,15 +7002,19 @@ describe('TextEditor', () => { }) describe('.unfoldAll()', () => { - it('unfolds every folded line', async () => { + it('unfolds every folded line and autoscrolls', async () => { editor = await atom.workspace.open('sample.js', {autoIndent: false}) + const autoscrollEvents = [] + editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) const initialScreenLineCount = editor.getScreenLineCount() editor.foldBufferRow(0) editor.foldBufferRow(1) expect(editor.getScreenLineCount()).toBeLessThan(initialScreenLineCount) + expect(autoscrollEvents.length).toBe(1) editor.unfoldAll() expect(editor.getScreenLineCount()).toBe(initialScreenLineCount) + expect(autoscrollEvents.length).toBe(2) }) it('unfolds every folded line with comments', async () => { @@ -7028,8 +7032,11 @@ describe('TextEditor', () => { describe('.foldAll()', () => { it('folds every foldable line', async () => { editor = await atom.workspace.open('sample.js', {autoIndent: false}) + const autoscrollEvents = [] + editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) editor.foldAll() + expect(autoscrollEvents.length).toBe(1) const [fold1, fold2, fold3] = editor.unfoldAll() expect([fold1.start.row, fold1.end.row]).toEqual([0, 12]) expect([fold2.start.row, fold2.end.row]).toEqual([1, 9]) @@ -7060,7 +7067,11 @@ describe('TextEditor', () => { describe('when bufferRow can be folded', () => { it('creates a fold based on the syntactic region starting at the given row', () => { + const autoscrollEvents = [] + editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) + editor.foldBufferRow(1) + expect(autoscrollEvents.length).toBe(1) const [fold] = editor.unfoldAll() expect([fold.start.row, fold.end.row]).toEqual([1, 9]) }) @@ -7107,10 +7118,14 @@ describe('TextEditor', () => { describe('.foldCurrentRow()', () => { it('creates a fold at the location of the last cursor', async () => { editor = await atom.workspace.open() + editor.setText('\nif (x) {\n y()\n}') editor.setCursorBufferPosition([1, 0]) expect(editor.getScreenLineCount()).toBe(4) + const autoscrollEvents = [] + editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) editor.foldCurrentRow() + expect(autoscrollEvents.length).toBe(1) expect(editor.getScreenLineCount()).toBe(3) }) @@ -7127,21 +7142,26 @@ describe('TextEditor', () => { describe('.foldAllAtIndentLevel(indentLevel)', () => { it('folds blocks of text at the given indentation level', async () => { editor = await atom.workspace.open('sample.js', {autoIndent: false}) + const autoscrollEvents = [] + editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) editor.foldAllAtIndentLevel(0) expect(editor.lineTextForScreenRow(0)).toBe(`var quicksort = function () {${editor.displayLayer.foldCharacter}`) expect(editor.getLastScreenRow()).toBe(0) + expect(autoscrollEvents.length).toBe(1) editor.foldAllAtIndentLevel(1) expect(editor.lineTextForScreenRow(0)).toBe('var quicksort = function () {') expect(editor.lineTextForScreenRow(1)).toBe(` var sort = function(items) {${editor.displayLayer.foldCharacter}`) expect(editor.getLastScreenRow()).toBe(4) + expect(autoscrollEvents.length).toBe(2) editor.foldAllAtIndentLevel(2) expect(editor.lineTextForScreenRow(0)).toBe('var quicksort = function () {') expect(editor.lineTextForScreenRow(1)).toBe(' var sort = function(items) {') expect(editor.lineTextForScreenRow(2)).toBe(' if (items.length <= 1) return items;') expect(editor.getLastScreenRow()).toBe(9) + expect(autoscrollEvents.length).toBe(3) }) it('folds every foldable range at a given indentLevel', async () => { diff --git a/src/text-editor.js b/src/text-editor.js index a0b9d19a0..8eee5c140 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -3750,13 +3750,19 @@ class TextEditor { foldCurrentRow () { const {row} = this.getCursorBufferPosition() const range = this.tokenizedBuffer.getFoldableRangeContainingPoint(Point(row, Infinity)) - if (range) return this.displayLayer.foldBufferRange(range) + if (range) { + const result = this.displayLayer.foldBufferRange(range) + this.scrollToCursorPosition() + return result + } } // Essential: Unfold the most recent cursor's row by one level. unfoldCurrentRow () { const {row} = this.getCursorBufferPosition() - return this.displayLayer.destroyFoldsContainingBufferPositions([Point(row, Infinity)], false) + const result = this.displayLayer.destroyFoldsContainingBufferPositions([Point(row, Infinity)], false) + this.scrollToCursorPosition() + return result } // Essential: Fold the given row in buffer coordinates based on its indentation @@ -3774,6 +3780,7 @@ class TextEditor { const existingFolds = this.displayLayer.foldsIntersectingBufferRange(Range(foldableRange.start, foldableRange.start)) if (existingFolds.length === 0) { this.displayLayer.foldBufferRange(foldableRange) + this.scrollToCursorPosition() } else { const firstExistingFoldRange = this.displayLayer.bufferRangeForFold(existingFolds[0]) if (firstExistingFoldRange.start.isLessThan(position)) { @@ -3791,7 +3798,9 @@ class TextEditor { // * `bufferRow` A {Number} unfoldBufferRow (bufferRow) { const position = Point(bufferRow, Infinity) - return this.displayLayer.destroyFoldsContainingBufferPositions([position]) + const result = this.displayLayer.destroyFoldsContainingBufferPositions([position]) + this.scrollToCursorPosition() + return result } // Extended: For each selection, fold the rows it intersects. @@ -3807,6 +3816,7 @@ class TextEditor { for (let range of this.tokenizedBuffer.getFoldableRanges(this.getTabLength())) { this.displayLayer.foldBufferRange(range) } + this.scrollToCursorPosition() } // Extended: Unfold all existing folds. @@ -3824,6 +3834,7 @@ class TextEditor { for (let range of this.tokenizedBuffer.getFoldableRangesAtIndentLevel(level, this.getTabLength())) { this.displayLayer.foldBufferRange(range) } + this.scrollToCursorPosition() } // Extended: Determine whether the given row in buffer coordinates is foldable. @@ -3851,11 +3862,14 @@ class TextEditor { // Extended: Fold the given buffer row if it isn't currently folded, and unfold // it otherwise. toggleFoldAtBufferRow (bufferRow) { + let result if (this.isFoldedAtBufferRow(bufferRow)) { - return this.unfoldBufferRow(bufferRow) + result = this.unfoldBufferRow(bufferRow) } else { - return this.foldBufferRow(bufferRow) + result = this.foldBufferRow(bufferRow) } + this.scrollToCursorPosition() + return result } // Extended: Determine whether the most recently added cursor's row is folded. @@ -3894,7 +3908,9 @@ class TextEditor { // // Returns the new {Fold}. foldBufferRowRange (startRow, endRow) { - return this.foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity))) + const result = this.foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity))) + this.scrollToCursorPosition() + return result } foldBufferRange (range) { From 77c685a1b7d285cc58e5fc4a59c7e190109becd9 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Mon, 6 Nov 2017 22:50:53 +0100 Subject: [PATCH 07/43] Use wheelDelta instead of delta In Atom 1.19 we changed the scroll handler to use deltaX and deltaY instead of wheelDeltaX/wheelDeltaY. wheelDelta is larger so this caused the scrolling speed to slow down. This change in speed was especially noticable on Linux --- src/text-editor-component.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 91ea18361..82ca7b676 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -1510,28 +1510,28 @@ class TextEditorComponent { didMouseWheel (event) { const scrollSensitivity = this.props.model.getScrollSensitivity() / 100 - let {deltaX, deltaY} = event + let {wheelDeltaX, wheelDeltaY} = event - if (Math.abs(deltaX) > Math.abs(deltaY)) { - deltaX = (Math.sign(deltaX) === 1) - ? Math.max(1, deltaX * scrollSensitivity) - : Math.min(-1, deltaX * scrollSensitivity) - deltaY = 0 + if (Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)) { + wheelDeltaX = (Math.sign(wheelDeltaX) === 1) + ? Math.max(1, wheelDeltaX * scrollSensitivity) + : Math.min(-1, wheelDeltaX * scrollSensitivity) + wheelDeltaY = 0 } else { - deltaX = 0 - deltaY = (Math.sign(deltaY) === 1) - ? Math.max(1, deltaY * scrollSensitivity) - : Math.min(-1, deltaY * scrollSensitivity) + wheelDeltaX = 0 + wheelDeltaY = (Math.sign(wheelDeltaY) === 1) + ? Math.max(1, wheelDeltaY * scrollSensitivity) + : Math.min(-1, wheelDeltaY * scrollSensitivity) } if (this.getPlatform() !== 'darwin' && event.shiftKey) { - let temp = deltaX - deltaX = deltaY - deltaY = temp + let temp = wheelDeltaX + wheelDeltaX = wheelDeltaY + wheelDeltaY = temp } - const scrollLeftChanged = deltaX !== 0 && this.setScrollLeft(this.getScrollLeft() + deltaX) - const scrollTopChanged = deltaY !== 0 && this.setScrollTop(this.getScrollTop() + deltaY) + const scrollLeftChanged = wheelDeltaX !== 0 && this.setScrollLeft(this.getScrollLeft() - wheelDeltaX) + const scrollTopChanged = wheelDeltaY !== 0 && this.setScrollTop(this.getScrollTop() - wheelDeltaY) if (scrollLeftChanged || scrollTopChanged) this.updateSync() } From bea80947654cf52b0af09a7487365448eefa4fe6 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Mon, 6 Nov 2017 23:53:55 +0100 Subject: [PATCH 08/43] Make the tests pass --- spec/text-editor-component-spec.js | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 992785d6e..323b980a2 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -25,7 +25,7 @@ document.registerElement('text-editor-component-test-element', { }) }) -describe('TextEditorComponent', () => { +fdescribe('TextEditorComponent', () => { beforeEach(() => { jasmine.useRealClock() @@ -1325,7 +1325,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = 20 * (scrollSensitivity / 100) const expectedScrollLeft = component.getScrollLeft() - component.didMouseWheel({deltaX: 5, deltaY: 20}) + component.didMouseWheel({wheelDeltaX: -5, wheelDeltaY: -20}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`) @@ -1334,7 +1334,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = component.getScrollTop() - (10 * (scrollSensitivity / 100)) const expectedScrollLeft = component.getScrollLeft() - component.didMouseWheel({deltaX: 5, deltaY: -10}) + component.didMouseWheel({wheelDeltaX: -5, wheelDeltaY: 10}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`) @@ -1343,7 +1343,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = component.getScrollTop() const expectedScrollLeft = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 20, deltaY: -10}) + component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 10}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`) @@ -1352,7 +1352,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = component.getScrollTop() const expectedScrollLeft = component.getScrollLeft() - (10 * (scrollSensitivity / 100)) - component.didMouseWheel({deltaX: -10, deltaY: 8}) + component.didMouseWheel({wheelDeltaX: 10, wheelDeltaY: -8}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`) @@ -1364,14 +1364,14 @@ describe('TextEditorComponent', () => { const {component, editor} = buildComponent({height: 50, width: 50, scrollSensitivity}) { - component.didMouseWheel({deltaX: 0, deltaY: 3}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -3}) expect(component.getScrollTop()).toBe(1) expect(component.getScrollLeft()).toBe(0) expect(component.refs.content.style.transform).toBe(`translate(0px, -1px)`) } { - component.didMouseWheel({deltaX: 4, deltaY: 0}) + component.didMouseWheel({wheelDeltaX: -4, wheelDeltaY: 0}) expect(component.getScrollTop()).toBe(1) expect(component.getScrollLeft()).toBe(1) expect(component.refs.content.style.transform).toBe(`translate(-1px, -1px)`) @@ -1379,14 +1379,14 @@ describe('TextEditorComponent', () => { editor.update({scrollSensitivity: 100}) { - component.didMouseWheel({deltaX: 0, deltaY: -0.3}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: 0.3}) expect(component.getScrollTop()).toBe(0) expect(component.getScrollLeft()).toBe(1) expect(component.refs.content.style.transform).toBe(`translate(-1px, 0px)`) } { - component.didMouseWheel({deltaX: -0.1, deltaY: 0}) + component.didMouseWheel({wheelDeltaX: 0.1, wheelDeltaY: 0}) expect(component.getScrollTop()).toBe(0) expect(component.getScrollLeft()).toBe(0) expect(component.refs.content.style.transform).toBe(`translate(0px, 0px)`) @@ -1400,7 +1400,7 @@ describe('TextEditorComponent', () => { component.props.platform = 'linux' { const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 0, deltaY: 20}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`) await setScrollTop(component, 0) @@ -1408,7 +1408,7 @@ describe('TextEditorComponent', () => { { const expectedScrollLeft = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 0, deltaY: 20, shiftKey: true}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true}) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(-${expectedScrollLeft}px, 0px)`) await setScrollLeft(component, 0) @@ -1416,7 +1416,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 20, deltaY: 0, shiftKey: true}) + component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`) await setScrollTop(component, 0) @@ -1425,7 +1425,7 @@ describe('TextEditorComponent', () => { component.props.platform = 'win32' { const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 0, deltaY: 20}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`) await setScrollTop(component, 0) @@ -1433,7 +1433,7 @@ describe('TextEditorComponent', () => { { const expectedScrollLeft = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 0, deltaY: 20, shiftKey: true}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true}) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(-${expectedScrollLeft}px, 0px)`) await setScrollLeft(component, 0) @@ -1441,7 +1441,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 20, deltaY: 0, shiftKey: true}) + component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`) await setScrollTop(component, 0) @@ -1450,7 +1450,7 @@ describe('TextEditorComponent', () => { component.props.platform = 'darwin' { const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 0, deltaY: 20}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`) await setScrollTop(component, 0) @@ -1458,7 +1458,7 @@ describe('TextEditorComponent', () => { { const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 0, deltaY: 20, shiftKey: true}) + component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true}) expect(component.getScrollTop()).toBe(expectedScrollTop) expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`) await setScrollTop(component, 0) @@ -1466,7 +1466,7 @@ describe('TextEditorComponent', () => { { const expectedScrollLeft = 20 * (scrollSensitivity / 100) - component.didMouseWheel({deltaX: 20, deltaY: 0, shiftKey: true}) + component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true}) expect(component.getScrollLeft()).toBe(expectedScrollLeft) expect(component.refs.content.style.transform).toBe(`translate(-${expectedScrollLeft}px, 0px)`) await setScrollLeft(component, 0) From 9f83c4b15bcc6ea0923f14752292b443584d60de Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Mon, 6 Nov 2017 23:55:24 +0100 Subject: [PATCH 09/43] And don't focus the test --- spec/text-editor-component-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 323b980a2..fbc8eb0e1 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -25,7 +25,7 @@ document.registerElement('text-editor-component-test-element', { }) }) -fdescribe('TextEditorComponent', () => { +describe('TextEditorComponent', () => { beforeEach(() => { jasmine.useRealClock() From 3b5948c11059fde31a73bc8c767b3519471fb2b7 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 6 Nov 2017 15:08:51 -0800 Subject: [PATCH 10/43] :arrow_up: github@0.8.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b010cd4a0..e87d07cb0 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "exception-reporting": "0.41.5", "find-and-replace": "0.213.0", "fuzzy-finder": "1.7.3", - "github": "0.8.1", + "github": "0.8.2", "git-diff": "1.3.6", "go-to-line": "0.32.1", "grammar-selector": "0.49.8", From 31eafc4622c55c5db50df5fd50664f9f9b5336eb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 6 Nov 2017 17:32:29 -0800 Subject: [PATCH 11/43] Convert atom-environment-spec to JS --- spec/atom-environment-spec.coffee | 711 ------------------------- spec/atom-environment-spec.js | 830 ++++++++++++++++++++++++++++++ 2 files changed, 830 insertions(+), 711 deletions(-) delete mode 100644 spec/atom-environment-spec.coffee create mode 100644 spec/atom-environment-spec.js diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee deleted file mode 100644 index f178bbb6c..000000000 --- a/spec/atom-environment-spec.coffee +++ /dev/null @@ -1,711 +0,0 @@ -_ = require 'underscore-plus' -path = require 'path' -temp = require('temp').track() -AtomEnvironment = require '../src/atom-environment' -StorageFolder = require '../src/storage-folder' - -describe "AtomEnvironment", -> - afterEach -> - try - temp.cleanupSync() - - describe 'window sizing methods', -> - describe '::getPosition and ::setPosition', -> - originalPosition = null - beforeEach -> - originalPosition = atom.getPosition() - - afterEach -> - atom.setPosition(originalPosition.x, originalPosition.y) - - it 'sets the position of the window, and can retrieve the position just set', -> - atom.setPosition(22, 45) - expect(atom.getPosition()).toEqual x: 22, y: 45 - - describe '::getSize and ::setSize', -> - originalSize = null - beforeEach -> - originalSize = atom.getSize() - afterEach -> - atom.setSize(originalSize.width, originalSize.height) - - it 'sets the size of the window, and can retrieve the size just set', -> - newWidth = originalSize.width - 12 - newHeight = originalSize.height - 23 - waitsForPromise -> - atom.setSize(newWidth, newHeight) - runs -> - expect(atom.getSize()).toEqual width: newWidth, height: newHeight - - describe ".isReleasedVersion()", -> - it "returns false if the version is a SHA and true otherwise", -> - version = '0.1.0' - spyOn(atom, 'getVersion').andCallFake -> version - expect(atom.isReleasedVersion()).toBe true - version = '36b5518' - expect(atom.isReleasedVersion()).toBe false - - describe "loading default config", -> - it 'loads the default core config schema', -> - expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe true - expect(atom.config.get('core.followSymlinks')).toBe true - expect(atom.config.get('editor.showInvisibles')).toBe false - - describe "window onerror handler", -> - devToolsPromise = null - beforeEach -> - devToolsPromise = Promise.resolve() - spyOn(atom, 'openDevTools').andReturn(devToolsPromise) - spyOn(atom, 'executeJavaScriptInDevTools') - - it "will open the dev tools when an error is triggered", -> - try - a + 1 - catch e - window.onerror.call(window, e.toString(), 'abc', 2, 3, e) - - waitsForPromise -> devToolsPromise - runs -> - expect(atom.openDevTools).toHaveBeenCalled() - expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled() - - describe "::onWillThrowError", -> - willThrowSpy = null - beforeEach -> - willThrowSpy = jasmine.createSpy() - - it "is called when there is an error", -> - error = null - atom.onWillThrowError(willThrowSpy) - try - a + 1 - catch e - error = e - window.onerror.call(window, e.toString(), 'abc', 2, 3, e) - - delete willThrowSpy.mostRecentCall.args[0].preventDefault - expect(willThrowSpy).toHaveBeenCalledWith - message: error.toString() - url: 'abc' - line: 2 - column: 3 - originalError: error - - it "will not show the devtools when preventDefault() is called", -> - willThrowSpy.andCallFake (errorObject) -> errorObject.preventDefault() - atom.onWillThrowError(willThrowSpy) - - try - a + 1 - catch e - window.onerror.call(window, e.toString(), 'abc', 2, 3, e) - - expect(willThrowSpy).toHaveBeenCalled() - expect(atom.openDevTools).not.toHaveBeenCalled() - expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled() - - describe "::onDidThrowError", -> - didThrowSpy = null - beforeEach -> - didThrowSpy = jasmine.createSpy() - - it "is called when there is an error", -> - error = null - atom.onDidThrowError(didThrowSpy) - try - a + 1 - catch e - error = e - window.onerror.call(window, e.toString(), 'abc', 2, 3, e) - expect(didThrowSpy).toHaveBeenCalledWith - message: error.toString() - url: 'abc' - line: 2 - column: 3 - originalError: error - - describe ".assert(condition, message, callback)", -> - errors = null - - beforeEach -> - errors = [] - spyOn(atom, 'isReleasedVersion').andReturn(true) - atom.onDidFailAssertion (error) -> errors.push(error) - - describe "if the condition is false", -> - it "notifies onDidFailAssertion handlers with an error object based on the call site of the assertion", -> - result = atom.assert(false, "a == b") - expect(result).toBe false - expect(errors.length).toBe 1 - expect(errors[0].message).toBe "Assertion failed: a == b" - expect(errors[0].stack).toContain('atom-environment-spec') - - describe "if passed a callback function", -> - it "calls the callback with the assertion failure's error object", -> - error = null - atom.assert(false, "a == b", (e) -> error = e) - expect(error).toBe errors[0] - - describe "if passed metadata", -> - it "assigns the metadata on the assertion failure's error object", -> - atom.assert(false, "a == b", {foo: 'bar'}) - expect(errors[0].metadata).toEqual {foo: 'bar'} - - describe "when Atom has been built from source", -> - it "throws an error", -> - atom.isReleasedVersion.andReturn(false) - expect(-> atom.assert(false, 'testing')).toThrow('Assertion failed: testing') - - describe "if the condition is true", -> - it "does nothing", -> - result = atom.assert(true, "a == b") - expect(result).toBe true - expect(errors).toEqual [] - - describe "saving and loading", -> - beforeEach -> - atom.enablePersistence = true - - afterEach -> - 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(), - initialPaths: [dir1] - windowState: null - - spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings - spyOn(atom, 'serialize').andReturn({stuff: 'cool'}) - - atom.project.setPaths([dir1, dir2]) - # State persistence will fail if other Atom instances are running - waitsForPromise -> - atom.stateStore.connect().then (isConnected) -> - expect(isConnected).toBe true - - waitsForPromise -> - atom.saveState().then -> - atom.loadState().then (state) -> - expect(state).toBeFalsy() - - waitsForPromise -> - loadSettings.initialPaths = [dir2, dir1] - atom.loadState().then (state) -> - expect(state).toEqual({stuff: 'cool'}) - - it "loads state from the storage folder when it can't be found in atom.stateStore", -> - jasmine.useRealClock() - - storageFolderState = {foo: 1, bar: 2} - serializedState = {someState: 42} - loadSettings = _.extend(atom.getLoadSettings(), {initialPaths: [temp.mkdirSync("project-directory")]}) - spyOn(atom, 'getLoadSettings').andReturn(loadSettings) - spyOn(atom, 'serialize').andReturn(serializedState) - spyOn(atom, 'getStorageFolder').andReturn(new StorageFolder(temp.mkdirSync("config-directory"))) - atom.project.setPaths(atom.getLoadSettings().initialPaths) - - waitsForPromise -> - atom.stateStore.connect() - - runs -> - atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState) - - waitsForPromise -> - atom.loadState().then (state) -> expect(state).toEqual(storageFolderState) - - waitsForPromise -> - atom.saveState() - - waitsForPromise -> - atom.loadState().then (state) -> expect(state).toEqual(serializedState) - - it "saves state when the CPU is idle after a keydown or mousedown event", -> - atomEnv = new AtomEnvironment({ - applicationDelegate: global.atom.applicationDelegate, - }) - idleCallbacks = [] - atomEnv.initialize({ - window: { - requestIdleCallback: (callback) -> idleCallbacks.push(callback), - addEventListener: -> - removeEventListener: -> - }, - document: document.implementation.createHTMLDocument() - }) - - spyOn(atomEnv, 'saveState') - - keydown = new KeyboardEvent('keydown') - atomEnv.document.dispatchEvent(keydown) - advanceClock atomEnv.saveStateDebounceInterval - idleCallbacks.shift()() - expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false}) - expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true}) - - atomEnv.saveState.reset() - mousedown = new MouseEvent('mousedown') - atomEnv.document.dispatchEvent(mousedown) - advanceClock atomEnv.saveStateDebounceInterval - idleCallbacks.shift()() - expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false}) - expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true}) - - atomEnv.destroy() - - it "ignores mousedown/keydown events happening after calling unloadEditorWindow", -> - atomEnv = new AtomEnvironment({ - applicationDelegate: global.atom.applicationDelegate, - }) - idleCallbacks = [] - atomEnv.initialize({ - window: { - requestIdleCallback: (callback) -> idleCallbacks.push(callback), - addEventListener: -> - removeEventListener: -> - }, - document: document.implementation.createHTMLDocument() - }) - - spyOn(atomEnv, 'saveState') - - mousedown = new MouseEvent('mousedown') - atomEnv.document.dispatchEvent(mousedown) - atomEnv.unloadEditorWindow() - expect(atomEnv.saveState).not.toHaveBeenCalled() - - advanceClock atomEnv.saveStateDebounceInterval - idleCallbacks.shift()() - expect(atomEnv.saveState).not.toHaveBeenCalled() - - mousedown = new MouseEvent('mousedown') - atomEnv.document.dispatchEvent(mousedown) - advanceClock atomEnv.saveStateDebounceInterval - idleCallbacks.shift()() - expect(atomEnv.saveState).not.toHaveBeenCalled() - - atomEnv.destroy() - - it "serializes the project state with all the options supplied in saveState", -> - spyOn(atom.project, 'serialize').andReturn({foo: 42}) - - waitsForPromise -> atom.saveState({anyOption: 'any option'}) - runs -> - expect(atom.project.serialize.calls.length).toBe(1) - expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'}) - - it "serializes the text editor registry", -> - editor = null - - waitsForPromise -> - atom.workspace.open('sample.js').then (e) -> editor = e - - waitsForPromise -> - atom.textEditors.setGrammarOverride(editor, 'text.plain') - - atom2 = new AtomEnvironment({ - applicationDelegate: atom.applicationDelegate, - window: document.createElement('div'), - document: Object.assign( - document.createElement('div'), - { - body: document.createElement('div'), - head: document.createElement('div'), - } - ) - }) - atom2.initialize({document, window}) - atom2.deserialize(atom.serialize()).then -> - expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain') - atom2.destroy() - - describe "deserialization failures", -> - - it "propagates project state restoration failures", -> - spyOn(atom.project, 'deserialize').andCallFake -> - err = new Error('deserialization failure') - err.missingProjectPaths = ['/foo'] - Promise.reject(err) - spyOn(atom.notifications, 'addError') - - waitsForPromise -> atom.deserialize({project: 'should work'}) - runs -> - expect(atom.notifications.addError).toHaveBeenCalledWith 'Unable to open project directory', - {description: 'Project directory `/foo` is no longer on disk.'} - - it "accumulates and reports two errors with one notification", -> - spyOn(atom.project, 'deserialize').andCallFake -> - err = new Error('deserialization failure') - err.missingProjectPaths = ['/foo', '/wat'] - Promise.reject(err) - spyOn(atom.notifications, 'addError') - - waitsForPromise -> atom.deserialize({project: 'should work'}) - runs -> - expect(atom.notifications.addError).toHaveBeenCalledWith 'Unable to open 2 project directories', - {description: 'Project directories `/foo` and `/wat` are no longer on disk.'} - - it "accumulates and reports three+ errors with one notification", -> - spyOn(atom.project, 'deserialize').andCallFake -> - err = new Error('deserialization failure') - err.missingProjectPaths = ['/foo', '/wat', '/stuff', '/things'] - Promise.reject(err) - spyOn(atom.notifications, 'addError') - - waitsForPromise -> atom.deserialize({project: 'should work'}) - runs -> - expect(atom.notifications.addError).toHaveBeenCalledWith 'Unable to open 4 project directories', - {description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'} - - describe "openInitialEmptyEditorIfNecessary", -> - describe "when there are no paths set", -> - beforeEach -> - spyOn(atom, 'getLoadSettings').andReturn(initialPaths: []) - - it "opens an empty buffer", -> - spyOn(atom.workspace, 'open') - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).toHaveBeenCalledWith(null) - - describe "when there is already a buffer open", -> - beforeEach -> - waitsForPromise -> atom.workspace.open() - - it "does not open an empty buffer", -> - spyOn(atom.workspace, 'open') - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).not.toHaveBeenCalled() - - describe "when the project has a path", -> - beforeEach -> - spyOn(atom, 'getLoadSettings').andReturn(initialPaths: ['something']) - spyOn(atom.workspace, 'open') - - it "does not open an empty buffer", -> - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).not.toHaveBeenCalled() - - describe "adding a project folder", -> - it "does nothing if the user dismisses the file picker", -> - initialPaths = atom.project.getPaths() - tempDirectory = temp.mkdirSync("a-new-directory") - spyOn(atom, "pickFolder").andCallFake (callback) -> callback(null) - atom.addProjectFolder() - expect(atom.project.getPaths()).toEqual(initialPaths) - - describe "when there is no saved state for the added folders", -> - beforeEach -> - spyOn(atom, 'loadState').andReturn(Promise.resolve(null)) - spyOn(atom, 'attemptRestoreProjectStateForPaths') - - it "adds the selected folder to the project", -> - initialPaths = atom.project.setPaths([]) - tempDirectory = temp.mkdirSync("a-new-directory") - spyOn(atom, "pickFolder").andCallFake (callback) -> - callback([tempDirectory]) - waitsForPromise -> - atom.addProjectFolder() - runs -> - expect(atom.project.getPaths()).toEqual([tempDirectory]) - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - - describe "when there is saved state for the relevant directories", -> - state = Symbol('savedState') - - beforeEach -> - spyOn(atom, "getStateKey").andCallFake (dirs) -> dirs.join(':') - spyOn(atom, "loadState").andCallFake (key) -> - if key is __dirname then Promise.resolve(state) else Promise.resolve(null) - spyOn(atom, "attemptRestoreProjectStateForPaths") - spyOn(atom, "pickFolder").andCallFake (callback) -> - callback([__dirname]) - atom.project.setPaths([]) - - describe "when there are no project folders", -> - it "attempts to restore the project state", -> - waitsForPromise -> - atom.addProjectFolder() - runs -> - expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname]) - expect(atom.project.getPaths()).toEqual([]) - - describe "when there are already project folders", -> - openedPath = path.join(__dirname, 'fixtures') - beforeEach -> - atom.project.setPaths([openedPath]) - - it "does not attempt to restore the project state, instead adding the project paths", -> - waitsForPromise -> - atom.addProjectFolder() - runs -> - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([openedPath, __dirname]) - - describe "attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)", -> - describe "when the window is clean (empty or has only unnamed, unmodified buffers)", -> - beforeEach -> - # Unnamed, unmodified buffer doesn't count toward "clean"-ness - waitsForPromise -> atom.workspace.open() - - it "automatically restores the saved state into the current environment", -> - state = Symbol() - spyOn(atom.workspace, 'open') - spyOn(atom, 'restoreStateIntoThisEnvironment') - - atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) - expect(atom.restoreStateIntoThisEnvironment).toHaveBeenCalledWith(state) - expect(atom.workspace.open.callCount).toBe(1) - expect(atom.workspace.open).toHaveBeenCalledWith(__filename) - - describe "when a dock has a non-text editor", -> - it "doesn't prompt the user to restore state", -> - dock = atom.workspace.getLeftDock() - dock.getActivePane().addItem - getTitle: -> 'title' - element: document.createElement 'div' - state = Symbol() - spyOn(atom, 'confirm') - atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) - expect(atom.confirm).not.toHaveBeenCalled() - - describe "when the window is dirty", -> - editor = null - - beforeEach -> - waitsForPromise -> atom.workspace.open().then (e) -> - editor = e - editor.setText('new editor') - - describe "when a dock has a modified editor", -> - it "prompts the user to restore the state", -> - dock = atom.workspace.getLeftDock() - dock.getActivePane().addItem editor - spyOn(atom, "confirm").andReturn(1) - spyOn(atom.project, 'addPath') - spyOn(atom.workspace, 'open') - state = Symbol() - atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) - expect(atom.confirm).toHaveBeenCalled() - - it "prompts the user to restore the state in a new window, discarding it and adding folder to current window", -> - spyOn(atom, "confirm").andReturn(1) - spyOn(atom.project, 'addPath') - spyOn(atom.workspace, 'open') - state = Symbol() - - atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) - expect(atom.confirm).toHaveBeenCalled() - expect(atom.project.addPath.callCount).toBe(1) - expect(atom.project.addPath).toHaveBeenCalledWith(__dirname) - expect(atom.workspace.open.callCount).toBe(1) - expect(atom.workspace.open).toHaveBeenCalledWith(__filename) - - it "prompts the user to restore the state in a new window, opening a new window", -> - spyOn(atom, "confirm").andReturn(0) - spyOn(atom, "open") - state = Symbol() - - atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) - expect(atom.confirm).toHaveBeenCalled() - expect(atom.open).toHaveBeenCalledWith - pathsToOpen: [__dirname, __filename] - newWindow: true - devMode: atom.inDevMode() - safeMode: atom.inSafeMode() - - describe "::unloadEditorWindow()", -> - it "saves the BlobStore so it can be loaded after reload", -> - configDirPath = temp.mkdirSync('atom-spec-environment') - fakeBlobStore = jasmine.createSpyObj("blob store", ["save"]) - atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true}) - atomEnvironment.initialize({configDirPath, blobStore: fakeBlobStore, window, document}) - - atomEnvironment.unloadEditorWindow() - - expect(fakeBlobStore.save).toHaveBeenCalled() - - atomEnvironment.destroy() - - describe "::destroy()", -> - it "does not throw exceptions when unsubscribing from ipc events (regression)", -> - configDirPath = temp.mkdirSync('atom-spec-environment') - fakeDocument = { - addEventListener: -> - removeEventListener: -> - head: document.createElement('head') - body: document.createElement('body') - } - atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate}) - atomEnvironment.initialize({window, document: fakeDocument}) - spyOn(atomEnvironment.packages, 'loadPackages').andReturn(Promise.resolve()) - spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve()) - spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve()) - waitsForPromise -> - atomEnvironment.startEditorWindow() - runs -> - atomEnvironment.unloadEditorWindow() - atomEnvironment.destroy() - - describe "::whenShellEnvironmentLoaded()", -> - [atomEnvironment, envLoaded, spy] = [] - - beforeEach -> - resolve = null - promise = new Promise (r) -> resolve = r - envLoaded = -> - resolve() - waitsForPromise -> promise - atomEnvironment = new AtomEnvironment - applicationDelegate: atom.applicationDelegate - updateProcessEnv: -> promise - atomEnvironment.initialize({window, document}) - spy = jasmine.createSpy() - - afterEach -> - atomEnvironment.destroy() - - it "is triggered once the shell environment is loaded", -> - atomEnvironment.whenShellEnvironmentLoaded spy - atomEnvironment.updateProcessEnvAndTriggerHooks() - envLoaded() - runs -> expect(spy).toHaveBeenCalled() - - it "triggers the callback immediately if the shell environment is already loaded", -> - atomEnvironment.updateProcessEnvAndTriggerHooks() - envLoaded() - runs -> - atomEnvironment.whenShellEnvironmentLoaded spy - expect(spy).toHaveBeenCalled() - - describe "::openLocations(locations) (called via IPC from browser process)", -> - beforeEach -> - spyOn(atom.workspace, 'open') - atom.project.setPaths([]) - - describe "when there is no saved state", -> - beforeEach -> - spyOn(atom, "loadState").andReturn(Promise.resolve(null)) - - describe "when the opened path exists", -> - it "adds it to the project's paths", -> - pathToOpen = __filename - waitsForPromise -> atom.openLocations([{pathToOpen}]) - runs -> expect(atom.project.getPaths()[0]).toBe __dirname - - describe "then a second path is opened with forceAddToWindow", -> - it "adds the second path to the project's paths", -> - firstPathToOpen = __dirname - secondPathToOpen = path.resolve(__dirname, './fixtures') - waitsForPromise -> atom.openLocations([{pathToOpen: firstPathToOpen}]) - waitsForPromise -> atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}]) - runs -> expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen]) - - describe "when the opened path does not exist but its parent directory does", -> - it "adds the parent directory to the project paths", -> - pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt') - waitsForPromise -> atom.openLocations([{pathToOpen}]) - runs -> expect(atom.project.getPaths()[0]).toBe __dirname - - describe "when the opened path is a file", -> - it "opens it in the workspace", -> - pathToOpen = __filename - waitsForPromise -> atom.openLocations([{pathToOpen}]) - runs -> expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename - - describe "when the opened path is a directory", -> - it "does not open it in the workspace", -> - pathToOpen = __dirname - waitsForPromise -> atom.openLocations([{pathToOpen}]) - runs -> expect(atom.workspace.open.callCount).toBe 0 - - describe "when the opened path is a uri", -> - it "adds it to the project's paths as is", -> - pathToOpen = 'remote://server:7644/some/dir/path' - spyOn(atom.project, 'addPath') - waitsForPromise -> atom.openLocations([{pathToOpen}]) - runs -> expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen) - - describe "when there is saved state for the relevant directories", -> - state = Symbol('savedState') - - beforeEach -> - spyOn(atom, "getStateKey").andCallFake (dirs) -> dirs.join(':') - spyOn(atom, "loadState").andCallFake (key) -> - if key is __dirname then Promise.resolve(state) else Promise.resolve(null) - spyOn(atom, "attemptRestoreProjectStateForPaths") - - describe "when there are no project folders", -> - it "attempts to restore the project state", -> - pathToOpen = __dirname - waitsForPromise -> atom.openLocations([{pathToOpen}]) - runs -> - expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], []) - expect(atom.project.getPaths()).toEqual([]) - - it "opens the specified files", -> - waitsForPromise -> atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}]) - runs -> - expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename]) - expect(atom.project.getPaths()).toEqual([]) - - - describe "when there are already project folders", -> - beforeEach -> - atom.project.setPaths([__dirname]) - - it "does not attempt to restore the project state, instead adding the project paths", -> - pathToOpen = path.join(__dirname, 'fixtures') - waitsForPromise -> atom.openLocations([{pathToOpen, forceAddToWindow: true}]) - runs -> - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) - - it "opens the specified files", -> - pathToOpen = path.join(__dirname, 'fixtures') - fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt') - waitsForPromise -> atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}]) - runs -> - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]) - expect(atom.project.getPaths()).toEqual([__dirname]) - - describe "::updateAvailable(info) (called via IPC from browser process)", -> - subscription = null - - afterEach -> - subscription?.dispose() - - it "invokes onUpdateAvailable listeners", -> - return unless process.platform is 'darwin' # Test tied to electron autoUpdater, we use something else on Linux and Win32 - - atom.listenForUpdates() - - updateAvailableHandler = jasmine.createSpy("update-available-handler") - subscription = atom.onUpdateAvailable updateAvailableHandler - - autoUpdater = require('electron').remote.autoUpdater - autoUpdater.emit 'update-downloaded', null, "notes", "version" - - waitsFor -> - updateAvailableHandler.callCount > 0 - - runs -> - {releaseVersion} = updateAvailableHandler.mostRecentCall.args[0] - expect(releaseVersion).toBe 'version' - - describe "::getReleaseChannel()", -> - [version] = [] - beforeEach -> - spyOn(atom, 'getVersion').andCallFake -> version - - it "returns the correct channel based on the version number", -> - version = '1.5.6' - expect(atom.getReleaseChannel()).toBe 'stable' - - version = '1.5.0-beta10' - expect(atom.getReleaseChannel()).toBe 'beta' - - version = '1.7.0-dev-5340c91' - expect(atom.getReleaseChannel()).toBe 'dev' diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js new file mode 100644 index 000000000..3095b94f0 --- /dev/null +++ b/spec/atom-environment-spec.js @@ -0,0 +1,830 @@ +const _ = require('underscore-plus') +const path = require('path') +const temp = require('temp').track() +const AtomEnvironment = require('../src/atom-environment') +const StorageFolder = require('../src/storage-folder') + +describe('AtomEnvironment', () => { + afterEach(() => { + try { + temp.cleanupSync() + } catch (error) {} + }) + + describe('window sizing methods', () => { + describe('::getPosition and ::setPosition', () => { + let originalPosition = null + beforeEach(() => originalPosition = atom.getPosition()) + + afterEach(() => atom.setPosition(originalPosition.x, originalPosition.y)) + + it('sets the position of the window, and can retrieve the position just set', () => { + atom.setPosition(22, 45) + expect(atom.getPosition()).toEqual({x: 22, y: 45}) + }) + }) + + describe('::getSize and ::setSize', () => { + let originalSize = null + beforeEach(() => originalSize = atom.getSize()) + afterEach(() => atom.setSize(originalSize.width, originalSize.height)) + + it('sets the size of the window, and can retrieve the size just set', () => { + const newWidth = originalSize.width - 12 + const newHeight = originalSize.height - 23 + waitsForPromise(() => atom.setSize(newWidth, newHeight)) + runs(() => expect(atom.getSize()).toEqual({width: newWidth, height: newHeight})) + }) + }) + }) + + describe('.isReleasedVersion()', () => { + it('returns false if the version is a SHA and true otherwise', () => { + let version = '0.1.0' + spyOn(atom, 'getVersion').andCallFake(() => version) + expect(atom.isReleasedVersion()).toBe(true) + version = '36b5518' + expect(atom.isReleasedVersion()).toBe(false) + }) + }) + + describe('loading default config', () => { + it('loads the default core config schema', () => { + expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe(true) + expect(atom.config.get('core.followSymlinks')).toBe(true) + expect(atom.config.get('editor.showInvisibles')).toBe(false) + }) + }) + + describe('window onerror handler', () => { + let devToolsPromise = null + beforeEach(() => { + devToolsPromise = Promise.resolve() + spyOn(atom, 'openDevTools').andReturn(devToolsPromise) + spyOn(atom, 'executeJavaScriptInDevTools') + }) + + it('will open the dev tools when an error is triggered', () => { + try { + a + 1 + } catch (e) { + window.onerror.call(window, e.toString(), 'abc', 2, 3, e) + } + + waitsForPromise(() => devToolsPromise) + runs(() => { + expect(atom.openDevTools).toHaveBeenCalled() + expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled() + }) + }) + + describe('::onWillThrowError', () => { + let willThrowSpy = null + beforeEach(() => willThrowSpy = jasmine.createSpy()) + + it('is called when there is an error', () => { + let error = null + atom.onWillThrowError(willThrowSpy) + try { + a + 1 + } catch (e) { + error = e + window.onerror.call(window, e.toString(), 'abc', 2, 3, e) + } + + delete willThrowSpy.mostRecentCall.args[0].preventDefault + expect(willThrowSpy).toHaveBeenCalledWith({ + message: error.toString(), + url: 'abc', + line: 2, + column: 3, + originalError: error + }) + }) + + it('will not show the devtools when preventDefault() is called', () => { + willThrowSpy.andCallFake(errorObject => errorObject.preventDefault()) + atom.onWillThrowError(willThrowSpy) + + try { + a + 1 + } catch (e) { + window.onerror.call(window, e.toString(), 'abc', 2, 3, e) + } + + expect(willThrowSpy).toHaveBeenCalled() + expect(atom.openDevTools).not.toHaveBeenCalled() + expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled() + }) + }) + + describe('::onDidThrowError', () => { + let didThrowSpy = null + beforeEach(() => didThrowSpy = jasmine.createSpy()) + + it('is called when there is an error', () => { + let error = null + atom.onDidThrowError(didThrowSpy) + try { + a + 1 + } catch (e) { + error = e + window.onerror.call(window, e.toString(), 'abc', 2, 3, e) + } + expect(didThrowSpy).toHaveBeenCalledWith({ + message: error.toString(), + url: 'abc', + line: 2, + column: 3, + originalError: error + }) + }) + }) + }) + + describe('.assert(condition, message, callback)', () => { + let errors = null + + beforeEach(() => { + errors = [] + spyOn(atom, 'isReleasedVersion').andReturn(true) + atom.onDidFailAssertion(error => errors.push(error)) + }) + + describe('if the condition is false', () => { + it('notifies onDidFailAssertion handlers with an error object based on the call site of the assertion', () => { + const result = atom.assert(false, 'a == b') + expect(result).toBe(false) + expect(errors.length).toBe(1) + expect(errors[0].message).toBe('Assertion failed: a == b') + expect(errors[0].stack).toContain('atom-environment-spec') + }) + + describe('if passed a callback function', () => { + it("calls the callback with the assertion failure's error object", () => { + let error = null + atom.assert(false, 'a == b', e => error = e) + expect(error).toBe(errors[0]) + }) + }) + + describe('if passed metadata', () => { + it("assigns the metadata on the assertion failure's error object", () => { + atom.assert(false, 'a == b', {foo: 'bar'}) + expect(errors[0].metadata).toEqual({foo: 'bar'}) + }) + }) + + describe('when Atom has been built from source', () => { + it('throws an error', () => { + atom.isReleasedVersion.andReturn(false) + expect(() => atom.assert(false, 'testing')).toThrow('Assertion failed: testing') + }) + }) + }) + + describe('if the condition is true', () => { + it('does nothing', () => { + const result = atom.assert(true, 'a == b') + expect(result).toBe(true) + expect(errors).toEqual([]) + }) + }) + }) + + describe('saving and loading', () => { + beforeEach(() => atom.enablePersistence = true) + + afterEach(() => atom.enablePersistence = false) + + it('selects the state based on the current project paths', () => { + jasmine.useRealClock() + + const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')] + + const loadSettings = _.extend(atom.getLoadSettings(), { + initialPaths: [dir1], + windowState: null + } + ) + + spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings) + spyOn(atom, 'serialize').andReturn({stuff: 'cool'}) + + atom.project.setPaths([dir1, dir2]) + // State persistence will fail if other Atom instances are running + waitsForPromise(() => + atom.stateStore.connect().then(isConnected => expect(isConnected).toBe(true)) + ) + + waitsForPromise(() => + atom.saveState().then(() => + atom.loadState().then(state => expect(state).toBeFalsy()) + ) + ) + + waitsForPromise(() => { + loadSettings.initialPaths = [dir2, dir1] + return atom.loadState().then(state => expect(state).toEqual({stuff: 'cool'})) + }) + }) + + it("loads state from the storage folder when it can't be found in atom.stateStore", () => { + jasmine.useRealClock() + + const storageFolderState = {foo: 1, bar: 2} + const serializedState = {someState: 42} + const loadSettings = _.extend(atom.getLoadSettings(), {initialPaths: [temp.mkdirSync('project-directory')]}) + spyOn(atom, 'getLoadSettings').andReturn(loadSettings) + spyOn(atom, 'serialize').andReturn(serializedState) + spyOn(atom, 'getStorageFolder').andReturn(new StorageFolder(temp.mkdirSync('config-directory'))) + atom.project.setPaths(atom.getLoadSettings().initialPaths) + + waitsForPromise(() => atom.stateStore.connect()) + + runs(() => atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState)) + + waitsForPromise(() => atom.loadState().then(state => expect(state).toEqual(storageFolderState))) + + waitsForPromise(() => atom.saveState()) + + waitsForPromise(() => atom.loadState().then(state => expect(state).toEqual(serializedState))) + }) + + it('saves state when the CPU is idle after a keydown or mousedown event', () => { + const atomEnv = new AtomEnvironment({ + applicationDelegate: global.atom.applicationDelegate + }) + const idleCallbacks = [] + atomEnv.initialize({ + window: { + requestIdleCallback (callback) { idleCallbacks.push(callback) }, + addEventListener () {}, + removeEventListener () {} + }, + document: document.implementation.createHTMLDocument() + }) + + spyOn(atomEnv, 'saveState') + + const keydown = new KeyboardEvent('keydown') + atomEnv.document.dispatchEvent(keydown) + advanceClock(atomEnv.saveStateDebounceInterval) + idleCallbacks.shift()() + expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false}) + expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true}) + + atomEnv.saveState.reset() + const mousedown = new MouseEvent('mousedown') + atomEnv.document.dispatchEvent(mousedown) + advanceClock(atomEnv.saveStateDebounceInterval) + idleCallbacks.shift()() + expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false}) + expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true}) + + atomEnv.destroy() + }) + + it('ignores mousedown/keydown events happening after calling unloadEditorWindow', () => { + const atomEnv = new AtomEnvironment({ + applicationDelegate: global.atom.applicationDelegate + }) + const idleCallbacks = [] + atomEnv.initialize({ + window: { + requestIdleCallback (callback) { idleCallbacks.push(callback) }, + addEventListener () {}, + removeEventListener () {} + }, + document: document.implementation.createHTMLDocument() + }) + + spyOn(atomEnv, 'saveState') + + let mousedown = new MouseEvent('mousedown') + atomEnv.document.dispatchEvent(mousedown) + atomEnv.unloadEditorWindow() + expect(atomEnv.saveState).not.toHaveBeenCalled() + + advanceClock(atomEnv.saveStateDebounceInterval) + idleCallbacks.shift()() + expect(atomEnv.saveState).not.toHaveBeenCalled() + + mousedown = new MouseEvent('mousedown') + atomEnv.document.dispatchEvent(mousedown) + advanceClock(atomEnv.saveStateDebounceInterval) + idleCallbacks.shift()() + expect(atomEnv.saveState).not.toHaveBeenCalled() + + atomEnv.destroy() + }) + + it('serializes the project state with all the options supplied in saveState', () => { + spyOn(atom.project, 'serialize').andReturn({foo: 42}) + + waitsForPromise(() => atom.saveState({anyOption: 'any option'})) + runs(() => { + expect(atom.project.serialize.calls.length).toBe(1) + expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'}) + }) + }) + + it('serializes the text editor registry', () => { + let editor = null + + waitsForPromise(() => atom.workspace.open('sample.js').then(e => editor = e)) + + waitsForPromise(() => { + atom.textEditors.setGrammarOverride(editor, 'text.plain') + + const atom2 = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + window: document.createElement('div'), + document: Object.assign( + document.createElement('div'), + { + body: document.createElement('div'), + head: document.createElement('div') + } + ) + }) + atom2.initialize({document, window}) + return atom2.deserialize(atom.serialize()).then(() => { + expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain') + return atom2.destroy() + }) + }) + }) + + describe('deserialization failures', () => { + it('propagates project state restoration failures', () => { + spyOn(atom.project, 'deserialize').andCallFake(() => { + const err = new Error('deserialization failure') + err.missingProjectPaths = ['/foo'] + return Promise.reject(err) + }) + spyOn(atom.notifications, 'addError') + + waitsForPromise(() => atom.deserialize({project: 'should work'})) + runs(() => { + expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open project directory', { + description: 'Project directory `/foo` is no longer on disk.' + }) + }) + }) + + it('accumulates and reports two errors with one notification', () => { + spyOn(atom.project, 'deserialize').andCallFake(() => { + const err = new Error('deserialization failure') + err.missingProjectPaths = ['/foo', '/wat'] + return Promise.reject(err) + }) + spyOn(atom.notifications, 'addError') + + waitsForPromise(() => atom.deserialize({project: 'should work'})) + runs(() => { + expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 2 project directories', { + description: 'Project directories `/foo` and `/wat` are no longer on disk.' + }) + }) + }) + + it('accumulates and reports three+ errors with one notification', () => { + spyOn(atom.project, 'deserialize').andCallFake(() => { + const err = new Error('deserialization failure') + err.missingProjectPaths = ['/foo', '/wat', '/stuff', '/things'] + return Promise.reject(err) + }) + spyOn(atom.notifications, 'addError') + + waitsForPromise(() => atom.deserialize({project: 'should work'})) + runs(() => + expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 4 project directories', + {description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'})) + }) + }) + }) + + describe('openInitialEmptyEditorIfNecessary', () => { + describe('when there are no paths set', () => { + beforeEach(() => spyOn(atom, 'getLoadSettings').andReturn({initialPaths: []})) + + it('opens an empty buffer', () => { + spyOn(atom.workspace, 'open') + atom.openInitialEmptyEditorIfNecessary() + expect(atom.workspace.open).toHaveBeenCalledWith(null) + }) + + describe('when there is already a buffer open', () => { + beforeEach(() => waitsForPromise(() => atom.workspace.open())) + + it('does not open an empty buffer', () => { + spyOn(atom.workspace, 'open') + atom.openInitialEmptyEditorIfNecessary() + expect(atom.workspace.open).not.toHaveBeenCalled() + }) + }) + }) + + describe('when the project has a path', () => { + beforeEach(() => { + spyOn(atom, 'getLoadSettings').andReturn({initialPaths: ['something']}) + spyOn(atom.workspace, 'open') + }) + + it('does not open an empty buffer', () => { + atom.openInitialEmptyEditorIfNecessary() + expect(atom.workspace.open).not.toHaveBeenCalled() + }) + }) + }) + + describe('adding a project folder', () => { + it('does nothing if the user dismisses the file picker', () => { + const initialPaths = atom.project.getPaths() + const tempDirectory = temp.mkdirSync('a-new-directory') + spyOn(atom, 'pickFolder').andCallFake(callback => callback(null)) + atom.addProjectFolder() + expect(atom.project.getPaths()).toEqual(initialPaths) + }) + + describe('when there is no saved state for the added folders', () => { + beforeEach(() => { + spyOn(atom, 'loadState').andReturn(Promise.resolve(null)) + spyOn(atom, 'attemptRestoreProjectStateForPaths') + }) + + it('adds the selected folder to the project', () => { + const initialPaths = atom.project.setPaths([]) + const tempDirectory = temp.mkdirSync('a-new-directory') + spyOn(atom, 'pickFolder').andCallFake(callback => callback([tempDirectory])) + waitsForPromise(() => atom.addProjectFolder()) + runs(() => { + expect(atom.project.getPaths()).toEqual([tempDirectory]) + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() + }) + }) + }) + + describe('when there is saved state for the relevant directories', () => { + const state = Symbol('savedState') + + beforeEach(() => { + spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')) + spyOn(atom, 'loadState').andCallFake(function (key) { + if (key === __dirname) { return Promise.resolve(state) } else { return Promise.resolve(null) } + }) + spyOn(atom, 'attemptRestoreProjectStateForPaths') + spyOn(atom, 'pickFolder').andCallFake(callback => callback([__dirname])) + atom.project.setPaths([]) + }) + + describe('when there are no project folders', () => { + it('attempts to restore the project state', () => { + waitsForPromise(() => atom.addProjectFolder()) + runs(() => { + expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname]) + expect(atom.project.getPaths()).toEqual([]) + }) + }) + }) + + describe('when there are already project folders', () => { + const openedPath = path.join(__dirname, 'fixtures') + beforeEach(() => atom.project.setPaths([openedPath])) + + it('does not attempt to restore the project state, instead adding the project paths', () => { + waitsForPromise(() => atom.addProjectFolder()) + runs(() => { + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() + expect(atom.project.getPaths()).toEqual([openedPath, __dirname]) + }) + }) + }) + }) + }) + + describe('attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)', () => { + describe('when the window is clean (empty or has only unnamed, unmodified buffers)', () => { + beforeEach(() => + // Unnamed, unmodified buffer doesn't count toward "clean"-ness + waitsForPromise(() => atom.workspace.open()) + ) + + it('automatically restores the saved state into the current environment', () => { + const state = Symbol() + spyOn(atom.workspace, 'open') + spyOn(atom, 'restoreStateIntoThisEnvironment') + + atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) + expect(atom.restoreStateIntoThisEnvironment).toHaveBeenCalledWith(state) + expect(atom.workspace.open.callCount).toBe(1) + expect(atom.workspace.open).toHaveBeenCalledWith(__filename) + }) + + describe('when a dock has a non-text editor', () => { + it("doesn't prompt the user to restore state", () => { + const dock = atom.workspace.getLeftDock() + dock.getActivePane().addItem({ + getTitle () { return 'title' }, + element: document.createElement('div') + }) + const state = Symbol() + spyOn(atom, 'confirm') + atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) + expect(atom.confirm).not.toHaveBeenCalled() + }) + }) + }) + + describe('when the window is dirty', () => { + let editor = null + + beforeEach(() => + waitsForPromise(() => atom.workspace.open().then(function (e) { + editor = e + editor.setText('new editor') + }) + ) + ) + + describe('when a dock has a modified editor', () => { + it('prompts the user to restore the state', () => { + const dock = atom.workspace.getLeftDock() + dock.getActivePane().addItem(editor) + spyOn(atom, 'confirm').andReturn(1) + spyOn(atom.project, 'addPath') + spyOn(atom.workspace, 'open') + const state = Symbol() + atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) + expect(atom.confirm).toHaveBeenCalled() + }) + }) + + it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', () => { + spyOn(atom, 'confirm').andReturn(1) + spyOn(atom.project, 'addPath') + spyOn(atom.workspace, 'open') + const state = Symbol() + + atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) + expect(atom.confirm).toHaveBeenCalled() + expect(atom.project.addPath.callCount).toBe(1) + expect(atom.project.addPath).toHaveBeenCalledWith(__dirname) + expect(atom.workspace.open.callCount).toBe(1) + expect(atom.workspace.open).toHaveBeenCalledWith(__filename) + }) + + it('prompts the user to restore the state in a new window, opening a new window', () => { + spyOn(atom, 'confirm').andReturn(0) + spyOn(atom, 'open') + const state = Symbol() + + atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) + expect(atom.confirm).toHaveBeenCalled() + expect(atom.open).toHaveBeenCalledWith({ + pathsToOpen: [__dirname, __filename], + newWindow: true, + devMode: atom.inDevMode(), + safeMode: atom.inSafeMode() + }) + }) + }) + }) + + describe('::unloadEditorWindow()', () => { + it('saves the BlobStore so it can be loaded after reload', () => { + const configDirPath = temp.mkdirSync('atom-spec-environment') + const fakeBlobStore = jasmine.createSpyObj('blob store', ['save']) + const atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true}) + atomEnvironment.initialize({configDirPath, blobStore: fakeBlobStore, window, document}) + + atomEnvironment.unloadEditorWindow() + + expect(fakeBlobStore.save).toHaveBeenCalled() + + atomEnvironment.destroy() + }) + }) + + describe('::destroy()', () => { + it('does not throw exceptions when unsubscribing from ipc events (regression)', () => { + const configDirPath = temp.mkdirSync('atom-spec-environment') + const fakeDocument = { + addEventListener () {}, + removeEventListener () {}, + head: document.createElement('head'), + body: document.createElement('body') + } + const atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate}) + atomEnvironment.initialize({window, document: fakeDocument}) + spyOn(atomEnvironment.packages, 'loadPackages').andReturn(Promise.resolve()) + spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve()) + spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve()) + waitsForPromise(() => atomEnvironment.startEditorWindow()) + runs(() => { + atomEnvironment.unloadEditorWindow() + atomEnvironment.destroy() + }) + }) + }) + + describe('::whenShellEnvironmentLoaded()', () => { + let atomEnvironment, envLoaded, spy + + beforeEach(() => { + let resolve = null + const promise = new Promise(function (r) { resolve = r }) + envLoaded = () => { + resolve() + waitsForPromise(() => promise) + } + atomEnvironment = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + updateProcessEnv () { return promise } + }) + atomEnvironment.initialize({window, document}) + spy = jasmine.createSpy() + }) + + afterEach(() => atomEnvironment.destroy()) + + it('is triggered once the shell environment is loaded', () => { + atomEnvironment.whenShellEnvironmentLoaded(spy) + atomEnvironment.updateProcessEnvAndTriggerHooks() + envLoaded() + runs(() => expect(spy).toHaveBeenCalled()) + }) + + it('triggers the callback immediately if the shell environment is already loaded', () => { + atomEnvironment.updateProcessEnvAndTriggerHooks() + envLoaded() + runs(() => { + atomEnvironment.whenShellEnvironmentLoaded(spy) + expect(spy).toHaveBeenCalled() + }) + }) + }) + + describe('::openLocations(locations) (called via IPC from browser process)', () => { + beforeEach(() => { + spyOn(atom.workspace, 'open') + atom.project.setPaths([]) + }) + + describe('when there is no saved state', () => { + beforeEach(() => spyOn(atom, 'loadState').andReturn(Promise.resolve(null))) + + describe('when the opened path exists', () => { + it("adds it to the project's paths", () => { + const pathToOpen = __filename + waitsForPromise(() => atom.openLocations([{pathToOpen}])) + runs(() => expect(atom.project.getPaths()[0]).toBe(__dirname)) + }) + + describe('then a second path is opened with forceAddToWindow', () => { + it("adds the second path to the project's paths", () => { + const firstPathToOpen = __dirname + const secondPathToOpen = path.resolve(__dirname, './fixtures') + waitsForPromise(() => atom.openLocations([{pathToOpen: firstPathToOpen}])) + waitsForPromise(() => atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])) + runs(() => expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])) + }) + }) + }) + + describe('when the opened path does not exist but its parent directory does', () => { + it('adds the parent directory to the project paths', () => { + const pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt') + waitsForPromise(() => atom.openLocations([{pathToOpen}])) + runs(() => expect(atom.project.getPaths()[0]).toBe(__dirname)) + }) + }) + + describe('when the opened path is a file', () => { + it('opens it in the workspace', () => { + const pathToOpen = __filename + waitsForPromise(() => atom.openLocations([{pathToOpen}])) + runs(() => expect(atom.workspace.open.mostRecentCall.args[0]).toBe(__filename)) + }) + }) + + describe('when the opened path is a directory', () => { + it('does not open it in the workspace', () => { + const pathToOpen = __dirname + waitsForPromise(() => atom.openLocations([{pathToOpen}])) + runs(() => expect(atom.workspace.open.callCount).toBe(0)) + }) + }) + + describe('when the opened path is a uri', () => { + it("adds it to the project's paths as is", () => { + const pathToOpen = 'remote://server:7644/some/dir/path' + spyOn(atom.project, 'addPath') + waitsForPromise(() => atom.openLocations([{pathToOpen}])) + runs(() => expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)) + }) + }) + }) + + describe('when there is saved state for the relevant directories', () => { + const state = Symbol('savedState') + + beforeEach(() => { + spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')) + spyOn(atom, 'loadState').andCallFake(function (key) { + if (key === __dirname) { return Promise.resolve(state) } else { return Promise.resolve(null) } + }) + spyOn(atom, 'attemptRestoreProjectStateForPaths') + }) + + describe('when there are no project folders', () => { + it('attempts to restore the project state', () => { + const pathToOpen = __dirname + waitsForPromise(() => atom.openLocations([{pathToOpen}])) + runs(() => { + expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], []) + expect(atom.project.getPaths()).toEqual([]) + }) + }) + + it('opens the specified files', () => { + waitsForPromise(() => atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}])) + runs(() => { + expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename]) + expect(atom.project.getPaths()).toEqual([]) + }) + }) + }) + + describe('when there are already project folders', () => { + beforeEach(() => atom.project.setPaths([__dirname])) + + it('does not attempt to restore the project state, instead adding the project paths', () => { + const pathToOpen = path.join(__dirname, 'fixtures') + waitsForPromise(() => atom.openLocations([{pathToOpen, forceAddToWindow: true}])) + runs(() => { + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() + expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) + }) + }) + + it('opens the specified files', () => { + const pathToOpen = path.join(__dirname, 'fixtures') + const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt') + waitsForPromise(() => atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}])) + runs(() => { + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]) + expect(atom.project.getPaths()).toEqual([__dirname]) + }) + }) + }) + }) + }) + + describe('::updateAvailable(info) (called via IPC from browser process)', () => { + let subscription + + afterEach(() => { + if (subscription) subscription.dispose() + }) + + it('invokes onUpdateAvailable listeners', () => { + if (process.platform !== 'darwin') return // Test tied to electron autoUpdater, we use something else on Linux and Win32 + + atom.listenForUpdates() + + const updateAvailableHandler = jasmine.createSpy('update-available-handler') + subscription = atom.onUpdateAvailable(updateAvailableHandler) + + const { autoUpdater } = require('electron').remote + autoUpdater.emit('update-downloaded', null, 'notes', 'version') + + waitsFor(() => updateAvailableHandler.callCount > 0) + + runs(() => { + const {releaseVersion} = updateAvailableHandler.mostRecentCall.args[0] + expect(releaseVersion).toBe('version') + }) + }) + }) + + describe('::getReleaseChannel()', () => { + let version + + beforeEach(() => { + spyOn(atom, 'getVersion').andCallFake(() => version) + }) + + it('returns the correct channel based on the version number', () => { + version = '1.5.6' + expect(atom.getReleaseChannel()).toBe('stable') + + version = '1.5.0-beta10' + expect(atom.getReleaseChannel()).toBe('beta') + + version = '1.7.0-dev-5340c91' + expect(atom.getReleaseChannel()).toBe('dev') + }) + }) +}) From 786f8b6a93ffdf7443490b82641939488cdeb0b0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 6 Nov 2017 17:51:49 -0800 Subject: [PATCH 12/43] Use await instead of waitsForPromise in atom-environment-spec --- spec/atom-environment-spec.js | 333 +++++++++++++++------------------- 1 file changed, 146 insertions(+), 187 deletions(-) diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index 3095b94f0..b8d7e309a 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -1,3 +1,4 @@ +const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers') const _ = require('underscore-plus') const path = require('path') const temp = require('temp').track() @@ -29,11 +30,11 @@ describe('AtomEnvironment', () => { beforeEach(() => originalSize = atom.getSize()) afterEach(() => atom.setSize(originalSize.width, originalSize.height)) - it('sets the size of the window, and can retrieve the size just set', () => { + it('sets the size of the window, and can retrieve the size just set', async () => { const newWidth = originalSize.width - 12 const newHeight = originalSize.height - 23 - waitsForPromise(() => atom.setSize(newWidth, newHeight)) - runs(() => expect(atom.getSize()).toEqual({width: newWidth, height: newHeight})) + await atom.setSize(newWidth, newHeight) + expect(atom.getSize()).toEqual({width: newWidth, height: newHeight}) }) }) }) @@ -64,23 +65,24 @@ describe('AtomEnvironment', () => { spyOn(atom, 'executeJavaScriptInDevTools') }) - it('will open the dev tools when an error is triggered', () => { + it('will open the dev tools when an error is triggered', async () => { try { a + 1 } catch (e) { window.onerror.call(window, e.toString(), 'abc', 2, 3, e) } - waitsForPromise(() => devToolsPromise) - runs(() => { - expect(atom.openDevTools).toHaveBeenCalled() - expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled() - }) + await devToolsPromise + expect(atom.openDevTools).toHaveBeenCalled() + expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled() }) describe('::onWillThrowError', () => { let willThrowSpy = null - beforeEach(() => willThrowSpy = jasmine.createSpy()) + + beforeEach(() => { + willThrowSpy = jasmine.createSpy() + }) it('is called when there is an error', () => { let error = null @@ -197,39 +199,32 @@ describe('AtomEnvironment', () => { afterEach(() => atom.enablePersistence = false) - it('selects the state based on the current project paths', () => { + it('selects the state based on the current project paths', async () => { jasmine.useRealClock() const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')] - const loadSettings = _.extend(atom.getLoadSettings(), { + const loadSettings = Object.assign(atom.getLoadSettings(), { initialPaths: [dir1], windowState: null - } - ) + }) spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings) spyOn(atom, 'serialize').andReturn({stuff: 'cool'}) atom.project.setPaths([dir1, dir2]) + // State persistence will fail if other Atom instances are running - waitsForPromise(() => - atom.stateStore.connect().then(isConnected => expect(isConnected).toBe(true)) - ) + expect(await atom.stateStore.connect()).toBe(true) - waitsForPromise(() => - atom.saveState().then(() => - atom.loadState().then(state => expect(state).toBeFalsy()) - ) - ) + await atom.saveState() + expect(await atom.loadState()).toBeFalsy() - waitsForPromise(() => { - loadSettings.initialPaths = [dir2, dir1] - return atom.loadState().then(state => expect(state).toEqual({stuff: 'cool'})) - }) + loadSettings.initialPaths = [dir2, dir1] + expect(await atom.loadState()).toEqual({stuff: 'cool'}) }) - it("loads state from the storage folder when it can't be found in atom.stateStore", () => { + it("loads state from the storage folder when it can't be found in atom.stateStore", async () => { jasmine.useRealClock() const storageFolderState = {foo: 1, bar: 2} @@ -240,15 +235,12 @@ describe('AtomEnvironment', () => { spyOn(atom, 'getStorageFolder').andReturn(new StorageFolder(temp.mkdirSync('config-directory'))) atom.project.setPaths(atom.getLoadSettings().initialPaths) - waitsForPromise(() => atom.stateStore.connect()) + await atom.stateStore.connect() + atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState) + expect(await atom.loadState()).toEqual(storageFolderState) - runs(() => atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState)) - - waitsForPromise(() => atom.loadState().then(state => expect(state).toEqual(storageFolderState))) - - waitsForPromise(() => atom.saveState()) - - waitsForPromise(() => atom.loadState().then(state => expect(state).toEqual(serializedState))) + await atom.saveState() + expect(await atom.loadState()).toEqual(serializedState) }) it('saves state when the CPU is idle after a keydown or mousedown event', () => { @@ -319,45 +311,38 @@ describe('AtomEnvironment', () => { atomEnv.destroy() }) - it('serializes the project state with all the options supplied in saveState', () => { + it('serializes the project state with all the options supplied in saveState', async () => { spyOn(atom.project, 'serialize').andReturn({foo: 42}) - waitsForPromise(() => atom.saveState({anyOption: 'any option'})) - runs(() => { - expect(atom.project.serialize.calls.length).toBe(1) - expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'}) - }) + await atom.saveState({anyOption: 'any option'}) + expect(atom.project.serialize.calls.length).toBe(1) + expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'}) }) - it('serializes the text editor registry', () => { - let editor = null + it('serializes the text editor registry', async () => { + const editor = await atom.workspace.open('sample.js') + atom.textEditors.setGrammarOverride(editor, 'text.plain') - waitsForPromise(() => atom.workspace.open('sample.js').then(e => editor = e)) - - waitsForPromise(() => { - atom.textEditors.setGrammarOverride(editor, 'text.plain') - - const atom2 = new AtomEnvironment({ - applicationDelegate: atom.applicationDelegate, - window: document.createElement('div'), - document: Object.assign( - document.createElement('div'), - { - body: document.createElement('div'), - head: document.createElement('div') - } - ) - }) - atom2.initialize({document, window}) - return atom2.deserialize(atom.serialize()).then(() => { - expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain') - return atom2.destroy() - }) + const atom2 = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + window: document.createElement('div'), + document: Object.assign( + document.createElement('div'), + { + body: document.createElement('div'), + head: document.createElement('div') + } + ) }) + atom2.initialize({document, window}) + + await atom2.deserialize(atom.serialize()) + expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain') + atom2.destroy() }) describe('deserialization failures', () => { - it('propagates project state restoration failures', () => { + it('propagates project state restoration failures', async () => { spyOn(atom.project, 'deserialize').andCallFake(() => { const err = new Error('deserialization failure') err.missingProjectPaths = ['/foo'] @@ -365,15 +350,13 @@ describe('AtomEnvironment', () => { }) spyOn(atom.notifications, 'addError') - waitsForPromise(() => atom.deserialize({project: 'should work'})) - runs(() => { - expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open project directory', { - description: 'Project directory `/foo` is no longer on disk.' - }) + await atom.deserialize({project: 'should work'}) + expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open project directory', { + description: 'Project directory `/foo` is no longer on disk.' }) }) - it('accumulates and reports two errors with one notification', () => { + it('accumulates and reports two errors with one notification', async () => { spyOn(atom.project, 'deserialize').andCallFake(() => { const err = new Error('deserialization failure') err.missingProjectPaths = ['/foo', '/wat'] @@ -381,15 +364,13 @@ describe('AtomEnvironment', () => { }) spyOn(atom.notifications, 'addError') - waitsForPromise(() => atom.deserialize({project: 'should work'})) - runs(() => { - expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 2 project directories', { - description: 'Project directories `/foo` and `/wat` are no longer on disk.' - }) + await atom.deserialize({project: 'should work'}) + expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 2 project directories', { + description: 'Project directories `/foo` and `/wat` are no longer on disk.' }) }) - it('accumulates and reports three+ errors with one notification', () => { + it('accumulates and reports three+ errors with one notification', async () => { spyOn(atom.project, 'deserialize').andCallFake(() => { const err = new Error('deserialization failure') err.missingProjectPaths = ['/foo', '/wat', '/stuff', '/things'] @@ -397,10 +378,10 @@ describe('AtomEnvironment', () => { }) spyOn(atom.notifications, 'addError') - waitsForPromise(() => atom.deserialize({project: 'should work'})) - runs(() => - expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 4 project directories', - {description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'})) + await atom.deserialize({project: 'should work'}) + expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 4 project directories', { + description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.' + }) }) }) }) @@ -416,7 +397,9 @@ describe('AtomEnvironment', () => { }) describe('when there is already a buffer open', () => { - beforeEach(() => waitsForPromise(() => atom.workspace.open())) + beforeEach(async () => { + await atom.workspace.open() + }) it('does not open an empty buffer', () => { spyOn(atom.workspace, 'open') @@ -454,15 +437,13 @@ describe('AtomEnvironment', () => { spyOn(atom, 'attemptRestoreProjectStateForPaths') }) - it('adds the selected folder to the project', () => { + it('adds the selected folder to the project', async () => { const initialPaths = atom.project.setPaths([]) const tempDirectory = temp.mkdirSync('a-new-directory') spyOn(atom, 'pickFolder').andCallFake(callback => callback([tempDirectory])) - waitsForPromise(() => atom.addProjectFolder()) - runs(() => { - expect(atom.project.getPaths()).toEqual([tempDirectory]) - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - }) + await atom.addProjectFolder() + expect(atom.project.getPaths()).toEqual([tempDirectory]) + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() }) }) @@ -471,34 +452,29 @@ describe('AtomEnvironment', () => { beforeEach(() => { spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')) - spyOn(atom, 'loadState').andCallFake(function (key) { - if (key === __dirname) { return Promise.resolve(state) } else { return Promise.resolve(null) } - }) + spyOn(atom, 'loadState').andCallFake(async (key) => key === __dirname ? state : null) spyOn(atom, 'attemptRestoreProjectStateForPaths') spyOn(atom, 'pickFolder').andCallFake(callback => callback([__dirname])) atom.project.setPaths([]) }) describe('when there are no project folders', () => { - it('attempts to restore the project state', () => { - waitsForPromise(() => atom.addProjectFolder()) - runs(() => { - expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname]) - expect(atom.project.getPaths()).toEqual([]) - }) + it('attempts to restore the project state', async () => { + await atom.addProjectFolder() + expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname]) + expect(atom.project.getPaths()).toEqual([]) }) }) describe('when there are already project folders', () => { const openedPath = path.join(__dirname, 'fixtures') + beforeEach(() => atom.project.setPaths([openedPath])) - it('does not attempt to restore the project state, instead adding the project paths', () => { - waitsForPromise(() => atom.addProjectFolder()) - runs(() => { - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([openedPath, __dirname]) - }) + it('does not attempt to restore the project state, instead adding the project paths', async () => { + await atom.addProjectFolder() + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() + expect(atom.project.getPaths()).toEqual([openedPath, __dirname]) }) }) }) @@ -506,10 +482,10 @@ describe('AtomEnvironment', () => { describe('attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)', () => { describe('when the window is clean (empty or has only unnamed, unmodified buffers)', () => { - beforeEach(() => + beforeEach(async () => { // Unnamed, unmodified buffer doesn't count toward "clean"-ness - waitsForPromise(() => atom.workspace.open()) - ) + await atom.workspace.open() + }) it('automatically restores the saved state into the current environment', () => { const state = Symbol() @@ -538,15 +514,12 @@ describe('AtomEnvironment', () => { }) describe('when the window is dirty', () => { - let editor = null + let editor - beforeEach(() => - waitsForPromise(() => atom.workspace.open().then(function (e) { - editor = e - editor.setText('new editor') - }) - ) - ) + beforeEach(async () => { + editor = await atom.workspace.open() + editor.setText('new editor') + }) describe('when a dock has a modified editor', () => { it('prompts the user to restore the state', () => { @@ -608,7 +581,7 @@ describe('AtomEnvironment', () => { }) describe('::destroy()', () => { - it('does not throw exceptions when unsubscribing from ipc events (regression)', () => { + it('does not throw exceptions when unsubscribing from ipc events (regression)', async () => { const configDirPath = temp.mkdirSync('atom-spec-environment') const fakeDocument = { addEventListener () {}, @@ -621,11 +594,9 @@ describe('AtomEnvironment', () => { spyOn(atomEnvironment.packages, 'loadPackages').andReturn(Promise.resolve()) spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve()) spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve()) - waitsForPromise(() => atomEnvironment.startEditorWindow()) - runs(() => { - atomEnvironment.unloadEditorWindow() - atomEnvironment.destroy() - }) + await atomEnvironment.startEditorWindow() + atomEnvironment.unloadEditorWindow() + atomEnvironment.destroy() }) }) @@ -634,10 +605,10 @@ describe('AtomEnvironment', () => { beforeEach(() => { let resolve = null - const promise = new Promise(function (r) { resolve = r }) + const promise = new Promise((r) => { resolve = r }) envLoaded = () => { resolve() - waitsForPromise(() => promise) + promise } atomEnvironment = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate, @@ -649,20 +620,18 @@ describe('AtomEnvironment', () => { afterEach(() => atomEnvironment.destroy()) - it('is triggered once the shell environment is loaded', () => { + it('is triggered once the shell environment is loaded', async () => { atomEnvironment.whenShellEnvironmentLoaded(spy) atomEnvironment.updateProcessEnvAndTriggerHooks() - envLoaded() - runs(() => expect(spy).toHaveBeenCalled()) + await envLoaded() + expect(spy).toHaveBeenCalled() }) - it('triggers the callback immediately if the shell environment is already loaded', () => { + it('triggers the callback immediately if the shell environment is already loaded', async () => { atomEnvironment.updateProcessEnvAndTriggerHooks() - envLoaded() - runs(() => { - atomEnvironment.whenShellEnvironmentLoaded(spy) - expect(spy).toHaveBeenCalled() - }) + await envLoaded() + atomEnvironment.whenShellEnvironmentLoaded(spy) + expect(spy).toHaveBeenCalled() }) }) @@ -673,56 +642,58 @@ describe('AtomEnvironment', () => { }) describe('when there is no saved state', () => { - beforeEach(() => spyOn(atom, 'loadState').andReturn(Promise.resolve(null))) + beforeEach(() => { + spyOn(atom, 'loadState').andReturn(Promise.resolve(null)) + }) describe('when the opened path exists', () => { - it("adds it to the project's paths", () => { + it("adds it to the project's paths", async () => { const pathToOpen = __filename - waitsForPromise(() => atom.openLocations([{pathToOpen}])) - runs(() => expect(atom.project.getPaths()[0]).toBe(__dirname)) + await atom.openLocations([{pathToOpen}]) + expect(atom.project.getPaths()[0]).toBe(__dirname) }) describe('then a second path is opened with forceAddToWindow', () => { - it("adds the second path to the project's paths", () => { + it("adds the second path to the project's paths", async () => { const firstPathToOpen = __dirname const secondPathToOpen = path.resolve(__dirname, './fixtures') - waitsForPromise(() => atom.openLocations([{pathToOpen: firstPathToOpen}])) - waitsForPromise(() => atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])) - runs(() => expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])) + await atom.openLocations([{pathToOpen: firstPathToOpen}]) + await atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}]) + expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen]) }) }) }) describe('when the opened path does not exist but its parent directory does', () => { - it('adds the parent directory to the project paths', () => { + it('adds the parent directory to the project paths', async () => { const pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt') - waitsForPromise(() => atom.openLocations([{pathToOpen}])) - runs(() => expect(atom.project.getPaths()[0]).toBe(__dirname)) + await atom.openLocations([{pathToOpen}]) + expect(atom.project.getPaths()[0]).toBe(__dirname) }) }) describe('when the opened path is a file', () => { - it('opens it in the workspace', () => { + it('opens it in the workspace', async () => { const pathToOpen = __filename - waitsForPromise(() => atom.openLocations([{pathToOpen}])) - runs(() => expect(atom.workspace.open.mostRecentCall.args[0]).toBe(__filename)) + await atom.openLocations([{pathToOpen}]) + expect(atom.workspace.open.mostRecentCall.args[0]).toBe(__filename) }) }) describe('when the opened path is a directory', () => { - it('does not open it in the workspace', () => { + it('does not open it in the workspace', async () => { const pathToOpen = __dirname - waitsForPromise(() => atom.openLocations([{pathToOpen}])) - runs(() => expect(atom.workspace.open.callCount).toBe(0)) + await atom.openLocations([{pathToOpen}]) + expect(atom.workspace.open.callCount).toBe(0) }) }) describe('when the opened path is a uri', () => { - it("adds it to the project's paths as is", () => { + it("adds it to the project's paths as is", async () => { const pathToOpen = 'remote://server:7644/some/dir/path' spyOn(atom.project, 'addPath') - waitsForPromise(() => atom.openLocations([{pathToOpen}])) - runs(() => expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)) + await atom.openLocations([{pathToOpen}]) + expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen) }) }) }) @@ -739,44 +710,36 @@ describe('AtomEnvironment', () => { }) describe('when there are no project folders', () => { - it('attempts to restore the project state', () => { + it('attempts to restore the project state', async () => { const pathToOpen = __dirname - waitsForPromise(() => atom.openLocations([{pathToOpen}])) - runs(() => { - expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], []) - expect(atom.project.getPaths()).toEqual([]) - }) + await atom.openLocations([{pathToOpen}]) + expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], []) + expect(atom.project.getPaths()).toEqual([]) }) - it('opens the specified files', () => { - waitsForPromise(() => atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}])) - runs(() => { - expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename]) - expect(atom.project.getPaths()).toEqual([]) - }) + it('opens the specified files', async () => { + await atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}]) + expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename]) + expect(atom.project.getPaths()).toEqual([]) }) }) describe('when there are already project folders', () => { beforeEach(() => atom.project.setPaths([__dirname])) - it('does not attempt to restore the project state, instead adding the project paths', () => { + it('does not attempt to restore the project state, instead adding the project paths', async () => { const pathToOpen = path.join(__dirname, 'fixtures') - waitsForPromise(() => atom.openLocations([{pathToOpen, forceAddToWindow: true}])) - runs(() => { - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) - }) + await atom.openLocations([{pathToOpen, forceAddToWindow: true}]) + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() + expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) }) - it('opens the specified files', () => { + it('opens the specified files', async () => { const pathToOpen = path.join(__dirname, 'fixtures') const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt') - waitsForPromise(() => atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}])) - runs(() => { - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]) - expect(atom.project.getPaths()).toEqual([__dirname]) - }) + await atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}]) + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]) + expect(atom.project.getPaths()).toEqual([__dirname]) }) }) }) @@ -789,23 +752,19 @@ describe('AtomEnvironment', () => { if (subscription) subscription.dispose() }) - it('invokes onUpdateAvailable listeners', () => { + it('invokes onUpdateAvailable listeners', async () => { if (process.platform !== 'darwin') return // Test tied to electron autoUpdater, we use something else on Linux and Win32 + const updateAvailablePromise = new Promise(resolve => { + subscription = atom.onUpdateAvailable(resolve) + }) + atom.listenForUpdates() - - const updateAvailableHandler = jasmine.createSpy('update-available-handler') - subscription = atom.onUpdateAvailable(updateAvailableHandler) - - const { autoUpdater } = require('electron').remote + const {autoUpdater} = require('electron').remote autoUpdater.emit('update-downloaded', null, 'notes', 'version') - waitsFor(() => updateAvailableHandler.callCount > 0) - - runs(() => { - const {releaseVersion} = updateAvailableHandler.mostRecentCall.args[0] - expect(releaseVersion).toBe('version') - }) + const {releaseVersion} = await updateAvailablePromise + expect(releaseVersion).toBe('version') }) }) From 843f91c8d2b5d436da2de5a64fbed42f1757a61b Mon Sep 17 00:00:00 2001 From: Justin Ratner Date: Tue, 7 Nov 2017 17:02:25 -0700 Subject: [PATCH 13/43] :arrow_up: text-buffer@13.8.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e87d07cb0..05ceaf931 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "service-hub": "^0.7.4", "sinon": "1.17.4", "temp": "^0.8.3", - "text-buffer": "13.8.1", + "text-buffer": "13.8.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From 156002c4188db6afa69c39d59fff6bf9af974082 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Wed, 8 Nov 2017 07:15:53 -0800 Subject: [PATCH 14/43] :arrow_up: language-typescript --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05ceaf931..ea9ed93ad 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "language-text": "0.7.3", "language-todo": "0.29.3", "language-toml": "0.18.1", - "language-typescript": "0.2.2", + "language-typescript": "0.2.3", "language-xml": "0.35.2", "language-yaml": "0.31.1" }, From 7bae4e73241072ad5dc948ba6814c2b5dcba54af Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Nov 2017 11:37:16 -0800 Subject: [PATCH 15/43] Use dedent for multiline template strings in text-editor-spec --- spec/text-editor-spec.js | 111 +++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 84eea43ef..79b1e37b6 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -3321,13 +3321,13 @@ describe('TextEditor', () => { beforeEach(() => { editor.setSoftWrapped(true) editor.setEditorWidthInChars(80) - editor.setText(`\ -1 -2 -Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. -3 -4\ -`) + editor.setText(dedent ` + 1 + 2 + Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + 3 + 4 + `) }) it('moves the lines past the soft wrapped line', () => { @@ -5881,21 +5881,20 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh editor.duplicateLines() - expect(editor.getTextInBufferRange([[2, 0], [13, 5]])).toBe(`\ -\ if (items.length <= 1) return items; - if (items.length <= 1) return items; - var pivot = items.shift(), current, left = [], right = []; - while(items.length > 0) { - current = items.shift(); - current < pivot ? left.push(current) : right.push(current); - } - var pivot = items.shift(), current, left = [], right = []; - while(items.length > 0) { - current = items.shift(); - current < pivot ? left.push(current) : right.push(current); - }\ -` - ) + expect(editor.getTextInBufferRange([[2, 0], [13, 5]])).toBe(dedent ` + if (items.length <= 1) return items; + if (items.length <= 1) return items; + var pivot = items.shift(), current, left = [], right = []; + while(items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + } + var pivot = items.shift(), current, left = [], right = []; + while(items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + }\ + `.split('\n').map(l => ` ${l}`).join('\n')) expect(editor.getSelectedBufferRanges()).toEqual([[[3, 5], [3, 5]], [[9, 0], [14, 0]]]) // folds are also duplicated @@ -5911,42 +5910,40 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh editor.duplicateLines() - expect(editor.getTextInBufferRange([[2, 0], [11, 5]])).toBe(`\ -\ if (items.length <= 1) return items; - var pivot = items.shift(), current, left = [], right = []; - while(items.length > 0) { - current = items.shift(); - current < pivot ? left.push(current) : right.push(current); - } - while(items.length > 0) { - current = items.shift(); - current < pivot ? left.push(current) : right.push(current); - }\ -` - ) + expect(editor.getTextInBufferRange([[2, 0], [11, 5]])).toBe(dedent` + if (items.length <= 1) return items; + var pivot = items.shift(), current, left = [], right = []; + while(items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + } + while(items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + } + `.split('\n').map(l => ` ${l}`).join('\n')) expect(editor.getSelectedBufferRange()).toEqual([[8, 0], [8, 0]]) }) it('can duplicate the last line of the buffer', () => { editor.setSelectedBufferRange([[11, 0], [12, 2]]) editor.duplicateLines() - expect(editor.getTextInBufferRange([[11, 0], [14, 2]])).toBe(`\ -\ return sort(Array.apply(this, arguments)); -}; - return sort(Array.apply(this, arguments)); -};\ -` - ) + expect(editor.getTextInBufferRange([[11, 0], [14, 2]])).toBe(' ' + dedent ` + return sort(Array.apply(this, arguments)); + }; + return sort(Array.apply(this, arguments)); + }; + `.trim()) expect(editor.getSelectedBufferRange()).toEqual([[13, 0], [14, 2]]) }) it('only duplicates lines containing multiple selections once', () => { - editor.setText(`\ -aaaaaa -bbbbbb -cccccc -dddddd\ -`) + editor.setText(dedent ` + aaaaaa + bbbbbb + cccccc + dddddd + `) editor.setSelectedBufferRanges([ [[0, 1], [0, 2]], [[0, 3], [0, 4]], @@ -5955,15 +5952,15 @@ dddddd\ [[3, 3], [3, 4]] ]) editor.duplicateLines() - expect(editor.getText()).toBe(`\ -aaaaaa -aaaaaa -bbbbbb -cccccc -dddddd -cccccc -dddddd\ -`) + expect(editor.getText()).toBe(dedent ` + aaaaaa + aaaaaa + bbbbbb + cccccc + dddddd + cccccc + dddddd + `) expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 1], [1, 2]], [[1, 3], [1, 4]], From ff8ecf1a4999294ed7e30ac77d66a17da892108c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Nov 2017 14:00:00 -0800 Subject: [PATCH 16/43] Fix errors when passing subword regex to native find methods --- package.json | 2 +- spec/text-editor-spec.js | 29 +++++++++++++++++++++ src/cursor.js | 55 +++++++++++++++++++++------------------- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index ea9ed93ad..89651c465 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "service-hub": "^0.7.4", "sinon": "1.17.4", "temp": "^0.8.3", - "text-buffer": "13.8.2", + "text-buffer": "13.8.3", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 79b1e37b6..fa8406731 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -1077,6 +1077,20 @@ describe('TextEditor', () => { expect(editor.getCursorBufferPosition()).toEqual([0, 1]) }) + it('stops at camelCase boundaries with non-ascii characters', () => { + editor.setText(' gétÁrevìôüsWord\n') + editor.setCursorBufferPosition([0, 16]) + + editor.moveToPreviousSubwordBoundary() + expect(editor.getCursorBufferPosition()).toEqual([0, 12]) + + editor.moveToPreviousSubwordBoundary() + expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + + editor.moveToPreviousSubwordBoundary() + expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + }) + it('skips consecutive non-word characters', () => { editor.setText('e, => \n') editor.setCursorBufferPosition([0, 6]) @@ -1102,6 +1116,21 @@ describe('TextEditor', () => { expect(editor.getCursorBufferPosition()).toEqual([0, 2]) }) + it('skips consecutive uppercase non-ascii letters', () => { + editor.setText(' ÀÁÅDF \n') + editor.setCursorBufferPosition([0, 7]) + editor.moveToPreviousSubwordBoundary() + expect(editor.getCursorBufferPosition()).toEqual([0, 6]) + + editor.moveToPreviousSubwordBoundary() + expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + + editor.setText('ALPhA\n') + editor.setCursorBufferPosition([0, 4]) + editor.moveToPreviousSubwordBoundary() + expect(editor.getCursorBufferPosition()).toEqual([0, 2]) + }) + it('skips consecutive numbers', () => { editor.setText(' 88 \n') editor.setCursorBufferPosition([0, 4]) diff --git a/src/cursor.js b/src/cursor.js index 10bdef804..181eeb971 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -454,23 +454,25 @@ class Cursor extends Model { getPreviousWordBoundaryBufferPosition (options = {}) { const currentBufferPosition = this.getBufferPosition() const previousNonBlankRow = this.editor.buffer.previousNonBlankRow(currentBufferPosition.row) - const scanRange = [[previousNonBlankRow || 0, 0], currentBufferPosition] + const scanRange = Range(Point(previousNonBlankRow || 0, 0), currentBufferPosition) - let beginningOfWordPosition - this.editor.backwardsScanInBufferRange(options.wordRegex || this.wordRegExp(), scanRange, ({range, stop}) => { + const ranges = this.editor.buffer.findAllInRangeSync( + options.wordRegex || this.wordRegExp(), + scanRange + ) + + const range = ranges[ranges.length - 1] + if (range) { if (range.start.row < currentBufferPosition.row && currentBufferPosition.column > 0) { - // force it to stop at the beginning of each line - beginningOfWordPosition = new Point(currentBufferPosition.row, 0) - } else if (range.end.isLessThan(currentBufferPosition)) { - beginningOfWordPosition = range.end + return Point(currentBufferPosition.row, 0) + } else if (currentBufferPosition.isGreaterThan(range.end)) { + return Point.fromObject(range.end) } else { - beginningOfWordPosition = range.start + return Point.fromObject(range.start) } - - if (!beginningOfWordPosition.isEqual(currentBufferPosition)) stop() - }) - - return beginningOfWordPosition || currentBufferPosition + } else { + return currentBufferPosition + } } // Public: Returns buffer position of the next word boundary. It might be on @@ -481,23 +483,24 @@ class Cursor extends Model { // (default: {::wordRegExp}) getNextWordBoundaryBufferPosition (options = {}) { const currentBufferPosition = this.getBufferPosition() - const scanRange = [currentBufferPosition, this.editor.getEofBufferPosition()] + const scanRange = Range(currentBufferPosition, this.editor.getEofBufferPosition()) - let endOfWordPosition - this.editor.scanInBufferRange((options.wordRegex != null ? options.wordRegex : this.wordRegExp()), scanRange, function ({range, stop}) { + const range = this.editor.buffer.findInRangeSync( + options.wordRegex || this.wordRegExp(), + scanRange + ) + + if (range) { if (range.start.row > currentBufferPosition.row) { - // force it to stop at the beginning of each line - endOfWordPosition = new Point(range.start.row, 0) - } else if (range.start.isGreaterThan(currentBufferPosition)) { - endOfWordPosition = range.start + return Point(range.start.row, 0) + } else if (currentBufferPosition.isLessThan(range.start)) { + return Point.fromObject(range.start) } else { - endOfWordPosition = range.end + return Point.fromObject(range.end) } - - if (!endOfWordPosition.isEqual(currentBufferPosition)) stop() - }) - - return endOfWordPosition || currentBufferPosition + } else { + return currentBufferPosition + } } // Public: Retrieves the buffer position of where the current word starts. From fb74992454f9cfa1027523a3448036e07fc67f20 Mon Sep 17 00:00:00 2001 From: Jason Rudolph Date: Wed, 8 Nov 2017 17:44:32 -0500 Subject: [PATCH 17/43] Enhance test to catch bug reported in #16135 Enhance the fake jQuery object to more closely match a real jQuery object. With this change, the test fails, thus allowing us to reproduce the regression reported in #16135. --- spec/tooltip-manager-spec.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/tooltip-manager-spec.js b/spec/tooltip-manager-spec.js index 65587839f..3a6b56a1b 100644 --- a/spec/tooltip-manager-spec.js +++ b/spec/tooltip-manager-spec.js @@ -108,8 +108,12 @@ describe('TooltipManager', () => { const element2 = document.createElement('div') jasmine.attachToDOM(element2) - const fakeJqueryWrapper = [element, element2] - fakeJqueryWrapper.jquery = 'any-version' + const fakeJqueryWrapper = { + 0: element, + 1: element2, + length: 2, + jquery: 'any-version' + } const disposable = manager.add(fakeJqueryWrapper, {title: 'Title'}) hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title')) From 0e82b8bb42ed9ca4a6a12497ffd0622fff0d45e8 Mon Sep 17 00:00:00 2001 From: Jason Rudolph Date: Wed, 8 Nov 2017 17:56:45 -0500 Subject: [PATCH 18/43] :bug: Fix #16135 --- src/tooltip-manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tooltip-manager.js b/src/tooltip-manager.js index 937f831d1..34f96775b 100644 --- a/src/tooltip-manager.js +++ b/src/tooltip-manager.js @@ -114,7 +114,9 @@ class TooltipManager { add (target, options) { if (target.jquery) { const disposable = new CompositeDisposable() - for (const element of target) { disposable.add(this.add(element, options)) } + for (let i = 0; i < target.length; i++) { + disposable.add(this.add(target[i], options)) + } return disposable } From bc774773f74557f50fc9b039ec90cdacf9412341 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Nov 2017 15:28:21 -0800 Subject: [PATCH 19/43] Convert AtomEnvironment to JS --- spec/atom-environment-spec.js | 23 +- src/atom-environment.coffee | 1168 ---------------------------- src/atom-environment.js | 1351 +++++++++++++++++++++++++++++++++ 3 files changed, 1353 insertions(+), 1189 deletions(-) delete mode 100644 src/atom-environment.coffee create mode 100644 src/atom-environment.js diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index b8d7e309a..84b415eab 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -224,25 +224,6 @@ describe('AtomEnvironment', () => { expect(await atom.loadState()).toEqual({stuff: 'cool'}) }) - it("loads state from the storage folder when it can't be found in atom.stateStore", async () => { - jasmine.useRealClock() - - const storageFolderState = {foo: 1, bar: 2} - const serializedState = {someState: 42} - const loadSettings = _.extend(atom.getLoadSettings(), {initialPaths: [temp.mkdirSync('project-directory')]}) - spyOn(atom, 'getLoadSettings').andReturn(loadSettings) - spyOn(atom, 'serialize').andReturn(serializedState) - spyOn(atom, 'getStorageFolder').andReturn(new StorageFolder(temp.mkdirSync('config-directory'))) - atom.project.setPaths(atom.getLoadSettings().initialPaths) - - await atom.stateStore.connect() - atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState) - expect(await atom.loadState()).toEqual(storageFolderState) - - await atom.saveState() - expect(await atom.loadState()).toEqual(serializedState) - }) - it('saves state when the CPU is idle after a keydown or mousedown event', () => { const atomEnv = new AtomEnvironment({ applicationDelegate: global.atom.applicationDelegate @@ -488,7 +469,7 @@ describe('AtomEnvironment', () => { }) it('automatically restores the saved state into the current environment', () => { - const state = Symbol() + const state = {} spyOn(atom.workspace, 'open') spyOn(atom, 'restoreStateIntoThisEnvironment') @@ -505,7 +486,7 @@ describe('AtomEnvironment', () => { getTitle () { return 'title' }, element: document.createElement('div') }) - const state = Symbol() + const state = {} spyOn(atom, 'confirm') atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename]) expect(atom.confirm).not.toHaveBeenCalled() diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee deleted file mode 100644 index 50b5d541e..000000000 --- a/src/atom-environment.coffee +++ /dev/null @@ -1,1168 +0,0 @@ -crypto = require 'crypto' -path = require 'path' -{ipcRenderer} = require 'electron' - -_ = require 'underscore-plus' -{deprecate} = require 'grim' -{CompositeDisposable, Disposable, Emitter} = require 'event-kit' -fs = require 'fs-plus' -{mapSourcePosition} = require '@atom/source-map-support' -Model = require './model' -WindowEventHandler = require './window-event-handler' -StateStore = require './state-store' -StorageFolder = require './storage-folder' -registerDefaultCommands = require './register-default-commands' -{updateProcessEnv} = require './update-process-env' -ConfigSchema = require './config-schema' - -DeserializerManager = require './deserializer-manager' -ViewRegistry = require './view-registry' -NotificationManager = require './notification-manager' -Config = require './config' -KeymapManager = require './keymap-extensions' -TooltipManager = require './tooltip-manager' -CommandRegistry = require './command-registry' -URIHandlerRegistry = require './uri-handler-registry' -GrammarRegistry = require './grammar-registry' -{HistoryManager, HistoryProject} = require './history-manager' -ReopenProjectMenuManager = require './reopen-project-menu-manager' -StyleManager = require './style-manager' -PackageManager = require './package-manager' -ThemeManager = require './theme-manager' -MenuManager = require './menu-manager' -ContextMenuManager = require './context-menu-manager' -CommandInstaller = require './command-installer' -CoreURIHandlers = require './core-uri-handlers' -ProtocolHandlerInstaller = require './protocol-handler-installer' -Project = require './project' -TitleBar = require './title-bar' -Workspace = require './workspace' -PanelContainer = require './panel-container' -Panel = require './panel' -PaneContainer = require './pane-container' -PaneAxis = require './pane-axis' -Pane = require './pane' -Dock = require './dock' -TextEditor = require './text-editor' -TextBuffer = require 'text-buffer' -Gutter = require './gutter' -TextEditorRegistry = require './text-editor-registry' -AutoUpdateManager = require './auto-update-manager' - -# Essential: Atom global for dealing with packages, themes, menus, and the window. -# -# An instance of this class is always available as the `atom` global. -module.exports = -class AtomEnvironment extends Model - @version: 1 # Increment this when the serialization format changes - - lastUncaughtError: null - - ### - Section: Properties - ### - - # Public: A {CommandRegistry} instance - commands: null - - # Public: A {Config} instance - config: null - - # Public: A {Clipboard} instance - clipboard: null - - # Public: A {ContextMenuManager} instance - contextMenu: null - - # Public: A {MenuManager} instance - menu: null - - # Public: A {KeymapManager} instance - keymaps: null - - # Public: A {TooltipManager} instance - tooltips: null - - # Public: A {NotificationManager} instance - notifications: null - - # Public: A {Project} instance - project: null - - # Public: A {GrammarRegistry} instance - grammars: null - - # Public: A {HistoryManager} instance - history: null - - # Public: A {PackageManager} instance - packages: null - - # Public: A {ThemeManager} instance - themes: null - - # Public: A {StyleManager} instance - styles: null - - # Public: A {DeserializerManager} instance - deserializers: null - - # Public: A {ViewRegistry} instance - views: null - - # Public: A {Workspace} instance - workspace: null - - # Public: A {TextEditorRegistry} instance - textEditors: null - - # Private: An {AutoUpdateManager} instance - autoUpdater: null - - saveStateDebounceInterval: 1000 - - ### - Section: Construction and Destruction - ### - - # Call .loadOrCreate instead - constructor: (params={}) -> - {@applicationDelegate, @clipboard, @enablePersistence, onlyLoadBaseStyleSheets, @updateProcessEnv} = params - - @nextProxyRequestId = 0 - @unloaded = false - @loadTime = null - @emitter = new Emitter - @disposables = new CompositeDisposable - @deserializers = new DeserializerManager(this) - @deserializeTimings = {} - @views = new ViewRegistry(this) - TextEditor.setScheduler(@views) - @notifications = new NotificationManager - @updateProcessEnv ?= updateProcessEnv # For testing - - @stateStore = new StateStore('AtomEnvironments', 1) - - @config = new Config({notificationManager: @notifications, @enablePersistence}) - @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} - - @keymaps = new KeymapManager({notificationManager: @notifications}) - @tooltips = new TooltipManager(keymapManager: @keymaps, viewRegistry: @views) - @commands = new CommandRegistry - @uriHandlerRegistry = new URIHandlerRegistry - @grammars = new GrammarRegistry({@config}) - @styles = new StyleManager() - @packages = new PackageManager({ - @config, styleManager: @styles, - commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications, - grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views, - uriHandlerRegistry: @uriHandlerRegistry - }) - @themes = new ThemeManager({ - packageManager: @packages, @config, styleManager: @styles, - notificationManager: @notifications, viewRegistry: @views - }) - @menu = new MenuManager({keymapManager: @keymaps, packageManager: @packages}) - @contextMenu = new ContextMenuManager({keymapManager: @keymaps}) - @packages.setMenuManager(@menu) - @packages.setContextMenuManager(@contextMenu) - @packages.setThemeManager(@themes) - - @project = new Project({notificationManager: @notifications, packageManager: @packages, @config, @applicationDelegate}) - @commandInstaller = new CommandInstaller(@applicationDelegate) - @protocolHandlerInstaller = new ProtocolHandlerInstaller() - - @textEditors = new TextEditorRegistry({ - @config, grammarRegistry: @grammars, assert: @assert.bind(this), - packageManager: @packages - }) - - @workspace = new Workspace({ - @config, @project, packageManager: @packages, grammarRegistry: @grammars, deserializerManager: @deserializers, - notificationManager: @notifications, @applicationDelegate, viewRegistry: @views, assert: @assert.bind(this), - textEditorRegistry: @textEditors, styleManager: @styles, @enablePersistence - }) - - @themes.workspace = @workspace - - @autoUpdater = new AutoUpdateManager({@applicationDelegate}) - - if @keymaps.canLoadBundledKeymapsFromMemory() - @keymaps.loadBundledKeymaps() - - @registerDefaultCommands() - @registerDefaultOpeners() - @registerDefaultDeserializers() - - @windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate}) - - @history = new HistoryManager({@project, @commands, @stateStore}) - # Keep instances of HistoryManager in sync - @disposables.add @history.onDidChangeProjects (e) => - @applicationDelegate.didChangeHistoryManager() unless e.reloaded - - initialize: (params={}) -> - # This will force TextEditorElement to register the custom element, so that - # using `document.createElement('atom-text-editor')` works if it's called - # before opening a buffer. - require './text-editor-element' - - {@window, @document, @blobStore, @configDirPath, onlyLoadBaseStyleSheets} = params - {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() - - if clearWindowState - @getStorageFolder().clear() - @stateStore.clear() - - ConfigSchema.projectHome = { - type: 'string', - default: path.join(fs.getHomeDirectory(), 'github'), - description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' - } - @config.initialize({@configDirPath, resourcePath, projectHomeSchema: ConfigSchema.projectHome}) - - @menu.initialize({resourcePath}) - @contextMenu.initialize({resourcePath, devMode}) - - @keymaps.configDirPath = @configDirPath - @keymaps.resourcePath = resourcePath - @keymaps.devMode = devMode - unless @keymaps.canLoadBundledKeymapsFromMemory() - @keymaps.loadBundledKeymaps() - - @commands.attach(@window) - - @styles.initialize({@configDirPath}) - @packages.initialize({devMode, @configDirPath, resourcePath, safeMode}) - @themes.initialize({@configDirPath, resourcePath, safeMode, devMode}) - - @commandInstaller.initialize(@getVersion()) - @protocolHandlerInstaller.initialize(@config, @notifications) - @uriHandlerRegistry.registerHostHandler('core', CoreURIHandlers.create(this)) - @autoUpdater.initialize() - - @config.load() - - @themes.loadBaseStylesheets() - @initialStyleElements = @styles.getSnapshot() - @themes.initialLoadComplete = true if onlyLoadBaseStyleSheets - @setBodyPlatformClass() - - @stylesElement = @styles.buildStylesElement() - @document.head.appendChild(@stylesElement) - - @keymaps.subscribeToFileReadFailure() - - @installUncaughtErrorHandler() - @attachSaveStateListeners() - @windowEventHandler.initialize(@window, @document) - - didChangeStyles = @didChangeStyles.bind(this) - @disposables.add(@styles.onDidAddStyleElement(didChangeStyles)) - @disposables.add(@styles.onDidUpdateStyleElement(didChangeStyles)) - @disposables.add(@styles.onDidRemoveStyleElement(didChangeStyles)) - - @observeAutoHideMenuBar() - - @disposables.add @applicationDelegate.onDidChangeHistoryManager(=> @history.loadState()) - - preloadPackages: -> - @packages.preloadPackages() - - attachSaveStateListeners: -> - saveState = _.debounce((=> - @window.requestIdleCallback => @saveState({isUnloading: false}) unless @unloaded - ), @saveStateDebounceInterval) - @document.addEventListener('mousedown', saveState, true) - @document.addEventListener('keydown', saveState, true) - @disposables.add new Disposable => - @document.removeEventListener('mousedown', saveState, true) - @document.removeEventListener('keydown', saveState, true) - - registerDefaultDeserializers: -> - @deserializers.add(Workspace) - @deserializers.add(PaneContainer) - @deserializers.add(PaneAxis) - @deserializers.add(Pane) - @deserializers.add(Dock) - @deserializers.add(Project) - @deserializers.add(TextEditor) - @deserializers.add(TextBuffer) - - registerDefaultCommands: -> - registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller, notificationManager: @notifications, @project, @clipboard}) - - registerDefaultOpeners: -> - @workspace.addOpener (uri) => - switch uri - when 'atom://.atom/stylesheet' - @workspace.openTextFile(@styles.getUserStyleSheetPath()) - when 'atom://.atom/keymap' - @workspace.openTextFile(@keymaps.getUserKeymapPath()) - when 'atom://.atom/config' - @workspace.openTextFile(@config.getUserConfigPath()) - when 'atom://.atom/init-script' - @workspace.openTextFile(@getUserInitScriptPath()) - - registerDefaultTargetForKeymaps: -> - @keymaps.defaultTarget = @workspace.getElement() - - observeAutoHideMenuBar: -> - @disposables.add @config.onDidChange 'core.autoHideMenuBar', ({newValue}) => - @setAutoHideMenuBar(newValue) - @setAutoHideMenuBar(true) if @config.get('core.autoHideMenuBar') - - reset: -> - @deserializers.clear() - @registerDefaultDeserializers() - - @config.clear() - @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} - - @keymaps.clear() - @keymaps.loadBundledKeymaps() - - @commands.clear() - @registerDefaultCommands() - - @styles.restoreSnapshot(@initialStyleElements) - - @menu.clear() - - @clipboard.reset() - - @notifications.clear() - - @contextMenu.clear() - - @packages.reset().then => - @workspace.reset(@packages) - @registerDefaultOpeners() - @project.reset(@packages) - @workspace.subscribeToEvents() - @grammars.clear() - @textEditors.clear() - @views.clear() - - destroy: -> - return if not @project - - @disposables.dispose() - @workspace?.destroy() - @workspace = null - @themes.workspace = null - @project?.destroy() - @project = null - @commands.clear() - @stylesElement.remove() - @config.unobserveUserConfig() - @autoUpdater.destroy() - @uriHandlerRegistry.destroy() - - @uninstallWindowEventHandler() - - ### - Section: Event Subscription - ### - - # Extended: Invoke the given callback whenever {::beep} is called. - # - # * `callback` {Function} to be called whenever {::beep} is called. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidBeep: (callback) -> - @emitter.on 'did-beep', callback - - # Extended: Invoke the given callback when there is an unhandled error, but - # before the devtools pop open - # - # * `callback` {Function} to be called whenever there is an unhandled error - # * `event` {Object} - # * `originalError` {Object} the original error object - # * `message` {String} the original error object - # * `url` {String} Url to the file where the error originated. - # * `line` {Number} - # * `column` {Number} - # * `preventDefault` {Function} call this to avoid popping up the dev tools. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillThrowError: (callback) -> - @emitter.on 'will-throw-error', callback - - # Extended: Invoke the given callback whenever there is an unhandled error. - # - # * `callback` {Function} to be called whenever there is an unhandled error - # * `event` {Object} - # * `originalError` {Object} the original error object - # * `message` {String} the original error object - # * `url` {String} Url to the file where the error originated. - # * `line` {Number} - # * `column` {Number} - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidThrowError: (callback) -> - @emitter.on 'did-throw-error', callback - - # TODO: Make this part of the public API. We should make onDidThrowError - # match the interface by only yielding an exception object to the handler - # and deprecating the old behavior. - onDidFailAssertion: (callback) -> - @emitter.on 'did-fail-assertion', callback - - # Extended: Invoke the given callback as soon as the shell environment is - # loaded (or immediately if it was already loaded). - # - # * `callback` {Function} to be called whenever there is an unhandled error - whenShellEnvironmentLoaded: (callback) -> - if @shellEnvironmentLoaded - callback() - new Disposable() - else - @emitter.once 'loaded-shell-environment', callback - - ### - Section: Atom Details - ### - - # Public: Returns a {Boolean} that is `true` if the current window is in development mode. - inDevMode: -> - @devMode ?= @getLoadSettings().devMode - - # Public: Returns a {Boolean} that is `true` if the current window is in safe mode. - inSafeMode: -> - @safeMode ?= @getLoadSettings().safeMode - - # Public: Returns a {Boolean} that is `true` if the current window is running specs. - inSpecMode: -> - @specMode ?= @getLoadSettings().isSpec - - # Returns a {Boolean} indicating whether this the first time the window's been - # loaded. - isFirstLoad: -> - @firstLoad ?= @getLoadSettings().firstLoad - - # Public: Get the version of the Atom application. - # - # Returns the version text {String}. - getVersion: -> - @appVersion ?= @getLoadSettings().appVersion - - # Public: Gets the release channel of the Atom application. - # - # Returns the release channel as a {String}. Will return one of `dev`, `beta`, or `stable`. - getReleaseChannel: -> - version = @getVersion() - if version.indexOf('beta') > -1 - 'beta' - else if version.indexOf('dev') > -1 - 'dev' - else - 'stable' - - # Public: Returns a {Boolean} that is `true` if the current version is an official release. - isReleasedVersion: -> - not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix - - # Public: Get the time taken to completely load the current window. - # - # This time include things like loading and activating packages, creating - # DOM elements for the editor, and reading the config. - # - # Returns the {Number} of milliseconds taken to load the window or null - # if the window hasn't finished loading yet. - getWindowLoadTime: -> - @loadTime - - # Public: Get the load settings for the current window. - # - # Returns an {Object} containing all the load setting key/value pairs. - getLoadSettings: -> - @applicationDelegate.getWindowLoadSettings() - - ### - Section: Managing The Atom Window - ### - - # Essential: Open a new Atom window using the given options. - # - # Calling this method without an options parameter will open a prompt to pick - # a file/folder to open in the new window. - # - # * `params` An {Object} with the following keys: - # * `pathsToOpen` An {Array} of {String} paths to open. - # * `newWindow` A {Boolean}, true to always open a new window instead of - # reusing existing windows depending on the paths to open. - # * `devMode` A {Boolean}, true to open the window in development mode. - # Development mode loads the Atom source from the locally cloned - # repository and also loads all the packages in ~/.atom/dev/packages - # * `safeMode` A {Boolean}, true to open the window in safe mode. Safe - # mode prevents all packages installed to ~/.atom/packages from loading. - open: (params) -> - @applicationDelegate.open(params) - - # Extended: Prompt the user to select one or more folders. - # - # * `callback` A {Function} to call once the user has confirmed the selection. - # * `paths` An {Array} of {String} paths that the user selected, or `null` - # if the user dismissed the dialog. - pickFolder: (callback) -> - @applicationDelegate.pickFolder(callback) - - # Essential: Close the current window. - close: -> - @applicationDelegate.closeWindow() - - # Essential: Get the size of current window. - # - # Returns an {Object} in the format `{width: 1000, height: 700}` - getSize: -> - @applicationDelegate.getWindowSize() - - # Essential: Set the size of current window. - # - # * `width` The {Number} of pixels. - # * `height` The {Number} of pixels. - setSize: (width, height) -> - @applicationDelegate.setWindowSize(width, height) - - # Essential: Get the position of current window. - # - # Returns an {Object} in the format `{x: 10, y: 20}` - getPosition: -> - @applicationDelegate.getWindowPosition() - - # Essential: Set the position of current window. - # - # * `x` The {Number} of pixels. - # * `y` The {Number} of pixels. - setPosition: (x, y) -> - @applicationDelegate.setWindowPosition(x, y) - - # Extended: Get the current window - getCurrentWindow: -> - @applicationDelegate.getCurrentWindow() - - # Extended: Move current window to the center of the screen. - center: -> - @applicationDelegate.centerWindow() - - # Extended: Focus the current window. - focus: -> - @applicationDelegate.focusWindow() - @window.focus() - - # Extended: Show the current window. - show: -> - @applicationDelegate.showWindow() - - # Extended: Hide the current window. - hide: -> - @applicationDelegate.hideWindow() - - # Extended: Reload the current window. - reload: -> - @applicationDelegate.reloadWindow() - - # Extended: Relaunch the entire application. - restartApplication: -> - @applicationDelegate.restartApplication() - - # Extended: Returns a {Boolean} that is `true` if the current window is maximized. - isMaximized: -> - @applicationDelegate.isWindowMaximized() - - maximize: -> - @applicationDelegate.maximizeWindow() - - # Extended: Returns a {Boolean} that is `true` if the current window is in full screen mode. - isFullScreen: -> - @applicationDelegate.isWindowFullScreen() - - # Extended: Set the full screen state of the current window. - setFullScreen: (fullScreen=false) -> - @applicationDelegate.setWindowFullScreen(fullScreen) - - # Extended: Toggle the full screen state of the current window. - toggleFullScreen: -> - @setFullScreen(not @isFullScreen()) - - # Restore the window to its previous dimensions and show it. - # - # Restores the full screen and maximized state after the window has resized to - # prevent resize glitches. - displayWindow: -> - @restoreWindowDimensions().then => - steps = [ - @restoreWindowBackground(), - @show(), - @focus() - ] - steps.push(@setFullScreen(true)) if @windowDimensions?.fullScreen - steps.push(@maximize()) if @windowDimensions?.maximized and process.platform isnt 'darwin' - Promise.all(steps) - - # Get the dimensions of this window. - # - # Returns an {Object} with the following keys: - # * `x` The window's x-position {Number}. - # * `y` The window's y-position {Number}. - # * `width` The window's width {Number}. - # * `height` The window's height {Number}. - getWindowDimensions: -> - browserWindow = @getCurrentWindow() - [x, y] = browserWindow.getPosition() - [width, height] = browserWindow.getSize() - maximized = browserWindow.isMaximized() - {x, y, width, height, maximized} - - # Set the dimensions of the window. - # - # The window will be centered if either the x or y coordinate is not set - # in the dimensions parameter. If x or y are omitted the window will be - # centered. If height or width are omitted only the position will be changed. - # - # * `dimensions` An {Object} with the following keys: - # * `x` The new x coordinate. - # * `y` The new y coordinate. - # * `width` The new width. - # * `height` The new height. - setWindowDimensions: ({x, y, width, height}) -> - steps = [] - if width? and height? - steps.push(@setSize(width, height)) - if x? and y? - steps.push(@setPosition(x, y)) - else - 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 - isValidDimensions: ({x, y, width, height}={}) -> - width > 0 and height > 0 and x + width > 0 and y + height > 0 - - storeWindowDimensions: -> - @windowDimensions = @getWindowDimensions() - if @isValidDimensions(@windowDimensions) - localStorage.setItem("defaultWindowDimensions", JSON.stringify(@windowDimensions)) - - getDefaultWindowDimensions: -> - {windowDimensions} = @getLoadSettings() - return windowDimensions if windowDimensions? - - dimensions = null - try - dimensions = JSON.parse(localStorage.getItem("defaultWindowDimensions")) - catch error - console.warn "Error parsing default window dimensions", error - localStorage.removeItem("defaultWindowDimensions") - - if @isValidDimensions(dimensions) - dimensions - else - {width, height} = @applicationDelegate.getPrimaryDisplayWorkAreaSize() - {x: 0, y: 0, width: Math.min(1024, width), height} - - restoreWindowDimensions: -> - unless @windowDimensions? and @isValidDimensions(@windowDimensions) - @windowDimensions = @getDefaultWindowDimensions() - @setWindowDimensions(@windowDimensions).then => @windowDimensions - - 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() - - backgroundColor = @window.getComputedStyle(@workspace.getElement())['background-color'] - @window.localStorage.setItem('atom:window-background-color', backgroundColor) - - # Call this method when establishing a real application window. - startEditorWindow: -> - @unloaded = false - - updateProcessEnvPromise = @updateProcessEnvAndTriggerHooks() - - loadStatePromise = @loadState().then (state) => - @windowDimensions = state?.windowDimensions - @displayWindow().then => - @commandInstaller.installAtomCommand false, (error) -> - console.warn error.message if error? - @commandInstaller.installApmCommand false, (error) -> - console.warn error.message if error? - - @disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this))) - @disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this))) - @disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this))) - @disposables.add(@applicationDelegate.onURIMessage(@dispatchURIMessage.bind(this))) - @disposables.add @applicationDelegate.onDidRequestUnload => - @saveState({isUnloading: true}) - .catch(console.error) - .then => - @workspace?.confirmClose({ - windowCloseRequested: true, - projectHasPaths: @project.getPaths().length > 0 - }) - .then (closing) => - if closing - @packages.deactivatePackages().then -> closing - else - closing - - @listenForUpdates() - - @registerDefaultTargetForKeymaps() - - @packages.loadPackages() - - startTime = Date.now() - @deserialize(state).then => - @deserializeTimings.atom = Date.now() - startTime - - if process.platform is 'darwin' and @config.get('core.titleBar') is 'custom' - @workspace.addHeaderPanel({item: new TitleBar({@workspace, @themes, @applicationDelegate})}) - @document.body.classList.add('custom-title-bar') - if process.platform is 'darwin' and @config.get('core.titleBar') is 'custom-inset' - @workspace.addHeaderPanel({item: new TitleBar({@workspace, @themes, @applicationDelegate})}) - @document.body.classList.add('custom-inset-title-bar') - if process.platform is 'darwin' and @config.get('core.titleBar') is 'hidden' - @document.body.classList.add('hidden-title-bar') - - @document.body.appendChild(@workspace.getElement()) - @backgroundStylesheet?.remove() - - @watchProjectPaths() - - @packages.activate() - @keymaps.loadUserKeymap() - @requireUserInitScript() unless @getLoadSettings().safeMode - - @menu.update() - - @openInitialEmptyEditorIfNecessary() - - loadHistoryPromise = @history.loadState().then => - @reopenProjectMenuManager = new ReopenProjectMenuManager({ - @menu, @commands, @history, @config, - open: (paths) => @open(pathsToOpen: paths) - }) - @reopenProjectMenuManager.update() - - Promise.all([loadStatePromise, loadHistoryPromise, updateProcessEnvPromise]) - - serialize: (options) -> - version: @constructor.version - project: @project.serialize(options) - workspace: @workspace.serialize() - packageStates: @packages.serialize() - grammars: {grammarOverridesByPath: @grammars.grammarOverridesByPath} - fullScreen: @isFullScreen() - windowDimensions: @windowDimensions - textEditors: @textEditors.serialize() - - unloadEditorWindow: -> - return if not @project - - @storeWindowBackground() - @saveBlobStoreSync() - @unloaded = true - - saveBlobStoreSync: -> - if @enablePersistence - @blobStore.save() - - openInitialEmptyEditorIfNecessary: -> - return unless @config.get('core.openEmptyEditorOnStart') - if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0 - @workspace.open(null) - - installUncaughtErrorHandler: -> - @previousWindowErrorHandler = @window.onerror - @window.onerror = => - @lastUncaughtError = Array::slice.call(arguments) - [message, url, line, column, originalError] = @lastUncaughtError - - {line, column, source} = mapSourcePosition({source: url, line, column}) - - if url is '' - url = source - - eventObject = {message, url, line, column, originalError} - - openDevTools = true - eventObject.preventDefault = -> openDevTools = false - - @emitter.emit 'will-throw-error', eventObject - - if openDevTools - @openDevTools().then => @executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")') - - @emitter.emit 'did-throw-error', {message, url, line, column, originalError} - - uninstallUncaughtErrorHandler: -> - @window.onerror = @previousWindowErrorHandler - - installWindowEventHandler: -> - @windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate}) - @windowEventHandler.initialize(@window, @document) - - uninstallWindowEventHandler: -> - @windowEventHandler?.unsubscribe() - @windowEventHandler = null - - didChangeStyles: (styleElement) -> - TextEditor.didUpdateStyles() - if styleElement.textContent.indexOf('scrollbar') >= 0 - TextEditor.didUpdateScrollbarStyles() - - updateProcessEnvAndTriggerHooks: -> - @updateProcessEnv(@getLoadSettings().env).then => - @shellEnvironmentLoaded = true - @emitter.emit('loaded-shell-environment') - @packages.triggerActivationHook('core:loaded-shell-environment') - - ### - Section: Messaging the User - ### - - # Essential: Visually and audibly trigger a beep. - beep: -> - @applicationDelegate.playBeepSound() if @config.get('core.audioBeep') - @emitter.emit 'did-beep' - - # Essential: A flexible way to open a dialog akin to an alert dialog. - # - # If the dialog is closed (via `Esc` key or `X` in the top corner) without selecting a button - # the first button will be clicked unless a "Cancel" or "No" button is provided. - # - # ## Examples - # - # ```coffee - # atom.confirm - # message: 'How you feeling?' - # detailedMessage: 'Be honest.' - # buttons: - # Good: -> window.alert('good to hear') - # Bad: -> window.alert('bummer') - # ``` - # - # * `options` An {Object} with the following keys: - # * `message` The {String} message to display. - # * `detailedMessage` (optional) The {String} detailed message to display. - # * `buttons` (optional) Either an array of strings or an object where keys are - # button names and the values are callbacks to invoke when clicked. - # - # Returns the chosen button index {Number} if the buttons option is an array or the return value of the callback if the buttons option is an object. - confirm: (params={}) -> - @applicationDelegate.confirm(params) - - ### - Section: Managing the Dev Tools - ### - - # Extended: Open the dev tools for the current window. - # - # Returns a {Promise} that resolves when the DevTools have been opened. - openDevTools: -> - @applicationDelegate.openWindowDevTools() - - # Extended: Toggle the visibility of the dev tools for the current window. - # - # Returns a {Promise} that resolves when the DevTools have been opened or - # closed. - toggleDevTools: -> - @applicationDelegate.toggleWindowDevTools() - - # Extended: Execute code in dev tools. - executeJavaScriptInDevTools: (code) -> - @applicationDelegate.executeJavaScriptInWindowDevTools(code) - - ### - Section: Private - ### - - assert: (condition, message, callbackOrMetadata) -> - return true if condition - - error = new Error("Assertion failed: #{message}") - Error.captureStackTrace(error, @assert) - - if callbackOrMetadata? - if typeof callbackOrMetadata is 'function' - callbackOrMetadata?(error) - else - error.metadata = callbackOrMetadata - - @emitter.emit 'did-fail-assertion', error - unless @isReleasedVersion() - throw error - - false - - loadThemes: -> - @themes.load() - - # Notify the browser project of the window's current project path - watchProjectPaths: -> - @disposables.add @project.onDidChangePaths => - @applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths()) - - setDocumentEdited: (edited) -> - @applicationDelegate.setWindowDocumentEdited?(edited) - - setRepresentedFilename: (filename) -> - @applicationDelegate.setWindowRepresentedFilename?(filename) - - addProjectFolder: -> - @pickFolder (selectedPaths = []) => - @addToProject(selectedPaths) - - addToProject: (projectPaths) -> - @loadState(@getStateKey(projectPaths)).then (state) => - if state and @project.getPaths().length is 0 - @attemptRestoreProjectStateForPaths(state, projectPaths) - else - @project.addPath(folder) for folder in projectPaths - - attemptRestoreProjectStateForPaths: (state, projectPaths, filesToOpen = []) -> - center = @workspace.getCenter() - windowIsUnused = => - for container in @workspace.getPaneContainers() - for item in container.getPaneItems() - if item instanceof TextEditor - return false if item.getPath() or item.isModified() - else - return false if container is center - true - - if windowIsUnused() - @restoreStateIntoThisEnvironment(state) - Promise.all (@workspace.open(file) for file in filesToOpen) - else - nouns = if projectPaths.length is 1 then 'folder' else 'folders' - btn = @confirm - message: 'Previous automatically-saved project state detected' - detailedMessage: "There is previously saved state for the selected #{nouns}. " + - "Would you like to add the #{nouns} to this window, permanently discarding the saved state, " + - "or open the #{nouns} in a new window, restoring the saved state?" - buttons: [ - '&Open in new window and recover state' - '&Add to this window and discard state' - ] - if btn is 0 - @open - pathsToOpen: projectPaths.concat(filesToOpen) - newWindow: true - devMode: @inDevMode() - safeMode: @inSafeMode() - Promise.resolve(null) - else if btn is 1 - @project.addPath(selectedPath) for selectedPath in projectPaths - Promise.all (@workspace.open(file) for file in filesToOpen) - - restoreStateIntoThisEnvironment: (state) -> - state.fullScreen = @isFullScreen() - pane.destroy() for pane in @workspace.getPanes() - @deserialize(state) - - showSaveDialog: (callback) -> - callback(@showSaveDialogSync()) - - showSaveDialogSync: (options={}) -> - @applicationDelegate.showSaveDialog(options) - - saveState: (options, storageKey) -> - new Promise (resolve, reject) => - if @enablePersistence and @project - state = @serialize(options) - savePromise = - if storageKey ?= @getStateKey(@project?.getPaths()) - @stateStore.save(storageKey, state) - else - @applicationDelegate.setTemporaryWindowState(state) - savePromise.catch(reject).then(resolve) - else - resolve() - - loadState: (stateKey) -> - if @enablePersistence - if stateKey ?= @getStateKey(@getLoadSettings().initialPaths) - @stateStore.load(stateKey).then (state) => - if state - state - else - # TODO: remove this when every user has migrated to the IndexedDb state store. - @getStorageFolder().load(stateKey) - else - @applicationDelegate.getTemporaryWindowState() - else - Promise.resolve(null) - - deserialize: (state) -> - return Promise.resolve() unless state? - - if grammarOverridesByPath = state.grammars?.grammarOverridesByPath - @grammars.grammarOverridesByPath = grammarOverridesByPath - - @setFullScreen(state.fullScreen) - - missingProjectPaths = [] - - @packages.packageStates = state.packageStates ? {} - - startTime = Date.now() - if state.project? - projectPromise = @project.deserialize(state.project, @deserializers) - .catch (err) => - if err.missingProjectPaths? - missingProjectPaths.push(err.missingProjectPaths...) - else - @notifications.addError "Unable to deserialize project", description: err.message, stack: err.stack - else - projectPromise = Promise.resolve() - - projectPromise.then => - @deserializeTimings.project = Date.now() - startTime - - @textEditors.deserialize(state.textEditors) if state.textEditors - - startTime = Date.now() - @workspace.deserialize(state.workspace, @deserializers) if state.workspace? - @deserializeTimings.workspace = Date.now() - startTime - - if missingProjectPaths.length > 0 - count = if missingProjectPaths.length is 1 then '' else missingProjectPaths.length + ' ' - noun = if missingProjectPaths.length is 1 then 'directory' else 'directories' - toBe = if missingProjectPaths.length is 1 then 'is' else 'are' - escaped = missingProjectPaths.map (projectPath) -> "`#{projectPath}`" - group = switch escaped.length - when 1 then escaped[0] - when 2 then "#{escaped[0]} and #{escaped[1]}" - else escaped[..-2].join(", ") + ", and #{escaped[escaped.length - 1]}" - - @notifications.addError "Unable to open #{count}project #{noun}", - description: "Project #{noun} #{group} #{toBe} no longer on disk." - - getStateKey: (paths) -> - if paths?.length > 0 - sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex') - "editor-#{sha1}" - else - null - - getStorageFolder: -> - @storageFolder ?= new StorageFolder(@getConfigDirPath()) - - getConfigDirPath: -> - @configDirPath ?= process.env.ATOM_HOME - - getUserInitScriptPath: -> - initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee']) - initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee') - - requireUserInitScript: -> - if userInitScriptPath = @getUserInitScriptPath() - try - require(userInitScriptPath) if fs.isFileSync(userInitScriptPath) - catch error - @notifications.addError "Failed to load `#{userInitScriptPath}`", - detail: error.message - dismissable: true - - # TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead - onUpdateAvailable: (callback) -> - @emitter.on 'update-available', callback - - updateAvailable: (details) -> - @emitter.emit 'update-available', details - - listenForUpdates: -> - # listen for updates available locally (that have been successfully downloaded) - @disposables.add(@autoUpdater.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this))) - - setBodyPlatformClass: -> - @document.body.classList.add("platform-#{process.platform}") - - setAutoHideMenuBar: (autoHide) -> - @applicationDelegate.setAutoHideWindowMenuBar(autoHide) - @applicationDelegate.setWindowMenuBarVisibility(not autoHide) - - dispatchApplicationMenuCommand: (command, arg) -> - activeElement = @document.activeElement - # Use the workspace element if body has focus - if activeElement is @document.body - activeElement = @workspace.getElement() - @commands.dispatch(activeElement, command, arg) - - dispatchContextMenuCommand: (command, args...) -> - @commands.dispatch(@contextMenu.activeElement, command, args) - - dispatchURIMessage: (uri) -> - if @packages.hasLoadedInitialPackages() - @uriHandlerRegistry.handleURI(uri) - else - sub = @packages.onDidLoadInitialPackages -> - sub.dispose() - @uriHandlerRegistry.handleURI(uri) - - openLocations: (locations) -> - needsProjectPaths = @project?.getPaths().length is 0 - - foldersToAddToProject = [] - fileLocationsToOpen = [] - - pushFolderToOpen = (folder) -> - if folder not in foldersToAddToProject - foldersToAddToProject.push(folder) - - for {pathToOpen, initialLine, initialColumn, forceAddToWindow} in locations - if pathToOpen? and (needsProjectPaths or forceAddToWindow) - if fs.existsSync(pathToOpen) - pushFolderToOpen @project.getDirectoryForProjectPath(pathToOpen).getPath() - else if fs.existsSync(path.dirname(pathToOpen)) - pushFolderToOpen @project.getDirectoryForProjectPath(path.dirname(pathToOpen)).getPath() - else - pushFolderToOpen @project.getDirectoryForProjectPath(pathToOpen).getPath() - - unless fs.isDirectorySync(pathToOpen) - fileLocationsToOpen.push({pathToOpen, initialLine, initialColumn}) - - promise = Promise.resolve(null) - if foldersToAddToProject.length > 0 - promise = @loadState(@getStateKey(foldersToAddToProject)).then (state) => - if state and needsProjectPaths # only load state if this is the first path added to the project - files = (location.pathToOpen for location in fileLocationsToOpen) - @attemptRestoreProjectStateForPaths(state, foldersToAddToProject, files) - else - promises = [] - @project.addPath(folder) for folder in foldersToAddToProject - for {pathToOpen, initialLine, initialColumn} in fileLocationsToOpen - promises.push @workspace?.open(pathToOpen, {initialLine, initialColumn}) - Promise.all(promises) - else - promises = [] - for {pathToOpen, initialLine, initialColumn} in fileLocationsToOpen - promises.push @workspace?.open(pathToOpen, {initialLine, initialColumn}) - promise = Promise.all(promises) - - promise.then -> - ipcRenderer.send 'window-command', 'window:locations-opened' - - resolveProxy: (url) -> - return new Promise (resolve, reject) => - requestId = @nextProxyRequestId++ - disposable = @applicationDelegate.onDidResolveProxy (id, proxy) -> - if id is requestId - disposable.dispose() - resolve(proxy) - - @applicationDelegate.resolveProxy(requestId, url) - -# Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner. -Promise.prototype.done = (callback) -> - deprecate("Atom now uses ES6 Promises instead of Q. Call promise.then instead of promise.done") - @then(callback) diff --git a/src/atom-environment.js b/src/atom-environment.js new file mode 100644 index 000000000..1c2f1ebcf --- /dev/null +++ b/src/atom-environment.js @@ -0,0 +1,1351 @@ +const crypto = require('crypto') +const path = require('path') +const {ipcRenderer} = require('electron') + +const _ = require('underscore-plus') +const {deprecate} = require('grim') +const {CompositeDisposable, Disposable, Emitter} = require('event-kit') +const fs = require('fs-plus') +const {mapSourcePosition} = require('@atom/source-map-support') +const WindowEventHandler = require('./window-event-handler') +const StateStore = require('./state-store') +const StorageFolder = require('./storage-folder') +const registerDefaultCommands = require('./register-default-commands') +const {updateProcessEnv} = require('./update-process-env') +const ConfigSchema = require('./config-schema') + +const DeserializerManager = require('./deserializer-manager') +const ViewRegistry = require('./view-registry') +const NotificationManager = require('./notification-manager') +const Config = require('./config') +const KeymapManager = require('./keymap-extensions') +const TooltipManager = require('./tooltip-manager') +const CommandRegistry = require('./command-registry') +const URIHandlerRegistry = require('./uri-handler-registry') +const GrammarRegistry = require('./grammar-registry') +const {HistoryManager} = require('./history-manager') +const ReopenProjectMenuManager = require('./reopen-project-menu-manager') +const StyleManager = require('./style-manager') +const PackageManager = require('./package-manager') +const ThemeManager = require('./theme-manager') +const MenuManager = require('./menu-manager') +const ContextMenuManager = require('./context-menu-manager') +const CommandInstaller = require('./command-installer') +const CoreURIHandlers = require('./core-uri-handlers') +const ProtocolHandlerInstaller = require('./protocol-handler-installer') +const Project = require('./project') +const TitleBar = require('./title-bar') +const Workspace = require('./workspace') +const PaneContainer = require('./pane-container') +const PaneAxis = require('./pane-axis') +const Pane = require('./pane') +const Dock = require('./dock') +const TextEditor = require('./text-editor') +const TextBuffer = require('text-buffer') +const TextEditorRegistry = require('./text-editor-registry') +const AutoUpdateManager = require('./auto-update-manager') + +let nextId = 0 + +// Essential: Atom global for dealing with packages, themes, menus, and the window. +// +// An instance of this class is always available as the `atom` global. +class AtomEnvironment { + /* + Section: Construction and Destruction + */ + + // Call .loadOrCreate instead + constructor (params = {}) { + this.id = (params.id != null) ? params.id : nextId++ + this.clipboard = params.clipboard + this.updateProcessEnv = params.updateProcessEnv || updateProcessEnv + this.enablePersistence = params.enablePersistence + this.applicationDelegate = params.applicationDelegate + + this.nextProxyRequestId = 0 + this.unloaded = false + this.loadTime = null + this.emitter = new Emitter() + this.disposables = new CompositeDisposable() + this.deserializers = new DeserializerManager(this) + this.deserializeTimings = {} + this.views = new ViewRegistry(this) + TextEditor.setScheduler(this.views) + this.notifications = new NotificationManager() + + this.stateStore = new StateStore('AtomEnvironments', 1) + + this.config = new Config({ + notificationManager: this.notifications, + enablePersistence: this.enablePersistence + }) + this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)}) + + this.keymaps = new KeymapManager({notificationManager: this.notifications}) + this.tooltips = new TooltipManager({keymapManager: this.keymaps, viewRegistry: this.views}) + this.commands = new CommandRegistry() + this.uriHandlerRegistry = new URIHandlerRegistry() + this.grammars = new GrammarRegistry({config: this.config}) + this.styles = new StyleManager() + this.packages = new PackageManager({ + config: this.config, + styleManager: this.styles, + commandRegistry: this.commands, + keymapManager: this.keymaps, + notificationManager: this.notifications, + grammarRegistry: this.grammars, + deserializerManager: this.deserializers, + viewRegistry: this.views, + uriHandlerRegistry: this.uriHandlerRegistry + }) + this.themes = new ThemeManager({ + packageManager: this.packages, + config: this.config, + styleManager: this.styles, + notificationManager: this.notifications, + viewRegistry: this.views + }) + this.menu = new MenuManager({keymapManager: this.keymaps, packageManager: this.packages}) + this.contextMenu = new ContextMenuManager({keymapManager: this.keymaps}) + this.packages.setMenuManager(this.menu) + this.packages.setContextMenuManager(this.contextMenu) + this.packages.setThemeManager(this.themes) + + this.project = new Project({notificationManager: this.notifications, packageManager: this.packages, config: this.config, applicationDelegate: this.applicationDelegate}) + this.commandInstaller = new CommandInstaller(this.applicationDelegate) + this.protocolHandlerInstaller = new ProtocolHandlerInstaller() + + this.textEditors = new TextEditorRegistry({ + config: this.config, + grammarRegistry: this.grammars, + assert: this.assert.bind(this), + packageManager: this.packages + }) + + this.workspace = new Workspace({ + config: this.config, + project: this.project, + packageManager: this.packages, + grammarRegistry: this.grammars, + deserializerManager: this.deserializers, + notificationManager: this.notifications, + applicationDelegate: this.applicationDelegate, + viewRegistry: this.views, + assert: this.assert.bind(this), + textEditorRegistry: this.textEditors, + styleManager: this.styles, + enablePersistence: this.enablePersistence + }) + + this.themes.workspace = this.workspace + + this.autoUpdater = new AutoUpdateManager({applicationDelegate: this.applicationDelegate}) + + if (this.keymaps.canLoadBundledKeymapsFromMemory()) { + this.keymaps.loadBundledKeymaps() + } + + this.registerDefaultCommands() + this.registerDefaultOpeners() + this.registerDefaultDeserializers() + + this.windowEventHandler = new WindowEventHandler({atomEnvironment: this, applicationDelegate: this.applicationDelegate}) + + this.history = new HistoryManager({project: this.project, commands: this.commands, stateStore: this.stateStore}) + // Keep instances of HistoryManager in sync + this.disposables.add(this.history.onDidChangeProjects(event => { + if (!event.reloaded) this.applicationDelegate.didChangeHistoryManager() + })) + } + + initialize (params = {}) { + // This will force TextEditorElement to register the custom element, so that + // using `document.createElement('atom-text-editor')` works if it's called + // before opening a buffer. + require('./text-editor-element') + + this.window = params.window + this.document = params.document + this.blobStore = params.blobStore + this.configDirPath = params.configDirPath + + const {devMode, safeMode, resourcePath, clearWindowState} = this.getLoadSettings() + + if (clearWindowState) { + this.getStorageFolder().clear() + this.stateStore.clear() + } + + ConfigSchema.projectHome = { + type: 'string', + default: path.join(fs.getHomeDirectory(), 'github'), + description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' + } + this.config.initialize({configDirPath: this.configDirPath, resourcePath, projectHomeSchema: ConfigSchema.projectHome}) + + this.menu.initialize({resourcePath}) + this.contextMenu.initialize({resourcePath, devMode}) + + this.keymaps.configDirPath = this.configDirPath + this.keymaps.resourcePath = resourcePath + this.keymaps.devMode = devMode + if (!this.keymaps.canLoadBundledKeymapsFromMemory()) { + this.keymaps.loadBundledKeymaps() + } + + this.commands.attach(this.window) + + this.styles.initialize({configDirPath: this.configDirPath}) + this.packages.initialize({devMode, configDirPath: this.configDirPath, resourcePath, safeMode}) + this.themes.initialize({configDirPath: this.configDirPath, resourcePath, safeMode, devMode}) + + this.commandInstaller.initialize(this.getVersion()) + this.protocolHandlerInstaller.initialize(this.config, this.notifications) + this.uriHandlerRegistry.registerHostHandler('core', CoreURIHandlers.create(this)) + this.autoUpdater.initialize() + + this.config.load() + + this.themes.loadBaseStylesheets() + this.initialStyleElements = this.styles.getSnapshot() + if (params.onlyLoadBaseStyleSheets) this.themes.initialLoadComplete = true + this.setBodyPlatformClass() + + this.stylesElement = this.styles.buildStylesElement() + this.document.head.appendChild(this.stylesElement) + + this.keymaps.subscribeToFileReadFailure() + + this.installUncaughtErrorHandler() + this.attachSaveStateListeners() + this.windowEventHandler.initialize(this.window, this.document) + + const didChangeStyles = this.didChangeStyles.bind(this) + this.disposables.add(this.styles.onDidAddStyleElement(didChangeStyles)) + this.disposables.add(this.styles.onDidUpdateStyleElement(didChangeStyles)) + this.disposables.add(this.styles.onDidRemoveStyleElement(didChangeStyles)) + + this.observeAutoHideMenuBar() + + this.disposables.add(this.applicationDelegate.onDidChangeHistoryManager(() => this.history.loadState())) + } + + preloadPackages () { + return this.packages.preloadPackages() + } + + attachSaveStateListeners () { + const saveState = _.debounce(() => { + this.window.requestIdleCallback(() => { + if (!this.unloaded) this.saveState({isUnloading: false}) + }) + }, this.saveStateDebounceInterval) + this.document.addEventListener('mousedown', saveState, true) + this.document.addEventListener('keydown', saveState, true) + this.disposables.add(new Disposable(() => { + this.document.removeEventListener('mousedown', saveState, true) + this.document.removeEventListener('keydown', saveState, true) + })) + } + + registerDefaultDeserializers () { + this.deserializers.add(Workspace) + this.deserializers.add(PaneContainer) + this.deserializers.add(PaneAxis) + this.deserializers.add(Pane) + this.deserializers.add(Dock) + this.deserializers.add(Project) + this.deserializers.add(TextEditor) + this.deserializers.add(TextBuffer) + } + + registerDefaultCommands () { + registerDefaultCommands({commandRegistry: this.commands, config: this.config, commandInstaller: this.commandInstaller, notificationManager: this.notifications, project: this.project, clipboard: this.clipboard}) + } + + registerDefaultOpeners () { + this.workspace.addOpener(uri => { + switch (uri) { + case 'atom://.atom/stylesheet': + return this.workspace.openTextFile(this.styles.getUserStyleSheetPath()) + case 'atom://.atom/keymap': + return this.workspace.openTextFile(this.keymaps.getUserKeymapPath()) + case 'atom://.atom/config': + return this.workspace.openTextFile(this.config.getUserConfigPath()) + case 'atom://.atom/init-script': + return this.workspace.openTextFile(this.getUserInitScriptPath()) + } + }) + } + + registerDefaultTargetForKeymaps () { + this.keymaps.defaultTarget = this.workspace.getElement() + } + + observeAutoHideMenuBar () { + this.disposables.add(this.config.onDidChange('core.autoHideMenuBar', ({newValue}) => { + this.setAutoHideMenuBar(newValue) + })) + if (this.config.get('core.autoHideMenuBar')) this.setAutoHideMenuBar(true) + } + + reset () { + this.deserializers.clear() + this.registerDefaultDeserializers() + + this.config.clear() + this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)}) + + this.keymaps.clear() + this.keymaps.loadBundledKeymaps() + + this.commands.clear() + this.registerDefaultCommands() + + this.styles.restoreSnapshot(this.initialStyleElements) + + this.menu.clear() + + this.clipboard.reset() + + this.notifications.clear() + + this.contextMenu.clear() + + return this.packages.reset().then(() => { + this.workspace.reset(this.packages) + this.registerDefaultOpeners() + this.project.reset(this.packages) + this.workspace.subscribeToEvents() + this.grammars.clear() + this.textEditors.clear() + this.views.clear() + }) + } + + destroy () { + if (!this.project) return + + this.disposables.dispose() + if (this.workspace) this.workspace.destroy() + this.workspace = null + this.themes.workspace = null + if (this.project) this.project.destroy() + this.project = null + this.commands.clear() + this.stylesElement.remove() + this.config.unobserveUserConfig() + this.autoUpdater.destroy() + this.uriHandlerRegistry.destroy() + + this.uninstallWindowEventHandler() + } + + /* + Section: Event Subscription + */ + + // Extended: Invoke the given callback whenever {::beep} is called. + // + // * `callback` {Function} to be called whenever {::beep} is called. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidBeep (callback) { + return this.emitter.on('did-beep', callback) + } + + // Extended: Invoke the given callback when there is an unhandled error, but + // before the devtools pop open + // + // * `callback` {Function} to be called whenever there is an unhandled error + // * `event` {Object} + // * `originalError` {Object} the original error object + // * `message` {String} the original error object + // * `url` {String} Url to the file where the error originated. + // * `line` {Number} + // * `column` {Number} + // * `preventDefault` {Function} call this to avoid popping up the dev tools. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onWillThrowError (callback) { + return this.emitter.on('will-throw-error', callback) + } + + // Extended: Invoke the given callback whenever there is an unhandled error. + // + // * `callback` {Function} to be called whenever there is an unhandled error + // * `event` {Object} + // * `originalError` {Object} the original error object + // * `message` {String} the original error object + // * `url` {String} Url to the file where the error originated. + // * `line` {Number} + // * `column` {Number} + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidThrowError (callback) { + return this.emitter.on('did-throw-error', callback) + } + + // TODO: Make this part of the public API. We should make onDidThrowError + // match the interface by only yielding an exception object to the handler + // and deprecating the old behavior. + onDidFailAssertion (callback) { + return this.emitter.on('did-fail-assertion', callback) + } + + // Extended: Invoke the given callback as soon as the shell environment is + // loaded (or immediately if it was already loaded). + // + // * `callback` {Function} to be called whenever there is an unhandled error + whenShellEnvironmentLoaded (callback) { + if (this.shellEnvironmentLoaded) { + callback() + return new Disposable() + } else { + return this.emitter.once('loaded-shell-environment', callback) + } + } + + /* + Section: Atom Details + */ + + // Public: Returns a {Boolean} that is `true` if the current window is in development mode. + inDevMode () { + if (this.devMode == null) this.devMode = this.getLoadSettings().devMode + return this.devMode + } + + // Public: Returns a {Boolean} that is `true` if the current window is in safe mode. + inSafeMode () { + if (this.safeMode == null) this.safeMode = this.getLoadSettings().safeMode + return this.safeMode + } + + // Public: Returns a {Boolean} that is `true` if the current window is running specs. + inSpecMode () { + if (this.specMode == null) this.specMode = this.getLoadSettings().isSpec + return this.specMode + } + + // Returns a {Boolean} indicating whether this the first time the window's been + // loaded. + isFirstLoad () { + if (this.firstLoad == null) this.firstLoad = this.getLoadSettings().firstLoad + return this.firstLoad + } + + // Public: Get the version of the Atom application. + // + // Returns the version text {String}. + getVersion () { + if (this.appVersion == null) this.appVersion = this.getLoadSettings().appVersion + return this.appVersion + } + + // Public: Gets the release channel of the Atom application. + // + // Returns the release channel as a {String}. Will return one of `dev`, `beta`, or `stable`. + getReleaseChannel () { + const version = this.getVersion() + if (version.includes('beta')) { + return 'beta' + } else if (version.includes('dev')) { + return 'dev' + } else { + return 'stable' + } + } + + // Public: Returns a {Boolean} that is `true` if the current version is an official release. + isReleasedVersion () { + return !/\w{7}/.test(this.getVersion()) // Check if the release is a 7-character SHA prefix + } + + // Public: Get the time taken to completely load the current window. + // + // This time include things like loading and activating packages, creating + // DOM elements for the editor, and reading the config. + // + // Returns the {Number} of milliseconds taken to load the window or null + // if the window hasn't finished loading yet. + getWindowLoadTime () { + return this.loadTime + } + + // Public: Get the load settings for the current window. + // + // Returns an {Object} containing all the load setting key/value pairs. + getLoadSettings () { + return this.applicationDelegate.getWindowLoadSettings() + } + + /* + Section: Managing The Atom Window + */ + + // Essential: Open a new Atom window using the given options. + // + // Calling this method without an options parameter will open a prompt to pick + // a file/folder to open in the new window. + // + // * `params` An {Object} with the following keys: + // * `pathsToOpen` An {Array} of {String} paths to open. + // * `newWindow` A {Boolean}, true to always open a new window instead of + // reusing existing windows depending on the paths to open. + // * `devMode` A {Boolean}, true to open the window in development mode. + // Development mode loads the Atom source from the locally cloned + // repository and also loads all the packages in ~/.atom/dev/packages + // * `safeMode` A {Boolean}, true to open the window in safe mode. Safe + // mode prevents all packages installed to ~/.atom/packages from loading. + open (params) { + return this.applicationDelegate.open(params) + } + + // Extended: Prompt the user to select one or more folders. + // + // * `callback` A {Function} to call once the user has confirmed the selection. + // * `paths` An {Array} of {String} paths that the user selected, or `null` + // if the user dismissed the dialog. + pickFolder (callback) { + return this.applicationDelegate.pickFolder(callback) + } + + // Essential: Close the current window. + close () { + return this.applicationDelegate.closeWindow() + } + + // Essential: Get the size of current window. + // + // Returns an {Object} in the format `{width: 1000, height: 700}` + getSize () { + return this.applicationDelegate.getWindowSize() + } + + // Essential: Set the size of current window. + // + // * `width` The {Number} of pixels. + // * `height` The {Number} of pixels. + setSize (width, height) { + return this.applicationDelegate.setWindowSize(width, height) + } + + // Essential: Get the position of current window. + // + // Returns an {Object} in the format `{x: 10, y: 20}` + getPosition () { + return this.applicationDelegate.getWindowPosition() + } + + // Essential: Set the position of current window. + // + // * `x` The {Number} of pixels. + // * `y` The {Number} of pixels. + setPosition (x, y) { + return this.applicationDelegate.setWindowPosition(x, y) + } + + // Extended: Get the current window + getCurrentWindow () { + return this.applicationDelegate.getCurrentWindow() + } + + // Extended: Move current window to the center of the screen. + center () { + return this.applicationDelegate.centerWindow() + } + + // Extended: Focus the current window. + focus () { + this.applicationDelegate.focusWindow() + return this.window.focus() + } + + // Extended: Show the current window. + show () { + return this.applicationDelegate.showWindow() + } + + // Extended: Hide the current window. + hide () { + return this.applicationDelegate.hideWindow() + } + + // Extended: Reload the current window. + reload () { + return this.applicationDelegate.reloadWindow() + } + + // Extended: Relaunch the entire application. + restartApplication () { + return this.applicationDelegate.restartApplication() + } + + // Extended: Returns a {Boolean} that is `true` if the current window is maximized. + isMaximized () { + return this.applicationDelegate.isWindowMaximized() + } + + maximize () { + return this.applicationDelegate.maximizeWindow() + } + + // Extended: Returns a {Boolean} that is `true` if the current window is in full screen mode. + isFullScreen () { + return this.applicationDelegate.isWindowFullScreen() + } + + // Extended: Set the full screen state of the current window. + setFullScreen (fullScreen = false) { + return this.applicationDelegate.setWindowFullScreen(fullScreen) + } + + // Extended: Toggle the full screen state of the current window. + toggleFullScreen () { + return this.setFullScreen(!this.isFullScreen()) + } + + // Restore the window to its previous dimensions and show it. + // + // Restores the full screen and maximized state after the window has resized to + // prevent resize glitches. + displayWindow () { + return this.restoreWindowDimensions().then(() => { + const steps = [ + this.restoreWindowBackground(), + this.show(), + this.focus() + ] + if (this.windowDimensions && this.windowDimensions.fullScreen) { + steps.push(this.setFullScreen(true)) + } + if (this.windowDimensions && this.windowDimensions.maximized && process.platform !== 'darwin') { + steps.push(this.maximize()) + } + return Promise.all(steps) + }) + } + + // Get the dimensions of this window. + // + // Returns an {Object} with the following keys: + // * `x` The window's x-position {Number}. + // * `y` The window's y-position {Number}. + // * `width` The window's width {Number}. + // * `height` The window's height {Number}. + getWindowDimensions () { + const browserWindow = this.getCurrentWindow() + const [x, y] = browserWindow.getPosition() + const [width, height] = browserWindow.getSize() + const maximized = browserWindow.isMaximized() + return {x, y, width, height, maximized} + } + + // Set the dimensions of the window. + // + // The window will be centered if either the x or y coordinate is not set + // in the dimensions parameter. If x or y are omitted the window will be + // centered. If height or width are omitted only the position will be changed. + // + // * `dimensions` An {Object} with the following keys: + // * `x` The new x coordinate. + // * `y` The new y coordinate. + // * `width` The new width. + // * `height` The new height. + setWindowDimensions ({x, y, width, height}) { + const steps = [] + if (width != null && height != null) { + steps.push(this.setSize(width, height)) + } + if (x != null && y != null) { + steps.push(this.setPosition(x, y)) + } else { + steps.push(this.center()) + } + return 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 + isValidDimensions ({x, y, width, height} = {}) { + return (width > 0) && (height > 0) && ((x + width) > 0) && ((y + height) > 0) + } + + storeWindowDimensions () { + this.windowDimensions = this.getWindowDimensions() + if (this.isValidDimensions(this.windowDimensions)) { + localStorage.setItem('defaultWindowDimensions', JSON.stringify(this.windowDimensions)) + } + } + + getDefaultWindowDimensions () { + const {windowDimensions} = this.getLoadSettings() + if (windowDimensions) return windowDimensions + + let dimensions + try { + dimensions = JSON.parse(localStorage.getItem('defaultWindowDimensions')) + } catch (error) { + console.warn('Error parsing default window dimensions', error) + localStorage.removeItem('defaultWindowDimensions') + } + + if (dimensions && this.isValidDimensions(dimensions)) { + return dimensions + } else { + const {width, height} = this.applicationDelegate.getPrimaryDisplayWorkAreaSize() + return {x: 0, y: 0, width: Math.min(1024, width), height} + } + } + + restoreWindowDimensions () { + if (!this.windowDimensions || !this.isValidDimensions(this.windowDimensions)) { + this.windowDimensions = this.getDefaultWindowDimensions() + } + return this.setWindowDimensions(this.windowDimensions).then(() => this.windowDimensions) + } + + restoreWindowBackground () { + const backgroundColor = window.localStorage.getItem('atom:window-background-color') + if (backgroundColor) { + this.backgroundStylesheet = document.createElement('style') + this.backgroundStylesheet.type = 'text/css' + this.backgroundStylesheet.innerText = `html, body { background: ${backgroundColor} !important; }` + document.head.appendChild(this.backgroundStylesheet) + } + } + + storeWindowBackground () { + if (this.inSpecMode()) return + + const backgroundColor = this.window.getComputedStyle(this.workspace.getElement())['background-color'] + this.window.localStorage.setItem('atom:window-background-color', backgroundColor) + } + + // Call this method when establishing a real application window. + startEditorWindow () { + this.unloaded = false + + const updateProcessEnvPromise = this.updateProcessEnvAndTriggerHooks() + + const loadStatePromise = this.loadState().then(state => { + this.windowDimensions = state && state.windowDimensions + return this.displayWindow().then(() => { + this.commandInstaller.installAtomCommand(false, (error) => { + if (error) console.warn(error.message) + }) + this.commandInstaller.installApmCommand(false, (error) => { + if (error) console.warn(error.message) + }) + + this.disposables.add(this.applicationDelegate.onDidOpenLocations(this.openLocations.bind(this))) + this.disposables.add(this.applicationDelegate.onApplicationMenuCommand(this.dispatchApplicationMenuCommand.bind(this))) + this.disposables.add(this.applicationDelegate.onContextMenuCommand(this.dispatchContextMenuCommand.bind(this))) + this.disposables.add(this.applicationDelegate.onURIMessage(this.dispatchURIMessage.bind(this))) + this.disposables.add(this.applicationDelegate.onDidRequestUnload(() => { + return this.saveState({isUnloading: true}) + .catch(console.error) + .then(() => { + if (this.workspace) { + return this.workspace.confirmClose({ + windowCloseRequested: true, + projectHasPaths: this.project.getPaths().length > 0 + }) + } + }).then(closing => { + if (closing) { + return this.packages.deactivatePackages().then(() => closing) + } else { + return closing + } + }) + })) + + this.listenForUpdates() + + this.registerDefaultTargetForKeymaps() + + this.packages.loadPackages() + + const startTime = Date.now() + return this.deserialize(state).then(() => { + this.deserializeTimings.atom = Date.now() - startTime + + if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom') { + this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) + this.document.body.classList.add('custom-title-bar') + } + if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom-inset') { + this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) + this.document.body.classList.add('custom-inset-title-bar') + } + if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'hidden') { + this.document.body.classList.add('hidden-title-bar') + } + + this.document.body.appendChild(this.workspace.getElement()) + if (this.backgroundStylesheet) this.backgroundStylesheet.remove() + + this.watchProjectPaths() + + this.packages.activate() + this.keymaps.loadUserKeymap() + if (!this.getLoadSettings().safeMode) this.requireUserInitScript() + + this.menu.update() + + return this.openInitialEmptyEditorIfNecessary() + }) + }) + }) + + const loadHistoryPromise = this.history.loadState().then(() => { + this.reopenProjectMenuManager = new ReopenProjectMenuManager({ + menu: this.menu, + commands: this.commands, + history: this.history, + config: this.config, + open: paths => this.open({pathsToOpen: paths}) + }) + return this.reopenProjectMenuManager.update() + }) + + return Promise.all([loadStatePromise, loadHistoryPromise, updateProcessEnvPromise]) + } + + serialize (options) { + return { + version: this.constructor.version, + project: this.project.serialize(options), + workspace: this.workspace.serialize(), + packageStates: this.packages.serialize(), + grammars: {grammarOverridesByPath: this.grammars.grammarOverridesByPath}, + fullScreen: this.isFullScreen(), + windowDimensions: this.windowDimensions, + textEditors: this.textEditors.serialize() + } + } + + unloadEditorWindow () { + if (!this.project) return + + this.storeWindowBackground() + this.saveBlobStoreSync() + this.unloaded = true + } + + saveBlobStoreSync () { + if (this.enablePersistence) { + this.blobStore.save() + } + } + + openInitialEmptyEditorIfNecessary () { + if (!this.config.get('core.openEmptyEditorOnStart')) return + const {initialPaths} = this.getLoadSettings() + if (initialPaths && initialPaths.length === 0 && this.workspace.getPaneItems().length === 0) { + return this.workspace.open(null) + } + } + + installUncaughtErrorHandler () { + this.previousWindowErrorHandler = this.window.onerror + this.window.onerror = (...args) => { + this.lastUncaughtError = args + let [message, url, line, column, originalError] = this.lastUncaughtError + + let source + ;({line, column, source} = mapSourcePosition({source: url, line, column})) + if (url === '') url = source + + const eventObject = {message, url, line, column, originalError} + + let openDevTools = true + eventObject.preventDefault = () => { openDevTools = false } + + this.emitter.emit('will-throw-error', eventObject) + + if (openDevTools) { + this.openDevTools().then(() => this.executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")')) + } + + this.emitter.emit('did-throw-error', {message, url, line, column, originalError}) + } + } + + uninstallUncaughtErrorHandler () { + this.window.onerror = this.previousWindowErrorHandler + } + + installWindowEventHandler () { + this.windowEventHandler = new WindowEventHandler({atomEnvironment: this, applicationDelegate: this.applicationDelegate}) + this.windowEventHandler.initialize(this.window, this.document) + } + + uninstallWindowEventHandler () { + if (this.windowEventHandler) { + this.windowEventHandler.unsubscribe() + } + this.windowEventHandler = null + } + + didChangeStyles (styleElement) { + TextEditor.didUpdateStyles() + if (styleElement.textContent.indexOf('scrollbar') >= 0) { + TextEditor.didUpdateScrollbarStyles() + } + } + + updateProcessEnvAndTriggerHooks () { + return this.updateProcessEnv(this.getLoadSettings().env).then(() => { + this.shellEnvironmentLoaded = true + this.emitter.emit('loaded-shell-environment') + this.packages.triggerActivationHook('core:loaded-shell-environment') + }) + } + + /* + Section: Messaging the User + */ + + // Essential: Visually and audibly trigger a beep. + beep () { + if (this.config.get('core.audioBeep')) this.applicationDelegate.playBeepSound() + this.emitter.emit('did-beep') + } + + // Essential: A flexible way to open a dialog akin to an alert dialog. + // + // If the dialog is closed (via `Esc` key or `X` in the top corner) without selecting a button + // the first button will be clicked unless a "Cancel" or "No" button is provided. + // + // ## Examples + // + // ```coffee + // atom.confirm + // message: 'How you feeling?' + // detailedMessage: 'Be honest.' + // buttons: + // Good: -> window.alert('good to hear') + // Bad: -> window.alert('bummer') + // ``` + // + // * `options` An {Object} with the following keys: + // * `message` The {String} message to display. + // * `detailedMessage` (optional) The {String} detailed message to display. + // * `buttons` (optional) Either an array of strings or an object where keys are + // button names and the values are callbacks to invoke when clicked. + // + // Returns the chosen button index {Number} if the buttons option is an array or the return value of the callback if the buttons option is an object. + confirm (params = {}) { + return this.applicationDelegate.confirm(params) + } + + /* + Section: Managing the Dev Tools + */ + + // Extended: Open the dev tools for the current window. + // + // Returns a {Promise} that resolves when the DevTools have been opened. + openDevTools () { + return this.applicationDelegate.openWindowDevTools() + } + + // Extended: Toggle the visibility of the dev tools for the current window. + // + // Returns a {Promise} that resolves when the DevTools have been opened or + // closed. + toggleDevTools () { + return this.applicationDelegate.toggleWindowDevTools() + } + + // Extended: Execute code in dev tools. + executeJavaScriptInDevTools (code) { + return this.applicationDelegate.executeJavaScriptInWindowDevTools(code) + } + + /* + Section: Private + */ + + assert (condition, message, callbackOrMetadata) { + if (condition) return true + + const error = new Error(`Assertion failed: ${message}`) + Error.captureStackTrace(error, this.assert) + + if (callbackOrMetadata) { + if (typeof callbackOrMetadata === 'function') { + callbackOrMetadata(error) + } else { + error.metadata = callbackOrMetadata + } + } + + this.emitter.emit('did-fail-assertion', error) + if (!this.isReleasedVersion()) throw error + + return false + } + + loadThemes () { + return this.themes.load() + } + + // Notify the browser project of the window's current project path + watchProjectPaths () { + this.disposables.add(this.project.onDidChangePaths(() => { + this.applicationDelegate.setRepresentedDirectoryPaths(this.project.getPaths()) + })) + } + + setDocumentEdited (edited) { + if (typeof this.applicationDelegate.setWindowDocumentEdited === 'function') { + this.applicationDelegate.setWindowDocumentEdited(edited) + } + } + + setRepresentedFilename (filename) { + if (typeof this.applicationDelegate.setWindowRepresentedFilename === 'function') { + this.applicationDelegate.setWindowRepresentedFilename(filename) + } + } + + addProjectFolder () { + this.pickFolder((selectedPaths = []) => { + this.addToProject(selectedPaths) + }) + } + + addToProject (projectPaths) { + this.loadState(this.getStateKey(projectPaths)).then(state => { + if (state && (this.project.getPaths().length === 0)) { + this.attemptRestoreProjectStateForPaths(state, projectPaths) + } else { + projectPaths.map((folder) => this.project.addPath(folder)) + } + }) + } + + attemptRestoreProjectStateForPaths (state, projectPaths, filesToOpen = []) { + const center = this.workspace.getCenter() + const windowIsUnused = () => { + for (let container of this.workspace.getPaneContainers()) { + for (let item of container.getPaneItems()) { + if (item instanceof TextEditor) { + if (item.getPath() || item.isModified()) return false + } else { + if (container === center) return false + } + } + } + return true + } + + if (windowIsUnused()) { + this.restoreStateIntoThisEnvironment(state) + return Promise.all(filesToOpen.map(file => this.workspace.open(file))) + } else { + const nouns = projectPaths.length === 1 ? 'folder' : 'folders' + const choice = this.confirm({ + message: 'Previous automatically-saved project state detected', + detailedMessage: `There is previously saved state for the selected ${nouns}. ` + + `Would you like to add the ${nouns} to this window, permanently discarding the saved state, ` + + `or open the ${nouns} in a new window, restoring the saved state?`, + buttons: [ + '&Open in new window and recover state', + '&Add to this window and discard state' + ]}) + if (choice === 0) { + this.open({ + pathsToOpen: projectPaths.concat(filesToOpen), + newWindow: true, + devMode: this.inDevMode(), + safeMode: this.inSafeMode() + }) + return Promise.resolve(null) + } else if (choice === 1) { + for (let selectedPath of projectPaths) { + this.project.addPath(selectedPath) + } + return Promise.all(filesToOpen.map(file => this.workspace.open(file))) + } + } + } + + restoreStateIntoThisEnvironment (state) { + state.fullScreen = this.isFullScreen() + for (let pane of this.workspace.getPanes()) { + pane.destroy() + } + return this.deserialize(state) + } + + showSaveDialog (callback) { + callback(this.showSaveDialogSync()) + } + + showSaveDialogSync (options = {}) { + this.applicationDelegate.showSaveDialog(options) + } + + saveState (options, storageKey) { + return new Promise((resolve, reject) => { + if (this.enablePersistence && this.project) { + const state = this.serialize(options) + if (!storageKey) storageKey = this.getStateKey(this.project && this.project.getPaths()) + const savePromise = storageKey + ? this.stateStore.save(storageKey, state) + : this.applicationDelegate.setTemporaryWindowState(state) + return savePromise.catch(reject).then(resolve) + } else { + return resolve() + } + }) + } + + loadState (stateKey) { + if (this.enablePersistence) { + if (!stateKey) stateKey = this.getStateKey(this.getLoadSettings().initialPaths) + if (stateKey) { + return this.stateStore.load(stateKey) + } else { + return this.applicationDelegate.getTemporaryWindowState() + } + } else { + return Promise.resolve(null) + } + } + + deserialize (state) { + if (!state) return Promise.resolve() + + const grammarOverridesByPath = state.grammars && state.grammars.grammarOverridesByPath + if (grammarOverridesByPath) { + this.grammars.grammarOverridesByPath = grammarOverridesByPath + } + + this.setFullScreen(state.fullScreen) + + const missingProjectPaths = [] + + this.packages.packageStates = state.packageStates || {} + + let projectPromise + let startTime = Date.now() + if (state.project) { + projectPromise = this.project.deserialize(state.project, this.deserializers) + .catch(err => { + if (err.missingProjectPaths) { + missingProjectPaths.push(...err.missingProjectPaths) + } else { + this.notifications.addError('Unable to deserialize project', { + description: err.message, + stack: err.stack + }) + } + }) + } else { + projectPromise = Promise.resolve() + } + + return projectPromise.then(() => { + this.deserializeTimings.project = Date.now() - startTime + + if (state.textEditors) this.textEditors.deserialize(state.textEditors) + + startTime = Date.now() + if (state.workspace) this.workspace.deserialize(state.workspace, this.deserializers) + this.deserializeTimings.workspace = Date.now() - startTime + + if (missingProjectPaths.length > 0) { + const count = missingProjectPaths.length === 1 ? '' : missingProjectPaths.length + ' ' + const noun = missingProjectPaths.length === 1 ? 'directory' : 'directories' + const toBe = missingProjectPaths.length === 1 ? 'is' : 'are' + const escaped = missingProjectPaths.map(projectPath => `\`${projectPath}\``) + let group + switch (escaped.length) { + case 1: + group = escaped[0] + break + case 2: + group = `${escaped[0]} and ${escaped[1]}` + break + default: + group = escaped.slice(0, -1).join(', ') + `, and ${escaped[escaped.length - 1]}` + } + + this.notifications.addError(`Unable to open ${count}project ${noun}`, { + description: `Project ${noun} ${group} ${toBe} no longer on disk.` + }) + } + }) + } + + getStateKey (paths) { + if (paths && paths.length > 0) { + const sha1 = crypto.createHash('sha1').update(paths.slice().sort().join('\n')).digest('hex') + return `editor-${sha1}` + } else { + return null + } + } + + getStorageFolder () { + if (!this.storageFolder) this.storageFolder = new StorageFolder(this.getConfigDirPath()) + return this.storageFolder + } + + getConfigDirPath () { + if (!this.configDirPath) this.configDirPath = process.env.ATOM_HOME + return this.configDirPath + } + + getUserInitScriptPath () { + const initScriptPath = fs.resolve(this.getConfigDirPath(), 'init', ['js', 'coffee']) + return initScriptPath || path.join(this.getConfigDirPath(), 'init.coffee') + } + + requireUserInitScript () { + const userInitScriptPath = this.getUserInitScriptPath() + if (userInitScriptPath) { + try { + if (fs.isFileSync(userInitScriptPath)) require(userInitScriptPath) + } catch (error) { + this.notifications.addError(`Failed to load \`${userInitScriptPath}\``, { + detail: error.message, + dismissable: true + }) + } + } + } + + // TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead + onUpdateAvailable (callback) { + return this.emitter.on('update-available', callback) + } + + updateAvailable (details) { + return this.emitter.emit('update-available', details) + } + + listenForUpdates () { + // listen for updates available locally (that have been successfully downloaded) + this.disposables.add(this.autoUpdater.onDidCompleteDownloadingUpdate(this.updateAvailable.bind(this))) + } + + setBodyPlatformClass () { + this.document.body.classList.add(`platform-${process.platform}`) + } + + setAutoHideMenuBar (autoHide) { + this.applicationDelegate.setAutoHideWindowMenuBar(autoHide) + this.applicationDelegate.setWindowMenuBarVisibility(!autoHide) + } + + dispatchApplicationMenuCommand (command, arg) { + let {activeElement} = this.document + // Use the workspace element if body has focus + if (activeElement === this.document.body) { + activeElement = this.workspace.getElement() + } + this.commands.dispatch(activeElement, command, arg) + } + + dispatchContextMenuCommand (command, ...args) { + this.commands.dispatch(this.contextMenu.activeElement, command, args) + } + + dispatchURIMessage (uri) { + if (this.packages.hasLoadedInitialPackages()) { + this.uriHandlerRegistry.handleURI(uri) + } else { + let subscription = this.packages.onDidLoadInitialPackages(() => { + subscription.dispose() + this.uriHandlerRegistry.handleURI(uri) + }) + } + } + + openLocations (locations) { + const needsProjectPaths = this.project && this.project.getPaths().length === 0 + const foldersToAddToProject = [] + const fileLocationsToOpen = [] + + function pushFolderToOpen (folder) { + if (!foldersToAddToProject.includes(folder)) { + foldersToAddToProject.push(folder) + } + } + + for (var {pathToOpen, initialLine, initialColumn, forceAddToWindow} of locations) { + if (pathToOpen && (needsProjectPaths || forceAddToWindow)) { + if (fs.existsSync(pathToOpen)) { + pushFolderToOpen(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + } else if (fs.existsSync(path.dirname(pathToOpen))) { + pushFolderToOpen(this.project.getDirectoryForProjectPath(path.dirname(pathToOpen)).getPath()) + } else { + pushFolderToOpen(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + } + } + + if (!fs.isDirectorySync(pathToOpen)) { + fileLocationsToOpen.push({pathToOpen, initialLine, initialColumn}) + } + } + + let promise = Promise.resolve(null) + if (foldersToAddToProject.length > 0) { + promise = this.loadState(this.getStateKey(foldersToAddToProject)).then(state => { + if (state && needsProjectPaths) { // only load state if this is the first path added to the project + const files = (fileLocationsToOpen.map((location) => location.pathToOpen)) + return this.attemptRestoreProjectStateForPaths(state, foldersToAddToProject, files) + } else { + const promises = [] + for (let folder of foldersToAddToProject) { + this.project.addPath(folder) + } + for ({pathToOpen, initialLine, initialColumn} of fileLocationsToOpen) { + promises.push(this.workspace && this.workspace.open(pathToOpen, {initialLine, initialColumn})) + } + return Promise.all(promises) + } + }) + } else { + const promises = [] + for ({pathToOpen, initialLine, initialColumn} of fileLocationsToOpen) { + promises.push(this.workspace && this.workspace.open(pathToOpen, {initialLine, initialColumn})) + } + promise = Promise.all(promises) + } + + return promise.then(() => ipcRenderer.send('window-command', 'window:locations-opened')) + } + + resolveProxy (url) { + return new Promise((resolve, reject) => { + const requestId = this.nextProxyRequestId++ + const disposable = this.applicationDelegate.onDidResolveProxy((id, proxy) => { + if (id === requestId) { + disposable.dispose() + resolve(proxy) + } + }) + + return this.applicationDelegate.resolveProxy(requestId, url) + }) + } +} + +AtomEnvironment.version = 1 +AtomEnvironment.prototype.saveStateDebounceInterval = 1000 +module.exports = AtomEnvironment + +// Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner. +Promise.prototype.done = function (callback) { + deprecate('Atom now uses ES6 Promises instead of Q. Call promise.then instead of promise.done') + return this.then(callback) +} From 188142bac30e7dc34414b75922fbdffc316b64f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Nov 2017 16:58:30 -0800 Subject: [PATCH 20/43] Suppress lint warning for Promise.prototype monkey patch --- src/atom-environment.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/atom-environment.js b/src/atom-environment.js index 1c2f1ebcf..c23d804c0 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -1344,8 +1344,12 @@ AtomEnvironment.version = 1 AtomEnvironment.prototype.saveStateDebounceInterval = 1000 module.exports = AtomEnvironment +/* eslint-disable */ + // Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner. Promise.prototype.done = function (callback) { deprecate('Atom now uses ES6 Promises instead of Q. Call promise.then instead of promise.done') return this.then(callback) } + +/* eslint-enable */ From fed595b49f07aaa259370f95e2080b3a7dfacd73 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Nov 2017 17:31:45 -0800 Subject: [PATCH 21/43] Use async/await in AtomEnvironment --- src/atom-environment.js | 368 +++++++++++++++++++--------------------- 1 file changed, 176 insertions(+), 192 deletions(-) diff --git a/src/atom-environment.js b/src/atom-environment.js index c23d804c0..663bb6c00 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -290,7 +290,7 @@ class AtomEnvironment { if (this.config.get('core.autoHideMenuBar')) this.setAutoHideMenuBar(true) } - reset () { + async reset () { this.deserializers.clear() this.registerDefaultDeserializers() @@ -313,15 +313,14 @@ class AtomEnvironment { this.contextMenu.clear() - return this.packages.reset().then(() => { - this.workspace.reset(this.packages) - this.registerDefaultOpeners() - this.project.reset(this.packages) - this.workspace.subscribeToEvents() - this.grammars.clear() - this.textEditors.clear() - this.views.clear() - }) + await this.packages.reset() + this.workspace.reset(this.packages) + this.registerDefaultOpeners() + this.project.reset(this.packages) + this.workspace.subscribeToEvents() + this.grammars.clear() + this.textEditors.clear() + this.views.clear() } destroy () { @@ -611,21 +610,20 @@ class AtomEnvironment { // // Restores the full screen and maximized state after the window has resized to // prevent resize glitches. - displayWindow () { - return this.restoreWindowDimensions().then(() => { - const steps = [ - this.restoreWindowBackground(), - this.show(), - this.focus() - ] - if (this.windowDimensions && this.windowDimensions.fullScreen) { - steps.push(this.setFullScreen(true)) - } - if (this.windowDimensions && this.windowDimensions.maximized && process.platform !== 'darwin') { - steps.push(this.maximize()) - } - return Promise.all(steps) - }) + async displayWindow () { + await this.restoreWindowDimensions() + const steps = [ + this.restoreWindowBackground(), + this.show(), + this.focus() + ] + if (this.windowDimensions && this.windowDimensions.fullScreen) { + steps.push(this.setFullScreen(true)) + } + if (this.windowDimensions && this.windowDimensions.maximized && process.platform !== 'darwin') { + steps.push(this.maximize()) + } + await Promise.all(steps) } // Get the dimensions of this window. @@ -700,11 +698,12 @@ class AtomEnvironment { } } - restoreWindowDimensions () { + async restoreWindowDimensions () { if (!this.windowDimensions || !this.isValidDimensions(this.windowDimensions)) { this.windowDimensions = this.getDefaultWindowDimensions() } - return this.setWindowDimensions(this.windowDimensions).then(() => this.windowDimensions) + await this.setWindowDimensions(this.windowDimensions) + return this.windowDimensions } restoreWindowBackground () { @@ -730,75 +729,70 @@ class AtomEnvironment { const updateProcessEnvPromise = this.updateProcessEnvAndTriggerHooks() - const loadStatePromise = this.loadState().then(state => { + const loadStatePromise = this.loadState().then(async state => { this.windowDimensions = state && state.windowDimensions - return this.displayWindow().then(() => { - this.commandInstaller.installAtomCommand(false, (error) => { - if (error) console.warn(error.message) - }) - this.commandInstaller.installApmCommand(false, (error) => { - if (error) console.warn(error.message) - }) - - this.disposables.add(this.applicationDelegate.onDidOpenLocations(this.openLocations.bind(this))) - this.disposables.add(this.applicationDelegate.onApplicationMenuCommand(this.dispatchApplicationMenuCommand.bind(this))) - this.disposables.add(this.applicationDelegate.onContextMenuCommand(this.dispatchContextMenuCommand.bind(this))) - this.disposables.add(this.applicationDelegate.onURIMessage(this.dispatchURIMessage.bind(this))) - this.disposables.add(this.applicationDelegate.onDidRequestUnload(() => { - return this.saveState({isUnloading: true}) - .catch(console.error) - .then(() => { - if (this.workspace) { - return this.workspace.confirmClose({ - windowCloseRequested: true, - projectHasPaths: this.project.getPaths().length > 0 - }) - } - }).then(closing => { - if (closing) { - return this.packages.deactivatePackages().then(() => closing) - } else { - return closing - } - }) - })) - - this.listenForUpdates() - - this.registerDefaultTargetForKeymaps() - - this.packages.loadPackages() - - const startTime = Date.now() - return this.deserialize(state).then(() => { - this.deserializeTimings.atom = Date.now() - startTime - - if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom') { - this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) - this.document.body.classList.add('custom-title-bar') - } - if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom-inset') { - this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) - this.document.body.classList.add('custom-inset-title-bar') - } - if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'hidden') { - this.document.body.classList.add('hidden-title-bar') - } - - this.document.body.appendChild(this.workspace.getElement()) - if (this.backgroundStylesheet) this.backgroundStylesheet.remove() - - this.watchProjectPaths() - - this.packages.activate() - this.keymaps.loadUserKeymap() - if (!this.getLoadSettings().safeMode) this.requireUserInitScript() - - this.menu.update() - - return this.openInitialEmptyEditorIfNecessary() - }) + await this.displayWindow() + this.commandInstaller.installAtomCommand(false, (error) => { + if (error) console.warn(error.message) }) + this.commandInstaller.installApmCommand(false, (error) => { + if (error) console.warn(error.message) + }) + + this.disposables.add(this.applicationDelegate.onDidOpenLocations(this.openLocations.bind(this))) + this.disposables.add(this.applicationDelegate.onApplicationMenuCommand(this.dispatchApplicationMenuCommand.bind(this))) + this.disposables.add(this.applicationDelegate.onContextMenuCommand(this.dispatchContextMenuCommand.bind(this))) + this.disposables.add(this.applicationDelegate.onURIMessage(this.dispatchURIMessage.bind(this))) + this.disposables.add(this.applicationDelegate.onDidRequestUnload(async () => { + try { + await this.saveState({isUnloading: true}) + } catch (error) { + console.error(error) + } + + const closing = !this.workspace || await this.workspace.confirmClose({ + windowCloseRequested: true, + projectHasPaths: this.project.getPaths().length > 0 + }) + + if (closing) await this.packages.deactivatePackages() + return closing + })) + + this.listenForUpdates() + + this.registerDefaultTargetForKeymaps() + + this.packages.loadPackages() + + const startTime = Date.now() + await this.deserialize(state) + this.deserializeTimings.atom = Date.now() - startTime + + if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom') { + this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) + this.document.body.classList.add('custom-title-bar') + } + if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom-inset') { + this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) + this.document.body.classList.add('custom-inset-title-bar') + } + if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'hidden') { + this.document.body.classList.add('hidden-title-bar') + } + + this.document.body.appendChild(this.workspace.getElement()) + if (this.backgroundStylesheet) this.backgroundStylesheet.remove() + + this.watchProjectPaths() + + this.packages.activate() + this.keymaps.loadUserKeymap() + if (!this.getLoadSettings().safeMode) this.requireUserInitScript() + + this.menu.update() + + await this.openInitialEmptyEditorIfNecessary() }) const loadHistoryPromise = this.history.loadState().then(() => { @@ -809,7 +803,7 @@ class AtomEnvironment { config: this.config, open: paths => this.open({pathsToOpen: paths}) }) - return this.reopenProjectMenuManager.update() + this.reopenProjectMenuManager.update() }) return Promise.all([loadStatePromise, loadHistoryPromise, updateProcessEnvPromise]) @@ -852,13 +846,11 @@ class AtomEnvironment { installUncaughtErrorHandler () { this.previousWindowErrorHandler = this.window.onerror - this.window.onerror = (...args) => { - this.lastUncaughtError = args - let [message, url, line, column, originalError] = this.lastUncaughtError - - let source - ;({line, column, source} = mapSourcePosition({source: url, line, column})) - if (url === '') url = source + this.window.onerror = (message, url, line, column, originalError) => { + const mapping = mapSourcePosition({source: url, line, column}) + line = mapping.line + column = mapping.column + if (url === '') url = mapping.source const eventObject = {message, url, line, column, originalError} @@ -868,7 +860,9 @@ class AtomEnvironment { this.emitter.emit('will-throw-error', eventObject) if (openDevTools) { - this.openDevTools().then(() => this.executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")')) + this.openDevTools().then(() => + this.executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")') + ) } this.emitter.emit('did-throw-error', {message, url, line, column, originalError}) @@ -898,12 +892,11 @@ class AtomEnvironment { } } - updateProcessEnvAndTriggerHooks () { - return this.updateProcessEnv(this.getLoadSettings().env).then(() => { - this.shellEnvironmentLoaded = true - this.emitter.emit('loaded-shell-environment') - this.packages.triggerActivationHook('core:loaded-shell-environment') - }) + async updateProcessEnvAndTriggerHooks () { + await this.updateProcessEnv(this.getLoadSettings().env) + this.shellEnvironmentLoaded = true + this.emitter.emit('loaded-shell-environment') + this.packages.triggerActivationHook('core:loaded-shell-environment') } /* @@ -1020,14 +1013,13 @@ class AtomEnvironment { }) } - addToProject (projectPaths) { - this.loadState(this.getStateKey(projectPaths)).then(state => { - if (state && (this.project.getPaths().length === 0)) { - this.attemptRestoreProjectStateForPaths(state, projectPaths) - } else { - projectPaths.map((folder) => this.project.addPath(folder)) - } - }) + async addToProject (projectPaths) { + const state = await this.loadState(this.getStateKey(projectPaths)) + if (state && (this.project.getPaths().length === 0)) { + this.attemptRestoreProjectStateForPaths(state, projectPaths) + } else { + projectPaths.map((folder) => this.project.addPath(folder)) + } } attemptRestoreProjectStateForPaths (state, projectPaths, filesToOpen = []) { @@ -1092,19 +1084,16 @@ class AtomEnvironment { this.applicationDelegate.showSaveDialog(options) } - saveState (options, storageKey) { - return new Promise((resolve, reject) => { - if (this.enablePersistence && this.project) { - const state = this.serialize(options) - if (!storageKey) storageKey = this.getStateKey(this.project && this.project.getPaths()) - const savePromise = storageKey - ? this.stateStore.save(storageKey, state) - : this.applicationDelegate.setTemporaryWindowState(state) - return savePromise.catch(reject).then(resolve) + async saveState (options, storageKey) { + if (this.enablePersistence && this.project) { + const state = this.serialize(options) + if (!storageKey) storageKey = this.getStateKey(this.project && this.project.getPaths()) + if (storageKey) { + await this.stateStore.save(storageKey, state) } else { - return resolve() + await this.applicationDelegate.setTemporaryWindowState(state) } - }) + } } loadState (stateKey) { @@ -1120,7 +1109,7 @@ class AtomEnvironment { } } - deserialize (state) { + async deserialize (state) { if (!state) return Promise.resolve() const grammarOverridesByPath = state.grammars && state.grammars.grammarOverridesByPath @@ -1134,55 +1123,51 @@ class AtomEnvironment { this.packages.packageStates = state.packageStates || {} - let projectPromise let startTime = Date.now() if (state.project) { - projectPromise = this.project.deserialize(state.project, this.deserializers) - .catch(err => { - if (err.missingProjectPaths) { - missingProjectPaths.push(...err.missingProjectPaths) - } else { - this.notifications.addError('Unable to deserialize project', { - description: err.message, - stack: err.stack - }) - } - }) - } else { - projectPromise = Promise.resolve() + try { + await this.project.deserialize(state.project, this.deserializers) + } catch (error) { + if (error.missingProjectPaths) { + missingProjectPaths.push(...error.missingProjectPaths) + } else { + this.notifications.addError('Unable to deserialize project', { + description: error.message, + stack: error.stack + }) + } + } } - return projectPromise.then(() => { - this.deserializeTimings.project = Date.now() - startTime + this.deserializeTimings.project = Date.now() - startTime - if (state.textEditors) this.textEditors.deserialize(state.textEditors) + if (state.textEditors) this.textEditors.deserialize(state.textEditors) - startTime = Date.now() - if (state.workspace) this.workspace.deserialize(state.workspace, this.deserializers) - this.deserializeTimings.workspace = Date.now() - startTime + startTime = Date.now() + if (state.workspace) this.workspace.deserialize(state.workspace, this.deserializers) + this.deserializeTimings.workspace = Date.now() - startTime - if (missingProjectPaths.length > 0) { - const count = missingProjectPaths.length === 1 ? '' : missingProjectPaths.length + ' ' - const noun = missingProjectPaths.length === 1 ? 'directory' : 'directories' - const toBe = missingProjectPaths.length === 1 ? 'is' : 'are' - const escaped = missingProjectPaths.map(projectPath => `\`${projectPath}\``) - let group - switch (escaped.length) { - case 1: - group = escaped[0] - break - case 2: - group = `${escaped[0]} and ${escaped[1]}` - break - default: - group = escaped.slice(0, -1).join(', ') + `, and ${escaped[escaped.length - 1]}` - } - - this.notifications.addError(`Unable to open ${count}project ${noun}`, { - description: `Project ${noun} ${group} ${toBe} no longer on disk.` - }) + if (missingProjectPaths.length > 0) { + const count = missingProjectPaths.length === 1 ? '' : missingProjectPaths.length + ' ' + const noun = missingProjectPaths.length === 1 ? 'directory' : 'directories' + const toBe = missingProjectPaths.length === 1 ? 'is' : 'are' + const escaped = missingProjectPaths.map(projectPath => `\`${projectPath}\``) + let group + switch (escaped.length) { + case 1: + group = escaped[0] + break + case 2: + group = `${escaped[0]} and ${escaped[1]}` + break + default: + group = escaped.slice(0, -1).join(', ') + `, and ${escaped[escaped.length - 1]}` } - }) + + this.notifications.addError(`Unable to open ${count}project ${noun}`, { + description: `Project ${noun} ${group} ${toBe} no longer on disk.` + }) + } } getStateKey (paths) { @@ -1270,7 +1255,7 @@ class AtomEnvironment { } } - openLocations (locations) { + async openLocations (locations) { const needsProjectPaths = this.project && this.project.getPaths().length === 0 const foldersToAddToProject = [] const fileLocationsToOpen = [] @@ -1297,32 +1282,31 @@ class AtomEnvironment { } } - let promise = Promise.resolve(null) + let restoredState = false if (foldersToAddToProject.length > 0) { - promise = this.loadState(this.getStateKey(foldersToAddToProject)).then(state => { - if (state && needsProjectPaths) { // only load state if this is the first path added to the project - const files = (fileLocationsToOpen.map((location) => location.pathToOpen)) - return this.attemptRestoreProjectStateForPaths(state, foldersToAddToProject, files) - } else { - const promises = [] - for (let folder of foldersToAddToProject) { - this.project.addPath(folder) - } - for ({pathToOpen, initialLine, initialColumn} of fileLocationsToOpen) { - promises.push(this.workspace && this.workspace.open(pathToOpen, {initialLine, initialColumn})) - } - return Promise.all(promises) + const state = await this.loadState(this.getStateKey(foldersToAddToProject)) + + // only restore state if this is the first path added to the project + if (state && needsProjectPaths) { + const files = fileLocationsToOpen.map((location) => location.pathToOpen) + await this.attemptRestoreProjectStateForPaths(state, foldersToAddToProject, files) + restoredState = true + } else { + for (let folder of foldersToAddToProject) { + this.project.addPath(folder) } - }) - } else { - const promises = [] - for ({pathToOpen, initialLine, initialColumn} of fileLocationsToOpen) { - promises.push(this.workspace && this.workspace.open(pathToOpen, {initialLine, initialColumn})) } - promise = Promise.all(promises) } - return promise.then(() => ipcRenderer.send('window-command', 'window:locations-opened')) + if (!restoredState) { + const fileOpenPromises = [] + for ({pathToOpen, initialLine, initialColumn} of fileLocationsToOpen) { + fileOpenPromises.push(this.workspace && this.workspace.open(pathToOpen, {initialLine, initialColumn})) + } + await Promise.all(fileOpenPromises) + } + + ipcRenderer.send('window-command', 'window:locations-opened') } resolveProxy (url) { From 0673866a399e944a859b7db4f20a5fa81f2487e2 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Thu, 9 Nov 2017 10:32:29 -0800 Subject: [PATCH 22/43] Point Atom Core and build documentation to new Flight Manual section --- CONTRIBUTING.md | 6 +- README.md | 6 +- docs/build-instructions/linux.md | 130 ----------------------------- docs/build-instructions/macOS.md | 29 ------- docs/build-instructions/windows.md | 90 -------------------- 5 files changed, 8 insertions(+), 253 deletions(-) delete mode 100644 docs/build-instructions/linux.md delete mode 100644 docs/build-instructions/macOS.md delete mode 100644 docs/build-instructions/windows.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77c1889ac..0f0d2d5a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -199,7 +199,10 @@ If you want to read about using Atom or developing packages in Atom, the [Atom F #### Local development -All packages can be developed locally. For instructions on how to do this, see [Contributing to Official Atom Packages][contributing-to-official-atom-packages] in the [Atom Flight Manual](http://flight-manual.atom.io). +Atom Core and all packages can be developed locally. For instructions on how to do this, see the following sections in the [Atom Flight Manual](http://flight-manual.atom.io): + +* [Hacking on Atom Core][hacking-on-atom-core] +* [Contributing to Official Atom Packages][contributing-to-official-atom-packages] ### Pull Requests @@ -492,3 +495,4 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and [beginner]:https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc [help-wanted]:https://github.com/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc+-label%3Abeginner [contributing-to-official-atom-packages]:http://flight-manual.atom.io/hacking-atom/sections/contributing-to-official-atom-packages/ +[hacking-on-atom-core]: http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/ diff --git a/README.md b/README.md index ab6cd06a6..c29203ea0 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,10 @@ repeat these steps to upgrade to future releases. ## Building -* [Linux](./docs/build-instructions/linux.md) -* [macOS](./docs/build-instructions/macOS.md) * [FreeBSD](./docs/build-instructions/freebsd.md) -* [Windows](./docs/build-instructions/windows.md) +* [Linux](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-linux) +* [macOS](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac) +* [Windows](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows) ## License diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md deleted file mode 100644 index dee67d726..000000000 --- a/docs/build-instructions/linux.md +++ /dev/null @@ -1,130 +0,0 @@ -# Linux - -Ubuntu LTS 12.04 64-bit is the recommended platform. - -## Requirements - -* OS with 64-bit or 32-bit architecture -* C++11 toolchain -* Git -* Node.js 6.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm)) -* npm 3.10.x or later (run `npm install -g npm`) -* Ensure node-gyp uses python2 (run `npm config set python /usr/bin/python2 -g`, use `sudo` if you didn't install node via nvm) -* Development headers for [libsecret](https://wiki.gnome.org/Projects/Libsecret). - -For more details, scroll down to find how to setup a specific Linux distro. - -## Instructions - -```sh -git clone https://github.com/atom/atom.git -cd atom -script/build -``` - -To also install the newly built application, use `--create-debian-package` or `--create-rpm-package` and then install the generated package via the system package manager. - -### `script/build` Options - -* `--compress-artifacts`: zips the generated application as `out/atom-{arch}.tar.gz`. -* `--create-debian-package`: creates a .deb package as `out/atom-{arch}.deb` -* `--create-rpm-package`: creates a .rpm package as `out/atom-{arch}.rpm` -* `--install[=dir]`: installs the application in `${dir}`; `${dir}` defaults to `/usr/local`. - -### Ubuntu / Debian - -* Install GNOME headers and other basic prerequisites: - - ```sh - sudo apt-get install build-essential git libsecret-1-dev fakeroot rpm libx11-dev libxkbfile-dev - ``` - -* If `script/build` exits with an error, you may need to install a newer C++ compiler with C++11: - - ```sh - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install gcc-5 g++-5 - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5 - sudo update-alternatives --config gcc # choose gcc-5 from the list - ``` - -### Fedora 22+ - -* `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel` - -### Fedora 21 / CentOS / RHEL - -* `sudo yum install -y make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools` - -### Arch - -* `sudo pacman -S --needed gconf base-devel git nodejs npm libsecret python2 libx11 libxkbfile` -* `export PYTHON=/usr/bin/python2` before building Atom. - -### Slackware - -* `sbopkg -k -i node -i atom` - -### openSUSE - -* `sudo zypper install nodejs nodejs-devel make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel` - - -## Troubleshooting - -### TypeError: Unable to watch path - -If you get following error with a big traceback right after Atom starts: - - ``` - TypeError: Unable to watch path - ``` - -you have to increase number of watched files by inotify. For testing if -this is the reason for this error you can issue - - ```sh - sudo sysctl fs.inotify.max_user_watches=32768 - ``` - -and restart Atom. If Atom now works fine, you can make this setting permanent: - - ```sh - echo 32768 | sudo tee -a /proc/sys/fs/inotify/max_user_watches - ``` - -See also [#2082](https://github.com/atom/atom/issues/2082). - -### /usr/bin/env: node: No such file or directory - -If you get this notice when attempting to run any script, you either do not have -Node.js installed, or node isn't identified as Node.js on your machine. If it's -the latter, this might be caused by installing Node.js via the distro package -manager and not nvm, so entering `sudo ln -s /usr/bin/nodejs /usr/bin/node` into -your terminal may fix the issue. On some variants (mostly Debian based distros) -you can use `update-alternatives` too: - -```sh -sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 1 --slave /usr/bin/js js /usr/bin/nodejs -``` - -### AttributeError: 'module' object has no attribute 'script_main' - -If you get following error with a big traceback while building Atom: - - ``` - sys.exit(gyp.script_main()) AttributeError: 'module' object has no attribute 'script_main' gyp ERR! - ``` - -you need to uninstall the system version of gyp. - -On Fedora you would do the following: - -```sh -sudo yum remove gyp -``` - -### Linux build error reports in atom/atom -* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues) - to get a list of reports about build errors on Linux. diff --git a/docs/build-instructions/macOS.md b/docs/build-instructions/macOS.md deleted file mode 100644 index ae3ed9c84..000000000 --- a/docs/build-instructions/macOS.md +++ /dev/null @@ -1,29 +0,0 @@ -# macOS - -## Requirements - - * macOS 10.8 or later - * Node.js 6.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm)) - * npm 3.10.x or later (run `npm install -g npm`) - * Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install) - -## Instructions - -```sh -git clone https://github.com/atom/atom.git -cd atom -script/build -``` - -To also install the newly built application, use `script/build --install`. - -### `script/build` Options - -* `--code-sign`: signs the application with the GitHub certificate specified in `$ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL`. -* `--compress-artifacts`: zips the generated application as `out/atom-mac.zip`. -* `--install[=dir]`: installs the application at `${dir}/Atom.app` for dev and stable versions or at `${dir}/Atom-Beta.app` for beta versions; `${dir}` defaults to `/Applications`. - -## Troubleshooting - -### macOS build error reports in atom/atom -* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Amac&type=Issues) to get a list of reports about build errors on macOS. diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md deleted file mode 100644 index a6c327ec8..000000000 --- a/docs/build-instructions/windows.md +++ /dev/null @@ -1,90 +0,0 @@ -# Windows - -## Requirements - -* Node.js 6.9.4 or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom) -* Python v2.7.x - * The python.exe must be available at `%SystemDrive%\Python27\python.exe`. If it is installed elsewhere create a symbolic link to the directory containing the python.exe using: `mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27` -* 7zip (7z.exe available from the command line) - for creating distribution zip files -* Visual Studio, either: - * [Visual C++ Build Tools 2015](http://landinghub.visualstudio.com/visual-cpp-build-tools) - * [Visual Studio 2013 Update 5](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express Edition or better) - * [Visual Studio 2015](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community Edition or better) - - Also ensure that: - * The default installation folder is chosen so the build tools can find it - * If using Visual Studio make sure Visual C++ support is selected/installed - * If using Visual C++ Build Tools make sure Windows 8 SDK is selected/installed - * A `git` command is in your path - * Set the `GYP_MSVS_VERSION` environment variable to the Visual Studio/Build Tools version (`2013` or `2015`) e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell (or set it in Windows advanced system settings). - -## Instructions - -You can run these commands using Command Prompt, PowerShell, Git Shell, or any other terminal. These instructions will assume the use of Command Prompt. - -``` -cd C:\ -git clone https://github.com/atom/atom.git -cd atom -script\build -``` - -To also install the newly built application, use `script\build --create-windows-installer` and launch the generated installers. - -### `script\build` Options -* `--code-sign`: signs the application with the GitHub certificate specified in `$WIN_P12KEY_URL`. -* `--compress-artifacts`: zips the generated application as `out\atom-windows.zip` (requires [7-Zip](http://www.7-zip.org)). -* `--create-windows-installer`: creates an `.msi`, an `.exe` and two `.nupkg` packages in the `out` directory. -* `--install[=dir]`: installs the application in `${dir}\Atom\app-dev`; `${dir}` defaults to `%LOCALAPPDATA%`. - -### Running tests - -In order to run tests from command line you need `apm`, available after you install Atom or after you build from source. If you installed it, run the following commands (assuming `C:\atom` is the root of your Atom repository): - -```bash -cd C:\atom -apm test -``` - -When building Atom from source, the `apm` command is not added to the system path by default. In this case, you can either add it yourself or explicitly list the complete path in previous commands. The default install location is `%LOCALAPPDATA%\Atom\app-dev\resources\cli\`. - -**NOTE**: Please keep in mind that there are still some tests that don't pass on Windows. - -## Troubleshooting - -### Common Errors -* `node is not recognized` - * If you just installed Node.js, you'll need to restart Command Prompt before the `node` command is available on your path. - -* `msbuild.exe failed with exit code: 1` - * If using **Visual Studio**, ensure you have the **Visual C++** component installed. Go into Add/Remove Programs, select Visual Studio, press Modify, and then check the Visual C++ box. - * If using **Visual C++ Build Tools**, ensure you have the **Windows 8 SDK** component installed. Go into Add/Remove Programs, select Visual C++ Build Tools, press Modify and then check the Windows 8 SDK box. - -* `script\build` stops with no error or warning shortly after displaying the versions of node, npm and Python - * Make sure that the path where you have checked out Atom does not include a space. For example, use `C:\atom` instead of `C:\my stuff\atom`. - * Try moving the repository to `C:\atom`. Most likely, the path is too long. See [issue #2200](https://github.com/atom/atom/issues/2200). - -* `error MSB4025: The project file could not be loaded. Invalid character in the given encoding.` - * This can occur because your home directory (`%USERPROFILE%`) has non-ASCII characters in it. This is a bug in [gyp](https://code.google.com/p/gyp/) - which is used to build native Node.js modules and there is no known workaround. - * https://github.com/TooTallNate/node-gyp/issues/297 - * https://code.google.com/p/gyp/issues/detail?id=393 - -* `'node_modules\.bin\npm' is not recognized as an internal or external command, operable program or batch file.` - * This occurs if the previous build left things in a bad state. Run `script\clean` and then `script\build` again. - -* `script\build` stops at installing runas with `Failed at the runas@x.y.z install script.` - * See the next item. - -* `error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.` - * Try setting the `GYP_MSVS_VERSION` environment variable to **2013** or **2015** depending on what version of Visual Studio/Build Tools is installed and then `script\clean` followed by `script\build` (re-open the Command Prompt if you set the variable using the GUI). - -* `'node-gyp' is not recognized as an internal or external command, operable program or batch file.` - * Try running `npm install -g node-gyp`, and run `script\build` again. - -* Other `node-gyp` errors on first build attempt, even though the right Node.js and Python versions are installed. - * Do try the build command one more time as experience shows it often works on second try in many cases. - -### Windows build error reports in atom/atom -* If all fails, use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues) to get a list of reports about build errors on Windows, and see if yours has already been reported. -* If it hasn't, please open a new issue with your Windows version, architecture (x86 or x64), and a screenshot of your build output, including the Node.js and Python versions. From 396b78f71d82bef0870a5fb0b0cf4bc5016729b8 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Thu, 9 Nov 2017 10:37:51 -0800 Subject: [PATCH 23/43] Add docs back with references to new location for bookmarks --- docs/build-instructions/linux.md | 1 + docs/build-instructions/macOS.md | 1 + docs/build-instructions/windows.md | 1 + 3 files changed, 3 insertions(+) create mode 100644 docs/build-instructions/linux.md create mode 100644 docs/build-instructions/macOS.md create mode 100644 docs/build-instructions/windows.md diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md new file mode 100644 index 000000000..3499f6ac9 --- /dev/null +++ b/docs/build-instructions/linux.md @@ -0,0 +1 @@ +See the [Hacking on Atom Core](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-linux) section in the [Atom Flight Manual](http://flight-manual.atom.io). diff --git a/docs/build-instructions/macOS.md b/docs/build-instructions/macOS.md new file mode 100644 index 000000000..3085d11f3 --- /dev/null +++ b/docs/build-instructions/macOS.md @@ -0,0 +1 @@ +See the [Hacking on Atom Core](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac) section in the [Atom Flight Manual](http://flight-manual.atom.io). diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md new file mode 100644 index 000000000..f75a07530 --- /dev/null +++ b/docs/build-instructions/windows.md @@ -0,0 +1 @@ +See the [Hacking on Atom Core](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows) section in the [Atom Flight Manual](http://flight-manual.atom.io). From e5ebdc08563706f1a731231a1ced9e4c6a5cd093 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Nov 2017 15:05:30 -0700 Subject: [PATCH 24/43] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89651c465..611e6aaf0 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "dev-live-reload": "0.47.1", "encoding-selector": "0.23.7", "exception-reporting": "0.41.5", - "find-and-replace": "0.213.0", + "find-and-replace": "0.214.0", "fuzzy-finder": "1.7.3", "github": "0.8.2", "git-diff": "1.3.6", From 9f2a07448cf566898771862424e98f28e48b2084 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sat, 11 Nov 2017 13:34:58 +0100 Subject: [PATCH 25/43] :arrow_up: tree-view@0.221.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 611e6aaf0..9a5c6c7cc 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "symbols-view": "0.118.1", "tabs": "0.109.1", "timecop": "0.36.2", - "tree-view": "0.221.2", + "tree-view": "0.221.3", "update-package-dependencies": "0.12.0", "welcome": "0.36.5", "whitespace": "0.37.5", From 4321db050c57fad4e453c46048c4efb194a9b9aa Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sat, 11 Nov 2017 15:55:30 +0100 Subject: [PATCH 26/43] :arrow_up: update-package-dependencies@0.13.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a5c6c7cc..95e817aab 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "tabs": "0.109.1", "timecop": "0.36.2", "tree-view": "0.221.3", - "update-package-dependencies": "0.12.0", + "update-package-dependencies": "0.13.0", "welcome": "0.36.5", "whitespace": "0.37.5", "wrap-guide": "0.40.2", From cb82a4201d87a0b4879f28ab1e74c347cd5cccf4 Mon Sep 17 00:00:00 2001 From: Justin Ratner Date: Sat, 11 Nov 2017 17:33:52 -0700 Subject: [PATCH 27/43] :arrow_up: autocomplete-plus@2.37.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95e817aab..3f94ad81e 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "autocomplete-atom-api": "0.10.5", "autocomplete-css": "0.17.4", "autocomplete-html": "0.8.3", - "autocomplete-plus": "2.37.2", + "autocomplete-plus": "2.37.3", "autocomplete-snippets": "1.11.2", "autoflow": "0.29.0", "autosave": "0.24.6", From feb40fa97a168c0d7d6fb901af13230fb23de482 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 12 Nov 2017 22:42:10 +0100 Subject: [PATCH 28/43] :memo: onDidChangeActiveThemes returns a Disposable --- src/theme-manager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/theme-manager.js b/src/theme-manager.js index 6abf0fc74..a23305c92 100644 --- a/src/theme-manager.js +++ b/src/theme-manager.js @@ -50,6 +50,8 @@ class ThemeManager { // updating the list of active themes have completed. // // * `callback` {Function} + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidChangeActiveThemes (callback) { return this.emitter.on('did-change-active-themes', callback) } From 0fa00ea3d25bd631b7689f99572f006073a9eef7 Mon Sep 17 00:00:00 2001 From: Umesh Yadav Date: Mon, 13 Nov 2017 17:47:41 +0530 Subject: [PATCH 29/43] Fixed non ssl links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c29203ea0..dc4062ea9 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ repeat these steps to upgrade to future releases. ## Building * [FreeBSD](./docs/build-instructions/freebsd.md) -* [Linux](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-linux) -* [macOS](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac) -* [Windows](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows) +* [Linux](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-linux) +* [macOS](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac) +* [Windows](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows) ## License From 9a0ad467394a849e1835ff8d8e7629836e8401af Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 10 Nov 2017 16:23:42 -0500 Subject: [PATCH 30/43] Ensure app windows launch in the order we assert they do --- spec/main-process/atom-application.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 01d052b96..7c19efb9c 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -352,11 +352,9 @@ describe('AtomApplication', function () { const atomApplication1 = buildAtomApplication() const app1Window1 = atomApplication1.launch(parseCommandLine([tempDirPath1])) + await emitterEventPromise(app1Window1, 'window:locations-opened') const app1Window2 = atomApplication1.launch(parseCommandLine([tempDirPath2])) - await Promise.all([ - emitterEventPromise(app1Window1, 'window:locations-opened'), - emitterEventPromise(app1Window2, 'window:locations-opened') - ]) + await emitterEventPromise(app1Window2, 'window:locations-opened') await Promise.all([ app1Window1.prepareToUnload(), From 4f6e8ed5f36e9b5a8267763388d34ff17fbb00d5 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 13 Nov 2017 18:57:29 +0100 Subject: [PATCH 31/43] :memo: [ci skip] --- src/path-watcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/path-watcher.js b/src/path-watcher.js index 5a2d10bde..d0ff90dd1 100644 --- a/src/path-watcher.js +++ b/src/path-watcher.js @@ -422,7 +422,7 @@ class PathWatcher { // Extended: Return a {Promise} that will resolve when the underlying native watcher is ready to begin sending events. // When testing filesystem watchers, it's important to await this promise before making filesystem changes that you // intend to assert about because there will be a delay between the instantiation of the watcher and the activation - // of the underlying OS resources that feed it events. + // of the underlying OS resources that feed its events. // // PathWatchers acquired through `watchPath` are already started. // @@ -533,7 +533,7 @@ class PathWatcher { } } - // Extended: Unsubscribe all subscribers from filesystem events. Native resources will be release asynchronously, + // Extended: Unsubscribe all subscribers from filesystem events. Native resources will be released asynchronously, // but this watcher will stop broadcasting events immediately. dispose () { for (const sub of this.changeCallbacks.values()) { From 09964826901fb91fd4f722d1b4932b0c458f5439 Mon Sep 17 00:00:00 2001 From: U8N WXD Date: Mon, 13 Nov 2017 13:13:28 -0800 Subject: [PATCH 32/43] Change HTTP Links to HTTPS in SUPPORT.md Similar to issue #16167 and PR #16173 Change links to `The Atom Flight Manual` and `Atom Slack team` to support SSL/TLS by changing URL to `https://` [ci skip] Signed-off-by: U8N WXD --- SUPPORT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SUPPORT.md b/SUPPORT.md index d908b3fff..a68fa1348 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -2,10 +2,10 @@ If you're looking for support for Atom there are a lot of options, check out: -* User Documentation — [The Atom Flight Manual](http://flight-manual.atom.io) +* User Documentation — [The Atom Flight Manual](https://flight-manual.atom.io) * Developer Documentation — [Atom API Documentation](https://atom.io/docs/api/latest) * FAQ — [The Atom FAQ on Discuss](https://discuss.atom.io/c/faq) * Message Board — [Discuss, the official Atom and Electron message board](https://discuss.atom.io) -* Chat — [Join the Atom Slack team](http://atom-slack.herokuapp.com/) +* Chat — [Join the Atom Slack team](https://atom-slack.herokuapp.com/) On Discuss and in the Atom Slack team, there are a bunch of helpful community members that should be willing to point you in the right direction. From d3c8f8287afc651ab04e19954f039e6e1fd7aa24 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 14 Nov 2017 00:10:21 +0100 Subject: [PATCH 33/43] :arrow_up: dev-live-reload@0.48.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f94ad81e..1da2e4fd9 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "command-palette": "0.42.0", "dalek": "0.2.1", "deprecation-cop": "0.56.9", - "dev-live-reload": "0.47.1", + "dev-live-reload": "0.48.0", "encoding-selector": "0.23.7", "exception-reporting": "0.41.5", "find-and-replace": "0.214.0", From 65292240850af92aefab49eb0979f9c7a622817f Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 14 Nov 2017 11:55:47 +0100 Subject: [PATCH 34/43] :arrow_up: dev-live-reload@0.48.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1da2e4fd9..6ad4fbeb0 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "command-palette": "0.42.0", "dalek": "0.2.1", "deprecation-cop": "0.56.9", - "dev-live-reload": "0.48.0", + "dev-live-reload": "0.48.1", "encoding-selector": "0.23.7", "exception-reporting": "0.41.5", "find-and-replace": "0.214.0", From ad1328db5aaef81da53cb1d1ab1f4dd6cbe91ceb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 14 Nov 2017 12:17:50 -0700 Subject: [PATCH 35/43] Revert "Merge pull request #16092 from atom/autoscroll-after-fold-or-unfold" This reverts commit 6227ecebedf676613d6a698a12d7eaaa8c791ff8, reversing changes made to 311055c57510197ee8bc6d24a9c1d3aa7d295beb. --- spec/text-editor-spec.js | 26 +++----------------------- src/text-editor.js | 28 ++++++---------------------- 2 files changed, 9 insertions(+), 45 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index fa8406731..198cf1c43 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -115,12 +115,12 @@ describe('TextEditor', () => { editor.update({showCursorOnSelection: false}) editor.setSelectedBufferRange([[1, 2], [3, 4]]) editor.addSelectionForBufferRange([[5, 6], [7, 8]], {reversed: true}) - editor.foldBufferRow(4) - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() editor.setScrollTopRow(3) expect(editor.getScrollTopRow()).toBe(3) editor.setScrollLeftColumn(4) expect(editor.getScrollLeftColumn()).toBe(4) + editor.foldBufferRow(4) + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() const editor2 = editor.copy() const element2 = editor2.getElement() @@ -7028,19 +7028,15 @@ describe('TextEditor', () => { }) describe('.unfoldAll()', () => { - it('unfolds every folded line and autoscrolls', async () => { + it('unfolds every folded line', async () => { editor = await atom.workspace.open('sample.js', {autoIndent: false}) - const autoscrollEvents = [] - editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) const initialScreenLineCount = editor.getScreenLineCount() editor.foldBufferRow(0) editor.foldBufferRow(1) expect(editor.getScreenLineCount()).toBeLessThan(initialScreenLineCount) - expect(autoscrollEvents.length).toBe(1) editor.unfoldAll() expect(editor.getScreenLineCount()).toBe(initialScreenLineCount) - expect(autoscrollEvents.length).toBe(2) }) it('unfolds every folded line with comments', async () => { @@ -7058,11 +7054,8 @@ describe('TextEditor', () => { describe('.foldAll()', () => { it('folds every foldable line', async () => { editor = await atom.workspace.open('sample.js', {autoIndent: false}) - const autoscrollEvents = [] - editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) editor.foldAll() - expect(autoscrollEvents.length).toBe(1) const [fold1, fold2, fold3] = editor.unfoldAll() expect([fold1.start.row, fold1.end.row]).toEqual([0, 12]) expect([fold2.start.row, fold2.end.row]).toEqual([1, 9]) @@ -7093,11 +7086,7 @@ describe('TextEditor', () => { describe('when bufferRow can be folded', () => { it('creates a fold based on the syntactic region starting at the given row', () => { - const autoscrollEvents = [] - editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) - editor.foldBufferRow(1) - expect(autoscrollEvents.length).toBe(1) const [fold] = editor.unfoldAll() expect([fold.start.row, fold.end.row]).toEqual([1, 9]) }) @@ -7144,14 +7133,10 @@ describe('TextEditor', () => { describe('.foldCurrentRow()', () => { it('creates a fold at the location of the last cursor', async () => { editor = await atom.workspace.open() - editor.setText('\nif (x) {\n y()\n}') editor.setCursorBufferPosition([1, 0]) expect(editor.getScreenLineCount()).toBe(4) - const autoscrollEvents = [] - editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) editor.foldCurrentRow() - expect(autoscrollEvents.length).toBe(1) expect(editor.getScreenLineCount()).toBe(3) }) @@ -7168,26 +7153,21 @@ describe('TextEditor', () => { describe('.foldAllAtIndentLevel(indentLevel)', () => { it('folds blocks of text at the given indentation level', async () => { editor = await atom.workspace.open('sample.js', {autoIndent: false}) - const autoscrollEvents = [] - editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) editor.foldAllAtIndentLevel(0) expect(editor.lineTextForScreenRow(0)).toBe(`var quicksort = function () {${editor.displayLayer.foldCharacter}`) expect(editor.getLastScreenRow()).toBe(0) - expect(autoscrollEvents.length).toBe(1) editor.foldAllAtIndentLevel(1) expect(editor.lineTextForScreenRow(0)).toBe('var quicksort = function () {') expect(editor.lineTextForScreenRow(1)).toBe(` var sort = function(items) {${editor.displayLayer.foldCharacter}`) expect(editor.getLastScreenRow()).toBe(4) - expect(autoscrollEvents.length).toBe(2) editor.foldAllAtIndentLevel(2) expect(editor.lineTextForScreenRow(0)).toBe('var quicksort = function () {') expect(editor.lineTextForScreenRow(1)).toBe(' var sort = function(items) {') expect(editor.lineTextForScreenRow(2)).toBe(' if (items.length <= 1) return items;') expect(editor.getLastScreenRow()).toBe(9) - expect(autoscrollEvents.length).toBe(3) }) it('folds every foldable range at a given indentLevel', async () => { diff --git a/src/text-editor.js b/src/text-editor.js index 8eee5c140..a0b9d19a0 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -3750,19 +3750,13 @@ class TextEditor { foldCurrentRow () { const {row} = this.getCursorBufferPosition() const range = this.tokenizedBuffer.getFoldableRangeContainingPoint(Point(row, Infinity)) - if (range) { - const result = this.displayLayer.foldBufferRange(range) - this.scrollToCursorPosition() - return result - } + if (range) return this.displayLayer.foldBufferRange(range) } // Essential: Unfold the most recent cursor's row by one level. unfoldCurrentRow () { const {row} = this.getCursorBufferPosition() - const result = this.displayLayer.destroyFoldsContainingBufferPositions([Point(row, Infinity)], false) - this.scrollToCursorPosition() - return result + return this.displayLayer.destroyFoldsContainingBufferPositions([Point(row, Infinity)], false) } // Essential: Fold the given row in buffer coordinates based on its indentation @@ -3780,7 +3774,6 @@ class TextEditor { const existingFolds = this.displayLayer.foldsIntersectingBufferRange(Range(foldableRange.start, foldableRange.start)) if (existingFolds.length === 0) { this.displayLayer.foldBufferRange(foldableRange) - this.scrollToCursorPosition() } else { const firstExistingFoldRange = this.displayLayer.bufferRangeForFold(existingFolds[0]) if (firstExistingFoldRange.start.isLessThan(position)) { @@ -3798,9 +3791,7 @@ class TextEditor { // * `bufferRow` A {Number} unfoldBufferRow (bufferRow) { const position = Point(bufferRow, Infinity) - const result = this.displayLayer.destroyFoldsContainingBufferPositions([position]) - this.scrollToCursorPosition() - return result + return this.displayLayer.destroyFoldsContainingBufferPositions([position]) } // Extended: For each selection, fold the rows it intersects. @@ -3816,7 +3807,6 @@ class TextEditor { for (let range of this.tokenizedBuffer.getFoldableRanges(this.getTabLength())) { this.displayLayer.foldBufferRange(range) } - this.scrollToCursorPosition() } // Extended: Unfold all existing folds. @@ -3834,7 +3824,6 @@ class TextEditor { for (let range of this.tokenizedBuffer.getFoldableRangesAtIndentLevel(level, this.getTabLength())) { this.displayLayer.foldBufferRange(range) } - this.scrollToCursorPosition() } // Extended: Determine whether the given row in buffer coordinates is foldable. @@ -3862,14 +3851,11 @@ class TextEditor { // Extended: Fold the given buffer row if it isn't currently folded, and unfold // it otherwise. toggleFoldAtBufferRow (bufferRow) { - let result if (this.isFoldedAtBufferRow(bufferRow)) { - result = this.unfoldBufferRow(bufferRow) + return this.unfoldBufferRow(bufferRow) } else { - result = this.foldBufferRow(bufferRow) + return this.foldBufferRow(bufferRow) } - this.scrollToCursorPosition() - return result } // Extended: Determine whether the most recently added cursor's row is folded. @@ -3908,9 +3894,7 @@ class TextEditor { // // Returns the new {Fold}. foldBufferRowRange (startRow, endRow) { - const result = this.foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity))) - this.scrollToCursorPosition() - return result + return this.foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity))) } foldBufferRange (range) { From 000d0d1a5ec0bd51d35aa81d47365a22536c30c4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 14 Nov 2017 12:29:32 -0700 Subject: [PATCH 36/43] Auto-scroll after folding/unfolding via a command In my previous attempt in #16092, I was autoscrolling in the TextEditor methods themselves. But this could lead to undesirable autoscrolling when folding with the mouse. --- src/register-default-commands.coffee | 44 +++++++++++++++++++++------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 7dc0d3298..0bacfbb8e 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -219,18 +219,40 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'editor:toggle-soft-wrap': -> @toggleSoftWrapped() 'editor:fold-all': -> @foldAll() 'editor:unfold-all': -> @unfoldAll() - 'editor:fold-current-row': -> @foldCurrentRow() - 'editor:unfold-current-row': -> @unfoldCurrentRow() + 'editor:fold-current-row': -> + @foldCurrentRow() + @scrollToCursorPosition() + 'editor:unfold-current-row': -> + @unfoldCurrentRow() + @scrollToCursorPosition() 'editor:fold-selection': -> @foldSelectedLines() - 'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0) - 'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1) - 'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2) - 'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3) - 'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4) - 'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5) - 'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6) - 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7) - 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8) + 'editor:fold-at-indent-level-1': -> + @foldAllAtIndentLevel(0) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-2': -> + @foldAllAtIndentLevel(1) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-3': -> + @foldAllAtIndentLevel(2) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-4': -> + @foldAllAtIndentLevel(3) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-5': -> + @foldAllAtIndentLevel(4) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-6': -> + @foldAllAtIndentLevel(5) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-7': -> + @foldAllAtIndentLevel(6) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-8': -> + @foldAllAtIndentLevel(7) + @scrollToCursorPosition() + 'editor:fold-at-indent-level-9': -> + @foldAllAtIndentLevel(8) + @scrollToCursorPosition() 'editor:log-cursor-scope': -> showCursorScope(@getCursorScope(), notificationManager) 'editor:copy-path': -> copyPathToClipboard(this, project, clipboard, false) 'editor:copy-project-path': -> copyPathToClipboard(this, project, clipboard, true) From 268461b7e05aa488ec0652f9b84b8e502244f41f Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 14 Nov 2017 23:33:34 +0100 Subject: [PATCH 37/43] :arrow_up: link@0.31.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ad4fbeb0..ab206ec40 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "incompatible-packages": "0.27.3", "keybinding-resolver": "0.38.1", "line-ending-selector": "0.7.4", - "link": "0.31.3", + "link": "0.31.4", "markdown-preview": "0.159.18", "metrics": "1.2.6", "notifications": "0.69.2", From c172b1236a71e91cf938399ae73a613229953c2b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 14 Nov 2017 10:41:59 -0800 Subject: [PATCH 38/43] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab206ec40..cb9f24d75 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "service-hub": "^0.7.4", "sinon": "1.17.4", "temp": "^0.8.3", - "text-buffer": "13.8.3", + "text-buffer": "13.8.5", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From 8a632d58ac3442900210331bbf858ab02952245d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 14 Nov 2017 14:54:34 -0800 Subject: [PATCH 39/43] :arrow_up: autocomplete-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb9f24d75..51261f056 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "autocomplete-atom-api": "0.10.5", "autocomplete-css": "0.17.4", "autocomplete-html": "0.8.3", - "autocomplete-plus": "2.37.3", + "autocomplete-plus": "2.37.4", "autocomplete-snippets": "1.11.2", "autoflow": "0.29.0", "autosave": "0.24.6", From 1563647baf4d16dbcf3178b764293b9dbfeacc5b Mon Sep 17 00:00:00 2001 From: Justin Ratner Date: Tue, 14 Nov 2017 17:35:36 -0700 Subject: [PATCH 40/43] :arrow_up: autocomplete-plus@2.37.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 51261f056..f0f6b6567 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "autocomplete-atom-api": "0.10.5", "autocomplete-css": "0.17.4", "autocomplete-html": "0.8.3", - "autocomplete-plus": "2.37.4", + "autocomplete-plus": "2.37.5", "autocomplete-snippets": "1.11.2", "autoflow": "0.29.0", "autosave": "0.24.6", From 4cc6ccacb11bbe79840edb10ff9ef91bccabfad5 Mon Sep 17 00:00:00 2001 From: U8N WXD Date: Tue, 14 Nov 2017 21:12:08 -0800 Subject: [PATCH 41/43] Change HTTP Links to HTTPS in CONTRIBUTING.md Similar to issue #16167 and PR #16173 Change links throughout CONTRIBUTING.md to support SSL/TLS by changing URL to `https://` [ci skip] Signed-off-by: U8N WXD --- CONTRIBUTING.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f0d2d5a2..0031de75a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,7 @@ This project and everyone participating in it is governed by the [Atom Code of C ## I don't want to read this whole thing I just have a question!!! -> **Note:** [Please don't file an issue to ask a question.](http://blog.atom.io/2016/04/19/managing-the-deluge-of-atom-issues.html) You'll get faster results by using the resources below. +> **Note:** [Please don't file an issue to ask a question.](https://blog.atom.io/2016/04/19/managing-the-deluge-of-atom-issues.html) You'll get faster results by using the resources below. We have an official message board with a detailed FAQ and where the community chimes in with helpful advice if you have questions. @@ -45,7 +45,7 @@ We have an official message board with a detailed FAQ and where the community ch If chat is more your speed, you can join the Atom and Electron Slack team: -* [Join the Atom and Electron Slack Team](http://atom-slack.herokuapp.com/) +* [Join the Atom and Electron Slack Team](https://atom-slack.herokuapp.com/) * Even though Slack is a chat service, sometimes it takes several hours for community members to respond — please be patient! * Use the `#atom` channel for general questions or discussion about Atom * Use the `#electron` channel for questions about Electron @@ -119,7 +119,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r #### Before Submitting A Bug Report -* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings). +* **Check the [debugging guide](https://flight-manual.atom.io/hacking-atom/sections/debugging/).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](https://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings). * **Check the [FAQs on the forum](https://discuss.atom.io/c/faq)** for a list of common questions and problems. * **Determine [which repository the problem should be reported in](#atom-and-packages)**. * **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. @@ -135,15 +135,15 @@ Explain the problem and include additional details to help maintainers reproduce * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. * **Explain which behavior you expected to see instead and why.** -* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. * **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On macOS, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist. -* **If the problem is related to performance or memory**, include a [CPU profile capture](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance) with your report. -* **If Chrome's developer tools pane is shown without you triggering it**, that normally means that you have a syntax error in one of your themes or in your `styles.less`. Try running in [Safe Mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode) and using a different theme or comment out the contents of your `styles.less` to see if that fixes the problem. +* **If the problem is related to performance or memory**, include a [CPU profile capture](https://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance) with your report. +* **If Chrome's developer tools pane is shown without you triggering it**, that normally means that you have a syntax error in one of your themes or in your `styles.less`. Try running in [Safe Mode](https://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode) and using a different theme or comment out the contents of your `styles.less` to see if that fixes the problem. * **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. Provide more context by answering these questions: -* **Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance-problems-with-the-dev-tools-cpu-profiler)?** +* **Can you reproduce the problem in [safe mode](https://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance-problems-with-the-dev-tools-cpu-profiler)?** * **Did the problem start happening recently** (e.g. after updating to a new version of Atom) or was this always a problem? * If the problem started happening recently, **can you reproduce the problem in an older version of Atom?** What's the most recent version in which the problem doesn't happen? You can download older versions of Atom from [the releases page](https://github.com/atom/atom/releases). * **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. @@ -155,7 +155,7 @@ Include details about your configuration and environment: * **What's the name and version of the OS you're using**? * **Are you running Atom in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest? * **Which [packages](#atom-and-packages) do you have installed?** You can get that list by running `apm list --installed`. -* **Are you using [local configuration files](http://flight-manual.atom.io/using-atom/sections/basic-customization/)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/). +* **Are you using [local configuration files](https://flight-manual.atom.io/using-atom/sections/basic-customization/)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/). * **Are you using Atom with multiple monitors?** If so, can you reproduce the problem when you use a single monitor? * **Which keyboard layout are you using?** Are you using a US layout or some other layout? @@ -167,7 +167,7 @@ Before creating enhancement suggestions, please check [this list](#before-submit #### Before Submitting An Enhancement Suggestion -* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings). +* **Check the [debugging guide](https://flight-manual.atom.io/hacking-atom/sections/debugging/)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](https://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings). * **Check if there's already [a package](https://atom.io/packages) which provides that enhancement.** * **Determine [which repository the enhancement should be suggested in](#atom-and-packages).** * **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. @@ -180,7 +180,7 @@ Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. * **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). * **Describe the current behavior** and **explain which behavior you expected to see instead** and why. -* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Atom which the suggestion is related to. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Atom which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. * **Explain why this enhancement would be useful** to most Atom users and isn't something that can or should be implemented as a [community package](#atom-and-packages). * **List some other text editors or applications where this enhancement exists.** * **Specify which version of Atom you're using.** You can get the exact version by running `atom -v` in your terminal, or by starting Atom and running the `Application: About` command from the [Command Palette](https://github.com/atom/command-palette). @@ -195,11 +195,11 @@ Unsure where to begin contributing to Atom? You can start by looking through the Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. -If you want to read about using Atom or developing packages in Atom, the [Atom Flight Manual](http://flight-manual.atom.io) is free and available online. You can find the source to the manual in [atom/flight-manual.atom.io](https://github.com/atom/flight-manual.atom.io). +If you want to read about using Atom or developing packages in Atom, the [Atom Flight Manual](https://flight-manual.atom.io) is free and available online. You can find the source to the manual in [atom/flight-manual.atom.io](https://github.com/atom/flight-manual.atom.io). #### Local development -Atom Core and all packages can be developed locally. For instructions on how to do this, see the following sections in the [Atom Flight Manual](http://flight-manual.atom.io): +Atom Core and all packages can be developed locally. For instructions on how to do this, see the following sections in the [Atom Flight Manual](https://flight-manual.atom.io): * [Hacking on Atom Core][hacking-on-atom-core] * [Contributing to Official Atom Packages][contributing-to-official-atom-packages] @@ -210,10 +210,10 @@ Atom Core and all packages can be developed locally. For instructions on how to * Do not include issue numbers in the PR title * Include screenshots and animated GIFs in your pull request whenever possible. * Follow the [JavaScript](#javascript-styleguide) and [CoffeeScript](#coffeescript-styleguide) styleguides. -* Include thoughtfully-worded, well-structured [Jasmine](http://jasmine.github.io/) specs in the `./spec` folder. Run them using `atom --test spec`. See the [Specs Styleguide](#specs-styleguide) below. +* Include thoughtfully-worded, well-structured [Jasmine](https://jasmine.github.io/) specs in the `./spec` folder. Run them using `atom --test spec`. See the [Specs Styleguide](#specs-styleguide) below. * Document new code based on the [Documentation Styleguide](#documentation-styleguide) * End all files with a newline -* [Avoid platform-dependent code](http://flight-manual.atom.io/hacking-atom/sections/cross-platform-compatibility/) +* [Avoid platform-dependent code](https://flight-manual.atom.io/hacking-atom/sections/cross-platform-compatibility/) * Place requires in the following order: * Built in Node Modules (such as `path`) * Built in Atom and Electron Modules (such as `atom`, `remote`) @@ -250,7 +250,7 @@ Atom Core and all packages can be developed locally. For instructions on how to ### JavaScript Styleguide -All JavaScript must adhere to [JavaScript Standard Style](http://standardjs.com/). +All JavaScript must adhere to [JavaScript Standard Style](https://standardjs.com/). * Prefer the object spread operator (`{...anotherObj}`) to `Object.assign()` * Inline `export`s with expressions whenever possible @@ -292,7 +292,7 @@ All JavaScript must adhere to [JavaScript Standard Style](http://standardjs.com/ ### Specs Styleguide -- Include thoughtfully-worded, well-structured [Jasmine](http://jasmine.github.io/) specs in the `./spec` folder. +- Include thoughtfully-worded, well-structured [Jasmine](https://jasmine.github.io/) specs in the `./spec` folder. - Treat `describe` as a noun or situation. - Treat `it` as a statement about state or how an operation changes state. @@ -369,7 +369,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and | `windows` | [search][search-atom-repo-label-windows] | [search][search-atom-org-label-windows] | Related to Atom running on Windows. | | `linux` | [search][search-atom-repo-label-linux] | [search][search-atom-org-label-linux] | Related to Atom running on Linux. | | `mac` | [search][search-atom-repo-label-mac] | [search][search-atom-org-label-mac] | Related to Atom running on macOS. | -| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/) and the [flight manual](http://flight-manual.atom.io/)). | +| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/) and the [flight manual](https://flight-manual.atom.io/)). | | `performance` | [search][search-atom-repo-label-performance] | [search][search-atom-org-label-performance] | Related to performance. | | `security` | [search][search-atom-repo-label-security] | [search][search-atom-org-label-security] | Related to security. | | `ui` | [search][search-atom-repo-label-ui] | [search][search-atom-org-label-ui] | Related to visual design. | @@ -494,5 +494,5 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and [beginner]:https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc [help-wanted]:https://github.com/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc+-label%3Abeginner -[contributing-to-official-atom-packages]:http://flight-manual.atom.io/hacking-atom/sections/contributing-to-official-atom-packages/ -[hacking-on-atom-core]: http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/ +[contributing-to-official-atom-packages]:https://flight-manual.atom.io/hacking-atom/sections/contributing-to-official-atom-packages/ +[hacking-on-atom-core]: https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/ From ea0a673b957a94fa8540564fb305e6315ad4b448 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Tue, 14 Nov 2017 23:41:24 -0800 Subject: [PATCH 42/43] :arrow_up: tree-view@0.222.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0f6b6567..9d56b75d4 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "symbols-view": "0.118.1", "tabs": "0.109.1", "timecop": "0.36.2", - "tree-view": "0.221.3", + "tree-view": "0.222.0", "update-package-dependencies": "0.13.0", "welcome": "0.36.5", "whitespace": "0.37.5", From 9860fa2e728c8b7ee47ef8eb5079f3a5a0fe7480 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 15 Nov 2017 00:10:03 -0800 Subject: [PATCH 43/43] :arrow_up: package-generator@1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d56b75d4..8fdef88d1 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "metrics": "1.2.6", "notifications": "0.69.2", "open-on-github": "1.3.0", - "package-generator": "1.1.1", + "package-generator": "1.2.0", "settings-view": "0.253.0", "snippets": "1.1.9", "spell-check": "0.72.3",