From f9227276447fe61e83af93acfb5275987b94db19 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 9 Jan 2018 21:17:17 -0500 Subject: [PATCH 01/16] Use async dialog.showMessageBox --- src/main-process/atom-application.js | 7 +++---- src/main-process/atom-window.js | 16 ++++++++-------- src/main-process/auto-update-manager.coffee | 6 ++++-- src/main-process/file-recovery-service.js | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 372bd537c..fd762a57c 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -1293,17 +1293,16 @@ class AtomApplication extends EventEmitter { // File dialog defaults to project directory of currently active editor if (path) openOptions.defaultPath = path - return dialog.showOpenDialog(parentWindow, openOptions, callback) + dialog.showOpenDialog(parentWindow, openOptions, callback) } promptForRestart () { - const chosen = dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { + dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { type: 'warning', title: 'Restart required', message: 'You will need to restart Atom for this change to take effect.', buttons: ['Restart Atom', 'Cancel'] - }) - if (chosen === 0) return this.restart() + }, response => { if (response === 0) this.restart() }) } restart () { diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index 0268cc1cf..ec19a0e9d 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -176,14 +176,13 @@ class AtomWindow extends EventEmitter { this.browserWindow.on('unresponsive', () => { if (this.isSpec) return - const chosen = dialog.showMessageBox(this.browserWindow, { + dialog.showMessageBox(this.browserWindow, { type: 'warning', buttons: ['Force Close', 'Keep Waiting'], message: 'Editor is not responding', detail: 'The editor is not responding. Would you like to force close it or just keep waiting?' - }) - if (chosen === 0) this.browserWindow.destroy() + }, response => { if (response === 0) this.browserWindow.destroy() }) }) this.browserWindow.webContents.on('crashed', () => { @@ -194,16 +193,17 @@ class AtomWindow extends EventEmitter { } this.fileRecoveryService.didCrashWindow(this) - const chosen = dialog.showMessageBox(this.browserWindow, { + dialog.showMessageBox(this.browserWindow, { type: 'warning', buttons: ['Close Window', 'Reload', 'Keep It Open'], message: 'The editor has crashed', detail: 'Please report this issue to https://github.com/atom/atom' + }, response => { + switch (response) { + case 0: return this.browserWindow.destroy() + case 1: return this.browserWindow.reload() + } }) - switch (chosen) { - case 0: return this.browserWindow.destroy() - case 1: return this.browserWindow.reload() - } }) this.browserWindow.webContents.on('will-navigate', (event, url) => { diff --git a/src/main-process/auto-update-manager.coffee b/src/main-process/auto-update-manager.coffee index 0e4144c1a..bc81d425d 100644 --- a/src/main-process/auto-update-manager.coffee +++ b/src/main-process/auto-update-manager.coffee @@ -118,24 +118,26 @@ class AutoUpdateManager onUpdateNotAvailable: => autoUpdater.removeListener 'error', @onUpdateError {dialog} = require 'electron' - dialog.showMessageBox + dialog.showMessageBox { type: 'info' buttons: ['OK'] icon: @iconPath message: 'No update available.' title: 'No Update Available' detail: "Version #{@version} is the latest version." + }, -> # noop callback to get async behavior onUpdateError: (event, message) => autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable {dialog} = require 'electron' - dialog.showMessageBox + dialog.showMessageBox { type: 'warning' buttons: ['OK'] icon: @iconPath message: 'There was an error checking for updates.' title: 'Update Error' detail: message + }, -> # noop callback to get async behavior getWindows: -> global.atomApplication.getAllWindows() diff --git a/src/main-process/file-recovery-service.js b/src/main-process/file-recovery-service.js index f55e3f956..392c88b27 100644 --- a/src/main-process/file-recovery-service.js +++ b/src/main-process/file-recovery-service.js @@ -65,7 +65,7 @@ export default class FileRecoveryService { `Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` + `Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".` console.log(detail) - dialog.showMessageBox(window.browserWindow, {type: 'info', buttons: ['OK'], message, detail}) + dialog.showMessageBox(window.browserWindow, {type: 'info', buttons: ['OK'], message, detail}, () => { /* noop callback to get async behavior */ }) } finally { for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { this.recoveryFilesByWindow.get(window).delete(recoveryFile) From 0390548e2c569277a5ed4590f9b4966e599967b2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2018 10:57:51 -0800 Subject: [PATCH 02/16] Make FileRecoveryService async --- .../file-recovery-service.test.js | 70 +++++------ src/application-delegate.js | 4 +- src/ipc-helpers.js | 22 ++-- src/main-process/atom-application.js | 14 +-- src/main-process/atom-window.js | 4 +- src/main-process/file-recovery-service.js | 116 +++++++++++------- src/project.js | 2 +- 7 files changed, 129 insertions(+), 103 deletions(-) diff --git a/spec/main-process/file-recovery-service.test.js b/spec/main-process/file-recovery-service.test.js index 618c30ab0..2a8f2088c 100644 --- a/spec/main-process/file-recovery-service.test.js +++ b/spec/main-process/file-recovery-service.test.js @@ -1,21 +1,21 @@ -/** @babel */ - -import {dialog} from 'electron' -import FileRecoveryService from '../../src/main-process/file-recovery-service' -import fs from 'fs-plus' -import sinon from 'sinon' -import {escapeRegExp} from 'underscore-plus' +const {dialog} = require('electron') +const FileRecoveryService = require('../../src/main-process/file-recovery-service') +const fs = require('fs-plus') +const sinon = require('sinon') +const {escapeRegExp} = require('underscore-plus') const temp = require('temp').track() describe("FileRecoveryService", () => { - let recoveryService, recoveryDirectory + let recoveryService, recoveryDirectory, spies beforeEach(() => { recoveryDirectory = temp.mkdirSync('atom-spec-file-recovery') recoveryService = new FileRecoveryService(recoveryDirectory) + spies = sinon.sandbox.create() }) afterEach(() => { + spies.restore() try { temp.cleanupSync() } catch (e) { @@ -24,38 +24,38 @@ describe("FileRecoveryService", () => { }) describe("when no crash happens during a save", () => { - it("creates a recovery file and deletes it after saving", () => { + it("creates a recovery file and deletes it after saving", async () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "some content") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "changed") - recoveryService.didSavePath(mockWindow, filePath) + await recoveryService.didSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") fs.removeSync(filePath) }) - it("creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it", () => { + it("creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it", async () => { const mockWindow = {} const anotherMockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "some content") - recoveryService.willSavePath(mockWindow, filePath) - recoveryService.willSavePath(anotherMockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "changed") - recoveryService.didSavePath(mockWindow, filePath) + await recoveryService.didSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") - recoveryService.didSavePath(anotherMockWindow, filePath) + await recoveryService.didSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") @@ -64,66 +64,66 @@ describe("FileRecoveryService", () => { }) describe("when a crash happens during a save", () => { - it("restores the created recovery file and deletes it", () => { + it("restores the created recovery file and deletes it", async () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "some content") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "changed") - recoveryService.didCrashWindow(mockWindow) + await recoveryService.didCrashWindow(mockWindow) assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) assert.equal(fs.readFileSync(filePath, 'utf8'), "some content") fs.removeSync(filePath) }) - it("restores the created recovery file when many windows attempt to save the same file and one of them crashes", () => { + it("restores the created recovery file when many windows attempt to save the same file and one of them crashes", async () => { const mockWindow = {} const anotherMockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "A") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) fs.writeFileSync(filePath, "B") - recoveryService.willSavePath(anotherMockWindow, filePath) + await recoveryService.willSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "C") - recoveryService.didCrashWindow(mockWindow) + await recoveryService.didCrashWindow(mockWindow) assert.equal(fs.readFileSync(filePath, 'utf8'), "A") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) fs.writeFileSync(filePath, "D") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) fs.writeFileSync(filePath, "E") - recoveryService.willSavePath(anotherMockWindow, filePath) + await recoveryService.willSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "F") - recoveryService.didCrashWindow(anotherMockWindow) + await recoveryService.didCrashWindow(anotherMockWindow) assert.equal(fs.readFileSync(filePath, 'utf8'), "D") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) fs.removeSync(filePath) }) - it("emits a warning when a file can't be recovered", sinon.test(function () { + it("emits a warning when a file can't be recovered", async () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "content") fs.chmodSync(filePath, 0444) let logs = [] - this.stub(console, 'log', (message) => logs.push(message)) - this.stub(dialog, 'showMessageBox') + spies.stub(console, 'log', (message) => logs.push(message)) + spies.stub(dialog, 'showMessageBox') - recoveryService.willSavePath(mockWindow, filePath) - recoveryService.didCrashWindow(mockWindow) + await recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.didCrashWindow(mockWindow) let recoveryFiles = fs.listTreeSync(recoveryDirectory) assert.equal(recoveryFiles.length, 1) assert.equal(logs.length, 1) @@ -131,16 +131,16 @@ describe("FileRecoveryService", () => { assert.match(logs[0], new RegExp(escapeRegExp(recoveryFiles[0]))) fs.removeSync(filePath) - })) + }) }) - it("doesn't create a recovery file when the file that's being saved doesn't exist yet", () => { + it("doesn't create a recovery file when the file that's being saved doesn't exist yet", async () => { const mockWindow = {} - recoveryService.willSavePath(mockWindow, "a-file-that-doesnt-exist") + await recoveryService.willSavePath(mockWindow, "a-file-that-doesnt-exist") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) - recoveryService.didSavePath(mockWindow, "a-file-that-doesnt-exist") + await recoveryService.didSavePath(mockWindow, "a-file-that-doesnt-exist") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) }) }) diff --git a/src/application-delegate.js b/src/application-delegate.js index a6d701078..1cb0c740a 100644 --- a/src/application-delegate.js +++ b/src/application-delegate.js @@ -354,11 +354,11 @@ class ApplicationDelegate { } emitWillSavePath (path) { - return ipcRenderer.sendSync('will-save-path', path) + return ipcHelpers.call('will-save-path', path) } emitDidSavePath (path) { - return ipcRenderer.sendSync('did-save-path', path) + return ipcHelpers.call('did-save-path', path) } resolveProxy (requestId, url) { diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js index 4be9f9613..9ea95092b 100644 --- a/src/ipc-helpers.js +++ b/src/ipc-helpers.js @@ -1,5 +1,3 @@ -'use strict' - const Disposable = require('event-kit').Disposable let ipcRenderer = null let ipcMain = null @@ -7,9 +5,7 @@ let BrowserWindow = null exports.on = function (emitter, eventName, callback) { emitter.on(eventName, callback) - return new Disposable(function () { - emitter.removeListener(eventName, callback) - }) + return new Disposable(() => emitter.removeListener(eventName, callback)) } exports.call = function (channel, ...args) { @@ -18,10 +14,10 @@ exports.call = function (channel, ...args) { ipcRenderer.setMaxListeners(20) } - var responseChannel = getResponseChannel(channel) + const responseChannel = getResponseChannel(channel) - return new Promise(function (resolve) { - ipcRenderer.on(responseChannel, function (event, result) { + return new Promise(resolve => { + ipcRenderer.on(responseChannel, (event, result) => { ipcRenderer.removeAllListeners(responseChannel) resolve(result) }) @@ -32,16 +28,16 @@ exports.call = function (channel, ...args) { exports.respondTo = function (channel, callback) { if (!ipcMain) { - var electron = require('electron') + const electron = require('electron') ipcMain = electron.ipcMain BrowserWindow = electron.BrowserWindow } - var responseChannel = getResponseChannel(channel) + const responseChannel = getResponseChannel(channel) - return exports.on(ipcMain, channel, function (event, ...args) { - var browserWindow = BrowserWindow.fromWebContents(event.sender) - var result = callback(browserWindow, ...args) + return exports.on(ipcMain, channel, async (event, ...args) => { + const browserWindow = BrowserWindow.fromWebContents(event.sender) + const result = await callback(browserWindow, ...args) event.sender.send(responseChannel, result) }) } diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 372bd537c..55c89b34f 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -569,15 +569,13 @@ class AtomApplication extends EventEmitter { event.returnValue = this.autoUpdateManager.getErrorMessage() })) - this.disposable.add(ipcHelpers.on(ipcMain, 'will-save-path', (event, path) => { - this.fileRecoveryService.willSavePath(this.atomWindowForEvent(event), path) - event.returnValue = true - })) + this.disposable.add(ipcHelpers.respondTo('will-save-path', (window, path) => + this.fileRecoveryService.willSavePath(window, path) + )) - this.disposable.add(ipcHelpers.on(ipcMain, 'did-save-path', (event, path) => { - this.fileRecoveryService.didSavePath(this.atomWindowForEvent(event), path) - event.returnValue = true - })) + this.disposable.add(ipcHelpers.respondTo('did-save-path', (window, path) => + this.fileRecoveryService.didSavePath(window, path) + )) this.disposable.add(ipcHelpers.on(ipcMain, 'did-change-paths', () => this.saveState(false) diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index 0268cc1cf..9ffd1e6c0 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -186,14 +186,14 @@ class AtomWindow extends EventEmitter { if (chosen === 0) this.browserWindow.destroy() }) - this.browserWindow.webContents.on('crashed', () => { + this.browserWindow.webContents.on('crashed', async () => { if (this.headless) { console.log('Renderer process crashed, exiting') this.atomApplication.exit(100) return } - this.fileRecoveryService.didCrashWindow(this) + await this.fileRecoveryService.didCrashWindow(this) const chosen = dialog.showMessageBox(this.browserWindow, { type: 'warning', buttons: ['Close Window', 'Reload', 'Keep It Open'], diff --git a/src/main-process/file-recovery-service.js b/src/main-process/file-recovery-service.js index f55e3f956..58ca84943 100644 --- a/src/main-process/file-recovery-service.js +++ b/src/main-process/file-recovery-service.js @@ -1,11 +1,10 @@ -'use babel' +const {dialog} = require('electron') +const crypto = require('crypto') +const Path = require('path') +const fs = require('fs-plus') -import {dialog} from 'electron' -import crypto from 'crypto' -import Path from 'path' -import fs from 'fs-plus' - -export default class FileRecoveryService { +module.exports = +class FileRecoveryService { constructor (recoveryDirectory) { this.recoveryDirectory = recoveryDirectory this.recoveryFilesByFilePath = new Map() @@ -13,15 +12,16 @@ export default class FileRecoveryService { this.windowsByRecoveryFile = new Map() } - willSavePath (window, path) { - if (!fs.existsSync(path)) return + async willSavePath (window, path) { + const stats = await tryStatFile(path) + if (!stats) return const recoveryPath = Path.join(this.recoveryDirectory, RecoveryFile.fileNameForPath(path)) const recoveryFile = - this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, recoveryPath) + this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, stats.mode, recoveryPath) try { - recoveryFile.retain() + await recoveryFile.retain() } catch (err) { console.log(`Couldn't retain ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`) return @@ -39,11 +39,11 @@ export default class FileRecoveryService { this.recoveryFilesByFilePath.set(path, recoveryFile) } - didSavePath (window, path) { + async didSavePath (window, path) { const recoveryFile = this.recoveryFilesByFilePath.get(path) if (recoveryFile != null) { try { - recoveryFile.release() + await recoveryFile.release() } catch (err) { console.log(`Couldn't release ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`) } @@ -53,27 +53,31 @@ export default class FileRecoveryService { } } - didCrashWindow (window) { + async didCrashWindow (window) { if (!this.recoveryFilesByWindow.has(window)) return + const promises = [] for (const recoveryFile of this.recoveryFilesByWindow.get(window)) { - try { - recoveryFile.recoverSync() - } catch (error) { - const message = 'A file that Atom was saving could be corrupted' - const detail = - `Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` + - `Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".` - console.log(detail) - dialog.showMessageBox(window.browserWindow, {type: 'info', buttons: ['OK'], message, detail}) - } finally { - for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { - this.recoveryFilesByWindow.get(window).delete(recoveryFile) - } - this.windowsByRecoveryFile.delete(recoveryFile) - this.recoveryFilesByFilePath.delete(recoveryFile.originalPath) - } + promises.push(recoveryFile.recover() + .catch(error => { + const message = 'A file that Atom was saving could be corrupted' + const detail = + `Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` + + `Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".` + console.log(detail) + dialog.showMessageBox(window, {type: 'info', buttons: ['OK'], message, detail}) + }) + .then(() => { + for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { + this.recoveryFilesByWindow.get(window).delete(recoveryFile) + } + this.windowsByRecoveryFile.delete(recoveryFile) + this.recoveryFilesByFilePath.delete(recoveryFile.originalPath) + }) + ) } + + await Promise.all(promises) } didCloseWindow (window) { @@ -94,36 +98,64 @@ class RecoveryFile { return `${basename}-${randomSuffix}${extension}` } - constructor (originalPath, recoveryPath) { + constructor (originalPath, fileMode, recoveryPath) { this.originalPath = originalPath + this.fileMode = fileMode this.recoveryPath = recoveryPath this.refCount = 0 } - storeSync () { - fs.copyFileSync(this.originalPath, this.recoveryPath) + async store () { + await copyFile(this.originalPath, this.recoveryPath, this.fileMode) } - recoverSync () { - fs.copyFileSync(this.recoveryPath, this.originalPath) - this.removeSync() + async recover () { + await copyFile(this.recoveryPath, this.originalPath, this.fileMode) + await this.remove() } - removeSync () { - fs.unlinkSync(this.recoveryPath) + async remove () { + return new Promise((resolve, reject) => + fs.unlink(this.recoveryPath, error => + error && error.code !== 'ENOENT' ? reject(error) : resolve() + ) + ) } - retain () { - if (this.isReleased()) this.storeSync() + async retain () { + if (this.isReleased()) await this.store() this.refCount++ } - release () { + async release () { this.refCount-- - if (this.isReleased()) this.removeSync() + if (this.isReleased()) await this.remove() } isReleased () { return this.refCount === 0 } } + +async function tryStatFile (path) { + return new Promise((resolve, reject) => + fs.stat(path, (error, result) => + resolve(error == null && result) + ) + ) +} + +async function copyFile (source, destination, mode) { + return new Promise((resolve, reject) => { + const readStream = fs.createReadStream(source) + readStream + .on('error', reject) + .once('open', () => { + const writeStream = fs.createWriteStream(destination, {mode}) + writeStream + .on('error', reject) + .on('open', () => readStream.pipe(writeStream)) + .once('close', () => resolve()) + }) + }) +} diff --git a/src/project.js b/src/project.js index 18e71c915..9cd2d1245 100644 --- a/src/project.js +++ b/src/project.js @@ -695,7 +695,7 @@ class Project extends Model { } subscribeToBuffer (buffer) { - buffer.onWillSave(({path}) => this.applicationDelegate.emitWillSavePath(path)) + buffer.onWillSave(async ({path}) => this.applicationDelegate.emitWillSavePath(path)) buffer.onDidSave(({path}) => this.applicationDelegate.emitDidSavePath(path)) buffer.onDidDestroy(() => this.removeBuffer(buffer)) buffer.onDidChangePath(() => { From 9694448f9d41dec92e9cb901e6aa1df163c09d30 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2018 11:26:07 -0800 Subject: [PATCH 03/16] Handle concurrent calls to the same channel in ipc helpers --- src/ipc-helpers.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js index 9ea95092b..b68877f99 100644 --- a/src/ipc-helpers.js +++ b/src/ipc-helpers.js @@ -3,6 +3,8 @@ let ipcRenderer = null let ipcMain = null let BrowserWindow = null +let nextResponseChannelId = 0 + exports.on = function (emitter, eventName, callback) { emitter.on(eventName, callback) return new Disposable(() => emitter.removeListener(eventName, callback)) @@ -14,7 +16,7 @@ exports.call = function (channel, ...args) { ipcRenderer.setMaxListeners(20) } - const responseChannel = getResponseChannel(channel) + const responseChannel = `ipc-helpers-response-${nextResponseChannelId++}` return new Promise(resolve => { ipcRenderer.on(responseChannel, (event, result) => { @@ -22,7 +24,7 @@ exports.call = function (channel, ...args) { resolve(result) }) - ipcRenderer.send(channel, ...args) + ipcRenderer.send(channel, responseChannel, ...args) }) } @@ -33,15 +35,9 @@ exports.respondTo = function (channel, callback) { BrowserWindow = electron.BrowserWindow } - const responseChannel = getResponseChannel(channel) - - return exports.on(ipcMain, channel, async (event, ...args) => { + return exports.on(ipcMain, channel, async (event, responseChannel, ...args) => { const browserWindow = BrowserWindow.fromWebContents(event.sender) const result = await callback(browserWindow, ...args) event.sender.send(responseChannel, result) }) } - -function getResponseChannel (channel) { - return 'ipc-helpers-' + channel + '-response' -} From 400abcba349222fb1c7b90985123886e2c8e1f15 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2018 12:02:43 -0800 Subject: [PATCH 04/16] Decaffeinate StorageFolder --- src/storage-folder.coffee | 39 --------------------------- src/storage-folder.js | 57 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 39 deletions(-) delete mode 100644 src/storage-folder.coffee create mode 100644 src/storage-folder.js diff --git a/src/storage-folder.coffee b/src/storage-folder.coffee deleted file mode 100644 index 280eb8b5c..000000000 --- a/src/storage-folder.coffee +++ /dev/null @@ -1,39 +0,0 @@ -path = require "path" -fs = require "fs-plus" - -module.exports = -class StorageFolder - constructor: (containingPath) -> - @path = path.join(containingPath, "storage") if containingPath? - - clear: -> - return unless @path? - - try - fs.removeSync(@path) - catch error - console.warn "Error deleting #{@path}", error.stack, error - - storeSync: (name, object) -> - return unless @path? - - fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8') - - load: (name) -> - return unless @path? - - statePath = @pathForKey(name) - try - stateString = fs.readFileSync(statePath, 'utf8') - catch error - unless error.code is 'ENOENT' - console.warn "Error reading state file: #{statePath}", error.stack, error - return undefined - - try - JSON.parse(stateString) - catch error - console.warn "Error parsing state file: #{statePath}", error.stack, error - - pathForKey: (name) -> path.join(@getPath(), name) - getPath: -> @path diff --git a/src/storage-folder.js b/src/storage-folder.js new file mode 100644 index 000000000..e40583da3 --- /dev/null +++ b/src/storage-folder.js @@ -0,0 +1,57 @@ +const path = require('path') +const fs = require('fs-plus') + +module.exports = +class StorageFolder { + constructor (containingPath) { + if (containingPath) { + this.path = path.join(containingPath, 'storage') + } + } + + clear () { + if (!this.path) return + + try { + fs.removeSync(this.path) + } catch (error) { + console.warn(`Error deleting ${this.path}`, error.stack, error) + } + } + + storeSync (name, object) { + if (!this.path) return + + fs.writeFileSync(this.pathForKey(name), JSON.stringify(object), 'utf8') + } + + load (name) { + if (!this.path) return + + const statePath = this.pathForKey(name) + + let stateString + try { + stateString = fs.readFileSync(statePath, 'utf8') + } catch (error) { + if (error.code !== 'ENOENT') { + console.warn(`Error reading state file: ${statePath}`, error.stack, error) + } + return null + } + + try { + return JSON.parse(stateString) + } catch (error) { + console.warn(`Error parsing state file: ${statePath}`, error.stack, error) + } + } + + pathForKey (name) { + return path.join(this.getPath(), name) + } + + getPath () { + return this.path + } +} From 7c8f73b2d816d928e6fc3094a76c0a35c1a36347 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2018 15:14:35 -0800 Subject: [PATCH 05/16] Make StorageFolder.clear async --- spec/atom-environment-spec.js | 1 - src/atom-environment.js | 16 +++++++++------- src/storage-folder.js | 14 +++++++------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index 324e9eddf..5574e9663 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -4,7 +4,6 @@ const fs = require('fs') const path = require('path') const temp = require('temp').track() const AtomEnvironment = require('../src/atom-environment') -const StorageFolder = require('../src/storage-folder') describe('AtomEnvironment', () => { afterEach(() => { diff --git a/src/atom-environment.js b/src/atom-environment.js index a80cde66c..73a2c284a 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -208,12 +208,7 @@ class AtomEnvironment { this.blobStore = params.blobStore this.configDirPath = params.configDirPath - const {devMode, safeMode, resourcePath, clearWindowState} = this.getLoadSettings() - - if (clearWindowState) { - this.getStorageFolder().clear() - this.stateStore.clear() - } + const {devMode, safeMode, resourcePath} = this.getLoadSettings() ConfigSchema.projectHome = { type: 'string', @@ -764,7 +759,14 @@ class AtomEnvironment { } // Call this method when establishing a real application window. - startEditorWindow () { + async startEditorWindow () { + if (this.getLoadSettings().clearWindowState) { + await Promise.all([ + this.getStorageFolder().clear(), + this.stateStore.clear() + ]) + } + this.unloaded = false const updateProcessEnvPromise = this.updateProcessEnvAndTriggerHooks() diff --git a/src/storage-folder.js b/src/storage-folder.js index e40583da3..b5d016fbc 100644 --- a/src/storage-folder.js +++ b/src/storage-folder.js @@ -10,13 +10,13 @@ class StorageFolder { } clear () { - if (!this.path) return - - try { - fs.removeSync(this.path) - } catch (error) { - console.warn(`Error deleting ${this.path}`, error.stack, error) - } + return new Promise(resolve => { + if (!this.path) return + fs.remove(this.path, error => { + if (error) console.warn(`Error deleting ${this.path}`, error.stack, error) + reolve() + }) + }) } storeSync (name, object) { From 86c37125209824dd593fa0766822cef115449d6b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Jan 2018 09:32:24 +0100 Subject: [PATCH 06/16] Don't transpile JS files found in benchmarks, exports and src folders --- script/lib/transpile-babel-paths.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index b7906b9b6..befd2477b 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -16,9 +16,6 @@ module.exports = function () { function getPathsToTranspile () { let paths = [] - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'), {nodir: true})) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'), {nodir: true})) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'), {nodir: true})) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { paths = paths.concat(glob.sync( path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.js'), From e68a2b1eb9318b6f4d38ad83ed8f93976415f477 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Jan 2018 09:33:26 +0100 Subject: [PATCH 07/16] Replace import and export keywords with `require` and `module.exports` --- exports/atom.js | 21 ++++++++++----------- src/atom-paths.js | 2 -- src/auto-update-manager.js | 7 +++---- src/buffered-node-process.js | 7 +++---- src/buffered-process.js | 13 ++++++------- src/clipboard.js | 9 ++++----- src/color.js | 5 ++--- src/deserializer-manager.js | 7 +++---- src/history-manager.js | 10 +++++----- src/initialize-benchmark-window.js | 12 +++++------- src/main-process/file-recovery-service.js | 13 ++++++------- src/main-process/win-shell.js | 6 ++---- src/native-watcher-registry.js | 2 -- src/null-grammar.js | 6 ++---- src/path-watcher.js | 2 -- src/reopen-project-list-view.js | 7 +++---- src/reopen-project-menu-manager.js | 9 ++++----- src/update-process-env.js | 8 +++----- src/workspace.js | 2 -- 19 files changed, 61 insertions(+), 87 deletions(-) diff --git a/exports/atom.js b/exports/atom.js index d7ca55909..359f0174e 100644 --- a/exports/atom.js +++ b/exports/atom.js @@ -1,13 +1,12 @@ -/** @babel */ - -import TextBuffer, {Point, Range} from 'text-buffer' -import {File, Directory} from 'pathwatcher' -import {Emitter, Disposable, CompositeDisposable} from 'event-kit' -import BufferedNodeProcess from '../src/buffered-node-process' -import BufferedProcess from '../src/buffered-process' -import GitRepository from '../src/git-repository' -import Notification from '../src/notification' -import {watchPath} from '../src/path-watcher' +const TextBuffer = require('text-buffer') +const {Point, Range} = TextBuffer +const {File, Directory} = require('pathwatcher') +const {Emitter, Disposable, CompositeDisposable} = require('event-kit') +const BufferedNodeProcess = require('../src/buffered-node-process') +const BufferedProcess = require('../src/buffered-process') +const GitRepository = require('../src/git-repository') +const Notification = require('../src/notification') +const {watchPath} = require('../src/path-watcher') const atomExport = { BufferedNodeProcess, @@ -42,4 +41,4 @@ if (process.type === 'renderer') { atomExport.TextEditor = require('../src/text-editor') } -export default atomExport +module.exports = atomExport diff --git a/src/atom-paths.js b/src/atom-paths.js index 39a768e91..d36ac25f5 100644 --- a/src/atom-paths.js +++ b/src/atom-paths.js @@ -1,5 +1,3 @@ -/** @babel */ - const fs = require('fs-plus') const path = require('path') diff --git a/src/auto-update-manager.js b/src/auto-update-manager.js index 111147f32..5e3a80129 100644 --- a/src/auto-update-manager.js +++ b/src/auto-update-manager.js @@ -1,8 +1,7 @@ -'use babel' +const {Emitter, CompositeDisposable} = require('event-kit') -import {Emitter, CompositeDisposable} from 'event-kit' - -export default class AutoUpdateManager { +module.exports = +class AutoUpdateManager { constructor ({applicationDelegate}) { this.applicationDelegate = applicationDelegate this.subscriptions = new CompositeDisposable() diff --git a/src/buffered-node-process.js b/src/buffered-node-process.js index 86b0c5747..a33176e51 100644 --- a/src/buffered-node-process.js +++ b/src/buffered-node-process.js @@ -1,6 +1,4 @@ -/** @babel */ - -import BufferedProcess from './buffered-process' +const BufferedProcess = require('./buffered-process') // Extended: Like {BufferedProcess}, but accepts a Node script as the command // to run. @@ -12,7 +10,8 @@ import BufferedProcess from './buffered-process' // ```js // const {BufferedNodeProcess} = require('atom') // ``` -export default class BufferedNodeProcess extends BufferedProcess { +module.exports = +class BufferedNodeProcess extends BufferedProcess { // Public: Runs the given Node script by spawning a new child process. // diff --git a/src/buffered-process.js b/src/buffered-process.js index 339bf05c5..0a01671fa 100644 --- a/src/buffered-process.js +++ b/src/buffered-process.js @@ -1,9 +1,7 @@ -/** @babel */ - -import _ from 'underscore-plus' -import ChildProcess from 'child_process' -import {Emitter} from 'event-kit' -import path from 'path' +const _ = require('underscore-plus') +const ChildProcess = require('child_process') +const {Emitter} = require('event-kit') +const path = require('path') // Extended: A wrapper which provides standard error/output line buffering for // Node's ChildProcess. @@ -19,7 +17,8 @@ import path from 'path' // const exit = (code) => console.log("ps -ef exited with #{code}") // const process = new BufferedProcess({command, args, stdout, exit}) // ``` -export default class BufferedProcess { +module.exports = +class BufferedProcess { /* Section: Construction */ diff --git a/src/clipboard.js b/src/clipboard.js index 34f6b1f83..d36bb1018 100644 --- a/src/clipboard.js +++ b/src/clipboard.js @@ -1,7 +1,5 @@ -/** @babel */ - -import crypto from 'crypto' -import clipboard from './safe-clipboard' +const crypto = require('crypto') +const clipboard = require('./safe-clipboard') // Extended: Represents the clipboard used for copying and pasting in Atom. // @@ -14,7 +12,8 @@ import clipboard from './safe-clipboard' // // console.log(atom.clipboard.read()) # 'hello' // ``` -export default class Clipboard { +module.exports = +class Clipboard { constructor () { this.reset() } diff --git a/src/color.js b/src/color.js index 52f555076..c183fb3e5 100644 --- a/src/color.js +++ b/src/color.js @@ -1,10 +1,9 @@ -/** @babel */ - let ParsedColor = null // Essential: A simple color class returned from {Config::get} when the value // at the key path is of type 'color'. -export default class Color { +module.exports = +class Color { // Essential: Parse a {String} or {Object} into a {Color}. // // * `value` A {String} such as `'white'`, `#ff00ff`, or diff --git a/src/deserializer-manager.js b/src/deserializer-manager.js index a11acc319..72ed9485d 100644 --- a/src/deserializer-manager.js +++ b/src/deserializer-manager.js @@ -1,6 +1,4 @@ -/** @babel */ - -import {Disposable} from 'event-kit' +const {Disposable} = require('event-kit') // Extended: Manages the deserializers used for serialized state // @@ -21,7 +19,8 @@ import {Disposable} from 'event-kit' // serialize: -> // @state // ``` -export default class DeserializerManager { +module.exports = +class DeserializerManager { constructor (atomEnvironment) { this.atomEnvironment = atomEnvironment this.deserializers = {} diff --git a/src/history-manager.js b/src/history-manager.js index 306c11812..e4651d9d9 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -1,13 +1,11 @@ -/** @babel */ - -import {Emitter, CompositeDisposable} from 'event-kit' +const {Emitter, CompositeDisposable} = require('event-kit') // Extended: History manager for remembering which projects have been opened. // // An instance of this class is always available as the `atom.history` global. // // The project history is used to enable the 'Reopen Project' menu. -export class HistoryManager { +class HistoryManager { constructor ({project, commands, stateStore}) { this.stateStore = stateStore this.emitter = new Emitter() @@ -116,7 +114,7 @@ function arrayEquivalent (a, b) { return true } -export class HistoryProject { +class HistoryProject { constructor (paths, lastOpened) { this.paths = paths this.lastOpened = lastOpened || new Date() @@ -128,3 +126,5 @@ export class HistoryProject { set lastOpened (lastOpened) { this._lastOpened = lastOpened } get lastOpened () { return this._lastOpened } } + +module.exports = {HistoryManager, HistoryProject} diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index 7ba99c468..131785454 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -1,11 +1,9 @@ -/** @babel */ +const {remote} = require('electron') +const path = require('path') +const ipcHelpers = require('./ipc-helpers') +const util = require('util') -import {remote} from 'electron' -import path from 'path' -import ipcHelpers from './ipc-helpers' -import util from 'util' - -export default async function () { +module.exports = async function () { const getWindowLoadSettings = require('./get-window-load-settings') const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() try { diff --git a/src/main-process/file-recovery-service.js b/src/main-process/file-recovery-service.js index f55e3f956..468c1803b 100644 --- a/src/main-process/file-recovery-service.js +++ b/src/main-process/file-recovery-service.js @@ -1,11 +1,10 @@ -'use babel' +const {dialog} = require('electron') +const crypto = require('crypto') +const Path = require('path') +const fs = require('fs-plus') -import {dialog} from 'electron' -import crypto from 'crypto' -import Path from 'path' -import fs from 'fs-plus' - -export default class FileRecoveryService { +module.exports = +class FileRecoveryService { constructor (recoveryDirectory) { this.recoveryDirectory = recoveryDirectory this.recoveryFilesByFilePath = new Map() diff --git a/src/main-process/win-shell.js b/src/main-process/win-shell.js index 9670936c7..dd694b9dd 100644 --- a/src/main-process/win-shell.js +++ b/src/main-process/win-shell.js @@ -1,7 +1,5 @@ -'use babel' - -import Registry from 'winreg' -import Path from 'path' +const Registry = require('winreg') +const Path = require('path') let exeName = Path.basename(process.execPath) let appPath = `\"${process.execPath}\"` diff --git a/src/native-watcher-registry.js b/src/native-watcher-registry.js index e63ac6cda..97f33e3fb 100644 --- a/src/native-watcher-registry.js +++ b/src/native-watcher-registry.js @@ -1,5 +1,3 @@ -/** @babel */ - const path = require('path') // Private: re-join the segments split from an absolute path to form another absolute path. diff --git a/src/null-grammar.js b/src/null-grammar.js index fe9c3889e..12cfbbe53 100644 --- a/src/null-grammar.js +++ b/src/null-grammar.js @@ -1,8 +1,6 @@ -/** @babel */ +const {Disposable} = require('event-kit') -import {Disposable} from 'event-kit' - -export default { +module.exports = { name: 'Null Grammar', scopeName: 'text.plain.null-grammar', scopeForId (id) { diff --git a/src/path-watcher.js b/src/path-watcher.js index d0ff90dd1..ff7e8fd56 100644 --- a/src/path-watcher.js +++ b/src/path-watcher.js @@ -1,5 +1,3 @@ -/** @babel */ - const fs = require('fs') const path = require('path') diff --git a/src/reopen-project-list-view.js b/src/reopen-project-list-view.js index f08ee725a..d59577684 100644 --- a/src/reopen-project-list-view.js +++ b/src/reopen-project-list-view.js @@ -1,8 +1,7 @@ -/** @babel */ +const SelectListView = require('atom-select-list') -import SelectListView from 'atom-select-list' - -export default class ReopenProjectListView { +module.exports = +class ReopenProjectListView { constructor (callback) { this.callback = callback this.selectListView = new SelectListView({ diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js index 3f88e41f0..35564f705 100644 --- a/src/reopen-project-menu-manager.js +++ b/src/reopen-project-menu-manager.js @@ -1,9 +1,8 @@ -/** @babel */ +const {CompositeDisposable} = require('event-kit') +const path = require('path') -import {CompositeDisposable} from 'event-kit' -import path from 'path' - -export default class ReopenProjectMenuManager { +module.exports = +class ReopenProjectMenuManager { constructor ({menu, commands, history, config, open}) { this.menuManager = menu this.historyManager = history diff --git a/src/update-process-env.js b/src/update-process-env.js index 00bb13927..6dab00a7d 100644 --- a/src/update-process-env.js +++ b/src/update-process-env.js @@ -1,7 +1,5 @@ -/** @babel */ - -import fs from 'fs' -import childProcess from 'child_process' +const fs = require('fs') +const childProcess = require('child_process') const ENVIRONMENT_VARIABLES_TO_PRESERVE = new Set([ 'NODE_ENV', @@ -120,4 +118,4 @@ async function getEnvFromShell (env) { return result } -export default { updateProcessEnv, shouldGetEnvFromShell } +module.exports = {updateProcessEnv, shouldGetEnvFromShell} diff --git a/src/workspace.js b/src/workspace.js index 66e7f8ba5..de51651ec 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -1,5 +1,3 @@ -'use babel' - const _ = require('underscore-plus') const url = require('url') const path = require('path') From 3fbfadde5a62ac2da18c3e8dd40fd616aba2469a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Jan 2018 11:20:53 +0100 Subject: [PATCH 08/16] Don't break subpixel AA when cursor is at the end of longest line With the Electron upgrade, something changed in the way characters are rendered/measured, and that was causing subpixel anti-aliasing to stop working when cursors were at the end of the longest line. Every cursor has a width that is calculated in the following way: * If there's a character after the cursor, the width corresponds to width of such character. * Otherwise, the width equals to the "base character width" measured on a dummy line. In the latter case, even if we were setting the width of the content container to account for the width of such cursor, some rounding problem was causing the cursor to be able to escape the container and thus break subpixel anti-aliasing. With this commit, instead of rounding the value we assign to the container width, we will always ceil it. This ensures that cursors are always strictly contained within the parent element and resolves the subpixel anti-aliasing issue. --- src/text-editor-component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 855920b3b..c88aab304 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -2694,7 +2694,7 @@ class TextEditorComponent { } getContentWidth () { - return Math.round(this.getLongestLineWidth() + this.getBaseCharacterWidth()) + return Math.ceil(this.getLongestLineWidth() + this.getBaseCharacterWidth()) } getScrollContainerClientWidthInBaseCharacters () { From e2ad4e6a8b0687424197ceefb158a2da696ccad2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Jan 2018 11:37:07 -0800 Subject: [PATCH 09/16] Make StorageFolder.store async Signed-off-by: Nathan Sobo --- src/main-process/atom-application.js | 4 ++-- src/storage-folder.js | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 55c89b34f..d0db06daa 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -909,7 +909,7 @@ class AtomApplication extends EventEmitter { } } - saveState (allowEmpty = false) { + async saveState (allowEmpty = false) { if (this.quitting) return const states = [] @@ -919,7 +919,7 @@ class AtomApplication extends EventEmitter { states.reverse() if (states.length > 0 || allowEmpty) { - this.storageFolder.storeSync('application.json', states) + await this.storageFolder.store('application.json', states) this.emit('application:did-save-state') } } diff --git a/src/storage-folder.js b/src/storage-folder.js index b5d016fbc..24fdfd3de 100644 --- a/src/storage-folder.js +++ b/src/storage-folder.js @@ -19,10 +19,13 @@ class StorageFolder { }) } - storeSync (name, object) { - if (!this.path) return - - fs.writeFileSync(this.pathForKey(name), JSON.stringify(object), 'utf8') + store (name, object) { + return new Promise((resolve, reject) => { + if (!this.path) return resolve() + fs.writeFile(this.pathForKey(name), JSON.stringify(object), 'utf8', error => + error ? reject(error) : resolve() + ) + }) } load (name) { From 61e53834e61f008dac7e79b144985252a8129a9c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Jan 2018 11:52:25 -0800 Subject: [PATCH 10/16] Make StorageFolder.load, AtomApplication.launch async Signed-off-by: Nathan Sobo --- spec/main-process/atom-application.test.js | 88 +++++++++++----------- src/main-process/atom-application.js | 10 +-- src/storage-folder.js | 33 ++++---- 3 files changed, 65 insertions(+), 66 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 90a512692..b67a98ae1 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -49,7 +49,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([filePath + ':3'])) + const window = await atomApplication.launch(parseCommandLine([filePath + ':3'])) await focusWindow(window) const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -66,7 +66,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([filePath + ':2:2'])) + const window = await atomApplication.launch(parseCommandLine([filePath + ':2:2'])) await focusWindow(window) const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -83,7 +83,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([filePath + ':: '])) + const window = await atomApplication.launch(parseCommandLine([filePath + ':: '])) await focusWindow(window) const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -99,11 +99,11 @@ describe('AtomApplication', function () { it('positions new windows at an offset distance from the previous window', async () => { const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([makeTempDir()])) + const window1 = await atomApplication.launch(parseCommandLine([makeTempDir()])) await focusWindow(window1) window1.browserWindow.setBounds({width: 400, height: 400, x: 0, y: 0}) - const window2 = atomApplication.launch(parseCommandLine([makeTempDir()])) + const window2 = await atomApplication.launch(parseCommandLine([makeTempDir()])) await focusWindow(window2) assert.notEqual(window1, window2) @@ -122,7 +122,7 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) await emitterEventPromise(window1, 'window:locations-opened') await focusWindow(window1) @@ -135,7 +135,7 @@ describe('AtomApplication', function () { // Reuses the window when opening *files*, even if they're in a different directory // Does not change the project paths when doing so. - const reusedWindow = atomApplication.launch(parseCommandLine([existingDirCFilePath])) + const reusedWindow = await atomApplication.launch(parseCommandLine([existingDirCFilePath])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -148,7 +148,7 @@ describe('AtomApplication', function () { assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) // Opens new windows when opening directories - const window2 = atomApplication.launch(parseCommandLine([dirCPath])) + const window2 = await atomApplication.launch(parseCommandLine([dirCPath])) await emitterEventPromise(window2, 'window:locations-opened') assert.notEqual(window2, window1) await focusWindow(window2) @@ -163,7 +163,7 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) await focusWindow(window1) let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -175,7 +175,7 @@ describe('AtomApplication', function () { // When opening *files* with --add, reuses an existing window and adds // parent directory to the project - let reusedWindow = atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) + let reusedWindow = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -189,7 +189,7 @@ describe('AtomApplication', function () { // When opening *directories* with add reuses an existing window and adds // the directory to the project - reusedWindow = atomApplication.launch(parseCommandLine([dirBPath, '-a'])) + reusedWindow = await atomApplication.launch(parseCommandLine([dirBPath, '-a'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) @@ -202,7 +202,7 @@ describe('AtomApplication', function () { const atomApplication = buildAtomApplication() const nonExistentFilePath = path.join(tempDirPath, 'new-file') - const window1 = atomApplication.launch(parseCommandLine([nonExistentFilePath])) + const window1 = await atomApplication.launch(parseCommandLine([nonExistentFilePath])) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { textEditor.insertText('Hello World!') @@ -214,7 +214,7 @@ describe('AtomApplication', function () { await window1.closedPromise // Restore unsaved state when opening the directory itself - const window2 = atomApplication.launch(parseCommandLine([tempDirPath])) + const window2 = await atomApplication.launch(parseCommandLine([tempDirPath])) await window2.loadedPromise const window2Text = await evalInWebContents(window2.browserWindow.webContents, sendBackToMainProcess => { const textEditor = atom.workspace.getActiveTextEditor() @@ -228,7 +228,7 @@ describe('AtomApplication', function () { await window2.closedPromise // Restore unsaved state when opening a path to a non-existent file in the directory - const window3 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) + const window3 = await atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) await window3.loadedPromise const window3Texts = await evalInWebContents(window3.browserWindow.webContents, (sendBackToMainProcess, nonExistentFilePath) => { sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText())) @@ -243,7 +243,7 @@ describe('AtomApplication', function () { fs.mkdirSync(dirBSubdirPath) const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) + const window1 = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) await focusWindow(window1) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) @@ -252,17 +252,17 @@ describe('AtomApplication', function () { it('reuses windows with no project paths to open directories', async () => { const tempDirPath = makeTempDir() const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) - const reusedWindow = atomApplication.launch(parseCommandLine([tempDirPath])) + const reusedWindow = await atomApplication.launch(parseCommandLine([tempDirPath])) assert.equal(reusedWindow, window1) await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length > 0) }) it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async () => { const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) @@ -287,7 +287,7 @@ describe('AtomApplication', function () { season.writeFileSync(configPath, config) const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) // wait a bit just to make sure we don't pass due to querying the render process before it loads @@ -302,7 +302,7 @@ describe('AtomApplication', function () { it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path', async () => { const atomApplication = buildAtomApplication() const newFilePath = path.join(makeTempDir(), 'new-file') - const window = atomApplication.launch(parseCommandLine([newFilePath])) + const window = await atomApplication.launch(parseCommandLine([newFilePath])) await focusWindow(window) const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(editor => { @@ -324,7 +324,7 @@ describe('AtomApplication', function () { atomApplication.config.set('core.disabledPackages', ['fuzzy-finder']) const remotePath = 'remote://server:3437/some/directory/path' - let window = atomApplication.launch(parseCommandLine([remotePath])) + let window = await atomApplication.launch(parseCommandLine([remotePath])) await focusWindow(window) await conditionPromise(async () => (await getProjectDirectories()).length > 0) @@ -350,9 +350,9 @@ describe('AtomApplication', function () { const tempDirPath2 = makeTempDir() const atomApplication1 = buildAtomApplication() - const app1Window1 = atomApplication1.launch(parseCommandLine([tempDirPath1])) + const app1Window1 = await atomApplication1.launch(parseCommandLine([tempDirPath1])) await emitterEventPromise(app1Window1, 'window:locations-opened') - const app1Window2 = atomApplication1.launch(parseCommandLine([tempDirPath2])) + const app1Window2 = await atomApplication1.launch(parseCommandLine([tempDirPath2])) await emitterEventPromise(app1Window2, 'window:locations-opened') await Promise.all([ @@ -361,7 +361,7 @@ describe('AtomApplication', function () { ]) const atomApplication2 = buildAtomApplication() - const [app2Window1, app2Window2] = atomApplication2.launch(parseCommandLine([])) + const [app2Window1, app2Window2] = await atomApplication2.launch(parseCommandLine([])) await Promise.all([ emitterEventPromise(app2Window1, 'window:locations-opened'), emitterEventPromise(app2Window2, 'window:locations-opened') @@ -373,9 +373,9 @@ describe('AtomApplication', function () { it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => { const atomApplication1 = buildAtomApplication() - const app1Window1 = atomApplication1.launch(parseCommandLine([makeTempDir()])) + const app1Window1 = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window1) - const app1Window2 = atomApplication1.launch(parseCommandLine([makeTempDir()])) + const app1Window2 = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window2) const configPath = path.join(process.env.ATOM_HOME, 'config.cson') @@ -385,7 +385,7 @@ describe('AtomApplication', function () { season.writeFileSync(configPath, config) const atomApplication2 = buildAtomApplication() - const app2Window = atomApplication2.launch(parseCommandLine([])) + const app2Window = await atomApplication2.launch(parseCommandLine([])) await focusWindow(app2Window) assert.deepEqual(app2Window.representedDirectoryPaths, []) }) @@ -405,10 +405,10 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened window is closed', async () => { - const window1 = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) + const window1 = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window1) - const [window2] = atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) + const [window2] = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) await focusWindow(window2) assert.deepEqual(killedPids, []) @@ -424,7 +424,7 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened file in an existing window is closed', async () => { - const window = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) + const window = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window) const filePath1 = temp.openSync('test').path @@ -432,7 +432,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath1, 'File 1') fs.writeFileSync(filePath2, 'File 2') - const reusedWindow = atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) + const reusedWindow = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) assert.equal(reusedWindow, window) const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => { @@ -467,11 +467,11 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened directory in an existing window is closed', async () => { - const window = atomApplication.launch(parseCommandLine([])) + const window = await atomApplication.launch(parseCommandLine([])) await focusWindow(window) const dirPath1 = makeTempDir() - const reusedWindow = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) + const reusedWindow = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) assert.equal(reusedWindow, window) assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1]) assert.deepEqual(killedPids, []) @@ -498,7 +498,7 @@ describe('AtomApplication', function () { if (process.platform === 'linux' || process.platform === 'win32') { it('quits the application', async () => { const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const window = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -508,7 +508,7 @@ describe('AtomApplication', function () { } else if (process.platform === 'darwin') { it('leaves the application open', async () => { const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const window = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -524,7 +524,7 @@ describe('AtomApplication', function () { const dirB = makeTempDir() const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([dirA, dirB])) + const window = await atomApplication.launch(parseCommandLine([dirA, dirB])) await emitterEventPromise(window, 'window:locations-opened') await focusWindow(window) assert.deepEqual(await getTreeViewRootDirectories(window), [dirA, dirB]) @@ -539,7 +539,7 @@ describe('AtomApplication', function () { // Window state should be saved when the project folder is removed const atomApplication2 = buildAtomApplication() - const [window2] = atomApplication2.launch(parseCommandLine([])) + const [window2] = await atomApplication2.launch(parseCommandLine([])) await emitterEventPromise(window2, 'window:locations-opened') await focusWindow(window2) assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB]) @@ -556,7 +556,7 @@ describe('AtomApplication', function () { const atomApplication = buildAtomApplication() const launchOptions = parseCommandLine([]) launchOptions.urlsToOpen = ['atom://package-with-url-main/test'] - let windows = atomApplication.launch(launchOptions) + let windows = await atomApplication.launch(launchOptions) await windows[0].loadedPromise let reached = await evalInWebContents(windows[0].browserWindow.webContents, sendBackToMainProcess => { @@ -571,9 +571,9 @@ describe('AtomApplication', function () { const dirBPath = makeTempDir('b') const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath)])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath)])) await focusWindow(window1) - const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath)])) + const window2 = await atomApplication.launch(parseCommandLine([path.join(dirBPath)])) await focusWindow(window2) const fileA = path.join(dirAPath, 'file-a') @@ -597,9 +597,9 @@ describe('AtomApplication', function () { const dirAPath = makeTempDir("a") const dirBPath = makeTempDir("b") const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) await focusWindow(window1) - const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) + const window2 = await atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) await focusWindow(window2) electron.app.quit() await new Promise(process.nextTick) @@ -612,8 +612,8 @@ describe('AtomApplication', function () { it('prevents quitting if user cancels when prompted to save an item', async () => { const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) - const window2 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) + const window2 = await atomApplication.launch(parseCommandLine([])) await Promise.all([window1.loadedPromise, window2.loadedPromise]) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.getActiveTextEditor().insertText('unsaved text') diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index d0db06daa..fcb84aae0 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -169,17 +169,17 @@ class AtomApplication extends EventEmitter { this.disposable.dispose() } - launch (options) { + async launch (options) { if (options.test || options.benchmark || options.benchmarkTest) { return this.openWithOptions(options) } else if ((options.pathsToOpen && options.pathsToOpen.length > 0) || (options.urlsToOpen && options.urlsToOpen.length > 0)) { if (this.config.get('core.restorePreviousWindowsOnStart') === 'always') { - this.loadState(_.deepClone(options)) + await this.loadState(_.deepClone(options)) } return this.openWithOptions(options) } else { - return this.loadState(options) || this.openPath(options) + return (await this.loadState(options)) || this.openPath(options) } } @@ -924,8 +924,8 @@ class AtomApplication extends EventEmitter { } } - loadState (options) { - const states = this.storageFolder.load('application.json') + async loadState (options) { + const states = await this.storageFolder.load('application.json') if ( ['yes', 'always'].includes(this.config.get('core.restorePreviousWindowsOnStart')) && states && states.length > 0 diff --git a/src/storage-folder.js b/src/storage-folder.js index 24fdfd3de..6803adb1e 100644 --- a/src/storage-folder.js +++ b/src/storage-folder.js @@ -29,25 +29,24 @@ class StorageFolder { } load (name) { - if (!this.path) return + return new Promise(resolve => { + if (!this.path) return resolve(null) + const statePath = this.pathForKey(name) + fs.readFile(statePath, 'utf8', (error, stateString) => { + if (error && error.code !== 'ENOENT') { + console.warn(`Error reading state file: ${statePath}`, error.stack, error) + } - const statePath = this.pathForKey(name) + if (!stateString) return resolve(null) - let stateString - try { - stateString = fs.readFileSync(statePath, 'utf8') - } catch (error) { - if (error.code !== 'ENOENT') { - console.warn(`Error reading state file: ${statePath}`, error.stack, error) - } - return null - } - - try { - return JSON.parse(stateString) - } catch (error) { - console.warn(`Error parsing state file: ${statePath}`, error.stack, error) - } + try { + resolve(JSON.parse(stateString)) + } catch (error) { + console.warn(`Error parsing state file: ${statePath}`, error.stack, error) + resolve(null) + } + }) + }) } pathForKey (name) { From fd5f8af292abac8352360f08c6b0ff4140b72445 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Jan 2018 11:52:34 -0800 Subject: [PATCH 11/16] Fix typo in StorageFolder.clear --- src/storage-folder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage-folder.js b/src/storage-folder.js index 6803adb1e..b99f33b81 100644 --- a/src/storage-folder.js +++ b/src/storage-folder.js @@ -14,7 +14,7 @@ class StorageFolder { if (!this.path) return fs.remove(this.path, error => { if (error) console.warn(`Error deleting ${this.path}`, error.stack, error) - reolve() + resolve() }) }) } From 744ae36f31762247bc8fdc9b8d62a6b6be925b70 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Jan 2018 11:56:25 -0800 Subject: [PATCH 12/16] :fire: dead code Signed-off-by: Nathan Sobo --- src/atom-environment.js | 11 +---------- src/storage-folder.js | 10 ---------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/atom-environment.js b/src/atom-environment.js index 73a2c284a..8ac507258 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -9,7 +9,6 @@ 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') @@ -761,10 +760,7 @@ class AtomEnvironment { // Call this method when establishing a real application window. async startEditorWindow () { if (this.getLoadSettings().clearWindowState) { - await Promise.all([ - this.getStorageFolder().clear(), - this.stateStore.clear() - ]) + await this.stateStore.clear() } this.unloaded = false @@ -1266,11 +1262,6 @@ or use Pane::saveItemAs for programmatic saving.`) } } - 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 diff --git a/src/storage-folder.js b/src/storage-folder.js index b99f33b81..4931dab11 100644 --- a/src/storage-folder.js +++ b/src/storage-folder.js @@ -9,16 +9,6 @@ class StorageFolder { } } - clear () { - return new Promise(resolve => { - if (!this.path) return - fs.remove(this.path, error => { - if (error) console.warn(`Error deleting ${this.path}`, error.stack, error) - resolve() - }) - }) - } - store (name, object) { return new Promise((resolve, reject) => { if (!this.path) return resolve() From f7aba5a132a00166cd0019ad3887a30424a43cbd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Jan 2018 12:52:48 -0800 Subject: [PATCH 13/16] Replace loadState with side-effect-free loadPreviousWindowOptions Signed-off-by: Nathan Sobo --- spec/main-process/atom-application.test.js | 4 +- src/main-process/atom-application.js | 64 +++++++++++++--------- src/main-process/atom-window.js | 4 +- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index b67a98ae1..ea38cbbdb 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -408,7 +408,7 @@ describe('AtomApplication', function () { const window1 = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window1) - const [window2] = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) + const window2 = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) await focusWindow(window2) assert.deepEqual(killedPids, []) @@ -539,7 +539,7 @@ describe('AtomApplication', function () { // Window state should be saved when the project folder is removed const atomApplication2 = buildAtomApplication() - const [window2] = await atomApplication2.launch(parseCommandLine([])) + const window2 = await atomApplication2.launch(parseCommandLine([])) await emitterEventPromise(window2, 'window:locations-opened') await focusWindow(window2) assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB]) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index fcb84aae0..f8863bf52 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -170,16 +170,35 @@ class AtomApplication extends EventEmitter { } async launch (options) { + const optionsForWindowsToOpen = [] + + let shouldReopenPreviousWindows = false + if (options.test || options.benchmark || options.benchmarkTest) { - return this.openWithOptions(options) + optionsForWindowsToOpen.push(options) } else if ((options.pathsToOpen && options.pathsToOpen.length > 0) || (options.urlsToOpen && options.urlsToOpen.length > 0)) { - if (this.config.get('core.restorePreviousWindowsOnStart') === 'always') { - await this.loadState(_.deepClone(options)) - } - return this.openWithOptions(options) + optionsForWindowsToOpen.push(options) + shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') === 'always' } else { - return (await this.loadState(options)) || this.openPath(options) + shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') !== 'no' + } + + if (shouldReopenPreviousWindows) { + for (const previousOptions of await this.loadPreviousWindowOptions()) { + optionsForWindowsToOpen.push(Object.assign({}, options, previousOptions)) + } + } + + if (optionsForWindowsToOpen.length === 0) { + optionsForWindowsToOpen.push(options) + } + + const result = optionsForWindowsToOpen.map(options => this.openWithOptions(options)) + if (result.length === 1) { + return result[0] + } else { + return result } } @@ -271,7 +290,7 @@ class AtomApplication extends EventEmitter { return } } - if (!window.isSpec) this.saveState(true) + if (!window.isSpec) this.saveCurrentWindowOptions(true) } // Public: Adds the {AtomWindow} to the global window list. @@ -285,7 +304,7 @@ class AtomApplication extends EventEmitter { if (!window.isSpec) { const focusHandler = () => this.windowStack.touch(window) - const blurHandler = () => this.saveState(false) + const blurHandler = () => this.saveCurrentWindowOptions(false) window.browserWindow.on('focus', focusHandler) window.browserWindow.on('blur', blurHandler) window.browserWindow.once('closed', () => { @@ -578,7 +597,7 @@ class AtomApplication extends EventEmitter { )) this.disposable.add(ipcHelpers.on(ipcMain, 'did-change-paths', () => - this.saveState(false) + this.saveCurrentWindowOptions(false) )) this.disposable.add(this.disableZoomOnDisplayChange()) @@ -909,7 +928,7 @@ class AtomApplication extends EventEmitter { } } - async saveState (allowEmpty = false) { + async saveCurrentWindowOptions (allowEmpty = false) { if (this.quitting) return const states = [] @@ -924,23 +943,18 @@ class AtomApplication extends EventEmitter { } } - async loadState (options) { + async loadPreviousWindowOptions () { const states = await this.storageFolder.load('application.json') - if ( - ['yes', 'always'].includes(this.config.get('core.restorePreviousWindowsOnStart')) && - states && states.length > 0 - ) { - return states.map(state => - this.openWithOptions(Object.assign(options, { - initialPaths: state.initialPaths, - pathsToOpen: state.initialPaths.filter(p => fs.isDirectorySync(p)), - urlsToOpen: [], - devMode: this.devMode, - safeMode: this.safeMode - })) - ) + if (states) { + return states.map(state => ({ + initialPaths: state.initialPaths, + pathsToOpen: state.initialPaths.filter(p => fs.isDirectorySync(p)), + urlsToOpen: [], + devMode: this.devMode, + safeMode: this.safeMode + })) } else { - return null + return [] } } diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index 9ffd1e6c0..51558ad6f 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -163,7 +163,7 @@ class AtomWindow extends EventEmitter { if (!this.atomApplication.quitting && !this.unloading) { event.preventDefault() this.unloading = true - this.atomApplication.saveState(false) + this.atomApplication.saveCurrentWindowOptions(false) if (await this.prepareToUnload()) this.close() } }) @@ -415,7 +415,7 @@ class AtomWindow extends EventEmitter { this.representedDirectoryPaths.sort() this.loadSettings.initialPaths = this.representedDirectoryPaths this.browserWindow.loadSettingsJSON = JSON.stringify(this.loadSettings) - return this.atomApplication.saveState() + return this.atomApplication.saveCurrentWindowOptions() } didClosePathWithWaitSession (path) { From 9f35de441317c7d2dd70cc4d7a72a0abbedbbe36 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Jan 2018 13:01:54 -0800 Subject: [PATCH 14/16] Always return an array from AtomApplication.launch --- spec/main-process/atom-application.test.js | 86 +++++++++++----------- src/main-process/atom-application.js | 7 +- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index ea38cbbdb..16aef8e27 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -49,7 +49,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = await atomApplication.launch(parseCommandLine([filePath + ':3'])) + const [window] = await atomApplication.launch(parseCommandLine([filePath + ':3'])) await focusWindow(window) const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -66,7 +66,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = await atomApplication.launch(parseCommandLine([filePath + ':2:2'])) + const [window] = await atomApplication.launch(parseCommandLine([filePath + ':2:2'])) await focusWindow(window) const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -83,7 +83,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = await atomApplication.launch(parseCommandLine([filePath + ':: '])) + const [window] = await atomApplication.launch(parseCommandLine([filePath + ':: '])) await focusWindow(window) const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -99,11 +99,11 @@ describe('AtomApplication', function () { it('positions new windows at an offset distance from the previous window', async () => { const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([makeTempDir()])) + const [window1] = await atomApplication.launch(parseCommandLine([makeTempDir()])) await focusWindow(window1) window1.browserWindow.setBounds({width: 400, height: 400, x: 0, y: 0}) - const window2 = await atomApplication.launch(parseCommandLine([makeTempDir()])) + const [window2] = await atomApplication.launch(parseCommandLine([makeTempDir()])) await focusWindow(window2) assert.notEqual(window1, window2) @@ -122,7 +122,7 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) await emitterEventPromise(window1, 'window:locations-opened') await focusWindow(window1) @@ -135,7 +135,7 @@ describe('AtomApplication', function () { // Reuses the window when opening *files*, even if they're in a different directory // Does not change the project paths when doing so. - const reusedWindow = await atomApplication.launch(parseCommandLine([existingDirCFilePath])) + const [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -148,7 +148,7 @@ describe('AtomApplication', function () { assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) // Opens new windows when opening directories - const window2 = await atomApplication.launch(parseCommandLine([dirCPath])) + const [window2] = await atomApplication.launch(parseCommandLine([dirCPath])) await emitterEventPromise(window2, 'window:locations-opened') assert.notEqual(window2, window1) await focusWindow(window2) @@ -163,7 +163,7 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) await focusWindow(window1) let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -175,7 +175,7 @@ describe('AtomApplication', function () { // When opening *files* with --add, reuses an existing window and adds // parent directory to the project - let reusedWindow = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) + let [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -189,7 +189,7 @@ describe('AtomApplication', function () { // When opening *directories* with add reuses an existing window and adds // the directory to the project - reusedWindow = await atomApplication.launch(parseCommandLine([dirBPath, '-a'])) + reusedWindow = (await atomApplication.launch(parseCommandLine([dirBPath, '-a'])))[0] assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) @@ -202,7 +202,7 @@ describe('AtomApplication', function () { const atomApplication = buildAtomApplication() const nonExistentFilePath = path.join(tempDirPath, 'new-file') - const window1 = await atomApplication.launch(parseCommandLine([nonExistentFilePath])) + const [window1] = await atomApplication.launch(parseCommandLine([nonExistentFilePath])) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { textEditor.insertText('Hello World!') @@ -214,7 +214,7 @@ describe('AtomApplication', function () { await window1.closedPromise // Restore unsaved state when opening the directory itself - const window2 = await atomApplication.launch(parseCommandLine([tempDirPath])) + const [window2] = await atomApplication.launch(parseCommandLine([tempDirPath])) await window2.loadedPromise const window2Text = await evalInWebContents(window2.browserWindow.webContents, sendBackToMainProcess => { const textEditor = atom.workspace.getActiveTextEditor() @@ -228,7 +228,7 @@ describe('AtomApplication', function () { await window2.closedPromise // Restore unsaved state when opening a path to a non-existent file in the directory - const window3 = await atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) + const [window3] = await atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) await window3.loadedPromise const window3Texts = await evalInWebContents(window3.browserWindow.webContents, (sendBackToMainProcess, nonExistentFilePath) => { sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText())) @@ -243,7 +243,7 @@ describe('AtomApplication', function () { fs.mkdirSync(dirBSubdirPath) const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) + const [window1] = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) await focusWindow(window1) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) @@ -252,17 +252,17 @@ describe('AtomApplication', function () { it('reuses windows with no project paths to open directories', async () => { const tempDirPath = makeTempDir() const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([])) + const [window1] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) - const reusedWindow = await atomApplication.launch(parseCommandLine([tempDirPath])) + const [reusedWindow] = await atomApplication.launch(parseCommandLine([tempDirPath])) assert.equal(reusedWindow, window1) await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length > 0) }) it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async () => { const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([])) + const [window1] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) @@ -287,7 +287,7 @@ describe('AtomApplication', function () { season.writeFileSync(configPath, config) const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([])) + const [window1] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) // wait a bit just to make sure we don't pass due to querying the render process before it loads @@ -302,7 +302,7 @@ describe('AtomApplication', function () { it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path', async () => { const atomApplication = buildAtomApplication() const newFilePath = path.join(makeTempDir(), 'new-file') - const window = await atomApplication.launch(parseCommandLine([newFilePath])) + const [window] = await atomApplication.launch(parseCommandLine([newFilePath])) await focusWindow(window) const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(editor => { @@ -324,7 +324,7 @@ describe('AtomApplication', function () { atomApplication.config.set('core.disabledPackages', ['fuzzy-finder']) const remotePath = 'remote://server:3437/some/directory/path' - let window = await atomApplication.launch(parseCommandLine([remotePath])) + let [window] = await atomApplication.launch(parseCommandLine([remotePath])) await focusWindow(window) await conditionPromise(async () => (await getProjectDirectories()).length > 0) @@ -350,9 +350,9 @@ describe('AtomApplication', function () { const tempDirPath2 = makeTempDir() const atomApplication1 = buildAtomApplication() - const app1Window1 = await atomApplication1.launch(parseCommandLine([tempDirPath1])) + const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1])) await emitterEventPromise(app1Window1, 'window:locations-opened') - const app1Window2 = await atomApplication1.launch(parseCommandLine([tempDirPath2])) + const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2])) await emitterEventPromise(app1Window2, 'window:locations-opened') await Promise.all([ @@ -373,9 +373,9 @@ describe('AtomApplication', function () { it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => { const atomApplication1 = buildAtomApplication() - const app1Window1 = await atomApplication1.launch(parseCommandLine([makeTempDir()])) + const [app1Window1] = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window1) - const app1Window2 = await atomApplication1.launch(parseCommandLine([makeTempDir()])) + const [app1Window2] = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window2) const configPath = path.join(process.env.ATOM_HOME, 'config.cson') @@ -385,7 +385,7 @@ describe('AtomApplication', function () { season.writeFileSync(configPath, config) const atomApplication2 = buildAtomApplication() - const app2Window = await atomApplication2.launch(parseCommandLine([])) + const [app2Window] = await atomApplication2.launch(parseCommandLine([])) await focusWindow(app2Window) assert.deepEqual(app2Window.representedDirectoryPaths, []) }) @@ -405,10 +405,10 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened window is closed', async () => { - const window1 = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) + const [window1] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window1) - const window2 = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) + const [window2] = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) await focusWindow(window2) assert.deepEqual(killedPids, []) @@ -424,7 +424,7 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened file in an existing window is closed', async () => { - const window = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) + const [window] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window) const filePath1 = temp.openSync('test').path @@ -432,7 +432,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath1, 'File 1') fs.writeFileSync(filePath2, 'File 2') - const reusedWindow = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) + const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) assert.equal(reusedWindow, window) const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => { @@ -467,11 +467,11 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened directory in an existing window is closed', async () => { - const window = await atomApplication.launch(parseCommandLine([])) + const [window] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window) const dirPath1 = makeTempDir() - const reusedWindow = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) + const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) assert.equal(reusedWindow, window) assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1]) assert.deepEqual(killedPids, []) @@ -498,7 +498,7 @@ describe('AtomApplication', function () { if (process.platform === 'linux' || process.platform === 'win32') { it('quits the application', async () => { const atomApplication = buildAtomApplication() - const window = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -508,7 +508,7 @@ describe('AtomApplication', function () { } else if (process.platform === 'darwin') { it('leaves the application open', async () => { const atomApplication = buildAtomApplication() - const window = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -524,7 +524,7 @@ describe('AtomApplication', function () { const dirB = makeTempDir() const atomApplication = buildAtomApplication() - const window = await atomApplication.launch(parseCommandLine([dirA, dirB])) + const [window] = await atomApplication.launch(parseCommandLine([dirA, dirB])) await emitterEventPromise(window, 'window:locations-opened') await focusWindow(window) assert.deepEqual(await getTreeViewRootDirectories(window), [dirA, dirB]) @@ -539,7 +539,7 @@ describe('AtomApplication', function () { // Window state should be saved when the project folder is removed const atomApplication2 = buildAtomApplication() - const window2 = await atomApplication2.launch(parseCommandLine([])) + const [window2] = await atomApplication2.launch(parseCommandLine([])) await emitterEventPromise(window2, 'window:locations-opened') await focusWindow(window2) assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB]) @@ -556,7 +556,7 @@ describe('AtomApplication', function () { const atomApplication = buildAtomApplication() const launchOptions = parseCommandLine([]) launchOptions.urlsToOpen = ['atom://package-with-url-main/test'] - let windows = await atomApplication.launch(launchOptions) + let [windows] = await atomApplication.launch(launchOptions) await windows[0].loadedPromise let reached = await evalInWebContents(windows[0].browserWindow.webContents, sendBackToMainProcess => { @@ -571,9 +571,9 @@ describe('AtomApplication', function () { const dirBPath = makeTempDir('b') const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath)])) + const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath)])) await focusWindow(window1) - const window2 = await atomApplication.launch(parseCommandLine([path.join(dirBPath)])) + const [window2] = await atomApplication.launch(parseCommandLine([path.join(dirBPath)])) await focusWindow(window2) const fileA = path.join(dirAPath, 'file-a') @@ -597,9 +597,9 @@ describe('AtomApplication', function () { const dirAPath = makeTempDir("a") const dirBPath = makeTempDir("b") const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) + const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) await focusWindow(window1) - const window2 = await atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) + const [window2] = await atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) await focusWindow(window2) electron.app.quit() await new Promise(process.nextTick) @@ -612,8 +612,8 @@ describe('AtomApplication', function () { it('prevents quitting if user cancels when prompted to save an item', async () => { const atomApplication = buildAtomApplication() - const window1 = await atomApplication.launch(parseCommandLine([])) - const window2 = await atomApplication.launch(parseCommandLine([])) + const [window1] = await atomApplication.launch(parseCommandLine([])) + const [window2] = await atomApplication.launch(parseCommandLine([])) await Promise.all([window1.loadedPromise, window2.loadedPromise]) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.getActiveTextEditor().insertText('unsaved text') diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index f8863bf52..dfe7797c1 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -194,12 +194,7 @@ class AtomApplication extends EventEmitter { optionsForWindowsToOpen.push(options) } - const result = optionsForWindowsToOpen.map(options => this.openWithOptions(options)) - if (result.length === 1) { - return result[0] - } else { - return result - } + return optionsForWindowsToOpen.map(options => this.openWithOptions(options)) } openWithOptions (options) { From 571db7848b4522fd1f07a30c85cea60b17f9276b Mon Sep 17 00:00:00 2001 From: Bryant Ung Date: Fri, 19 Jan 2018 14:42:14 -0800 Subject: [PATCH 15/16] :arrow_up: bracket-matcher --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 974a9ab95..8b053850b 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "autosave": "0.24.6", "background-tips": "0.27.1", "bookmarks": "0.45.1", - "bracket-matcher": "0.89.0", + "bracket-matcher": "0.89.1", "command-palette": "0.43.0", "dalek": "0.2.1", "deprecation-cop": "0.56.9", From 8e74d06f647ecdc37d1f6231fa76f9e8e23408a0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 20 Jan 2018 11:09:26 +0100 Subject: [PATCH 16/16] Fix tests --- spec/text-editor-component-spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index c744ce795..3432a0fea 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -104,7 +104,7 @@ describe('TextEditorComponent', () => { { expect(editor.getApproximateLongestScreenRow()).toBe(3) - const expectedWidth = Math.round( + const expectedWidth = Math.ceil( component.pixelPositionForScreenPosition(Point(3, Infinity)).left + component.getBaseCharacterWidth() ) @@ -121,7 +121,7 @@ describe('TextEditorComponent', () => { // Capture the width of the lines before requesting the width of // longest line, because making that request forces a DOM update const actualWidth = element.querySelector('.lines').style.width - const expectedWidth = Math.round( + const expectedWidth = Math.ceil( component.pixelPositionForScreenPosition(Point(6, Infinity)).left + component.getBaseCharacterWidth() ) @@ -3980,7 +3980,7 @@ describe('TextEditorComponent', () => { // Capture the width of the lines before requesting the width of // longest line, because making that request forces a DOM update const actualWidth = element.querySelector('.lines').style.width - const expectedWidth = Math.round( + const expectedWidth = Math.ceil( component.pixelPositionForScreenPosition(Point(3, Infinity)).left + component.getBaseCharacterWidth() )