From 1d16d687bb09589ec2ae54ec73af1740fd4003eb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Nov 2016 14:33:38 +0100 Subject: [PATCH 001/194] Start on generating a startup snapshot script via electron-link --- script/build | 2 ++ script/lib/generate-startup-snapshot.js | 48 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 script/lib/generate-startup-snapshot.js diff --git a/script/build b/script/build index 680666b05..3a5a545d4 100755 --- a/script/build +++ b/script/build @@ -35,6 +35,7 @@ const dumpSymbols = require('./lib/dump-symbols') const generateAPIDocs = require('./lib/generate-api-docs') const generateMetadata = require('./lib/generate-metadata') const generateModuleCache = require('./lib/generate-module-cache') +const generateStartupSnapshot = require('./lib/generate-startup-snapshot') const installApplication = require('./lib/install-application') const packageApplication = require('./lib/package-application') const prebuildLessCache = require('./lib/prebuild-less-cache') @@ -57,6 +58,7 @@ transpilePegJsPaths() generateModuleCache() prebuildLessCache() generateMetadata() +generateStartupSnapshot() generateAPIDocs() downloadChromedriver() dumpSymbols() diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js new file mode 100644 index 000000000..63335c90a --- /dev/null +++ b/script/lib/generate-startup-snapshot.js @@ -0,0 +1,48 @@ +const fs = require('fs') +const path = require('path') +const electronLink = require('electron-link') +const CONFIG = require('../config') + +module.exports = function () { + const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js') + console.log(`Generating snapshot script at "${snapshotScriptPath}"`) + const coreModules = new Set([ + 'path', 'electron', 'module', 'fs', 'child_process', 'crypto', 'url', + 'atom', 'vm', 'events', 'os', 'assert', 'buffer', 'tty', 'net', 'constants', + 'http', 'https' + ]) + const snapshotScriptContent = electronLink({ + baseDirPath: CONFIG.intermediateAppPath, + mainPath: path.join(CONFIG.intermediateAppPath, 'src', 'initialize-application-window.js'), + shouldExcludeModule: (modulePath) => { + const relativePath = path.relative(CONFIG.intermediateAppPath, modulePath) + return ( + modulePath.endsWith('.node') || modulePath === 'buffer-offset-index' || + coreModules.has(modulePath) || + (relativePath.startsWith('src' + path.sep) && relativePath.endsWith('-element.js')) || + relativePath == path.join('exports', 'atom.js') || + relativePath == path.join('src', 'config-schema.js') || + relativePath == path.join('src', 'electron-shims.js') || + relativePath == path.join('src', 'module-cache.js') || + relativePath == path.join('src', 'safe-clipboard.js') || + relativePath == path.join('node_modules', 'atom-keymap', 'lib', 'command-event.js') || + relativePath == path.join('node_modules', 'babel-core', 'index.js') || + relativePath == path.join('node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('node_modules', 'git-utils', 'lib', 'git.js') || + relativePath == path.join('node_modules', 'less', 'lib', 'less', 'fs.js') || + relativePath == path.join('node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || + relativePath == path.join('node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || + relativePath == path.join('node_modules', 'mime', 'mime.js') || + relativePath == path.join('node_modules', 'oniguruma', 'lib', 'oniguruma.js') || + relativePath == path.join('node_modules', 'pathwatcher', 'lib', 'main.js') || + relativePath == path.join('node_modules', 'request', 'request.js') || + relativePath == path.join('node_modules', 'resolve', 'index.js') || + relativePath == path.join('node_modules', 'resolve', 'lib', 'core.js') || + relativePath == path.join('node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') + ) + } + }) + fs.writeFileSync(snapshotScriptPath, snapshotScriptContent) +} From 3514adfe4ed8dbce812aae3832ee9f453817cd22 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Nov 2016 14:34:00 +0100 Subject: [PATCH 002/194] Import Point and Range from text-buffer --- src/text-editor-registry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-registry.js b/src/text-editor-registry.js index b29a3887c..2c66ae90e 100644 --- a/src/text-editor-registry.js +++ b/src/text-editor-registry.js @@ -1,7 +1,7 @@ /** @babel */ import {Emitter, Disposable, CompositeDisposable} from 'event-kit' -import {Point, Range} from 'atom' +import {Point, Range} from 'text-buffer' import TextEditor from './text-editor' import ScopeDescriptor from './scope-descriptor' From 6ccdc421cbb6b167f6cc50cf23a5f5a3645a812a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Nov 2016 15:37:44 +0100 Subject: [PATCH 003/194] Use a different entry script to generate the startup snapshot --- script/lib/generate-startup-snapshot.js | 2 +- src/initialize-snapshot.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/initialize-snapshot.js diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 63335c90a..86d1585c1 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -13,7 +13,7 @@ module.exports = function () { ]) const snapshotScriptContent = electronLink({ baseDirPath: CONFIG.intermediateAppPath, - mainPath: path.join(CONFIG.intermediateAppPath, 'src', 'initialize-application-window.js'), + mainPath: path.join(CONFIG.intermediateAppPath, 'src', 'initialize-snapshot.js'), shouldExcludeModule: (modulePath) => { const relativePath = path.relative(CONFIG.intermediateAppPath, modulePath) return ( diff --git a/src/initialize-snapshot.js b/src/initialize-snapshot.js new file mode 100644 index 000000000..47bd5afa0 --- /dev/null +++ b/src/initialize-snapshot.js @@ -0,0 +1,5 @@ +require('./update-process-env') +require('./window') +require('./atom-environment') +require('./application-delegate') +require('./clipboard') From 77b8089e3abefc245f0ce83fc8da21c8341d9035 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Nov 2016 16:39:58 +0100 Subject: [PATCH 004/194] Use the snapshot result to require core modules --- script/build | 2 +- script/lib/generate-startup-snapshot.js | 54 ++++++------ src/atom-environment.coffee | 8 +- src/initialize-application-window.coffee | 16 ++-- src/main-process/main.js | 4 + src/task.coffee | 4 +- static/index.js | 102 ++++++----------------- 7 files changed, 70 insertions(+), 120 deletions(-) diff --git a/script/build b/script/build index 3a5a545d4..b4bfee6f9 100755 --- a/script/build +++ b/script/build @@ -50,6 +50,7 @@ process.on('unhandledRejection', function (e) { }) cleanOutputDirectory() +downloadChromedriver() copyAssets() transpileBabelPaths() transpileCoffeeScriptPaths() @@ -60,7 +61,6 @@ prebuildLessCache() generateMetadata() generateStartupSnapshot() generateAPIDocs() -downloadChromedriver() dumpSymbols() .then(packageApplication) .then(packagedAppPath => { diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 86d1585c1..f8a0b5e4d 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -11,36 +11,40 @@ module.exports = function () { 'atom', 'vm', 'events', 'os', 'assert', 'buffer', 'tty', 'net', 'constants', 'http', 'https' ]) + const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') const snapshotScriptContent = electronLink({ - baseDirPath: CONFIG.intermediateAppPath, - mainPath: path.join(CONFIG.intermediateAppPath, 'src', 'initialize-snapshot.js'), + baseDirPath, + mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'), shouldExcludeModule: (modulePath) => { - const relativePath = path.relative(CONFIG.intermediateAppPath, modulePath) + const relativePath = path.relative(baseDirPath, modulePath) return ( modulePath.endsWith('.node') || modulePath === 'buffer-offset-index' || coreModules.has(modulePath) || - (relativePath.startsWith('src' + path.sep) && relativePath.endsWith('-element.js')) || - relativePath == path.join('exports', 'atom.js') || - relativePath == path.join('src', 'config-schema.js') || - relativePath == path.join('src', 'electron-shims.js') || - relativePath == path.join('src', 'module-cache.js') || - relativePath == path.join('src', 'safe-clipboard.js') || - relativePath == path.join('node_modules', 'atom-keymap', 'lib', 'command-event.js') || - relativePath == path.join('node_modules', 'babel-core', 'index.js') || - relativePath == path.join('node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || - relativePath == path.join('node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || - relativePath == path.join('node_modules', 'fs-plus', 'lib', 'fs-plus.js') || - relativePath == path.join('node_modules', 'git-utils', 'lib', 'git.js') || - relativePath == path.join('node_modules', 'less', 'lib', 'less', 'fs.js') || - relativePath == path.join('node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || - relativePath == path.join('node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || - relativePath == path.join('node_modules', 'mime', 'mime.js') || - relativePath == path.join('node_modules', 'oniguruma', 'lib', 'oniguruma.js') || - relativePath == path.join('node_modules', 'pathwatcher', 'lib', 'main.js') || - relativePath == path.join('node_modules', 'request', 'request.js') || - relativePath == path.join('node_modules', 'resolve', 'index.js') || - relativePath == path.join('node_modules', 'resolve', 'lib', 'core.js') || - relativePath == path.join('node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') + (relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) || + relativePath == path.join('..', 'exports', 'atom.js') || + relativePath == path.join('..', 'src', 'config-schema.js') || + relativePath == path.join('..', 'src', 'electron-shims.js') || + relativePath == path.join('..', 'src', 'module-cache.js') || + relativePath == path.join('..', 'src', 'safe-clipboard.js') || + relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || + relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'helpers.js') || + relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || + relativePath == path.join('..', 'node_modules', 'clear-cut', 'index.js') || + relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || + relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'index.js') || + relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || + relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || + relativePath == path.join('..', 'node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || + relativePath == path.join('..', 'node_modules', 'mime', 'mime.js') || + relativePath == path.join('..', 'node_modules', 'oniguruma', 'lib', 'oniguruma.js') || + relativePath == path.join('..', 'node_modules', 'pathwatcher', 'lib', 'main.js') || + relativePath == path.join('..', 'node_modules', 'request', 'request.js') || + relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || + relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || + relativePath == path.join('..', 'node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') ) } }) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 32cfa331d..09c85afca 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -132,7 +132,7 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@blobStore, @applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params @unloaded = false @loadTime = null @@ -738,7 +738,6 @@ class AtomEnvironment extends Model @storeWindowBackground() @packages.deactivatePackages() - @saveBlobStoreSync() @unloaded = true openInitialEmptyEditorIfNecessary: -> @@ -867,11 +866,6 @@ class AtomEnvironment extends Model showSaveDialogSync: (options={}) -> @applicationDelegate.showSaveDialog(options) - saveBlobStoreSync: -> - return unless @enablePersistence - - @blobStore.save() - saveState: (options) -> new Promise (resolve, reject) => if @enablePersistence and @project diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 7d3a23db7..38133e414 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -1,5 +1,10 @@ +AtomEnvironment = require './atom-environment' +ApplicationDelegate = require './application-delegate' +Clipboard = require './clipboard' +TextEditor = require './text-editor' + # Like sands through the hourglass, so are the days of our lives. -module.exports = ({blobStore}) -> +module.exports = -> {updateProcessEnv} = require('./update-process-env') path = require 'path' require './window' @@ -16,23 +21,18 @@ module.exports = ({blobStore}) -> # Make React faster process.env.NODE_ENV ?= 'production' unless devMode - AtomEnvironment = require './atom-environment' - ApplicationDelegate = require './application-delegate' - Clipboard = require './clipboard' - TextEditor = require './text-editor' - clipboard = new Clipboard TextEditor.setClipboard(clipboard) window.atom = new AtomEnvironment({ - window, document, clipboard, blobStore, + window, document, clipboard, applicationDelegate: new ApplicationDelegate, configDirPath: process.env.ATOM_HOME, enablePersistence: true, env: process.env }) - atom.startEditorWindow().then -> + window.atom.startEditorWindow().then -> # Workaround for focus getting cleared upon window creation windowFocused = -> window.removeEventListener('focus', windowFocused) diff --git a/src/main-process/main.js b/src/main-process/main.js index 7ccd1a6c3..e77d1930f 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -1,3 +1,7 @@ +if (typeof snapshotResult !== 'undefined') { + snapshotResult.setGlobals(global, process, {}, require) +} + const startTime = Date.now() const electron = require('electron') diff --git a/src/task.coffee b/src/task.coffee index fc8c5bd6b..8555b6554 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -70,7 +70,9 @@ class Task compileCachePath = require('./compile-cache').getCacheDirectory() taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');" bootstrap = """ - #{compileCacheRequire}.setCacheDirectory('#{compileCachePath}'); + snapshotResult.setGlobals(global, process, global, require) + CompileCache = snapshotResult.customRequire('../src/compile-cache.js') + CompileCache.setCacheDirectory('#{compileCachePath}'); #{taskBootstrapRequire} """ bootstrap = bootstrap.replace(/\\/g, "\\\\") diff --git a/static/index.js b/static/index.js index b76477e03..0f900d213 100644 --- a/static/index.js +++ b/static/index.js @@ -1,92 +1,41 @@ (function () { - var path = require('path') - var FileSystemBlobStore = require('../src/file-system-blob-store') - var NativeCompileCache = require('../src/native-compile-cache') - - var loadSettings = null - var loadSettingsError = null - var blobStore = null - - window.onload = function () { - try { - var startTime = Date.now() + let loadSettings + const Module = require('module') + const Path = require('path') + const vm = require('vm') + if (typeof snapshotResult !== 'undefined') { + window.onload = function () { + process.resourcesPath = Path.normalize(process.resourcesPath) process.on('unhandledRejection', function (error, promise) { console.error('Unhandled promise rejection %o with error: %o', promise, error) }) - blobStore = FileSystemBlobStore.load( - path.join(process.env.ATOM_HOME, 'blob-store/') - ) - NativeCompileCache.setCacheStore(blobStore) - NativeCompileCache.setV8Version(process.versions.v8) - NativeCompileCache.install() + parseLoadSettings() + setupAtomHome() + require('../src/crash-reporter-start')({_version: loadSettings.appVersion}) - // Normalize to make sure drive letter case is consistent on Windows - process.resourcesPath = path.normalize(process.resourcesPath) - - if (loadSettingsError) { - throw loadSettingsError + const entryPointDirPath = __dirname + Module.prototype.require = function (path) { + const absoluteFilePath = Module._resolveFilename(path, this, false) + const relativeFilePath = Path.relative(entryPointDirPath, absoluteFilePath) + const cachedModule = snapshotResult.customRequire.cache[relativeFilePath] + return cachedModule ? cachedModule : Module._load(path, this, false) } - var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep) + snapshotResult.setGlobals(global, process, window, require) - if (devMode) { - setupDeprecatedPackages() - } + const CSON = snapshotResult.customRequire("../node_modules/season/lib/cson.js") + CSON.setCacheDir(Path.join(process.env.ATOM_HOME, 'compile-cache', 'cson')) - if (loadSettings.profileStartup) { - profileStartup(loadSettings, Date.now() - startTime) - } else { - setupWindow(loadSettings) - setLoadTime(Date.now() - startTime) - } - } catch (error) { - handleSetupError(error) + const CompileCache = snapshotResult.customRequire('../src/compile-cache.js') + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + + const initialize = snapshotResult.customRequire('../src/initialize-application-window.js') + initialize() } } - function setLoadTime (loadTime) { - if (global.atom) { - global.atom.loadTime = loadTime - } - } - - function handleSetupError (error) { - var currentWindow = require('electron').remote.getCurrentWindow() - currentWindow.setSize(800, 600) - currentWindow.center() - currentWindow.show() - currentWindow.openDevTools() - console.error(error.stack || error) - } - - function setupWindow (loadSettings) { - var CompileCache = require('../src/compile-cache') - CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - - var ModuleCache = require('../src/module-cache') - ModuleCache.register(loadSettings) - ModuleCache.add(loadSettings.resourcePath) - - // By explicitly passing the app version here, we could save the call - // of "require('remote').require('app').getVersion()". - var startCrashReporter = require('../src/crash-reporter-start') - startCrashReporter({_version: loadSettings.appVersion}) - - setupVmCompatibility() - setupCsonCache(CompileCache.getCacheDirectory()) - - var initialize = require(loadSettings.windowInitializationScript) - return initialize({blobStore: blobStore}).then(function () { - require('electron').ipcRenderer.send('window-command', 'window:loaded') - }) - } - - function setupCsonCache (cacheDir) { - require('season').setCacheDir(path.join(cacheDir, 'cson')) - } - function setupVmCompatibility () { var vm = require('vm') if (!vm.Script.createContext) { @@ -149,7 +98,4 @@ process.env.ATOM_HOME = loadSettings.atomHome } } - - parseLoadSettings() - setupAtomHome() })() From 72b017b01a6d83a8148dabc64a036560a088bf13 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 15 Nov 2016 18:03:00 +0100 Subject: [PATCH 005/194] WIP: Start on requiring packages --- script/lib/generate-startup-snapshot.js | 13 +++- src/initialize-application-window.coffee | 78 ++++++++++++++++++++++++ src/lines-component.coffee | 40 ++++++------ static/index.js | 2 +- 4 files changed, 110 insertions(+), 23 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index f8a0b5e4d..d7da7d8de 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -9,7 +9,7 @@ module.exports = function () { const coreModules = new Set([ 'path', 'electron', 'module', 'fs', 'child_process', 'crypto', 'url', 'atom', 'vm', 'events', 'os', 'assert', 'buffer', 'tty', 'net', 'constants', - 'http', 'https' + 'http', 'https', 'shell', 'querystring', 'zlib', 'stream', 'WNdb', 'lapack' ]) const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') const snapshotScriptContent = electronLink({ @@ -33,7 +33,10 @@ module.exports = function () { relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || + relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || + relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || @@ -44,7 +47,13 @@ module.exports = function () { relativePath == path.join('..', 'node_modules', 'request', 'request.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || - relativePath == path.join('..', 'node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || + relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || + relativePath == path.join('..', 'node_modules', 'styleguide', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'tar', 'tar.js') || + relativePath == path.join('..', 'node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') || + relativePath == path.join('..', 'node_modules', 'temp', 'lib', 'temp.js') || + relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ) } }) diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 38133e414..3254e5ca5 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -2,6 +2,84 @@ AtomEnvironment = require './atom-environment' ApplicationDelegate = require './application-delegate' Clipboard = require './clipboard' TextEditor = require './text-editor' +TextEditorComponent = require './text-editor-component' +require('about') +require('archive-view') +require('autocomplete-atom-api') +require('autocomplete-css') +require('autocomplete-html') +require('autocomplete-plus') +require('autocomplete-snippets') +require('autoflow') +require('autosave') +require('background-tips') +require('bookmarks') +require('bracket-matcher') +require('command-palette') +require('deprecation-cop') +require('dev-live-reload') +require('encoding-selector') +require('exception-reporting') +require('find-and-replace') +require('fuzzy-finder') +require('git-diff') +require('go-to-line') +require('grammar-selector') +require('image-view') +require('incompatible-packages') +require('keybinding-resolver') +require('line-ending-selector') +require('link') +require('markdown-preview') +require('metrics') +require('notifications') +require('open-on-github') +require('package-generator') +require('settings-view') +require('snippets') +require('spell-check') +# require('status-bar') +require('styleguide') +require('symbols-view') +require('tabs') +require('timecop') +require('tree-view') +# require('update-package-dependencies') +require('welcome') +require('whitespace') +# require('wrap-guide') +# require('language-c') +# require('language-clojure') +# require('language-coffee-script') +# require('language-csharp') +# require('language-css') +# require('language-gfm') +# require('language-git') +# require('language-go') +# require('language-html') +# require('language-hyperlink') +# require('language-java') +# require('language-javascript') +# require('language-json') +# require('language-less') +# require('language-make') +# require('language-mustache') +# require('language-objective-c') +# require('language-perl') +# require('language-php') +# require('language-property-list') +# require('language-python') +# require('language-ruby') +# require('language-ruby-on-rails') +# require('language-sass') +# require('language-shellscript') +# require('language-source') +# require('language-sql') +# require('language-text') +# require('language-todo') +# require('language-toml') +# require('language-xml') +# require('language-yaml') # Like sands through the hourglass, so are the days of our lives. module.exports = -> diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 6c9271179..02d396021 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -2,24 +2,24 @@ CursorsComponent = require './cursors-component' LinesTileComponent = require './lines-tile-component' TiledComponent = require './tiled-component' -DummyLineNode = document.createElement('div') -DummyLineNode.className = 'line' -DummyLineNode.style.position = 'absolute' -DummyLineNode.style.visibility = 'hidden' -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.children[0].textContent = 'x' -DummyLineNode.children[1].textContent = '我' -DummyLineNode.children[2].textContent = 'ハ' -DummyLineNode.children[3].textContent = '세' - module.exports = class LinesComponent extends TiledComponent placeholderTextDiv: null constructor: ({@views, @presenter, @domElementPool, @assert}) -> + @DummyLineNode = document.createElement('div') + @DummyLineNode.className = 'line' + @DummyLineNode.style.position = 'absolute' + @DummyLineNode.style.visibility = 'hidden' + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.children[0].textContent = 'x' + @DummyLineNode.children[1].textContent = '我' + @DummyLineNode.children[2].textContent = 'ハ' + @DummyLineNode.children[3].textContent = '세' + @domNode = document.createElement('div') @domNode.classList.add('lines') @tilesNode = document.createElement("div") @@ -78,15 +78,15 @@ class LinesComponent extends TiledComponent getTilesNode: -> @tilesNode measureLineHeightAndDefaultCharWidth: -> - @domNode.appendChild(DummyLineNode) + @domNode.appendChild(@DummyLineNode) - lineHeightInPixels = DummyLineNode.getBoundingClientRect().height - defaultCharWidth = DummyLineNode.children[0].getBoundingClientRect().width - doubleWidthCharWidth = DummyLineNode.children[1].getBoundingClientRect().width - halfWidthCharWidth = DummyLineNode.children[2].getBoundingClientRect().width - koreanCharWidth = DummyLineNode.children[3].getBoundingClientRect().width + lineHeightInPixels = @DummyLineNode.getBoundingClientRect().height + defaultCharWidth = @DummyLineNode.children[0].getBoundingClientRect().width + doubleWidthCharWidth = @DummyLineNode.children[1].getBoundingClientRect().width + halfWidthCharWidth = @DummyLineNode.children[2].getBoundingClientRect().width + koreanCharWidth = @DummyLineNode.children[3].getBoundingClientRect().width - @domNode.removeChild(DummyLineNode) + @domNode.removeChild(@DummyLineNode) @presenter.setLineHeight(lineHeightInPixels) @presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) diff --git a/static/index.js b/static/index.js index 0f900d213..889b7f98b 100644 --- a/static/index.js +++ b/static/index.js @@ -23,7 +23,7 @@ return cachedModule ? cachedModule : Module._load(path, this, false) } - snapshotResult.setGlobals(global, process, window, require) + snapshotResult.setGlobals(global, process, window, document, require) const CSON = snapshotResult.customRequire("../node_modules/season/lib/cson.js") CSON.setCacheDir(Path.join(process.env.ATOM_HOME, 'compile-cache', 'cson')) From c061f3604007b5210c37549aa7b736e5621a01d0 Mon Sep 17 00:00:00 2001 From: Bryant Ung Date: Fri, 17 Feb 2017 10:39:53 -0800 Subject: [PATCH 006/194] Updating requires for linux to support 32bit build --- resources/linux/redhat/atom.spec.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/linux/redhat/atom.spec.in b/resources/linux/redhat/atom.spec.in index 82a5fbf9a..306f1029e 100644 --- a/resources/linux/redhat/atom.spec.in +++ b/resources/linux/redhat/atom.spec.in @@ -7,7 +7,11 @@ URL: https://atom.io/ AutoReqProv: no # Avoid libchromiumcontent.so missing dependency Prefix: <%= installDir %> +%ifarch i386 i486 i586 i686 +Requires: lsb-core-noarch, libXss.so.1 +%else Requires: lsb-core-noarch, libXss.so.1()(64bit) +%endif %description <%= description %> From e624f24b6d8e314938216503aac35120e6f6a411 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 23 Feb 2017 17:04:43 +0100 Subject: [PATCH 007/194] WIP --- script/build | 99 ++++++++++++------------ script/lib/generate-startup-snapshot.js | 13 +++- script/lib/transpile-babel-paths.js | 1 + src/coffee-script.js | 3 +- src/initialize-application-window.coffee | 38 +-------- static/babelrc.json | 1 - 6 files changed, 66 insertions(+), 89 deletions(-) diff --git a/script/build b/script/build index cfcbc4a93..9afc5b378 100755 --- a/script/build +++ b/script/build @@ -4,7 +4,7 @@ // Run bootstrap first to ensure all the dependencies used later in this script // are installed. -require('./bootstrap') +// require('./bootstrap') // Needed so we can require src/module-cache.coffee during generateModuleCache require('coffee-script/register') @@ -51,58 +51,57 @@ process.on('unhandledRejection', function (e) { checkChromedriverVersion() cleanOutputDirectory() -downloadChromedriver() copyAssets() transpileBabelPaths() transpileCoffeeScriptPaths() transpileCsonPaths() transpilePegJsPaths() -generateModuleCache() -prebuildLessCache() -generateMetadata() +// generateModuleCache() +// prebuildLessCache() +// generateMetadata() generateStartupSnapshot() -generateAPIDocs() -dumpSymbols() - .then(packageApplication) - .then(packagedAppPath => { - if (process.platform === 'darwin') { - if (argv.codeSign) { - codeSignOnMac(packagedAppPath) - } else { - console.log('Skipping code-signing. Specify the --code-sign option to perform code-signing'.gray) - } - } else if (process.platform === 'win32') { - if (argv.createWindowsInstaller) { - return createWindowsInstaller(packagedAppPath, argv.codeSign).then(() => packagedAppPath) - } - else { - console.log('Skipping creating installer. Specify the --create-windows-installer option to create a Squirrel-based Windows installer. Code-signing was skipped too.'.gray) - } - } else if (process.platform === 'linux') { - if (argv.createDebianPackage) { - createDebianPackage(packagedAppPath) - } else { - console.log('Skipping creating debian package. Specify the --create-debian-package option to create it.'.gray) - } - - if (argv.createRpmPackage) { - createRpmPackage(packagedAppPath) - } else { - console.log('Skipping creating rpm package. Specify the --create-rpm-package option to create it.'.gray) - } - } - - return Promise.resolve(packagedAppPath) - }).then(packagedAppPath => { - if (argv.compressArtifacts) { - compressArtifacts(packagedAppPath) - } else { - console.log('Skipping artifacts compression. Specify the --compress-artifacts option to compress Atom binaries (and symbols on macOS)'.gray) - } - - if (argv.install) { - installApplication(packagedAppPath) - } else { - console.log('Skipping installation. Specify the --install option to install Atom'.gray) - } - }) +// generateAPIDocs() +// dumpSymbols() +// .then(packageApplication) +// .then(packagedAppPath => { +// if (process.platform === 'darwin') { +// if (argv.codeSign) { +// codeSignOnMac(packagedAppPath) +// } else { +// console.log('Skipping code-signing. Specify the --code-sign option to perform code-signing'.gray) +// } +// } else if (process.platform === 'win32') { +// if (argv.createWindowsInstaller) { +// return createWindowsInstaller(packagedAppPath, argv.codeSign).then(() => packagedAppPath) +// } +// else { +// console.log('Skipping creating installer. Specify the --create-windows-installer option to create a Squirrel-based Windows installer. Code-signing was skipped too.'.gray) +// } +// } else if (process.platform === 'linux') { +// if (argv.createDebianPackage) { +// createDebianPackage(packagedAppPath) +// } else { +// console.log('Skipping creating debian package. Specify the --create-debian-package option to create it.'.gray) +// } +// +// if (argv.createRpmPackage) { +// createRpmPackage(packagedAppPath) +// } else { +// console.log('Skipping creating rpm package. Specify the --create-rpm-package option to create it.'.gray) +// } +// } +// +// return Promise.resolve(packagedAppPath) +// }).then(packagedAppPath => { +// if (argv.compressArtifacts) { +// compressArtifacts(packagedAppPath) +// } else { +// console.log('Skipping artifacts compression. Specify the --compress-artifacts option to compress Atom binaries (and symbols on macOS)'.gray) +// } +// +// if (argv.install) { +// installApplication(packagedAppPath) +// } else { +// console.log('Skipping installation. Specify the --install option to install Atom'.gray) +// } +// }) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index d7da7d8de..0824b7e12 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -9,7 +9,7 @@ module.exports = function () { const coreModules = new Set([ 'path', 'electron', 'module', 'fs', 'child_process', 'crypto', 'url', 'atom', 'vm', 'events', 'os', 'assert', 'buffer', 'tty', 'net', 'constants', - 'http', 'https', 'shell', 'querystring', 'zlib', 'stream', 'WNdb', 'lapack' + 'http', 'https', 'shell', 'querystring', 'zlib', 'stream', 'WNdb', 'lapack', 'remote' ]) const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') const snapshotScriptContent = electronLink({ @@ -26,14 +26,21 @@ module.exports = function () { relativePath == path.join('..', 'src', 'electron-shims.js') || relativePath == path.join('..', 'src', 'module-cache.js') || relativePath == path.join('..', 'src', 'safe-clipboard.js') || + relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || + relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'temp', 'lib', 'temp.js') || + relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'tar', 'tar.js') || relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'helpers.js') || relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || relativePath == path.join('..', 'node_modules', 'clear-cut', 'index.js') || relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'find-and-replace', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || + relativePath == path.join('..', 'node_modules', 'exception-reporting', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'symbols-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || @@ -41,6 +48,9 @@ module.exports = function () { relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || relativePath == path.join('..', 'node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || + relativePath == path.join('..', 'node_modules', 'metrics', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'notifications', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'superstring', 'index.js') || relativePath == path.join('..', 'node_modules', 'mime', 'mime.js') || relativePath == path.join('..', 'node_modules', 'oniguruma', 'lib', 'oniguruma.js') || relativePath == path.join('..', 'node_modules', 'pathwatcher', 'lib', 'main.js') || @@ -51,6 +61,7 @@ module.exports = function () { relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || relativePath == path.join('..', 'node_modules', 'styleguide', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'tar', 'tar.js') || + relativePath == path.join('..', 'node_modules', 'tree-view', 'node_modules', 'pathwatcher', 'lib', 'main.js') || relativePath == path.join('..', 'node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') || relativePath == path.join('..', 'node_modules', 'temp', 'lib', 'temp.js') || relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index 3c440a4bd..0a6a15f93 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -28,6 +28,7 @@ function getPathsToTranspile () { paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, '**', 'atom-select-list', 'src', 'select-list-view.js'))) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { paths = paths.concat(glob.sync( path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.js'), diff --git a/src/coffee-script.js b/src/coffee-script.js index 0437e787f..45f55e219 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -35,8 +35,7 @@ exports.compile = function (sourceCode, filePath) { var output = CoffeeScript.compile(sourceCode, { filename: filePath, - sourceFiles: [filePath], - inlineMap: true + sourceFiles: [filePath] }) // Strip sourceURL from output so there wouldn't be duplicate entries diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 9eda1ef3b..00aad7714 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -3,6 +3,7 @@ ApplicationDelegate = require './application-delegate' Clipboard = require './clipboard' TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' + require('about') require('archive-view') require('autocomplete-atom-api') @@ -38,48 +39,15 @@ require('package-generator') require('settings-view') require('snippets') require('spell-check') -# require('status-bar') +require('status-bar') require('styleguide') require('symbols-view') require('tabs') require('timecop') require('tree-view') -# require('update-package-dependencies') +require('update-package-dependencies') require('welcome') require('whitespace') -# require('wrap-guide') -# require('language-c') -# require('language-clojure') -# require('language-coffee-script') -# require('language-csharp') -# require('language-css') -# require('language-gfm') -# require('language-git') -# require('language-go') -# require('language-html') -# require('language-hyperlink') -# require('language-java') -# require('language-javascript') -# require('language-json') -# require('language-less') -# require('language-make') -# require('language-mustache') -# require('language-objective-c') -# require('language-perl') -# require('language-php') -# require('language-property-list') -# require('language-python') -# require('language-ruby') -# require('language-ruby-on-rails') -# require('language-sass') -# require('language-shellscript') -# require('language-source') -# require('language-sql') -# require('language-text') -# require('language-todo') -# require('language-toml') -# require('language-xml') -# require('language-yaml') # Like sands through the hourglass, so are the days of our lives. module.exports = -> diff --git a/static/babelrc.json b/static/babelrc.json index 11474dd8d..5d887251a 100644 --- a/static/babelrc.json +++ b/static/babelrc.json @@ -1,5 +1,4 @@ { - "sourceMap": "inline", "plugins": [ ["add-module-exports", {}], ["transform-async-to-generator", {}], From c2f02e16fe38217caa103c32e1d68e375d6d4d53 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Feb 2017 08:13:26 +0100 Subject: [PATCH 008/194] Verify that snapshot can be executed via `mksnapshot` --- script/lib/generate-startup-snapshot.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 0824b7e12..286643fa7 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -2,20 +2,26 @@ const fs = require('fs') const path = require('path') const electronLink = require('electron-link') const CONFIG = require('../config') +const vm = require('vm') module.exports = function () { const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js') - console.log(`Generating snapshot script at "${snapshotScriptPath}"`) const coreModules = new Set([ 'path', 'electron', 'module', 'fs', 'child_process', 'crypto', 'url', 'atom', 'vm', 'events', 'os', 'assert', 'buffer', 'tty', 'net', 'constants', 'http', 'https', 'shell', 'querystring', 'zlib', 'stream', 'WNdb', 'lapack', 'remote' ]) const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') + let processedFiles = 0 const snapshotScriptContent = electronLink({ baseDirPath, mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'), shouldExcludeModule: (modulePath) => { + if (processedFiles > 0) { + process.stdout.write('\r') + } + process.stdout.write(`Generating snapshot script at "${snapshotScriptPath}" (${++processedFiles})`) + const relativePath = path.relative(baseDirPath, modulePath) return ( modulePath.endsWith('.node') || modulePath === 'buffer-offset-index' || @@ -69,4 +75,8 @@ module.exports = function () { } }) fs.writeFileSync(snapshotScriptPath, snapshotScriptContent) + process.stdout.write('\n') + + console.log('Verifying if snapshot can be executed via `mksnapshot`') + vm.runInNewContext(snapshotScriptContent, undefined, {filename: snapshotScriptPath, displayErrors: true}) } From 5dbae1c3179b20327576133c25469e4b3932f822 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Feb 2017 10:39:05 +0100 Subject: [PATCH 009/194] Copy the generated snapshot into the app bundle --- script/lib/generate-startup-snapshot.js | 34 ++++++++++++++++++++----- src/initialize-snapshot.js | 5 ---- src/main-process/main.js | 2 +- src/task.coffee | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) delete mode 100644 src/initialize-snapshot.js diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 286643fa7..1d5954a31 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -1,16 +1,13 @@ +const childProcess = require('child_process') const fs = require('fs') const path = require('path') const electronLink = require('electron-link') const CONFIG = require('../config') const vm = require('vm') -module.exports = function () { +module.exports = function (packagedAppPath) { const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js') - const coreModules = new Set([ - 'path', 'electron', 'module', 'fs', 'child_process', 'crypto', 'url', - 'atom', 'vm', 'events', 'os', 'assert', 'buffer', 'tty', 'net', 'constants', - 'http', 'https', 'shell', 'querystring', 'zlib', 'stream', 'WNdb', 'lapack', 'remote' - ]) + const coreModules = new Set(['electron', 'atom', 'shell', 'WNdb', 'lapack', 'remote']) const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') let processedFiles = 0 const snapshotScriptContent = electronLink({ @@ -45,11 +42,14 @@ module.exports = function () { relativePath == path.join('..', 'node_modules', 'find-and-replace', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || + relativePath == path.join('..', 'node_modules', 'debug', 'node.js') || relativePath == path.join('..', 'node_modules', 'exception-reporting', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'symbols-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || + relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'internal.js') || + relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || @@ -64,8 +64,15 @@ module.exports = function () { relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'htmlparser2', 'lib', 'index.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'request', 'request.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'cookie.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'memstore.js') || relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || + relativePath == path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') || relativePath == path.join('..', 'node_modules', 'styleguide', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'status-bar', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'tar', 'tar.js') || relativePath == path.join('..', 'node_modules', 'tree-view', 'node_modules', 'pathwatcher', 'lib', 'main.js') || relativePath == path.join('..', 'node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') || @@ -79,4 +86,19 @@ module.exports = function () { console.log('Verifying if snapshot can be executed via `mksnapshot`') vm.runInNewContext(snapshotScriptContent, undefined, {filename: snapshotScriptPath, displayErrors: true}) + + const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin') + console.log(`Generating startup blob at "${generatedStartupBlobPath}"`) + childProcess.execFileSync( + path.join(CONFIG.repositoryRootPath, 'electron', 'mksnapshot', `mksnapshot`), + [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] + ) + + const startupBlobDestinationPath = path.join( + packagedAppPath, + 'Contents', 'Frameworks', 'Electron Framework.framework', 'Resources', 'snapshot_blob.bin' + ) + console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) + fs.unlinkSync(startupBlobDestinationPath) + fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) } diff --git a/src/initialize-snapshot.js b/src/initialize-snapshot.js deleted file mode 100644 index 47bd5afa0..000000000 --- a/src/initialize-snapshot.js +++ /dev/null @@ -1,5 +0,0 @@ -require('./update-process-env') -require('./window') -require('./atom-environment') -require('./application-delegate') -require('./clipboard') diff --git a/src/main-process/main.js b/src/main-process/main.js index e77d1930f..0614f19d2 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -1,5 +1,5 @@ if (typeof snapshotResult !== 'undefined') { - snapshotResult.setGlobals(global, process, {}, require) + snapshotResult.setGlobals(global, process, global, {}, require) } const startTime = Date.now() diff --git a/src/task.coffee b/src/task.coffee index 8555b6554..f3e56a507 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -70,7 +70,7 @@ class Task compileCachePath = require('./compile-cache').getCacheDirectory() taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');" bootstrap = """ - snapshotResult.setGlobals(global, process, global, require) + snapshotResult.setGlobals(global, process, global, {}, require) CompileCache = snapshotResult.customRequire('../src/compile-cache.js') CompileCache.setCacheDirectory('#{compileCachePath}'); #{taskBootstrapRequire} From f87a53bce9dbf34f46b696e974894d2cbb746790 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Feb 2017 11:01:59 +0100 Subject: [PATCH 010/194] Run all steps in script/build --- script/build | 101 ++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/script/build b/script/build index 9afc5b378..376067000 100755 --- a/script/build +++ b/script/build @@ -4,7 +4,7 @@ // Run bootstrap first to ensure all the dependencies used later in this script // are installed. -// require('./bootstrap') +require('./bootstrap') // Needed so we can require src/module-cache.coffee during generateModuleCache require('coffee-script/register') @@ -56,52 +56,53 @@ transpileBabelPaths() transpileCoffeeScriptPaths() transpileCsonPaths() transpilePegJsPaths() -// generateModuleCache() -// prebuildLessCache() -// generateMetadata() -generateStartupSnapshot() -// generateAPIDocs() -// dumpSymbols() -// .then(packageApplication) -// .then(packagedAppPath => { -// if (process.platform === 'darwin') { -// if (argv.codeSign) { -// codeSignOnMac(packagedAppPath) -// } else { -// console.log('Skipping code-signing. Specify the --code-sign option to perform code-signing'.gray) -// } -// } else if (process.platform === 'win32') { -// if (argv.createWindowsInstaller) { -// return createWindowsInstaller(packagedAppPath, argv.codeSign).then(() => packagedAppPath) -// } -// else { -// console.log('Skipping creating installer. Specify the --create-windows-installer option to create a Squirrel-based Windows installer. Code-signing was skipped too.'.gray) -// } -// } else if (process.platform === 'linux') { -// if (argv.createDebianPackage) { -// createDebianPackage(packagedAppPath) -// } else { -// console.log('Skipping creating debian package. Specify the --create-debian-package option to create it.'.gray) -// } -// -// if (argv.createRpmPackage) { -// createRpmPackage(packagedAppPath) -// } else { -// console.log('Skipping creating rpm package. Specify the --create-rpm-package option to create it.'.gray) -// } -// } -// -// return Promise.resolve(packagedAppPath) -// }).then(packagedAppPath => { -// if (argv.compressArtifacts) { -// compressArtifacts(packagedAppPath) -// } else { -// console.log('Skipping artifacts compression. Specify the --compress-artifacts option to compress Atom binaries (and symbols on macOS)'.gray) -// } -// -// if (argv.install) { -// installApplication(packagedAppPath) -// } else { -// console.log('Skipping installation. Specify the --install option to install Atom'.gray) -// } -// }) +generateModuleCache() +prebuildLessCache() +generateMetadata() +generateAPIDocs() +dumpSymbols() + .then(packageApplication) + .then(packagedAppPath => { + generateStartupSnapshot(packagedAppPath) + + if (process.platform === 'darwin') { + if (argv.codeSign) { + codeSignOnMac(packagedAppPath) + } else { + console.log('Skipping code-signing. Specify the --code-sign option to perform code-signing'.gray) + } + } else if (process.platform === 'win32') { + if (argv.createWindowsInstaller) { + return createWindowsInstaller(packagedAppPath, argv.codeSign).then(() => packagedAppPath) + } + else { + console.log('Skipping creating installer. Specify the --create-windows-installer option to create a Squirrel-based Windows installer. Code-signing was skipped too.'.gray) + } + } else if (process.platform === 'linux') { + if (argv.createDebianPackage) { + createDebianPackage(packagedAppPath) + } else { + console.log('Skipping creating debian package. Specify the --create-debian-package option to create it.'.gray) + } + + if (argv.createRpmPackage) { + createRpmPackage(packagedAppPath) + } else { + console.log('Skipping creating rpm package. Specify the --create-rpm-package option to create it.'.gray) + } + } + + return Promise.resolve(packagedAppPath) + }).then(packagedAppPath => { + if (argv.compressArtifacts) { + compressArtifacts(packagedAppPath) + } else { + console.log('Skipping artifacts compression. Specify the --compress-artifacts option to compress Atom binaries (and symbols on macOS)'.gray) + } + + if (argv.install) { + installApplication(packagedAppPath) + } else { + console.log('Skipping installation. Specify the --install option to install Atom'.gray) + } + }) From 367d595ab8b1b3b26b48c0322ccb0af729b86029 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Feb 2017 11:49:17 +0100 Subject: [PATCH 011/194] Update static/index to match what we do on master --- script/lib/generate-metadata.js | 4 +- static/index.js | 79 ++++++--------------------------- 2 files changed, 15 insertions(+), 68 deletions(-) diff --git a/script/lib/generate-metadata.js b/script/lib/generate-metadata.js index 89ef04f69..8267b1cee 100644 --- a/script/lib/generate-metadata.js +++ b/script/lib/generate-metadata.js @@ -27,8 +27,8 @@ function buildBundledPackagesMetadata () { const packagePath = path.join(CONFIG.intermediateAppPath, 'node_modules', packageName) const packageMetadataPath = path.join(packagePath, 'package.json') const packageMetadata = JSON.parse(fs.readFileSync(packageMetadataPath, 'utf8')) - normalizePackageData(packageMetadata, () => { - throw new Error(`Invalid package metadata. ${metadata.name}: ${msg}`) + normalizePackageData(packageMetadata, (msg) => { + console.warn(`Invalid package metadata. ${packageMetadata.name}: ${msg}`) }, true) if (packageMetadata.repository && packageMetadata.repository.url && packageMetadata.repository.type === 'git') { packageMetadata.repository.url = packageMetadata.repository.url.replace(/^git\+/, '') diff --git a/static/index.js b/static/index.js index 889b7f98b..e1d06558f 100644 --- a/static/index.js +++ b/static/index.js @@ -3,6 +3,7 @@ const Module = require('module') const Path = require('path') const vm = require('vm') + const {remote, ipcRenderer} = require('electron') if (typeof snapshotResult !== 'undefined') { window.onload = function () { @@ -11,8 +12,17 @@ console.error('Unhandled promise rejection %o with error: %o', promise, error) }) - parseLoadSettings() - setupAtomHome() + loadSettings = remote.getCurrentWindow().loadSettings + + if (!process.env.ATOM_HOME) { + // Ensure ATOM_HOME is always set before anything else is required + // This is because of a difference in Linux not inherited between browser and render processes + // https://github.com/atom/atom/issues/5412 + if (loadSettings && loadSettings.atomHome) { + process.env.ATOM_HOME = loadSettings.atomHome + } + } + require('../src/crash-reporter-start')({_version: loadSettings.appVersion}) const entryPointDirPath = __dirname @@ -32,70 +42,7 @@ CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) const initialize = snapshotResult.customRequire('../src/initialize-application-window.js') - initialize() - } - } - - function setupVmCompatibility () { - var vm = require('vm') - if (!vm.Script.createContext) { - vm.Script.createContext = vm.createContext - } - } - - function setupDeprecatedPackages () { - var metadata = require('../package.json') - if (!metadata._deprecatedPackages) { - try { - metadata._deprecatedPackages = require('../script/deprecated-packages.json') - } catch (requireError) { - console.error('Failed to setup deprecated packages list', requireError.stack) - } - } - } - - function profileStartup (loadSettings, initialTime) { - function profile () { - console.profile('startup') - var startTime = Date.now() - setupWindow(loadSettings).then(function () { - setLoadTime(Date.now() - startTime + initialTime) - console.profileEnd('startup') - console.log('Switch to the Profiles tab to view the created startup profile') - }) - } - - var currentWindow = require('electron').remote.getCurrentWindow() - if (currentWindow.devToolsWebContents) { - profile() - } else { - currentWindow.openDevTools() - currentWindow.once('devtools-opened', function () { - setTimeout(profile, 1000) - }) - } - } - - function parseLoadSettings () { - var rawLoadSettings = decodeURIComponent(window.location.hash.substr(1)) - try { - loadSettings = JSON.parse(rawLoadSettings) - } catch (error) { - console.error('Failed to parse load settings: ' + rawLoadSettings) - loadSettingsError = error - } - } - - var setupAtomHome = function () { - if (process.env.ATOM_HOME) { - return - } - - // Ensure ATOM_HOME is always set before anything else is required - // This is because of a difference in Linux not inherited between browser and render processes - // https://github.com/atom/atom/issues/5412 - if (loadSettings && loadSettings.atomHome) { - process.env.ATOM_HOME = loadSettings.atomHome + initialize().then(() => { ipcRenderer.send('window-command', 'window:loaded') }) } } })() From 91a38b633ef713d61cca093502799613b4c9488c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Feb 2017 13:22:24 +0100 Subject: [PATCH 012/194] Require wrap-guide during snapshot --- src/initialize-application-window.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 00aad7714..24039ac4e 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -48,6 +48,7 @@ require('tree-view') require('update-package-dependencies') require('welcome') require('whitespace') +require('wrap-guide') # Like sands through the hourglass, so are the days of our lives. module.exports = -> From ea879340c02f1afae7f5e19288fa3607aaed7b34 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Feb 2017 16:36:16 +0100 Subject: [PATCH 013/194] WIP --- script/build | 2 +- script/lib/package-application.js | 1 - src/package.coffee | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/script/build b/script/build index 376067000..376550f3f 100755 --- a/script/build +++ b/script/build @@ -4,7 +4,7 @@ // Run bootstrap first to ensure all the dependencies used later in this script // are installed. -require('./bootstrap') +//require('./bootstrap') // Needed so we can require src/module-cache.coffee during generateModuleCache require('coffee-script/register') diff --git a/script/lib/package-application.js b/script/lib/package-application.js index 1e63b8dc0..8c4127b4f 100644 --- a/script/lib/package-application.js +++ b/script/lib/package-application.js @@ -19,7 +19,6 @@ module.exports = function () { 'app-copyright': `Copyright © 2014-${(new Date()).getFullYear()} GitHub, Inc. All rights reserved.`, 'app-version': CONFIG.appMetadata.version, 'arch': process.platform === 'darwin' ? 'x64' : process.arch, // OS X is 64-bit only - 'asar': {unpack: buildAsarUnpackGlobExpression()}, 'build-version': CONFIG.appMetadata.version, 'download': {cache: CONFIG.electronDownloadPath}, 'dir': CONFIG.intermediateAppPath, diff --git a/src/package.coffee b/src/package.coffee index 63efbf02c..e34c70981 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -7,7 +7,6 @@ fs = require 'fs-plus' {Emitter, CompositeDisposable} = require 'event-kit' CompileCache = require './compile-cache' -ModuleCache = require './module-cache' ScopedProperties = require './scoped-properties' BufferedProcess = require './buffered-process' @@ -42,7 +41,7 @@ class Package @metadata ?= @packageManager.loadPackageMetadata(@path) @bundledPackage = @packageManager.isBundledPackagePath(@path) @name = @metadata?.name ? path.basename(@path) - ModuleCache.add(@path, @metadata) + # ModuleCache.add(@path, @metadata) @reset() ### From acbe228901c73cd54bb83fcc4816020807f38707 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Feb 2017 11:15:20 +0100 Subject: [PATCH 014/194] Ignore `ref` and `key` properties in `TextEditor.prototype.update` --- src/text-editor.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 13b8a0bfa..3cdd363a7 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -353,7 +353,8 @@ class TextEditor extends Model cursor.setShowCursorOnSelection(value) for cursor in @getCursors() else - throw new TypeError("Invalid TextEditor parameter: '#{param}'") + if param isnt 'ref' and param isnt 'key' + throw new TypeError("Invalid TextEditor parameter: '#{param}'") @displayLayer.reset(displayLayerParams) From 54eb65631a5f4086dc9dee0f4ae293ef0023a98d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Feb 2017 09:38:00 +0100 Subject: [PATCH 015/194] :arrow_up: electron-link --- script/build | 3 +- script/lib/generate-startup-snapshot.js | 45 +++++++++++++------------ script/package.json | 1 + 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/script/build b/script/build index 376550f3f..7ceb239b9 100755 --- a/script/build +++ b/script/build @@ -62,9 +62,8 @@ generateMetadata() generateAPIDocs() dumpSymbols() .then(packageApplication) + .then(packagedAppPath => generateStartupSnapshot(packagedAppPath).then(() => packagedAppPath)) .then(packagedAppPath => { - generateStartupSnapshot(packagedAppPath) - if (process.platform === 'darwin') { if (argv.codeSign) { codeSignOnMac(packagedAppPath) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 1d5954a31..2a05ab027 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -10,9 +10,11 @@ module.exports = function (packagedAppPath) { const coreModules = new Set(['electron', 'atom', 'shell', 'WNdb', 'lapack', 'remote']) const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') let processedFiles = 0 - const snapshotScriptContent = electronLink({ + + return electronLink({ baseDirPath, mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'), + cachePath: path.join(CONFIG.atomHomeDirPath, 'snapshot-cache'), shouldExcludeModule: (modulePath) => { if (processedFiles > 0) { process.stdout.write('\r') @@ -80,25 +82,26 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ) } + }).then((snapshotScriptContent) => { + fs.writeFileSync(snapshotScriptPath, snapshotScriptContent) + process.stdout.write('\n') + + console.log('Verifying if snapshot can be executed via `mksnapshot`') + vm.runInNewContext(snapshotScriptContent, undefined, {filename: snapshotScriptPath, displayErrors: true}) + + const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin') + console.log(`Generating startup blob at "${generatedStartupBlobPath}"`) + childProcess.execFileSync( + path.join(CONFIG.repositoryRootPath, 'electron', 'mksnapshot', `mksnapshot`), + [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] + ) + + const startupBlobDestinationPath = path.join( + packagedAppPath, + 'Contents', 'Frameworks', 'Electron Framework.framework', 'Resources', 'snapshot_blob.bin' + ) + console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) + fs.unlinkSync(startupBlobDestinationPath) + fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) }) - fs.writeFileSync(snapshotScriptPath, snapshotScriptContent) - process.stdout.write('\n') - - console.log('Verifying if snapshot can be executed via `mksnapshot`') - vm.runInNewContext(snapshotScriptContent, undefined, {filename: snapshotScriptPath, displayErrors: true}) - - const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin') - console.log(`Generating startup blob at "${generatedStartupBlobPath}"`) - childProcess.execFileSync( - path.join(CONFIG.repositoryRootPath, 'electron', 'mksnapshot', `mksnapshot`), - [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] - ) - - const startupBlobDestinationPath = path.join( - packagedAppPath, - 'Contents', 'Frameworks', 'Electron Framework.framework', 'Resources', 'snapshot_blob.bin' - ) - console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) - fs.unlinkSync(startupBlobDestinationPath) - fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) } diff --git a/script/package.json b/script/package.json index 87c43f261..ed21ea906 100644 --- a/script/package.json +++ b/script/package.json @@ -8,6 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", + "electron-link": "0.0.7", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", "fs-extra": "0.30.0", From 681f0f9fe1ca40425342102f01b0f1f406013858 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Feb 2017 14:47:20 +0100 Subject: [PATCH 016/194] Make Atom work also without a snapshot --- src/package.coffee | 3 +- static/index.js | 143 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 30 deletions(-) diff --git a/src/package.coffee b/src/package.coffee index e34c70981..386cc0018 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -41,7 +41,8 @@ class Package @metadata ?= @packageManager.loadPackageMetadata(@path) @bundledPackage = @packageManager.isBundledPackagePath(@path) @name = @metadata?.name ? path.basename(@path) - # ModuleCache.add(@path, @metadata) + unless @bundledPackage + ModuleCache.add(@path, @metadata) @reset() ### diff --git a/static/index.js b/static/index.js index e1d06558f..9fbfbcea7 100644 --- a/static/index.js +++ b/static/index.js @@ -1,48 +1,133 @@ (function () { - let loadSettings - const Module = require('module') - const Path = require('path') - const vm = require('vm') - const {remote, ipcRenderer} = require('electron') + const path = require('path') + const getWindowLoadSettings = require('../src/get-window-load-settings') + const entryPointDirPath = __dirname + let blobStore = null + let devMode = false + let useSnapshot = false + let requireFunction = null + + window.onload = function () { + try { + var startTime = Date.now() - if (typeof snapshotResult !== 'undefined') { - window.onload = function () { - process.resourcesPath = Path.normalize(process.resourcesPath) process.on('unhandledRejection', function (error, promise) { console.error('Unhandled promise rejection %o with error: %o', promise, error) }) - loadSettings = remote.getCurrentWindow().loadSettings + // Normalize to make sure drive letter case is consistent on Windows + process.resourcesPath = path.normalize(process.resourcesPath) - if (!process.env.ATOM_HOME) { - // Ensure ATOM_HOME is always set before anything else is required - // This is because of a difference in Linux not inherited between browser and render processes - // https://github.com/atom/atom/issues/5412 - if (loadSettings && loadSettings.atomHome) { - process.env.ATOM_HOME = loadSettings.atomHome + setupAtomHome() + devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) + useSnapshot = !devMode && typeof snapshotResult !== 'undefined' + requireFunction = useSnapshot ? snapshotResult.customRequire : require + + if (devMode) { + var metadata = require('../package.json') + if (!metadata._deprecatedPackages) { + try { + metadata._deprecatedPackages = require('../script/deprecated-packages.json') + } catch (requireError) { + console.error('Failed to setup deprecated packages list', requireError.stack) + } } + } else if (useSnapshot) { + Module.prototype.require = function (modulePath) { + const absoluteFilePath = Module._resolveFilename(modulePath, this, false) + const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) + return snapshotResult.customRequire(relativeFilePath) + } + + snapshotResult.setGlobals(global, process, window, document, Module._load) } - require('../src/crash-reporter-start')({_version: loadSettings.appVersion}) + const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') + blobStore = FileSystemBlobStore.load( + path.join(process.env.ATOM_HOME, 'blob-store/') + ) - const entryPointDirPath = __dirname - Module.prototype.require = function (path) { - const absoluteFilePath = Module._resolveFilename(path, this, false) - const relativeFilePath = Path.relative(entryPointDirPath, absoluteFilePath) - const cachedModule = snapshotResult.customRequire.cache[relativeFilePath] - return cachedModule ? cachedModule : Module._load(path, this, false) + const NativeCompileCache = requireFunction('../src/native-compile-cache.js') + NativeCompileCache.setCacheStore(blobStore) + NativeCompileCache.setV8Version(process.versions.v8) + NativeCompileCache.install() + + if (getWindowLoadSettings().profileStartup) { + profileStartup(Date.now() - startTime) + } else { + setupWindow() + setLoadTime(Date.now() - startTime) } + } catch (error) { + handleSetupError(error) + } + } - snapshotResult.setGlobals(global, process, window, document, require) + function setLoadTime (loadTime) { + if (global.atom) { + global.atom.loadTime = loadTime + } + } - const CSON = snapshotResult.customRequire("../node_modules/season/lib/cson.js") - CSON.setCacheDir(Path.join(process.env.ATOM_HOME, 'compile-cache', 'cson')) + function handleSetupError (error) { + const currentWindow = require('electron').remote.getCurrentWindow() + currentWindow.setSize(800, 600) + currentWindow.center() + currentWindow.show() + currentWindow.openDevTools() + console.error(error.stack || error) + } - const CompileCache = snapshotResult.customRequire('../src/compile-cache.js') - CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + function setupWindow () { + const CompileCache = requireFunction('../src/compile-cache.js') + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - const initialize = snapshotResult.customRequire('../src/initialize-application-window.js') - initialize().then(() => { ipcRenderer.send('window-command', 'window:loaded') }) + const ModuleCache = requireFunction('../src/module-cache.js') + ModuleCache.register(getWindowLoadSettings()) + + const startCrashReporter = requireFunction('../src/crash-reporter-start.js') + startCrashReporter({_version: getWindowLoadSettings().appVersion}) + + const CSON = requireFunction(useSnapshot ? '../node_modules/season/lib/cson.js' : 'season') + CSON.setCacheDir(path.join(CompileCache.getCacheDirectory(), 'cson')) + + const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) + const initialize = requireFunction(initScriptPath) + return initialize({blobStore: blobStore}).then(function () { + require('electron').ipcRenderer.send('window-command', 'window:loaded') + }) + } + + function profileStartup (initialTime) { + function profile () { + console.profile('startup') + var startTime = Date.now() + setupWindow().then(function () { + setLoadTime(Date.now() - startTime + initialTime) + console.profileEnd('startup') + console.log('Switch to the Profiles tab to view the created startup profile') + }) + } + + const webContents = require('electron').remote.getCurrentWindow().webContents + if (webContents.devToolsWebContents) { + profile() + } else { + webContents.once('devtools-opened', () => { setTimeout(profile, 1000) }) + webContents.openDevTools() + } + } + + function setupAtomHome () { + if (process.env.ATOM_HOME) { + return + } + + // Ensure ATOM_HOME is always set before anything else is required + // This is because of a difference in Linux not inherited between browser and render processes + // https://github.com/atom/atom/issues/5412 + if (getWindowLoadSettings() && getWindowLoadSettings().atomHome) { + process.env.ATOM_HOME = getWindowLoadSettings().atomHome } } })() From aec88679047a408ea1c51a47bb7ab7c0236573fd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Feb 2017 14:48:45 +0100 Subject: [PATCH 017/194] Require electron once --- static/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/index.js b/static/index.js index 9fbfbcea7..5c65cbd46 100644 --- a/static/index.js +++ b/static/index.js @@ -1,4 +1,5 @@ (function () { + const electron = require('electron') const path = require('path') const getWindowLoadSettings = require('../src/get-window-load-settings') const entryPointDirPath = __dirname @@ -70,7 +71,7 @@ } function handleSetupError (error) { - const currentWindow = require('electron').remote.getCurrentWindow() + const currentWindow = electron.remote.getCurrentWindow() currentWindow.setSize(800, 600) currentWindow.center() currentWindow.show() @@ -94,7 +95,7 @@ const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) const initialize = requireFunction(initScriptPath) return initialize({blobStore: blobStore}).then(function () { - require('electron').ipcRenderer.send('window-command', 'window:loaded') + electron.ipcRenderer.send('window-command', 'window:loaded') }) } @@ -109,7 +110,7 @@ }) } - const webContents = require('electron').remote.getCurrentWindow().webContents + const webContents = electron.remote.getCurrentWindow().webContents if (webContents.devToolsWebContents) { profile() } else { From 7b8849b8355f05d97d4b038287c29865d5a0decd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Feb 2017 14:51:33 +0100 Subject: [PATCH 018/194] :art: --- static/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/index.js b/static/index.js index 5c65cbd46..8809bf436 100644 --- a/static/index.js +++ b/static/index.js @@ -10,7 +10,7 @@ window.onload = function () { try { - var startTime = Date.now() + const startTime = Date.now() process.on('unhandledRejection', function (error, promise) { console.error('Unhandled promise rejection %o with error: %o', promise, error) @@ -25,7 +25,7 @@ requireFunction = useSnapshot ? snapshotResult.customRequire : require if (devMode) { - var metadata = require('../package.json') + const metadata = require('../package.json') if (!metadata._deprecatedPackages) { try { metadata._deprecatedPackages = require('../script/deprecated-packages.json') @@ -102,7 +102,7 @@ function profileStartup (initialTime) { function profile () { console.profile('startup') - var startTime = Date.now() + const startTime = Date.now() setupWindow().then(function () { setLoadTime(Date.now() - startTime + initialTime) console.profileEnd('startup') From e453b04105abdea29fefb3a85bc11636471b027f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Feb 2017 16:37:48 +0100 Subject: [PATCH 019/194] Put back code that was commented out --- script/build | 2 +- script/lib/transpile-babel-paths.js | 1 - src/atom-environment.coffee | 8 +++++++- src/coffee-script.js | 3 ++- src/initialize-application-window.coffee | 4 ++-- src/package.coffee | 1 + static/index.js | 11 ++++++----- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/script/build b/script/build index 7ceb239b9..2b45e425d 100755 --- a/script/build +++ b/script/build @@ -4,7 +4,7 @@ // Run bootstrap first to ensure all the dependencies used later in this script // are installed. -//require('./bootstrap') +require('./bootstrap') // Needed so we can require src/module-cache.coffee during generateModuleCache require('coffee-script/register') diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index 0a6a15f93..3c440a4bd 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -28,7 +28,6 @@ function getPathsToTranspile () { paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, '**', 'atom-select-list', 'src', 'select-list-view.js'))) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { paths = paths.concat(glob.sync( path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.js'), diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index dae45f7a2..3133b5af8 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -131,7 +131,7 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@blobStore, @applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params @unloaded = false @loadTime = null @@ -733,6 +733,7 @@ class AtomEnvironment extends Model @storeWindowBackground() @packages.deactivatePackages() + @saveBlobStoreSync() @unloaded = true openInitialEmptyEditorIfNecessary: -> @@ -866,6 +867,11 @@ class AtomEnvironment extends Model showSaveDialogSync: (options={}) -> @applicationDelegate.showSaveDialog(options) + saveBlobStoreSync: -> + return unless @enablePersistence + + @blobStore.save() + saveState: (options) -> new Promise (resolve, reject) => if @enablePersistence and @project diff --git a/src/coffee-script.js b/src/coffee-script.js index 45f55e219..0437e787f 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -35,7 +35,8 @@ exports.compile = function (sourceCode, filePath) { var output = CoffeeScript.compile(sourceCode, { filename: filePath, - sourceFiles: [filePath] + sourceFiles: [filePath], + inlineMap: true }) // Strip sourceURL from output so there wouldn't be duplicate entries diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 24039ac4e..a3a57e886 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -51,7 +51,7 @@ require('whitespace') require('wrap-guide') # Like sands through the hourglass, so are the days of our lives. -module.exports = -> +module.exports = ({blobStore}) -> {updateProcessEnv} = require('./update-process-env') path = require 'path' require './window' @@ -72,7 +72,7 @@ module.exports = -> TextEditor.setClipboard(clipboard) window.atom = new AtomEnvironment({ - window, document, clipboard, + window, document, clipboard, blobStore, applicationDelegate: new ApplicationDelegate, configDirPath: process.env.ATOM_HOME, enablePersistence: true, diff --git a/src/package.coffee b/src/package.coffee index 386cc0018..dea9c0e58 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -7,6 +7,7 @@ fs = require 'fs-plus' {Emitter, CompositeDisposable} = require 'event-kit' CompileCache = require './compile-cache' +ModuleCache = require './module-cache' ScopedProperties = require './scoped-properties' BufferedProcess = require './buffered-process' diff --git a/static/index.js b/static/index.js index 8809bf436..8d5263c00 100644 --- a/static/index.js +++ b/static/index.js @@ -34,13 +34,14 @@ } } } else if (useSnapshot) { - Module.prototype.require = function (modulePath) { - const absoluteFilePath = Module._resolveFilename(modulePath, this, false) - const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) - return snapshotResult.customRequire(relativeFilePath) + Module.prototype.require = function (path) { + const absoluteFilePath = Module._resolveFilename(path, this, false) + const relativeFilePath = Path.relative(entryPointDirPath, absoluteFilePath) + const cachedModule = snapshotResult.customRequire.cache[relativeFilePath] + return cachedModule ? cachedModule : Module._load(path, this, false) } - snapshotResult.setGlobals(global, process, window, document, Module._load) + snapshotResult.setGlobals(global, process, window, document, require) } const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') From dd07768f2266304f9ca472bd8026d2de44898daa Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 27 Feb 2017 11:12:50 -0500 Subject: [PATCH 020/194] :arrow_up: language-javascript@0.126.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a799e71e..fbed97460 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "language-html": "0.47.2", "language-hyperlink": "0.16.1", "language-java": "0.26.0", - "language-javascript": "0.126.0", + "language-javascript": "0.126.1", "language-json": "0.18.3", "language-less": "0.30.1", "language-make": "0.22.3", From cd873c7f8abc9a83840073573da1e452529558b0 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 27 Feb 2017 13:29:54 -0500 Subject: [PATCH 021/194] :arrow_up: settings-view@0.247.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fbed97460..387bba8b8 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "notifications": "0.66.2", "open-on-github": "1.2.1", "package-generator": "1.1.0", - "settings-view": "0.247.0", + "settings-view": "0.247.1", "snippets": "1.0.5", "spell-check": "0.71.0", "status-bar": "1.8.1", From 1dcf2f3ba47c71fb136466dd12a9893d6a43de2b Mon Sep 17 00:00:00 2001 From: bene Date: Mon, 27 Feb 2017 22:42:22 +0100 Subject: [PATCH 022/194] :arrow_up: atom-keymap --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 387bba8b8..f76b2f94c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.13", "dependencies": { "async": "0.2.6", - "atom-keymap": "7.1.20", + "atom-keymap": "7.1.21", "atom-select-list": "0.0.12", "atom-ui": "0.4.1", "babel-core": "6.22.1", From f8de954d3599922b3af0b29480553e61d17ee5e7 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 28 Feb 2017 17:30:41 +0900 Subject: [PATCH 023/194] :arrow_up: settings-view@v0.247.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f76b2f94c..41ac9e189 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "notifications": "0.66.2", "open-on-github": "1.2.1", "package-generator": "1.1.0", - "settings-view": "0.247.1", + "settings-view": "0.247.2", "snippets": "1.0.5", "spell-check": "0.71.0", "status-bar": "1.8.1", From 9b8c7973d724fd66064e814a4c2699abb1649a32 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 09:34:55 +0100 Subject: [PATCH 024/194] WIP --- script/build | 2 +- script/lib/generate-startup-snapshot.js | 6 ++--- script/lib/transpile-babel-paths.js | 1 + src/atom-environment.coffee | 8 +----- src/initialize-application-window.coffee | 3 +++ src/module-cache.coffee | 32 ++---------------------- src/task.coffee | 2 +- static/index.js | 32 +++++++++++++----------- 8 files changed, 29 insertions(+), 57 deletions(-) diff --git a/script/build b/script/build index 2b45e425d..a5368768c 100755 --- a/script/build +++ b/script/build @@ -4,7 +4,7 @@ // Run bootstrap first to ensure all the dependencies used later in this script // are installed. -require('./bootstrap') +// require('./bootstrap') // Needed so we can require src/module-cache.coffee during generateModuleCache require('coffee-script/register') diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 2a05ab027..f7951daac 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -28,8 +28,8 @@ module.exports = function (packagedAppPath) { (relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) || relativePath == path.join('..', 'exports', 'atom.js') || relativePath == path.join('..', 'src', 'config-schema.js') || + relativePath == path.join('..', 'src', 'compile-cache.js') || relativePath == path.join('..', 'src', 'electron-shims.js') || - relativePath == path.join('..', 'src', 'module-cache.js') || relativePath == path.join('..', 'src', 'safe-clipboard.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || @@ -62,13 +62,13 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'mime', 'mime.js') || relativePath == path.join('..', 'node_modules', 'oniguruma', 'lib', 'oniguruma.js') || relativePath == path.join('..', 'node_modules', 'pathwatcher', 'lib', 'main.js') || - relativePath == path.join('..', 'node_modules', 'request', 'request.js') || + relativePath == path.join('..', 'node_modules', 'request', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'htmlparser2', 'lib', 'index.js') || - relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'request', 'request.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'request', 'index.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'cookie.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'memstore.js') || relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index 3c440a4bd..0a6a15f93 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -28,6 +28,7 @@ function getPathsToTranspile () { paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, '**', 'atom-select-list', 'src', 'select-list-view.js'))) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { paths = paths.concat(glob.sync( path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.js'), diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 3133b5af8..dae45f7a2 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -131,7 +131,7 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@blobStore, @applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params @unloaded = false @loadTime = null @@ -733,7 +733,6 @@ class AtomEnvironment extends Model @storeWindowBackground() @packages.deactivatePackages() - @saveBlobStoreSync() @unloaded = true openInitialEmptyEditorIfNecessary: -> @@ -867,11 +866,6 @@ class AtomEnvironment extends Model showSaveDialogSync: (options={}) -> @applicationDelegate.showSaveDialog(options) - saveBlobStoreSync: -> - return unless @enablePersistence - - @blobStore.save() - saveState: (options) -> new Promise (resolve, reject) => if @enablePersistence and @project diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index a3a57e886..507818998 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -3,6 +3,9 @@ ApplicationDelegate = require './application-delegate' Clipboard = require './clipboard' TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' +FileSystemBlobStore = require './file-system-blob-store' +CompileCache = require './compile-cache' +ModuleCache = require './module-cache' require('about') require('archive-view') diff --git a/src/module-cache.coffee b/src/module-cache.coffee index 8c6a7c312..5bc162ab1 100644 --- a/src/module-cache.coffee +++ b/src/module-cache.coffee @@ -20,7 +20,7 @@ class Range extends semver.Range @unmatchedVersions.add(version) matches -nativeModules = process.binding('natives') +nativeModules = null cache = builtins: {} @@ -171,6 +171,7 @@ resolveModulePath = (relativePath, parentModule) -> return unless relativePath return unless parentModule?.filename + nativeModules ?= process.binding('natives') return if nativeModules.hasOwnProperty(relativePath) return if relativePath[0] is '.' return if isAbsolute(relativePath) @@ -212,35 +213,6 @@ registerBuiltins = (devMode) -> for builtin in rendererBuiltins cache.builtins[builtin] = path.join(rendererRoot, "#{builtin}.js") -if cache.debug - cache.findPathCount = 0 - cache.findPathTime = 0 - cache.loadCount = 0 - cache.requireTime = 0 - global.moduleCache = cache - - originalLoad = Module::load - Module::load = -> - cache.loadCount++ - originalLoad.apply(this, arguments) - - originalRequire = Module::require - Module::require = -> - startTime = Date.now() - exports = originalRequire.apply(this, arguments) - cache.requireTime += Date.now() - startTime - exports - - originalFindPath = Module._findPath - Module._findPath = (request, paths) -> - cacheKey = JSON.stringify({request, paths}) - cache.findPathCount++ unless Module._pathCache[cacheKey] - - startTime = Date.now() - foundPath = originalFindPath.apply(global, arguments) - cache.findPathTime += Date.now() - startTime - foundPath - exports.create = (modulePath) -> fs = require 'fs-plus' diff --git a/src/task.coffee b/src/task.coffee index f3e56a507..9f115e869 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -71,7 +71,7 @@ class Task taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');" bootstrap = """ snapshotResult.setGlobals(global, process, global, {}, require) - CompileCache = snapshotResult.customRequire('../src/compile-cache.js') + CompileCache = #{compileCacheRequire} CompileCache.setCacheDirectory('#{compileCachePath}'); #{taskBootstrapRequire} """ diff --git a/static/index.js b/static/index.js index 8d5263c00..fa240fcdb 100644 --- a/static/index.js +++ b/static/index.js @@ -1,8 +1,10 @@ (function () { const electron = require('electron') const path = require('path') + const Module = require('module') const getWindowLoadSettings = require('../src/get-window-load-settings') const entryPointDirPath = __dirname + let CompileCache = null let blobStore = null let devMode = false let useSnapshot = false @@ -34,25 +36,28 @@ } } } else if (useSnapshot) { - Module.prototype.require = function (path) { - const absoluteFilePath = Module._resolveFilename(path, this, false) - const relativeFilePath = Path.relative(entryPointDirPath, absoluteFilePath) + Module.prototype.require = function (module) { + const absoluteFilePath = Module._resolveFilename(module, this, false) + const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) const cachedModule = snapshotResult.customRequire.cache[relativeFilePath] - return cachedModule ? cachedModule : Module._load(path, this, false) + return cachedModule ? cachedModule : Module._load(module, this, false) } snapshotResult.setGlobals(global, process, window, document, require) } - const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') - blobStore = FileSystemBlobStore.load( - path.join(process.env.ATOM_HOME, 'blob-store/') - ) + CompileCache = require('../src/compile-cache') + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - const NativeCompileCache = requireFunction('../src/native-compile-cache.js') - NativeCompileCache.setCacheStore(blobStore) - NativeCompileCache.setV8Version(process.versions.v8) - NativeCompileCache.install() + // const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') + // blobStore = FileSystemBlobStore.load( + // path.join(process.env.ATOM_HOME, 'blob-store/') + // ) + + // const NativeCompileCache = requireFunction('../src/native-compile-cache.js') + // NativeCompileCache.setCacheStore(blobStore) + // NativeCompileCache.setV8Version(process.versions.v8) + // NativeCompileCache.install() if (getWindowLoadSettings().profileStartup) { profileStartup(Date.now() - startTime) @@ -81,9 +86,6 @@ } function setupWindow () { - const CompileCache = requireFunction('../src/compile-cache.js') - CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - const ModuleCache = requireFunction('../src/module-cache.js') ModuleCache.register(getWindowLoadSettings()) From 8a1956ceca2f2ea4ea2daebf4030808e517a7628 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 10:46:45 +0100 Subject: [PATCH 025/194] :arrow_up: etch and atom-select-list --- package.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 41ac9e189..01eedc2eb 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "async": "0.2.6", "atom-keymap": "7.1.21", - "atom-select-list": "0.0.12", + "atom-select-list": "0.0.15", "atom-ui": "0.4.1", "babel-core": "6.22.1", "babel-plugin-add-module-exports": "0.2.1", @@ -95,7 +95,7 @@ "one-light-syntax": "1.7.1", "solarized-dark-syntax": "1.1.2", "solarized-light-syntax": "1.1.2", - "about": "1.7.2", + "about": "1.7.4", "archive-view": "0.62.2", "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.15.0", @@ -105,23 +105,23 @@ "autoflow": "0.29.0", "autosave": "0.24.0", "background-tips": "0.26.1", - "bookmarks": "0.44.1", + "bookmarks": "0.44.2", "bracket-matcher": "0.85.3", - "command-palette": "0.40.2", + "command-palette": "0.40.3", "dalek": "0.2.0", "deprecation-cop": "0.56.2", "dev-live-reload": "0.47.0", - "encoding-selector": "0.23.1", + "encoding-selector": "0.23.2", "exception-reporting": "0.41.1", "find-and-replace": "0.206.3", "fuzzy-finder": "1.4.1", - "git-diff": "1.3.1", + "git-diff": "1.3.2", "go-to-line": "0.32.0", - "grammar-selector": "0.49.2", - "image-view": "0.61.0", - "incompatible-packages": "0.27.0", - "keybinding-resolver": "0.36.1", - "line-ending-selector": "0.6.1", + "grammar-selector": "0.49.3", + "image-view": "0.61.1", + "incompatible-packages": "0.27.1", + "keybinding-resolver": "0.36.3", + "line-ending-selector": "0.6.2", "link": "0.31.2", "markdown-preview": "0.159.7", "metrics": "1.2.1", @@ -130,15 +130,15 @@ "package-generator": "1.1.0", "settings-view": "0.247.2", "snippets": "1.0.5", - "spell-check": "0.71.0", + "spell-check": "0.71.1", "status-bar": "1.8.1", - "styleguide": "0.49.2", + "styleguide": "0.49.3", "symbols-view": "0.114.0", "tabs": "0.104.1", "timecop": "0.36.0", "tree-view": "0.214.1", "update-package-dependencies": "0.10.0", - "welcome": "0.36.1", + "welcome": "0.36.2", "whitespace": "0.36.2", "wrap-guide": "0.39.1", "language-c": "0.56.0", From 09c36c0b3752aea3eb275804dd5b8c92755aba7c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 15:15:08 +0100 Subject: [PATCH 026/194] :arrow_up: git-diff --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01eedc2eb..671875642 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "exception-reporting": "0.41.1", "find-and-replace": "0.206.3", "fuzzy-finder": "1.4.1", - "git-diff": "1.3.2", + "git-diff": "1.3.3", "go-to-line": "0.32.0", "grammar-selector": "0.49.3", "image-view": "0.61.1", From adb0e39166e839a287254e77d8b5eff67ea839e8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 12:15:00 +0100 Subject: [PATCH 027/194] WIP --- script/lib/generate-startup-snapshot.js | 1 - script/lib/transpile-babel-paths.js | 1 - src/compile-cache.js | 190 ++++++++++++----------- src/initialize-application-window.coffee | 1 + src/main-process/start.js | 1 + src/task.coffee | 1 + static/index.js | 17 +- 7 files changed, 111 insertions(+), 101 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index f7951daac..479fbbbf0 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -28,7 +28,6 @@ module.exports = function (packagedAppPath) { (relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) || relativePath == path.join('..', 'exports', 'atom.js') || relativePath == path.join('..', 'src', 'config-schema.js') || - relativePath == path.join('..', 'src', 'compile-cache.js') || relativePath == path.join('..', 'src', 'electron-shims.js') || relativePath == path.join('..', 'src', 'safe-clipboard.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index 0a6a15f93..3c440a4bd 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -28,7 +28,6 @@ function getPathsToTranspile () { paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, '**', 'atom-select-list', 'src', 'select-list-view.js'))) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { paths = paths.concat(glob.sync( path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.js'), diff --git a/src/compile-cache.js b/src/compile-cache.js index 8a4451d90..e15674a2e 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -7,6 +7,7 @@ var path = require('path') var fs = require('fs-plus') +var sourceMapSupport = require('source-map-support') var PackageTranspilationRegistry = require('./package-transpilation-registry') var CSON = null @@ -113,109 +114,112 @@ function writeCachedJavascript (relativeCachePath, code) { var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg -require('source-map-support').install({ - handleUncaughtExceptions: false, +exports.install = function (nodeRequire) { + sourceMapSupport.install({ + handleUncaughtExceptions: false, - // Most of this logic is the same as the default implementation in the - // source-map-support module, but we've overridden it to read the javascript - // code from our cache directory. - retrieveSourceMap: function (filePath) { - if (!cacheDirectory || !fs.isFileSync(filePath)) { - return null - } + // Most of this logic is the same as the default implementation in the + // source-map-support module, but we've overridden it to read the javascript + // code from our cache directory. + retrieveSourceMap: function (filePath) { + if (!cacheDirectory || !fs.isFileSync(filePath)) { + return null + } - try { - var sourceCode = fs.readFileSync(filePath, 'utf8') - } catch (error) { - console.warn('Error reading source file', error.stack) - return null - } + try { + var sourceCode = fs.readFileSync(filePath, 'utf8') + } catch (error) { + console.warn('Error reading source file', error.stack) + return null + } - var compiler = COMPILERS[path.extname(filePath)] - if (!compiler) compiler = COMPILERS['.js'] + var compiler = COMPILERS[path.extname(filePath)] + if (!compiler) compiler = COMPILERS['.js'] - try { - var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) - } catch (error) { - console.warn('Error reading compiled file', error.stack) - return null - } + try { + var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) + } catch (error) { + console.warn('Error reading compiled file', error.stack) + return null + } - if (fileData == null) { - return null - } + if (fileData == null) { + return null + } - var match, lastMatch - INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 - while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { - lastMatch = match - } - if (lastMatch == null) { - return null - } + var match, lastMatch + INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 + while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { + lastMatch = match + } + if (lastMatch == null) { + return null + } - var sourceMappingURL = lastMatch[1] - var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) + var sourceMappingURL = lastMatch[1] + var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) - try { - var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) - } catch (error) { - console.warn('Error parsing source map', error.stack) - return null - } + try { + var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) + } catch (error) { + console.warn('Error parsing source map', error.stack) + return null + } - return { - map: sourceMap, - url: null - } - } -}) - -var prepareStackTraceWithSourceMapping = Error.prepareStackTrace -var prepareStackTrace = prepareStackTraceWithSourceMapping - -function prepareStackTraceWithRawStackAssignment (error, frames) { - if (error.rawStack) { // avoid infinite recursion - return prepareStackTraceWithSourceMapping(error, frames) - } else { - error.rawStack = frames - return prepareStackTrace(error, frames) - } -} - -Error.stackTraceLimit = 30 - -Object.defineProperty(Error, 'prepareStackTrace', { - get: function () { - return prepareStackTraceWithRawStackAssignment - }, - - set: function (newValue) { - prepareStackTrace = newValue - process.nextTick(function () { - prepareStackTrace = prepareStackTraceWithSourceMapping - }) - } -}) - -Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native - // Access this.stack to ensure prepareStackTrace has been run on this error - // because it assigns this.rawStack as a side-effect - this.stack - return this.rawStack -} - -Object.keys(COMPILERS).forEach(function (extension) { - var compiler = COMPILERS[extension] - - Object.defineProperty(require.extensions, extension, { - enumerable: true, - writable: false, - value: function (module, filePath) { - var code = compileFileAtPath(compiler, filePath, extension) - return module._compile(code, filePath) + return { + map: sourceMap, + url: null + } } }) -}) + + var prepareStackTraceWithSourceMapping = Error.prepareStackTrace + var prepareStackTrace = prepareStackTraceWithSourceMapping + + function prepareStackTraceWithRawStackAssignment (error, frames) { + if (error.rawStack) { // avoid infinite recursion + return prepareStackTraceWithSourceMapping(error, frames) + } else { + error.rawStack = frames + return prepareStackTrace(error, frames) + } + } + + Error.stackTraceLimit = 30 + + Object.defineProperty(Error, 'prepareStackTrace', { + get: function () { + return prepareStackTraceWithRawStackAssignment + }, + + set: function (newValue) { + prepareStackTrace = newValue + process.nextTick(function () { + prepareStackTrace = prepareStackTraceWithSourceMapping + }) + } + }) + + Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native + // Access this.stack to ensure prepareStackTrace has been run on this error + // because it assigns this.rawStack as a side-effect + this.stack + return this.rawStack + } + + Object.keys(COMPILERS).forEach(function (extension) { + var compiler = COMPILERS[extension] + + Object.defineProperty(nodeRequire.extensions, extension, { + enumerable: true, + writable: false, + value: function (module, filePath) { + var code = compileFileAtPath(compiler, filePath, extension) + return module._compile(code, filePath) + } + }) + }) +} + exports.resetCacheStats() diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 507818998..13562fae2 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -24,6 +24,7 @@ require('deprecation-cop') require('dev-live-reload') require('encoding-selector') require('exception-reporting') +require('dalek') require('find-and-replace') require('fuzzy-finder') require('git-diff') diff --git a/src/main-process/start.js b/src/main-process/start.js index f54d263e0..368370939 100644 --- a/src/main-process/start.js +++ b/src/main-process/start.js @@ -83,4 +83,5 @@ function handleStartupEventWithSquirrel () { function setupCompileCache () { const CompileCache = require('../compile-cache') CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + CompileCache.install(require) } diff --git a/src/task.coffee b/src/task.coffee index 9f115e869..182d9df88 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -73,6 +73,7 @@ class Task snapshotResult.setGlobals(global, process, global, {}, require) CompileCache = #{compileCacheRequire} CompileCache.setCacheDirectory('#{compileCachePath}'); + CompileCache.install(require) #{taskBootstrapRequire} """ bootstrap = bootstrap.replace(/\\/g, "\\\\") diff --git a/static/index.js b/static/index.js index fa240fcdb..4b7543dff 100644 --- a/static/index.js +++ b/static/index.js @@ -1,10 +1,10 @@ (function () { const electron = require('electron') const path = require('path') + const fs = require('fs-plus') const Module = require('module') const getWindowLoadSettings = require('../src/get-window-load-settings') const entryPointDirPath = __dirname - let CompileCache = null let blobStore = null let devMode = false let useSnapshot = false @@ -39,16 +39,17 @@ Module.prototype.require = function (module) { const absoluteFilePath = Module._resolveFilename(module, this, false) const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) - const cachedModule = snapshotResult.customRequire.cache[relativeFilePath] - return cachedModule ? cachedModule : Module._load(module, this, false) + let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] + if (!cachedModule) { + cachedModule = Module._load(module, this, false) + snapshotResult.customRequire.cache[relativeFilePath] = cachedModule + } + return cachedModule } snapshotResult.setGlobals(global, process, window, document, require) } - CompileCache = require('../src/compile-cache') - CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - // const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') // blobStore = FileSystemBlobStore.load( // path.join(process.env.ATOM_HOME, 'blob-store/') @@ -86,6 +87,10 @@ } function setupWindow () { + const CompileCache = requireFunction('../src/compile-cache.js') + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + CompileCache.install(require) + const ModuleCache = requireFunction('../src/module-cache.js') ModuleCache.register(getWindowLoadSettings()) From 66c7f2f83c23a3a491b3b4885fbc409ea2ee5dea Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 17:12:57 +0100 Subject: [PATCH 028/194] Provide snapshotResult.entryPointDirPath --- static/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/index.js b/static/index.js index 4b7543dff..2d660c844 100644 --- a/static/index.js +++ b/static/index.js @@ -48,6 +48,7 @@ } snapshotResult.setGlobals(global, process, window, document, require) + snapshotResult.entryPointDirPath = __dirname } // const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') From deb94f6f4aa414e9dad32c20e8dca38bc1c142cf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 17:17:02 +0100 Subject: [PATCH 029/194] Fix dev mode --- static/index.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/static/index.js b/static/index.js index 2d660c844..c79055200 100644 --- a/static/index.js +++ b/static/index.js @@ -8,7 +8,6 @@ let blobStore = null let devMode = false let useSnapshot = false - let requireFunction = null window.onload = function () { try { @@ -24,7 +23,6 @@ setupAtomHome() devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) useSnapshot = !devMode && typeof snapshotResult !== 'undefined' - requireFunction = useSnapshot ? snapshotResult.customRequire : require if (devMode) { const metadata = require('../package.json') @@ -51,16 +49,6 @@ snapshotResult.entryPointDirPath = __dirname } - // const FileSystemBlobStore = requireFunction('../src/file-system-blob-store.js') - // blobStore = FileSystemBlobStore.load( - // path.join(process.env.ATOM_HOME, 'blob-store/') - // ) - - // const NativeCompileCache = requireFunction('../src/native-compile-cache.js') - // NativeCompileCache.setCacheStore(blobStore) - // NativeCompileCache.setV8Version(process.versions.v8) - // NativeCompileCache.install() - if (getWindowLoadSettings().profileStartup) { profileStartup(Date.now() - startTime) } else { @@ -88,21 +76,21 @@ } function setupWindow () { - const CompileCache = requireFunction('../src/compile-cache.js') + const CompileCache = useSnapshot ? snapshotResult.customRequire('../src/compile-cache.js') : require('../src/compile-cache') CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) CompileCache.install(require) - const ModuleCache = requireFunction('../src/module-cache.js') + const ModuleCache = useSnapshot ? snapshotResult.customRequire('../src/module-cache.js') : require('../src/module-cache') ModuleCache.register(getWindowLoadSettings()) - const startCrashReporter = requireFunction('../src/crash-reporter-start.js') + const startCrashReporter = useSnapshot ? snapshotResult.customRequire('../src/crash-reporter-start.js') : require('../src/crash-reporter-start') startCrashReporter({_version: getWindowLoadSettings().appVersion}) - const CSON = requireFunction(useSnapshot ? '../node_modules/season/lib/cson.js' : 'season') + const CSON = useSnapshot ? snapshotResult.customRequire('../node_modules/season/lib/cson.js') : require('season') CSON.setCacheDir(path.join(CompileCache.getCacheDirectory(), 'cson')) const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) - const initialize = requireFunction(initScriptPath) + const initialize = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) return initialize({blobStore: blobStore}).then(function () { electron.ipcRenderer.send('window-command', 'window:loaded') }) From e275570a4eeb160a8841fb63c7235fda835fa7a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 17:20:49 +0100 Subject: [PATCH 030/194] Remove blob store and native module cache --- spec/file-system-blob-store-spec.coffee | 108 ------------------ spec/fixtures/native-cache/file-1.js | 1 - spec/fixtures/native-cache/file-2.js | 1 - spec/fixtures/native-cache/file-3.js | 1 - spec/fixtures/native-cache/file-4.js | 1 - spec/native-compile-cache-spec.coffee | 104 ------------------ src/file-system-blob-store.js | 133 ----------------------- src/initialize-application-window.coffee | 5 +- src/initialize-test-window.coffee | 3 +- src/native-compile-cache.js | 118 -------------------- static/index.js | 6 +- 11 files changed, 5 insertions(+), 476 deletions(-) delete mode 100644 spec/file-system-blob-store-spec.coffee delete mode 100644 spec/fixtures/native-cache/file-1.js delete mode 100644 spec/fixtures/native-cache/file-2.js delete mode 100644 spec/fixtures/native-cache/file-3.js delete mode 100644 spec/fixtures/native-cache/file-4.js delete mode 100644 spec/native-compile-cache-spec.coffee delete mode 100644 src/file-system-blob-store.js delete mode 100644 src/native-compile-cache.js diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee deleted file mode 100644 index a2ed39014..000000000 --- a/spec/file-system-blob-store-spec.coffee +++ /dev/null @@ -1,108 +0,0 @@ -temp = require('temp').track() -path = require 'path' -fs = require 'fs-plus' -FileSystemBlobStore = require '../src/file-system-blob-store' - -describe "FileSystemBlobStore", -> - [storageDirectory, blobStore] = [] - - beforeEach -> - storageDirectory = temp.path('atom-spec-filesystemblobstore') - blobStore = FileSystemBlobStore.load(storageDirectory) - - afterEach -> - fs.removeSync(storageDirectory) - - it "is empty when the file doesn't exist", -> - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() - - it "allows to read and write buffers from/to memory without persisting them", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) - - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) - - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() - - it "persists buffers when saved and retrieves them on load, giving priority to in-memory ones", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) - blobStore.save() - - blobStore = FileSystemBlobStore.load(storageDirectory) - - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() - - blobStore.set("foo", "new-key", new Buffer("changed")) - - expect(blobStore.get("foo", "new-key")).toEqual(new Buffer("changed")) - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() - - it "persists both in-memory and previously stored buffers when saved", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) - blobStore.save() - - blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.set("bar", "invalidation-key-3", new Buffer("changed")) - blobStore.set("qux", "invalidation-key-4", new Buffer("qux")) - blobStore.save() - - blobStore = FileSystemBlobStore.load(storageDirectory) - - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-3")).toEqual(new Buffer("changed")) - expect(blobStore.get("qux", "invalidation-key-4")).toEqual(new Buffer("qux")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() - expect(blobStore.get("qux", "unexisting-key")).toBeUndefined() - - it "allows to delete keys from both memory and stored buffers", -> - blobStore.set("a", "invalidation-key-1", new Buffer("a")) - blobStore.set("b", "invalidation-key-2", new Buffer("b")) - blobStore.save() - - blobStore = FileSystemBlobStore.load(storageDirectory) - - blobStore.set("b", "invalidation-key-3", new Buffer("b")) - blobStore.set("c", "invalidation-key-4", new Buffer("c")) - blobStore.delete("b") - blobStore.delete("c") - blobStore.save() - - blobStore = FileSystemBlobStore.load(storageDirectory) - - expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("a")) - expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() - expect(blobStore.get("b", "invalidation-key-3")).toBeUndefined() - expect(blobStore.get("c", "invalidation-key-4")).toBeUndefined() - - it "ignores errors when loading an invalid blob store", -> - blobStore.set("a", "invalidation-key-1", new Buffer("a")) - blobStore.set("b", "invalidation-key-2", new Buffer("b")) - blobStore.save() - - # Simulate corruption - fs.writeFileSync(path.join(storageDirectory, "MAP"), new Buffer([0])) - fs.writeFileSync(path.join(storageDirectory, "INVKEYS"), new Buffer([0])) - fs.writeFileSync(path.join(storageDirectory, "BLOB"), new Buffer([0])) - - blobStore = FileSystemBlobStore.load(storageDirectory) - - expect(blobStore.get("a", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() - - blobStore.set("a", "invalidation-key-1", new Buffer("x")) - blobStore.set("b", "invalidation-key-2", new Buffer("y")) - blobStore.save() - - blobStore = FileSystemBlobStore.load(storageDirectory) - - expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("x")) - expect(blobStore.get("b", "invalidation-key-2")).toEqual(new Buffer("y")) diff --git a/spec/fixtures/native-cache/file-1.js b/spec/fixtures/native-cache/file-1.js deleted file mode 100644 index ce195a18e..000000000 --- a/spec/fixtures/native-cache/file-1.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = function () { return 1; } diff --git a/spec/fixtures/native-cache/file-2.js b/spec/fixtures/native-cache/file-2.js deleted file mode 100644 index e0cdf1485..000000000 --- a/spec/fixtures/native-cache/file-2.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = function () { return 2; } diff --git a/spec/fixtures/native-cache/file-3.js b/spec/fixtures/native-cache/file-3.js deleted file mode 100644 index 36ca6e14a..000000000 --- a/spec/fixtures/native-cache/file-3.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = function () { return 3; } diff --git a/spec/fixtures/native-cache/file-4.js b/spec/fixtures/native-cache/file-4.js deleted file mode 100644 index 1b8fd4e15..000000000 --- a/spec/fixtures/native-cache/file-4.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = function () { return "file-4" } diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee deleted file mode 100644 index 1531deaf9..000000000 --- a/spec/native-compile-cache-spec.coffee +++ /dev/null @@ -1,104 +0,0 @@ -fs = require 'fs' -path = require 'path' -Module = require 'module' - -describe "NativeCompileCache", -> - nativeCompileCache = require '../src/native-compile-cache' - [fakeCacheStore, cachedFiles] = [] - - beforeEach -> - cachedFiles = [] - fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]) - fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> - fakeCacheStore.get(cacheKey, invalidationKey)? - fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> - for entry in cachedFiles by -1 - continue if entry.cacheKey isnt cacheKey - continue if entry.invalidationKey isnt invalidationKey - return entry.cacheBuffer - return - fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) -> - cachedFiles.push({cacheKey, invalidationKey, cacheBuffer}) - - nativeCompileCache.setCacheStore(fakeCacheStore) - nativeCompileCache.setV8Version("a-v8-version") - nativeCompileCache.install() - - it "writes and reads from the cache storage when requiring files", -> - fn1 = require('./fixtures/native-cache/file-1') - fn2 = require('./fixtures/native-cache/file-2') - - expect(cachedFiles.length).toBe(2) - - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) - expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) - expect(fn1()).toBe(1) - - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-2')) - expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) - expect(fn2()).toBe(2) - - delete Module._cache[require.resolve('./fixtures/native-cache/file-1')] - fn1 = require('./fixtures/native-cache/file-1') - expect(cachedFiles.length).toBe(2) - expect(fn1()).toBe(1) - - describe "when v8 version changes", -> - it "updates the cache of previously required files", -> - nativeCompileCache.setV8Version("version-1") - fn4 = require('./fixtures/native-cache/file-4') - - expect(cachedFiles.length).toBe(1) - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) - expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) - expect(fn4()).toBe("file-4") - - nativeCompileCache.setV8Version("version-2") - delete Module._cache[require.resolve('./fixtures/native-cache/file-4')] - fn4 = require('./fixtures/native-cache/file-4') - - expect(cachedFiles.length).toBe(2) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) - expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) - expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) - - describe "when a previously required and cached file changes", -> - beforeEach -> - fs.writeFileSync path.resolve(__dirname + '/fixtures/native-cache/file-5'), """ - module.exports = function () { return "file-5" } - """ - - afterEach -> - fs.unlinkSync path.resolve(__dirname + '/fixtures/native-cache/file-5') - - it "removes it from the store and re-inserts it with the new cache", -> - fn5 = require('./fixtures/native-cache/file-5') - - expect(cachedFiles.length).toBe(1) - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) - expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) - expect(fn5()).toBe("file-5") - - delete Module._cache[require.resolve('./fixtures/native-cache/file-5')] - fs.appendFileSync(require.resolve('./fixtures/native-cache/file-5'), "\n\n") - fn5 = require('./fixtures/native-cache/file-5') - - expect(cachedFiles.length).toBe(2) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) - expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) - expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) - - it "deletes previously cached code when the cache is an invalid file", -> - fakeCacheStore.has.andReturn(true) - fakeCacheStore.get.andCallFake -> new Buffer("an invalid cache") - - fn3 = require('./fixtures/native-cache/file-3') - - expect(fakeCacheStore.delete).toHaveBeenCalledWith(require.resolve('./fixtures/native-cache/file-3')) - expect(fn3()).toBe(3) diff --git a/src/file-system-blob-store.js b/src/file-system-blob-store.js deleted file mode 100644 index 7bbbdcb14..000000000 --- a/src/file-system-blob-store.js +++ /dev/null @@ -1,133 +0,0 @@ -'use strict' - -const fs = require('fs-plus') -const path = require('path') - -module.exports = -class FileSystemBlobStore { - static load (directory) { - let instance = new FileSystemBlobStore(directory) - instance.load() - return instance - } - - constructor (directory) { - this.blobFilename = path.join(directory, 'BLOB') - this.blobMapFilename = path.join(directory, 'MAP') - this.invalidationKeysFilename = path.join(directory, 'INVKEYS') - this.lockFilename = path.join(directory, 'LOCK') - this.reset() - } - - reset () { - this.inMemoryBlobs = new Map() - this.invalidationKeys = {} - this.storedBlob = new Buffer(0) - this.storedBlobMap = {} - } - - load () { - if (!fs.existsSync(this.blobMapFilename)) { - return - } - if (!fs.existsSync(this.blobFilename)) { - return - } - if (!fs.existsSync(this.invalidationKeysFilename)) { - return - } - - try { - this.storedBlob = fs.readFileSync(this.blobFilename) - this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) - this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename)) - } catch (e) { - this.reset() - } - } - - save () { - let dump = this.getDump() - let blobToStore = Buffer.concat(dump[0]) - let mapToStore = JSON.stringify(dump[1]) - let invalidationKeysToStore = JSON.stringify(this.invalidationKeys) - - let acquiredLock = false - try { - fs.writeFileSync(this.lockFilename, 'LOCK', {flag: 'wx'}) - acquiredLock = true - - fs.writeFileSync(this.blobFilename, blobToStore) - fs.writeFileSync(this.blobMapFilename, mapToStore) - fs.writeFileSync(this.invalidationKeysFilename, invalidationKeysToStore) - } catch (error) { - // Swallow the exception silently only if we fail to acquire the lock. - if (error.code !== 'EEXIST') { - throw error - } - } finally { - if (acquiredLock) { - fs.unlinkSync(this.lockFilename) - } - } - } - - has (key, invalidationKey) { - let containsKey = this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) - let isValid = this.invalidationKeys[key] === invalidationKey - return containsKey && isValid - } - - get (key, invalidationKey) { - if (this.has(key, invalidationKey)) { - return this.getFromMemory(key) || this.getFromStorage(key) - } - } - - set (key, invalidationKey, buffer) { - this.invalidationKeys[key] = invalidationKey - return this.inMemoryBlobs.set(key, buffer) - } - - delete (key) { - this.inMemoryBlobs.delete(key) - delete this.storedBlobMap[key] - } - - getFromMemory (key) { - return this.inMemoryBlobs.get(key) - } - - getFromStorage (key) { - if (!this.storedBlobMap[key]) { - return - } - - return this.storedBlob.slice.apply(this.storedBlob, this.storedBlobMap[key]) - } - - getDump () { - let buffers = [] - let blobMap = {} - let currentBufferStart = 0 - - function dump (key, getBufferByKey) { - let buffer = getBufferByKey(key) - buffers.push(buffer) - blobMap[key] = [currentBufferStart, currentBufferStart + buffer.length] - currentBufferStart += buffer.length - } - - for (let key of this.inMemoryBlobs.keys()) { - dump(key, this.getFromMemory.bind(this)) - } - - for (let key of Object.keys(this.storedBlobMap)) { - if (!blobMap[key]) { - dump(key, this.getFromStorage.bind(this)) - } - } - - return [buffers, blobMap] - } -} diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 13562fae2..b855936e0 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -3,7 +3,6 @@ ApplicationDelegate = require './application-delegate' Clipboard = require './clipboard' TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' -FileSystemBlobStore = require './file-system-blob-store' CompileCache = require './compile-cache' ModuleCache = require './module-cache' @@ -55,7 +54,7 @@ require('whitespace') require('wrap-guide') # Like sands through the hourglass, so are the days of our lives. -module.exports = ({blobStore}) -> +module.exports = -> {updateProcessEnv} = require('./update-process-env') path = require 'path' require './window' @@ -76,7 +75,7 @@ module.exports = ({blobStore}) -> TextEditor.setClipboard(clipboard) window.atom = new AtomEnvironment({ - window, document, clipboard, blobStore, + window, document, clipboard, applicationDelegate: new ApplicationDelegate, configDirPath: process.env.ATOM_HOME, enablePersistence: true, diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee index 794db3174..3649fea94 100644 --- a/src/initialize-test-window.coffee +++ b/src/initialize-test-window.coffee @@ -5,7 +5,7 @@ cloneObject = (object) -> clone[key] = value for key, value of object clone -module.exports = ({blobStore}) -> +module.exports = -> startCrashReporter = require('./crash-reporter-start') {remote} = require 'electron' @@ -77,7 +77,6 @@ module.exports = ({blobStore}) -> buildAtomEnvironment = (params) -> params = cloneObject(params) params.clipboard = clipboard unless params.hasOwnProperty("clipboard") - params.blobStore = blobStore unless params.hasOwnProperty("blobStore") params.onlyLoadBaseStyleSheets = true unless params.hasOwnProperty("onlyLoadBaseStyleSheets") new AtomEnvironment(params) diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js deleted file mode 100644 index e4e7fc146..000000000 --- a/src/native-compile-cache.js +++ /dev/null @@ -1,118 +0,0 @@ -'use strict' - -const Module = require('module') -const path = require('path') -const cachedVm = require('cached-run-in-this-context') -const crypto = require('crypto') - -function computeHash (contents) { - return crypto.createHash('sha1').update(contents, 'utf8').digest('hex') -} - -class NativeCompileCache { - constructor () { - this.cacheStore = null - this.previousModuleCompile = null - } - - setCacheStore (store) { - this.cacheStore = store - } - - setV8Version (v8Version) { - this.v8Version = v8Version.toString() - } - - install () { - this.savePreviousModuleCompile() - this.overrideModuleCompile() - } - - uninstall () { - this.restorePreviousModuleCompile() - } - - savePreviousModuleCompile () { - this.previousModuleCompile = Module.prototype._compile - } - - overrideModuleCompile () { - let self = this - let resolvedArgv = null - // Here we override Node's module.js - // (https://github.com/atom/node/blob/atom/lib/module.js#L378), changing - // only the bits that affect compilation in order to use the cached one. - Module.prototype._compile = function (content, filename) { - let moduleSelf = this - // remove shebang - content = content.replace(/^#!.*/, '') - function require (path) { - return moduleSelf.require(path) - } - require.resolve = function (request) { - return Module._resolveFilename(request, moduleSelf) - } - require.main = process.mainModule - - // Enable support to add extra extension types - require.extensions = Module._extensions - require.cache = Module._cache - - let dirname = path.dirname(filename) - - // create wrapper function - let wrapper = Module.wrap(content) - - let cacheKey = filename - let invalidationKey = computeHash(wrapper + self.v8Version) - let compiledWrapper = null - if (self.cacheStore.has(cacheKey, invalidationKey)) { - let buffer = self.cacheStore.get(cacheKey, invalidationKey) - let compilationResult = cachedVm.runInThisContextCached(wrapper, filename, buffer) - compiledWrapper = compilationResult.result - if (compilationResult.wasRejected) { - self.cacheStore.delete(cacheKey) - } - } else { - let compilationResult - try { - compilationResult = cachedVm.runInThisContext(wrapper, filename) - } catch (err) { - console.error(`Error running script ${filename}`) - throw err - } - if (compilationResult.cacheBuffer) { - self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) - } - compiledWrapper = compilationResult.result - } - if (global.v8debug) { - if (!resolvedArgv) { - // we enter the repl if we're not given a filename argument. - if (process.argv[1]) { - resolvedArgv = Module._resolveFilename(process.argv[1], null) - } else { - resolvedArgv = 'repl' - } - } - - // Set breakpoint on module start - if (filename === resolvedArgv) { - // Installing this dummy debug event listener tells V8 to start - // the debugger. Without it, the setBreakPoint() fails with an - // 'illegal access' error. - global.v8debug.Debug.setListener(function () {}) - global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0) - } - } - let args = [moduleSelf.exports, require, moduleSelf, filename, dirname, process, global] - return compiledWrapper.apply(moduleSelf.exports, args) - } - } - - restorePreviousModuleCompile () { - Module.prototype._compile = this.previousModuleCompile - } -} - -module.exports = new NativeCompileCache() diff --git a/static/index.js b/static/index.js index c79055200..0da1730f8 100644 --- a/static/index.js +++ b/static/index.js @@ -5,8 +5,6 @@ const Module = require('module') const getWindowLoadSettings = require('../src/get-window-load-settings') const entryPointDirPath = __dirname - let blobStore = null - let devMode = false let useSnapshot = false window.onload = function () { @@ -21,7 +19,7 @@ process.resourcesPath = path.normalize(process.resourcesPath) setupAtomHome() - devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) + const devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) useSnapshot = !devMode && typeof snapshotResult !== 'undefined' if (devMode) { @@ -91,7 +89,7 @@ const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) const initialize = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) - return initialize({blobStore: blobStore}).then(function () { + return initialize().then(function () { electron.ipcRenderer.send('window-command', 'window:loaded') }) } From dcd9c48f63bce7351fa3218fa755a10fc2e48aba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Feb 2017 19:01:21 +0100 Subject: [PATCH 031/194] Use CompileCache `supportedExtensions` instead of `require.extensions` --- src/compile-cache.js | 2 +- src/package.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index e15674a2e..9b1966fc8 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -221,5 +221,5 @@ exports.install = function (nodeRequire) { }) } - +exports.supportedExtensions = Object.keys(COMPILERS) exports.resetCacheStats() diff --git a/src/package.coffee b/src/package.coffee index dea9c0e58..ed0f7aa87 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -503,7 +503,7 @@ class Package path.join(@path, @metadata.main) else path.join(@path, 'index') - @mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...]) + @mainModulePath = fs.resolveExtension(mainModulePath, ["", CompileCache.supportedExtensions...]) activationShouldBeDeferred: -> @hasActivationCommands() or @hasActivationHooks() From 5582766563fb293469f6e63d14da888ea2c51b7c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Feb 2017 10:48:25 -0800 Subject: [PATCH 032/194] Use new maxLineLength parameter to GrammarRegistry --- package.json | 2 +- src/grammar-registry.coffee | 2 +- src/tokenized-buffer.coffee | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 671875642..de3587aba 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "devtron": "1.3.0", "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", - "first-mate": "6.1.0", + "first-mate": "6.2.2-0", "fs-plus": "2.9.2", "fstream": "0.1.24", "fuzzaldrin": "^2.1", diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee index 899bb4cff..a2341c967 100644 --- a/src/grammar-registry.coffee +++ b/src/grammar-registry.coffee @@ -15,7 +15,7 @@ PathSplitRegex = new RegExp("[/.]") module.exports = class GrammarRegistry extends FirstMate.GrammarRegistry constructor: ({@config}={}) -> - super(maxTokensPerLine: 100) + super(maxTokensPerLine: 100, maxLineLength: 1000) createToken: (value, scopes) -> new Token({value, scopes}) diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 77221f52e..234f82be9 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -8,8 +8,6 @@ ScopeDescriptor = require './scope-descriptor' TokenizedBufferIterator = require './tokenized-buffer-iterator' NullGrammar = require './null-grammar' -MAX_LINE_LENGTH_TO_TOKENIZE = 500 - module.exports = class TokenizedBuffer extends Model grammar: null @@ -253,8 +251,6 @@ class TokenizedBuffer extends Model buildTokenizedLineForRowWithText: (row, text, ruleStack = @stackForRow(row - 1), openScopes = @openScopesForRow(row)) -> lineEnding = @buffer.lineEndingForRow(row) - if text.length > MAX_LINE_LENGTH_TO_TOKENIZE - text = text.slice(0, MAX_LINE_LENGTH_TO_TOKENIZE) {tags, ruleStack} = @grammar.tokenizeLine(text, ruleStack, row is 0, false) new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator}) From 507535e0702294c54a7f61bf9859b646aedf9a86 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Feb 2017 12:41:53 -0800 Subject: [PATCH 033/194] :arrow_up: first-mate (prerelease) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de3587aba..9465d321d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "devtron": "1.3.0", "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", - "first-mate": "6.2.2-0", + "first-mate": "6.2.2-1", "fs-plus": "2.9.2", "fstream": "0.1.24", "fuzzaldrin": "^2.1", From 9aae33a0f1db1f878e34573435fc158821c0d6f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Mar 2017 08:58:58 +0100 Subject: [PATCH 034/194] Use a new version of clear-cut that can be snapshotted --- package.json | 2 +- script/lib/generate-startup-snapshot.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 01eedc2eb..13812c36d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "cached-run-in-this-context": "0.4.1", "chai": "3.5.0", "chart.js": "^2.3.0", - "clear-cut": "^2.0.1", + "clear-cut": "^2.0.2", "coffee-script": "1.11.1", "color": "^0.7.3", "dedent": "^0.6.0", diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 479fbbbf0..cb8c4c231 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -35,9 +35,7 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'temp', 'lib', 'temp.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'tar', 'tar.js') || relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || - relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'helpers.js') || relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || - relativePath == path.join('..', 'node_modules', 'clear-cut', 'index.js') || relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'find-and-replace', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || From fabc38162b7e6b4b4ff003810853d523edc7a349 Mon Sep 17 00:00:00 2001 From: Wliu Date: Wed, 1 Mar 2017 17:32:31 -0500 Subject: [PATCH 035/194] Remove macOS emoji workaround No longer needed now that we are on Chrome 53 --- spec/workspace-element-spec.coffee | 2 -- src/workspace-element.coffee | 7 +------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/spec/workspace-element-spec.coffee b/spec/workspace-element-spec.coffee index a741dbbd4..ec24242ac 100644 --- a/spec/workspace-element-spec.coffee +++ b/spec/workspace-element-spec.coffee @@ -54,12 +54,10 @@ describe "WorkspaceElement", -> it "updates the font-family based on the 'editor.fontFamily' config value", -> initialCharWidth = editor.getDefaultCharWidth() fontFamily = atom.config.get('editor.fontFamily') - fontFamily += ', "Apple Color Emoji"' if process.platform is 'darwin' expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily atom.config.set('editor.fontFamily', 'sans-serif') fontFamily = atom.config.get('editor.fontFamily') - fontFamily += ', "Apple Color Emoji"' if process.platform is 'darwin' expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee index f598bef0b..6defe33da 100644 --- a/src/workspace-element.coffee +++ b/src/workspace-element.coffee @@ -44,15 +44,10 @@ class WorkspaceElement extends HTMLElement @subscriptions.add @config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this) updateGlobalTextEditorStyleSheet: -> - fontFamily = @config.get('editor.fontFamily') - # TODO: There is a bug in how some emojis (e.g. ❤️) are rendered on macOS. - # This workaround should be removed once we update to Chromium 51, where the - # problem was fixed. - fontFamily += ', "Apple Color Emoji"' if process.platform is 'darwin' styleSheetSource = """ atom-text-editor { font-size: #{@config.get('editor.fontSize')}px; - font-family: #{fontFamily}; + font-family: #{@config.get('editor.fontFamily')}; line-height: #{@config.get('editor.lineHeight')}; } """ From c9358c6f433efe942be1526ffd0b88ad14bd8879 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Mar 2017 16:05:59 -0800 Subject: [PATCH 036/194] :arrow_up: first-mate --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9465d321d..667506303 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "devtron": "1.3.0", "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", - "first-mate": "6.2.2-1", + "first-mate": "6.3.0", "fs-plus": "2.9.2", "fstream": "0.1.24", "fuzzaldrin": "^2.1", From 70c82b1ffaeea203c8710567d3bb4f4c3ea8fc4a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Mar 2017 17:57:33 +0100 Subject: [PATCH 037/194] Snapshot pathwatcher and fs-plus --- script/lib/generate-startup-snapshot.js | 12 ------------ static/index.js | 1 - 2 files changed, 13 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index cb8c4c231..3e2a56c38 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -30,7 +30,6 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'src', 'config-schema.js') || relativePath == path.join('..', 'src', 'electron-shims.js') || relativePath == path.join('..', 'src', 'safe-clipboard.js') || - relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'temp', 'lib', 'temp.js') || relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'tar', 'tar.js') || @@ -38,12 +37,8 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || - relativePath == path.join('..', 'node_modules', 'find-and-replace', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || - relativePath == path.join('..', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || relativePath == path.join('..', 'node_modules', 'debug', 'node.js') || - relativePath == path.join('..', 'node_modules', 'exception-reporting', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || - relativePath == path.join('..', 'node_modules', 'symbols-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || @@ -53,17 +48,13 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || relativePath == path.join('..', 'node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || - relativePath == path.join('..', 'node_modules', 'metrics', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || - relativePath == path.join('..', 'node_modules', 'notifications', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'superstring', 'index.js') || relativePath == path.join('..', 'node_modules', 'mime', 'mime.js') || relativePath == path.join('..', 'node_modules', 'oniguruma', 'lib', 'oniguruma.js') || - relativePath == path.join('..', 'node_modules', 'pathwatcher', 'lib', 'main.js') || relativePath == path.join('..', 'node_modules', 'request', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || - relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'htmlparser2', 'lib', 'index.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'request', 'index.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'cookie.js') || @@ -71,10 +62,7 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || relativePath == path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') || relativePath == path.join('..', 'node_modules', 'styleguide', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || - relativePath == path.join('..', 'node_modules', 'status-bar', 'node_modules', 'fs-plus', 'lib', 'fs-plus.js') || relativePath == path.join('..', 'node_modules', 'tar', 'tar.js') || - relativePath == path.join('..', 'node_modules', 'tree-view', 'node_modules', 'pathwatcher', 'lib', 'main.js') || - relativePath == path.join('..', 'node_modules', 'text-buffer', 'node_modules', 'pathwatcher', 'lib', 'main.js') || relativePath == path.join('..', 'node_modules', 'temp', 'lib', 'temp.js') || relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ) diff --git a/static/index.js b/static/index.js index 0da1730f8..5e5ddc347 100644 --- a/static/index.js +++ b/static/index.js @@ -1,7 +1,6 @@ (function () { const electron = require('electron') const path = require('path') - const fs = require('fs-plus') const Module = require('module') const getWindowLoadSettings = require('../src/get-window-load-settings') const entryPointDirPath = __dirname From d73547242b3d5b0b15613e419bd69eeea82578c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Mar 2017 08:29:45 +0100 Subject: [PATCH 038/194] Run script/bootstrap during script/build --- script/build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/build b/script/build index a5368768c..2b45e425d 100755 --- a/script/build +++ b/script/build @@ -4,7 +4,7 @@ // Run bootstrap first to ensure all the dependencies used later in this script // are installed. -// require('./bootstrap') +require('./bootstrap') // Needed so we can require src/module-cache.coffee during generateModuleCache require('coffee-script/register') From 8d46de418f2ad0022374300cab862c08eb563528 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Mar 2017 09:11:54 +0100 Subject: [PATCH 039/194] Re-enable native module cache --- spec/file-system-blob-store-spec.coffee | 109 ++++++++++++++++++ spec/fixtures/native-cache/file-1.js | 1 + spec/fixtures/native-cache/file-2.js | 1 + spec/fixtures/native-cache/file-3.js | 1 + spec/fixtures/native-cache/file-4.js | 1 + spec/native-compile-cache-spec.coffee | 104 +++++++++++++++++ src/atom-environment.coffee | 7 +- src/file-system-blob-store.js | 138 +++++++++++++++++++++++ src/initialize-application-window.coffee | 5 +- src/initialize-test-window.coffee | 3 +- src/native-compile-cache.js | 116 +++++++++++++++++++ static/index.js | 11 +- 12 files changed, 492 insertions(+), 5 deletions(-) create mode 100644 spec/file-system-blob-store-spec.coffee create mode 100644 spec/fixtures/native-cache/file-1.js create mode 100644 spec/fixtures/native-cache/file-2.js create mode 100644 spec/fixtures/native-cache/file-3.js create mode 100644 spec/fixtures/native-cache/file-4.js create mode 100644 spec/native-compile-cache-spec.coffee create mode 100644 src/file-system-blob-store.js create mode 100644 src/native-compile-cache.js diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee new file mode 100644 index 000000000..70e4d2b8d --- /dev/null +++ b/spec/file-system-blob-store-spec.coffee @@ -0,0 +1,109 @@ +temp = require('temp').track() +path = require 'path' +fs = require 'fs-plus' +FileSystemBlobStore = require '../src/file-system-blob-store' + +describe "FileSystemBlobStore", -> + [storageDirectory, blobStore] = [] + + beforeEach -> + storageDirectory = temp.path('atom-spec-filesystemblobstore') + blobStore = FileSystemBlobStore.load(storageDirectory) + + afterEach -> + fs.removeSync(storageDirectory) + + it "is empty when the file doesn't exist", -> + expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() + expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() + + it "allows to read and write buffers from/to memory without persisting them", -> + blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) + blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + + expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) + + expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() + expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() + + it "persists buffers when saved and retrieves them on load, giving priority to in-memory ones", -> + blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) + blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.save() + + blobStore = FileSystemBlobStore.load(storageDirectory) + + expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) + expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() + expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() + + blobStore.set("foo", "new-key", new Buffer("changed")) + + expect(blobStore.get("foo", "new-key")).toEqual(new Buffer("changed")) + expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() + + it "persists in-memory and previously stored buffers, and deletes unused keys when saved", -> + blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) + blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.save() + + blobStore = FileSystemBlobStore.load(storageDirectory) + blobStore.set("bar", "invalidation-key-3", new Buffer("changed")) + blobStore.set("qux", "invalidation-key-4", new Buffer("qux")) + blobStore.save() + + blobStore = FileSystemBlobStore.load(storageDirectory) + + expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() + expect(blobStore.get("bar", "invalidation-key-3")).toEqual(new Buffer("changed")) + expect(blobStore.get("qux", "invalidation-key-4")).toEqual(new Buffer("qux")) + expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() + expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("qux", "unexisting-key")).toBeUndefined() + + it "allows to delete keys from both memory and stored buffers", -> + blobStore.set("a", "invalidation-key-1", new Buffer("a")) + blobStore.set("b", "invalidation-key-2", new Buffer("b")) + blobStore.save() + + blobStore = FileSystemBlobStore.load(storageDirectory) + + blobStore.get("a", "invalidation-key-1") # prevent the key from being deleted on save + blobStore.set("b", "invalidation-key-3", new Buffer("b")) + blobStore.set("c", "invalidation-key-4", new Buffer("c")) + blobStore.delete("b") + blobStore.delete("c") + blobStore.save() + + blobStore = FileSystemBlobStore.load(storageDirectory) + + expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("a")) + expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("b", "invalidation-key-3")).toBeUndefined() + expect(blobStore.get("c", "invalidation-key-4")).toBeUndefined() + + it "ignores errors when loading an invalid blob store", -> + blobStore.set("a", "invalidation-key-1", new Buffer("a")) + blobStore.set("b", "invalidation-key-2", new Buffer("b")) + blobStore.save() + + # Simulate corruption + fs.writeFileSync(path.join(storageDirectory, "MAP"), new Buffer([0])) + fs.writeFileSync(path.join(storageDirectory, "INVKEYS"), new Buffer([0])) + fs.writeFileSync(path.join(storageDirectory, "BLOB"), new Buffer([0])) + + blobStore = FileSystemBlobStore.load(storageDirectory) + + expect(blobStore.get("a", "invalidation-key-1")).toBeUndefined() + expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() + + blobStore.set("a", "invalidation-key-1", new Buffer("x")) + blobStore.set("b", "invalidation-key-2", new Buffer("y")) + blobStore.save() + + blobStore = FileSystemBlobStore.load(storageDirectory) + + expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("x")) + expect(blobStore.get("b", "invalidation-key-2")).toEqual(new Buffer("y")) diff --git a/spec/fixtures/native-cache/file-1.js b/spec/fixtures/native-cache/file-1.js new file mode 100644 index 000000000..ce195a18e --- /dev/null +++ b/spec/fixtures/native-cache/file-1.js @@ -0,0 +1 @@ +module.exports = function () { return 1; } diff --git a/spec/fixtures/native-cache/file-2.js b/spec/fixtures/native-cache/file-2.js new file mode 100644 index 000000000..e0cdf1485 --- /dev/null +++ b/spec/fixtures/native-cache/file-2.js @@ -0,0 +1 @@ +module.exports = function () { return 2; } diff --git a/spec/fixtures/native-cache/file-3.js b/spec/fixtures/native-cache/file-3.js new file mode 100644 index 000000000..36ca6e14a --- /dev/null +++ b/spec/fixtures/native-cache/file-3.js @@ -0,0 +1 @@ +module.exports = function () { return 3; } diff --git a/spec/fixtures/native-cache/file-4.js b/spec/fixtures/native-cache/file-4.js new file mode 100644 index 000000000..1b8fd4e15 --- /dev/null +++ b/spec/fixtures/native-cache/file-4.js @@ -0,0 +1 @@ +module.exports = function () { return "file-4" } diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee new file mode 100644 index 000000000..1531deaf9 --- /dev/null +++ b/spec/native-compile-cache-spec.coffee @@ -0,0 +1,104 @@ +fs = require 'fs' +path = require 'path' +Module = require 'module' + +describe "NativeCompileCache", -> + nativeCompileCache = require '../src/native-compile-cache' + [fakeCacheStore, cachedFiles] = [] + + beforeEach -> + cachedFiles = [] + fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]) + fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> + fakeCacheStore.get(cacheKey, invalidationKey)? + fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> + for entry in cachedFiles by -1 + continue if entry.cacheKey isnt cacheKey + continue if entry.invalidationKey isnt invalidationKey + return entry.cacheBuffer + return + fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) -> + cachedFiles.push({cacheKey, invalidationKey, cacheBuffer}) + + nativeCompileCache.setCacheStore(fakeCacheStore) + nativeCompileCache.setV8Version("a-v8-version") + nativeCompileCache.install() + + it "writes and reads from the cache storage when requiring files", -> + fn1 = require('./fixtures/native-cache/file-1') + fn2 = require('./fixtures/native-cache/file-2') + + expect(cachedFiles.length).toBe(2) + + expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) + expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) + expect(fn1()).toBe(1) + + expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-2')) + expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) + expect(fn2()).toBe(2) + + delete Module._cache[require.resolve('./fixtures/native-cache/file-1')] + fn1 = require('./fixtures/native-cache/file-1') + expect(cachedFiles.length).toBe(2) + expect(fn1()).toBe(1) + + describe "when v8 version changes", -> + it "updates the cache of previously required files", -> + nativeCompileCache.setV8Version("version-1") + fn4 = require('./fixtures/native-cache/file-4') + + expect(cachedFiles.length).toBe(1) + expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) + expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) + expect(fn4()).toBe("file-4") + + nativeCompileCache.setV8Version("version-2") + delete Module._cache[require.resolve('./fixtures/native-cache/file-4')] + fn4 = require('./fixtures/native-cache/file-4') + + expect(cachedFiles.length).toBe(2) + expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) + expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) + expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) + + describe "when a previously required and cached file changes", -> + beforeEach -> + fs.writeFileSync path.resolve(__dirname + '/fixtures/native-cache/file-5'), """ + module.exports = function () { return "file-5" } + """ + + afterEach -> + fs.unlinkSync path.resolve(__dirname + '/fixtures/native-cache/file-5') + + it "removes it from the store and re-inserts it with the new cache", -> + fn5 = require('./fixtures/native-cache/file-5') + + expect(cachedFiles.length).toBe(1) + expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) + expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) + expect(fn5()).toBe("file-5") + + delete Module._cache[require.resolve('./fixtures/native-cache/file-5')] + fs.appendFileSync(require.resolve('./fixtures/native-cache/file-5'), "\n\n") + fn5 = require('./fixtures/native-cache/file-5') + + expect(cachedFiles.length).toBe(2) + expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) + expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) + expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) + + it "deletes previously cached code when the cache is an invalid file", -> + fakeCacheStore.has.andReturn(true) + fakeCacheStore.get.andCallFake -> new Buffer("an invalid cache") + + fn3 = require('./fixtures/native-cache/file-3') + + expect(fakeCacheStore.delete).toHaveBeenCalledWith(require.resolve('./fixtures/native-cache/file-3')) + expect(fn3()).toBe(3) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index dae45f7a2..2dfa736a2 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -131,7 +131,7 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@applicationDelegate, @window, @document, @blobStore, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params @unloaded = false @loadTime = null @@ -733,8 +733,13 @@ class AtomEnvironment extends Model @storeWindowBackground() @packages.deactivatePackages() + @saveBlobStoreSync() @unloaded = true + saveBlobStoreSync: -> + if @enablePersistence + @blobStore.save() + openInitialEmptyEditorIfNecessary: -> return unless @config.get('core.openEmptyEditorOnStart') if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0 diff --git a/src/file-system-blob-store.js b/src/file-system-blob-store.js new file mode 100644 index 000000000..828e23e94 --- /dev/null +++ b/src/file-system-blob-store.js @@ -0,0 +1,138 @@ +'use strict' + +const fs = require('fs-plus') +const path = require('path') + +module.exports = +class FileSystemBlobStore { + static load (directory) { + let instance = new FileSystemBlobStore(directory) + instance.load() + return instance + } + + constructor (directory) { + this.blobFilename = path.join(directory, 'BLOB') + this.blobMapFilename = path.join(directory, 'MAP') + this.invalidationKeysFilename = path.join(directory, 'INVKEYS') + this.lockFilename = path.join(directory, 'LOCK') + this.reset() + } + + reset () { + this.inMemoryBlobs = new Map() + this.invalidationKeys = {} + this.storedBlob = new Buffer(0) + this.storedBlobMap = {} + this.usedKeys = new Set() + } + + load () { + if (!fs.existsSync(this.blobMapFilename)) { + return + } + if (!fs.existsSync(this.blobFilename)) { + return + } + if (!fs.existsSync(this.invalidationKeysFilename)) { + return + } + + try { + this.storedBlob = fs.readFileSync(this.blobFilename) + this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) + this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename)) + } catch (e) { + this.reset() + } + } + + save () { + let dump = this.getDump() + let blobToStore = Buffer.concat(dump[0]) + let mapToStore = JSON.stringify(dump[1]) + let invalidationKeysToStore = JSON.stringify(this.invalidationKeys) + + let acquiredLock = false + try { + fs.writeFileSync(this.lockFilename, 'LOCK', {flag: 'wx'}) + acquiredLock = true + + fs.writeFileSync(this.blobFilename, blobToStore) + fs.writeFileSync(this.blobMapFilename, mapToStore) + fs.writeFileSync(this.invalidationKeysFilename, invalidationKeysToStore) + } catch (error) { + // Swallow the exception silently only if we fail to acquire the lock. + if (error.code !== 'EEXIST') { + throw error + } + } finally { + if (acquiredLock) { + fs.unlinkSync(this.lockFilename) + } + } + } + + has (key, invalidationKey) { + let containsKey = this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) + let isValid = this.invalidationKeys[key] === invalidationKey + return containsKey && isValid + } + + get (key, invalidationKey) { + if (this.has(key, invalidationKey)) { + this.usedKeys.add(key) + return this.getFromMemory(key) || this.getFromStorage(key) + } + } + + set (key, invalidationKey, buffer) { + this.usedKeys.add(key) + this.invalidationKeys[key] = invalidationKey + return this.inMemoryBlobs.set(key, buffer) + } + + delete (key) { + this.inMemoryBlobs.delete(key) + delete this.storedBlobMap[key] + } + + getFromMemory (key) { + return this.inMemoryBlobs.get(key) + } + + getFromStorage (key) { + if (!this.storedBlobMap[key]) { + return + } + + return this.storedBlob.slice.apply(this.storedBlob, this.storedBlobMap[key]) + } + + getDump () { + let buffers = [] + let blobMap = {} + let currentBufferStart = 0 + + function dump (key, getBufferByKey) { + let buffer = getBufferByKey(key) + buffers.push(buffer) + blobMap[key] = [currentBufferStart, currentBufferStart + buffer.length] + currentBufferStart += buffer.length + } + + for (let key of this.inMemoryBlobs.keys()) { + if (this.usedKeys.has(key)) { + dump(key, this.getFromMemory.bind(this)) + } + } + + for (let key of Object.keys(this.storedBlobMap)) { + if (!blobMap[key] && this.usedKeys.has(key)) { + dump(key, this.getFromStorage.bind(this)) + } + } + + return [buffers, blobMap] + } +} diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index b855936e0..13562fae2 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -3,6 +3,7 @@ ApplicationDelegate = require './application-delegate' Clipboard = require './clipboard' TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' +FileSystemBlobStore = require './file-system-blob-store' CompileCache = require './compile-cache' ModuleCache = require './module-cache' @@ -54,7 +55,7 @@ require('whitespace') require('wrap-guide') # Like sands through the hourglass, so are the days of our lives. -module.exports = -> +module.exports = ({blobStore}) -> {updateProcessEnv} = require('./update-process-env') path = require 'path' require './window' @@ -75,7 +76,7 @@ module.exports = -> TextEditor.setClipboard(clipboard) window.atom = new AtomEnvironment({ - window, document, clipboard, + window, document, clipboard, blobStore, applicationDelegate: new ApplicationDelegate, configDirPath: process.env.ATOM_HOME, enablePersistence: true, diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee index 3649fea94..794db3174 100644 --- a/src/initialize-test-window.coffee +++ b/src/initialize-test-window.coffee @@ -5,7 +5,7 @@ cloneObject = (object) -> clone[key] = value for key, value of object clone -module.exports = -> +module.exports = ({blobStore}) -> startCrashReporter = require('./crash-reporter-start') {remote} = require 'electron' @@ -77,6 +77,7 @@ module.exports = -> buildAtomEnvironment = (params) -> params = cloneObject(params) params.clipboard = clipboard unless params.hasOwnProperty("clipboard") + params.blobStore = blobStore unless params.hasOwnProperty("blobStore") params.onlyLoadBaseStyleSheets = true unless params.hasOwnProperty("onlyLoadBaseStyleSheets") new AtomEnvironment(params) diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js new file mode 100644 index 000000000..3f3d05991 --- /dev/null +++ b/src/native-compile-cache.js @@ -0,0 +1,116 @@ +const Module = require('module') +const path = require('path') +const cachedVm = require('cached-run-in-this-context') +const crypto = require('crypto') + +function computeHash (contents) { + return crypto.createHash('sha1').update(contents, 'utf8').digest('hex') +} + +class NativeCompileCache { + constructor () { + this.cacheStore = null + this.previousModuleCompile = null + } + + setCacheStore (store) { + this.cacheStore = store + } + + setV8Version (v8Version) { + this.v8Version = v8Version.toString() + } + + install () { + this.savePreviousModuleCompile() + this.overrideModuleCompile() + } + + uninstall () { + this.restorePreviousModuleCompile() + } + + savePreviousModuleCompile () { + this.previousModuleCompile = Module.prototype._compile + } + + overrideModuleCompile () { + let self = this + let resolvedArgv = null + // Here we override Node's module.js + // (https://github.com/atom/node/blob/atom/lib/module.js#L378), changing + // only the bits that affect compilation in order to use the cached one. + Module.prototype._compile = function (content, filename) { + let moduleSelf = this + // remove shebang + content = content.replace(/^#!.*/, '') + function require (path) { + return moduleSelf.require(path) + } + require.resolve = function (request) { + return Module._resolveFilename(request, moduleSelf) + } + require.main = process.mainModule + + // Enable support to add extra extension types + require.extensions = Module._extensions + require.cache = Module._cache + + let dirname = path.dirname(filename) + + // create wrapper function + let wrapper = Module.wrap(content) + + let cacheKey = filename + let invalidationKey = computeHash(wrapper + self.v8Version) + let compiledWrapper = null + if (self.cacheStore.has(cacheKey, invalidationKey)) { + let buffer = self.cacheStore.get(cacheKey, invalidationKey) + let compilationResult = cachedVm.runInThisContextCached(wrapper, filename, buffer) + compiledWrapper = compilationResult.result + if (compilationResult.wasRejected) { + self.cacheStore.delete(cacheKey) + } + } else { + let compilationResult + try { + compilationResult = cachedVm.runInThisContext(wrapper, filename) + } catch (err) { + console.error(`Error running script ${filename}`) + throw err + } + if (compilationResult.cacheBuffer) { + self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) + } + compiledWrapper = compilationResult.result + } + if (global.v8debug) { + if (!resolvedArgv) { + // we enter the repl if we're not given a filename argument. + if (process.argv[1]) { + resolvedArgv = Module._resolveFilename(process.argv[1], null) + } else { + resolvedArgv = 'repl' + } + } + + // Set breakpoint on module start + if (filename === resolvedArgv) { + // Installing this dummy debug event listener tells V8 to start + // the debugger. Without it, the setBreakPoint() fails with an + // 'illegal access' error. + global.v8debug.Debug.setListener(function () {}) + global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0) + } + } + let args = [moduleSelf.exports, require, moduleSelf, filename, dirname, process, global] + return compiledWrapper.apply(moduleSelf.exports, args) + } + } + + restorePreviousModuleCompile () { + Module.prototype._compile = this.previousModuleCompile + } +} + +module.exports = new NativeCompileCache() diff --git a/static/index.js b/static/index.js index 5e5ddc347..2154f72b1 100644 --- a/static/index.js +++ b/static/index.js @@ -4,6 +4,7 @@ const Module = require('module') const getWindowLoadSettings = require('../src/get-window-load-settings') const entryPointDirPath = __dirname + let blobStore = null let useSnapshot = false window.onload = function () { @@ -46,6 +47,14 @@ snapshotResult.entryPointDirPath = __dirname } + const FileSystemBlobStore = useSnapshot ? snapshotResult.customRequire('../src/file-system-blob-store.js') : require('../src/file-system-blob-store') + blobStore = FileSystemBlobStore.load(path.join(process.env.ATOM_HOME, 'blob-store')) + + const NativeCompileCache = useSnapshot ? snapshotResult.customRequire('../src/native-compile-cache.js') : require('../src/native-compile-cache') + NativeCompileCache.setCacheStore(blobStore) + NativeCompileCache.setV8Version(process.versions.v8) + NativeCompileCache.install() + if (getWindowLoadSettings().profileStartup) { profileStartup(Date.now() - startTime) } else { @@ -88,7 +97,7 @@ const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) const initialize = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) - return initialize().then(function () { + return initialize({blobStore: blobStore}).then(function () { electron.ipcRenderer.send('window-command', 'window:loaded') }) } From a9ada3535460567f924886a6b58fb970b6f2315e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Mar 2017 09:29:07 +0100 Subject: [PATCH 040/194] Snapshot NativeCompileCache --- script/lib/generate-startup-snapshot.js | 1 + src/initialize-application-window.coffee | 1 + src/native-compile-cache.js | 18 ------------------ static/index.js | 4 ++++ 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 3e2a56c38..fea976b11 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -35,6 +35,7 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'tar', 'tar.js') || relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || + relativePath == path.join('..', 'node_modules', 'cached-run-in-this-context', 'lib', 'main.js') || relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 13562fae2..f0ea0ed12 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -4,6 +4,7 @@ Clipboard = require './clipboard' TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' FileSystemBlobStore = require './file-system-blob-store' +NativeCompileCache = require './native-compile-cache' CompileCache = require './compile-cache' ModuleCache = require './module-cache' diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 3f3d05991..6a9f69053 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -84,25 +84,7 @@ class NativeCompileCache { } compiledWrapper = compilationResult.result } - if (global.v8debug) { - if (!resolvedArgv) { - // we enter the repl if we're not given a filename argument. - if (process.argv[1]) { - resolvedArgv = Module._resolveFilename(process.argv[1], null) - } else { - resolvedArgv = 'repl' - } - } - // Set breakpoint on module start - if (filename === resolvedArgv) { - // Installing this dummy debug event listener tells V8 to start - // the debugger. Without it, the setBreakPoint() fails with an - // 'illegal access' error. - global.v8debug.Debug.setListener(function () {}) - global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0) - } - } let args = [moduleSelf.exports, require, moduleSelf, filename, dirname, process, global] return compiledWrapper.apply(moduleSelf.exports, args) } diff --git a/static/index.js b/static/index.js index 2154f72b1..f407778dc 100644 --- a/static/index.js +++ b/static/index.js @@ -1,4 +1,8 @@ (function () { + // Eagerly require cached-run-in-this-context to prevent a circular require + // when using `NativeCompileCache` for the first time. + require('cached-run-in-this-context') + const electron = require('electron') const path = require('path') const Module = require('module') From 5a8aa54167548b86c664623e845ae9cff861c11f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Mar 2017 09:48:12 +0100 Subject: [PATCH 041/194] :arrow_up: electron-link --- script/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/package.json b/script/package.json index ed21ea906..56c656866 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.7", + "electron-link": "0.0.8", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", "fs-extra": "0.30.0", From 9e3999cab9de99688d8dcbce432ccdb7595238cf Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Mar 2017 10:04:07 -0700 Subject: [PATCH 042/194] Cause an assertion failure if defaultMarkerLayer is destroyed early This is to investigate a case where the default marker layer of the editor is destroyed without the editor itself or its buffer being destroyed, which is causing `Cannot decorate a destroyed marker` exceptions. --- src/text-editor.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 3cdd363a7..1f24fa252 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -192,6 +192,9 @@ class TextEditor extends Model @displayLayer.setTextDecorationLayer(@tokenizedBuffer) @defaultMarkerLayer = @displayLayer.addMarkerLayer() + @disposables.add(@defaultMarkerLayer.onDidDestroy => + @assert(false, "defaultMarkerLayer destroyed at an unexpected time") + ) @selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true) @selectionsMarkerLayer.trackDestructionInOnDidCreateMarkerCallbacks = true From c26509cfab267f888bad325f153d2fcb6eb9390c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 2 Mar 2017 10:14:59 -0800 Subject: [PATCH 043/194] Throw an error when assertions fail if built from source Signed-off-by: Nathan Sobo --- spec/atom-environment-spec.coffee | 6 ++++++ src/atom-environment.coffee | 2 ++ 2 files changed, 8 insertions(+) diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee index d967fb97b..d1eabf2c8 100644 --- a/spec/atom-environment-spec.coffee +++ b/spec/atom-environment-spec.coffee @@ -126,6 +126,7 @@ describe "AtomEnvironment", -> beforeEach -> errors = [] + spyOn(atom, 'isReleasedVersion').andReturn(true) atom.onDidFailAssertion (error) -> errors.push(error) describe "if the condition is false", -> @@ -147,6 +148,11 @@ describe "AtomEnvironment", -> atom.assert(false, "a == b", {foo: 'bar'}) expect(errors[0].metadata).toEqual {foo: 'bar'} + describe "when Atom has been built from source", -> + it "throws an error", -> + atom.isReleasedVersion.andReturn(false) + expect(-> atom.assert(false, 'testing')).toThrow('Assertion failed: testing') + describe "if the condition is true", -> it "does nothing", -> result = atom.assert(true, "a == b") diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 3133b5af8..dc4318bec 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -840,6 +840,8 @@ class AtomEnvironment extends Model error.metadata = callbackOrMetadata @emitter.emit 'did-fail-assertion', error + unless @isReleasedVersion() + throw error false From d6981dfcab63129143b366efffd964eaa94d76f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 2 Mar 2017 10:19:48 -0800 Subject: [PATCH 044/194] Avoid throwing intentional errors in DOMElementPool test --- spec/dom-element-pool-spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/dom-element-pool-spec.js b/spec/dom-element-pool-spec.js index 9de932e27..91120ee48 100644 --- a/spec/dom-element-pool-spec.js +++ b/spec/dom-element-pool-spec.js @@ -3,7 +3,10 @@ const DOMElementPool = require ('../src/dom-element-pool') describe('DOMElementPool', function () { let domElementPool - beforeEach(() => { domElementPool = new DOMElementPool() }) + beforeEach(() => { + domElementPool = new DOMElementPool() + spyOn(atom, 'isReleasedVersion').andReturn(true) + }) it('builds DOM nodes, recycling them when they are freed', function () { let elements From c1d1bbcb18fe74fd3d29f1b859fcf30a67740b9c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 2 Mar 2017 11:54:00 -0800 Subject: [PATCH 045/194] Provide a fresh environment when deserializing in specs --- spec/text-editor-spec.coffee | 8 +++- spec/workspace-spec.coffee | 72 +++++++++++++++++++++++------------- src/text-editor.coffee | 2 +- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 911270d16..81c69f63f 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -28,7 +28,13 @@ describe "TextEditor", -> editor.foldBufferRow(4) expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - editor2 = TextEditor.deserialize(editor.serialize(), atom) + editor2 = TextEditor.deserialize(editor.serialize(), { + assert: atom.assert, + textEditors: atom.textEditors, + project: { + bufferForIdSync: (id) -> TextBuffer.deserialize(editor.buffer.serialize()) + } + }) expect(editor2.id).toBe editor.id expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath() diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index 153cc5dc3..8e4da1185 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -7,6 +7,7 @@ platform = require './spec-helper-platform' _ = require 'underscore-plus' fstream = require 'fstream' fs = require 'fs-plus' +AtomEnvironment = require '../src/atom-environment' describe "Workspace", -> [workspace, setDocumentEdited] = [] @@ -865,24 +866,35 @@ describe "Workspace", -> i = /test/; #FIXME """ - state = atom.workspace.serialize() - expect(state.packagesWithActiveGrammars).toEqual ['language-coffee-script', 'language-javascript', 'language-todo'] - - jsPackage = atom.packages.getLoadedPackage('language-javascript') - coffeePackage = atom.packages.getLoadedPackage('language-coffee-script') - spyOn(jsPackage, 'loadGrammarsSync') - spyOn(coffeePackage, 'loadGrammarsSync') - - workspace2 = new Workspace({ - config: atom.config, project: atom.project, packageManager: atom.packages, - notificationManager: atom.notifications, deserializerManager: atom.deserializers, - viewRegistry: atom.views, grammarRegistry: atom.grammars, - applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom), - textEditorRegistry: atom.textEditors + atom2 = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + window: document.createElement('div'), + document: Object.assign( + document.createElement('div'), + { + body: document.createElement('div'), + head: document.createElement('div'), + } + ) }) - workspace2.deserialize(state, atom.deserializers) - expect(jsPackage.loadGrammarsSync.callCount).toBe 1 - expect(coffeePackage.loadGrammarsSync.callCount).toBe 1 + + atom2.packages.loadPackage('language-javascript') + atom2.packages.loadPackage('language-coffee-script') + atom2.packages.loadPackage('language-todo') + atom2.project.deserialize(atom.project.serialize()) + atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers) + + expect(atom2.grammars.getGrammars().map((grammar) -> grammar.name).sort()).toEqual([ + 'CoffeeScript', + 'CoffeeScript (Literate)', + 'JavaScript', + 'Null Grammar', + 'Regular Expression Replacement (JavaScript)', + 'Regular Expressions (JavaScript)', + 'TODO' + ]) + + atom2.destroy() describe "document.title", -> describe "when there is no item open", -> @@ -971,18 +983,26 @@ describe "Workspace", -> it "updates the title to contain the project's path", -> document.title = null - workspace2 = new Workspace({ - config: atom.config, project: atom.project, packageManager: atom.packages, - notificationManager: atom.notifications, deserializerManager: atom.deserializers, - viewRegistry: atom.views, grammarRegistry: atom.grammars, - applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom), - textEditorRegistry: atom.textEditors + + atom2 = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + window: document.createElement('div'), + document: Object.assign( + document.createElement('div'), + { + body: document.createElement('div'), + head: document.createElement('div'), + } + ) }) - workspace2.deserialize(atom.workspace.serialize(), atom.deserializers) - item = workspace2.getActivePaneItem() + + atom2.project.deserialize(atom.project.serialize()) + atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers) + item = atom2.workspace.getActivePaneItem() pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{pathEscaped}/// - workspace2.destroy() + + atom2.destroy() describe "document edited status", -> [item1, item2] = [] diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 1f24fa252..8095632fd 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -387,7 +387,7 @@ class TextEditor extends Model softWrapHangingIndentLength: @displayLayer.softWrapHangingIndent @id, @softTabs, @softWrapped, @softWrapAtPreferredLineLength, - @preferredLineLength, @mini, @editorWidthInChars, @width, @largeFileMode, + @preferredLineLength, @mini, @editorWidthInChars, @width, @largeFileMode, @registered, @invisibles, @showInvisibles, @showIndentGuide, @autoHeight, @autoWidth } From 6262a0cb67bf64e4b9d29d5032be54a58c9d93f3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 2 Mar 2017 12:01:20 -0800 Subject: [PATCH 046/194] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 667506303..79e8f5933 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "dev-live-reload": "0.47.0", "encoding-selector": "0.23.2", "exception-reporting": "0.41.1", - "find-and-replace": "0.206.3", + "find-and-replace": "0.207.0", "fuzzy-finder": "1.4.1", "git-diff": "1.3.3", "go-to-line": "0.32.0", From 920655419f20379b4510e3d0ef8ac4909700970a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 2 Mar 2017 16:25:42 -0800 Subject: [PATCH 047/194] :arrow_up: language-gfm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79e8f5933..10a5f57f6 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "language-coffee-script": "0.48.4", "language-csharp": "0.14.2", "language-css": "0.42.0", - "language-gfm": "0.88.0", + "language-gfm": "0.88.1", "language-git": "0.19.0", "language-go": "0.43.1", "language-html": "0.47.2", From cb71ce64ae57fd40eeef6d522764203349d95230 Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Thu, 2 Mar 2017 14:31:20 -0800 Subject: [PATCH 048/194] Convert workspace to JavaScript: decaffeinate --- spec/workspace-spec.coffee | 1798 -------------------------------- spec/workspace-spec.js | 1977 ++++++++++++++++++++++++++++++++++++ src/workspace.coffee | 1121 -------------------- src/workspace.js | 1314 ++++++++++++++++++++++++ 4 files changed, 3291 insertions(+), 2919 deletions(-) delete mode 100644 spec/workspace-spec.coffee create mode 100644 spec/workspace-spec.js delete mode 100644 src/workspace.coffee create mode 100644 src/workspace.js diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee deleted file mode 100644 index 8e4da1185..000000000 --- a/spec/workspace-spec.coffee +++ /dev/null @@ -1,1798 +0,0 @@ -path = require 'path' -temp = require('temp').track() -TextEditor = require '../src/text-editor' -Workspace = require '../src/workspace' -Project = require '../src/project' -platform = require './spec-helper-platform' -_ = require 'underscore-plus' -fstream = require 'fstream' -fs = require 'fs-plus' -AtomEnvironment = require '../src/atom-environment' - -describe "Workspace", -> - [workspace, setDocumentEdited] = [] - - beforeEach -> - workspace = atom.workspace - workspace.resetFontSize() - spyOn(atom.applicationDelegate, "confirm") - setDocumentEdited = spyOn(atom.applicationDelegate, 'setWindowDocumentEdited') - atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')]) - waits(1) - - afterEach -> - temp.cleanupSync() - - describe "serialization", -> - simulateReload = -> - workspaceState = atom.workspace.serialize() - projectState = atom.project.serialize({isUnloading: true}) - atom.workspace.destroy() - atom.project.destroy() - atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom), applicationDelegate: atom.applicationDelegate}) - atom.project.deserialize(projectState) - atom.workspace = new Workspace({ - config: atom.config, project: atom.project, packageManager: atom.packages, - grammarRegistry: atom.grammars, deserializerManager: atom.deserializers, - notificationManager: atom.notifications, - applicationDelegate: atom.applicationDelegate, - viewRegistry: atom.views, assert: atom.assert.bind(atom), - textEditorRegistry: atom.textEditors - }) - atom.workspace.deserialize(workspaceState, atom.deserializers) - - describe "when the workspace contains text editors", -> - it "constructs the view with the same panes", -> - pane1 = atom.workspace.getActivePane() - pane2 = pane1.splitRight(copyActiveItem: true) - pane3 = pane2.splitRight(copyActiveItem: true) - pane4 = null - - waitsForPromise -> - atom.workspace.open(null).then (editor) -> editor.setText("An untitled editor.") - - waitsForPromise -> - atom.workspace.open('b').then (editor) -> - pane2.activateItem(editor.copy()) - - waitsForPromise -> - atom.workspace.open('../sample.js').then (editor) -> - pane3.activateItem(editor) - - runs -> - pane3.activeItem.setCursorScreenPosition([2, 4]) - pane4 = pane2.splitDown() - - waitsForPromise -> - atom.workspace.open('../sample.txt').then (editor) -> - pane4.activateItem(editor) - - runs -> - pane4.getActiveItem().setCursorScreenPosition([0, 2]) - pane2.activate() - - simulateReload() - - expect(atom.workspace.getTextEditors().length).toBe 5 - [editor1, editor2, untitledEditor, editor3, editor4] = atom.workspace.getTextEditors() - expect(editor1.getPath()).toBe atom.project.getDirectories()[0]?.resolve('b') - expect(editor2.getPath()).toBe atom.project.getDirectories()[0]?.resolve('../sample.txt') - expect(editor2.getCursorScreenPosition()).toEqual [0, 2] - expect(editor3.getPath()).toBe atom.project.getDirectories()[0]?.resolve('b') - expect(editor4.getPath()).toBe atom.project.getDirectories()[0]?.resolve('../sample.js') - expect(editor4.getCursorScreenPosition()).toEqual [2, 4] - expect(untitledEditor.getPath()).toBeUndefined() - expect(untitledEditor.getText()).toBe("An untitled editor.") - - expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath() - pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{pathEscaped}/// - - describe "where there are no open panes or editors", -> - it "constructs the view with no open editors", -> - atom.workspace.getActivePane().destroy() - expect(atom.workspace.getTextEditors().length).toBe 0 - simulateReload() - expect(atom.workspace.getTextEditors().length).toBe 0 - - describe "::open(uri, options)", -> - openEvents = null - - beforeEach -> - openEvents = [] - workspace.onDidOpen (event) -> openEvents.push(event) - spyOn(workspace.getActivePane(), 'activate').andCallThrough() - - describe "when the 'searchAllPanes' option is false (default)", -> - describe "when called without a uri", -> - it "adds and activates an empty editor on the active pane", -> - [editor1, editor2] = [] - - waitsForPromise -> - workspace.open().then (editor) -> editor1 = editor - - runs -> - expect(editor1.getPath()).toBeUndefined() - expect(workspace.getActivePane().items).toEqual [editor1] - expect(workspace.getActivePaneItem()).toBe editor1 - expect(workspace.getActivePane().activate).toHaveBeenCalled() - expect(openEvents).toEqual [{uri: undefined, pane: workspace.getActivePane(), item: editor1, index: 0}] - openEvents = [] - - waitsForPromise -> - workspace.open().then (editor) -> editor2 = editor - - runs -> - expect(editor2.getPath()).toBeUndefined() - expect(workspace.getActivePane().items).toEqual [editor1, editor2] - expect(workspace.getActivePaneItem()).toBe editor2 - expect(workspace.getActivePane().activate).toHaveBeenCalled() - expect(openEvents).toEqual [{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}] - - describe "when called with a uri", -> - describe "when the active pane already has an editor for the given uri", -> - it "activates the existing editor on the active pane", -> - editor = null - editor1 = null - editor2 = null - - waitsForPromise -> - workspace.open('a').then (o) -> - editor1 = o - workspace.open('b').then (o) -> - editor2 = o - workspace.open('a').then (o) -> - editor = o - - runs -> - expect(editor).toBe editor1 - expect(workspace.getActivePaneItem()).toBe editor - expect(workspace.getActivePane().activate).toHaveBeenCalled() - - expect(openEvents).toEqual [ - { - uri: atom.project.getDirectories()[0]?.resolve('a') - item: editor1 - pane: atom.workspace.getActivePane() - index: 0 - } - { - uri: atom.project.getDirectories()[0]?.resolve('b') - item: editor2 - pane: atom.workspace.getActivePane() - index: 1 - } - { - uri: atom.project.getDirectories()[0]?.resolve('a') - item: editor1 - pane: atom.workspace.getActivePane() - index: 0 - } - ] - - describe "when the active pane does not have an editor for the given uri", -> - it "adds and activates a new editor for the given path on the active pane", -> - editor = null - waitsForPromise -> - workspace.open('a').then (o) -> editor = o - - runs -> - expect(editor.getURI()).toBe atom.project.getDirectories()[0]?.resolve('a') - expect(workspace.getActivePaneItem()).toBe editor - expect(workspace.getActivePane().items).toEqual [editor] - expect(workspace.getActivePane().activate).toHaveBeenCalled() - - describe "when the 'searchAllPanes' option is true", -> - describe "when an editor for the given uri is already open on an inactive pane", -> - it "activates the existing editor on the inactive pane, then activates that pane", -> - editor1 = null - editor2 = null - pane1 = workspace.getActivePane() - pane2 = workspace.getActivePane().splitRight() - - waitsForPromise -> - pane1.activate() - workspace.open('a').then (o) -> editor1 = o - - waitsForPromise -> - pane2.activate() - workspace.open('b').then (o) -> editor2 = o - - runs -> - expect(workspace.getActivePaneItem()).toBe editor2 - - waitsForPromise -> - workspace.open('a', searchAllPanes: true) - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(workspace.getActivePaneItem()).toBe editor1 - - describe "when no editor for the given uri is open in any pane", -> - it "opens an editor for the given uri in the active pane", -> - editor = null - waitsForPromise -> - workspace.open('a', searchAllPanes: true).then (o) -> editor = o - - runs -> - expect(workspace.getActivePaneItem()).toBe editor - - describe "when the 'split' option is set", -> - describe "when the 'split' option is 'left'", -> - it "opens the editor in the leftmost pane of the current pane axis", -> - pane1 = workspace.getActivePane() - pane2 = pane1.splitRight() - expect(workspace.getActivePane()).toBe pane2 - - editor = null - waitsForPromise -> - workspace.open('a', split: 'left').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(pane1.items).toEqual [editor] - expect(pane2.items).toEqual [] - - # Focus right pane and reopen the file on the left - waitsForPromise -> - pane2.focus() - workspace.open('a', split: 'left').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(pane1.items).toEqual [editor] - expect(pane2.items).toEqual [] - - describe "when a pane axis is the leftmost sibling of the current pane", -> - it "opens the new item in the current pane", -> - editor = null - pane1 = workspace.getActivePane() - pane2 = pane1.splitLeft() - pane3 = pane2.splitDown() - pane1.activate() - expect(workspace.getActivePane()).toBe pane1 - - waitsForPromise -> - workspace.open('a', split: 'left').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(pane1.items).toEqual [editor] - - describe "when the 'split' option is 'right'", -> - it "opens the editor in the rightmost pane of the current pane axis", -> - editor = null - pane1 = workspace.getActivePane() - pane2 = null - waitsForPromise -> - workspace.open('a', split: 'right').then (o) -> editor = o - - runs -> - pane2 = workspace.getPanes().filter((p) -> p isnt pane1)[0] - expect(workspace.getActivePane()).toBe pane2 - expect(pane1.items).toEqual [] - expect(pane2.items).toEqual [editor] - - # Focus right pane and reopen the file on the right - waitsForPromise -> - pane1.focus() - workspace.open('a', split: 'right').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane2 - expect(pane1.items).toEqual [] - expect(pane2.items).toEqual [editor] - - describe "when a pane axis is the rightmost sibling of the current pane", -> - it "opens the new item in a new pane split to the right of the current pane", -> - editor = null - pane1 = workspace.getActivePane() - pane2 = pane1.splitRight() - pane3 = pane2.splitDown() - pane1.activate() - expect(workspace.getActivePane()).toBe pane1 - pane4 = null - - waitsForPromise -> - workspace.open('a', split: 'right').then (o) -> editor = o - - runs -> - pane4 = workspace.getPanes().filter((p) -> p isnt pane1)[0] - expect(workspace.getActivePane()).toBe pane4 - expect(pane4.items).toEqual [editor] - expect(workspace.paneContainer.root.children[0]).toBe pane1 - expect(workspace.paneContainer.root.children[1]).toBe pane4 - - describe "when the 'split' option is 'up'", -> - it "opens the editor in the topmost pane of the current pane axis", -> - pane1 = workspace.getActivePane() - pane2 = pane1.splitDown() - expect(workspace.getActivePane()).toBe pane2 - - editor = null - waitsForPromise -> - workspace.open('a', split: 'up').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(pane1.items).toEqual [editor] - expect(pane2.items).toEqual [] - - # Focus bottom pane and reopen the file on the top - waitsForPromise -> - pane2.focus() - workspace.open('a', split: 'up').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(pane1.items).toEqual [editor] - expect(pane2.items).toEqual [] - - describe "when a pane axis is the topmost sibling of the current pane", -> - it "opens the new item in the current pane", -> - editor = null - pane1 = workspace.getActivePane() - pane2 = pane1.splitUp() - pane3 = pane2.splitRight() - pane1.activate() - expect(workspace.getActivePane()).toBe pane1 - - waitsForPromise -> - workspace.open('a', split: 'up').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane1 - expect(pane1.items).toEqual [editor] - - describe "when the 'split' option is 'down'", -> - it "opens the editor in the bottommost pane of the current pane axis", -> - editor = null - pane1 = workspace.getActivePane() - pane2 = null - waitsForPromise -> - workspace.open('a', split: 'down').then (o) -> editor = o - - runs -> - pane2 = workspace.getPanes().filter((p) -> p isnt pane1)[0] - expect(workspace.getActivePane()).toBe pane2 - expect(pane1.items).toEqual [] - expect(pane2.items).toEqual [editor] - - # Focus bottom pane and reopen the file on the right - waitsForPromise -> - pane1.focus() - workspace.open('a', split: 'down').then (o) -> editor = o - - runs -> - expect(workspace.getActivePane()).toBe pane2 - expect(pane1.items).toEqual [] - expect(pane2.items).toEqual [editor] - - describe "when a pane axis is the bottommost sibling of the current pane", -> - it "opens the new item in a new pane split to the bottom of the current pane", -> - editor = null - pane1 = workspace.getActivePane() - pane2 = pane1.splitDown() - pane1.activate() - expect(workspace.getActivePane()).toBe pane1 - pane4 = null - - waitsForPromise -> - workspace.open('a', split: 'down').then (o) -> editor = o - - runs -> - pane4 = workspace.getPanes().filter((p) -> p isnt pane1)[0] - expect(workspace.getActivePane()).toBe pane4 - expect(pane4.items).toEqual [editor] - expect(workspace.paneContainer.root.children[0]).toBe pane1 - expect(workspace.paneContainer.root.children[1]).toBe pane2 - - describe "when an initialLine and initialColumn are specified", -> - it "moves the cursor to the indicated location", -> - waitsForPromise -> - workspace.open('a', initialLine: 1, initialColumn: 5) - - runs -> - expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual [1, 5] - - waitsForPromise -> - workspace.open('a', initialLine: 2, initialColumn: 4) - - runs -> - expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual [2, 4] - - waitsForPromise -> - workspace.open('a', initialLine: 0, initialColumn: 0) - - runs -> - expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual [0, 0] - - waitsForPromise -> - workspace.open('a', initialLine: NaN, initialColumn: 4) - - runs -> - expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual [0, 4] - - waitsForPromise -> - workspace.open('a', initialLine: 2, initialColumn: NaN) - - runs -> - expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual [2, 0] - - waitsForPromise -> - workspace.open('a', initialLine: Infinity, initialColumn: Infinity) - - runs -> - expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual [2, 11] - - describe "when the file is over 2MB", -> - it "opens the editor with largeFileMode: true", -> - spyOn(fs, 'getSizeSync').andReturn 2 * 1048577 # 2MB - - editor = null - waitsForPromise -> - workspace.open('sample.js').then (e) -> editor = e - - runs -> - expect(editor.largeFileMode).toBe true - - describe "when the file is over user-defined limit", -> - shouldPromptForFileOfSize = (size, shouldPrompt) -> - spyOn(fs, 'getSizeSync').andReturn size * 1048577 - atom.applicationDelegate.confirm.andCallFake -> selectedButtonIndex - atom.applicationDelegate.confirm() - selectedButtonIndex = 1 # cancel - - editor = null - waitsForPromise -> - workspace.open('sample.js').then (e) -> editor = e - if shouldPrompt - runs -> - expect(editor).toBeUndefined() - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - - atom.applicationDelegate.confirm.reset() - selectedButtonIndex = 0 # open the file - - waitsForPromise -> - workspace.open('sample.js').then (e) -> editor = e - - runs -> - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - expect(editor.largeFileMode).toBe true - else - runs -> - expect(editor).not.toBeUndefined() - - it "prompts the user to make sure they want to open a file this big", -> - atom.config.set "core.warnOnLargeFileLimit", 20 - shouldPromptForFileOfSize 20, true - - it "doesn't prompt on files below the limit", -> - atom.config.set "core.warnOnLargeFileLimit", 30 - shouldPromptForFileOfSize 20, false - - it "prompts for smaller files with a lower limit", -> - atom.config.set "core.warnOnLargeFileLimit", 5 - shouldPromptForFileOfSize 10, true - - describe "when passed a path that matches a custom opener", -> - it "returns the resource returned by the custom opener", -> - fooOpener = (pathToOpen, options) -> {foo: pathToOpen, options} if pathToOpen?.match(/\.foo/) - barOpener = (pathToOpen) -> {bar: pathToOpen} if pathToOpen?.match(/^bar:\/\//) - workspace.addOpener(fooOpener) - workspace.addOpener(barOpener) - - waitsForPromise -> - pathToOpen = atom.project.getDirectories()[0]?.resolve('a.foo') - workspace.open(pathToOpen, hey: "there").then (item) -> - expect(item).toEqual {foo: pathToOpen, options: {hey: "there"}} - - waitsForPromise -> - workspace.open("bar://baz").then (item) -> - expect(item).toEqual {bar: "bar://baz"} - - it "adds the file to the application's recent documents list", -> - return unless process.platform is 'darwin' # Feature only supported on macOS - spyOn(atom.applicationDelegate, 'addRecentDocument') - - waitsForPromise -> - workspace.open() - - runs -> - expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled() - - waitsForPromise -> - workspace.open('something://a/url') - - runs -> - expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled() - - waitsForPromise -> - workspace.open(__filename) - - runs -> - expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith(__filename) - - it "notifies ::onDidAddTextEditor observers", -> - absolutePath = require.resolve('./fixtures/dir/a') - newEditorHandler = jasmine.createSpy('newEditorHandler') - workspace.onDidAddTextEditor newEditorHandler - - editor = null - waitsForPromise -> - workspace.open(absolutePath).then (e) -> editor = e - - runs -> - expect(newEditorHandler.argsForCall[0][0].textEditor).toBe editor - - describe "when there is an error opening the file", -> - notificationSpy = null - beforeEach -> - atom.notifications.onDidAddNotification notificationSpy = jasmine.createSpy() - - describe "when a file does not exist", -> - it "creates an empty buffer for the specified path", -> - waitsForPromise -> - workspace.open('not-a-file.md') - - runs -> - editor = workspace.getActiveTextEditor() - expect(notificationSpy).not.toHaveBeenCalled() - expect(editor.getPath()).toContain 'not-a-file.md' - - describe "when the user does not have access to the file", -> - beforeEach -> - spyOn(fs, 'openSync').andCallFake (path) -> - error = new Error("EACCES, permission denied '#{path}'") - error.path = path - error.code = 'EACCES' - throw error - - it "creates a notification", -> - waitsForPromise -> - workspace.open('file1') - - runs -> - expect(notificationSpy).toHaveBeenCalled() - notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe 'warning' - expect(notification.getMessage()).toContain 'Permission denied' - expect(notification.getMessage()).toContain 'file1' - - describe "when the the operation is not permitted", -> - beforeEach -> - spyOn(fs, 'openSync').andCallFake (path) -> - error = new Error("EPERM, operation not permitted '#{path}'") - error.path = path - error.code = 'EPERM' - throw error - - it "creates a notification", -> - waitsForPromise -> - workspace.open('file1') - - runs -> - expect(notificationSpy).toHaveBeenCalled() - notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe 'warning' - expect(notification.getMessage()).toContain 'Unable to open' - expect(notification.getMessage()).toContain 'file1' - - describe "when the the file is already open in windows", -> - beforeEach -> - spyOn(fs, 'openSync').andCallFake (path) -> - error = new Error("EBUSY, resource busy or locked '#{path}'") - error.path = path - error.code = 'EBUSY' - throw error - - it "creates a notification", -> - waitsForPromise -> - workspace.open('file1') - - runs -> - expect(notificationSpy).toHaveBeenCalled() - notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe 'warning' - expect(notification.getMessage()).toContain 'Unable to open' - expect(notification.getMessage()).toContain 'file1' - - describe "when there is an unhandled error", -> - beforeEach -> - spyOn(fs, 'openSync').andCallFake (path) -> - throw new Error("I dont even know what is happening right now!!") - - it "creates a notification", -> - open = -> workspace.open('file1', workspace.getActivePane()) - expect(open).toThrow() - - describe "when the file is already open in pending state", -> - it "should terminate the pending state", -> - editor = null - pane = null - - waitsForPromise -> - atom.workspace.open('sample.js', pending: true).then (o) -> - editor = o - pane = atom.workspace.getActivePane() - - runs -> - expect(pane.getPendingItem()).toEqual editor - - waitsForPromise -> - atom.workspace.open('sample.js') - - runs -> - expect(pane.getPendingItem()).toBeNull() - - describe "when opening will switch from a pending tab to a permanent tab", -> - it "keeps the pending tab open", -> - editor1 = null - editor2 = null - - waitsForPromise -> - atom.workspace.open('sample.txt').then (o) -> - editor1 = o - - waitsForPromise -> - atom.workspace.open('sample2.txt', pending: true).then (o) -> - editor2 = o - - runs -> - pane = atom.workspace.getActivePane() - pane.activateItem(editor1) - expect(pane.getItems().length).toBe 2 - expect(pane.getItems()).toEqual [editor1, editor2] - - describe "when replacing a pending item which is the last item in a second pane", -> - it "does not destroy the pane even if core.destroyEmptyPanes is on", -> - atom.config.set('core.destroyEmptyPanes', true) - editor1 = null - editor2 = null - leftPane = atom.workspace.getActivePane() - rightPane = null - - waitsForPromise -> - atom.workspace.open('sample.js', pending: true, split: 'right').then (o) -> - editor1 = o - rightPane = atom.workspace.getActivePane() - spyOn rightPane, "destroyed" - - runs -> - expect(leftPane).not.toBe rightPane - expect(atom.workspace.getActivePane()).toBe rightPane - expect(atom.workspace.getActivePane().getItems().length).toBe 1 - expect(rightPane.getPendingItem()).toBe editor1 - - waitsForPromise -> - atom.workspace.open('sample.txt', pending: true).then (o) -> - editor2 = o - - runs -> - expect(rightPane.getPendingItem()).toBe editor2 - expect(rightPane.destroyed.callCount).toBe 0 - - describe 'the grammar-used hook', -> - it 'fires when opening a file or changing the grammar of an open file', -> - editor = null - javascriptGrammarUsed = false - coffeescriptGrammarUsed = false - - atom.packages.triggerDeferredActivationHooks() - - runs -> - atom.packages.onDidTriggerActivationHook 'language-javascript:grammar-used', -> javascriptGrammarUsed = true - atom.packages.onDidTriggerActivationHook 'language-coffee-script:grammar-used', -> coffeescriptGrammarUsed = true - - waitsForPromise -> - atom.workspace.open('sample.js', autoIndent: false).then (o) -> editor = o - - waitsForPromise -> - atom.packages.activatePackage('language-javascript') - - waitsFor -> javascriptGrammarUsed - - waitsForPromise -> - atom.packages.activatePackage('language-coffee-script') - - runs -> - editor.setGrammar(atom.grammars.selectGrammar('.coffee')) - - waitsFor -> coffeescriptGrammarUsed - - describe "::reopenItem()", -> - it "opens the uri associated with the last closed pane that isn't currently open", -> - pane = workspace.getActivePane() - waitsForPromise -> - workspace.open('a').then -> - workspace.open('b').then -> - workspace.open('file1').then -> - workspace.open() - - runs -> - # does not reopen items with no uri - expect(workspace.getActivePaneItem().getURI()).toBeUndefined() - pane.destroyActiveItem() - - waitsForPromise -> - workspace.reopenItem() - - runs -> - expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined() - - # destroy all items - expect(workspace.getActivePaneItem().getURI()).toBe atom.project.getDirectories()[0]?.resolve('file1') - pane.destroyActiveItem() - expect(workspace.getActivePaneItem().getURI()).toBe atom.project.getDirectories()[0]?.resolve('b') - pane.destroyActiveItem() - expect(workspace.getActivePaneItem().getURI()).toBe atom.project.getDirectories()[0]?.resolve('a') - pane.destroyActiveItem() - - # reopens items with uris - expect(workspace.getActivePaneItem()).toBeUndefined() - - waitsForPromise -> - workspace.reopenItem() - - runs -> - expect(workspace.getActivePaneItem().getURI()).toBe atom.project.getDirectories()[0]?.resolve('a') - - # does not reopen items that are already open - waitsForPromise -> - workspace.open('b') - - runs -> - expect(workspace.getActivePaneItem().getURI()).toBe atom.project.getDirectories()[0]?.resolve('b') - - waitsForPromise -> - workspace.reopenItem() - - runs -> - expect(workspace.getActivePaneItem().getURI()).toBe atom.project.getDirectories()[0]?.resolve('file1') - - describe "::increase/decreaseFontSize()", -> - it "increases/decreases the font size without going below 1", -> - atom.config.set('editor.fontSize', 1) - workspace.increaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe 2 - workspace.increaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe 3 - workspace.decreaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe 2 - workspace.decreaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe 1 - workspace.decreaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe 1 - - describe "::resetFontSize()", -> - it "resets the font size to the window's starting font size", -> - originalFontSize = atom.config.get('editor.fontSize') - - workspace.increaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize + 1 - workspace.resetFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize - workspace.decreaseFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize - 1 - workspace.resetFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize - - it "does nothing if the font size has not been changed", -> - originalFontSize = atom.config.get('editor.fontSize') - - workspace.resetFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize - - it "resets the font size when the editor's font size changes", -> - originalFontSize = atom.config.get('editor.fontSize') - - atom.config.set('editor.fontSize', originalFontSize + 1) - workspace.resetFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize - atom.config.set('editor.fontSize', originalFontSize - 1) - workspace.resetFontSize() - expect(atom.config.get('editor.fontSize')).toBe originalFontSize - - describe "::openLicense()", -> - it "opens the license as plain-text in a buffer", -> - waitsForPromise -> workspace.openLicense() - runs -> expect(workspace.getActivePaneItem().getText()).toMatch /Copyright/ - - describe "::isTextEditor(obj)", -> - it "returns true when the passed object is an instance of `TextEditor`", -> - expect(workspace.isTextEditor(new TextEditor)).toBe(true) - expect(workspace.isTextEditor({getText: -> null})).toBe(false) - expect(workspace.isTextEditor(null)).toBe(false) - expect(workspace.isTextEditor(undefined)).toBe(false) - - describe "::observeTextEditors()", -> - it "invokes the observer with current and future text editors", -> - observed = [] - - waitsForPromise -> workspace.open() - waitsForPromise -> workspace.open() - waitsForPromise -> workspace.openLicense() - - runs -> - workspace.observeTextEditors (editor) -> observed.push(editor) - - waitsForPromise -> workspace.open() - - expect(observed).toEqual workspace.getTextEditors() - - describe "when an editor is destroyed", -> - it "removes the editor", -> - editor = null - - waitsForPromise -> - workspace.open("a").then (e) -> editor = e - - runs -> - expect(workspace.getTextEditors()).toHaveLength 1 - editor.destroy() - expect(workspace.getTextEditors()).toHaveLength 0 - - describe "when an editor is copied because its pane is split", -> - it "sets up the new editor to be configured by the text editor registry", -> - waitsForPromise -> - atom.packages.activatePackage('language-javascript') - - waitsForPromise -> - workspace.open('a').then (editor) -> - atom.textEditors.setGrammarOverride(editor, 'source.js') - expect(editor.getGrammar().name).toBe('JavaScript') - - workspace.getActivePane().splitRight(copyActiveItem: true) - newEditor = workspace.getActiveTextEditor() - expect(newEditor).not.toBe(editor) - expect(newEditor.getGrammar().name).toBe('JavaScript') - - it "stores the active grammars used by all the open editors", -> - waitsForPromise -> - atom.packages.activatePackage('language-javascript') - - waitsForPromise -> - atom.packages.activatePackage('language-coffee-script') - - waitsForPromise -> - atom.packages.activatePackage('language-todo') - - waitsForPromise -> - atom.workspace.open('sample.coffee') - - runs -> - atom.workspace.getActiveTextEditor().setText """ - i = /test/; #FIXME - """ - - atom2 = new AtomEnvironment({ - applicationDelegate: atom.applicationDelegate, - window: document.createElement('div'), - document: Object.assign( - document.createElement('div'), - { - body: document.createElement('div'), - head: document.createElement('div'), - } - ) - }) - - atom2.packages.loadPackage('language-javascript') - atom2.packages.loadPackage('language-coffee-script') - atom2.packages.loadPackage('language-todo') - atom2.project.deserialize(atom.project.serialize()) - atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers) - - expect(atom2.grammars.getGrammars().map((grammar) -> grammar.name).sort()).toEqual([ - 'CoffeeScript', - 'CoffeeScript (Literate)', - 'JavaScript', - 'Null Grammar', - 'Regular Expression Replacement (JavaScript)', - 'Regular Expressions (JavaScript)', - 'TODO' - ]) - - atom2.destroy() - - describe "document.title", -> - describe "when there is no item open", -> - it "sets the title to the project path", -> - expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0])) - - it "sets the title to 'untitled' if there is no project path", -> - atom.project.setPaths([]) - expect(document.title).toMatch /^untitled/ - - describe "when the active pane item's path is not inside a project path", -> - beforeEach -> - waitsForPromise -> - atom.workspace.open('b').then -> - atom.project.setPaths([]) - - it "sets the title to the pane item's title plus the item's path", -> - item = atom.workspace.getActivePaneItem() - pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))) - expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}/// - - describe "when the title of the active pane item changes", -> - it "updates the window title based on the item's new title", -> - editor = atom.workspace.getActivePaneItem() - editor.buffer.setPath(path.join(temp.dir, 'hi')) - pathEscaped = fs.tildify(escapeStringRegex(path.dirname(editor.getPath()))) - expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}/// - - describe "when the active pane's item changes", -> - it "updates the title to the new item's title plus the project path", -> - atom.workspace.getActivePane().activateNextItem() - item = atom.workspace.getActivePaneItem() - pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))) - expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}/// - - describe "when an inactive pane's item changes", -> - it "does not update the title", -> - pane = atom.workspace.getActivePane() - pane.splitRight() - initialTitle = document.title - pane.activateNextItem() - expect(document.title).toBe initialTitle - - describe "when the active pane item is inside a project path", -> - beforeEach -> - waitsForPromise -> - atom.workspace.open('b') - - describe "when there is an active pane item", -> - it "sets the title to the pane item's title plus the project path", -> - item = atom.workspace.getActivePaneItem() - pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}/// - - describe "when the title of the active pane item changes", -> - it "updates the window title based on the item's new title", -> - editor = atom.workspace.getActivePaneItem() - editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')) - pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}/// - - describe "when the active pane's item changes", -> - it "updates the title to the new item's title plus the project path", -> - atom.workspace.getActivePane().activateNextItem() - item = atom.workspace.getActivePaneItem() - pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}/// - - describe "when the last pane item is removed", -> - it "updates the title to the project's first path", -> - atom.workspace.getActivePane().destroy() - expect(atom.workspace.getActivePaneItem()).toBeUndefined() - expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0])) - - describe "when an inactive pane's item changes", -> - it "does not update the title", -> - pane = atom.workspace.getActivePane() - pane.splitRight() - initialTitle = document.title - pane.activateNextItem() - expect(document.title).toBe initialTitle - - describe "when the workspace is deserialized", -> - beforeEach -> - waitsForPromise -> atom.workspace.open('a') - - it "updates the title to contain the project's path", -> - document.title = null - - atom2 = new AtomEnvironment({ - applicationDelegate: atom.applicationDelegate, - window: document.createElement('div'), - document: Object.assign( - document.createElement('div'), - { - body: document.createElement('div'), - head: document.createElement('div'), - } - ) - }) - - atom2.project.deserialize(atom.project.serialize()) - atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers) - item = atom2.workspace.getActivePaneItem() - pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{pathEscaped}/// - - atom2.destroy() - - describe "document edited status", -> - [item1, item2] = [] - - beforeEach -> - waitsForPromise -> atom.workspace.open('a') - waitsForPromise -> atom.workspace.open('b') - runs -> - [item1, item2] = atom.workspace.getPaneItems() - - it "calls setDocumentEdited when the active item changes", -> - expect(atom.workspace.getActivePaneItem()).toBe item2 - item1.insertText('a') - expect(item1.isModified()).toBe true - atom.workspace.getActivePane().activateNextItem() - - expect(setDocumentEdited).toHaveBeenCalledWith(true) - - it "calls atom.setDocumentEdited when the active item's modified status changes", -> - expect(atom.workspace.getActivePaneItem()).toBe item2 - item2.insertText('a') - advanceClock(item2.getBuffer().getStoppedChangingDelay()) - - expect(item2.isModified()).toBe true - expect(setDocumentEdited).toHaveBeenCalledWith(true) - - item2.undo() - advanceClock(item2.getBuffer().getStoppedChangingDelay()) - - expect(item2.isModified()).toBe false - expect(setDocumentEdited).toHaveBeenCalledWith(false) - - describe "adding panels", -> - class TestItem - - class TestItemElement extends HTMLElement - constructor: -> - initialize: (@model) -> this - getModel: -> @model - - beforeEach -> - atom.views.addViewProvider TestItem, (model) -> - new TestItemElement().initialize(model) - - describe '::addLeftPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getLeftPanels().length).toBe(0) - atom.workspace.panelContainers.left.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addLeftPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getLeftPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe '::addRightPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getRightPanels().length).toBe(0) - atom.workspace.panelContainers.right.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addRightPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getRightPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe '::addTopPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getTopPanels().length).toBe(0) - atom.workspace.panelContainers.top.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addTopPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getTopPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe '::addBottomPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getBottomPanels().length).toBe(0) - atom.workspace.panelContainers.bottom.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addBottomPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getBottomPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe '::addHeaderPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getHeaderPanels().length).toBe(0) - atom.workspace.panelContainers.header.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addHeaderPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getHeaderPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe '::addFooterPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getFooterPanels().length).toBe(0) - atom.workspace.panelContainers.footer.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addFooterPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getFooterPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe '::addModalPanel(model)', -> - it 'adds a panel to the correct panel container', -> - expect(atom.workspace.getModalPanels().length).toBe(0) - atom.workspace.panelContainers.modal.onDidAddPanel addPanelSpy = jasmine.createSpy() - - model = new TestItem - panel = atom.workspace.addModalPanel(item: model) - - expect(panel).toBeDefined() - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - - itemView = atom.views.getView(atom.workspace.getModalPanels()[0].getItem()) - expect(itemView instanceof TestItemElement).toBe(true) - expect(itemView.getModel()).toBe(model) - - describe "::panelForItem(item)", -> - it "returns the panel associated with the item", -> - item = new TestItem - panel = atom.workspace.addLeftPanel(item: item) - - itemWithNoPanel = new TestItem - - expect(atom.workspace.panelForItem(item)).toBe panel - expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe null - - describe "::scan(regex, options, callback)", -> - describe "when called with a regex", -> - it "calls the callback with all regex results in all files in the project", -> - results = [] - waitsForPromise -> - atom.workspace.scan /(a)+/, (result) -> - results.push(result) - - runs -> - expect(results).toHaveLength(3) - expect(results[0].filePath).toBe atom.project.getDirectories()[0]?.resolve('a') - expect(results[0].matches).toHaveLength(3) - expect(results[0].matches[0]).toEqual - matchText: 'aaa' - lineText: 'aaa bbb' - lineTextOffset: 0 - range: [[0, 0], [0, 3]] - - it "works with with escaped literals (like $ and ^)", -> - results = [] - waitsForPromise -> - atom.workspace.scan /\$\w+/, (result) -> results.push(result) - - runs -> - expect(results.length).toBe 1 - - {filePath, matches} = results[0] - expect(filePath).toBe atom.project.getDirectories()[0]?.resolve('a') - expect(matches).toHaveLength 1 - expect(matches[0]).toEqual - matchText: '$bill' - lineText: 'dollar$bill' - lineTextOffset: 0 - range: [[2, 6], [2, 11]] - - it "works on evil filenames", -> - atom.config.set('core.excludeVcsIgnoredPaths', false) - platform.generateEvilFiles() - atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')]) - paths = [] - matches = [] - waitsForPromise -> - atom.workspace.scan /evil/, (result) -> - paths.push(result.filePath) - matches = matches.concat(result.matches) - - runs -> - _.each(matches, (m) -> expect(m.matchText).toEqual 'evil') - - if platform.isWindows() - expect(paths.length).toBe 3 - expect(paths[0]).toMatch /a_file_with_utf8.txt$/ - expect(paths[1]).toMatch /file with spaces.txt$/ - expect(path.basename(paths[2])).toBe "utfa\u0306.md" - else - expect(paths.length).toBe 5 - expect(paths[0]).toMatch /a_file_with_utf8.txt$/ - expect(paths[1]).toMatch /file with spaces.txt$/ - expect(paths[2]).toMatch /goddam\nnewlines$/m - expect(paths[3]).toMatch /quote".txt$/m - expect(path.basename(paths[4])).toBe "utfa\u0306.md" - - it "ignores case if the regex includes the `i` flag", -> - results = [] - waitsForPromise -> - atom.workspace.scan /DOLLAR/i, (result) -> results.push(result) - - runs -> - expect(results).toHaveLength 1 - - describe "when the core.excludeVcsIgnoredPaths config is truthy", -> - [projectPath, ignoredPath] = [] - - beforeEach -> - sourceProjectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir') - projectPath = path.join(temp.mkdirSync("atom")) - - writerStream = fstream.Writer(projectPath) - fstream.Reader(sourceProjectPath).pipe(writerStream) - - waitsFor (done) -> - writerStream.on 'close', done - writerStream.on 'error', done - - runs -> - fs.rename(path.join(projectPath, 'git.git'), path.join(projectPath, '.git')) - ignoredPath = path.join(projectPath, 'ignored.txt') - fs.writeFileSync(ignoredPath, 'this match should not be included') - - afterEach -> - fs.removeSync(projectPath) if fs.existsSync(projectPath) - - it "excludes ignored files", -> - atom.project.setPaths([projectPath]) - atom.config.set('core.excludeVcsIgnoredPaths', true) - resultHandler = jasmine.createSpy("result found") - waitsForPromise -> - atom.workspace.scan /match/, (results) -> - resultHandler() - - runs -> - expect(resultHandler).not.toHaveBeenCalled() - - it "includes only files when a directory filter is specified", -> - projectPath = path.join(path.join(__dirname, 'fixtures', 'dir')) - atom.project.setPaths([projectPath]) - - filePath = path.join(projectPath, 'a-dir', 'oh-git') - - paths = [] - matches = [] - waitsForPromise -> - atom.workspace.scan /aaa/, paths: ["a-dir#{path.sep}"], (result) -> - paths.push(result.filePath) - matches = matches.concat(result.matches) - - runs -> - expect(paths.length).toBe 1 - expect(paths[0]).toBe filePath - expect(matches.length).toBe 1 - - it "includes files and folders that begin with a '.'", -> - projectPath = temp.mkdirSync('atom-spec-workspace') - filePath = path.join(projectPath, '.text') - fs.writeFileSync(filePath, 'match this') - atom.project.setPaths([projectPath]) - paths = [] - matches = [] - waitsForPromise -> - atom.workspace.scan /match this/, (result) -> - paths.push(result.filePath) - matches = matches.concat(result.matches) - - runs -> - expect(paths.length).toBe 1 - expect(paths[0]).toBe filePath - expect(matches.length).toBe 1 - - it "excludes values in core.ignoredNames", -> - ignoredNames = atom.config.get("core.ignoredNames") - ignoredNames.push("a") - atom.config.set("core.ignoredNames", ignoredNames) - - resultHandler = jasmine.createSpy("result found") - waitsForPromise -> - atom.workspace.scan /dollar/, (results) -> - resultHandler() - - runs -> - expect(resultHandler).not.toHaveBeenCalled() - - it "scans buffer contents if the buffer is modified", -> - editor = null - results = [] - - waitsForPromise -> - atom.workspace.open('a').then (o) -> - editor = o - editor.setText("Elephant") - - waitsForPromise -> - atom.workspace.scan /a|Elephant/, (result) -> results.push result - - runs -> - expect(results).toHaveLength 3 - resultForA = _.find results, ({filePath}) -> path.basename(filePath) is 'a' - expect(resultForA.matches).toHaveLength 1 - expect(resultForA.matches[0].matchText).toBe 'Elephant' - - it "ignores buffers outside the project", -> - editor = null - results = [] - - waitsForPromise -> - atom.workspace.open(temp.openSync().path).then (o) -> - editor = o - editor.setText("Elephant") - - waitsForPromise -> - atom.workspace.scan /Elephant/, (result) -> results.push result - - runs -> - expect(results).toHaveLength 0 - - describe "when the project has multiple root directories", -> - [dir1, dir2, file1, file2] = [] - - beforeEach -> - [dir1] = atom.project.getPaths() - file1 = path.join(dir1, "a-dir", "oh-git") - - dir2 = temp.mkdirSync("a-second-dir") - aDir2 = path.join(dir2, "a-dir") - file2 = path.join(aDir2, "a-file") - fs.mkdirSync(aDir2) - fs.writeFileSync(file2, "ccc aaaa") - - atom.project.addPath(dir2) - - it "searches matching files in all of the project's root directories", -> - resultPaths = [] - waitsForPromise -> - atom.workspace.scan /aaaa/, ({filePath}) -> - resultPaths.push(filePath) - - runs -> - expect(resultPaths.sort()).toEqual([file1, file2].sort()) - - describe "when an inclusion path starts with the basename of a root directory", -> - it "interprets the inclusion path as starting from that directory", -> - waitsForPromise -> - resultPaths = [] - atom.workspace - .scan /aaaa/, paths: ["dir"], ({filePath}) -> - resultPaths.push(filePath) unless filePath in resultPaths - .then -> - expect(resultPaths).toEqual([file1]) - - waitsForPromise -> - resultPaths = [] - atom.workspace - .scan /aaaa/, paths: [path.join("dir", "a-dir")], ({filePath}) -> - resultPaths.push(filePath) unless filePath in resultPaths - .then -> - expect(resultPaths).toEqual([file1]) - - waitsForPromise -> - resultPaths = [] - atom.workspace - .scan /aaaa/, paths: [path.basename(dir2)], ({filePath}) -> - resultPaths.push(filePath) unless filePath in resultPaths - .then -> - expect(resultPaths).toEqual([file2]) - - waitsForPromise -> - resultPaths = [] - atom.workspace - .scan /aaaa/, paths: [path.join(path.basename(dir2), "a-dir")], ({filePath}) -> - resultPaths.push(filePath) unless filePath in resultPaths - .then -> - expect(resultPaths).toEqual([file2]) - - describe "when a custom directory searcher is registered", -> - fakeSearch = null - # Function that is invoked once all of the fields on fakeSearch are set. - onFakeSearchCreated = null - - class FakeSearch - constructor: (@options) -> - # Note that hoisting resolve and reject in this way is generally frowned upon. - @promise = new Promise (resolve, reject) => - @hoistedResolve = resolve - @hoistedReject = reject - onFakeSearchCreated?(this) - then: (args...) -> - @promise.then.apply(@promise, args) - cancel: -> - @cancelled = true - # According to the spec for a DirectorySearcher, invoking `cancel()` should - # resolve the thenable rather than reject it. - @hoistedResolve() - - beforeEach -> - fakeSearch = null - onFakeSearchCreated = null - atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { - canSearchDirectory: (directory) -> directory.getPath() is dir1 - search: (directory, regex, options) -> fakeSearch = new FakeSearch(options) - }) - - waitsFor -> - atom.workspace.directorySearchers.length > 0 - - it "can override the DefaultDirectorySearcher on a per-directory basis", -> - foreignFilePath = 'ssh://foreign-directory:8080/hello.txt' - numPathsSearchedInDir2 = 1 - numPathsToPretendToSearchInCustomDirectorySearcher = 10 - searchResult = - filePath: foreignFilePath, - matches: [ - { - lineText: 'Hello world', - lineTextOffset: 0, - matchText: 'Hello', - range: [[0, 0], [0, 5]], - }, - ] - onFakeSearchCreated = (fakeSearch) -> - fakeSearch.options.didMatch(searchResult) - fakeSearch.options.didSearchPaths(numPathsToPretendToSearchInCustomDirectorySearcher) - fakeSearch.hoistedResolve() - - resultPaths = [] - onPathsSearched = jasmine.createSpy('onPathsSearched') - waitsForPromise -> - atom.workspace.scan /aaaa/, {onPathsSearched}, ({filePath}) -> - resultPaths.push(filePath) - - runs -> - expect(resultPaths.sort()).toEqual([foreignFilePath, file2].sort()) - # onPathsSearched should be called once by each DirectorySearcher. The order is not - # guaranteed, so we can only verify the total number of paths searched is correct - # after the second call. - expect(onPathsSearched.callCount).toBe(2) - expect(onPathsSearched.mostRecentCall.args[0]).toBe( - numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2) - - it "can be cancelled when the object returned by scan() has its cancel() method invoked", -> - thenable = atom.workspace.scan /aaaa/, -> - resultOfPromiseSearch = null - - waitsFor 'fakeSearch to be defined', -> fakeSearch? - - runs -> - expect(fakeSearch.cancelled).toBe(undefined) - thenable.cancel() - expect(fakeSearch.cancelled).toBe(true) - - - waitsForPromise -> - thenable.then (promiseResult) -> resultOfPromiseSearch = promiseResult - - runs -> - expect(resultOfPromiseSearch).toBe('cancelled') - - it "will have the side-effect of failing the overall search if it fails", -> - # This provider's search should be cancelled when the first provider fails - fakeSearch2 = null - atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { - canSearchDirectory: (directory) -> directory.getPath() is dir2 - search: (directory, regex, options) -> fakeSearch2 = new FakeSearch(options) - }) - - didReject = false - promise = cancelableSearch = atom.workspace.scan /aaaa/, -> - waitsFor 'fakeSearch to be defined', -> fakeSearch? - - runs -> - fakeSearch.hoistedReject() - - waitsForPromise -> - cancelableSearch.catch -> didReject = true - - waitsFor (done) -> promise.then(null, done) - - runs -> - expect(didReject).toBe(true) - expect(fakeSearch2.cancelled).toBe true # Cancels other ongoing searches - - describe "::replace(regex, replacementText, paths, iterator)", -> - [filePath, commentFilePath, sampleContent, sampleCommentContent] = [] - - beforeEach -> - atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('../')]) - - filePath = atom.project.getDirectories()[0]?.resolve('sample.js') - commentFilePath = atom.project.getDirectories()[0]?.resolve('sample-with-comments.js') - sampleContent = fs.readFileSync(filePath).toString() - sampleCommentContent = fs.readFileSync(commentFilePath).toString() - - afterEach -> - fs.writeFileSync(filePath, sampleContent) - fs.writeFileSync(commentFilePath, sampleCommentContent) - - describe "when a file doesn't exist", -> - it "calls back with an error", -> - errors = [] - missingPath = path.resolve('/not-a-file.js') - expect(fs.existsSync(missingPath)).toBeFalsy() - - waitsForPromise -> - atom.workspace.replace /items/gi, 'items', [missingPath], (result, error) -> - errors.push(error) - - runs -> - expect(errors).toHaveLength 1 - expect(errors[0].path).toBe missingPath - - describe "when called with unopened files", -> - it "replaces properly", -> - results = [] - waitsForPromise -> - atom.workspace.replace /items/gi, 'items', [filePath], (result) -> - results.push(result) - - runs -> - expect(results).toHaveLength 1 - expect(results[0].filePath).toBe filePath - expect(results[0].replacements).toBe 6 - - describe "when a buffer is already open", -> - it "replaces properly and saves when not modified", -> - editor = null - results = [] - - waitsForPromise -> - atom.workspace.open('sample.js').then (o) -> editor = o - - runs -> - expect(editor.isModified()).toBeFalsy() - - waitsForPromise -> - atom.workspace.replace /items/gi, 'items', [filePath], (result) -> - results.push(result) - - runs -> - expect(results).toHaveLength 1 - expect(results[0].filePath).toBe filePath - expect(results[0].replacements).toBe 6 - - expect(editor.isModified()).toBeFalsy() - - it "does not replace when the path is not specified", -> - editor = null - results = [] - - waitsForPromise -> - atom.workspace.open('sample-with-comments.js').then (o) -> editor = o - - waitsForPromise -> - atom.workspace.replace /items/gi, 'items', [commentFilePath], (result) -> - results.push(result) - - runs -> - expect(results).toHaveLength 1 - expect(results[0].filePath).toBe commentFilePath - - it "does NOT save when modified", -> - editor = null - results = [] - - waitsForPromise -> - atom.workspace.open('sample.js').then (o) -> editor = o - - runs -> - editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg') - expect(editor.isModified()).toBeTruthy() - - waitsForPromise -> - atom.workspace.replace /items/gi, 'okthen', [filePath], (result) -> - results.push(result) - - runs -> - expect(results).toHaveLength 1 - expect(results[0].filePath).toBe filePath - expect(results[0].replacements).toBe 6 - - expect(editor.isModified()).toBeTruthy() - - describe "::saveActivePaneItem()", -> - editor = null - beforeEach -> - waitsForPromise -> - atom.workspace.open('sample.js').then (o) -> editor = o - - describe "when there is an error", -> - it "emits a warning notification when the file cannot be saved", -> - spyOn(editor, 'save').andCallFake -> - throw new Error("'/some/file' is a directory") - - atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy() - atom.workspace.saveActivePaneItem() - expect(addedSpy).toHaveBeenCalled() - expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning' - - it "emits a warning notification when the directory cannot be written to", -> - spyOn(editor, 'save').andCallFake -> - throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'") - - atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy() - atom.workspace.saveActivePaneItem() - expect(addedSpy).toHaveBeenCalled() - expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning' - - it "emits a warning notification when the user does not have permission", -> - spyOn(editor, 'save').andCallFake -> - error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'") - error.code = 'EACCES' - error.path = '/Some/dir/and-a-file.js' - throw error - - atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy() - atom.workspace.saveActivePaneItem() - expect(addedSpy).toHaveBeenCalled() - expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning' - - it "emits a warning notification when the operation is not permitted", -> - spyOn(editor, 'save').andCallFake -> - error = new Error("EPERM, operation not permitted '/Some/dir/and-a-file.js'") - error.code = 'EPERM' - error.path = '/Some/dir/and-a-file.js' - throw error - - it "emits a warning notification when the file is already open by another app", -> - spyOn(editor, 'save').andCallFake -> - error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'") - error.code = 'EBUSY' - error.path = '/Some/dir/and-a-file.js' - throw error - - atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy() - atom.workspace.saveActivePaneItem() - expect(addedSpy).toHaveBeenCalled() - - notificaiton = addedSpy.mostRecentCall.args[0] - expect(notificaiton.getType()).toBe 'warning' - expect(notificaiton.getMessage()).toContain 'Unable to save' - - it "emits a warning notification when the file system is read-only", -> - spyOn(editor, 'save').andCallFake -> - error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'") - error.code = 'EROFS' - error.path = '/Some/dir/and-a-file.js' - throw error - - atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy() - atom.workspace.saveActivePaneItem() - expect(addedSpy).toHaveBeenCalled() - - notification = addedSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe 'warning' - expect(notification.getMessage()).toContain 'Unable to save' - - it "emits a warning notification when the file cannot be saved", -> - spyOn(editor, 'save').andCallFake -> - throw new Error("no one knows") - - save = -> atom.workspace.saveActivePaneItem() - expect(save).toThrow() - - describe "::closeActivePaneItemOrEmptyPaneOrWindow", -> - beforeEach -> - spyOn(atom, 'close') - waitsForPromise -> atom.workspace.open() - - it "closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane", -> - atom.config.set('core.destroyEmptyPanes', false) - - pane1 = atom.workspace.getActivePane() - pane2 = pane1.splitRight(copyActiveItem: true) - - expect(atom.workspace.getPanes().length).toBe 2 - expect(pane2.getItems().length).toBe 1 - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - - expect(atom.workspace.getPanes().length).toBe 2 - expect(pane2.getItems().length).toBe 0 - - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - - expect(atom.workspace.getPanes().length).toBe 1 - expect(pane1.getItems().length).toBe 1 - - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - expect(atom.workspace.getPanes().length).toBe 1 - expect(pane1.getItems().length).toBe 0 - - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - expect(atom.workspace.getPanes().length).toBe 1 - - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - expect(atom.close).toHaveBeenCalled() - - describe "when the core.allowPendingPaneItems option is falsey", -> - it "does not open item with `pending: true` option as pending", -> - pane = null - atom.config.set('core.allowPendingPaneItems', false) - - waitsForPromise -> - atom.workspace.open('sample.js', pending: true).then -> - pane = atom.workspace.getActivePane() - - runs -> - expect(pane.getPendingItem()).toBeFalsy() - - describe "grammar activation", -> - it "notifies the workspace of which grammar is used", -> - editor = null - atom.packages.triggerDeferredActivationHooks() - - javascriptGrammarUsed = jasmine.createSpy('js grammar used') - rubyGrammarUsed = jasmine.createSpy('ruby grammar used') - cGrammarUsed = jasmine.createSpy('c grammar used') - - atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', javascriptGrammarUsed) - atom.packages.onDidTriggerActivationHook('language-ruby:grammar-used', rubyGrammarUsed) - atom.packages.onDidTriggerActivationHook('language-c:grammar-used', cGrammarUsed) - - waitsForPromise -> atom.packages.activatePackage('language-ruby') - waitsForPromise -> atom.packages.activatePackage('language-javascript') - waitsForPromise -> atom.packages.activatePackage('language-c') - waitsForPromise -> atom.workspace.open('sample-with-comments.js') - - runs -> - # Hooks are triggered when opening new editors - expect(javascriptGrammarUsed).toHaveBeenCalled() - - # Hooks are triggered when changing existing editors grammars - atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.c')) - expect(cGrammarUsed).toHaveBeenCalled() - - # Hooks are triggered when editors are added in other ways. - atom.workspace.getActivePane().splitRight(copyActiveItem: true) - atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.ruby')) - expect(rubyGrammarUsed).toHaveBeenCalled() - - describe ".checkoutHeadRevision()", -> - editor = null - beforeEach -> - atom.config.set("editor.confirmCheckoutHeadRevision", false) - - waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o - - it "reverts to the version of its file checked into the project repository", -> - editor.setCursorBufferPosition([0, 0]) - editor.insertText("---\n") - expect(editor.lineTextForBufferRow(0)).toBe "---" - - waitsForPromise -> - atom.workspace.checkoutHeadRevision(editor) - - runs -> - expect(editor.lineTextForBufferRow(0)).toBe "" - - describe "when there's no repository for the editor's file", -> - it "doesn't do anything", -> - editor = new TextEditor - editor.setText("stuff") - atom.workspace.checkoutHeadRevision(editor) - - waitsForPromise -> atom.workspace.checkoutHeadRevision(editor) - - escapeStringRegex = (str) -> - str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js new file mode 100644 index 000000000..d5ddad7d2 --- /dev/null +++ b/spec/workspace-spec.js @@ -0,0 +1,1977 @@ +const path = require('path'); +const temp = require('temp').track(); +const TextEditor = require('../src/text-editor'); +const Workspace = require('../src/workspace'); +const Project = require('../src/project'); +const platform = require('./spec-helper-platform'); +const _ = require('underscore-plus'); +const fstream = require('fstream'); +const fs = require('fs-plus'); +const AtomEnvironment = require('../src/atom-environment'); + +describe("Workspace", function() { + let escapeStringRegex; + let [workspace, setDocumentEdited] = Array.from([]); + + beforeEach(function() { + ({ workspace } = atom); + workspace.resetFontSize(); + spyOn(atom.applicationDelegate, "confirm"); + setDocumentEdited = spyOn(atom.applicationDelegate, 'setWindowDocumentEdited'); + atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('dir'))]); + return waits(1); + }); + + afterEach(() => temp.cleanupSync()); + + describe("serialization", function() { + const simulateReload = function() { + const workspaceState = atom.workspace.serialize(); + const projectState = atom.project.serialize({isUnloading: true}); + atom.workspace.destroy(); + atom.project.destroy(); + atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom), applicationDelegate: atom.applicationDelegate}); + atom.project.deserialize(projectState); + atom.workspace = new Workspace({ + config: atom.config, project: atom.project, packageManager: atom.packages, + grammarRegistry: atom.grammars, deserializerManager: atom.deserializers, + notificationManager: atom.notifications, + applicationDelegate: atom.applicationDelegate, + viewRegistry: atom.views, assert: atom.assert.bind(atom), + textEditorRegistry: atom.textEditors + }); + return atom.workspace.deserialize(workspaceState, atom.deserializers); + }; + + describe("when the workspace contains text editors", () => + it("constructs the view with the same panes", function() { + const pane1 = atom.workspace.getActivePane(); + const pane2 = pane1.splitRight({copyActiveItem: true}); + const pane3 = pane2.splitRight({copyActiveItem: true}); + let pane4 = null; + + waitsForPromise(() => atom.workspace.open(null).then(editor => editor.setText("An untitled editor."))); + + waitsForPromise(() => + atom.workspace.open('b').then(editor => pane2.activateItem(editor.copy())) + ); + + waitsForPromise(() => + atom.workspace.open('../sample.js').then(editor => pane3.activateItem(editor)) + ); + + runs(function() { + pane3.activeItem.setCursorScreenPosition([2, 4]); + return pane4 = pane2.splitDown(); + }); + + waitsForPromise(() => + atom.workspace.open('../sample.txt').then(editor => pane4.activateItem(editor)) + ); + + return runs(function() { + pane4.getActiveItem().setCursorScreenPosition([0, 2]); + pane2.activate(); + + simulateReload(); + + expect(atom.workspace.getTextEditors().length).toBe(5); + const [editor1, editor2, untitledEditor, editor3, editor4] = Array.from(atom.workspace.getTextEditors()); + expect(editor1.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b'))); + expect(editor2.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('../sample.txt'))); + expect(editor2.getCursorScreenPosition()).toEqual([0, 2]); + expect(editor3.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('b'))); + expect(editor4.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x3 => x3.resolve('../sample.js'))); + expect(editor4.getCursorScreenPosition()).toEqual([2, 4]); + expect(untitledEditor.getPath()).toBeUndefined(); + expect(untitledEditor.getText()).toBe("An untitled editor."); + + expect(atom.workspace.getActiveTextEditor().getPath()).toBe(editor3.getPath()); + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); + return expect(document.title).toMatch(new RegExp(`^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}`)); + }); + }) + ); + + return describe("where there are no open panes or editors", () => + it("constructs the view with no open editors", function() { + atom.workspace.getActivePane().destroy(); + expect(atom.workspace.getTextEditors().length).toBe(0); + simulateReload(); + return expect(atom.workspace.getTextEditors().length).toBe(0); + }) + ); + }); + + describe("::open(uri, options)", function() { + let openEvents = null; + + beforeEach(function() { + openEvents = []; + workspace.onDidOpen(event => openEvents.push(event)); + return spyOn(workspace.getActivePane(), 'activate').andCallThrough(); + }); + + describe("when the 'searchAllPanes' option is false (default)", function() { + describe("when called without a uri", () => + it("adds and activates an empty editor on the active pane", function() { + let [editor1, editor2] = Array.from([]); + + waitsForPromise(() => workspace.open().then(editor => editor1 = editor)); + + runs(function() { + expect(editor1.getPath()).toBeUndefined(); + expect(workspace.getActivePane().items).toEqual([editor1]); + expect(workspace.getActivePaneItem()).toBe(editor1); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor1, index: 0}]); + return openEvents = [];}); + + waitsForPromise(() => workspace.open().then(editor => editor2 = editor)); + + return runs(function() { + expect(editor2.getPath()).toBeUndefined(); + expect(workspace.getActivePane().items).toEqual([editor1, editor2]); + expect(workspace.getActivePaneItem()).toBe(editor2); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + return expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}]);});})); + + return describe("when called with a uri", function() { + describe("when the active pane already has an editor for the given uri", () => + it("activates the existing editor on the active pane", function() { + let editor = null; + let editor1 = null; + let editor2 = null; + + waitsForPromise(() => + workspace.open('a').then(function(o) { + editor1 = o; + return workspace.open('b').then(function(o) { + editor2 = o; + return workspace.open('a').then(o => editor = o); + }); + }) + ); + + return runs(function() { + expect(editor).toBe(editor1); + expect(workspace.getActivePaneItem()).toBe(editor); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + + return expect(openEvents).toEqual([ + { + uri: __guard__(atom.project.getDirectories()[0], x => x.resolve('a')), + item: editor1, + pane: atom.workspace.getActivePane(), + index: 0 + }, + { + uri: __guard__(atom.project.getDirectories()[0], x1 => x1.resolve('b')), + item: editor2, + pane: atom.workspace.getActivePane(), + index: 1 + }, + { + uri: __guard__(atom.project.getDirectories()[0], x2 => x2.resolve('a')), + item: editor1, + pane: atom.workspace.getActivePane(), + index: 0 + } + ]);});})); + + return describe("when the active pane does not have an editor for the given uri", () => + it("adds and activates a new editor for the given path on the active pane", function() { + let editor = null; + waitsForPromise(() => workspace.open('a').then(o => editor = o)); + + return runs(function() { + expect(editor.getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))); + expect(workspace.getActivePaneItem()).toBe(editor); + expect(workspace.getActivePane().items).toEqual([editor]); + return expect(workspace.getActivePane().activate).toHaveBeenCalled(); + }); + }) + ); + }); + }); + + describe("when the 'searchAllPanes' option is true", function() { + describe("when an editor for the given uri is already open on an inactive pane", () => + it("activates the existing editor on the inactive pane, then activates that pane", function() { + let editor1 = null; + let editor2 = null; + const pane1 = workspace.getActivePane(); + const pane2 = workspace.getActivePane().splitRight(); + + waitsForPromise(function() { + pane1.activate(); + return workspace.open('a').then(o => editor1 = o); + }); + + waitsForPromise(function() { + pane2.activate(); + return workspace.open('b').then(o => editor2 = o); + }); + + runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)); + + waitsForPromise(() => workspace.open('a', {searchAllPanes: true})); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + return expect(workspace.getActivePaneItem()).toBe(editor1); + }); + }) + ); + + return describe("when no editor for the given uri is open in any pane", () => + it("opens an editor for the given uri in the active pane", function() { + let editor = null; + waitsForPromise(() => workspace.open('a', {searchAllPanes: true}).then(o => editor = o)); + + return runs(() => expect(workspace.getActivePaneItem()).toBe(editor)); + }) + ); + }); + + describe("when the 'split' option is set", function() { + describe("when the 'split' option is 'left'", () => + it("opens the editor in the leftmost pane of the current pane axis", function() { + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitRight(); + expect(workspace.getActivePane()).toBe(pane2); + + let editor = null; + waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => editor = o)); + + runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + return expect(pane2.items).toEqual([]);}); + + // Focus right pane and reopen the file on the left + waitsForPromise(function() { + pane2.focus(); + return workspace.open('a', {split: 'left'}).then(o => editor = o); + }); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + return expect(pane2.items).toEqual([]);});})); + + describe("when a pane axis is the leftmost sibling of the current pane", () => + it("opens the new item in the current pane", function() { + let editor = null; + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitLeft(); + const pane3 = pane2.splitDown(); + pane1.activate(); + expect(workspace.getActivePane()).toBe(pane1); + + waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => editor = o)); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + return expect(pane1.items).toEqual([editor]);});})); + + describe("when the 'split' option is 'right'", function() { + it("opens the editor in the rightmost pane of the current pane axis", function() { + let editor = null; + const pane1 = workspace.getActivePane(); + let pane2 = null; + waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => editor = o)); + + runs(function() { + pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + return expect(pane2.items).toEqual([editor]);}); + + // Focus right pane and reopen the file on the right + waitsForPromise(function() { + pane1.focus(); + return workspace.open('a', {split: 'right'}).then(o => editor = o); + }); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + return expect(pane2.items).toEqual([editor]);});}); + + return describe("when a pane axis is the rightmost sibling of the current pane", () => + it("opens the new item in a new pane split to the right of the current pane", function() { + let editor = null; + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitRight(); + const pane3 = pane2.splitDown(); + pane1.activate(); + expect(workspace.getActivePane()).toBe(pane1); + let pane4 = null; + + waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => editor = o)); + + return runs(function() { + pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane4); + expect(pane4.items).toEqual([editor]); + expect(workspace.paneContainer.root.children[0]).toBe(pane1); + return expect(workspace.paneContainer.root.children[1]).toBe(pane4); + }); + }) + ); + }); + + describe("when the 'split' option is 'up'", () => + it("opens the editor in the topmost pane of the current pane axis", function() { + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitDown(); + expect(workspace.getActivePane()).toBe(pane2); + + let editor = null; + waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => editor = o)); + + runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + return expect(pane2.items).toEqual([]);}); + + // Focus bottom pane and reopen the file on the top + waitsForPromise(function() { + pane2.focus(); + return workspace.open('a', {split: 'up'}).then(o => editor = o); + }); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + return expect(pane2.items).toEqual([]);});})); + + describe("when a pane axis is the topmost sibling of the current pane", () => + it("opens the new item in the current pane", function() { + let editor = null; + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitUp(); + const pane3 = pane2.splitRight(); + pane1.activate(); + expect(workspace.getActivePane()).toBe(pane1); + + waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => editor = o)); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane1); + return expect(pane1.items).toEqual([editor]);});})); + + return describe("when the 'split' option is 'down'", function() { + it("opens the editor in the bottommost pane of the current pane axis", function() { + let editor = null; + const pane1 = workspace.getActivePane(); + let pane2 = null; + waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => editor = o)); + + runs(function() { + pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + return expect(pane2.items).toEqual([editor]);}); + + // Focus bottom pane and reopen the file on the right + waitsForPromise(function() { + pane1.focus(); + return workspace.open('a', {split: 'down'}).then(o => editor = o); + }); + + return runs(function() { + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + return expect(pane2.items).toEqual([editor]);});}); + + return describe("when a pane axis is the bottommost sibling of the current pane", () => + it("opens the new item in a new pane split to the bottom of the current pane", function() { + let editor = null; + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitDown(); + pane1.activate(); + expect(workspace.getActivePane()).toBe(pane1); + let pane4 = null; + + waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => editor = o)); + + return runs(function() { + pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane4); + expect(pane4.items).toEqual([editor]); + expect(workspace.paneContainer.root.children[0]).toBe(pane1); + return expect(workspace.paneContainer.root.children[1]).toBe(pane2); + }); + }) + ); + }); + }); + + describe("when an initialLine and initialColumn are specified", () => + it("moves the cursor to the indicated location", function() { + waitsForPromise(() => workspace.open('a', {initialLine: 1, initialColumn: 5})); + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([1, 5])); + + waitsForPromise(() => workspace.open('a', {initialLine: 2, initialColumn: 4})); + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 4])); + + waitsForPromise(() => workspace.open('a', {initialLine: 0, initialColumn: 0})); + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([0, 0])); + + waitsForPromise(() => workspace.open('a', {initialLine: NaN, initialColumn: 4})); + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([0, 4])); + + waitsForPromise(() => workspace.open('a', {initialLine: 2, initialColumn: NaN})); + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 0])); + + waitsForPromise(() => workspace.open('a', {initialLine: Infinity, initialColumn: Infinity})); + + return runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 11]));})); + + describe("when the file is over 2MB", () => + it("opens the editor with largeFileMode: true", function() { + spyOn(fs, 'getSizeSync').andReturn(2 * 1048577); // 2MB + + let editor = null; + waitsForPromise(() => workspace.open('sample.js').then(e => editor = e)); + + return runs(() => expect(editor.largeFileMode).toBe(true)); + }) + ); + + describe("when the file is over user-defined limit", function() { + const shouldPromptForFileOfSize = function(size, shouldPrompt) { + spyOn(fs, 'getSizeSync').andReturn(size * 1048577); + atom.applicationDelegate.confirm.andCallFake(() => selectedButtonIndex); + atom.applicationDelegate.confirm(); + var selectedButtonIndex = 1; // cancel + + let editor = null; + waitsForPromise(() => workspace.open('sample.js').then(e => editor = e)); + if (shouldPrompt) { + runs(function() { + expect(editor).toBeUndefined(); + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + + atom.applicationDelegate.confirm.reset(); + return selectedButtonIndex = 0; + }); // open the file + + waitsForPromise(() => workspace.open('sample.js').then(e => editor = e)); + + return runs(function() { + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + return expect(editor.largeFileMode).toBe(true); + }); + } else { + return runs(() => expect(editor).not.toBeUndefined()); + } + }; + + it("prompts the user to make sure they want to open a file this big", function() { + atom.config.set("core.warnOnLargeFileLimit", 20); + return shouldPromptForFileOfSize(20, true); + }); + + it("doesn't prompt on files below the limit", function() { + atom.config.set("core.warnOnLargeFileLimit", 30); + return shouldPromptForFileOfSize(20, false); + }); + + return it("prompts for smaller files with a lower limit", function() { + atom.config.set("core.warnOnLargeFileLimit", 5); + return shouldPromptForFileOfSize(10, true); + }); + }); + + describe("when passed a path that matches a custom opener", () => + it("returns the resource returned by the custom opener", function() { + const fooOpener = function(pathToOpen, options) { if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { return {foo: pathToOpen, options}; } }; + const barOpener = function(pathToOpen) { if (pathToOpen != null ? pathToOpen.match(/^bar:\/\//) : undefined) { return {bar: pathToOpen}; } }; + workspace.addOpener(fooOpener); + workspace.addOpener(barOpener); + + waitsForPromise(function() { + const pathToOpen = __guard__(atom.project.getDirectories()[0], x => x.resolve('a.foo')); + return workspace.open(pathToOpen, {hey: "there"}).then(item => expect(item).toEqual({foo: pathToOpen, options: {hey: "there"}}));}); + + return waitsForPromise(() => + workspace.open("bar://baz").then(item => expect(item).toEqual({bar: "bar://baz"})));})); + + it("adds the file to the application's recent documents list", function() { + if (process.platform !== 'darwin') { return; } // Feature only supported on macOS + spyOn(atom.applicationDelegate, 'addRecentDocument'); + + waitsForPromise(() => workspace.open()); + + runs(() => expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled()); + + waitsForPromise(() => workspace.open('something://a/url')); + + runs(() => expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled()); + + waitsForPromise(() => workspace.open(__filename)); + + return runs(() => expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith(__filename)); + }); + + it("notifies ::onDidAddTextEditor observers", function() { + const absolutePath = require.resolve('./fixtures/dir/a'); + const newEditorHandler = jasmine.createSpy('newEditorHandler'); + workspace.onDidAddTextEditor(newEditorHandler); + + let editor = null; + waitsForPromise(() => workspace.open(absolutePath).then(e => editor = e)); + + return runs(() => expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor)); + }); + + describe("when there is an error opening the file", function() { + let notificationSpy = null; + beforeEach(() => atom.notifications.onDidAddNotification(notificationSpy = jasmine.createSpy())); + + describe("when a file does not exist", () => + it("creates an empty buffer for the specified path", function() { + waitsForPromise(() => workspace.open('not-a-file.md')); + + return runs(function() { + const editor = workspace.getActiveTextEditor(); + expect(notificationSpy).not.toHaveBeenCalled(); + return expect(editor.getPath()).toContain('not-a-file.md'); + }); + }) + ); + + describe("when the user does not have access to the file", function() { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function(path) { + const error = new Error(`EACCES, permission denied '${path}'`); + error.path = path; + error.code = 'EACCES'; + throw error; + }) + ); + + return it("creates a notification", function() { + waitsForPromise(() => workspace.open('file1')); + + return runs(function() { + expect(notificationSpy).toHaveBeenCalled(); + const notification = notificationSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + return expect(notification.getMessage()).toContain('file1'); + }); + }); + }); + + describe("when the the operation is not permitted", function() { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function(path) { + const error = new Error(`EPERM, operation not permitted '${path}'`); + error.path = path; + error.code = 'EPERM'; + throw error; + }) + ); + + return it("creates a notification", function() { + waitsForPromise(() => workspace.open('file1')); + + return runs(function() { + expect(notificationSpy).toHaveBeenCalled(); + const notification = notificationSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Unable to open'); + return expect(notification.getMessage()).toContain('file1'); + }); + }); + }); + + describe("when the the file is already open in windows", function() { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function(path) { + const error = new Error(`EBUSY, resource busy or locked '${path}'`); + error.path = path; + error.code = 'EBUSY'; + throw error; + }) + ); + + return it("creates a notification", function() { + waitsForPromise(() => workspace.open('file1')); + + return runs(function() { + expect(notificationSpy).toHaveBeenCalled(); + const notification = notificationSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Unable to open'); + return expect(notification.getMessage()).toContain('file1'); + }); + }); + }); + + return describe("when there is an unhandled error", function() { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function(path) { + throw new Error("I dont even know what is happening right now!!"); + }) + ); + + return it("creates a notification", function() { + const open = () => workspace.open('file1', workspace.getActivePane()); + return expect(open).toThrow(); + }); + }); + }); + + describe("when the file is already open in pending state", () => + it("should terminate the pending state", function() { + let editor = null; + let pane = null; + + waitsForPromise(() => + atom.workspace.open('sample.js', {pending: true}).then(function(o) { + editor = o; + return pane = atom.workspace.getActivePane(); + }) + ); + + runs(() => expect(pane.getPendingItem()).toEqual(editor)); + + waitsForPromise(() => atom.workspace.open('sample.js')); + + return runs(() => expect(pane.getPendingItem()).toBeNull()); + }) + ); + + describe("when opening will switch from a pending tab to a permanent tab", () => + it("keeps the pending tab open", function() { + let editor1 = null; + let editor2 = null; + + waitsForPromise(() => + atom.workspace.open('sample.txt').then(o => editor1 = o) + ); + + waitsForPromise(() => + atom.workspace.open('sample2.txt', {pending: true}).then(o => editor2 = o) + ); + + return runs(function() { + const pane = atom.workspace.getActivePane(); + pane.activateItem(editor1); + expect(pane.getItems().length).toBe(2); + return expect(pane.getItems()).toEqual([editor1, editor2]);});})); + + return describe("when replacing a pending item which is the last item in a second pane", () => + it("does not destroy the pane even if core.destroyEmptyPanes is on", function() { + atom.config.set('core.destroyEmptyPanes', true); + let editor1 = null; + let editor2 = null; + const leftPane = atom.workspace.getActivePane(); + let rightPane = null; + + waitsForPromise(() => + atom.workspace.open('sample.js', {pending: true, split: 'right'}).then(function(o) { + editor1 = o; + rightPane = atom.workspace.getActivePane(); + return spyOn(rightPane, "destroyed"); + }) + ); + + runs(function() { + expect(leftPane).not.toBe(rightPane); + expect(atom.workspace.getActivePane()).toBe(rightPane); + expect(atom.workspace.getActivePane().getItems().length).toBe(1); + return expect(rightPane.getPendingItem()).toBe(editor1); + }); + + waitsForPromise(() => + atom.workspace.open('sample.txt', {pending: true}).then(o => editor2 = o) + ); + + return runs(function() { + expect(rightPane.getPendingItem()).toBe(editor2); + return expect(rightPane.destroyed.callCount).toBe(0); + }); + }) + ); + }); + + describe('the grammar-used hook', () => + it('fires when opening a file or changing the grammar of an open file', function() { + let editor = null; + let javascriptGrammarUsed = false; + let coffeescriptGrammarUsed = false; + + atom.packages.triggerDeferredActivationHooks(); + + runs(function() { + atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', () => javascriptGrammarUsed = true); + return atom.packages.onDidTriggerActivationHook('language-coffee-script:grammar-used', () => coffeescriptGrammarUsed = true); + }); + + waitsForPromise(() => atom.workspace.open('sample.js', {autoIndent: false}).then(o => editor = o)); + + waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + + waitsFor(() => javascriptGrammarUsed); + + waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')); + + runs(() => editor.setGrammar(atom.grammars.selectGrammar('.coffee'))); + + return waitsFor(() => coffeescriptGrammarUsed); + }) + ); + + describe("::reopenItem()", () => + it("opens the uri associated with the last closed pane that isn't currently open", function() { + const pane = workspace.getActivePane(); + waitsForPromise(() => + workspace.open('a').then(() => + workspace.open('b').then(() => + workspace.open('file1').then(() => workspace.open()) + ) + ) + ); + + runs(function() { + // does not reopen items with no uri + expect(workspace.getActivePaneItem().getURI()).toBeUndefined(); + return pane.destroyActiveItem(); + }); + + waitsForPromise(() => workspace.reopenItem()); + + runs(function() { + expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined(); + + // destroy all items + expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1'))); + pane.destroyActiveItem(); + expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('b'))); + pane.destroyActiveItem(); + expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('a'))); + pane.destroyActiveItem(); + + // reopens items with uris + return expect(workspace.getActivePaneItem()).toBeUndefined(); + }); + + waitsForPromise(() => workspace.reopenItem()); + + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a')))); + + // does not reopen items that are already open + waitsForPromise(() => workspace.open('b')); + + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b')))); + + waitsForPromise(() => workspace.reopenItem()); + + return runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1')))); + }) + ); + + describe("::increase/decreaseFontSize()", () => + it("increases/decreases the font size without going below 1", function() { + atom.config.set('editor.fontSize', 1); + workspace.increaseFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(2); + workspace.increaseFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(3); + workspace.decreaseFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(2); + workspace.decreaseFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(1); + workspace.decreaseFontSize(); + return expect(atom.config.get('editor.fontSize')).toBe(1); + }) + ); + + describe("::resetFontSize()", function() { + it("resets the font size to the window's starting font size", function() { + const originalFontSize = atom.config.get('editor.fontSize'); + + workspace.increaseFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize + 1); + workspace.resetFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); + workspace.decreaseFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize - 1); + workspace.resetFontSize(); + return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); + }); + + it("does nothing if the font size has not been changed", function() { + const originalFontSize = atom.config.get('editor.fontSize'); + + workspace.resetFontSize(); + return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); + }); + + return it("resets the font size when the editor's font size changes", function() { + const originalFontSize = atom.config.get('editor.fontSize'); + + atom.config.set('editor.fontSize', originalFontSize + 1); + workspace.resetFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); + atom.config.set('editor.fontSize', originalFontSize - 1); + workspace.resetFontSize(); + return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); + }); + }); + + describe("::openLicense()", () => + it("opens the license as plain-text in a buffer", function() { + waitsForPromise(() => workspace.openLicense()); + return runs(() => expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/)); + }) + ); + + describe("::isTextEditor(obj)", () => + it("returns true when the passed object is an instance of `TextEditor`", function() { + expect(workspace.isTextEditor(new TextEditor)).toBe(true); + expect(workspace.isTextEditor({getText() { return null; }})).toBe(false); + expect(workspace.isTextEditor(null)).toBe(false); + return expect(workspace.isTextEditor(undefined)).toBe(false); + }) + ); + + describe("::observeTextEditors()", () => + it("invokes the observer with current and future text editors", function() { + const observed = []; + + waitsForPromise(() => workspace.open()); + waitsForPromise(() => workspace.open()); + waitsForPromise(() => workspace.openLicense()); + + runs(() => workspace.observeTextEditors(editor => observed.push(editor))); + + waitsForPromise(() => workspace.open()); + + return expect(observed).toEqual(workspace.getTextEditors()); + }) + ); + + describe("when an editor is destroyed", () => + it("removes the editor", function() { + let editor = null; + + waitsForPromise(() => workspace.open("a").then(e => editor = e)); + + return runs(function() { + expect(workspace.getTextEditors()).toHaveLength(1); + editor.destroy(); + return expect(workspace.getTextEditors()).toHaveLength(0); + }); + }) + ); + + describe("when an editor is copied because its pane is split", () => + it("sets up the new editor to be configured by the text editor registry", function() { + waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + + return waitsForPromise(() => + workspace.open('a').then(function(editor) { + atom.textEditors.setGrammarOverride(editor, 'source.js'); + expect(editor.getGrammar().name).toBe('JavaScript'); + + workspace.getActivePane().splitRight({copyActiveItem: true}); + const newEditor = workspace.getActiveTextEditor(); + expect(newEditor).not.toBe(editor); + return expect(newEditor.getGrammar().name).toBe('JavaScript'); + }) + ); + }) + ); + + it("stores the active grammars used by all the open editors", function() { + waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + + waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')); + + waitsForPromise(() => atom.packages.activatePackage('language-todo')); + + waitsForPromise(() => atom.workspace.open('sample.coffee')); + + return runs(function() { + atom.workspace.getActiveTextEditor().setText(`\ +i = /test/; #FIXME\ +` + ); + + const atom2 = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + window: document.createElement('div'), + document: Object.assign( + document.createElement('div'), + { + body: document.createElement('div'), + head: document.createElement('div'), + } + ) + }); + + atom2.packages.loadPackage('language-javascript'); + atom2.packages.loadPackage('language-coffee-script'); + atom2.packages.loadPackage('language-todo'); + atom2.project.deserialize(atom.project.serialize()); + atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers); + + expect(atom2.grammars.getGrammars().map(grammar => grammar.name).sort()).toEqual([ + 'CoffeeScript', + 'CoffeeScript (Literate)', + 'JavaScript', + 'Null Grammar', + 'Regular Expression Replacement (JavaScript)', + 'Regular Expressions (JavaScript)', + 'TODO' + ]); + + return atom2.destroy(); + }); + }); + + describe("document.title", function() { + describe("when there is no item open", function() { + it("sets the title to the project path", () => expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0])))); + + return it("sets the title to 'untitled' if there is no project path", function() { + atom.project.setPaths([]); + return expect(document.title).toMatch(/^untitled/); + }); + }); + + describe("when the active pane item's path is not inside a project path", function() { + beforeEach(() => + waitsForPromise(() => + atom.workspace.open('b').then(() => atom.project.setPaths([])) + ) + ); + + it("sets the title to the pane item's title plus the item's path", function() { + const item = atom.workspace.getActivePaneItem(); + const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))); + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + }); + + describe("when the title of the active pane item changes", () => + it("updates the window title based on the item's new title", function() { + const editor = atom.workspace.getActivePaneItem(); + editor.buffer.setPath(path.join(temp.dir, 'hi')); + const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(editor.getPath()))); + return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)); + }) + ); + + describe("when the active pane's item changes", () => + it("updates the title to the new item's title plus the project path", function() { + atom.workspace.getActivePane().activateNextItem(); + const item = atom.workspace.getActivePaneItem(); + const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))); + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + }) + ); + + return describe("when an inactive pane's item changes", () => + it("does not update the title", function() { + const pane = atom.workspace.getActivePane(); + pane.splitRight(); + const initialTitle = document.title; + pane.activateNextItem(); + return expect(document.title).toBe(initialTitle); + }) + ); + }); + + describe("when the active pane item is inside a project path", function() { + beforeEach(() => + waitsForPromise(() => atom.workspace.open('b')) + ); + + describe("when there is an active pane item", () => + it("sets the title to the pane item's title plus the project path", function() { + const item = atom.workspace.getActivePaneItem(); + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + }) + ); + + describe("when the title of the active pane item changes", () => + it("updates the window title based on the item's new title", function() { + const editor = atom.workspace.getActivePaneItem(); + editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')); + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); + return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)); + }) + ); + + describe("when the active pane's item changes", () => + it("updates the title to the new item's title plus the project path", function() { + atom.workspace.getActivePane().activateNextItem(); + const item = atom.workspace.getActivePaneItem(); + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + }) + ); + + describe("when the last pane item is removed", () => + it("updates the title to the project's first path", function() { + atom.workspace.getActivePane().destroy(); + expect(atom.workspace.getActivePaneItem()).toBeUndefined(); + return expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))); + }) + ); + + return describe("when an inactive pane's item changes", () => + it("does not update the title", function() { + const pane = atom.workspace.getActivePane(); + pane.splitRight(); + const initialTitle = document.title; + pane.activateNextItem(); + return expect(document.title).toBe(initialTitle); + }) + ); + }); + + return describe("when the workspace is deserialized", function() { + beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))); + + return it("updates the title to contain the project's path", function() { + document.title = null; + + const atom2 = new AtomEnvironment({ + applicationDelegate: atom.applicationDelegate, + window: document.createElement('div'), + document: Object.assign( + document.createElement('div'), + { + body: document.createElement('div'), + head: document.createElement('div'), + } + ) + }); + + atom2.project.deserialize(atom.project.serialize()); + atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers); + const item = atom2.workspace.getActivePaneItem(); + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); + expect(document.title).toMatch(new RegExp(`^${item.getLongTitle()} \\u2014 ${pathEscaped}`)); + + return atom2.destroy(); + }); + }); + }); + + describe("document edited status", function() { + let [item1, item2] = Array.from([]); + + beforeEach(function() { + waitsForPromise(() => atom.workspace.open('a')); + waitsForPromise(() => atom.workspace.open('b')); + return runs(() => [item1, item2] = Array.from(atom.workspace.getPaneItems())); + }); + + it("calls setDocumentEdited when the active item changes", function() { + expect(atom.workspace.getActivePaneItem()).toBe(item2); + item1.insertText('a'); + expect(item1.isModified()).toBe(true); + atom.workspace.getActivePane().activateNextItem(); + + return expect(setDocumentEdited).toHaveBeenCalledWith(true); + }); + + return it("calls atom.setDocumentEdited when the active item's modified status changes", function() { + expect(atom.workspace.getActivePaneItem()).toBe(item2); + item2.insertText('a'); + advanceClock(item2.getBuffer().getStoppedChangingDelay()); + + expect(item2.isModified()).toBe(true); + expect(setDocumentEdited).toHaveBeenCalledWith(true); + + item2.undo(); + advanceClock(item2.getBuffer().getStoppedChangingDelay()); + + expect(item2.isModified()).toBe(false); + return expect(setDocumentEdited).toHaveBeenCalledWith(false); + }); + }); + + describe("adding panels", function() { + class TestItem {} + + class TestItemElement extends HTMLElement { + constructor() {} + initialize(model) { this.model = model; return this; } + getModel() { return this.model; } + } + + beforeEach(() => + atom.views.addViewProvider(TestItem, model => new TestItemElement().initialize(model)) + ); + + describe('::addLeftPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getLeftPanels().length).toBe(0); + atom.workspace.panelContainers.left.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addLeftPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getLeftPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + describe('::addRightPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getRightPanels().length).toBe(0); + atom.workspace.panelContainers.right.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addRightPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getRightPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + describe('::addTopPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getTopPanels().length).toBe(0); + atom.workspace.panelContainers.top.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addTopPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getTopPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + describe('::addBottomPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getBottomPanels().length).toBe(0); + atom.workspace.panelContainers.bottom.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addBottomPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getBottomPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + describe('::addHeaderPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getHeaderPanels().length).toBe(0); + atom.workspace.panelContainers.header.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addHeaderPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getHeaderPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + describe('::addFooterPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getFooterPanels().length).toBe(0); + atom.workspace.panelContainers.footer.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addFooterPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getFooterPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + describe('::addModalPanel(model)', () => + it('adds a panel to the correct panel container', function() { + let addPanelSpy; + expect(atom.workspace.getModalPanels().length).toBe(0); + atom.workspace.panelContainers.modal.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const model = new TestItem; + const panel = atom.workspace.addModalPanel({item: model}); + + expect(panel).toBeDefined(); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + + const itemView = atom.views.getView(atom.workspace.getModalPanels()[0].getItem()); + expect(itemView instanceof TestItemElement).toBe(true); + return expect(itemView.getModel()).toBe(model); + }) + ); + + return describe("::panelForItem(item)", () => + it("returns the panel associated with the item", function() { + const item = new TestItem; + const panel = atom.workspace.addLeftPanel({item}); + + const itemWithNoPanel = new TestItem; + + expect(atom.workspace.panelForItem(item)).toBe(panel); + return expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null); + }) + ); + }); + + describe("::scan(regex, options, callback)", () => + describe("when called with a regex", function() { + it("calls the callback with all regex results in all files in the project", function() { + const results = []; + waitsForPromise(() => + atom.workspace.scan(/(a)+/, result => results.push(result)) + ); + + return runs(function() { + expect(results).toHaveLength(3); + expect(results[0].filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))); + expect(results[0].matches).toHaveLength(3); + return expect(results[0].matches[0]).toEqual({ + matchText: 'aaa', + lineText: 'aaa bbb', + lineTextOffset: 0, + range: [[0, 0], [0, 3]]});});}); + + it("works with with escaped literals (like $ and ^)", function() { + const results = []; + waitsForPromise(() => atom.workspace.scan(/\$\w+/, result => results.push(result))); + + return runs(function() { + expect(results.length).toBe(1); + + const {filePath, matches} = results[0]; + expect(filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))); + expect(matches).toHaveLength(1); + return expect(matches[0]).toEqual({ + matchText: '$bill', + lineText: 'dollar$bill', + lineTextOffset: 0, + range: [[2, 6], [2, 11]]});});}); + + it("works on evil filenames", function() { + atom.config.set('core.excludeVcsIgnoredPaths', false); + platform.generateEvilFiles(); + atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')]); + const paths = []; + let matches = []; + waitsForPromise(() => + atom.workspace.scan(/evil/, function(result) { + paths.push(result.filePath); + return matches = matches.concat(result.matches); + }) + ); + + return runs(function() { + _.each(matches, m => expect(m.matchText).toEqual('evil')); + + if (platform.isWindows()) { + expect(paths.length).toBe(3); + expect(paths[0]).toMatch(/a_file_with_utf8.txt$/); + expect(paths[1]).toMatch(/file with spaces.txt$/); + return expect(path.basename(paths[2])).toBe("utfa\u0306.md"); + } else { + expect(paths.length).toBe(5); + expect(paths[0]).toMatch(/a_file_with_utf8.txt$/); + expect(paths[1]).toMatch(/file with spaces.txt$/); + expect(paths[2]).toMatch(/goddam\nnewlines$/m); + expect(paths[3]).toMatch(/quote".txt$/m); + return expect(path.basename(paths[4])).toBe("utfa\u0306.md"); + } + }); + }); + + it("ignores case if the regex includes the `i` flag", function() { + const results = []; + waitsForPromise(() => atom.workspace.scan(/DOLLAR/i, result => results.push(result))); + + return runs(() => expect(results).toHaveLength(1)); + }); + + describe("when the core.excludeVcsIgnoredPaths config is truthy", function() { + let [projectPath, ignoredPath] = Array.from([]); + + beforeEach(function() { + const sourceProjectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir'); + projectPath = path.join(temp.mkdirSync("atom")); + + const writerStream = fstream.Writer(projectPath); + fstream.Reader(sourceProjectPath).pipe(writerStream); + + waitsFor(function(done) { + writerStream.on('close', done); + return writerStream.on('error', done); + }); + + return runs(function() { + fs.rename(path.join(projectPath, 'git.git'), path.join(projectPath, '.git')); + ignoredPath = path.join(projectPath, 'ignored.txt'); + return fs.writeFileSync(ignoredPath, 'this match should not be included'); + }); + }); + + afterEach(function() { + if (fs.existsSync(projectPath)) { return fs.removeSync(projectPath); } + }); + + return it("excludes ignored files", function() { + atom.project.setPaths([projectPath]); + atom.config.set('core.excludeVcsIgnoredPaths', true); + const resultHandler = jasmine.createSpy("result found"); + waitsForPromise(() => + atom.workspace.scan(/match/, results => resultHandler()) + ); + + return runs(() => expect(resultHandler).not.toHaveBeenCalled()); + }); + }); + + it("includes only files when a directory filter is specified", function() { + const projectPath = path.join(path.join(__dirname, 'fixtures', 'dir')); + atom.project.setPaths([projectPath]); + + const filePath = path.join(projectPath, 'a-dir', 'oh-git'); + + const paths = []; + let matches = []; + waitsForPromise(() => + atom.workspace.scan(/aaa/, {paths: [`a-dir${path.sep}`]}, function(result) { + paths.push(result.filePath); + return matches = matches.concat(result.matches); + }) + ); + + return runs(function() { + expect(paths.length).toBe(1); + expect(paths[0]).toBe(filePath); + return expect(matches.length).toBe(1); + }); + }); + + it("includes files and folders that begin with a '.'", function() { + const projectPath = temp.mkdirSync('atom-spec-workspace'); + const filePath = path.join(projectPath, '.text'); + fs.writeFileSync(filePath, 'match this'); + atom.project.setPaths([projectPath]); + const paths = []; + let matches = []; + waitsForPromise(() => + atom.workspace.scan(/match this/, function(result) { + paths.push(result.filePath); + return matches = matches.concat(result.matches); + }) + ); + + return runs(function() { + expect(paths.length).toBe(1); + expect(paths[0]).toBe(filePath); + return expect(matches.length).toBe(1); + }); + }); + + it("excludes values in core.ignoredNames", function() { + const ignoredNames = atom.config.get("core.ignoredNames"); + ignoredNames.push("a"); + atom.config.set("core.ignoredNames", ignoredNames); + + const resultHandler = jasmine.createSpy("result found"); + waitsForPromise(() => + atom.workspace.scan(/dollar/, results => resultHandler()) + ); + + return runs(() => expect(resultHandler).not.toHaveBeenCalled()); + }); + + it("scans buffer contents if the buffer is modified", function() { + let editor = null; + const results = []; + + waitsForPromise(() => + atom.workspace.open('a').then(function(o) { + editor = o; + return editor.setText("Elephant"); + }) + ); + + waitsForPromise(() => atom.workspace.scan(/a|Elephant/, result => results.push(result))); + + return runs(function() { + expect(results).toHaveLength(3); + const resultForA = _.find(results, ({filePath}) => path.basename(filePath) === 'a'); + expect(resultForA.matches).toHaveLength(1); + return expect(resultForA.matches[0].matchText).toBe('Elephant'); + }); + }); + + it("ignores buffers outside the project", function() { + let editor = null; + const results = []; + + waitsForPromise(() => + atom.workspace.open(temp.openSync().path).then(function(o) { + editor = o; + return editor.setText("Elephant"); + }) + ); + + waitsForPromise(() => atom.workspace.scan(/Elephant/, result => results.push(result))); + + return runs(() => expect(results).toHaveLength(0)); + }); + + return describe("when the project has multiple root directories", function() { + let [dir1, dir2, file1, file2] = Array.from([]); + + beforeEach(function() { + [dir1] = Array.from(atom.project.getPaths()); + file1 = path.join(dir1, "a-dir", "oh-git"); + + dir2 = temp.mkdirSync("a-second-dir"); + const aDir2 = path.join(dir2, "a-dir"); + file2 = path.join(aDir2, "a-file"); + fs.mkdirSync(aDir2); + fs.writeFileSync(file2, "ccc aaaa"); + + return atom.project.addPath(dir2); + }); + + it("searches matching files in all of the project's root directories", function() { + const resultPaths = []; + waitsForPromise(() => + atom.workspace.scan(/aaaa/, ({filePath}) => resultPaths.push(filePath)) + ); + + return runs(() => expect(resultPaths.sort()).toEqual([file1, file2].sort())); + }); + + describe("when an inclusion path starts with the basename of a root directory", () => + it("interprets the inclusion path as starting from that directory", function() { + waitsForPromise(function() { + const resultPaths = []; + return atom.workspace + .scan(/aaaa/, {paths: ["dir"]}, function({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file1])); + }); + + waitsForPromise(function() { + const resultPaths = []; + return atom.workspace + .scan(/aaaa/, {paths: [path.join("dir", "a-dir")]}, function({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file1])); + }); + + waitsForPromise(function() { + const resultPaths = []; + return atom.workspace + .scan(/aaaa/, {paths: [path.basename(dir2)]}, function({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file2])); + }); + + return waitsForPromise(function() { + const resultPaths = []; + return atom.workspace + .scan(/aaaa/, {paths: [path.join(path.basename(dir2), "a-dir")]}, function({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file2])); + }); + }) + ); + + return describe("when a custom directory searcher is registered", function() { + let fakeSearch = null; + // Function that is invoked once all of the fields on fakeSearch are set. + let onFakeSearchCreated = null; + + class FakeSearch { + constructor(options) { + // Note that hoisting resolve and reject in this way is generally frowned upon. + this.options = options; + this.promise = new Promise((function(resolve, reject) { + this.hoistedResolve = resolve; + this.hoistedReject = reject; + return (typeof onFakeSearchCreated === 'function' ? onFakeSearchCreated(this) : undefined); + }.bind(this))); + } + then(...args) { + return this.promise.then.apply(this.promise, args); + } + cancel() { + this.cancelled = true; + // According to the spec for a DirectorySearcher, invoking `cancel()` should + // resolve the thenable rather than reject it. + return this.hoistedResolve(); + } + } + + beforeEach(function() { + fakeSearch = null; + onFakeSearchCreated = null; + atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { + canSearchDirectory(directory) { return directory.getPath() === dir1; }, + search(directory, regex, options) { return fakeSearch = new FakeSearch(options); } + }); + + return waitsFor(() => atom.workspace.directorySearchers.length > 0); + }); + + it("can override the DefaultDirectorySearcher on a per-directory basis", function() { + const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt'; + const numPathsSearchedInDir2 = 1; + const numPathsToPretendToSearchInCustomDirectorySearcher = 10; + const searchResult = { + filePath: foreignFilePath, + matches: [ + { + lineText: 'Hello world', + lineTextOffset: 0, + matchText: 'Hello', + range: [[0, 0], [0, 5]], + }, + ] + }; + onFakeSearchCreated = function(fakeSearch) { + fakeSearch.options.didMatch(searchResult); + fakeSearch.options.didSearchPaths(numPathsToPretendToSearchInCustomDirectorySearcher); + return fakeSearch.hoistedResolve(); + }; + + const resultPaths = []; + const onPathsSearched = jasmine.createSpy('onPathsSearched'); + waitsForPromise(() => + atom.workspace.scan(/aaaa/, {onPathsSearched}, ({filePath}) => resultPaths.push(filePath)) + ); + + return runs(function() { + expect(resultPaths.sort()).toEqual([foreignFilePath, file2].sort()); + // onPathsSearched should be called once by each DirectorySearcher. The order is not + // guaranteed, so we can only verify the total number of paths searched is correct + // after the second call. + expect(onPathsSearched.callCount).toBe(2); + return expect(onPathsSearched.mostRecentCall.args[0]).toBe( + numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2); + }); + }); + + it("can be cancelled when the object returned by scan() has its cancel() method invoked", function() { + const thenable = atom.workspace.scan(/aaaa/, function() {}); + let resultOfPromiseSearch = null; + + waitsFor('fakeSearch to be defined', () => fakeSearch != null); + + runs(function() { + expect(fakeSearch.cancelled).toBe(undefined); + thenable.cancel(); + return expect(fakeSearch.cancelled).toBe(true); + }); + + + waitsForPromise(() => thenable.then(promiseResult => resultOfPromiseSearch = promiseResult)); + + return runs(() => expect(resultOfPromiseSearch).toBe('cancelled')); + }); + + return it("will have the side-effect of failing the overall search if it fails", function() { + // This provider's search should be cancelled when the first provider fails + let cancelableSearch; + let fakeSearch2 = null; + atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { + canSearchDirectory(directory) { return directory.getPath() === dir2; }, + search(directory, regex, options) { return fakeSearch2 = new FakeSearch(options); } + }); + + let didReject = false; + const promise = cancelableSearch = atom.workspace.scan(/aaaa/, function() {}); + waitsFor('fakeSearch to be defined', () => fakeSearch != null); + + runs(() => fakeSearch.hoistedReject()); + + waitsForPromise(() => cancelableSearch.catch(() => didReject = true)); + + waitsFor(done => promise.then(null, done)); + + return runs(function() { + expect(didReject).toBe(true); + return expect(fakeSearch2.cancelled).toBe(true); + }); + }); + }); + }); + }) + ); // Cancels other ongoing searches + + describe("::replace(regex, replacementText, paths, iterator)", function() { + let [filePath, commentFilePath, sampleContent, sampleCommentContent] = Array.from([]); + + beforeEach(function() { + atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('../'))]); + + filePath = __guard__(atom.project.getDirectories()[0], x1 => x1.resolve('sample.js')); + commentFilePath = __guard__(atom.project.getDirectories()[0], x2 => x2.resolve('sample-with-comments.js')); + sampleContent = fs.readFileSync(filePath).toString(); + return sampleCommentContent = fs.readFileSync(commentFilePath).toString(); + }); + + afterEach(function() { + fs.writeFileSync(filePath, sampleContent); + return fs.writeFileSync(commentFilePath, sampleCommentContent); + }); + + describe("when a file doesn't exist", () => + it("calls back with an error", function() { + const errors = []; + const missingPath = path.resolve('/not-a-file.js'); + expect(fs.existsSync(missingPath)).toBeFalsy(); + + waitsForPromise(() => + atom.workspace.replace(/items/gi, 'items', [missingPath], (result, error) => errors.push(error)) + ); + + return runs(function() { + expect(errors).toHaveLength(1); + return expect(errors[0].path).toBe(missingPath); + }); + }) + ); + + describe("when called with unopened files", () => + it("replaces properly", function() { + const results = []; + waitsForPromise(() => + atom.workspace.replace(/items/gi, 'items', [filePath], result => results.push(result)) + ); + + return runs(function() { + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + return expect(results[0].replacements).toBe(6); + }); + }) + ); + + return describe("when a buffer is already open", function() { + it("replaces properly and saves when not modified", function() { + let editor = null; + const results = []; + + waitsForPromise(() => atom.workspace.open('sample.js').then(o => editor = o)); + + runs(() => expect(editor.isModified()).toBeFalsy()); + + waitsForPromise(() => + atom.workspace.replace(/items/gi, 'items', [filePath], result => results.push(result)) + ); + + return runs(function() { + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); + + return expect(editor.isModified()).toBeFalsy(); + }); + }); + + it("does not replace when the path is not specified", function() { + let editor = null; + const results = []; + + waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => editor = o)); + + waitsForPromise(() => + atom.workspace.replace(/items/gi, 'items', [commentFilePath], result => results.push(result)) + ); + + return runs(function() { + expect(results).toHaveLength(1); + return expect(results[0].filePath).toBe(commentFilePath); + }); + }); + + return it("does NOT save when modified", function() { + let editor = null; + const results = []; + + waitsForPromise(() => atom.workspace.open('sample.js').then(o => editor = o)); + + runs(function() { + editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg'); + return expect(editor.isModified()).toBeTruthy(); + }); + + waitsForPromise(() => + atom.workspace.replace(/items/gi, 'okthen', [filePath], result => results.push(result)) + ); + + return runs(function() { + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); + + return expect(editor.isModified()).toBeTruthy(); + }); + }); + }); + }); + + describe("::saveActivePaneItem()", function() { + let editor = null; + beforeEach(() => + waitsForPromise(() => atom.workspace.open('sample.js').then(o => editor = o)) + ); + + return describe("when there is an error", function() { + it("emits a warning notification when the file cannot be saved", function() { + let addedSpy; + spyOn(editor, 'save').andCallFake(function() { + throw new Error("'/some/file' is a directory"); + }); + + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); + atom.workspace.saveActivePaneItem(); + expect(addedSpy).toHaveBeenCalled(); + return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning'); + }); + + it("emits a warning notification when the directory cannot be written to", function() { + let addedSpy; + spyOn(editor, 'save').andCallFake(function() { + throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'"); + }); + + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); + atom.workspace.saveActivePaneItem(); + expect(addedSpy).toHaveBeenCalled(); + return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning'); + }); + + it("emits a warning notification when the user does not have permission", function() { + let addedSpy; + spyOn(editor, 'save').andCallFake(function() { + const error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'"); + error.code = 'EACCES'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); + + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); + atom.workspace.saveActivePaneItem(); + expect(addedSpy).toHaveBeenCalled(); + return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning'); + }); + + it("emits a warning notification when the operation is not permitted", () => + spyOn(editor, 'save').andCallFake(function() { + const error = new Error("EPERM, operation not permitted '/Some/dir/and-a-file.js'"); + error.code = 'EPERM'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }) + ); + + it("emits a warning notification when the file is already open by another app", function() { + let addedSpy; + spyOn(editor, 'save').andCallFake(function() { + const error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'"); + error.code = 'EBUSY'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); + + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); + atom.workspace.saveActivePaneItem(); + expect(addedSpy).toHaveBeenCalled(); + + const notificaiton = addedSpy.mostRecentCall.args[0]; + expect(notificaiton.getType()).toBe('warning'); + return expect(notificaiton.getMessage()).toContain('Unable to save'); + }); + + it("emits a warning notification when the file system is read-only", function() { + let addedSpy; + spyOn(editor, 'save').andCallFake(function() { + const error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'"); + error.code = 'EROFS'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); + + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); + atom.workspace.saveActivePaneItem(); + expect(addedSpy).toHaveBeenCalled(); + + const notification = addedSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('warning'); + return expect(notification.getMessage()).toContain('Unable to save'); + }); + + return it("emits a warning notification when the file cannot be saved", function() { + spyOn(editor, 'save').andCallFake(function() { + throw new Error("no one knows"); + }); + + const save = () => atom.workspace.saveActivePaneItem(); + return expect(save).toThrow(); + }); + }); + }); + + describe("::closeActivePaneItemOrEmptyPaneOrWindow", function() { + beforeEach(function() { + spyOn(atom, 'close'); + return waitsForPromise(() => atom.workspace.open()); + }); + + return it("closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane", function() { + atom.config.set('core.destroyEmptyPanes', false); + + const pane1 = atom.workspace.getActivePane(); + const pane2 = pane1.splitRight({copyActiveItem: true}); + + expect(atom.workspace.getPanes().length).toBe(2); + expect(pane2.getItems().length).toBe(1); + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + + expect(atom.workspace.getPanes().length).toBe(2); + expect(pane2.getItems().length).toBe(0); + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + + expect(atom.workspace.getPanes().length).toBe(1); + expect(pane1.getItems().length).toBe(1); + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + expect(atom.workspace.getPanes().length).toBe(1); + expect(pane1.getItems().length).toBe(0); + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + expect(atom.workspace.getPanes().length).toBe(1); + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + return expect(atom.close).toHaveBeenCalled(); + }); + }); + + describe("when the core.allowPendingPaneItems option is falsey", () => + it("does not open item with `pending: true` option as pending", function() { + let pane = null; + atom.config.set('core.allowPendingPaneItems', false); + + waitsForPromise(() => + atom.workspace.open('sample.js', {pending: true}).then(() => pane = atom.workspace.getActivePane()) + ); + + return runs(() => expect(pane.getPendingItem()).toBeFalsy()); + }) + ); + + describe("grammar activation", () => + it("notifies the workspace of which grammar is used", function() { + const editor = null; + atom.packages.triggerDeferredActivationHooks(); + + const javascriptGrammarUsed = jasmine.createSpy('js grammar used'); + const rubyGrammarUsed = jasmine.createSpy('ruby grammar used'); + const cGrammarUsed = jasmine.createSpy('c grammar used'); + + atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', javascriptGrammarUsed); + atom.packages.onDidTriggerActivationHook('language-ruby:grammar-used', rubyGrammarUsed); + atom.packages.onDidTriggerActivationHook('language-c:grammar-used', cGrammarUsed); + + waitsForPromise(() => atom.packages.activatePackage('language-ruby')); + waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + waitsForPromise(() => atom.packages.activatePackage('language-c')); + waitsForPromise(() => atom.workspace.open('sample-with-comments.js')); + + return runs(function() { + // Hooks are triggered when opening new editors + expect(javascriptGrammarUsed).toHaveBeenCalled(); + + // Hooks are triggered when changing existing editors grammars + atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.c')); + expect(cGrammarUsed).toHaveBeenCalled(); + + // Hooks are triggered when editors are added in other ways. + atom.workspace.getActivePane().splitRight({copyActiveItem: true}); + atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.ruby')); + return expect(rubyGrammarUsed).toHaveBeenCalled(); + }); + }) + ); + + describe(".checkoutHeadRevision()", function() { + let editor = null; + beforeEach(function() { + atom.config.set("editor.confirmCheckoutHeadRevision", false); + + return waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => editor = o)); + }); + + it("reverts to the version of its file checked into the project repository", function() { + editor.setCursorBufferPosition([0, 0]); + editor.insertText("---\n"); + expect(editor.lineTextForBufferRow(0)).toBe("---"); + + waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)); + + return runs(() => expect(editor.lineTextForBufferRow(0)).toBe("")); + }); + + return describe("when there's no repository for the editor's file", () => + it("doesn't do anything", function() { + editor = new TextEditor; + editor.setText("stuff"); + atom.workspace.checkoutHeadRevision(editor); + + return waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)); + }) + ); + }); + + return escapeStringRegex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} diff --git a/src/workspace.coffee b/src/workspace.coffee deleted file mode 100644 index 2a46ce57a..000000000 --- a/src/workspace.coffee +++ /dev/null @@ -1,1121 +0,0 @@ -_ = require 'underscore-plus' -url = require 'url' -path = require 'path' -{Emitter, Disposable, CompositeDisposable} = require 'event-kit' -fs = require 'fs-plus' -{Directory} = require 'pathwatcher' -DefaultDirectorySearcher = require './default-directory-searcher' -Model = require './model' -TextEditor = require './text-editor' -PaneContainer = require './pane-container' -Panel = require './panel' -PanelContainer = require './panel-container' -Task = require './task' - -# Essential: Represents the state of the user interface for the entire window. -# An instance of this class is available via the `atom.workspace` global. -# -# Interact with this object to open files, be notified of current and future -# editors, and manipulate panes. To add panels, use {Workspace::addTopPanel} -# and friends. -# -# * `editor` {TextEditor} the new editor -# -module.exports = -class Workspace extends Model - constructor: (params) -> - super - - { - @packageManager, @config, @project, @grammarRegistry, @notificationManager, - @viewRegistry, @grammarRegistry, @applicationDelegate, @assert, - @deserializerManager, @textEditorRegistry - } = params - - @emitter = new Emitter - @openers = [] - @destroyedItemURIs = [] - - @paneContainer = new PaneContainer({@config, @applicationDelegate, @notificationManager, @deserializerManager}) - @paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem) - - @defaultDirectorySearcher = new DefaultDirectorySearcher() - @consumeServices(@packageManager) - - # One cannot simply .bind here since it could be used as a component with - # Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always - # the newly created object. - realThis = this - @buildTextEditor = -> Workspace.prototype.buildTextEditor.apply(realThis, arguments) - - @panelContainers = - top: new PanelContainer({location: 'top'}) - left: new PanelContainer({location: 'left'}) - right: new PanelContainer({location: 'right'}) - bottom: new PanelContainer({location: 'bottom'}) - header: new PanelContainer({location: 'header'}) - footer: new PanelContainer({location: 'footer'}) - modal: new PanelContainer({location: 'modal'}) - - @subscribeToEvents() - - reset: (@packageManager) -> - @emitter.dispose() - @emitter = new Emitter - - @paneContainer.destroy() - panelContainer.destroy() for panelContainer in @panelContainers - - @paneContainer = new PaneContainer({@config, @applicationDelegate, @notificationManager, @deserializerManager}) - @paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem) - - @panelContainers = - top: new PanelContainer({location: 'top'}) - left: new PanelContainer({location: 'left'}) - right: new PanelContainer({location: 'right'}) - bottom: new PanelContainer({location: 'bottom'}) - header: new PanelContainer({location: 'header'}) - footer: new PanelContainer({location: 'footer'}) - modal: new PanelContainer({location: 'modal'}) - - @originalFontSize = null - @openers = [] - @destroyedItemURIs = [] - @consumeServices(@packageManager) - - subscribeToEvents: -> - @subscribeToActiveItem() - @subscribeToFontSize() - @subscribeToAddedItems() - - consumeServices: ({serviceHub}) -> - @directorySearchers = [] - serviceHub.consume( - 'atom.directory-searcher', - '^0.1.0', - (provider) => @directorySearchers.unshift(provider)) - - # Called by the Serializable mixin during serialization. - serialize: -> - deserializer: 'Workspace' - paneContainer: @paneContainer.serialize() - packagesWithActiveGrammars: @getPackageNamesWithActiveGrammars() - destroyedItemURIs: @destroyedItemURIs.slice() - - deserialize: (state, deserializerManager) -> - for packageName in state.packagesWithActiveGrammars ? [] - @packageManager.getLoadedPackage(packageName)?.loadGrammarsSync() - if state.destroyedItemURIs? - @destroyedItemURIs = state.destroyedItemURIs - @paneContainer.deserialize(state.paneContainer, deserializerManager) - - getPackageNamesWithActiveGrammars: -> - packageNames = [] - addGrammar = ({includedGrammarScopes, packageName}={}) => - return unless packageName - # Prevent cycles - return if packageNames.indexOf(packageName) isnt -1 - - packageNames.push(packageName) - for scopeName in includedGrammarScopes ? [] - addGrammar(@grammarRegistry.grammarForScopeName(scopeName)) - return - - editors = @getTextEditors() - addGrammar(editor.getGrammar()) for editor in editors - - if editors.length > 0 - for grammar in @grammarRegistry.getGrammars() when grammar.injectionSelector - addGrammar(grammar) - - _.uniq(packageNames) - - subscribeToActiveItem: -> - @updateWindowTitle() - @updateDocumentEdited() - @project.onDidChangePaths @updateWindowTitle - - @observeActivePaneItem (item) => - @updateWindowTitle() - @updateDocumentEdited() - - @activeItemSubscriptions?.dispose() - @activeItemSubscriptions = new CompositeDisposable - - if typeof item?.onDidChangeTitle is 'function' - titleSubscription = item.onDidChangeTitle(@updateWindowTitle) - else if typeof item?.on is 'function' - titleSubscription = item.on('title-changed', @updateWindowTitle) - unless typeof titleSubscription?.dispose is 'function' - titleSubscription = new Disposable => item.off('title-changed', @updateWindowTitle) - - if typeof item?.onDidChangeModified is 'function' - modifiedSubscription = item.onDidChangeModified(@updateDocumentEdited) - else if typeof item?.on? is 'function' - modifiedSubscription = item.on('modified-status-changed', @updateDocumentEdited) - unless typeof modifiedSubscription?.dispose is 'function' - modifiedSubscription = new Disposable => item.off('modified-status-changed', @updateDocumentEdited) - - @activeItemSubscriptions.add(titleSubscription) if titleSubscription? - @activeItemSubscriptions.add(modifiedSubscription) if modifiedSubscription? - - subscribeToAddedItems: -> - @onDidAddPaneItem ({item, pane, index}) => - if item instanceof TextEditor - subscriptions = new CompositeDisposable( - @textEditorRegistry.add(item) - @textEditorRegistry.maintainGrammar(item) - @textEditorRegistry.maintainConfig(item) - item.observeGrammar(@handleGrammarUsed.bind(this)) - ) - item.onDidDestroy -> subscriptions.dispose() - @emitter.emit 'did-add-text-editor', {textEditor: item, pane, index} - - # Updates the application's title and proxy icon based on whichever file is - # open. - updateWindowTitle: => - appName = 'Atom' - projectPaths = @project.getPaths() ? [] - if item = @getActivePaneItem() - itemPath = item.getPath?() - itemTitle = item.getLongTitle?() ? item.getTitle?() - projectPath = _.find projectPaths, (projectPath) -> - itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep) - itemTitle ?= "untitled" - projectPath ?= if itemPath then path.dirname(itemPath) else projectPaths[0] - if projectPath? - projectPath = fs.tildify(projectPath) - - titleParts = [] - if item? and projectPath? - titleParts.push itemTitle, projectPath - representedPath = itemPath ? projectPath - else if projectPath? - titleParts.push projectPath - representedPath = projectPath - else - titleParts.push itemTitle - representedPath = "" - - unless process.platform is 'darwin' - titleParts.push appName - - document.title = titleParts.join(" \u2014 ") - @applicationDelegate.setRepresentedFilename(representedPath) - - # On macOS, fades the application window's proxy icon when the current file - # has been modified. - updateDocumentEdited: => - modified = @getActivePaneItem()?.isModified?() ? false - @applicationDelegate.setWindowDocumentEdited(modified) - - ### - Section: Event Subscription - ### - - # Essential: Invoke the given callback with all current and future text - # editors in the workspace. - # - # * `callback` {Function} to be called with current and future text editors. - # * `editor` An {TextEditor} that is present in {::getTextEditors} at the time - # of subscription or that is added at some later time. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeTextEditors: (callback) -> - callback(textEditor) for textEditor in @getTextEditors() - @onDidAddTextEditor ({textEditor}) -> callback(textEditor) - - # Essential: Invoke the given callback with all current and future panes items - # in the workspace. - # - # * `callback` {Function} to be called with current and future pane items. - # * `item` An item that is present in {::getPaneItems} at the time of - # subscription or that is added at some later time. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observePaneItems: (callback) -> @paneContainer.observePaneItems(callback) - - # Essential: Invoke the given callback when the active pane item changes. - # - # Because observers are invoked synchronously, it's important not to perform - # any expensive operations via this method. Consider - # {::onDidStopChangingActivePaneItem} to delay operations until after changes - # stop occurring. - # - # * `callback` {Function} to be called when the active pane item changes. - # * `item` The active pane item. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePaneItem: (callback) -> - @paneContainer.onDidChangeActivePaneItem(callback) - - # Essential: Invoke the given callback when the active pane item stops - # changing. - # - # Observers are called asynchronously 100ms after the last active pane item - # change. Handling changes here rather than in the synchronous - # {::onDidChangeActivePaneItem} prevents unneeded work if the user is quickly - # changing or closing tabs and ensures critical UI feedback, like changing the - # highlighted tab, gets priority over work that can be done asynchronously. - # - # * `callback` {Function} to be called when the active pane item stopts - # changing. - # * `item` The active pane item. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidStopChangingActivePaneItem: (callback) -> - @paneContainer.onDidStopChangingActivePaneItem(callback) - - # Essential: Invoke the given callback with the current active pane item and - # with all future active pane items in the workspace. - # - # * `callback` {Function} to be called when the active pane item changes. - # * `item` The current active pane item. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActivePaneItem: (callback) -> @paneContainer.observeActivePaneItem(callback) - - # Essential: Invoke the given callback whenever an item is opened. Unlike - # {::onDidAddPaneItem}, observers will be notified for items that are already - # present in the workspace when they are reopened. - # - # * `callback` {Function} to be called whenever an item is opened. - # * `event` {Object} with the following keys: - # * `uri` {String} representing the opened URI. Could be `undefined`. - # * `item` The opened item. - # * `pane` The pane in which the item was opened. - # * `index` The index of the opened item on its pane. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidOpen: (callback) -> - @emitter.on 'did-open', callback - - # Extended: Invoke the given callback when a pane is added to the workspace. - # - # * `callback` {Function} to be called panes are added. - # * `event` {Object} with the following keys: - # * `pane` The added pane. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddPane: (callback) -> @paneContainer.onDidAddPane(callback) - - # Extended: Invoke the given callback before a pane is destroyed in the - # workspace. - # - # * `callback` {Function} to be called before panes are destroyed. - # * `event` {Object} with the following keys: - # * `pane` The pane to be destroyed. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillDestroyPane: (callback) -> @paneContainer.onWillDestroyPane(callback) - - # Extended: Invoke the given callback when a pane is destroyed in the - # workspace. - # - # * `callback` {Function} to be called panes are destroyed. - # * `event` {Object} with the following keys: - # * `pane` The destroyed pane. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroyPane: (callback) -> @paneContainer.onDidDestroyPane(callback) - - # Extended: Invoke the given callback with all current and future panes in the - # workspace. - # - # * `callback` {Function} to be called with current and future panes. - # * `pane` A {Pane} that is present in {::getPanes} at the time of - # subscription or that is added at some later time. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observePanes: (callback) -> @paneContainer.observePanes(callback) - - # Extended: Invoke the given callback when the active pane changes. - # - # * `callback` {Function} to be called when the active pane changes. - # * `pane` A {Pane} that is the current return value of {::getActivePane}. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePane: (callback) -> @paneContainer.onDidChangeActivePane(callback) - - # Extended: Invoke the given callback with the current active pane and when - # the active pane changes. - # - # * `callback` {Function} to be called with the current and future active# - # panes. - # * `pane` A {Pane} that is the current return value of {::getActivePane}. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActivePane: (callback) -> @paneContainer.observeActivePane(callback) - - # Extended: Invoke the given callback when a pane item is added to the - # workspace. - # - # * `callback` {Function} to be called when pane items are added. - # * `event` {Object} with the following keys: - # * `item` The added pane item. - # * `pane` {Pane} containing the added item. - # * `index` {Number} indicating the index of the added item in its pane. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddPaneItem: (callback) -> @paneContainer.onDidAddPaneItem(callback) - - # Extended: Invoke the given callback when a pane item is about to be - # destroyed, before the user is prompted to save it. - # - # * `callback` {Function} to be called before pane items are destroyed. - # * `event` {Object} with the following keys: - # * `item` The item to be destroyed. - # * `pane` {Pane} containing the item to be destroyed. - # * `index` {Number} indicating the index of the item to be destroyed in - # its pane. - # - # Returns a {Disposable} on which `.dispose` can be called to unsubscribe. - onWillDestroyPaneItem: (callback) -> @paneContainer.onWillDestroyPaneItem(callback) - - # Extended: Invoke the given callback when a pane item is destroyed. - # - # * `callback` {Function} to be called when pane items are destroyed. - # * `event` {Object} with the following keys: - # * `item` The destroyed item. - # * `pane` {Pane} containing the destroyed item. - # * `index` {Number} indicating the index of the destroyed item in its - # pane. - # - # Returns a {Disposable} on which `.dispose` can be called to unsubscribe. - onDidDestroyPaneItem: (callback) -> @paneContainer.onDidDestroyPaneItem(callback) - - # Extended: Invoke the given callback when a text editor is added to the - # workspace. - # - # * `callback` {Function} to be called panes are added. - # * `event` {Object} with the following keys: - # * `textEditor` {TextEditor} that was added. - # * `pane` {Pane} containing the added text editor. - # * `index` {Number} indicating the index of the added text editor in its - # pane. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddTextEditor: (callback) -> - @emitter.on 'did-add-text-editor', callback - - ### - Section: Opening - ### - - # Essential: Opens the given URI in Atom asynchronously. - # If the URI is already open, the existing item for that URI will be - # activated. If no URI is given, or no registered opener can open - # the URI, a new empty {TextEditor} will be created. - # - # * `uri` (optional) A {String} containing a URI. - # * `options` (optional) {Object} - # * `initialLine` A {Number} indicating which row to move the cursor to - # initially. Defaults to `0`. - # * `initialColumn` A {Number} indicating which column to move the cursor to - # initially. Defaults to `0`. - # * `split` Either 'left', 'right', 'up' or 'down'. - # If 'left', the item will be opened in leftmost pane of the current active pane's row. - # If 'right', the item will be opened in the rightmost pane of the current active pane's row. If only one pane exists in the row, a new pane will be created. - # If 'up', the item will be opened in topmost pane of the current active pane's column. - # If 'down', the item will be opened in the bottommost pane of the current active pane's column. If only one pane exists in the column, a new pane will be created. - # * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on - # containing pane. Defaults to `true`. - # * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} - # on containing pane. Defaults to `true`. - # * `pending` A {Boolean} indicating whether or not the item should be opened - # in a pending state. Existing pending items in a pane are replaced with - # new pending items when they are opened. - # * `searchAllPanes` A {Boolean}. If `true`, the workspace will attempt to - # activate an existing item for the given URI on any pane. - # If `false`, only the active pane will be searched for - # an existing item for the same URI. Defaults to `false`. - # - # Returns a {Promise} that resolves to the {TextEditor} for the file URI. - open: (uri, options={}) -> - searchAllPanes = options.searchAllPanes - split = options.split - uri = @project.resolvePath(uri) - - if not atom.config.get('core.allowPendingPaneItems') - options.pending = false - - # Avoid adding URLs as recent documents to work-around this Spotlight crash: - # https://github.com/atom/atom/issues/10071 - if uri? and (not url.parse(uri).protocol? or process.platform is 'win32') - @applicationDelegate.addRecentDocument(uri) - - pane = @paneContainer.paneForURI(uri) if searchAllPanes - pane ?= switch split - when 'left' - @getActivePane().findLeftmostSibling() - when 'right' - @getActivePane().findOrCreateRightmostSibling() - when 'up' - @getActivePane().findTopmostSibling() - when 'down' - @getActivePane().findOrCreateBottommostSibling() - else - @getActivePane() - - @openURIInPane(uri, pane, options) - - # Open Atom's license in the active pane. - openLicense: -> - @open(path.join(process.resourcesPath, 'LICENSE.md')) - - # Synchronously open the given URI in the active pane. **Only use this method - # in specs. Calling this in production code will block the UI thread and - # everyone will be mad at you.** - # - # * `uri` A {String} containing a URI. - # * `options` An optional options {Object} - # * `initialLine` A {Number} indicating which row to move the cursor to - # initially. Defaults to `0`. - # * `initialColumn` A {Number} indicating which column to move the cursor to - # initially. Defaults to `0`. - # * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on - # the containing pane. Defaults to `true`. - # * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} - # on containing pane. Defaults to `true`. - openSync: (uri='', options={}) -> - {initialLine, initialColumn} = options - activatePane = options.activatePane ? true - activateItem = options.activateItem ? true - - uri = @project.resolvePath(uri) - item = @getActivePane().itemForURI(uri) - if uri - item ?= opener(uri, options) for opener in @getOpeners() when not item - item ?= @project.openSync(uri, {initialLine, initialColumn}) - - @getActivePane().activateItem(item) if activateItem - @itemOpened(item) - @getActivePane().activate() if activatePane - item - - openURIInPane: (uri, pane, options={}) -> - activatePane = options.activatePane ? true - activateItem = options.activateItem ? true - - if uri? - if item = pane.itemForURI(uri) - pane.clearPendingItem() if not options.pending and pane.getPendingItem() is item - item ?= opener(uri, options) for opener in @getOpeners() when not item - - try - item ?= @openTextFile(uri, options) - catch error - switch error.code - when 'CANCELLED' - return Promise.resolve() - when 'EACCES' - @notificationManager.addWarning("Permission denied '#{error.path}'") - return Promise.resolve() - when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR', 'EAGAIN' - @notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message) - return Promise.resolve() - else - throw error - - Promise.resolve(item) - .then (item) => - return item if pane.isDestroyed() - - @itemOpened(item) - pane.activateItem(item, {pending: options.pending}) if activateItem - pane.activate() if activatePane - - initialLine = initialColumn = 0 - unless Number.isNaN(options.initialLine) - initialLine = options.initialLine - unless Number.isNaN(options.initialColumn) - initialColumn = options.initialColumn - if initialLine >= 0 or initialColumn >= 0 - item.setCursorBufferPosition?([initialLine, initialColumn]) - - index = pane.getActiveItemIndex() - @emitter.emit 'did-open', {uri, pane, item, index} - item - - openTextFile: (uri, options) -> - filePath = @project.resolvePath(uri) - - if filePath? - try - fs.closeSync(fs.openSync(filePath, 'r')) - catch error - # allow ENOENT errors to create an editor for paths that dont exist - throw error unless error.code is 'ENOENT' - - fileSize = fs.getSizeSync(filePath) - - largeFileMode = fileSize >= 2 * 1048576 # 2MB - if fileSize >= @config.get('core.warnOnLargeFileLimit') * 1048576 # 20MB by default - choice = @applicationDelegate.confirm - message: 'Atom will be unresponsive during the loading of very large files.' - detailedMessage: "Do you still want to load this file?" - buttons: ["Proceed", "Cancel"] - if choice is 1 - error = new Error - error.code = 'CANCELLED' - throw error - - @project.bufferForPath(filePath, options).then (buffer) => - @textEditorRegistry.build(Object.assign({buffer, largeFileMode, autoHeight: false}, options)) - - handleGrammarUsed: (grammar) -> - return unless grammar? - - @packageManager.triggerActivationHook("#{grammar.packageName}:grammar-used") - - # Public: Returns a {Boolean} that is `true` if `object` is a `TextEditor`. - # - # * `object` An {Object} you want to perform the check against. - isTextEditor: (object) -> - object instanceof TextEditor - - # Extended: Create a new text editor. - # - # Returns a {TextEditor}. - buildTextEditor: (params) -> - editor = @textEditorRegistry.build(params) - subscriptions = new CompositeDisposable( - @textEditorRegistry.maintainGrammar(editor) - @textEditorRegistry.maintainConfig(editor), - ) - editor.onDidDestroy -> subscriptions.dispose() - editor - - # Public: Asynchronously reopens the last-closed item's URI if it hasn't already been - # reopened. - # - # Returns a {Promise} that is resolved when the item is opened - reopenItem: -> - if uri = @destroyedItemURIs.pop() - @open(uri) - else - Promise.resolve() - - # Public: Register an opener for a uri. - # - # When a URI is opened via {Workspace::open}, Atom loops through its registered - # opener functions until one returns a value for the given uri. - # Openers are expected to return an object that inherits from HTMLElement or - # a model which has an associated view in the {ViewRegistry}. - # A {TextEditor} will be used if no opener returns a value. - # - # ## Examples - # - # ```coffee - # atom.workspace.addOpener (uri) -> - # if path.extname(uri) is '.toml' - # return new TomlEditor(uri) - # ``` - # - # * `opener` A {Function} to be called when a path is being opened. - # - # Returns a {Disposable} on which `.dispose()` can be called to remove the - # opener. - # - # Note that the opener will be called if and only if the URI is not already open - # in the current pane. The searchAllPanes flag expands the search from the - # current pane to all panes. If you wish to open a view of a different type for - # a file that is already open, consider changing the protocol of the URI. For - # example, perhaps you wish to preview a rendered version of the file `/foo/bar/baz.quux` - # that is already open in a text editor view. You could signal this by calling - # {Workspace::open} on the URI `quux-preview://foo/bar/baz.quux`. Then your opener - # can check the protocol for quux-preview and only handle those URIs that match. - addOpener: (opener) -> - @openers.push(opener) - new Disposable => _.remove(@openers, opener) - - getOpeners: -> - @openers - - ### - Section: Pane Items - ### - - # Essential: Get all pane items in the workspace. - # - # Returns an {Array} of items. - getPaneItems: -> - @paneContainer.getPaneItems() - - # Essential: Get the active {Pane}'s active item. - # - # Returns an pane item {Object}. - getActivePaneItem: -> - @paneContainer.getActivePaneItem() - - # Essential: Get all text editors in the workspace. - # - # Returns an {Array} of {TextEditor}s. - getTextEditors: -> - @getPaneItems().filter (item) -> item instanceof TextEditor - - # Essential: Get the active item if it is an {TextEditor}. - # - # Returns an {TextEditor} or `undefined` if the current active item is not an - # {TextEditor}. - getActiveTextEditor: -> - activeItem = @getActivePaneItem() - activeItem if activeItem instanceof TextEditor - - # Save all pane items. - saveAll: -> - @paneContainer.saveAll() - - confirmClose: (options) -> - @paneContainer.confirmClose(options) - - # Save the active pane item. - # - # If the active pane item currently has a URI according to the item's - # `.getURI` method, calls `.save` on the item. Otherwise - # {::saveActivePaneItemAs} # will be called instead. This method does nothing - # if the active item does not implement a `.save` method. - saveActivePaneItem: -> - @getActivePane().saveActiveItem() - - # Prompt the user for a path and save the active pane item to it. - # - # Opens a native dialog where the user selects a path on disk, then calls - # `.saveAs` on the item with the selected path. This method does nothing if - # the active item does not implement a `.saveAs` method. - saveActivePaneItemAs: -> - @getActivePane().saveActiveItemAs() - - # Destroy (close) the active pane item. - # - # Removes the active pane item and calls the `.destroy` method on it if one is - # defined. - destroyActivePaneItem: -> - @getActivePane().destroyActiveItem() - - ### - Section: Panes - ### - - # Extended: Get all panes in the workspace. - # - # Returns an {Array} of {Pane}s. - getPanes: -> - @paneContainer.getPanes() - - # Extended: Get the active {Pane}. - # - # Returns a {Pane}. - getActivePane: -> - @paneContainer.getActivePane() - - # Extended: Make the next pane active. - activateNextPane: -> - @paneContainer.activateNextPane() - - # Extended: Make the previous pane active. - activatePreviousPane: -> - @paneContainer.activatePreviousPane() - - # Extended: Get the first {Pane} with an item for the given URI. - # - # * `uri` {String} uri - # - # Returns a {Pane} or `undefined` if no pane exists for the given URI. - paneForURI: (uri) -> - @paneContainer.paneForURI(uri) - - # Extended: Get the {Pane} containing the given item. - # - # * `item` Item the returned pane contains. - # - # Returns a {Pane} or `undefined` if no pane exists for the given item. - paneForItem: (item) -> - @paneContainer.paneForItem(item) - - # Destroy (close) the active pane. - destroyActivePane: -> - @getActivePane()?.destroy() - - # Close the active pane item, or the active pane if it is empty, - # or the current window if there is only the empty root pane. - closeActivePaneItemOrEmptyPaneOrWindow: -> - if @getActivePaneItem()? - @destroyActivePaneItem() - else if @getPanes().length > 1 - @destroyActivePane() - else if @config.get('core.closeEmptyWindows') - atom.close() - - # Increase the editor font size by 1px. - increaseFontSize: -> - @config.set("editor.fontSize", @config.get("editor.fontSize") + 1) - - # Decrease the editor font size by 1px. - decreaseFontSize: -> - fontSize = @config.get("editor.fontSize") - @config.set("editor.fontSize", fontSize - 1) if fontSize > 1 - - # Restore to the window's original editor font size. - resetFontSize: -> - if @originalFontSize - @config.set("editor.fontSize", @originalFontSize) - - subscribeToFontSize: -> - @config.onDidChange 'editor.fontSize', ({oldValue}) => - @originalFontSize ?= oldValue - - # Removes the item's uri from the list of potential items to reopen. - itemOpened: (item) -> - if typeof item.getURI is 'function' - uri = item.getURI() - else if typeof item.getUri is 'function' - uri = item.getUri() - - if uri? - _.remove(@destroyedItemURIs, uri) - - # Adds the destroyed item's uri to the list of items to reopen. - didDestroyPaneItem: ({item}) => - if typeof item.getURI is 'function' - uri = item.getURI() - else if typeof item.getUri is 'function' - uri = item.getUri() - - if uri? - @destroyedItemURIs.push(uri) - - # Called by Model superclass when destroyed - destroyed: -> - @paneContainer.destroy() - @activeItemSubscriptions?.dispose() - - - ### - Section: Panels - - Panels are used to display UI related to an editor window. They are placed at one of the four - edges of the window: left, right, top or bottom. If there are multiple panels on the same window - edge they are stacked in order of priority: higher priority is closer to the center, lower - priority towards the edge. - - *Note:* If your panel changes its size throughout its lifetime, consider giving it a higher - priority, allowing fixed size panels to be closer to the edge. This allows control targets to - remain more static for easier targeting by users that employ mice or trackpads. (See - [atom/atom#4834](https://github.com/atom/atom/issues/4834) for discussion.) - ### - - # Essential: Get an {Array} of all the panel items at the bottom of the editor window. - getBottomPanels: -> - @getPanels('bottom') - - # Essential: Adds a panel item to the bottom of the editor window. - # - # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addBottomPanel: (options) -> - @addPanel('bottom', options) - - # Essential: Get an {Array} of all the panel items to the left of the editor window. - getLeftPanels: -> - @getPanels('left') - - # Essential: Adds a panel item to the left of the editor window. - # - # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addLeftPanel: (options) -> - @addPanel('left', options) - - # Essential: Get an {Array} of all the panel items to the right of the editor window. - getRightPanels: -> - @getPanels('right') - - # Essential: Adds a panel item to the right of the editor window. - # - # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addRightPanel: (options) -> - @addPanel('right', options) - - # Essential: Get an {Array} of all the panel items at the top of the editor window. - getTopPanels: -> - @getPanels('top') - - # Essential: Adds a panel item to the top of the editor window above the tabs. - # - # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addTopPanel: (options) -> - @addPanel('top', options) - - # Essential: Get an {Array} of all the panel items in the header. - getHeaderPanels: -> - @getPanels('header') - - # Essential: Adds a panel item to the header. - # - # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addHeaderPanel: (options) -> - @addPanel('header', options) - - # Essential: Get an {Array} of all the panel items in the footer. - getFooterPanels: -> - @getPanels('footer') - - # Essential: Adds a panel item to the footer. - # - # * `options` {Object} - # * `item` Your panel content. It can be DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # latter. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addFooterPanel: (options) -> - @addPanel('footer', options) - - # Essential: Get an {Array} of all the modal panel items - getModalPanels: -> - @getPanels('modal') - - # Essential: Adds a panel item as a modal dialog. - # - # * `options` {Object} - # * `item` Your panel content. It can be a DOM element, a jQuery element, or - # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the - # model option. See {ViewRegistry::addViewProvider} for more information. - # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden - # (default: true) - # * `priority` (optional) {Number} Determines stacking order. Lower priority items are - # forced closer to the edges of the window. (default: 100) - # - # Returns a {Panel} - addModalPanel: (options={}) -> - @addPanel('modal', options) - - # Essential: Returns the {Panel} associated with the given item. Returns - # `null` when the item has no panel. - # - # * `item` Item the panel contains - panelForItem: (item) -> - for location, container of @panelContainers - panel = container.panelForItem(item) - return panel if panel? - null - - getPanels: (location) -> - @panelContainers[location].getPanels() - - addPanel: (location, options) -> - options ?= {} - @panelContainers[location].addPanel(new Panel(options)) - - ### - Section: Searching and Replacing - ### - - # Public: Performs a search across all files in the workspace. - # - # * `regex` {RegExp} to search with. - # * `options` (optional) {Object} - # * `paths` An {Array} of glob patterns to search within. - # * `onPathsSearched` (optional) {Function} to be periodically called - # with number of paths searched. - # * `iterator` {Function} callback on each file found. - # - # Returns a {Promise} with a `cancel()` method that will cancel all - # of the underlying searches that were started as part of this scan. - scan: (regex, options={}, iterator) -> - if _.isFunction(options) - iterator = options - options = {} - - # Find a searcher for every Directory in the project. Each searcher that is matched - # will be associated with an Array of Directory objects in the Map. - directoriesForSearcher = new Map() - for directory in @project.getDirectories() - searcher = @defaultDirectorySearcher - for directorySearcher in @directorySearchers - if directorySearcher.canSearchDirectory(directory) - searcher = directorySearcher - break - directories = directoriesForSearcher.get(searcher) - unless directories - directories = [] - directoriesForSearcher.set(searcher, directories) - directories.push(directory) - - # Define the onPathsSearched callback. - if _.isFunction(options.onPathsSearched) - # Maintain a map of directories to the number of search results. When notified of a new count, - # replace the entry in the map and update the total. - onPathsSearchedOption = options.onPathsSearched - totalNumberOfPathsSearched = 0 - numberOfPathsSearchedForSearcher = new Map() - onPathsSearched = (searcher, numberOfPathsSearched) -> - oldValue = numberOfPathsSearchedForSearcher.get(searcher) - if oldValue - totalNumberOfPathsSearched -= oldValue - numberOfPathsSearchedForSearcher.set(searcher, numberOfPathsSearched) - totalNumberOfPathsSearched += numberOfPathsSearched - onPathsSearchedOption(totalNumberOfPathsSearched) - else - onPathsSearched = -> - - # Kick off all of the searches and unify them into one Promise. - allSearches = [] - directoriesForSearcher.forEach (directories, searcher) => - searchOptions = - inclusions: options.paths or [] - includeHidden: true - excludeVcsIgnores: @config.get('core.excludeVcsIgnoredPaths') - exclusions: @config.get('core.ignoredNames') - follow: @config.get('core.followSymlinks') - didMatch: (result) => - iterator(result) unless @project.isPathModified(result.filePath) - didError: (error) -> - iterator(null, error) - didSearchPaths: (count) -> onPathsSearched(searcher, count) - directorySearcher = searcher.search(directories, regex, searchOptions) - allSearches.push(directorySearcher) - searchPromise = Promise.all(allSearches) - - for buffer in @project.getBuffers() when buffer.isModified() - filePath = buffer.getPath() - continue unless @project.contains(filePath) - matches = [] - buffer.scan regex, (match) -> matches.push match - iterator {filePath, matches} if matches.length > 0 - - # Make sure the Promise that is returned to the client is cancelable. To be consistent - # with the existing behavior, instead of cancel() rejecting the promise, it should - # resolve it with the special value 'cancelled'. At least the built-in find-and-replace - # package relies on this behavior. - isCancelled = false - cancellablePromise = new Promise (resolve, reject) -> - onSuccess = -> - if isCancelled - resolve('cancelled') - else - resolve(null) - - onFailure = -> - promise.cancel() for promise in allSearches - reject() - - searchPromise.then(onSuccess, onFailure) - cancellablePromise.cancel = -> - isCancelled = true - # Note that cancelling all of the members of allSearches will cause all of the searches - # to resolve, which causes searchPromise to resolve, which is ultimately what causes - # cancellablePromise to resolve. - promise.cancel() for promise in allSearches - - # Although this method claims to return a `Promise`, the `ResultsPaneView.onSearch()` - # method in the find-and-replace package expects the object returned by this method to have a - # `done()` method. Include a done() method until find-and-replace can be updated. - cancellablePromise.done = (onSuccessOrFailure) -> - cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure) - cancellablePromise - - # Public: Performs a replace across all the specified files in the project. - # - # * `regex` A {RegExp} to search with. - # * `replacementText` {String} to replace all matches of regex with. - # * `filePaths` An {Array} of file path strings to run the replace on. - # * `iterator` A {Function} callback on each file with replacements: - # * `options` {Object} with keys `filePath` and `replacements`. - # - # Returns a {Promise}. - replace: (regex, replacementText, filePaths, iterator) -> - new Promise (resolve, reject) => - openPaths = (buffer.getPath() for buffer in @project.getBuffers()) - outOfProcessPaths = _.difference(filePaths, openPaths) - - inProcessFinished = not openPaths.length - outOfProcessFinished = not outOfProcessPaths.length - checkFinished = -> - resolve() if outOfProcessFinished and inProcessFinished - - unless outOfProcessFinished.length - flags = 'g' - flags += 'i' if regex.ignoreCase - - task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, -> - outOfProcessFinished = true - checkFinished() - - task.on 'replace:path-replaced', iterator - task.on 'replace:file-error', (error) -> iterator(null, error) - - for buffer in @project.getBuffers() - continue unless buffer.getPath() in filePaths - replacements = buffer.replace(regex, replacementText, iterator) - iterator({filePath: buffer.getPath(), replacements}) if replacements - - inProcessFinished = true - checkFinished() - - checkoutHeadRevision: (editor) -> - if editor.getPath() - checkoutHead = => - @project.repositoryForDirectory(new Directory(editor.getDirectoryPath())) - .then (repository) -> - repository?.checkoutHeadForEditor(editor) - - if @config.get('editor.confirmCheckoutHeadRevision') - @applicationDelegate.confirm - message: 'Confirm Checkout HEAD Revision' - detailedMessage: "Are you sure you want to discard all changes to \"#{editor.getFileName()}\" since the last Git commit?" - buttons: - OK: checkoutHead - Cancel: null - else - checkoutHead() - else - Promise.resolve(false) diff --git a/src/workspace.js b/src/workspace.js new file mode 100644 index 000000000..612224da3 --- /dev/null +++ b/src/workspace.js @@ -0,0 +1,1314 @@ +let Workspace; +const _ = require('underscore-plus'); +const url = require('url'); +const path = require('path'); +const {Emitter, Disposable, CompositeDisposable} = require('event-kit'); +const fs = require('fs-plus'); +const {Directory} = require('pathwatcher'); +const DefaultDirectorySearcher = require('./default-directory-searcher'); +const Model = require('./model'); +const TextEditor = require('./text-editor'); +const PaneContainer = require('./pane-container'); +const Panel = require('./panel'); +const PanelContainer = require('./panel-container'); +const Task = require('./task'); + +// Essential: Represents the state of the user interface for the entire window. +// An instance of this class is available via the `atom.workspace` global. +// +// Interact with this object to open files, be notified of current and future +// editors, and manipulate panes. To add panels, use {Workspace::addTopPanel} +// and friends. +// +// * `editor` {TextEditor} the new editor +// +module.exports = +Workspace = class Workspace extends Model { + constructor(params) { + this.updateWindowTitle = this.updateWindowTitle.bind(this); + this.updateDocumentEdited = this.updateDocumentEdited.bind(this); + this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this); + super(...arguments); + + ({ + packageManager: this.packageManager, config: this.config, project: this.project, grammarRegistry: this.grammarRegistry, notificationManager: this.notificationManager, + viewRegistry: this.viewRegistry, grammarRegistry: this.grammarRegistry, applicationDelegate: this.applicationDelegate, assert: this.assert, + deserializerManager: this.deserializerManager, textEditorRegistry: this.textEditorRegistry + } = params); + + this.emitter = new Emitter; + this.openers = []; + this.destroyedItemURIs = []; + + this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}); + this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem); + + this.defaultDirectorySearcher = new DefaultDirectorySearcher(); + this.consumeServices(this.packageManager); + + // One cannot simply .bind here since it could be used as a component with + // Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always + // the newly created object. + const realThis = this; + this.buildTextEditor = function() { return Workspace.prototype.buildTextEditor.apply(realThis, arguments); }; + + this.panelContainers = { + top: new PanelContainer({location: 'top'}), + left: new PanelContainer({location: 'left'}), + right: new PanelContainer({location: 'right'}), + bottom: new PanelContainer({location: 'bottom'}), + header: new PanelContainer({location: 'header'}), + footer: new PanelContainer({location: 'footer'}), + modal: new PanelContainer({location: 'modal'}) + }; + + this.subscribeToEvents(); + } + + reset(packageManager) { + this.packageManager = packageManager; + this.emitter.dispose(); + this.emitter = new Emitter; + + this.paneContainer.destroy(); + for (let panelContainer of this.panelContainers) { panelContainer.destroy(); } + + this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}); + this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem); + + this.panelContainers = { + top: new PanelContainer({location: 'top'}), + left: new PanelContainer({location: 'left'}), + right: new PanelContainer({location: 'right'}), + bottom: new PanelContainer({location: 'bottom'}), + header: new PanelContainer({location: 'header'}), + footer: new PanelContainer({location: 'footer'}), + modal: new PanelContainer({location: 'modal'}) + }; + + this.originalFontSize = null; + this.openers = []; + this.destroyedItemURIs = []; + return this.consumeServices(this.packageManager); + } + + subscribeToEvents() { + this.subscribeToActiveItem(); + this.subscribeToFontSize(); + return this.subscribeToAddedItems(); + } + + consumeServices({serviceHub}) { + this.directorySearchers = []; + return serviceHub.consume( + 'atom.directory-searcher', + '^0.1.0', + provider => this.directorySearchers.unshift(provider)); + } + + // Called by the Serializable mixin during serialization. + serialize() { + return { + deserializer: 'Workspace', + paneContainer: this.paneContainer.serialize(), + packagesWithActiveGrammars: this.getPackageNamesWithActiveGrammars(), + destroyedItemURIs: this.destroyedItemURIs.slice() + }; + } + + deserialize(state, deserializerManager) { + for (let packageName of state.packagesWithActiveGrammars != null ? state.packagesWithActiveGrammars : []) { + __guard__(this.packageManager.getLoadedPackage(packageName), x => x.loadGrammarsSync()); + } + if (state.destroyedItemURIs != null) { + this.destroyedItemURIs = state.destroyedItemURIs; + } + return this.paneContainer.deserialize(state.paneContainer, deserializerManager); + } + + getPackageNamesWithActiveGrammars() { + const packageNames = []; + var addGrammar = ({includedGrammarScopes, packageName}={}) => { + if (!packageName) { return; } + // Prevent cycles + if (packageNames.indexOf(packageName) !== -1) { return; } + + packageNames.push(packageName); + for (let scopeName of includedGrammarScopes != null ? includedGrammarScopes : []) { + addGrammar(this.grammarRegistry.grammarForScopeName(scopeName)); + } + }; + + const editors = this.getTextEditors(); + for (let editor of editors) { addGrammar(editor.getGrammar()); } + + if (editors.length > 0) { + for (let grammar of this.grammarRegistry.getGrammars()) { + if (grammar.injectionSelector) { + addGrammar(grammar); + } + } + } + + return _.uniq(packageNames); + } + + subscribeToActiveItem() { + this.updateWindowTitle(); + this.updateDocumentEdited(); + this.project.onDidChangePaths(this.updateWindowTitle); + + return this.observeActivePaneItem(item => { + let modifiedSubscription, titleSubscription; + this.updateWindowTitle(); + this.updateDocumentEdited(); + + if (this.activeItemSubscriptions != null) { + this.activeItemSubscriptions.dispose(); + } + this.activeItemSubscriptions = new CompositeDisposable; + + if (typeof (item != null ? item.onDidChangeTitle : undefined) === 'function') { + titleSubscription = item.onDidChangeTitle(this.updateWindowTitle); + } else if (typeof (item != null ? item.on : undefined) === 'function') { + titleSubscription = item.on('title-changed', this.updateWindowTitle); + if (typeof (titleSubscription != null ? titleSubscription.dispose : undefined) !== 'function') { + titleSubscription = new Disposable((function() { return item.off('title-changed', this.updateWindowTitle); }.bind(this))); + } + } + + if (typeof (item != null ? item.onDidChangeModified : undefined) === 'function') { + modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited); + } else if (typeof ((item != null ? item.on : undefined) != null) === 'function') { + modifiedSubscription = item.on('modified-status-changed', this.updateDocumentEdited); + if (typeof (modifiedSubscription != null ? modifiedSubscription.dispose : undefined) !== 'function') { + modifiedSubscription = new Disposable((function() { return item.off('modified-status-changed', this.updateDocumentEdited); }.bind(this))); + } + } + + if (titleSubscription != null) { this.activeItemSubscriptions.add(titleSubscription); } + if (modifiedSubscription != null) { return this.activeItemSubscriptions.add(modifiedSubscription); } + } + ); + } + + subscribeToAddedItems() { + return this.onDidAddPaneItem(({item, pane, index}) => { + if (item instanceof TextEditor) { + const subscriptions = new CompositeDisposable( + this.textEditorRegistry.add(item), + this.textEditorRegistry.maintainGrammar(item), + this.textEditorRegistry.maintainConfig(item), + item.observeGrammar(this.handleGrammarUsed.bind(this)) + ); + item.onDidDestroy(() => subscriptions.dispose()); + return this.emitter.emit('did-add-text-editor', {textEditor: item, pane, index}); + } + }); + } + + // Updates the application's title and proxy icon based on whichever file is + // open. + updateWindowTitle() { + let item, itemPath, itemTitle, left, projectPath, representedPath; + const appName = 'Atom'; + const projectPaths = (left = this.project.getPaths()) != null ? left : []; + if (item = this.getActivePaneItem()) { + let left1; + itemPath = typeof item.getPath === 'function' ? item.getPath() : undefined; + itemTitle = (left1 = (typeof item.getLongTitle === 'function' ? item.getLongTitle() : undefined)) != null ? left1 : (typeof item.getTitle === 'function' ? item.getTitle() : undefined); + projectPath = _.find(projectPaths, projectPath => (itemPath === projectPath) || (itemPath != null ? itemPath.startsWith(projectPath + path.sep) : undefined)); + } + if (itemTitle == null) { itemTitle = "untitled"; } + if (projectPath == null) { projectPath = itemPath ? path.dirname(itemPath) : projectPaths[0]; } + if (projectPath != null) { + projectPath = fs.tildify(projectPath); + } + + const titleParts = []; + if ((item != null) && (projectPath != null)) { + titleParts.push(itemTitle, projectPath); + representedPath = itemPath != null ? itemPath : projectPath; + } else if (projectPath != null) { + titleParts.push(projectPath); + representedPath = projectPath; + } else { + titleParts.push(itemTitle); + representedPath = ""; + } + + if (process.platform !== 'darwin') { + titleParts.push(appName); + } + + document.title = titleParts.join(" \u2014 "); + return this.applicationDelegate.setRepresentedFilename(representedPath); + } + + // On macOS, fades the application window's proxy icon when the current file + // has been modified. + updateDocumentEdited() { + let left; + const modified = (left = __guardMethod__(this.getActivePaneItem(), 'isModified', o => o.isModified())) != null ? left : false; + return this.applicationDelegate.setWindowDocumentEdited(modified); + } + + /* + Section: Event Subscription + */ + + // Essential: Invoke the given callback with all current and future text + // editors in the workspace. + // + // * `callback` {Function} to be called with current and future text editors. + // * `editor` An {TextEditor} that is present in {::getTextEditors} at the time + // of subscription or that is added at some later time. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observeTextEditors(callback) { + for (let textEditor of this.getTextEditors()) { callback(textEditor); } + return this.onDidAddTextEditor(({textEditor}) => callback(textEditor)); + } + + // Essential: Invoke the given callback with all current and future panes items + // in the workspace. + // + // * `callback` {Function} to be called with current and future pane items. + // * `item` An item that is present in {::getPaneItems} at the time of + // subscription or that is added at some later time. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observePaneItems(callback) { return this.paneContainer.observePaneItems(callback); } + + // Essential: Invoke the given callback when the active pane item changes. + // + // Because observers are invoked synchronously, it's important not to perform + // any expensive operations via this method. Consider + // {::onDidStopChangingActivePaneItem} to delay operations until after changes + // stop occurring. + // + // * `callback` {Function} to be called when the active pane item changes. + // * `item` The active pane item. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangeActivePaneItem(callback) { + return this.paneContainer.onDidChangeActivePaneItem(callback); + } + + // Essential: Invoke the given callback when the active pane item stops + // changing. + // + // Observers are called asynchronously 100ms after the last active pane item + // change. Handling changes here rather than in the synchronous + // {::onDidChangeActivePaneItem} prevents unneeded work if the user is quickly + // changing or closing tabs and ensures critical UI feedback, like changing the + // highlighted tab, gets priority over work that can be done asynchronously. + // + // * `callback` {Function} to be called when the active pane item stopts + // changing. + // * `item` The active pane item. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidStopChangingActivePaneItem(callback) { + return this.paneContainer.onDidStopChangingActivePaneItem(callback); + } + + // Essential: Invoke the given callback with the current active pane item and + // with all future active pane items in the workspace. + // + // * `callback` {Function} to be called when the active pane item changes. + // * `item` The current active pane item. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observeActivePaneItem(callback) { return this.paneContainer.observeActivePaneItem(callback); } + + // Essential: Invoke the given callback whenever an item is opened. Unlike + // {::onDidAddPaneItem}, observers will be notified for items that are already + // present in the workspace when they are reopened. + // + // * `callback` {Function} to be called whenever an item is opened. + // * `event` {Object} with the following keys: + // * `uri` {String} representing the opened URI. Could be `undefined`. + // * `item` The opened item. + // * `pane` The pane in which the item was opened. + // * `index` The index of the opened item on its pane. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidOpen(callback) { + return this.emitter.on('did-open', callback); + } + + // Extended: Invoke the given callback when a pane is added to the workspace. + // + // * `callback` {Function} to be called panes are added. + // * `event` {Object} with the following keys: + // * `pane` The added pane. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidAddPane(callback) { return this.paneContainer.onDidAddPane(callback); } + + // Extended: Invoke the given callback before a pane is destroyed in the + // workspace. + // + // * `callback` {Function} to be called before panes are destroyed. + // * `event` {Object} with the following keys: + // * `pane` The pane to be destroyed. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onWillDestroyPane(callback) { return this.paneContainer.onWillDestroyPane(callback); } + + // Extended: Invoke the given callback when a pane is destroyed in the + // workspace. + // + // * `callback` {Function} to be called panes are destroyed. + // * `event` {Object} with the following keys: + // * `pane` The destroyed pane. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidDestroyPane(callback) { return this.paneContainer.onDidDestroyPane(callback); } + + // Extended: Invoke the given callback with all current and future panes in the + // workspace. + // + // * `callback` {Function} to be called with current and future panes. + // * `pane` A {Pane} that is present in {::getPanes} at the time of + // subscription or that is added at some later time. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observePanes(callback) { return this.paneContainer.observePanes(callback); } + + // Extended: Invoke the given callback when the active pane changes. + // + // * `callback` {Function} to be called when the active pane changes. + // * `pane` A {Pane} that is the current return value of {::getActivePane}. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangeActivePane(callback) { return this.paneContainer.onDidChangeActivePane(callback); } + + // Extended: Invoke the given callback with the current active pane and when + // the active pane changes. + // + // * `callback` {Function} to be called with the current and future active# + // panes. + // * `pane` A {Pane} that is the current return value of {::getActivePane}. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observeActivePane(callback) { return this.paneContainer.observeActivePane(callback); } + + // Extended: Invoke the given callback when a pane item is added to the + // workspace. + // + // * `callback` {Function} to be called when pane items are added. + // * `event` {Object} with the following keys: + // * `item` The added pane item. + // * `pane` {Pane} containing the added item. + // * `index` {Number} indicating the index of the added item in its pane. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidAddPaneItem(callback) { return this.paneContainer.onDidAddPaneItem(callback); } + + // Extended: Invoke the given callback when a pane item is about to be + // destroyed, before the user is prompted to save it. + // + // * `callback` {Function} to be called before pane items are destroyed. + // * `event` {Object} with the following keys: + // * `item` The item to be destroyed. + // * `pane` {Pane} containing the item to be destroyed. + // * `index` {Number} indicating the index of the item to be destroyed in + // its pane. + // + // Returns a {Disposable} on which `.dispose` can be called to unsubscribe. + onWillDestroyPaneItem(callback) { return this.paneContainer.onWillDestroyPaneItem(callback); } + + // Extended: Invoke the given callback when a pane item is destroyed. + // + // * `callback` {Function} to be called when pane items are destroyed. + // * `event` {Object} with the following keys: + // * `item` The destroyed item. + // * `pane` {Pane} containing the destroyed item. + // * `index` {Number} indicating the index of the destroyed item in its + // pane. + // + // Returns a {Disposable} on which `.dispose` can be called to unsubscribe. + onDidDestroyPaneItem(callback) { return this.paneContainer.onDidDestroyPaneItem(callback); } + + // Extended: Invoke the given callback when a text editor is added to the + // workspace. + // + // * `callback` {Function} to be called panes are added. + // * `event` {Object} with the following keys: + // * `textEditor` {TextEditor} that was added. + // * `pane` {Pane} containing the added text editor. + // * `index` {Number} indicating the index of the added text editor in its + // pane. + // + // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidAddTextEditor(callback) { + return this.emitter.on('did-add-text-editor', callback); + } + + /* + Section: Opening + */ + + // Essential: Opens the given URI in Atom asynchronously. + // If the URI is already open, the existing item for that URI will be + // activated. If no URI is given, or no registered opener can open + // the URI, a new empty {TextEditor} will be created. + // + // * `uri` (optional) A {String} containing a URI. + // * `options` (optional) {Object} + // * `initialLine` A {Number} indicating which row to move the cursor to + // initially. Defaults to `0`. + // * `initialColumn` A {Number} indicating which column to move the cursor to + // initially. Defaults to `0`. + // * `split` Either 'left', 'right', 'up' or 'down'. + // If 'left', the item will be opened in leftmost pane of the current active pane's row. + // If 'right', the item will be opened in the rightmost pane of the current active pane's row. If only one pane exists in the row, a new pane will be created. + // If 'up', the item will be opened in topmost pane of the current active pane's column. + // If 'down', the item will be opened in the bottommost pane of the current active pane's column. If only one pane exists in the column, a new pane will be created. + // * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on + // containing pane. Defaults to `true`. + // * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} + // on containing pane. Defaults to `true`. + // * `pending` A {Boolean} indicating whether or not the item should be opened + // in a pending state. Existing pending items in a pane are replaced with + // new pending items when they are opened. + // * `searchAllPanes` A {Boolean}. If `true`, the workspace will attempt to + // activate an existing item for the given URI on any pane. + // If `false`, only the active pane will be searched for + // an existing item for the same URI. Defaults to `false`. + // + // Returns a {Promise} that resolves to the {TextEditor} for the file URI. + open(uri, options={}) { + let pane; + const { searchAllPanes } = options; + const { split } = options; + uri = this.project.resolvePath(uri); + + if (!atom.config.get('core.allowPendingPaneItems')) { + options.pending = false; + } + + // Avoid adding URLs as recent documents to work-around this Spotlight crash: + // https://github.com/atom/atom/issues/10071 + if ((uri != null) && ((url.parse(uri).protocol == null) || (process.platform === 'win32'))) { + this.applicationDelegate.addRecentDocument(uri); + } + + if (searchAllPanes) { pane = this.paneContainer.paneForURI(uri); } + if (pane == null) { pane = (() => { switch (split) { + case 'left': + return this.getActivePane().findLeftmostSibling(); + case 'right': + return this.getActivePane().findOrCreateRightmostSibling(); + case 'up': + return this.getActivePane().findTopmostSibling(); + case 'down': + return this.getActivePane().findOrCreateBottommostSibling(); + default: + return this.getActivePane(); + } })(); } + + return this.openURIInPane(uri, pane, options); + } + + // Open Atom's license in the active pane. + openLicense() { + return this.open(path.join(process.resourcesPath, 'LICENSE.md')); + } + + // Synchronously open the given URI in the active pane. **Only use this method + // in specs. Calling this in production code will block the UI thread and + // everyone will be mad at you.** + // + // * `uri` A {String} containing a URI. + // * `options` An optional options {Object} + // * `initialLine` A {Number} indicating which row to move the cursor to + // initially. Defaults to `0`. + // * `initialColumn` A {Number} indicating which column to move the cursor to + // initially. Defaults to `0`. + // * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on + // the containing pane. Defaults to `true`. + // * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} + // on containing pane. Defaults to `true`. + openSync(uri='', options={}) { + const {initialLine, initialColumn} = options; + const activatePane = options.activatePane != null ? options.activatePane : true; + const activateItem = options.activateItem != null ? options.activateItem : true; + + uri = this.project.resolvePath(uri); + let item = this.getActivePane().itemForURI(uri); + if (uri) { + for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options); } } } + } + if (item == null) { item = this.project.openSync(uri, {initialLine, initialColumn}); } + + if (activateItem) { this.getActivePane().activateItem(item); } + this.itemOpened(item); + if (activatePane) { this.getActivePane().activate(); } + return item; + } + + openURIInPane(uri, pane, options={}) { + let item; + const activatePane = options.activatePane != null ? options.activatePane : true; + const activateItem = options.activateItem != null ? options.activateItem : true; + + if (uri != null) { + if (item = pane.itemForURI(uri)) { + if (!options.pending && (pane.getPendingItem() === item)) { pane.clearPendingItem(); } + } + for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options); } } } + } + + try { + if (item == null) { item = this.openTextFile(uri, options); } + } catch (error) { + switch (error.code) { + case 'CANCELLED': + return Promise.resolve(); + break; + case 'EACCES': + this.notificationManager.addWarning(`Permission denied '${error.path}'`); + return Promise.resolve(); + break; + case 'EPERM': case 'EBUSY': case 'ENXIO': case 'EIO': case 'ENOTCONN': case 'UNKNOWN': case 'ECONNRESET': case 'EINVAL': case 'EMFILE': case 'ENOTDIR': case 'EAGAIN': + this.notificationManager.addWarning(`Unable to open '${error.path != null ? error.path : uri}'`, {detail: error.message}); + return Promise.resolve(); + break; + default: + throw error; + } + } + + return Promise.resolve(item) + .then(item => { + let initialColumn; + if (pane.isDestroyed()) { return item; } + + this.itemOpened(item); + if (activateItem) { pane.activateItem(item, {pending: options.pending}); } + if (activatePane) { pane.activate(); } + + let initialLine = initialColumn = 0; + if (!Number.isNaN(options.initialLine)) { + ({ initialLine } = options); + } + if (!Number.isNaN(options.initialColumn)) { + ({ initialColumn } = options); + } + if ((initialLine >= 0) || (initialColumn >= 0)) { + if (typeof item.setCursorBufferPosition === 'function') { + item.setCursorBufferPosition([initialLine, initialColumn]); + } + } + + const index = pane.getActiveItemIndex(); + this.emitter.emit('did-open', {uri, pane, item, index}); + return item; + } + ); + } + + openTextFile(uri, options) { + const filePath = this.project.resolvePath(uri); + + if (filePath != null) { + try { + fs.closeSync(fs.openSync(filePath, 'r')); + } catch (error) { + // allow ENOENT errors to create an editor for paths that dont exist + if (error.code !== 'ENOENT') { throw error; } + } + } + + const fileSize = fs.getSizeSync(filePath); + + const largeFileMode = fileSize >= (2 * 1048576); // 2MB + if (fileSize >= (this.config.get('core.warnOnLargeFileLimit') * 1048576)) { // 20MB by default + const choice = this.applicationDelegate.confirm({ + message: 'Atom will be unresponsive during the loading of very large files.', + detailedMessage: "Do you still want to load this file?", + buttons: ["Proceed", "Cancel"]}); + if (choice === 1) { + const error = new Error; + error.code = 'CANCELLED'; + throw error; + } + } + + return this.project.bufferForPath(filePath, options).then(buffer => { + return this.textEditorRegistry.build(Object.assign({buffer, largeFileMode, autoHeight: false}, options)); + } + ); + } + + handleGrammarUsed(grammar) { + if (grammar == null) { return; } + + return this.packageManager.triggerActivationHook(`${grammar.packageName}:grammar-used`); + } + + // Public: Returns a {Boolean} that is `true` if `object` is a `TextEditor`. + // + // * `object` An {Object} you want to perform the check against. + isTextEditor(object) { + return object instanceof TextEditor; + } + + // Extended: Create a new text editor. + // + // Returns a {TextEditor}. + buildTextEditor(params) { + const editor = this.textEditorRegistry.build(params); + const subscriptions = new CompositeDisposable( + this.textEditorRegistry.maintainGrammar(editor), + this.textEditorRegistry.maintainConfig(editor) + ); + editor.onDidDestroy(() => subscriptions.dispose()); + return editor; + } + + // Public: Asynchronously reopens the last-closed item's URI if it hasn't already been + // reopened. + // + // Returns a {Promise} that is resolved when the item is opened + reopenItem() { + let uri; + if (uri = this.destroyedItemURIs.pop()) { + return this.open(uri); + } else { + return Promise.resolve(); + } + } + + // Public: Register an opener for a uri. + // + // When a URI is opened via {Workspace::open}, Atom loops through its registered + // opener functions until one returns a value for the given uri. + // Openers are expected to return an object that inherits from HTMLElement or + // a model which has an associated view in the {ViewRegistry}. + // A {TextEditor} will be used if no opener returns a value. + // + // ## Examples + // + // ```coffee + // atom.workspace.addOpener (uri) -> + // if path.extname(uri) is '.toml' + // return new TomlEditor(uri) + // ``` + // + // * `opener` A {Function} to be called when a path is being opened. + // + // Returns a {Disposable} on which `.dispose()` can be called to remove the + // opener. + // + // Note that the opener will be called if and only if the URI is not already open + // in the current pane. The searchAllPanes flag expands the search from the + // current pane to all panes. If you wish to open a view of a different type for + // a file that is already open, consider changing the protocol of the URI. For + // example, perhaps you wish to preview a rendered version of the file `/foo/bar/baz.quux` + // that is already open in a text editor view. You could signal this by calling + // {Workspace::open} on the URI `quux-preview://foo/bar/baz.quux`. Then your opener + // can check the protocol for quux-preview and only handle those URIs that match. + addOpener(opener) { + this.openers.push(opener); + return new Disposable((function() { return _.remove(this.openers, opener); }.bind(this))); + } + + getOpeners() { + return this.openers; + } + + /* + Section: Pane Items + */ + + // Essential: Get all pane items in the workspace. + // + // Returns an {Array} of items. + getPaneItems() { + return this.paneContainer.getPaneItems(); + } + + // Essential: Get the active {Pane}'s active item. + // + // Returns an pane item {Object}. + getActivePaneItem() { + return this.paneContainer.getActivePaneItem(); + } + + // Essential: Get all text editors in the workspace. + // + // Returns an {Array} of {TextEditor}s. + getTextEditors() { + return this.getPaneItems().filter(item => item instanceof TextEditor); + } + + // Essential: Get the active item if it is an {TextEditor}. + // + // Returns an {TextEditor} or `undefined` if the current active item is not an + // {TextEditor}. + getActiveTextEditor() { + const activeItem = this.getActivePaneItem(); + if (activeItem instanceof TextEditor) { return activeItem; } + } + + // Save all pane items. + saveAll() { + return this.paneContainer.saveAll(); + } + + confirmClose(options) { + return this.paneContainer.confirmClose(options); + } + + // Save the active pane item. + // + // If the active pane item currently has a URI according to the item's + // `.getURI` method, calls `.save` on the item. Otherwise + // {::saveActivePaneItemAs} # will be called instead. This method does nothing + // if the active item does not implement a `.save` method. + saveActivePaneItem() { + return this.getActivePane().saveActiveItem(); + } + + // Prompt the user for a path and save the active pane item to it. + // + // Opens a native dialog where the user selects a path on disk, then calls + // `.saveAs` on the item with the selected path. This method does nothing if + // the active item does not implement a `.saveAs` method. + saveActivePaneItemAs() { + return this.getActivePane().saveActiveItemAs(); + } + + // Destroy (close) the active pane item. + // + // Removes the active pane item and calls the `.destroy` method on it if one is + // defined. + destroyActivePaneItem() { + return this.getActivePane().destroyActiveItem(); + } + + /* + Section: Panes + */ + + // Extended: Get all panes in the workspace. + // + // Returns an {Array} of {Pane}s. + getPanes() { + return this.paneContainer.getPanes(); + } + + // Extended: Get the active {Pane}. + // + // Returns a {Pane}. + getActivePane() { + return this.paneContainer.getActivePane(); + } + + // Extended: Make the next pane active. + activateNextPane() { + return this.paneContainer.activateNextPane(); + } + + // Extended: Make the previous pane active. + activatePreviousPane() { + return this.paneContainer.activatePreviousPane(); + } + + // Extended: Get the first {Pane} with an item for the given URI. + // + // * `uri` {String} uri + // + // Returns a {Pane} or `undefined` if no pane exists for the given URI. + paneForURI(uri) { + return this.paneContainer.paneForURI(uri); + } + + // Extended: Get the {Pane} containing the given item. + // + // * `item` Item the returned pane contains. + // + // Returns a {Pane} or `undefined` if no pane exists for the given item. + paneForItem(item) { + return this.paneContainer.paneForItem(item); + } + + // Destroy (close) the active pane. + destroyActivePane() { + return __guard__(this.getActivePane(), x => x.destroy()); + } + + // Close the active pane item, or the active pane if it is empty, + // or the current window if there is only the empty root pane. + closeActivePaneItemOrEmptyPaneOrWindow() { + if (this.getActivePaneItem() != null) { + return this.destroyActivePaneItem(); + } else if (this.getPanes().length > 1) { + return this.destroyActivePane(); + } else if (this.config.get('core.closeEmptyWindows')) { + return atom.close(); + } + } + + // Increase the editor font size by 1px. + increaseFontSize() { + return this.config.set("editor.fontSize", this.config.get("editor.fontSize") + 1); + } + + // Decrease the editor font size by 1px. + decreaseFontSize() { + const fontSize = this.config.get("editor.fontSize"); + if (fontSize > 1) { return this.config.set("editor.fontSize", fontSize - 1); } + } + + // Restore to the window's original editor font size. + resetFontSize() { + if (this.originalFontSize) { + return this.config.set("editor.fontSize", this.originalFontSize); + } + } + + subscribeToFontSize() { + return this.config.onDidChange('editor.fontSize', ({oldValue}) => { + return this.originalFontSize != null ? this.originalFontSize : (this.originalFontSize = oldValue); + } + ); + } + + // Removes the item's uri from the list of potential items to reopen. + itemOpened(item) { + let uri; + if (typeof item.getURI === 'function') { + uri = item.getURI(); + } else if (typeof item.getUri === 'function') { + uri = item.getUri(); + } + + if (uri != null) { + return _.remove(this.destroyedItemURIs, uri); + } + } + + // Adds the destroyed item's uri to the list of items to reopen. + didDestroyPaneItem({item}) { + let uri; + if (typeof item.getURI === 'function') { + uri = item.getURI(); + } else if (typeof item.getUri === 'function') { + uri = item.getUri(); + } + + if (uri != null) { + return this.destroyedItemURIs.push(uri); + } + } + + // Called by Model superclass when destroyed + destroyed() { + this.paneContainer.destroy(); + return (this.activeItemSubscriptions != null ? this.activeItemSubscriptions.dispose() : undefined); + } + + + /* + Section: Panels + + Panels are used to display UI related to an editor window. They are placed at one of the four + edges of the window: left, right, top or bottom. If there are multiple panels on the same window + edge they are stacked in order of priority: higher priority is closer to the center, lower + priority towards the edge. + + *Note:* If your panel changes its size throughout its lifetime, consider giving it a higher + priority, allowing fixed size panels to be closer to the edge. This allows control targets to + remain more static for easier targeting by users that employ mice or trackpads. (See + [atom/atom#4834](https://github.com/atom/atom/issues/4834) for discussion.) + */ + + // Essential: Get an {Array} of all the panel items at the bottom of the editor window. + getBottomPanels() { + return this.getPanels('bottom'); + } + + // Essential: Adds a panel item to the bottom of the editor window. + // + // * `options` {Object} + // * `item` Your panel content. It can be DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // latter. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addBottomPanel(options) { + return this.addPanel('bottom', options); + } + + // Essential: Get an {Array} of all the panel items to the left of the editor window. + getLeftPanels() { + return this.getPanels('left'); + } + + // Essential: Adds a panel item to the left of the editor window. + // + // * `options` {Object} + // * `item` Your panel content. It can be DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // latter. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addLeftPanel(options) { + return this.addPanel('left', options); + } + + // Essential: Get an {Array} of all the panel items to the right of the editor window. + getRightPanels() { + return this.getPanels('right'); + } + + // Essential: Adds a panel item to the right of the editor window. + // + // * `options` {Object} + // * `item` Your panel content. It can be DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // latter. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addRightPanel(options) { + return this.addPanel('right', options); + } + + // Essential: Get an {Array} of all the panel items at the top of the editor window. + getTopPanels() { + return this.getPanels('top'); + } + + // Essential: Adds a panel item to the top of the editor window above the tabs. + // + // * `options` {Object} + // * `item` Your panel content. It can be DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // latter. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addTopPanel(options) { + return this.addPanel('top', options); + } + + // Essential: Get an {Array} of all the panel items in the header. + getHeaderPanels() { + return this.getPanels('header'); + } + + // Essential: Adds a panel item to the header. + // + // * `options` {Object} + // * `item` Your panel content. It can be DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // latter. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addHeaderPanel(options) { + return this.addPanel('header', options); + } + + // Essential: Get an {Array} of all the panel items in the footer. + getFooterPanels() { + return this.getPanels('footer'); + } + + // Essential: Adds a panel item to the footer. + // + // * `options` {Object} + // * `item` Your panel content. It can be DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // latter. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addFooterPanel(options) { + return this.addPanel('footer', options); + } + + // Essential: Get an {Array} of all the modal panel items + getModalPanels() { + return this.getPanels('modal'); + } + + // Essential: Adds a panel item as a modal dialog. + // + // * `options` {Object} + // * `item` Your panel content. It can be a DOM element, a jQuery element, or + // a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + // model option. See {ViewRegistry::addViewProvider} for more information. + // * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + // (default: true) + // * `priority` (optional) {Number} Determines stacking order. Lower priority items are + // forced closer to the edges of the window. (default: 100) + // + // Returns a {Panel} + addModalPanel(options={}) { + return this.addPanel('modal', options); + } + + // Essential: Returns the {Panel} associated with the given item. Returns + // `null` when the item has no panel. + // + // * `item` Item the panel contains + panelForItem(item) { + for (let location in this.panelContainers) { + const container = this.panelContainers[location]; + const panel = container.panelForItem(item); + if (panel != null) { return panel; } + } + return null; + } + + getPanels(location) { + return this.panelContainers[location].getPanels(); + } + + addPanel(location, options) { + if (options == null) { options = {}; } + return this.panelContainers[location].addPanel(new Panel(options)); + } + + /* + Section: Searching and Replacing + */ + + // Public: Performs a search across all files in the workspace. + // + // * `regex` {RegExp} to search with. + // * `options` (optional) {Object} + // * `paths` An {Array} of glob patterns to search within. + // * `onPathsSearched` (optional) {Function} to be periodically called + // with number of paths searched. + // * `iterator` {Function} callback on each file found. + // + // Returns a {Promise} with a `cancel()` method that will cancel all + // of the underlying searches that were started as part of this scan. + scan(regex, options={}, iterator) { + let directorySearcher, onPathsSearched; + if (_.isFunction(options)) { + iterator = options; + options = {}; + } + + // Find a searcher for every Directory in the project. Each searcher that is matched + // will be associated with an Array of Directory objects in the Map. + const directoriesForSearcher = new Map(); + for (let directory of this.project.getDirectories()) { + let searcher = this.defaultDirectorySearcher; + for (directorySearcher of this.directorySearchers) { + if (directorySearcher.canSearchDirectory(directory)) { + searcher = directorySearcher; + break; + } + } + let directories = directoriesForSearcher.get(searcher); + if (!directories) { + directories = []; + directoriesForSearcher.set(searcher, directories); + } + directories.push(directory); + } + + // Define the onPathsSearched callback. + if (_.isFunction(options.onPathsSearched)) { + // Maintain a map of directories to the number of search results. When notified of a new count, + // replace the entry in the map and update the total. + const onPathsSearchedOption = options.onPathsSearched; + let totalNumberOfPathsSearched = 0; + const numberOfPathsSearchedForSearcher = new Map(); + onPathsSearched = function(searcher, numberOfPathsSearched) { + const oldValue = numberOfPathsSearchedForSearcher.get(searcher); + if (oldValue) { + totalNumberOfPathsSearched -= oldValue; + } + numberOfPathsSearchedForSearcher.set(searcher, numberOfPathsSearched); + totalNumberOfPathsSearched += numberOfPathsSearched; + return onPathsSearchedOption(totalNumberOfPathsSearched); + }; + } else { + onPathsSearched = function() {}; + } + + // Kick off all of the searches and unify them into one Promise. + const allSearches = []; + directoriesForSearcher.forEach((directories, searcher) => { + const searchOptions = { + inclusions: options.paths || [], + includeHidden: true, + excludeVcsIgnores: this.config.get('core.excludeVcsIgnoredPaths'), + exclusions: this.config.get('core.ignoredNames'), + follow: this.config.get('core.followSymlinks'), + didMatch: result => { + if (!this.project.isPathModified(result.filePath)) { return iterator(result); } + }, + didError(error) { + return iterator(null, error); + }, + didSearchPaths(count) { return onPathsSearched(searcher, count); } + }; + directorySearcher = searcher.search(directories, regex, searchOptions); + return allSearches.push(directorySearcher); + } + ); + const searchPromise = Promise.all(allSearches); + + for (let buffer of this.project.getBuffers()) { + if (buffer.isModified()) { + const filePath = buffer.getPath(); + if (!this.project.contains(filePath)) { continue; } + var matches = []; + buffer.scan(regex, match => matches.push(match)); + if (matches.length > 0) { iterator({filePath, matches}); } + } + } + + // Make sure the Promise that is returned to the client is cancelable. To be consistent + // with the existing behavior, instead of cancel() rejecting the promise, it should + // resolve it with the special value 'cancelled'. At least the built-in find-and-replace + // package relies on this behavior. + let isCancelled = false; + const cancellablePromise = new Promise(function(resolve, reject) { + const onSuccess = function() { + if (isCancelled) { + return resolve('cancelled'); + } else { + return resolve(null); + } + }; + + const onFailure = function() { + for (let promise of allSearches) { promise.cancel(); } + return reject(); + }; + + return searchPromise.then(onSuccess, onFailure); + }); + cancellablePromise.cancel = function() { + isCancelled = true; + // Note that cancelling all of the members of allSearches will cause all of the searches + // to resolve, which causes searchPromise to resolve, which is ultimately what causes + // cancellablePromise to resolve. + return allSearches.map((promise) => promise.cancel()); + }; + + // Although this method claims to return a `Promise`, the `ResultsPaneView.onSearch()` + // method in the find-and-replace package expects the object returned by this method to have a + // `done()` method. Include a done() method until find-and-replace can be updated. + cancellablePromise.done = onSuccessOrFailure => cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure); + return cancellablePromise; + } + + // Public: Performs a replace across all the specified files in the project. + // + // * `regex` A {RegExp} to search with. + // * `replacementText` {String} to replace all matches of regex with. + // * `filePaths` An {Array} of file path strings to run the replace on. + // * `iterator` A {Function} callback on each file with replacements: + // * `options` {Object} with keys `filePath` and `replacements`. + // + // Returns a {Promise}. + replace(regex, replacementText, filePaths, iterator) { + return new Promise((function(resolve, reject) { + let buffer; + const openPaths = ((() => { + const result = []; + for (buffer of this.project.getBuffers()) { result.push(buffer.getPath()); + } + return result; + })()); + const outOfProcessPaths = _.difference(filePaths, openPaths); + + let inProcessFinished = !openPaths.length; + let outOfProcessFinished = !outOfProcessPaths.length; + const checkFinished = function() { + if (outOfProcessFinished && inProcessFinished) { return resolve(); } + }; + + if (!outOfProcessFinished.length) { + let flags = 'g'; + if (regex.ignoreCase) { flags += 'i'; } + + const task = Task.once(require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, function() { + outOfProcessFinished = true; + return checkFinished(); + }); + + task.on('replace:path-replaced', iterator); + task.on('replace:file-error', function(error) { return iterator(null, error); }); + } + + for (buffer of this.project.getBuffers()) { + if (!Array.from(filePaths).includes(buffer.getPath())) { continue; } + const replacements = buffer.replace(regex, replacementText, iterator); + if (replacements) { iterator({filePath: buffer.getPath(), replacements}); } + } + + inProcessFinished = true; + return checkFinished(); + }.bind(this))); + } + + checkoutHeadRevision(editor) { + if (editor.getPath()) { + const checkoutHead = () => { + return this.project.repositoryForDirectory(new Directory(editor.getDirectoryPath())) + .then(repository => repository != null ? repository.checkoutHeadForEditor(editor) : undefined); + }; + + if (this.config.get('editor.confirmCheckoutHeadRevision')) { + return this.applicationDelegate.confirm({ + message: 'Confirm Checkout HEAD Revision', + detailedMessage: `Are you sure you want to discard all changes to \"${editor.getFileName()}\" since the last Git commit?`, + buttons: { + OK: checkoutHead, + Cancel: null + } + }); + } else { + return checkoutHead(); + } + } else { + return Promise.resolve(false); + } + } +}; + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} +function __guardMethod__(obj, methodName, transform) { + if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { + return transform(obj, methodName); + } else { + return undefined; + } +} From 7a0d7f8b4c04ef1130e569916903291be9f0cc5a Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Thu, 2 Mar 2017 15:12:12 -0800 Subject: [PATCH 049/194] Convert workspace to JavaScript: lint --- spec/workspace-spec.js | 2928 ++++++++++++++++++++-------------------- src/workspace.js | 874 ++++++------ 2 files changed, 1929 insertions(+), 1873 deletions(-) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index d5ddad7d2..ba7da0fce 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1,162 +1,172 @@ -const path = require('path'); -const temp = require('temp').track(); -const TextEditor = require('../src/text-editor'); -const Workspace = require('../src/workspace'); -const Project = require('../src/project'); -const platform = require('./spec-helper-platform'); -const _ = require('underscore-plus'); -const fstream = require('fstream'); -const fs = require('fs-plus'); -const AtomEnvironment = require('../src/atom-environment'); +/* global advanceClock, HTMLElement, waits */ -describe("Workspace", function() { - let escapeStringRegex; - let [workspace, setDocumentEdited] = Array.from([]); +const path = require('path') +const temp = require('temp').track() +const TextEditor = require('../src/text-editor') +const Workspace = require('../src/workspace') +const Project = require('../src/project') +const platform = require('./spec-helper-platform') +const _ = require('underscore-plus') +const fstream = require('fstream') +const fs = require('fs-plus') +const AtomEnvironment = require('../src/atom-environment') - beforeEach(function() { - ({ workspace } = atom); - workspace.resetFontSize(); - spyOn(atom.applicationDelegate, "confirm"); - setDocumentEdited = spyOn(atom.applicationDelegate, 'setWindowDocumentEdited'); - atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('dir'))]); - return waits(1); - }); +describe('Workspace', function () { + let escapeStringRegex + let [workspace, setDocumentEdited] = Array.from([]) - afterEach(() => temp.cleanupSync()); + beforeEach(function () { + ({ workspace } = atom) + workspace.resetFontSize() + spyOn(atom.applicationDelegate, 'confirm') + setDocumentEdited = spyOn(atom.applicationDelegate, 'setWindowDocumentEdited') + atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('dir'))]) + return waits(1) + }) - describe("serialization", function() { - const simulateReload = function() { - const workspaceState = atom.workspace.serialize(); - const projectState = atom.project.serialize({isUnloading: true}); - atom.workspace.destroy(); - atom.project.destroy(); - atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom), applicationDelegate: atom.applicationDelegate}); - atom.project.deserialize(projectState); + afterEach(() => temp.cleanupSync()) + + describe('serialization', function () { + const simulateReload = function () { + const workspaceState = atom.workspace.serialize() + const projectState = atom.project.serialize({isUnloading: true}) + atom.workspace.destroy() + atom.project.destroy() + atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom), applicationDelegate: atom.applicationDelegate}) + atom.project.deserialize(projectState) atom.workspace = new Workspace({ - config: atom.config, project: atom.project, packageManager: atom.packages, - grammarRegistry: atom.grammars, deserializerManager: atom.deserializers, + config: atom.config, + project: atom.project, + packageManager: atom.packages, + grammarRegistry: atom.grammars, + deserializerManager: atom.deserializers, notificationManager: atom.notifications, applicationDelegate: atom.applicationDelegate, - viewRegistry: atom.views, assert: atom.assert.bind(atom), + viewRegistry: atom.views, + assert: atom.assert.bind(atom), textEditorRegistry: atom.textEditors - }); - return atom.workspace.deserialize(workspaceState, atom.deserializers); - }; + }) + return atom.workspace.deserialize(workspaceState, atom.deserializers) + } - describe("when the workspace contains text editors", () => - it("constructs the view with the same panes", function() { - const pane1 = atom.workspace.getActivePane(); - const pane2 = pane1.splitRight({copyActiveItem: true}); - const pane3 = pane2.splitRight({copyActiveItem: true}); - let pane4 = null; + describe('when the workspace contains text editors', () => + it('constructs the view with the same panes', function () { + const pane1 = atom.workspace.getActivePane() + const pane2 = pane1.splitRight({copyActiveItem: true}) + const pane3 = pane2.splitRight({copyActiveItem: true}) + let pane4 = null - waitsForPromise(() => atom.workspace.open(null).then(editor => editor.setText("An untitled editor."))); + waitsForPromise(() => atom.workspace.open(null).then(editor => editor.setText('An untitled editor.'))) waitsForPromise(() => atom.workspace.open('b').then(editor => pane2.activateItem(editor.copy())) - ); + ) waitsForPromise(() => atom.workspace.open('../sample.js').then(editor => pane3.activateItem(editor)) - ); + ) - runs(function() { - pane3.activeItem.setCursorScreenPosition([2, 4]); - return pane4 = pane2.splitDown(); - }); + runs(function () { + pane3.activeItem.setCursorScreenPosition([2, 4]) + return (pane4 = pane2.splitDown()) + }) waitsForPromise(() => atom.workspace.open('../sample.txt').then(editor => pane4.activateItem(editor)) - ); + ) - return runs(function() { - pane4.getActiveItem().setCursorScreenPosition([0, 2]); - pane2.activate(); + return runs(function () { + pane4.getActiveItem().setCursorScreenPosition([0, 2]) + pane2.activate() - simulateReload(); + simulateReload() - expect(atom.workspace.getTextEditors().length).toBe(5); - const [editor1, editor2, untitledEditor, editor3, editor4] = Array.from(atom.workspace.getTextEditors()); - expect(editor1.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b'))); - expect(editor2.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('../sample.txt'))); - expect(editor2.getCursorScreenPosition()).toEqual([0, 2]); - expect(editor3.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('b'))); - expect(editor4.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x3 => x3.resolve('../sample.js'))); - expect(editor4.getCursorScreenPosition()).toEqual([2, 4]); - expect(untitledEditor.getPath()).toBeUndefined(); - expect(untitledEditor.getText()).toBe("An untitled editor."); + expect(atom.workspace.getTextEditors().length).toBe(5) + const [editor1, editor2, untitledEditor, editor3, editor4] = Array.from(atom.workspace.getTextEditors()) + expect(editor1.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b'))) + expect(editor2.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('../sample.txt'))) + expect(editor2.getCursorScreenPosition()).toEqual([0, 2]) + expect(editor3.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('b'))) + expect(editor4.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x3 => x3.resolve('../sample.js'))) + expect(editor4.getCursorScreenPosition()).toEqual([2, 4]) + expect(untitledEditor.getPath()).toBeUndefined() + expect(untitledEditor.getText()).toBe('An untitled editor.') - expect(atom.workspace.getActiveTextEditor().getPath()).toBe(editor3.getPath()); - const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); - return expect(document.title).toMatch(new RegExp(`^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}`)); - }); + expect(atom.workspace.getActiveTextEditor().getPath()).toBe(editor3.getPath()) + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) + return expect(document.title).toMatch(new RegExp(`^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}`)) + }) }) - ); + ) - return describe("where there are no open panes or editors", () => - it("constructs the view with no open editors", function() { - atom.workspace.getActivePane().destroy(); - expect(atom.workspace.getTextEditors().length).toBe(0); - simulateReload(); - return expect(atom.workspace.getTextEditors().length).toBe(0); + return describe('where there are no open panes or editors', () => + it('constructs the view with no open editors', function () { + atom.workspace.getActivePane().destroy() + expect(atom.workspace.getTextEditors().length).toBe(0) + simulateReload() + return expect(atom.workspace.getTextEditors().length).toBe(0) }) - ); - }); + ) + }) - describe("::open(uri, options)", function() { - let openEvents = null; + describe('::open(uri, options)', function () { + let openEvents = null - beforeEach(function() { - openEvents = []; - workspace.onDidOpen(event => openEvents.push(event)); - return spyOn(workspace.getActivePane(), 'activate').andCallThrough(); - }); + beforeEach(function () { + openEvents = [] + workspace.onDidOpen(event => openEvents.push(event)) + return spyOn(workspace.getActivePane(), 'activate').andCallThrough() + }) - describe("when the 'searchAllPanes' option is false (default)", function() { - describe("when called without a uri", () => - it("adds and activates an empty editor on the active pane", function() { - let [editor1, editor2] = Array.from([]); + describe("when the 'searchAllPanes' option is false (default)", function () { + describe('when called without a uri', () => + it('adds and activates an empty editor on the active pane', function () { + let [editor1, editor2] = Array.from([]) - waitsForPromise(() => workspace.open().then(editor => editor1 = editor)); + waitsForPromise(() => workspace.open().then(editor => (editor1 = editor))) - runs(function() { - expect(editor1.getPath()).toBeUndefined(); - expect(workspace.getActivePane().items).toEqual([editor1]); - expect(workspace.getActivePaneItem()).toBe(editor1); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); - expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor1, index: 0}]); - return openEvents = [];}); + runs(function () { + expect(editor1.getPath()).toBeUndefined() + expect(workspace.getActivePane().items).toEqual([editor1]) + expect(workspace.getActivePaneItem()).toBe(editor1) + expect(workspace.getActivePane().activate).toHaveBeenCalled() + expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor1, index: 0}]) + return (openEvents = []) + }) - waitsForPromise(() => workspace.open().then(editor => editor2 = editor)); + waitsForPromise(() => workspace.open().then(editor => (editor2 = editor))) - return runs(function() { - expect(editor2.getPath()).toBeUndefined(); - expect(workspace.getActivePane().items).toEqual([editor1, editor2]); - expect(workspace.getActivePaneItem()).toBe(editor2); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); - return expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}]);});})); + return runs(function () { + expect(editor2.getPath()).toBeUndefined() + expect(workspace.getActivePane().items).toEqual([editor1, editor2]) + expect(workspace.getActivePaneItem()).toBe(editor2) + expect(workspace.getActivePane().activate).toHaveBeenCalled() + return expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}]) + }) + }) + ) - return describe("when called with a uri", function() { - describe("when the active pane already has an editor for the given uri", () => - it("activates the existing editor on the active pane", function() { - let editor = null; - let editor1 = null; - let editor2 = null; + return describe('when called with a uri', function () { + describe('when the active pane already has an editor for the given uri', () => + it('activates the existing editor on the active pane', function () { + let editor = null + let editor1 = null + let editor2 = null waitsForPromise(() => - workspace.open('a').then(function(o) { - editor1 = o; - return workspace.open('b').then(function(o) { - editor2 = o; - return workspace.open('a').then(o => editor = o); - }); + workspace.open('a').then(function (o) { + editor1 = o + return workspace.open('b').then(function (o) { + editor2 = o + return workspace.open('a').then(o => (editor = o)) + }) }) - ); + ) - return runs(function() { - expect(editor).toBe(editor1); - expect(workspace.getActivePaneItem()).toBe(editor); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); + return runs(function () { + expect(editor).toBe(editor1) + expect(workspace.getActivePaneItem()).toBe(editor) + expect(workspace.getActivePane().activate).toHaveBeenCalled() return expect(openEvents).toEqual([ { @@ -177,738 +187,769 @@ describe("Workspace", function() { pane: atom.workspace.getActivePane(), index: 0 } - ]);});})); - - return describe("when the active pane does not have an editor for the given uri", () => - it("adds and activates a new editor for the given path on the active pane", function() { - let editor = null; - waitsForPromise(() => workspace.open('a').then(o => editor = o)); - - return runs(function() { - expect(editor.getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))); - expect(workspace.getActivePaneItem()).toBe(editor); - expect(workspace.getActivePane().items).toEqual([editor]); - return expect(workspace.getActivePane().activate).toHaveBeenCalled(); - }); + ]) + }) }) - ); - }); - }); + ) - describe("when the 'searchAllPanes' option is true", function() { - describe("when an editor for the given uri is already open on an inactive pane", () => - it("activates the existing editor on the inactive pane, then activates that pane", function() { - let editor1 = null; - let editor2 = null; - const pane1 = workspace.getActivePane(); - const pane2 = workspace.getActivePane().splitRight(); + return describe('when the active pane does not have an editor for the given uri', () => + it('adds and activates a new editor for the given path on the active pane', function () { + let editor = null + waitsForPromise(() => workspace.open('a').then(o => (editor = o))) - waitsForPromise(function() { - pane1.activate(); - return workspace.open('a').then(o => editor1 = o); - }); + return runs(function () { + expect(editor.getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))) + expect(workspace.getActivePaneItem()).toBe(editor) + expect(workspace.getActivePane().items).toEqual([editor]) + return expect(workspace.getActivePane().activate).toHaveBeenCalled() + }) + }) + ) + }) + }) - waitsForPromise(function() { - pane2.activate(); - return workspace.open('b').then(o => editor2 = o); - }); + describe("when the 'searchAllPanes' option is true", function () { + describe('when an editor for the given uri is already open on an inactive pane', () => + it('activates the existing editor on the inactive pane, then activates that pane', function () { + let editor1 = null + let editor2 = null + const pane1 = workspace.getActivePane() + const pane2 = workspace.getActivePane().splitRight() - runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)); + waitsForPromise(function () { + pane1.activate() + return workspace.open('a').then(o => (editor1 = o)) + }) - waitsForPromise(() => workspace.open('a', {searchAllPanes: true})); + waitsForPromise(function () { + pane2.activate() + return workspace.open('b').then(o => (editor2 = o)) + }) - return runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - return expect(workspace.getActivePaneItem()).toBe(editor1); - }); + runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)) + + waitsForPromise(() => workspace.open('a', {searchAllPanes: true})) + + return runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + return expect(workspace.getActivePaneItem()).toBe(editor1) + }) }) - ); + ) - return describe("when no editor for the given uri is open in any pane", () => - it("opens an editor for the given uri in the active pane", function() { - let editor = null; - waitsForPromise(() => workspace.open('a', {searchAllPanes: true}).then(o => editor = o)); + return describe('when no editor for the given uri is open in any pane', () => + it('opens an editor for the given uri in the active pane', function () { + let editor = null + waitsForPromise(() => workspace.open('a', {searchAllPanes: true}).then(o => (editor = o))) - return runs(() => expect(workspace.getActivePaneItem()).toBe(editor)); + return runs(() => expect(workspace.getActivePaneItem()).toBe(editor)) }) - ); - }); + ) + }) - describe("when the 'split' option is set", function() { + describe("when the 'split' option is set", function () { describe("when the 'split' option is 'left'", () => - it("opens the editor in the leftmost pane of the current pane axis", function() { - const pane1 = workspace.getActivePane(); - const pane2 = pane1.splitRight(); - expect(workspace.getActivePane()).toBe(pane2); + it('opens the editor in the leftmost pane of the current pane axis', function () { + const pane1 = workspace.getActivePane() + const pane2 = pane1.splitRight() + expect(workspace.getActivePane()).toBe(pane2) - let editor = null; - waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => editor = o)); + let editor = null + waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => (editor = o))) - runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - return expect(pane2.items).toEqual([]);}); + runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + expect(pane1.items).toEqual([editor]) + return expect(pane2.items).toEqual([]) + }) // Focus right pane and reopen the file on the left - waitsForPromise(function() { - pane2.focus(); - return workspace.open('a', {split: 'left'}).then(o => editor = o); - }); + waitsForPromise(function () { + pane2.focus() + return workspace.open('a', {split: 'left'}).then(o => (editor = o)) + }) - return runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - return expect(pane2.items).toEqual([]);});})); + return runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + expect(pane1.items).toEqual([editor]) + return expect(pane2.items).toEqual([]) + }) + }) + ) - describe("when a pane axis is the leftmost sibling of the current pane", () => - it("opens the new item in the current pane", function() { - let editor = null; - const pane1 = workspace.getActivePane(); - const pane2 = pane1.splitLeft(); - const pane3 = pane2.splitDown(); - pane1.activate(); - expect(workspace.getActivePane()).toBe(pane1); + describe('when a pane axis is the leftmost sibling of the current pane', () => + it('opens the new item in the current pane', function () { + let editor = null + const pane1 = workspace.getActivePane() + const pane2 = pane1.splitLeft() + pane2.splitDown() + pane1.activate() + expect(workspace.getActivePane()).toBe(pane1) - waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => editor = o)); + waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => (editor = o))) - return runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - return expect(pane1.items).toEqual([editor]);});})); + return runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + return expect(pane1.items).toEqual([editor]) + }) + }) + ) - describe("when the 'split' option is 'right'", function() { - it("opens the editor in the rightmost pane of the current pane axis", function() { - let editor = null; - const pane1 = workspace.getActivePane(); - let pane2 = null; - waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => editor = o)); + describe("when the 'split' option is 'right'", function () { + it('opens the editor in the rightmost pane of the current pane axis', function () { + let editor = null + const pane1 = workspace.getActivePane() + let pane2 = null + waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => (editor = o))) - runs(function() { - pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - return expect(pane2.items).toEqual([editor]);}); + runs(function () { + pane2 = workspace.getPanes().filter(p => p !== pane1)[0] + expect(workspace.getActivePane()).toBe(pane2) + expect(pane1.items).toEqual([]) + return expect(pane2.items).toEqual([editor]) + }) // Focus right pane and reopen the file on the right - waitsForPromise(function() { - pane1.focus(); - return workspace.open('a', {split: 'right'}).then(o => editor = o); - }); - - return runs(function() { - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - return expect(pane2.items).toEqual([editor]);});}); - - return describe("when a pane axis is the rightmost sibling of the current pane", () => - it("opens the new item in a new pane split to the right of the current pane", function() { - let editor = null; - const pane1 = workspace.getActivePane(); - const pane2 = pane1.splitRight(); - const pane3 = pane2.splitDown(); - pane1.activate(); - expect(workspace.getActivePane()).toBe(pane1); - let pane4 = null; - - waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => editor = o)); - - return runs(function() { - pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane4); - expect(pane4.items).toEqual([editor]); - expect(workspace.paneContainer.root.children[0]).toBe(pane1); - return expect(workspace.paneContainer.root.children[1]).toBe(pane4); - }); + waitsForPromise(function () { + pane1.focus() + return workspace.open('a', {split: 'right'}).then(o => (editor = o)) }) - ); - }); + + return runs(function () { + expect(workspace.getActivePane()).toBe(pane2) + expect(pane1.items).toEqual([]) + return expect(pane2.items).toEqual([editor]) + }) + }) + + return describe('when a pane axis is the rightmost sibling of the current pane', () => + it('opens the new item in a new pane split to the right of the current pane', function () { + let editor = null + const pane1 = workspace.getActivePane() + const pane2 = pane1.splitRight() + pane2.splitDown() + pane1.activate() + expect(workspace.getActivePane()).toBe(pane1) + let pane4 = null + + waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => (editor = o))) + + return runs(function () { + pane4 = workspace.getPanes().filter(p => p !== pane1)[0] + expect(workspace.getActivePane()).toBe(pane4) + expect(pane4.items).toEqual([editor]) + expect(workspace.paneContainer.root.children[0]).toBe(pane1) + return expect(workspace.paneContainer.root.children[1]).toBe(pane4) + }) + }) + ) + }) describe("when the 'split' option is 'up'", () => - it("opens the editor in the topmost pane of the current pane axis", function() { - const pane1 = workspace.getActivePane(); - const pane2 = pane1.splitDown(); - expect(workspace.getActivePane()).toBe(pane2); + it('opens the editor in the topmost pane of the current pane axis', function () { + const pane1 = workspace.getActivePane() + const pane2 = pane1.splitDown() + expect(workspace.getActivePane()).toBe(pane2) - let editor = null; - waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => editor = o)); + let editor = null + waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => (editor = o))) - runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - return expect(pane2.items).toEqual([]);}); + runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + expect(pane1.items).toEqual([editor]) + return expect(pane2.items).toEqual([]) + }) // Focus bottom pane and reopen the file on the top - waitsForPromise(function() { - pane2.focus(); - return workspace.open('a', {split: 'up'}).then(o => editor = o); - }); + waitsForPromise(function () { + pane2.focus() + return workspace.open('a', {split: 'up'}).then(o => (editor = o)) + }) - return runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - return expect(pane2.items).toEqual([]);});})); + return runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + expect(pane1.items).toEqual([editor]) + return expect(pane2.items).toEqual([]) + }) + }) + ) - describe("when a pane axis is the topmost sibling of the current pane", () => - it("opens the new item in the current pane", function() { - let editor = null; - const pane1 = workspace.getActivePane(); - const pane2 = pane1.splitUp(); - const pane3 = pane2.splitRight(); - pane1.activate(); - expect(workspace.getActivePane()).toBe(pane1); + describe('when a pane axis is the topmost sibling of the current pane', () => + it('opens the new item in the current pane', function () { + let editor = null + const pane1 = workspace.getActivePane() + const pane2 = pane1.splitUp() + pane2.splitRight() + pane1.activate() + expect(workspace.getActivePane()).toBe(pane1) - waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => editor = o)); + waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => (editor = o))) - return runs(function() { - expect(workspace.getActivePane()).toBe(pane1); - return expect(pane1.items).toEqual([editor]);});})); + return runs(function () { + expect(workspace.getActivePane()).toBe(pane1) + return expect(pane1.items).toEqual([editor]) + }) + }) + ) - return describe("when the 'split' option is 'down'", function() { - it("opens the editor in the bottommost pane of the current pane axis", function() { - let editor = null; - const pane1 = workspace.getActivePane(); - let pane2 = null; - waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => editor = o)); + return describe("when the 'split' option is 'down'", function () { + it('opens the editor in the bottommost pane of the current pane axis', function () { + let editor = null + const pane1 = workspace.getActivePane() + let pane2 = null + waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => (editor = o))) - runs(function() { - pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - return expect(pane2.items).toEqual([editor]);}); + runs(function () { + pane2 = workspace.getPanes().filter(p => p !== pane1)[0] + expect(workspace.getActivePane()).toBe(pane2) + expect(pane1.items).toEqual([]) + return expect(pane2.items).toEqual([editor]) + }) // Focus bottom pane and reopen the file on the right - waitsForPromise(function() { - pane1.focus(); - return workspace.open('a', {split: 'down'}).then(o => editor = o); - }); - - return runs(function() { - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - return expect(pane2.items).toEqual([editor]);});}); - - return describe("when a pane axis is the bottommost sibling of the current pane", () => - it("opens the new item in a new pane split to the bottom of the current pane", function() { - let editor = null; - const pane1 = workspace.getActivePane(); - const pane2 = pane1.splitDown(); - pane1.activate(); - expect(workspace.getActivePane()).toBe(pane1); - let pane4 = null; - - waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => editor = o)); - - return runs(function() { - pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane4); - expect(pane4.items).toEqual([editor]); - expect(workspace.paneContainer.root.children[0]).toBe(pane1); - return expect(workspace.paneContainer.root.children[1]).toBe(pane2); - }); + waitsForPromise(function () { + pane1.focus() + return workspace.open('a', {split: 'down'}).then(o => (editor = o)) }) - ); - }); - }); - describe("when an initialLine and initialColumn are specified", () => - it("moves the cursor to the indicated location", function() { - waitsForPromise(() => workspace.open('a', {initialLine: 1, initialColumn: 5})); + return runs(function () { + expect(workspace.getActivePane()).toBe(pane2) + expect(pane1.items).toEqual([]) + return expect(pane2.items).toEqual([editor]) + }) + }) - runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([1, 5])); + return describe('when a pane axis is the bottommost sibling of the current pane', () => + it('opens the new item in a new pane split to the bottom of the current pane', function () { + let editor = null + const pane1 = workspace.getActivePane() + const pane2 = pane1.splitDown() + pane1.activate() + expect(workspace.getActivePane()).toBe(pane1) + let pane4 = null - waitsForPromise(() => workspace.open('a', {initialLine: 2, initialColumn: 4})); + waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => (editor = o))) - runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 4])); - - waitsForPromise(() => workspace.open('a', {initialLine: 0, initialColumn: 0})); - - runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([0, 0])); - - waitsForPromise(() => workspace.open('a', {initialLine: NaN, initialColumn: 4})); - - runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([0, 4])); - - waitsForPromise(() => workspace.open('a', {initialLine: 2, initialColumn: NaN})); - - runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 0])); - - waitsForPromise(() => workspace.open('a', {initialLine: Infinity, initialColumn: Infinity})); - - return runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 11]));})); - - describe("when the file is over 2MB", () => - it("opens the editor with largeFileMode: true", function() { - spyOn(fs, 'getSizeSync').andReturn(2 * 1048577); // 2MB - - let editor = null; - waitsForPromise(() => workspace.open('sample.js').then(e => editor = e)); - - return runs(() => expect(editor.largeFileMode).toBe(true)); + return runs(function () { + pane4 = workspace.getPanes().filter(p => p !== pane1)[0] + expect(workspace.getActivePane()).toBe(pane4) + expect(pane4.items).toEqual([editor]) + expect(workspace.paneContainer.root.children[0]).toBe(pane1) + return expect(workspace.paneContainer.root.children[1]).toBe(pane2) + }) + }) + ) }) - ); + }) - describe("when the file is over user-defined limit", function() { - const shouldPromptForFileOfSize = function(size, shouldPrompt) { - spyOn(fs, 'getSizeSync').andReturn(size * 1048577); - atom.applicationDelegate.confirm.andCallFake(() => selectedButtonIndex); - atom.applicationDelegate.confirm(); - var selectedButtonIndex = 1; // cancel + describe('when an initialLine and initialColumn are specified', () => + it('moves the cursor to the indicated location', function () { + waitsForPromise(() => workspace.open('a', {initialLine: 1, initialColumn: 5})) - let editor = null; - waitsForPromise(() => workspace.open('sample.js').then(e => editor = e)); + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([1, 5])) + + waitsForPromise(() => workspace.open('a', {initialLine: 2, initialColumn: 4})) + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 4])) + + waitsForPromise(() => workspace.open('a', {initialLine: 0, initialColumn: 0})) + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([0, 0])) + + waitsForPromise(() => workspace.open('a', {initialLine: NaN, initialColumn: 4})) + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([0, 4])) + + waitsForPromise(() => workspace.open('a', {initialLine: 2, initialColumn: NaN})) + + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 0])) + + waitsForPromise(() => workspace.open('a', {initialLine: Infinity, initialColumn: Infinity})) + + return runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 11])) + }) + ) + + describe('when the file is over 2MB', () => + it('opens the editor with largeFileMode: true', function () { + spyOn(fs, 'getSizeSync').andReturn(2 * 1048577) // 2MB + + let editor = null + waitsForPromise(() => workspace.open('sample.js').then(e => (editor = e))) + + return runs(() => expect(editor.largeFileMode).toBe(true)) + }) + ) + + describe('when the file is over user-defined limit', function () { + const shouldPromptForFileOfSize = function (size, shouldPrompt) { + spyOn(fs, 'getSizeSync').andReturn(size * 1048577) + atom.applicationDelegate.confirm.andCallFake(() => selectedButtonIndex) + atom.applicationDelegate.confirm() + var selectedButtonIndex = 1 // cancel + + let editor = null + waitsForPromise(() => workspace.open('sample.js').then(e => (editor = e))) if (shouldPrompt) { - runs(function() { - expect(editor).toBeUndefined(); - expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + runs(function () { + expect(editor).toBeUndefined() + expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - atom.applicationDelegate.confirm.reset(); - return selectedButtonIndex = 0; - }); // open the file + atom.applicationDelegate.confirm.reset() + return (selectedButtonIndex = 0) + }) // open the file - waitsForPromise(() => workspace.open('sample.js').then(e => editor = e)); + waitsForPromise(() => workspace.open('sample.js').then(e => (editor = e))) - return runs(function() { - expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); - return expect(editor.largeFileMode).toBe(true); - }); + return runs(function () { + expect(atom.applicationDelegate.confirm).toHaveBeenCalled() + return expect(editor.largeFileMode).toBe(true) + }) } else { - return runs(() => expect(editor).not.toBeUndefined()); + return runs(() => expect(editor).not.toBeUndefined()) } - }; + } - it("prompts the user to make sure they want to open a file this big", function() { - atom.config.set("core.warnOnLargeFileLimit", 20); - return shouldPromptForFileOfSize(20, true); - }); + it('prompts the user to make sure they want to open a file this big', function () { + atom.config.set('core.warnOnLargeFileLimit', 20) + return shouldPromptForFileOfSize(20, true) + }) - it("doesn't prompt on files below the limit", function() { - atom.config.set("core.warnOnLargeFileLimit", 30); - return shouldPromptForFileOfSize(20, false); - }); + it("doesn't prompt on files below the limit", function () { + atom.config.set('core.warnOnLargeFileLimit', 30) + return shouldPromptForFileOfSize(20, false) + }) - return it("prompts for smaller files with a lower limit", function() { - atom.config.set("core.warnOnLargeFileLimit", 5); - return shouldPromptForFileOfSize(10, true); - }); - }); + return it('prompts for smaller files with a lower limit', function () { + atom.config.set('core.warnOnLargeFileLimit', 5) + return shouldPromptForFileOfSize(10, true) + }) + }) - describe("when passed a path that matches a custom opener", () => - it("returns the resource returned by the custom opener", function() { - const fooOpener = function(pathToOpen, options) { if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { return {foo: pathToOpen, options}; } }; - const barOpener = function(pathToOpen) { if (pathToOpen != null ? pathToOpen.match(/^bar:\/\//) : undefined) { return {bar: pathToOpen}; } }; - workspace.addOpener(fooOpener); - workspace.addOpener(barOpener); + describe('when passed a path that matches a custom opener', () => + it('returns the resource returned by the custom opener', function () { + const fooOpener = function (pathToOpen, options) { if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { return {foo: pathToOpen, options} } } + const barOpener = function (pathToOpen) { if (pathToOpen != null ? pathToOpen.match(/^bar:\/\//) : undefined) { return {bar: pathToOpen} } } + workspace.addOpener(fooOpener) + workspace.addOpener(barOpener) - waitsForPromise(function() { - const pathToOpen = __guard__(atom.project.getDirectories()[0], x => x.resolve('a.foo')); - return workspace.open(pathToOpen, {hey: "there"}).then(item => expect(item).toEqual({foo: pathToOpen, options: {hey: "there"}}));}); + waitsForPromise(function () { + const pathToOpen = __guard__(atom.project.getDirectories()[0], x => x.resolve('a.foo')) + return workspace.open(pathToOpen, {hey: 'there'}).then(item => expect(item).toEqual({foo: pathToOpen, options: {hey: 'there'}})) + }) return waitsForPromise(() => - workspace.open("bar://baz").then(item => expect(item).toEqual({bar: "bar://baz"})));})); + workspace.open('bar://baz').then(item => expect(item).toEqual({bar: 'bar://baz'}))) + }) + ) - it("adds the file to the application's recent documents list", function() { - if (process.platform !== 'darwin') { return; } // Feature only supported on macOS - spyOn(atom.applicationDelegate, 'addRecentDocument'); + it("adds the file to the application's recent documents list", function () { + if (process.platform !== 'darwin') { return } // Feature only supported on macOS + spyOn(atom.applicationDelegate, 'addRecentDocument') - waitsForPromise(() => workspace.open()); + waitsForPromise(() => workspace.open()) - runs(() => expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled()); + runs(() => expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled()) - waitsForPromise(() => workspace.open('something://a/url')); + waitsForPromise(() => workspace.open('something://a/url')) - runs(() => expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled()); + runs(() => expect(atom.applicationDelegate.addRecentDocument).not.toHaveBeenCalled()) - waitsForPromise(() => workspace.open(__filename)); + waitsForPromise(() => workspace.open(__filename)) - return runs(() => expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith(__filename)); - }); + return runs(() => expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith(__filename)) + }) - it("notifies ::onDidAddTextEditor observers", function() { - const absolutePath = require.resolve('./fixtures/dir/a'); - const newEditorHandler = jasmine.createSpy('newEditorHandler'); - workspace.onDidAddTextEditor(newEditorHandler); + it('notifies ::onDidAddTextEditor observers', function () { + const absolutePath = require.resolve('./fixtures/dir/a') + const newEditorHandler = jasmine.createSpy('newEditorHandler') + workspace.onDidAddTextEditor(newEditorHandler) - let editor = null; - waitsForPromise(() => workspace.open(absolutePath).then(e => editor = e)); + let editor = null + waitsForPromise(() => workspace.open(absolutePath).then(e => (editor = e))) - return runs(() => expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor)); - }); + return runs(() => expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor)) + }) - describe("when there is an error opening the file", function() { - let notificationSpy = null; - beforeEach(() => atom.notifications.onDidAddNotification(notificationSpy = jasmine.createSpy())); + describe('when there is an error opening the file', function () { + let notificationSpy = null + beforeEach(() => atom.notifications.onDidAddNotification(notificationSpy = jasmine.createSpy())) - describe("when a file does not exist", () => - it("creates an empty buffer for the specified path", function() { - waitsForPromise(() => workspace.open('not-a-file.md')); + describe('when a file does not exist', () => + it('creates an empty buffer for the specified path', function () { + waitsForPromise(() => workspace.open('not-a-file.md')) - return runs(function() { - const editor = workspace.getActiveTextEditor(); - expect(notificationSpy).not.toHaveBeenCalled(); - return expect(editor.getPath()).toContain('not-a-file.md'); - }); + return runs(function () { + const editor = workspace.getActiveTextEditor() + expect(notificationSpy).not.toHaveBeenCalled() + return expect(editor.getPath()).toContain('not-a-file.md') + }) }) - ); + ) - describe("when the user does not have access to the file", function() { + describe('when the user does not have access to the file', function () { beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function(path) { - const error = new Error(`EACCES, permission denied '${path}'`); - error.path = path; - error.code = 'EACCES'; - throw error; + spyOn(fs, 'openSync').andCallFake(function (path) { + const error = new Error(`EACCES, permission denied '${path}'`) + error.path = path + error.code = 'EACCES' + throw error }) - ); + ) - return it("creates a notification", function() { - waitsForPromise(() => workspace.open('file1')); + return it('creates a notification', function () { + waitsForPromise(() => workspace.open('file1')) - return runs(function() { - expect(notificationSpy).toHaveBeenCalled(); - const notification = notificationSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Permission denied'); - return expect(notification.getMessage()).toContain('file1'); - }); - }); - }); - - describe("when the the operation is not permitted", function() { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function(path) { - const error = new Error(`EPERM, operation not permitted '${path}'`); - error.path = path; - error.code = 'EPERM'; - throw error; + return runs(function () { + expect(notificationSpy).toHaveBeenCalled() + const notification = notificationSpy.mostRecentCall.args[0] + expect(notification.getType()).toBe('warning') + expect(notification.getMessage()).toContain('Permission denied') + return expect(notification.getMessage()).toContain('file1') }) - ); - - return it("creates a notification", function() { - waitsForPromise(() => workspace.open('file1')); - - return runs(function() { - expect(notificationSpy).toHaveBeenCalled(); - const notification = notificationSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Unable to open'); - return expect(notification.getMessage()).toContain('file1'); - }); - }); - }); - - describe("when the the file is already open in windows", function() { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function(path) { - const error = new Error(`EBUSY, resource busy or locked '${path}'`); - error.path = path; - error.code = 'EBUSY'; - throw error; - }) - ); - - return it("creates a notification", function() { - waitsForPromise(() => workspace.open('file1')); - - return runs(function() { - expect(notificationSpy).toHaveBeenCalled(); - const notification = notificationSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Unable to open'); - return expect(notification.getMessage()).toContain('file1'); - }); - }); - }); - - return describe("when there is an unhandled error", function() { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function(path) { - throw new Error("I dont even know what is happening right now!!"); - }) - ); - - return it("creates a notification", function() { - const open = () => workspace.open('file1', workspace.getActivePane()); - return expect(open).toThrow(); - }); - }); - }); - - describe("when the file is already open in pending state", () => - it("should terminate the pending state", function() { - let editor = null; - let pane = null; - - waitsForPromise(() => - atom.workspace.open('sample.js', {pending: true}).then(function(o) { - editor = o; - return pane = atom.workspace.getActivePane(); - }) - ); - - runs(() => expect(pane.getPendingItem()).toEqual(editor)); - - waitsForPromise(() => atom.workspace.open('sample.js')); - - return runs(() => expect(pane.getPendingItem()).toBeNull()); + }) }) - ); - describe("when opening will switch from a pending tab to a permanent tab", () => - it("keeps the pending tab open", function() { - let editor1 = null; - let editor2 = null; - - waitsForPromise(() => - atom.workspace.open('sample.txt').then(o => editor1 = o) - ); - - waitsForPromise(() => - atom.workspace.open('sample2.txt', {pending: true}).then(o => editor2 = o) - ); - - return runs(function() { - const pane = atom.workspace.getActivePane(); - pane.activateItem(editor1); - expect(pane.getItems().length).toBe(2); - return expect(pane.getItems()).toEqual([editor1, editor2]);});})); - - return describe("when replacing a pending item which is the last item in a second pane", () => - it("does not destroy the pane even if core.destroyEmptyPanes is on", function() { - atom.config.set('core.destroyEmptyPanes', true); - let editor1 = null; - let editor2 = null; - const leftPane = atom.workspace.getActivePane(); - let rightPane = null; - - waitsForPromise(() => - atom.workspace.open('sample.js', {pending: true, split: 'right'}).then(function(o) { - editor1 = o; - rightPane = atom.workspace.getActivePane(); - return spyOn(rightPane, "destroyed"); + describe('when the the operation is not permitted', function () { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function (path) { + const error = new Error(`EPERM, operation not permitted '${path}'`) + error.path = path + error.code = 'EPERM' + throw error }) - ); + ) - runs(function() { - expect(leftPane).not.toBe(rightPane); - expect(atom.workspace.getActivePane()).toBe(rightPane); - expect(atom.workspace.getActivePane().getItems().length).toBe(1); - return expect(rightPane.getPendingItem()).toBe(editor1); - }); + return it('creates a notification', function () { + waitsForPromise(() => workspace.open('file1')) + + return runs(function () { + expect(notificationSpy).toHaveBeenCalled() + const notification = notificationSpy.mostRecentCall.args[0] + expect(notification.getType()).toBe('warning') + expect(notification.getMessage()).toContain('Unable to open') + return expect(notification.getMessage()).toContain('file1') + }) + }) + }) + + describe('when the the file is already open in windows', function () { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function (path) { + const error = new Error(`EBUSY, resource busy or locked '${path}'`) + error.path = path + error.code = 'EBUSY' + throw error + }) + ) + + return it('creates a notification', function () { + waitsForPromise(() => workspace.open('file1')) + + return runs(function () { + expect(notificationSpy).toHaveBeenCalled() + const notification = notificationSpy.mostRecentCall.args[0] + expect(notification.getType()).toBe('warning') + expect(notification.getMessage()).toContain('Unable to open') + return expect(notification.getMessage()).toContain('file1') + }) + }) + }) + + return describe('when there is an unhandled error', function () { + beforeEach(() => + spyOn(fs, 'openSync').andCallFake(function (path) { + throw new Error('I dont even know what is happening right now!!') + }) + ) + + return it('creates a notification', function () { + const open = () => workspace.open('file1', workspace.getActivePane()) + return expect(open).toThrow() + }) + }) + }) + + describe('when the file is already open in pending state', () => + it('should terminate the pending state', function () { + let editor = null + let pane = null waitsForPromise(() => - atom.workspace.open('sample.txt', {pending: true}).then(o => editor2 = o) - ); + atom.workspace.open('sample.js', {pending: true}).then(function (o) { + editor = o + return (pane = atom.workspace.getActivePane()) + }) + ) - return runs(function() { - expect(rightPane.getPendingItem()).toBe(editor2); - return expect(rightPane.destroyed.callCount).toBe(0); - }); + runs(() => expect(pane.getPendingItem()).toEqual(editor)) + + waitsForPromise(() => atom.workspace.open('sample.js')) + + return runs(() => expect(pane.getPendingItem()).toBeNull()) }) - ); - }); + ) + + describe('when opening will switch from a pending tab to a permanent tab', () => + it('keeps the pending tab open', function () { + let editor1 = null + let editor2 = null + + waitsForPromise(() => + atom.workspace.open('sample.txt').then(o => (editor1 = o)) + ) + + waitsForPromise(() => + atom.workspace.open('sample2.txt', {pending: true}).then(o => (editor2 = o)) + ) + + return runs(function () { + const pane = atom.workspace.getActivePane() + pane.activateItem(editor1) + expect(pane.getItems().length).toBe(2) + return expect(pane.getItems()).toEqual([editor1, editor2]) + }) + }) + ) + + return describe('when replacing a pending item which is the last item in a second pane', () => + it('does not destroy the pane even if core.destroyEmptyPanes is on', function () { + atom.config.set('core.destroyEmptyPanes', true) + let editor1 = null + let editor2 = null + const leftPane = atom.workspace.getActivePane() + let rightPane = null + + waitsForPromise(() => + atom.workspace.open('sample.js', {pending: true, split: 'right'}).then(function (o) { + editor1 = o + rightPane = atom.workspace.getActivePane() + return spyOn(rightPane, 'destroyed') + }) + ) + + runs(function () { + expect(leftPane).not.toBe(rightPane) + expect(atom.workspace.getActivePane()).toBe(rightPane) + expect(atom.workspace.getActivePane().getItems().length).toBe(1) + return expect(rightPane.getPendingItem()).toBe(editor1) + }) + + waitsForPromise(() => + atom.workspace.open('sample.txt', {pending: true}).then(o => (editor2 = o)) + ) + + return runs(function () { + expect(rightPane.getPendingItem()).toBe(editor2) + return expect(rightPane.destroyed.callCount).toBe(0) + }) + }) + ) + }) describe('the grammar-used hook', () => - it('fires when opening a file or changing the grammar of an open file', function() { - let editor = null; - let javascriptGrammarUsed = false; - let coffeescriptGrammarUsed = false; + it('fires when opening a file or changing the grammar of an open file', function () { + let editor = null + let javascriptGrammarUsed = false + let coffeescriptGrammarUsed = false - atom.packages.triggerDeferredActivationHooks(); + atom.packages.triggerDeferredActivationHooks() - runs(function() { - atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', () => javascriptGrammarUsed = true); - return atom.packages.onDidTriggerActivationHook('language-coffee-script:grammar-used', () => coffeescriptGrammarUsed = true); - }); + runs(function () { + atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', () => (javascriptGrammarUsed = true)) + return atom.packages.onDidTriggerActivationHook('language-coffee-script:grammar-used', () => (coffeescriptGrammarUsed = true)) + }) - waitsForPromise(() => atom.workspace.open('sample.js', {autoIndent: false}).then(o => editor = o)); + waitsForPromise(() => atom.workspace.open('sample.js', {autoIndent: false}).then(o => (editor = o))) - waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + waitsForPromise(() => atom.packages.activatePackage('language-javascript')) - waitsFor(() => javascriptGrammarUsed); + waitsFor(() => javascriptGrammarUsed) - waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')); + waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')) - runs(() => editor.setGrammar(atom.grammars.selectGrammar('.coffee'))); + runs(() => editor.setGrammar(atom.grammars.selectGrammar('.coffee'))) - return waitsFor(() => coffeescriptGrammarUsed); + return waitsFor(() => coffeescriptGrammarUsed) }) - ); + ) - describe("::reopenItem()", () => - it("opens the uri associated with the last closed pane that isn't currently open", function() { - const pane = workspace.getActivePane(); + describe('::reopenItem()', () => + it("opens the uri associated with the last closed pane that isn't currently open", function () { + const pane = workspace.getActivePane() waitsForPromise(() => workspace.open('a').then(() => workspace.open('b').then(() => workspace.open('file1').then(() => workspace.open()) ) ) - ); + ) - runs(function() { + runs(function () { // does not reopen items with no uri - expect(workspace.getActivePaneItem().getURI()).toBeUndefined(); - return pane.destroyActiveItem(); - }); + expect(workspace.getActivePaneItem().getURI()).toBeUndefined() + return pane.destroyActiveItem() + }) - waitsForPromise(() => workspace.reopenItem()); + waitsForPromise(() => workspace.reopenItem()) - runs(function() { - expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined(); + runs(function () { + expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined() // destroy all items - expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1'))); - pane.destroyActiveItem(); - expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('b'))); - pane.destroyActiveItem(); - expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('a'))); - pane.destroyActiveItem(); + expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1'))) + pane.destroyActiveItem() + expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('b'))) + pane.destroyActiveItem() + expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('a'))) + pane.destroyActiveItem() // reopens items with uris - return expect(workspace.getActivePaneItem()).toBeUndefined(); - }); + return expect(workspace.getActivePaneItem()).toBeUndefined() + }) - waitsForPromise(() => workspace.reopenItem()); + waitsForPromise(() => workspace.reopenItem()) - runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a')))); + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a')))) // does not reopen items that are already open - waitsForPromise(() => workspace.open('b')); + waitsForPromise(() => workspace.open('b')) - runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b')))); + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b')))) - waitsForPromise(() => workspace.reopenItem()); + waitsForPromise(() => workspace.reopenItem()) - return runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1')))); + return runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1')))) }) - ); + ) - describe("::increase/decreaseFontSize()", () => - it("increases/decreases the font size without going below 1", function() { - atom.config.set('editor.fontSize', 1); - workspace.increaseFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(2); - workspace.increaseFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(3); - workspace.decreaseFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(2); - workspace.decreaseFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(1); - workspace.decreaseFontSize(); - return expect(atom.config.get('editor.fontSize')).toBe(1); + describe('::increase/decreaseFontSize()', () => + it('increases/decreases the font size without going below 1', function () { + atom.config.set('editor.fontSize', 1) + workspace.increaseFontSize() + expect(atom.config.get('editor.fontSize')).toBe(2) + workspace.increaseFontSize() + expect(atom.config.get('editor.fontSize')).toBe(3) + workspace.decreaseFontSize() + expect(atom.config.get('editor.fontSize')).toBe(2) + workspace.decreaseFontSize() + expect(atom.config.get('editor.fontSize')).toBe(1) + workspace.decreaseFontSize() + return expect(atom.config.get('editor.fontSize')).toBe(1) }) - ); + ) - describe("::resetFontSize()", function() { - it("resets the font size to the window's starting font size", function() { - const originalFontSize = atom.config.get('editor.fontSize'); + describe('::resetFontSize()', function () { + it("resets the font size to the window's starting font size", function () { + const originalFontSize = atom.config.get('editor.fontSize') - workspace.increaseFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(originalFontSize + 1); - workspace.resetFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); - workspace.decreaseFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(originalFontSize - 1); - workspace.resetFontSize(); - return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); - }); - - it("does nothing if the font size has not been changed", function() { - const originalFontSize = atom.config.get('editor.fontSize'); - - workspace.resetFontSize(); - return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); - }); - - return it("resets the font size when the editor's font size changes", function() { - const originalFontSize = atom.config.get('editor.fontSize'); - - atom.config.set('editor.fontSize', originalFontSize + 1); - workspace.resetFontSize(); - expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); - atom.config.set('editor.fontSize', originalFontSize - 1); - workspace.resetFontSize(); - return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); - }); - }); - - describe("::openLicense()", () => - it("opens the license as plain-text in a buffer", function() { - waitsForPromise(() => workspace.openLicense()); - return runs(() => expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/)); + workspace.increaseFontSize() + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize + 1) + workspace.resetFontSize() + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) + workspace.decreaseFontSize() + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize - 1) + workspace.resetFontSize() + return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) }) - ); - describe("::isTextEditor(obj)", () => - it("returns true when the passed object is an instance of `TextEditor`", function() { - expect(workspace.isTextEditor(new TextEditor)).toBe(true); - expect(workspace.isTextEditor({getText() { return null; }})).toBe(false); - expect(workspace.isTextEditor(null)).toBe(false); - return expect(workspace.isTextEditor(undefined)).toBe(false); + it('does nothing if the font size has not been changed', function () { + const originalFontSize = atom.config.get('editor.fontSize') + + workspace.resetFontSize() + return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) }) - ); - describe("::observeTextEditors()", () => - it("invokes the observer with current and future text editors", function() { - const observed = []; + return it("resets the font size when the editor's font size changes", function () { + const originalFontSize = atom.config.get('editor.fontSize') - waitsForPromise(() => workspace.open()); - waitsForPromise(() => workspace.open()); - waitsForPromise(() => workspace.openLicense()); - - runs(() => workspace.observeTextEditors(editor => observed.push(editor))); - - waitsForPromise(() => workspace.open()); - - return expect(observed).toEqual(workspace.getTextEditors()); + atom.config.set('editor.fontSize', originalFontSize + 1) + workspace.resetFontSize() + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) + atom.config.set('editor.fontSize', originalFontSize - 1) + workspace.resetFontSize() + return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) }) - ); + }) - describe("when an editor is destroyed", () => - it("removes the editor", function() { - let editor = null; - - waitsForPromise(() => workspace.open("a").then(e => editor = e)); - - return runs(function() { - expect(workspace.getTextEditors()).toHaveLength(1); - editor.destroy(); - return expect(workspace.getTextEditors()).toHaveLength(0); - }); + describe('::openLicense()', () => + it('opens the license as plain-text in a buffer', function () { + waitsForPromise(() => workspace.openLicense()) + return runs(() => expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/)) }) - ); + ) - describe("when an editor is copied because its pane is split", () => - it("sets up the new editor to be configured by the text editor registry", function() { - waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + describe('::isTextEditor(obj)', () => + it('returns true when the passed object is an instance of `TextEditor`', function () { + expect(workspace.isTextEditor(new TextEditor())).toBe(true) + expect(workspace.isTextEditor({getText () { return null }})).toBe(false) + expect(workspace.isTextEditor(null)).toBe(false) + return expect(workspace.isTextEditor(undefined)).toBe(false) + }) + ) + + describe('::observeTextEditors()', () => + it('invokes the observer with current and future text editors', function () { + const observed = [] + + waitsForPromise(() => workspace.open()) + waitsForPromise(() => workspace.open()) + waitsForPromise(() => workspace.openLicense()) + + runs(() => workspace.observeTextEditors(editor => observed.push(editor))) + + waitsForPromise(() => workspace.open()) + + return expect(observed).toEqual(workspace.getTextEditors()) + }) + ) + + describe('when an editor is destroyed', () => + it('removes the editor', function () { + let editor = null + + waitsForPromise(() => workspace.open('a').then(e => (editor = e))) + + return runs(function () { + expect(workspace.getTextEditors()).toHaveLength(1) + editor.destroy() + return expect(workspace.getTextEditors()).toHaveLength(0) + }) + }) + ) + + describe('when an editor is copied because its pane is split', () => + it('sets up the new editor to be configured by the text editor registry', function () { + waitsForPromise(() => atom.packages.activatePackage('language-javascript')) return waitsForPromise(() => - workspace.open('a').then(function(editor) { - atom.textEditors.setGrammarOverride(editor, 'source.js'); - expect(editor.getGrammar().name).toBe('JavaScript'); + workspace.open('a').then(function (editor) { + atom.textEditors.setGrammarOverride(editor, 'source.js') + expect(editor.getGrammar().name).toBe('JavaScript') - workspace.getActivePane().splitRight({copyActiveItem: true}); - const newEditor = workspace.getActiveTextEditor(); - expect(newEditor).not.toBe(editor); - return expect(newEditor.getGrammar().name).toBe('JavaScript'); + workspace.getActivePane().splitRight({copyActiveItem: true}) + const newEditor = workspace.getActiveTextEditor() + expect(newEditor).not.toBe(editor) + return expect(newEditor.getGrammar().name).toBe('JavaScript') }) - ); + ) }) - ); + ) - it("stores the active grammars used by all the open editors", function() { - waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + it('stores the active grammars used by all the open editors', function () { + waitsForPromise(() => atom.packages.activatePackage('language-javascript')) - waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')); + waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')) - waitsForPromise(() => atom.packages.activatePackage('language-todo')); + waitsForPromise(() => atom.packages.activatePackage('language-todo')) - waitsForPromise(() => atom.workspace.open('sample.coffee')); + waitsForPromise(() => atom.workspace.open('sample.coffee')) - return runs(function() { + return runs(function () { atom.workspace.getActiveTextEditor().setText(`\ i = /test/; #FIXME\ ` - ); + ) const atom2 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate, @@ -917,16 +958,16 @@ i = /test/; #FIXME\ document.createElement('div'), { body: document.createElement('div'), - head: document.createElement('div'), + head: document.createElement('div') } ) - }); + }) - atom2.packages.loadPackage('language-javascript'); - atom2.packages.loadPackage('language-coffee-script'); - atom2.packages.loadPackage('language-todo'); - atom2.project.deserialize(atom.project.serialize()); - atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers); + atom2.packages.loadPackage('language-javascript') + atom2.packages.loadPackage('language-coffee-script') + atom2.packages.loadPackage('language-todo') + atom2.project.deserialize(atom.project.serialize()) + atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers) expect(atom2.grammars.getGrammars().map(grammar => grammar.name).sort()).toEqual([ 'CoffeeScript', @@ -936,119 +977,119 @@ i = /test/; #FIXME\ 'Regular Expression Replacement (JavaScript)', 'Regular Expressions (JavaScript)', 'TODO' - ]); + ]) - return atom2.destroy(); - }); - }); + return atom2.destroy() + }) + }) - describe("document.title", function() { - describe("when there is no item open", function() { - it("sets the title to the project path", () => expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0])))); + describe('document.title', function () { + describe('when there is no item open', function () { + it('sets the title to the project path', () => expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0])))) - return it("sets the title to 'untitled' if there is no project path", function() { - atom.project.setPaths([]); - return expect(document.title).toMatch(/^untitled/); - }); - }); + return it("sets the title to 'untitled' if there is no project path", function () { + atom.project.setPaths([]) + return expect(document.title).toMatch(/^untitled/) + }) + }) - describe("when the active pane item's path is not inside a project path", function() { + describe("when the active pane item's path is not inside a project path", function () { beforeEach(() => waitsForPromise(() => atom.workspace.open('b').then(() => atom.project.setPaths([])) ) - ); + ) - it("sets the title to the pane item's title plus the item's path", function() { - const item = atom.workspace.getActivePaneItem(); - const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))); - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); - }); + it("sets the title to the pane item's title plus the item's path", function () { + const item = atom.workspace.getActivePaneItem() + const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))) + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) + }) - describe("when the title of the active pane item changes", () => - it("updates the window title based on the item's new title", function() { - const editor = atom.workspace.getActivePaneItem(); - editor.buffer.setPath(path.join(temp.dir, 'hi')); - const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(editor.getPath()))); - return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)); + describe('when the title of the active pane item changes', () => + it("updates the window title based on the item's new title", function () { + const editor = atom.workspace.getActivePaneItem() + editor.buffer.setPath(path.join(temp.dir, 'hi')) + const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(editor.getPath()))) + return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)) }) - ); + ) describe("when the active pane's item changes", () => - it("updates the title to the new item's title plus the project path", function() { - atom.workspace.getActivePane().activateNextItem(); - const item = atom.workspace.getActivePaneItem(); - const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))); - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + it("updates the title to the new item's title plus the project path", function () { + atom.workspace.getActivePane().activateNextItem() + const item = atom.workspace.getActivePaneItem() + const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))) + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - ); + ) return describe("when an inactive pane's item changes", () => - it("does not update the title", function() { - const pane = atom.workspace.getActivePane(); - pane.splitRight(); - const initialTitle = document.title; - pane.activateNextItem(); - return expect(document.title).toBe(initialTitle); + it('does not update the title', function () { + const pane = atom.workspace.getActivePane() + pane.splitRight() + const initialTitle = document.title + pane.activateNextItem() + return expect(document.title).toBe(initialTitle) }) - ); - }); + ) + }) - describe("when the active pane item is inside a project path", function() { + describe('when the active pane item is inside a project path', function () { beforeEach(() => waitsForPromise(() => atom.workspace.open('b')) - ); + ) - describe("when there is an active pane item", () => - it("sets the title to the pane item's title plus the project path", function() { - const item = atom.workspace.getActivePaneItem(); - const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + describe('when there is an active pane item', () => + it("sets the title to the pane item's title plus the project path", function () { + const item = atom.workspace.getActivePaneItem() + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - ); + ) - describe("when the title of the active pane item changes", () => - it("updates the window title based on the item's new title", function() { - const editor = atom.workspace.getActivePaneItem(); - editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')); - const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); - return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)); + describe('when the title of the active pane item changes', () => + it("updates the window title based on the item's new title", function () { + const editor = atom.workspace.getActivePaneItem() + editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')) + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) + return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)) }) - ); + ) describe("when the active pane's item changes", () => - it("updates the title to the new item's title plus the project path", function() { - atom.workspace.getActivePane().activateNextItem(); - const item = atom.workspace.getActivePaneItem(); - const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)); + it("updates the title to the new item's title plus the project path", function () { + atom.workspace.getActivePane().activateNextItem() + const item = atom.workspace.getActivePaneItem() + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) + return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - ); + ) - describe("when the last pane item is removed", () => - it("updates the title to the project's first path", function() { - atom.workspace.getActivePane().destroy(); - expect(atom.workspace.getActivePaneItem()).toBeUndefined(); - return expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))); + describe('when the last pane item is removed', () => + it("updates the title to the project's first path", function () { + atom.workspace.getActivePane().destroy() + expect(atom.workspace.getActivePaneItem()).toBeUndefined() + return expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))) }) - ); + ) return describe("when an inactive pane's item changes", () => - it("does not update the title", function() { - const pane = atom.workspace.getActivePane(); - pane.splitRight(); - const initialTitle = document.title; - pane.activateNextItem(); - return expect(document.title).toBe(initialTitle); + it('does not update the title', function () { + const pane = atom.workspace.getActivePane() + pane.splitRight() + const initialTitle = document.title + pane.activateNextItem() + return expect(document.title).toBe(initialTitle) }) - ); - }); + ) + }) - return describe("when the workspace is deserialized", function() { - beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))); + return describe('when the workspace is deserialized', function () { + beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))) - return it("updates the title to contain the project's path", function() { - document.title = null; + return it("updates the title to contain the project's path", function () { + document.title = null const atom2 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate, @@ -1057,510 +1098,528 @@ i = /test/; #FIXME\ document.createElement('div'), { body: document.createElement('div'), - head: document.createElement('div'), + head: document.createElement('div') } ) - }); + }) - atom2.project.deserialize(atom.project.serialize()); - atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers); - const item = atom2.workspace.getActivePaneItem(); - const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])); - expect(document.title).toMatch(new RegExp(`^${item.getLongTitle()} \\u2014 ${pathEscaped}`)); + atom2.project.deserialize(atom.project.serialize()) + atom2.workspace.deserialize(atom.workspace.serialize(), atom2.deserializers) + const item = atom2.workspace.getActivePaneItem() + const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) + expect(document.title).toMatch(new RegExp(`^${item.getLongTitle()} \\u2014 ${pathEscaped}`)) - return atom2.destroy(); - }); - }); - }); + return atom2.destroy() + }) + }) + }) - describe("document edited status", function() { - let [item1, item2] = Array.from([]); + describe('document edited status', function () { + let [item1, item2] = Array.from([]) - beforeEach(function() { - waitsForPromise(() => atom.workspace.open('a')); - waitsForPromise(() => atom.workspace.open('b')); - return runs(() => [item1, item2] = Array.from(atom.workspace.getPaneItems())); - }); + beforeEach(function () { + waitsForPromise(() => atom.workspace.open('a')) + waitsForPromise(() => atom.workspace.open('b')) + return runs(() => ([item1, item2] = Array.from(atom.workspace.getPaneItems()))) + }) - it("calls setDocumentEdited when the active item changes", function() { - expect(atom.workspace.getActivePaneItem()).toBe(item2); - item1.insertText('a'); - expect(item1.isModified()).toBe(true); - atom.workspace.getActivePane().activateNextItem(); + it('calls setDocumentEdited when the active item changes', function () { + expect(atom.workspace.getActivePaneItem()).toBe(item2) + item1.insertText('a') + expect(item1.isModified()).toBe(true) + atom.workspace.getActivePane().activateNextItem() - return expect(setDocumentEdited).toHaveBeenCalledWith(true); - }); + return expect(setDocumentEdited).toHaveBeenCalledWith(true) + }) - return it("calls atom.setDocumentEdited when the active item's modified status changes", function() { - expect(atom.workspace.getActivePaneItem()).toBe(item2); - item2.insertText('a'); - advanceClock(item2.getBuffer().getStoppedChangingDelay()); + return it("calls atom.setDocumentEdited when the active item's modified status changes", function () { + expect(atom.workspace.getActivePaneItem()).toBe(item2) + item2.insertText('a') + advanceClock(item2.getBuffer().getStoppedChangingDelay()) - expect(item2.isModified()).toBe(true); - expect(setDocumentEdited).toHaveBeenCalledWith(true); + expect(item2.isModified()).toBe(true) + expect(setDocumentEdited).toHaveBeenCalledWith(true) - item2.undo(); - advanceClock(item2.getBuffer().getStoppedChangingDelay()); + item2.undo() + advanceClock(item2.getBuffer().getStoppedChangingDelay()) - expect(item2.isModified()).toBe(false); - return expect(setDocumentEdited).toHaveBeenCalledWith(false); - }); - }); + expect(item2.isModified()).toBe(false) + return expect(setDocumentEdited).toHaveBeenCalledWith(false) + }) + }) - describe("adding panels", function() { + describe('adding panels', function () { class TestItem {} - class TestItemElement extends HTMLElement { - constructor() {} - initialize(model) { this.model = model; return this; } - getModel() { return this.model; } - } + // Don't use ES6 classes because then we'll have to call `super()` which we can't do with + // HTMLElement + function TestItemElement () { this.constructor = TestItemElement } + function Ctor () { this.constructor = TestItemElement } + Ctor.prototype = HTMLElement.prototype + TestItemElement.prototype = new Ctor() + TestItemElement.__super__ = HTMLElement.prototype + TestItemElement.prototype.initialize = function (model) { this.model = model; return this } + TestItemElement.prototype.getModel = function () { return this.model } beforeEach(() => atom.views.addViewProvider(TestItem, model => new TestItemElement().initialize(model)) - ); + ) describe('::addLeftPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getLeftPanels().length).toBe(0); - atom.workspace.panelContainers.left.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getLeftPanels().length).toBe(0) + atom.workspace.panelContainers.left.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addLeftPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addLeftPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getLeftPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getLeftPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) describe('::addRightPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getRightPanels().length).toBe(0); - atom.workspace.panelContainers.right.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getRightPanels().length).toBe(0) + atom.workspace.panelContainers.right.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addRightPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addRightPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getRightPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getRightPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) describe('::addTopPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getTopPanels().length).toBe(0); - atom.workspace.panelContainers.top.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getTopPanels().length).toBe(0) + atom.workspace.panelContainers.top.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addTopPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addTopPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getTopPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getTopPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) describe('::addBottomPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getBottomPanels().length).toBe(0); - atom.workspace.panelContainers.bottom.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getBottomPanels().length).toBe(0) + atom.workspace.panelContainers.bottom.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addBottomPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addBottomPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getBottomPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getBottomPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) describe('::addHeaderPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getHeaderPanels().length).toBe(0); - atom.workspace.panelContainers.header.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getHeaderPanels().length).toBe(0) + atom.workspace.panelContainers.header.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addHeaderPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addHeaderPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getHeaderPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getHeaderPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) describe('::addFooterPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getFooterPanels().length).toBe(0); - atom.workspace.panelContainers.footer.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getFooterPanels().length).toBe(0) + atom.workspace.panelContainers.footer.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addFooterPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addFooterPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getFooterPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getFooterPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) describe('::addModalPanel(model)', () => - it('adds a panel to the correct panel container', function() { - let addPanelSpy; - expect(atom.workspace.getModalPanels().length).toBe(0); - atom.workspace.panelContainers.modal.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + it('adds a panel to the correct panel container', function () { + let addPanelSpy + expect(atom.workspace.getModalPanels().length).toBe(0) + atom.workspace.panelContainers.modal.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const model = new TestItem; - const panel = atom.workspace.addModalPanel({item: model}); + const model = new TestItem() + const panel = atom.workspace.addModalPanel({item: model}) - expect(panel).toBeDefined(); - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - const itemView = atom.views.getView(atom.workspace.getModalPanels()[0].getItem()); - expect(itemView instanceof TestItemElement).toBe(true); - return expect(itemView.getModel()).toBe(model); + const itemView = atom.views.getView(atom.workspace.getModalPanels()[0].getItem()) + expect(itemView instanceof TestItemElement).toBe(true) + return expect(itemView.getModel()).toBe(model) }) - ); + ) - return describe("::panelForItem(item)", () => - it("returns the panel associated with the item", function() { - const item = new TestItem; - const panel = atom.workspace.addLeftPanel({item}); + return describe('::panelForItem(item)', () => + it('returns the panel associated with the item', function () { + const item = new TestItem() + const panel = atom.workspace.addLeftPanel({item}) - const itemWithNoPanel = new TestItem; + const itemWithNoPanel = new TestItem() - expect(atom.workspace.panelForItem(item)).toBe(panel); - return expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null); + expect(atom.workspace.panelForItem(item)).toBe(panel) + return expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null) }) - ); - }); + ) + }) - describe("::scan(regex, options, callback)", () => - describe("when called with a regex", function() { - it("calls the callback with all regex results in all files in the project", function() { - const results = []; + describe('::scan(regex, options, callback)', () => + describe('when called with a regex', function () { + it('calls the callback with all regex results in all files in the project', function () { + const results = [] waitsForPromise(() => atom.workspace.scan(/(a)+/, result => results.push(result)) - ); + ) - return runs(function() { - expect(results).toHaveLength(3); - expect(results[0].filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))); - expect(results[0].matches).toHaveLength(3); + return runs(function () { + expect(results).toHaveLength(3) + expect(results[0].filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))) + expect(results[0].matches).toHaveLength(3) return expect(results[0].matches[0]).toEqual({ matchText: 'aaa', lineText: 'aaa bbb', lineTextOffset: 0, - range: [[0, 0], [0, 3]]});});}); + range: [[0, 0], [0, 3]] + }) + }) + }) - it("works with with escaped literals (like $ and ^)", function() { - const results = []; - waitsForPromise(() => atom.workspace.scan(/\$\w+/, result => results.push(result))); + it('works with with escaped literals (like $ and ^)', function () { + const results = [] + waitsForPromise(() => atom.workspace.scan(/\$\w+/, result => results.push(result))) - return runs(function() { - expect(results.length).toBe(1); + return runs(function () { + expect(results.length).toBe(1) - const {filePath, matches} = results[0]; - expect(filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))); - expect(matches).toHaveLength(1); + const {filePath, matches} = results[0] + expect(filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))) + expect(matches).toHaveLength(1) return expect(matches[0]).toEqual({ matchText: '$bill', lineText: 'dollar$bill', lineTextOffset: 0, - range: [[2, 6], [2, 11]]});});}); - - it("works on evil filenames", function() { - atom.config.set('core.excludeVcsIgnoredPaths', false); - platform.generateEvilFiles(); - atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')]); - const paths = []; - let matches = []; - waitsForPromise(() => - atom.workspace.scan(/evil/, function(result) { - paths.push(result.filePath); - return matches = matches.concat(result.matches); + range: [[2, 6], [2, 11]] }) - ); + }) + }) - return runs(function() { - _.each(matches, m => expect(m.matchText).toEqual('evil')); + it('works on evil filenames', function () { + atom.config.set('core.excludeVcsIgnoredPaths', false) + platform.generateEvilFiles() + atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')]) + const paths = [] + let matches = [] + waitsForPromise(() => + atom.workspace.scan(/evil/, function (result) { + paths.push(result.filePath) + return (matches = matches.concat(result.matches)) + }) + ) + + return runs(function () { + _.each(matches, m => expect(m.matchText).toEqual('evil')) if (platform.isWindows()) { - expect(paths.length).toBe(3); - expect(paths[0]).toMatch(/a_file_with_utf8.txt$/); - expect(paths[1]).toMatch(/file with spaces.txt$/); - return expect(path.basename(paths[2])).toBe("utfa\u0306.md"); + expect(paths.length).toBe(3) + expect(paths[0]).toMatch(/a_file_with_utf8.txt$/) + expect(paths[1]).toMatch(/file with spaces.txt$/) + return expect(path.basename(paths[2])).toBe('utfa\u0306.md') } else { - expect(paths.length).toBe(5); - expect(paths[0]).toMatch(/a_file_with_utf8.txt$/); - expect(paths[1]).toMatch(/file with spaces.txt$/); - expect(paths[2]).toMatch(/goddam\nnewlines$/m); - expect(paths[3]).toMatch(/quote".txt$/m); - return expect(path.basename(paths[4])).toBe("utfa\u0306.md"); + expect(paths.length).toBe(5) + expect(paths[0]).toMatch(/a_file_with_utf8.txt$/) + expect(paths[1]).toMatch(/file with spaces.txt$/) + expect(paths[2]).toMatch(/goddam\nnewlines$/m) + expect(paths[3]).toMatch(/quote".txt$/m) + return expect(path.basename(paths[4])).toBe('utfa\u0306.md') } - }); - }); + }) + }) - it("ignores case if the regex includes the `i` flag", function() { - const results = []; - waitsForPromise(() => atom.workspace.scan(/DOLLAR/i, result => results.push(result))); + it('ignores case if the regex includes the `i` flag', function () { + const results = [] + waitsForPromise(() => atom.workspace.scan(/DOLLAR/i, result => results.push(result))) - return runs(() => expect(results).toHaveLength(1)); - }); + return runs(() => expect(results).toHaveLength(1)) + }) - describe("when the core.excludeVcsIgnoredPaths config is truthy", function() { - let [projectPath, ignoredPath] = Array.from([]); + describe('when the core.excludeVcsIgnoredPaths config is truthy', function () { + let [projectPath, ignoredPath] = Array.from([]) - beforeEach(function() { - const sourceProjectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir'); - projectPath = path.join(temp.mkdirSync("atom")); + beforeEach(function () { + const sourceProjectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir') + projectPath = path.join(temp.mkdirSync('atom')) - const writerStream = fstream.Writer(projectPath); - fstream.Reader(sourceProjectPath).pipe(writerStream); + const writerStream = fstream.Writer(projectPath) + fstream.Reader(sourceProjectPath).pipe(writerStream) - waitsFor(function(done) { - writerStream.on('close', done); - return writerStream.on('error', done); - }); + waitsFor(function (done) { + writerStream.on('close', done) + return writerStream.on('error', done) + }) - return runs(function() { - fs.rename(path.join(projectPath, 'git.git'), path.join(projectPath, '.git')); - ignoredPath = path.join(projectPath, 'ignored.txt'); - return fs.writeFileSync(ignoredPath, 'this match should not be included'); - }); - }); + return runs(function () { + fs.rename(path.join(projectPath, 'git.git'), path.join(projectPath, '.git')) + ignoredPath = path.join(projectPath, 'ignored.txt') + return fs.writeFileSync(ignoredPath, 'this match should not be included') + }) + }) - afterEach(function() { - if (fs.existsSync(projectPath)) { return fs.removeSync(projectPath); } - }); + afterEach(function () { + if (fs.existsSync(projectPath)) { return fs.removeSync(projectPath) } + }) - return it("excludes ignored files", function() { - atom.project.setPaths([projectPath]); - atom.config.set('core.excludeVcsIgnoredPaths', true); - const resultHandler = jasmine.createSpy("result found"); + return it('excludes ignored files', function () { + atom.project.setPaths([projectPath]) + atom.config.set('core.excludeVcsIgnoredPaths', true) + const resultHandler = jasmine.createSpy('result found') waitsForPromise(() => atom.workspace.scan(/match/, results => resultHandler()) - ); + ) - return runs(() => expect(resultHandler).not.toHaveBeenCalled()); - }); - }); + return runs(() => expect(resultHandler).not.toHaveBeenCalled()) + }) + }) - it("includes only files when a directory filter is specified", function() { - const projectPath = path.join(path.join(__dirname, 'fixtures', 'dir')); - atom.project.setPaths([projectPath]); + it('includes only files when a directory filter is specified', function () { + const projectPath = path.join(path.join(__dirname, 'fixtures', 'dir')) + atom.project.setPaths([projectPath]) - const filePath = path.join(projectPath, 'a-dir', 'oh-git'); + const filePath = path.join(projectPath, 'a-dir', 'oh-git') - const paths = []; - let matches = []; + const paths = [] + let matches = [] waitsForPromise(() => - atom.workspace.scan(/aaa/, {paths: [`a-dir${path.sep}`]}, function(result) { - paths.push(result.filePath); - return matches = matches.concat(result.matches); + atom.workspace.scan(/aaa/, {paths: [`a-dir${path.sep}`]}, function (result) { + paths.push(result.filePath) + return (matches = matches.concat(result.matches)) }) - ); + ) - return runs(function() { - expect(paths.length).toBe(1); - expect(paths[0]).toBe(filePath); - return expect(matches.length).toBe(1); - }); - }); + return runs(function () { + expect(paths.length).toBe(1) + expect(paths[0]).toBe(filePath) + return expect(matches.length).toBe(1) + }) + }) - it("includes files and folders that begin with a '.'", function() { - const projectPath = temp.mkdirSync('atom-spec-workspace'); - const filePath = path.join(projectPath, '.text'); - fs.writeFileSync(filePath, 'match this'); - atom.project.setPaths([projectPath]); - const paths = []; - let matches = []; + it("includes files and folders that begin with a '.'", function () { + const projectPath = temp.mkdirSync('atom-spec-workspace') + const filePath = path.join(projectPath, '.text') + fs.writeFileSync(filePath, 'match this') + atom.project.setPaths([projectPath]) + const paths = [] + let matches = [] waitsForPromise(() => - atom.workspace.scan(/match this/, function(result) { - paths.push(result.filePath); - return matches = matches.concat(result.matches); + atom.workspace.scan(/match this/, function (result) { + paths.push(result.filePath) + return (matches = matches.concat(result.matches)) }) - ); + ) - return runs(function() { - expect(paths.length).toBe(1); - expect(paths[0]).toBe(filePath); - return expect(matches.length).toBe(1); - }); - }); + return runs(function () { + expect(paths.length).toBe(1) + expect(paths[0]).toBe(filePath) + return expect(matches.length).toBe(1) + }) + }) - it("excludes values in core.ignoredNames", function() { - const ignoredNames = atom.config.get("core.ignoredNames"); - ignoredNames.push("a"); - atom.config.set("core.ignoredNames", ignoredNames); + it('excludes values in core.ignoredNames', function () { + const ignoredNames = atom.config.get('core.ignoredNames') + ignoredNames.push('a') + atom.config.set('core.ignoredNames', ignoredNames) - const resultHandler = jasmine.createSpy("result found"); + const resultHandler = jasmine.createSpy('result found') waitsForPromise(() => atom.workspace.scan(/dollar/, results => resultHandler()) - ); + ) - return runs(() => expect(resultHandler).not.toHaveBeenCalled()); - }); + return runs(() => expect(resultHandler).not.toHaveBeenCalled()) + }) - it("scans buffer contents if the buffer is modified", function() { - let editor = null; - const results = []; + it('scans buffer contents if the buffer is modified', function () { + let editor = null + const results = [] waitsForPromise(() => - atom.workspace.open('a').then(function(o) { - editor = o; - return editor.setText("Elephant"); + atom.workspace.open('a').then(function (o) { + editor = o + return editor.setText('Elephant') }) - ); + ) - waitsForPromise(() => atom.workspace.scan(/a|Elephant/, result => results.push(result))); + waitsForPromise(() => atom.workspace.scan(/a|Elephant/, result => results.push(result))) - return runs(function() { - expect(results).toHaveLength(3); - const resultForA = _.find(results, ({filePath}) => path.basename(filePath) === 'a'); - expect(resultForA.matches).toHaveLength(1); - return expect(resultForA.matches[0].matchText).toBe('Elephant'); - }); - }); + return runs(function () { + expect(results).toHaveLength(3) + const resultForA = _.find(results, ({filePath}) => path.basename(filePath) === 'a') + expect(resultForA.matches).toHaveLength(1) + return expect(resultForA.matches[0].matchText).toBe('Elephant') + }) + }) - it("ignores buffers outside the project", function() { - let editor = null; - const results = []; + it('ignores buffers outside the project', function () { + let editor = null + const results = [] waitsForPromise(() => - atom.workspace.open(temp.openSync().path).then(function(o) { - editor = o; - return editor.setText("Elephant"); + atom.workspace.open(temp.openSync().path).then(function (o) { + editor = o + return editor.setText('Elephant') }) - ); + ) - waitsForPromise(() => atom.workspace.scan(/Elephant/, result => results.push(result))); + waitsForPromise(() => atom.workspace.scan(/Elephant/, result => results.push(result))) - return runs(() => expect(results).toHaveLength(0)); - }); + return runs(() => expect(results).toHaveLength(0)) + }) - return describe("when the project has multiple root directories", function() { - let [dir1, dir2, file1, file2] = Array.from([]); + return describe('when the project has multiple root directories', function () { + let [dir1, dir2, file1, file2] = Array.from([]) - beforeEach(function() { - [dir1] = Array.from(atom.project.getPaths()); - file1 = path.join(dir1, "a-dir", "oh-git"); + beforeEach(function () { + [dir1] = Array.from(atom.project.getPaths()) + file1 = path.join(dir1, 'a-dir', 'oh-git') - dir2 = temp.mkdirSync("a-second-dir"); - const aDir2 = path.join(dir2, "a-dir"); - file2 = path.join(aDir2, "a-file"); - fs.mkdirSync(aDir2); - fs.writeFileSync(file2, "ccc aaaa"); + dir2 = temp.mkdirSync('a-second-dir') + const aDir2 = path.join(dir2, 'a-dir') + file2 = path.join(aDir2, 'a-file') + fs.mkdirSync(aDir2) + fs.writeFileSync(file2, 'ccc aaaa') - return atom.project.addPath(dir2); - }); + return atom.project.addPath(dir2) + }) - it("searches matching files in all of the project's root directories", function() { - const resultPaths = []; + it("searches matching files in all of the project's root directories", function () { + const resultPaths = [] waitsForPromise(() => atom.workspace.scan(/aaaa/, ({filePath}) => resultPaths.push(filePath)) - ); + ) - return runs(() => expect(resultPaths.sort()).toEqual([file1, file2].sort())); - }); + return runs(() => expect(resultPaths.sort()).toEqual([file1, file2].sort())) + }) - describe("when an inclusion path starts with the basename of a root directory", () => - it("interprets the inclusion path as starting from that directory", function() { - waitsForPromise(function() { - const resultPaths = []; + describe('when an inclusion path starts with the basename of a root directory', () => + it('interprets the inclusion path as starting from that directory', function () { + waitsForPromise(function () { + const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: ["dir"]}, function({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file1])); - }); + .scan(/aaaa/, {paths: ['dir']}, function ({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + }) + .then(() => expect(resultPaths).toEqual([file1])) + }) - waitsForPromise(function() { - const resultPaths = []; + waitsForPromise(function () { + const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: [path.join("dir", "a-dir")]}, function({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file1])); - }); + .scan(/aaaa/, {paths: [path.join('dir', 'a-dir')]}, function ({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + }) + .then(() => expect(resultPaths).toEqual([file1])) + }) - waitsForPromise(function() { - const resultPaths = []; + waitsForPromise(function () { + const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: [path.basename(dir2)]}, function({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file2])); - }); + .scan(/aaaa/, {paths: [path.basename(dir2)]}, function ({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + }) + .then(() => expect(resultPaths).toEqual([file2])) + }) - return waitsForPromise(function() { - const resultPaths = []; + return waitsForPromise(function () { + const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: [path.join(path.basename(dir2), "a-dir")]}, function({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath); }}).then(() => expect(resultPaths).toEqual([file2])); - }); + .scan(/aaaa/, {paths: [path.join(path.basename(dir2), 'a-dir')]}, function ({filePath}) { + if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + }) + .then(() => expect(resultPaths).toEqual([file2])) + }) }) - ); + ) - return describe("when a custom directory searcher is registered", function() { - let fakeSearch = null; + return describe('when a custom directory searcher is registered', function () { + let fakeSearch = null // Function that is invoked once all of the fields on fakeSearch are set. - let onFakeSearchCreated = null; + let onFakeSearchCreated = null class FakeSearch { - constructor(options) { + constructor (options) { // Note that hoisting resolve and reject in this way is generally frowned upon. - this.options = options; - this.promise = new Promise((function(resolve, reject) { - this.hoistedResolve = resolve; - this.hoistedReject = reject; - return (typeof onFakeSearchCreated === 'function' ? onFakeSearchCreated(this) : undefined); - }.bind(this))); + this.options = options + this.promise = new Promise((function (resolve, reject) { + this.hoistedResolve = resolve + this.hoistedReject = reject + return (typeof onFakeSearchCreated === 'function' ? onFakeSearchCreated(this) : undefined) + }.bind(this))) } - then(...args) { - return this.promise.then.apply(this.promise, args); + then (...args) { + return this.promise.then.apply(this.promise, args) } - cancel() { - this.cancelled = true; + cancel () { + this.cancelled = true // According to the spec for a DirectorySearcher, invoking `cancel()` should // resolve the thenable rather than reject it. - return this.hoistedResolve(); + return this.hoistedResolve() } } - beforeEach(function() { - fakeSearch = null; - onFakeSearchCreated = null; + beforeEach(function () { + fakeSearch = null + onFakeSearchCreated = null atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { - canSearchDirectory(directory) { return directory.getPath() === dir1; }, - search(directory, regex, options) { return fakeSearch = new FakeSearch(options); } - }); + canSearchDirectory (directory) { return directory.getPath() === dir1 }, + search (directory, regex, options) { return (fakeSearch = new FakeSearch(options)) } + }) - return waitsFor(() => atom.workspace.directorySearchers.length > 0); - }); + return waitsFor(() => atom.workspace.directorySearchers.length > 0) + }) - it("can override the DefaultDirectorySearcher on a per-directory basis", function() { - const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt'; - const numPathsSearchedInDir2 = 1; - const numPathsToPretendToSearchInCustomDirectorySearcher = 10; + it('can override the DefaultDirectorySearcher on a per-directory basis', function () { + const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt' + const numPathsSearchedInDir2 = 1 + const numPathsToPretendToSearchInCustomDirectorySearcher = 10 const searchResult = { filePath: foreignFilePath, matches: [ @@ -1568,410 +1627,407 @@ i = /test/; #FIXME\ lineText: 'Hello world', lineTextOffset: 0, matchText: 'Hello', - range: [[0, 0], [0, 5]], - }, + range: [[0, 0], [0, 5]] + } ] - }; - onFakeSearchCreated = function(fakeSearch) { - fakeSearch.options.didMatch(searchResult); - fakeSearch.options.didSearchPaths(numPathsToPretendToSearchInCustomDirectorySearcher); - return fakeSearch.hoistedResolve(); - }; + } + onFakeSearchCreated = function (fakeSearch) { + fakeSearch.options.didMatch(searchResult) + fakeSearch.options.didSearchPaths(numPathsToPretendToSearchInCustomDirectorySearcher) + return fakeSearch.hoistedResolve() + } - const resultPaths = []; - const onPathsSearched = jasmine.createSpy('onPathsSearched'); + const resultPaths = [] + const onPathsSearched = jasmine.createSpy('onPathsSearched') waitsForPromise(() => atom.workspace.scan(/aaaa/, {onPathsSearched}, ({filePath}) => resultPaths.push(filePath)) - ); + ) - return runs(function() { - expect(resultPaths.sort()).toEqual([foreignFilePath, file2].sort()); + return runs(function () { + expect(resultPaths.sort()).toEqual([foreignFilePath, file2].sort()) // onPathsSearched should be called once by each DirectorySearcher. The order is not // guaranteed, so we can only verify the total number of paths searched is correct // after the second call. - expect(onPathsSearched.callCount).toBe(2); + expect(onPathsSearched.callCount).toBe(2) return expect(onPathsSearched.mostRecentCall.args[0]).toBe( - numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2); - }); - }); + numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2) + }) + }) - it("can be cancelled when the object returned by scan() has its cancel() method invoked", function() { - const thenable = atom.workspace.scan(/aaaa/, function() {}); - let resultOfPromiseSearch = null; + it('can be cancelled when the object returned by scan() has its cancel() method invoked', function () { + const thenable = atom.workspace.scan(/aaaa/, function () {}) + let resultOfPromiseSearch = null - waitsFor('fakeSearch to be defined', () => fakeSearch != null); + waitsFor('fakeSearch to be defined', () => fakeSearch != null) - runs(function() { - expect(fakeSearch.cancelled).toBe(undefined); - thenable.cancel(); - return expect(fakeSearch.cancelled).toBe(true); - }); + runs(function () { + expect(fakeSearch.cancelled).toBe(undefined) + thenable.cancel() + return expect(fakeSearch.cancelled).toBe(true) + }) + waitsForPromise(() => thenable.then(promiseResult => (resultOfPromiseSearch = promiseResult))) - waitsForPromise(() => thenable.then(promiseResult => resultOfPromiseSearch = promiseResult)); + return runs(() => expect(resultOfPromiseSearch).toBe('cancelled')) + }) - return runs(() => expect(resultOfPromiseSearch).toBe('cancelled')); - }); - - return it("will have the side-effect of failing the overall search if it fails", function() { + return it('will have the side-effect of failing the overall search if it fails', function () { // This provider's search should be cancelled when the first provider fails - let cancelableSearch; - let fakeSearch2 = null; + let cancelableSearch + let fakeSearch2 = null atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { - canSearchDirectory(directory) { return directory.getPath() === dir2; }, - search(directory, regex, options) { return fakeSearch2 = new FakeSearch(options); } - }); + canSearchDirectory (directory) { return directory.getPath() === dir2 }, + search (directory, regex, options) { return (fakeSearch2 = new FakeSearch(options)) } + }) - let didReject = false; - const promise = cancelableSearch = atom.workspace.scan(/aaaa/, function() {}); - waitsFor('fakeSearch to be defined', () => fakeSearch != null); + let didReject = false + const promise = cancelableSearch = atom.workspace.scan(/aaaa/, function () {}) + waitsFor('fakeSearch to be defined', () => fakeSearch != null) - runs(() => fakeSearch.hoistedReject()); + runs(() => fakeSearch.hoistedReject()) - waitsForPromise(() => cancelableSearch.catch(() => didReject = true)); + waitsForPromise(() => cancelableSearch.catch(() => (didReject = true))) - waitsFor(done => promise.then(null, done)); + waitsFor(done => promise.then(null, done)) - return runs(function() { - expect(didReject).toBe(true); - return expect(fakeSearch2.cancelled).toBe(true); - }); - }); - }); - }); + return runs(function () { + expect(didReject).toBe(true) + return expect(fakeSearch2.cancelled).toBe(true) + }) + }) + }) + }) }) - ); // Cancels other ongoing searches + ) // Cancels other ongoing searches - describe("::replace(regex, replacementText, paths, iterator)", function() { - let [filePath, commentFilePath, sampleContent, sampleCommentContent] = Array.from([]); + describe('::replace(regex, replacementText, paths, iterator)', function () { + let [filePath, commentFilePath, sampleContent, sampleCommentContent] = Array.from([]) - beforeEach(function() { - atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('../'))]); + beforeEach(function () { + atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('../'))]) - filePath = __guard__(atom.project.getDirectories()[0], x1 => x1.resolve('sample.js')); - commentFilePath = __guard__(atom.project.getDirectories()[0], x2 => x2.resolve('sample-with-comments.js')); - sampleContent = fs.readFileSync(filePath).toString(); - return sampleCommentContent = fs.readFileSync(commentFilePath).toString(); - }); + filePath = __guard__(atom.project.getDirectories()[0], x1 => x1.resolve('sample.js')) + commentFilePath = __guard__(atom.project.getDirectories()[0], x2 => x2.resolve('sample-with-comments.js')) + sampleContent = fs.readFileSync(filePath).toString() + return (sampleCommentContent = fs.readFileSync(commentFilePath).toString()) + }) - afterEach(function() { - fs.writeFileSync(filePath, sampleContent); - return fs.writeFileSync(commentFilePath, sampleCommentContent); - }); + afterEach(function () { + fs.writeFileSync(filePath, sampleContent) + return fs.writeFileSync(commentFilePath, sampleCommentContent) + }) describe("when a file doesn't exist", () => - it("calls back with an error", function() { - const errors = []; - const missingPath = path.resolve('/not-a-file.js'); - expect(fs.existsSync(missingPath)).toBeFalsy(); + it('calls back with an error', function () { + const errors = [] + const missingPath = path.resolve('/not-a-file.js') + expect(fs.existsSync(missingPath)).toBeFalsy() waitsForPromise(() => atom.workspace.replace(/items/gi, 'items', [missingPath], (result, error) => errors.push(error)) - ); + ) - return runs(function() { - expect(errors).toHaveLength(1); - return expect(errors[0].path).toBe(missingPath); - }); + return runs(function () { + expect(errors).toHaveLength(1) + return expect(errors[0].path).toBe(missingPath) + }) }) - ); + ) - describe("when called with unopened files", () => - it("replaces properly", function() { - const results = []; + describe('when called with unopened files', () => + it('replaces properly', function () { + const results = [] waitsForPromise(() => atom.workspace.replace(/items/gi, 'items', [filePath], result => results.push(result)) - ); + ) - return runs(function() { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - return expect(results[0].replacements).toBe(6); - }); + return runs(function () { + expect(results).toHaveLength(1) + expect(results[0].filePath).toBe(filePath) + return expect(results[0].replacements).toBe(6) + }) }) - ); + ) - return describe("when a buffer is already open", function() { - it("replaces properly and saves when not modified", function() { - let editor = null; - const results = []; + return describe('when a buffer is already open', function () { + it('replaces properly and saves when not modified', function () { + let editor = null + const results = [] - waitsForPromise(() => atom.workspace.open('sample.js').then(o => editor = o)); + waitsForPromise(() => atom.workspace.open('sample.js').then(o => (editor = o))) - runs(() => expect(editor.isModified()).toBeFalsy()); + runs(() => expect(editor.isModified()).toBeFalsy()) waitsForPromise(() => atom.workspace.replace(/items/gi, 'items', [filePath], result => results.push(result)) - ); + ) - return runs(function() { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(6); + return runs(function () { + expect(results).toHaveLength(1) + expect(results[0].filePath).toBe(filePath) + expect(results[0].replacements).toBe(6) - return expect(editor.isModified()).toBeFalsy(); - }); - }); + return expect(editor.isModified()).toBeFalsy() + }) + }) - it("does not replace when the path is not specified", function() { - let editor = null; - const results = []; + it('does not replace when the path is not specified', function () { + const results = [] - waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => editor = o)); + waitsForPromise(() => atom.workspace.open('sample-with-comments.js')) waitsForPromise(() => atom.workspace.replace(/items/gi, 'items', [commentFilePath], result => results.push(result)) - ); + ) - return runs(function() { - expect(results).toHaveLength(1); - return expect(results[0].filePath).toBe(commentFilePath); - }); - }); + return runs(function () { + expect(results).toHaveLength(1) + return expect(results[0].filePath).toBe(commentFilePath) + }) + }) - return it("does NOT save when modified", function() { - let editor = null; - const results = []; + return it('does NOT save when modified', function () { + let editor = null + const results = [] - waitsForPromise(() => atom.workspace.open('sample.js').then(o => editor = o)); + waitsForPromise(() => atom.workspace.open('sample.js').then(o => (editor = o))) - runs(function() { - editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg'); - return expect(editor.isModified()).toBeTruthy(); - }); + runs(function () { + editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg') + return expect(editor.isModified()).toBeTruthy() + }) waitsForPromise(() => atom.workspace.replace(/items/gi, 'okthen', [filePath], result => results.push(result)) - ); + ) - return runs(function() { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(6); + return runs(function () { + expect(results).toHaveLength(1) + expect(results[0].filePath).toBe(filePath) + expect(results[0].replacements).toBe(6) - return expect(editor.isModified()).toBeTruthy(); - }); - }); - }); - }); - - describe("::saveActivePaneItem()", function() { - let editor = null; - beforeEach(() => - waitsForPromise(() => atom.workspace.open('sample.js').then(o => editor = o)) - ); - - return describe("when there is an error", function() { - it("emits a warning notification when the file cannot be saved", function() { - let addedSpy; - spyOn(editor, 'save').andCallFake(function() { - throw new Error("'/some/file' is a directory"); - }); - - atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); - atom.workspace.saveActivePaneItem(); - expect(addedSpy).toHaveBeenCalled(); - return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning'); - }); - - it("emits a warning notification when the directory cannot be written to", function() { - let addedSpy; - spyOn(editor, 'save').andCallFake(function() { - throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'"); - }); - - atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); - atom.workspace.saveActivePaneItem(); - expect(addedSpy).toHaveBeenCalled(); - return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning'); - }); - - it("emits a warning notification when the user does not have permission", function() { - let addedSpy; - spyOn(editor, 'save').andCallFake(function() { - const error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'"); - error.code = 'EACCES'; - error.path = '/Some/dir/and-a-file.js'; - throw error; - }); - - atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); - atom.workspace.saveActivePaneItem(); - expect(addedSpy).toHaveBeenCalled(); - return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning'); - }); - - it("emits a warning notification when the operation is not permitted", () => - spyOn(editor, 'save').andCallFake(function() { - const error = new Error("EPERM, operation not permitted '/Some/dir/and-a-file.js'"); - error.code = 'EPERM'; - error.path = '/Some/dir/and-a-file.js'; - throw error; + return expect(editor.isModified()).toBeTruthy() }) - ); + }) + }) + }) - it("emits a warning notification when the file is already open by another app", function() { - let addedSpy; - spyOn(editor, 'save').andCallFake(function() { - const error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'"); - error.code = 'EBUSY'; - error.path = '/Some/dir/and-a-file.js'; - throw error; - }); + describe('::saveActivePaneItem()', function () { + let editor = null + beforeEach(() => + waitsForPromise(() => atom.workspace.open('sample.js').then(o => (editor = o))) + ) - atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); - atom.workspace.saveActivePaneItem(); - expect(addedSpy).toHaveBeenCalled(); + return describe('when there is an error', function () { + it('emits a warning notification when the file cannot be saved', function () { + let addedSpy + spyOn(editor, 'save').andCallFake(function () { + throw new Error("'/some/file' is a directory") + }) - const notificaiton = addedSpy.mostRecentCall.args[0]; - expect(notificaiton.getType()).toBe('warning'); - return expect(notificaiton.getMessage()).toContain('Unable to save'); - }); + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) + atom.workspace.saveActivePaneItem() + expect(addedSpy).toHaveBeenCalled() + return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') + }) - it("emits a warning notification when the file system is read-only", function() { - let addedSpy; - spyOn(editor, 'save').andCallFake(function() { - const error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'"); - error.code = 'EROFS'; - error.path = '/Some/dir/and-a-file.js'; - throw error; - }); + it('emits a warning notification when the directory cannot be written to', function () { + let addedSpy + spyOn(editor, 'save').andCallFake(function () { + throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'") + }) - atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()); - atom.workspace.saveActivePaneItem(); - expect(addedSpy).toHaveBeenCalled(); + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) + atom.workspace.saveActivePaneItem() + expect(addedSpy).toHaveBeenCalled() + return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') + }) - const notification = addedSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - return expect(notification.getMessage()).toContain('Unable to save'); - }); + it('emits a warning notification when the user does not have permission', function () { + let addedSpy + spyOn(editor, 'save').andCallFake(function () { + const error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'") + error.code = 'EACCES' + error.path = '/Some/dir/and-a-file.js' + throw error + }) - return it("emits a warning notification when the file cannot be saved", function() { - spyOn(editor, 'save').andCallFake(function() { - throw new Error("no one knows"); - }); + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) + atom.workspace.saveActivePaneItem() + expect(addedSpy).toHaveBeenCalled() + return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') + }) - const save = () => atom.workspace.saveActivePaneItem(); - return expect(save).toThrow(); - }); - }); - }); + it('emits a warning notification when the operation is not permitted', () => + spyOn(editor, 'save').andCallFake(function () { + const error = new Error("EPERM, operation not permitted '/Some/dir/and-a-file.js'") + error.code = 'EPERM' + error.path = '/Some/dir/and-a-file.js' + throw error + }) + ) - describe("::closeActivePaneItemOrEmptyPaneOrWindow", function() { - beforeEach(function() { - spyOn(atom, 'close'); - return waitsForPromise(() => atom.workspace.open()); - }); + it('emits a warning notification when the file is already open by another app', function () { + let addedSpy + spyOn(editor, 'save').andCallFake(function () { + const error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'") + error.code = 'EBUSY' + error.path = '/Some/dir/and-a-file.js' + throw error + }) - return it("closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane", function() { - atom.config.set('core.destroyEmptyPanes', false); + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) + atom.workspace.saveActivePaneItem() + expect(addedSpy).toHaveBeenCalled() - const pane1 = atom.workspace.getActivePane(); - const pane2 = pane1.splitRight({copyActiveItem: true}); + const notificaiton = addedSpy.mostRecentCall.args[0] + expect(notificaiton.getType()).toBe('warning') + return expect(notificaiton.getMessage()).toContain('Unable to save') + }) - expect(atom.workspace.getPanes().length).toBe(2); - expect(pane2.getItems().length).toBe(1); - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + it('emits a warning notification when the file system is read-only', function () { + let addedSpy + spyOn(editor, 'save').andCallFake(function () { + const error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'") + error.code = 'EROFS' + error.path = '/Some/dir/and-a-file.js' + throw error + }) - expect(atom.workspace.getPanes().length).toBe(2); - expect(pane2.getItems().length).toBe(0); + atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) + atom.workspace.saveActivePaneItem() + expect(addedSpy).toHaveBeenCalled() - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + const notification = addedSpy.mostRecentCall.args[0] + expect(notification.getType()).toBe('warning') + return expect(notification.getMessage()).toContain('Unable to save') + }) - expect(atom.workspace.getPanes().length).toBe(1); - expect(pane1.getItems().length).toBe(1); + return it('emits a warning notification when the file cannot be saved', function () { + spyOn(editor, 'save').andCallFake(function () { + throw new Error('no one knows') + }) - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); - expect(atom.workspace.getPanes().length).toBe(1); - expect(pane1.getItems().length).toBe(0); + const save = () => atom.workspace.saveActivePaneItem() + return expect(save).toThrow() + }) + }) + }) - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); - expect(atom.workspace.getPanes().length).toBe(1); + describe('::closeActivePaneItemOrEmptyPaneOrWindow', function () { + beforeEach(function () { + spyOn(atom, 'close') + return waitsForPromise(() => atom.workspace.open()) + }) - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); - return expect(atom.close).toHaveBeenCalled(); - }); - }); + return it('closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane', function () { + atom.config.set('core.destroyEmptyPanes', false) - describe("when the core.allowPendingPaneItems option is falsey", () => - it("does not open item with `pending: true` option as pending", function() { - let pane = null; - atom.config.set('core.allowPendingPaneItems', false); + const pane1 = atom.workspace.getActivePane() + const pane2 = pane1.splitRight({copyActiveItem: true}) + + expect(atom.workspace.getPanes().length).toBe(2) + expect(pane2.getItems().length).toBe(1) + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + + expect(atom.workspace.getPanes().length).toBe(2) + expect(pane2.getItems().length).toBe(0) + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + + expect(atom.workspace.getPanes().length).toBe(1) + expect(pane1.getItems().length).toBe(1) + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + expect(atom.workspace.getPanes().length).toBe(1) + expect(pane1.getItems().length).toBe(0) + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + expect(atom.workspace.getPanes().length).toBe(1) + + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + return expect(atom.close).toHaveBeenCalled() + }) + }) + + describe('when the core.allowPendingPaneItems option is falsey', () => + it('does not open item with `pending: true` option as pending', function () { + let pane = null + atom.config.set('core.allowPendingPaneItems', false) waitsForPromise(() => - atom.workspace.open('sample.js', {pending: true}).then(() => pane = atom.workspace.getActivePane()) - ); + atom.workspace.open('sample.js', {pending: true}).then(() => (pane = atom.workspace.getActivePane())) + ) - return runs(() => expect(pane.getPendingItem()).toBeFalsy()); + return runs(() => expect(pane.getPendingItem()).toBeFalsy()) }) - ); + ) - describe("grammar activation", () => - it("notifies the workspace of which grammar is used", function() { - const editor = null; - atom.packages.triggerDeferredActivationHooks(); + describe('grammar activation', () => + it('notifies the workspace of which grammar is used', function () { + atom.packages.triggerDeferredActivationHooks() - const javascriptGrammarUsed = jasmine.createSpy('js grammar used'); - const rubyGrammarUsed = jasmine.createSpy('ruby grammar used'); - const cGrammarUsed = jasmine.createSpy('c grammar used'); + const javascriptGrammarUsed = jasmine.createSpy('js grammar used') + const rubyGrammarUsed = jasmine.createSpy('ruby grammar used') + const cGrammarUsed = jasmine.createSpy('c grammar used') - atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', javascriptGrammarUsed); - atom.packages.onDidTriggerActivationHook('language-ruby:grammar-used', rubyGrammarUsed); - atom.packages.onDidTriggerActivationHook('language-c:grammar-used', cGrammarUsed); + atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', javascriptGrammarUsed) + atom.packages.onDidTriggerActivationHook('language-ruby:grammar-used', rubyGrammarUsed) + atom.packages.onDidTriggerActivationHook('language-c:grammar-used', cGrammarUsed) - waitsForPromise(() => atom.packages.activatePackage('language-ruby')); - waitsForPromise(() => atom.packages.activatePackage('language-javascript')); - waitsForPromise(() => atom.packages.activatePackage('language-c')); - waitsForPromise(() => atom.workspace.open('sample-with-comments.js')); + waitsForPromise(() => atom.packages.activatePackage('language-ruby')) + waitsForPromise(() => atom.packages.activatePackage('language-javascript')) + waitsForPromise(() => atom.packages.activatePackage('language-c')) + waitsForPromise(() => atom.workspace.open('sample-with-comments.js')) - return runs(function() { + return runs(function () { // Hooks are triggered when opening new editors - expect(javascriptGrammarUsed).toHaveBeenCalled(); + expect(javascriptGrammarUsed).toHaveBeenCalled() // Hooks are triggered when changing existing editors grammars - atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.c')); - expect(cGrammarUsed).toHaveBeenCalled(); + atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.c')) + expect(cGrammarUsed).toHaveBeenCalled() // Hooks are triggered when editors are added in other ways. - atom.workspace.getActivePane().splitRight({copyActiveItem: true}); - atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.ruby')); - return expect(rubyGrammarUsed).toHaveBeenCalled(); - }); + atom.workspace.getActivePane().splitRight({copyActiveItem: true}) + atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.ruby')) + return expect(rubyGrammarUsed).toHaveBeenCalled() + }) }) - ); + ) - describe(".checkoutHeadRevision()", function() { - let editor = null; - beforeEach(function() { - atom.config.set("editor.confirmCheckoutHeadRevision", false); + describe('.checkoutHeadRevision()', function () { + let editor = null + beforeEach(function () { + atom.config.set('editor.confirmCheckoutHeadRevision', false) - return waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => editor = o)); - }); + return waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => (editor = o))) + }) - it("reverts to the version of its file checked into the project repository", function() { - editor.setCursorBufferPosition([0, 0]); - editor.insertText("---\n"); - expect(editor.lineTextForBufferRow(0)).toBe("---"); + it('reverts to the version of its file checked into the project repository', function () { + editor.setCursorBufferPosition([0, 0]) + editor.insertText('---\n') + expect(editor.lineTextForBufferRow(0)).toBe('---') - waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)); + waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)) - return runs(() => expect(editor.lineTextForBufferRow(0)).toBe("")); - }); + return runs(() => expect(editor.lineTextForBufferRow(0)).toBe('')) + }) return describe("when there's no repository for the editor's file", () => - it("doesn't do anything", function() { - editor = new TextEditor; - editor.setText("stuff"); - atom.workspace.checkoutHeadRevision(editor); + it("doesn't do anything", function () { + editor = new TextEditor() + editor.setText('stuff') + atom.workspace.checkoutHeadRevision(editor) - return waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)); + return waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)) }) - ); - }); + ) + }) - return escapeStringRegex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); -}); + return (escapeStringRegex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')) +}) -function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +function __guard__ (value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined } diff --git a/src/workspace.js b/src/workspace.js index 612224da3..7ffe8f387 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -1,17 +1,16 @@ -let Workspace; -const _ = require('underscore-plus'); -const url = require('url'); -const path = require('path'); -const {Emitter, Disposable, CompositeDisposable} = require('event-kit'); -const fs = require('fs-plus'); -const {Directory} = require('pathwatcher'); -const DefaultDirectorySearcher = require('./default-directory-searcher'); -const Model = require('./model'); -const TextEditor = require('./text-editor'); -const PaneContainer = require('./pane-container'); -const Panel = require('./panel'); -const PanelContainer = require('./panel-container'); -const Task = require('./task'); +const _ = require('underscore-plus') +const url = require('url') +const path = require('path') +const {Emitter, Disposable, CompositeDisposable} = require('event-kit') +const fs = require('fs-plus') +const {Directory} = require('pathwatcher') +const DefaultDirectorySearcher = require('./default-directory-searcher') +const Model = require('./model') +const TextEditor = require('./text-editor') +const PaneContainer = require('./pane-container') +const Panel = require('./panel') +const PanelContainer = require('./panel-container') +const Task = require('./task') // Essential: Represents the state of the user interface for the entire window. // An instance of this class is available via the `atom.workspace` global. @@ -22,35 +21,35 @@ const Task = require('./task'); // // * `editor` {TextEditor} the new editor // -module.exports = -Workspace = class Workspace extends Model { - constructor(params) { - this.updateWindowTitle = this.updateWindowTitle.bind(this); - this.updateDocumentEdited = this.updateDocumentEdited.bind(this); - this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this); - super(...arguments); +module.exports = class Workspace extends Model { + constructor (params) { + super(...arguments) - ({ + this.updateWindowTitle = this.updateWindowTitle.bind(this) + this.updateDocumentEdited = this.updateDocumentEdited.bind(this) + this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this) + + ;({ packageManager: this.packageManager, config: this.config, project: this.project, grammarRegistry: this.grammarRegistry, notificationManager: this.notificationManager, viewRegistry: this.viewRegistry, grammarRegistry: this.grammarRegistry, applicationDelegate: this.applicationDelegate, assert: this.assert, deserializerManager: this.deserializerManager, textEditorRegistry: this.textEditorRegistry - } = params); + } = params) - this.emitter = new Emitter; - this.openers = []; - this.destroyedItemURIs = []; + this.emitter = new Emitter() + this.openers = [] + this.destroyedItemURIs = [] - this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}); - this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem); + this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}) + this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem) - this.defaultDirectorySearcher = new DefaultDirectorySearcher(); - this.consumeServices(this.packageManager); + this.defaultDirectorySearcher = new DefaultDirectorySearcher() + this.consumeServices(this.packageManager) // One cannot simply .bind here since it could be used as a component with // Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always // the newly created object. - const realThis = this; - this.buildTextEditor = function() { return Workspace.prototype.buildTextEditor.apply(realThis, arguments); }; + const realThis = this + this.buildTextEditor = function () { return Workspace.prototype.buildTextEditor.apply(realThis, arguments) } this.panelContainers = { top: new PanelContainer({location: 'top'}), @@ -60,21 +59,21 @@ Workspace = class Workspace extends Model { header: new PanelContainer({location: 'header'}), footer: new PanelContainer({location: 'footer'}), modal: new PanelContainer({location: 'modal'}) - }; + } - this.subscribeToEvents(); + this.subscribeToEvents() } - reset(packageManager) { - this.packageManager = packageManager; - this.emitter.dispose(); - this.emitter = new Emitter; + reset (packageManager) { + this.packageManager = packageManager + this.emitter.dispose() + this.emitter = new Emitter() - this.paneContainer.destroy(); - for (let panelContainer of this.panelContainers) { panelContainer.destroy(); } + this.paneContainer.destroy() + for (let panelContainer of this.panelContainers) { panelContainer.destroy() } - this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}); - this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem); + this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}) + this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem) this.panelContainers = { top: new PanelContainer({location: 'top'}), @@ -84,115 +83,115 @@ Workspace = class Workspace extends Model { header: new PanelContainer({location: 'header'}), footer: new PanelContainer({location: 'footer'}), modal: new PanelContainer({location: 'modal'}) - }; + } - this.originalFontSize = null; - this.openers = []; - this.destroyedItemURIs = []; - return this.consumeServices(this.packageManager); + this.originalFontSize = null + this.openers = [] + this.destroyedItemURIs = [] + return this.consumeServices(this.packageManager) } - subscribeToEvents() { - this.subscribeToActiveItem(); - this.subscribeToFontSize(); - return this.subscribeToAddedItems(); + subscribeToEvents () { + this.subscribeToActiveItem() + this.subscribeToFontSize() + return this.subscribeToAddedItems() } - consumeServices({serviceHub}) { - this.directorySearchers = []; + consumeServices ({serviceHub}) { + this.directorySearchers = [] return serviceHub.consume( 'atom.directory-searcher', '^0.1.0', - provider => this.directorySearchers.unshift(provider)); + provider => this.directorySearchers.unshift(provider)) } // Called by the Serializable mixin during serialization. - serialize() { + serialize () { return { deserializer: 'Workspace', paneContainer: this.paneContainer.serialize(), packagesWithActiveGrammars: this.getPackageNamesWithActiveGrammars(), destroyedItemURIs: this.destroyedItemURIs.slice() - }; + } } - deserialize(state, deserializerManager) { + deserialize (state, deserializerManager) { for (let packageName of state.packagesWithActiveGrammars != null ? state.packagesWithActiveGrammars : []) { - __guard__(this.packageManager.getLoadedPackage(packageName), x => x.loadGrammarsSync()); + __guard__(this.packageManager.getLoadedPackage(packageName), x => x.loadGrammarsSync()) } if (state.destroyedItemURIs != null) { - this.destroyedItemURIs = state.destroyedItemURIs; + this.destroyedItemURIs = state.destroyedItemURIs } - return this.paneContainer.deserialize(state.paneContainer, deserializerManager); + return this.paneContainer.deserialize(state.paneContainer, deserializerManager) } - getPackageNamesWithActiveGrammars() { - const packageNames = []; - var addGrammar = ({includedGrammarScopes, packageName}={}) => { - if (!packageName) { return; } + getPackageNamesWithActiveGrammars () { + const packageNames = [] + var addGrammar = ({includedGrammarScopes, packageName} = {}) => { + if (!packageName) { return } // Prevent cycles - if (packageNames.indexOf(packageName) !== -1) { return; } + if (packageNames.indexOf(packageName) !== -1) { return } - packageNames.push(packageName); + packageNames.push(packageName) for (let scopeName of includedGrammarScopes != null ? includedGrammarScopes : []) { - addGrammar(this.grammarRegistry.grammarForScopeName(scopeName)); + addGrammar(this.grammarRegistry.grammarForScopeName(scopeName)) } - }; + } - const editors = this.getTextEditors(); - for (let editor of editors) { addGrammar(editor.getGrammar()); } + const editors = this.getTextEditors() + for (let editor of editors) { addGrammar(editor.getGrammar()) } if (editors.length > 0) { for (let grammar of this.grammarRegistry.getGrammars()) { if (grammar.injectionSelector) { - addGrammar(grammar); + addGrammar(grammar) } } } - return _.uniq(packageNames); + return _.uniq(packageNames) } - subscribeToActiveItem() { - this.updateWindowTitle(); - this.updateDocumentEdited(); - this.project.onDidChangePaths(this.updateWindowTitle); + subscribeToActiveItem () { + this.updateWindowTitle() + this.updateDocumentEdited() + this.project.onDidChangePaths(this.updateWindowTitle) return this.observeActivePaneItem(item => { - let modifiedSubscription, titleSubscription; - this.updateWindowTitle(); - this.updateDocumentEdited(); + let modifiedSubscription, titleSubscription + this.updateWindowTitle() + this.updateDocumentEdited() if (this.activeItemSubscriptions != null) { - this.activeItemSubscriptions.dispose(); + this.activeItemSubscriptions.dispose() } - this.activeItemSubscriptions = new CompositeDisposable; + this.activeItemSubscriptions = new CompositeDisposable() if (typeof (item != null ? item.onDidChangeTitle : undefined) === 'function') { - titleSubscription = item.onDidChangeTitle(this.updateWindowTitle); + titleSubscription = item.onDidChangeTitle(this.updateWindowTitle) } else if (typeof (item != null ? item.on : undefined) === 'function') { - titleSubscription = item.on('title-changed', this.updateWindowTitle); + titleSubscription = item.on('title-changed', this.updateWindowTitle) if (typeof (titleSubscription != null ? titleSubscription.dispose : undefined) !== 'function') { - titleSubscription = new Disposable((function() { return item.off('title-changed', this.updateWindowTitle); }.bind(this))); + titleSubscription = new Disposable((function () { return item.off('title-changed', this.updateWindowTitle) }.bind(this))) } } if (typeof (item != null ? item.onDidChangeModified : undefined) === 'function') { - modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited); + modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited) } else if (typeof ((item != null ? item.on : undefined) != null) === 'function') { - modifiedSubscription = item.on('modified-status-changed', this.updateDocumentEdited); + modifiedSubscription = item.on('modified-status-changed', this.updateDocumentEdited) if (typeof (modifiedSubscription != null ? modifiedSubscription.dispose : undefined) !== 'function') { - modifiedSubscription = new Disposable((function() { return item.off('modified-status-changed', this.updateDocumentEdited); }.bind(this))); + modifiedSubscription = new Disposable((function () { return item.off('modified-status-changed', this.updateDocumentEdited) }.bind(this))) } } - if (titleSubscription != null) { this.activeItemSubscriptions.add(titleSubscription); } - if (modifiedSubscription != null) { return this.activeItemSubscriptions.add(modifiedSubscription); } + if (titleSubscription != null) { this.activeItemSubscriptions.add(titleSubscription) } + if (modifiedSubscription != null) { return this.activeItemSubscriptions.add(modifiedSubscription) } } - ); + ) } - subscribeToAddedItems() { + subscribeToAddedItems () { return this.onDidAddPaneItem(({item, pane, index}) => { if (item instanceof TextEditor) { const subscriptions = new CompositeDisposable( @@ -200,57 +199,57 @@ Workspace = class Workspace extends Model { this.textEditorRegistry.maintainGrammar(item), this.textEditorRegistry.maintainConfig(item), item.observeGrammar(this.handleGrammarUsed.bind(this)) - ); - item.onDidDestroy(() => subscriptions.dispose()); - return this.emitter.emit('did-add-text-editor', {textEditor: item, pane, index}); + ) + item.onDidDestroy(() => subscriptions.dispose()) + return this.emitter.emit('did-add-text-editor', {textEditor: item, pane, index}) } - }); + }) } // Updates the application's title and proxy icon based on whichever file is // open. - updateWindowTitle() { - let item, itemPath, itemTitle, left, projectPath, representedPath; - const appName = 'Atom'; - const projectPaths = (left = this.project.getPaths()) != null ? left : []; - if (item = this.getActivePaneItem()) { - let left1; - itemPath = typeof item.getPath === 'function' ? item.getPath() : undefined; - itemTitle = (left1 = (typeof item.getLongTitle === 'function' ? item.getLongTitle() : undefined)) != null ? left1 : (typeof item.getTitle === 'function' ? item.getTitle() : undefined); - projectPath = _.find(projectPaths, projectPath => (itemPath === projectPath) || (itemPath != null ? itemPath.startsWith(projectPath + path.sep) : undefined)); + updateWindowTitle () { + let item, itemPath, itemTitle, left, projectPath, representedPath + const appName = 'Atom' + const projectPaths = (left = this.project.getPaths()) != null ? left : [] + if ((item = this.getActivePaneItem())) { + let left1 + itemPath = typeof item.getPath === 'function' ? item.getPath() : undefined + itemTitle = (left1 = (typeof item.getLongTitle === 'function' ? item.getLongTitle() : undefined)) != null ? left1 : (typeof item.getTitle === 'function' ? item.getTitle() : undefined) + projectPath = _.find(projectPaths, projectPath => (itemPath === projectPath) || (itemPath != null ? itemPath.startsWith(projectPath + path.sep) : undefined)) } - if (itemTitle == null) { itemTitle = "untitled"; } - if (projectPath == null) { projectPath = itemPath ? path.dirname(itemPath) : projectPaths[0]; } + if (itemTitle == null) { itemTitle = 'untitled' } + if (projectPath == null) { projectPath = itemPath ? path.dirname(itemPath) : projectPaths[0] } if (projectPath != null) { - projectPath = fs.tildify(projectPath); + projectPath = fs.tildify(projectPath) } - const titleParts = []; + const titleParts = [] if ((item != null) && (projectPath != null)) { - titleParts.push(itemTitle, projectPath); - representedPath = itemPath != null ? itemPath : projectPath; + titleParts.push(itemTitle, projectPath) + representedPath = itemPath != null ? itemPath : projectPath } else if (projectPath != null) { - titleParts.push(projectPath); - representedPath = projectPath; + titleParts.push(projectPath) + representedPath = projectPath } else { - titleParts.push(itemTitle); - representedPath = ""; + titleParts.push(itemTitle) + representedPath = '' } if (process.platform !== 'darwin') { - titleParts.push(appName); + titleParts.push(appName) } - document.title = titleParts.join(" \u2014 "); - return this.applicationDelegate.setRepresentedFilename(representedPath); + document.title = titleParts.join(' \u2014 ') + return this.applicationDelegate.setRepresentedFilename(representedPath) } // On macOS, fades the application window's proxy icon when the current file // has been modified. - updateDocumentEdited() { - let left; - const modified = (left = __guardMethod__(this.getActivePaneItem(), 'isModified', o => o.isModified())) != null ? left : false; - return this.applicationDelegate.setWindowDocumentEdited(modified); + updateDocumentEdited () { + let left + const modified = (left = __guardMethod__(this.getActivePaneItem(), 'isModified', o => o.isModified())) != null ? left : false + return this.applicationDelegate.setWindowDocumentEdited(modified) } /* @@ -265,9 +264,9 @@ Workspace = class Workspace extends Model { // of subscription or that is added at some later time. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeTextEditors(callback) { - for (let textEditor of this.getTextEditors()) { callback(textEditor); } - return this.onDidAddTextEditor(({textEditor}) => callback(textEditor)); + observeTextEditors (callback) { + for (let textEditor of this.getTextEditors()) { callback(textEditor) } + return this.onDidAddTextEditor(({textEditor}) => callback(textEditor)) } // Essential: Invoke the given callback with all current and future panes items @@ -278,7 +277,7 @@ Workspace = class Workspace extends Model { // subscription or that is added at some later time. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observePaneItems(callback) { return this.paneContainer.observePaneItems(callback); } + observePaneItems (callback) { return this.paneContainer.observePaneItems(callback) } // Essential: Invoke the given callback when the active pane item changes. // @@ -291,8 +290,8 @@ Workspace = class Workspace extends Model { // * `item` The active pane item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePaneItem(callback) { - return this.paneContainer.onDidChangeActivePaneItem(callback); + onDidChangeActivePaneItem (callback) { + return this.paneContainer.onDidChangeActivePaneItem(callback) } // Essential: Invoke the given callback when the active pane item stops @@ -309,8 +308,8 @@ Workspace = class Workspace extends Model { // * `item` The active pane item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidStopChangingActivePaneItem(callback) { - return this.paneContainer.onDidStopChangingActivePaneItem(callback); + onDidStopChangingActivePaneItem (callback) { + return this.paneContainer.onDidStopChangingActivePaneItem(callback) } // Essential: Invoke the given callback with the current active pane item and @@ -320,7 +319,7 @@ Workspace = class Workspace extends Model { // * `item` The current active pane item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActivePaneItem(callback) { return this.paneContainer.observeActivePaneItem(callback); } + observeActivePaneItem (callback) { return this.paneContainer.observeActivePaneItem(callback) } // Essential: Invoke the given callback whenever an item is opened. Unlike // {::onDidAddPaneItem}, observers will be notified for items that are already @@ -334,8 +333,8 @@ Workspace = class Workspace extends Model { // * `index` The index of the opened item on its pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidOpen(callback) { - return this.emitter.on('did-open', callback); + onDidOpen (callback) { + return this.emitter.on('did-open', callback) } // Extended: Invoke the given callback when a pane is added to the workspace. @@ -345,7 +344,7 @@ Workspace = class Workspace extends Model { // * `pane` The added pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddPane(callback) { return this.paneContainer.onDidAddPane(callback); } + onDidAddPane (callback) { return this.paneContainer.onDidAddPane(callback) } // Extended: Invoke the given callback before a pane is destroyed in the // workspace. @@ -355,7 +354,7 @@ Workspace = class Workspace extends Model { // * `pane` The pane to be destroyed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillDestroyPane(callback) { return this.paneContainer.onWillDestroyPane(callback); } + onWillDestroyPane (callback) { return this.paneContainer.onWillDestroyPane(callback) } // Extended: Invoke the given callback when a pane is destroyed in the // workspace. @@ -365,7 +364,7 @@ Workspace = class Workspace extends Model { // * `pane` The destroyed pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroyPane(callback) { return this.paneContainer.onDidDestroyPane(callback); } + onDidDestroyPane (callback) { return this.paneContainer.onDidDestroyPane(callback) } // Extended: Invoke the given callback with all current and future panes in the // workspace. @@ -375,7 +374,7 @@ Workspace = class Workspace extends Model { // subscription or that is added at some later time. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observePanes(callback) { return this.paneContainer.observePanes(callback); } + observePanes (callback) { return this.paneContainer.observePanes(callback) } // Extended: Invoke the given callback when the active pane changes. // @@ -383,7 +382,7 @@ Workspace = class Workspace extends Model { // * `pane` A {Pane} that is the current return value of {::getActivePane}. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePane(callback) { return this.paneContainer.onDidChangeActivePane(callback); } + onDidChangeActivePane (callback) { return this.paneContainer.onDidChangeActivePane(callback) } // Extended: Invoke the given callback with the current active pane and when // the active pane changes. @@ -393,7 +392,7 @@ Workspace = class Workspace extends Model { // * `pane` A {Pane} that is the current return value of {::getActivePane}. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActivePane(callback) { return this.paneContainer.observeActivePane(callback); } + observeActivePane (callback) { return this.paneContainer.observeActivePane(callback) } // Extended: Invoke the given callback when a pane item is added to the // workspace. @@ -405,7 +404,7 @@ Workspace = class Workspace extends Model { // * `index` {Number} indicating the index of the added item in its pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddPaneItem(callback) { return this.paneContainer.onDidAddPaneItem(callback); } + onDidAddPaneItem (callback) { return this.paneContainer.onDidAddPaneItem(callback) } // Extended: Invoke the given callback when a pane item is about to be // destroyed, before the user is prompted to save it. @@ -418,7 +417,7 @@ Workspace = class Workspace extends Model { // its pane. // // Returns a {Disposable} on which `.dispose` can be called to unsubscribe. - onWillDestroyPaneItem(callback) { return this.paneContainer.onWillDestroyPaneItem(callback); } + onWillDestroyPaneItem (callback) { return this.paneContainer.onWillDestroyPaneItem(callback) } // Extended: Invoke the given callback when a pane item is destroyed. // @@ -430,7 +429,7 @@ Workspace = class Workspace extends Model { // pane. // // Returns a {Disposable} on which `.dispose` can be called to unsubscribe. - onDidDestroyPaneItem(callback) { return this.paneContainer.onDidDestroyPaneItem(callback); } + onDidDestroyPaneItem (callback) { return this.paneContainer.onDidDestroyPaneItem(callback) } // Extended: Invoke the given callback when a text editor is added to the // workspace. @@ -443,8 +442,8 @@ Workspace = class Workspace extends Model { // pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddTextEditor(callback) { - return this.emitter.on('did-add-text-editor', callback); + onDidAddTextEditor (callback) { + return this.emitter.on('did-add-text-editor', callback) } /* @@ -480,42 +479,46 @@ Workspace = class Workspace extends Model { // an existing item for the same URI. Defaults to `false`. // // Returns a {Promise} that resolves to the {TextEditor} for the file URI. - open(uri, options={}) { - let pane; - const { searchAllPanes } = options; - const { split } = options; - uri = this.project.resolvePath(uri); + open (uri, options = {}) { + let pane + const { searchAllPanes } = options + const { split } = options + uri = this.project.resolvePath(uri) if (!atom.config.get('core.allowPendingPaneItems')) { - options.pending = false; + options.pending = false } // Avoid adding URLs as recent documents to work-around this Spotlight crash: // https://github.com/atom/atom/issues/10071 if ((uri != null) && ((url.parse(uri).protocol == null) || (process.platform === 'win32'))) { - this.applicationDelegate.addRecentDocument(uri); + this.applicationDelegate.addRecentDocument(uri) } - if (searchAllPanes) { pane = this.paneContainer.paneForURI(uri); } - if (pane == null) { pane = (() => { switch (split) { - case 'left': - return this.getActivePane().findLeftmostSibling(); - case 'right': - return this.getActivePane().findOrCreateRightmostSibling(); - case 'up': - return this.getActivePane().findTopmostSibling(); - case 'down': - return this.getActivePane().findOrCreateBottommostSibling(); - default: - return this.getActivePane(); - } })(); } + if (searchAllPanes) { pane = this.paneContainer.paneForURI(uri) } + if (pane == null) { + pane = (() => { + switch (split) { + case 'left': + return this.getActivePane().findLeftmostSibling() + case 'right': + return this.getActivePane().findOrCreateRightmostSibling() + case 'up': + return this.getActivePane().findTopmostSibling() + case 'down': + return this.getActivePane().findOrCreateBottommostSibling() + default: + return this.getActivePane() + } + })() + } - return this.openURIInPane(uri, pane, options); + return this.openURIInPane(uri, pane, options) } // Open Atom's license in the active pane. - openLicense() { - return this.open(path.join(process.resourcesPath, 'LICENSE.md')); + openLicense () { + return this.open(path.join(process.resourcesPath, 'LICENSE.md')) } // Synchronously open the given URI in the active pane. **Only use this method @@ -532,154 +535,151 @@ Workspace = class Workspace extends Model { // the containing pane. Defaults to `true`. // * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} // on containing pane. Defaults to `true`. - openSync(uri='', options={}) { - const {initialLine, initialColumn} = options; - const activatePane = options.activatePane != null ? options.activatePane : true; - const activateItem = options.activateItem != null ? options.activateItem : true; + openSync (uri = '', options = {}) { + const {initialLine, initialColumn} = options + const activatePane = options.activatePane != null ? options.activatePane : true + const activateItem = options.activateItem != null ? options.activateItem : true - uri = this.project.resolvePath(uri); - let item = this.getActivePane().itemForURI(uri); + uri = this.project.resolvePath(uri) + let item = this.getActivePane().itemForURI(uri) if (uri) { - for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options); } } } + for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options) } } } } - if (item == null) { item = this.project.openSync(uri, {initialLine, initialColumn}); } + if (item == null) { item = this.project.openSync(uri, {initialLine, initialColumn}) } - if (activateItem) { this.getActivePane().activateItem(item); } - this.itemOpened(item); - if (activatePane) { this.getActivePane().activate(); } - return item; + if (activateItem) { this.getActivePane().activateItem(item) } + this.itemOpened(item) + if (activatePane) { this.getActivePane().activate() } + return item } - openURIInPane(uri, pane, options={}) { - let item; - const activatePane = options.activatePane != null ? options.activatePane : true; - const activateItem = options.activateItem != null ? options.activateItem : true; + openURIInPane (uri, pane, options = {}) { + let item + const activatePane = options.activatePane != null ? options.activatePane : true + const activateItem = options.activateItem != null ? options.activateItem : true if (uri != null) { - if (item = pane.itemForURI(uri)) { - if (!options.pending && (pane.getPendingItem() === item)) { pane.clearPendingItem(); } + if ((item = pane.itemForURI(uri))) { + if (!options.pending && (pane.getPendingItem() === item)) { pane.clearPendingItem() } } - for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options); } } } + for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options) } } } } try { - if (item == null) { item = this.openTextFile(uri, options); } + if (item == null) { item = this.openTextFile(uri, options) } } catch (error) { switch (error.code) { case 'CANCELLED': - return Promise.resolve(); - break; + return Promise.resolve() case 'EACCES': - this.notificationManager.addWarning(`Permission denied '${error.path}'`); - return Promise.resolve(); - break; + this.notificationManager.addWarning(`Permission denied '${error.path}'`) + return Promise.resolve() case 'EPERM': case 'EBUSY': case 'ENXIO': case 'EIO': case 'ENOTCONN': case 'UNKNOWN': case 'ECONNRESET': case 'EINVAL': case 'EMFILE': case 'ENOTDIR': case 'EAGAIN': - this.notificationManager.addWarning(`Unable to open '${error.path != null ? error.path : uri}'`, {detail: error.message}); - return Promise.resolve(); - break; + this.notificationManager.addWarning(`Unable to open '${error.path != null ? error.path : uri}'`, {detail: error.message}) + return Promise.resolve() default: - throw error; + throw error } } return Promise.resolve(item) .then(item => { - let initialColumn; - if (pane.isDestroyed()) { return item; } + let initialColumn + if (pane.isDestroyed()) { return item } - this.itemOpened(item); - if (activateItem) { pane.activateItem(item, {pending: options.pending}); } - if (activatePane) { pane.activate(); } + this.itemOpened(item) + if (activateItem) { pane.activateItem(item, {pending: options.pending}) } + if (activatePane) { pane.activate() } - let initialLine = initialColumn = 0; + let initialLine = initialColumn = 0 if (!Number.isNaN(options.initialLine)) { - ({ initialLine } = options); + ({ initialLine } = options) } if (!Number.isNaN(options.initialColumn)) { - ({ initialColumn } = options); + ({ initialColumn } = options) } if ((initialLine >= 0) || (initialColumn >= 0)) { if (typeof item.setCursorBufferPosition === 'function') { - item.setCursorBufferPosition([initialLine, initialColumn]); + item.setCursorBufferPosition([initialLine, initialColumn]) } } - const index = pane.getActiveItemIndex(); - this.emitter.emit('did-open', {uri, pane, item, index}); - return item; + const index = pane.getActiveItemIndex() + this.emitter.emit('did-open', {uri, pane, item, index}) + return item } - ); + ) } - openTextFile(uri, options) { - const filePath = this.project.resolvePath(uri); + openTextFile (uri, options) { + const filePath = this.project.resolvePath(uri) if (filePath != null) { try { - fs.closeSync(fs.openSync(filePath, 'r')); + fs.closeSync(fs.openSync(filePath, 'r')) } catch (error) { // allow ENOENT errors to create an editor for paths that dont exist - if (error.code !== 'ENOENT') { throw error; } + if (error.code !== 'ENOENT') { throw error } } } - const fileSize = fs.getSizeSync(filePath); + const fileSize = fs.getSizeSync(filePath) - const largeFileMode = fileSize >= (2 * 1048576); // 2MB + const largeFileMode = fileSize >= (2 * 1048576) // 2MB if (fileSize >= (this.config.get('core.warnOnLargeFileLimit') * 1048576)) { // 20MB by default const choice = this.applicationDelegate.confirm({ message: 'Atom will be unresponsive during the loading of very large files.', - detailedMessage: "Do you still want to load this file?", - buttons: ["Proceed", "Cancel"]}); + detailedMessage: 'Do you still want to load this file?', + buttons: ['Proceed', 'Cancel']}) if (choice === 1) { - const error = new Error; - error.code = 'CANCELLED'; - throw error; + const error = new Error() + error.code = 'CANCELLED' + throw error } } return this.project.bufferForPath(filePath, options).then(buffer => { - return this.textEditorRegistry.build(Object.assign({buffer, largeFileMode, autoHeight: false}, options)); + return this.textEditorRegistry.build(Object.assign({buffer, largeFileMode, autoHeight: false}, options)) } - ); + ) } - handleGrammarUsed(grammar) { - if (grammar == null) { return; } + handleGrammarUsed (grammar) { + if (grammar == null) { return } - return this.packageManager.triggerActivationHook(`${grammar.packageName}:grammar-used`); + return this.packageManager.triggerActivationHook(`${grammar.packageName}:grammar-used`) } // Public: Returns a {Boolean} that is `true` if `object` is a `TextEditor`. // // * `object` An {Object} you want to perform the check against. - isTextEditor(object) { - return object instanceof TextEditor; + isTextEditor (object) { + return object instanceof TextEditor } // Extended: Create a new text editor. // // Returns a {TextEditor}. - buildTextEditor(params) { - const editor = this.textEditorRegistry.build(params); + buildTextEditor (params) { + const editor = this.textEditorRegistry.build(params) const subscriptions = new CompositeDisposable( this.textEditorRegistry.maintainGrammar(editor), this.textEditorRegistry.maintainConfig(editor) - ); - editor.onDidDestroy(() => subscriptions.dispose()); - return editor; + ) + editor.onDidDestroy(() => subscriptions.dispose()) + return editor } // Public: Asynchronously reopens the last-closed item's URI if it hasn't already been // reopened. // // Returns a {Promise} that is resolved when the item is opened - reopenItem() { - let uri; - if (uri = this.destroyedItemURIs.pop()) { - return this.open(uri); + reopenItem () { + let uri + if ((uri = this.destroyedItemURIs.pop())) { + return this.open(uri) } else { - return Promise.resolve(); + return Promise.resolve() } } @@ -712,13 +712,13 @@ Workspace = class Workspace extends Model { // that is already open in a text editor view. You could signal this by calling // {Workspace::open} on the URI `quux-preview://foo/bar/baz.quux`. Then your opener // can check the protocol for quux-preview and only handle those URIs that match. - addOpener(opener) { - this.openers.push(opener); - return new Disposable((function() { return _.remove(this.openers, opener); }.bind(this))); + addOpener (opener) { + this.openers.push(opener) + return new Disposable((function () { return _.remove(this.openers, opener) }.bind(this))) } - getOpeners() { - return this.openers; + getOpeners () { + return this.openers } /* @@ -728,40 +728,40 @@ Workspace = class Workspace extends Model { // Essential: Get all pane items in the workspace. // // Returns an {Array} of items. - getPaneItems() { - return this.paneContainer.getPaneItems(); + getPaneItems () { + return this.paneContainer.getPaneItems() } // Essential: Get the active {Pane}'s active item. // // Returns an pane item {Object}. - getActivePaneItem() { - return this.paneContainer.getActivePaneItem(); + getActivePaneItem () { + return this.paneContainer.getActivePaneItem() } // Essential: Get all text editors in the workspace. // // Returns an {Array} of {TextEditor}s. - getTextEditors() { - return this.getPaneItems().filter(item => item instanceof TextEditor); + getTextEditors () { + return this.getPaneItems().filter(item => item instanceof TextEditor) } // Essential: Get the active item if it is an {TextEditor}. // // Returns an {TextEditor} or `undefined` if the current active item is not an // {TextEditor}. - getActiveTextEditor() { - const activeItem = this.getActivePaneItem(); - if (activeItem instanceof TextEditor) { return activeItem; } + getActiveTextEditor () { + const activeItem = this.getActivePaneItem() + if (activeItem instanceof TextEditor) { return activeItem } } // Save all pane items. - saveAll() { - return this.paneContainer.saveAll(); + saveAll () { + return this.paneContainer.saveAll() } - confirmClose(options) { - return this.paneContainer.confirmClose(options); + confirmClose (options) { + return this.paneContainer.confirmClose(options) } // Save the active pane item. @@ -770,8 +770,8 @@ Workspace = class Workspace extends Model { // `.getURI` method, calls `.save` on the item. Otherwise // {::saveActivePaneItemAs} # will be called instead. This method does nothing // if the active item does not implement a `.save` method. - saveActivePaneItem() { - return this.getActivePane().saveActiveItem(); + saveActivePaneItem () { + return this.getActivePane().saveActiveItem() } // Prompt the user for a path and save the active pane item to it. @@ -779,16 +779,16 @@ Workspace = class Workspace extends Model { // Opens a native dialog where the user selects a path on disk, then calls // `.saveAs` on the item with the selected path. This method does nothing if // the active item does not implement a `.saveAs` method. - saveActivePaneItemAs() { - return this.getActivePane().saveActiveItemAs(); + saveActivePaneItemAs () { + return this.getActivePane().saveActiveItemAs() } // Destroy (close) the active pane item. // // Removes the active pane item and calls the `.destroy` method on it if one is // defined. - destroyActivePaneItem() { - return this.getActivePane().destroyActiveItem(); + destroyActivePaneItem () { + return this.getActivePane().destroyActiveItem() } /* @@ -798,25 +798,25 @@ Workspace = class Workspace extends Model { // Extended: Get all panes in the workspace. // // Returns an {Array} of {Pane}s. - getPanes() { - return this.paneContainer.getPanes(); + getPanes () { + return this.paneContainer.getPanes() } // Extended: Get the active {Pane}. // // Returns a {Pane}. - getActivePane() { - return this.paneContainer.getActivePane(); + getActivePane () { + return this.paneContainer.getActivePane() } // Extended: Make the next pane active. - activateNextPane() { - return this.paneContainer.activateNextPane(); + activateNextPane () { + return this.paneContainer.activateNextPane() } // Extended: Make the previous pane active. - activatePreviousPane() { - return this.paneContainer.activatePreviousPane(); + activatePreviousPane () { + return this.paneContainer.activatePreviousPane() } // Extended: Get the first {Pane} with an item for the given URI. @@ -824,8 +824,8 @@ Workspace = class Workspace extends Model { // * `uri` {String} uri // // Returns a {Pane} or `undefined` if no pane exists for the given URI. - paneForURI(uri) { - return this.paneContainer.paneForURI(uri); + paneForURI (uri) { + return this.paneContainer.paneForURI(uri) } // Extended: Get the {Pane} containing the given item. @@ -833,87 +833,86 @@ Workspace = class Workspace extends Model { // * `item` Item the returned pane contains. // // Returns a {Pane} or `undefined` if no pane exists for the given item. - paneForItem(item) { - return this.paneContainer.paneForItem(item); + paneForItem (item) { + return this.paneContainer.paneForItem(item) } // Destroy (close) the active pane. - destroyActivePane() { - return __guard__(this.getActivePane(), x => x.destroy()); + destroyActivePane () { + return __guard__(this.getActivePane(), x => x.destroy()) } // Close the active pane item, or the active pane if it is empty, // or the current window if there is only the empty root pane. - closeActivePaneItemOrEmptyPaneOrWindow() { + closeActivePaneItemOrEmptyPaneOrWindow () { if (this.getActivePaneItem() != null) { - return this.destroyActivePaneItem(); + return this.destroyActivePaneItem() } else if (this.getPanes().length > 1) { - return this.destroyActivePane(); + return this.destroyActivePane() } else if (this.config.get('core.closeEmptyWindows')) { - return atom.close(); + return atom.close() } } // Increase the editor font size by 1px. - increaseFontSize() { - return this.config.set("editor.fontSize", this.config.get("editor.fontSize") + 1); + increaseFontSize () { + return this.config.set('editor.fontSize', this.config.get('editor.fontSize') + 1) } // Decrease the editor font size by 1px. - decreaseFontSize() { - const fontSize = this.config.get("editor.fontSize"); - if (fontSize > 1) { return this.config.set("editor.fontSize", fontSize - 1); } + decreaseFontSize () { + const fontSize = this.config.get('editor.fontSize') + if (fontSize > 1) { return this.config.set('editor.fontSize', fontSize - 1) } } // Restore to the window's original editor font size. - resetFontSize() { + resetFontSize () { if (this.originalFontSize) { - return this.config.set("editor.fontSize", this.originalFontSize); + return this.config.set('editor.fontSize', this.originalFontSize) } } - subscribeToFontSize() { + subscribeToFontSize () { return this.config.onDidChange('editor.fontSize', ({oldValue}) => { - return this.originalFontSize != null ? this.originalFontSize : (this.originalFontSize = oldValue); + return this.originalFontSize != null ? this.originalFontSize : (this.originalFontSize = oldValue) } - ); + ) } // Removes the item's uri from the list of potential items to reopen. - itemOpened(item) { - let uri; + itemOpened (item) { + let uri if (typeof item.getURI === 'function') { - uri = item.getURI(); + uri = item.getURI() } else if (typeof item.getUri === 'function') { - uri = item.getUri(); + uri = item.getUri() } if (uri != null) { - return _.remove(this.destroyedItemURIs, uri); + return _.remove(this.destroyedItemURIs, uri) } } // Adds the destroyed item's uri to the list of items to reopen. - didDestroyPaneItem({item}) { - let uri; + didDestroyPaneItem ({item}) { + let uri if (typeof item.getURI === 'function') { - uri = item.getURI(); + uri = item.getURI() } else if (typeof item.getUri === 'function') { - uri = item.getUri(); + uri = item.getUri() } if (uri != null) { - return this.destroyedItemURIs.push(uri); + return this.destroyedItemURIs.push(uri) } } // Called by Model superclass when destroyed - destroyed() { - this.paneContainer.destroy(); - return (this.activeItemSubscriptions != null ? this.activeItemSubscriptions.dispose() : undefined); + destroyed () { + this.paneContainer.destroy() + return (this.activeItemSubscriptions != null ? this.activeItemSubscriptions.dispose() : undefined) } - /* Section: Panels @@ -929,8 +928,8 @@ Workspace = class Workspace extends Model { */ // Essential: Get an {Array} of all the panel items at the bottom of the editor window. - getBottomPanels() { - return this.getPanels('bottom'); + getBottomPanels () { + return this.getPanels('bottom') } // Essential: Adds a panel item to the bottom of the editor window. @@ -945,13 +944,13 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addBottomPanel(options) { - return this.addPanel('bottom', options); + addBottomPanel (options) { + return this.addPanel('bottom', options) } // Essential: Get an {Array} of all the panel items to the left of the editor window. - getLeftPanels() { - return this.getPanels('left'); + getLeftPanels () { + return this.getPanels('left') } // Essential: Adds a panel item to the left of the editor window. @@ -966,13 +965,13 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addLeftPanel(options) { - return this.addPanel('left', options); + addLeftPanel (options) { + return this.addPanel('left', options) } // Essential: Get an {Array} of all the panel items to the right of the editor window. - getRightPanels() { - return this.getPanels('right'); + getRightPanels () { + return this.getPanels('right') } // Essential: Adds a panel item to the right of the editor window. @@ -987,13 +986,13 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addRightPanel(options) { - return this.addPanel('right', options); + addRightPanel (options) { + return this.addPanel('right', options) } // Essential: Get an {Array} of all the panel items at the top of the editor window. - getTopPanels() { - return this.getPanels('top'); + getTopPanels () { + return this.getPanels('top') } // Essential: Adds a panel item to the top of the editor window above the tabs. @@ -1008,13 +1007,13 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addTopPanel(options) { - return this.addPanel('top', options); + addTopPanel (options) { + return this.addPanel('top', options) } // Essential: Get an {Array} of all the panel items in the header. - getHeaderPanels() { - return this.getPanels('header'); + getHeaderPanels () { + return this.getPanels('header') } // Essential: Adds a panel item to the header. @@ -1029,13 +1028,13 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addHeaderPanel(options) { - return this.addPanel('header', options); + addHeaderPanel (options) { + return this.addPanel('header', options) } // Essential: Get an {Array} of all the panel items in the footer. - getFooterPanels() { - return this.getPanels('footer'); + getFooterPanels () { + return this.getPanels('footer') } // Essential: Adds a panel item to the footer. @@ -1050,13 +1049,13 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addFooterPanel(options) { - return this.addPanel('footer', options); + addFooterPanel (options) { + return this.addPanel('footer', options) } // Essential: Get an {Array} of all the modal panel items - getModalPanels() { - return this.getPanels('modal'); + getModalPanels () { + return this.getPanels('modal') } // Essential: Adds a panel item as a modal dialog. @@ -1071,30 +1070,30 @@ Workspace = class Workspace extends Model { // forced closer to the edges of the window. (default: 100) // // Returns a {Panel} - addModalPanel(options={}) { - return this.addPanel('modal', options); + addModalPanel (options = {}) { + return this.addPanel('modal', options) } // Essential: Returns the {Panel} associated with the given item. Returns // `null` when the item has no panel. // // * `item` Item the panel contains - panelForItem(item) { + panelForItem (item) { for (let location in this.panelContainers) { - const container = this.panelContainers[location]; - const panel = container.panelForItem(item); - if (panel != null) { return panel; } + const container = this.panelContainers[location] + const panel = container.panelForItem(item) + if (panel != null) { return panel } } - return null; + return null } - getPanels(location) { - return this.panelContainers[location].getPanels(); + getPanels (location) { + return this.panelContainers[location].getPanels() } - addPanel(location, options) { - if (options == null) { options = {}; } - return this.panelContainers[location].addPanel(new Panel(options)); + addPanel (location, options) { + if (options == null) { options = {} } + return this.panelContainers[location].addPanel(new Panel(options)) } /* @@ -1112,54 +1111,54 @@ Workspace = class Workspace extends Model { // // Returns a {Promise} with a `cancel()` method that will cancel all // of the underlying searches that were started as part of this scan. - scan(regex, options={}, iterator) { - let directorySearcher, onPathsSearched; + scan (regex, options = {}, iterator) { + let directorySearcher, onPathsSearched if (_.isFunction(options)) { - iterator = options; - options = {}; + iterator = options + options = {} } // Find a searcher for every Directory in the project. Each searcher that is matched // will be associated with an Array of Directory objects in the Map. - const directoriesForSearcher = new Map(); + const directoriesForSearcher = new Map() for (let directory of this.project.getDirectories()) { - let searcher = this.defaultDirectorySearcher; + let searcher = this.defaultDirectorySearcher for (directorySearcher of this.directorySearchers) { if (directorySearcher.canSearchDirectory(directory)) { - searcher = directorySearcher; - break; + searcher = directorySearcher + break } } - let directories = directoriesForSearcher.get(searcher); + let directories = directoriesForSearcher.get(searcher) if (!directories) { - directories = []; - directoriesForSearcher.set(searcher, directories); + directories = [] + directoriesForSearcher.set(searcher, directories) } - directories.push(directory); + directories.push(directory) } // Define the onPathsSearched callback. if (_.isFunction(options.onPathsSearched)) { // Maintain a map of directories to the number of search results. When notified of a new count, // replace the entry in the map and update the total. - const onPathsSearchedOption = options.onPathsSearched; - let totalNumberOfPathsSearched = 0; - const numberOfPathsSearchedForSearcher = new Map(); - onPathsSearched = function(searcher, numberOfPathsSearched) { - const oldValue = numberOfPathsSearchedForSearcher.get(searcher); + const onPathsSearchedOption = options.onPathsSearched + let totalNumberOfPathsSearched = 0 + const numberOfPathsSearchedForSearcher = new Map() + onPathsSearched = function (searcher, numberOfPathsSearched) { + const oldValue = numberOfPathsSearchedForSearcher.get(searcher) if (oldValue) { - totalNumberOfPathsSearched -= oldValue; + totalNumberOfPathsSearched -= oldValue } - numberOfPathsSearchedForSearcher.set(searcher, numberOfPathsSearched); - totalNumberOfPathsSearched += numberOfPathsSearched; - return onPathsSearchedOption(totalNumberOfPathsSearched); - }; + numberOfPathsSearchedForSearcher.set(searcher, numberOfPathsSearched) + totalNumberOfPathsSearched += numberOfPathsSearched + return onPathsSearchedOption(totalNumberOfPathsSearched) + } } else { - onPathsSearched = function() {}; + onPathsSearched = function () {} } // Kick off all of the searches and unify them into one Promise. - const allSearches = []; + const allSearches = [] directoriesForSearcher.forEach((directories, searcher) => { const searchOptions = { inclusions: options.paths || [], @@ -1168,26 +1167,26 @@ Workspace = class Workspace extends Model { exclusions: this.config.get('core.ignoredNames'), follow: this.config.get('core.followSymlinks'), didMatch: result => { - if (!this.project.isPathModified(result.filePath)) { return iterator(result); } + if (!this.project.isPathModified(result.filePath)) { return iterator(result) } }, - didError(error) { - return iterator(null, error); + didError (error) { + return iterator(null, error) }, - didSearchPaths(count) { return onPathsSearched(searcher, count); } - }; - directorySearcher = searcher.search(directories, regex, searchOptions); - return allSearches.push(directorySearcher); + didSearchPaths (count) { return onPathsSearched(searcher, count) } + } + directorySearcher = searcher.search(directories, regex, searchOptions) + return allSearches.push(directorySearcher) } - ); - const searchPromise = Promise.all(allSearches); + ) + const searchPromise = Promise.all(allSearches) for (let buffer of this.project.getBuffers()) { if (buffer.isModified()) { - const filePath = buffer.getPath(); - if (!this.project.contains(filePath)) { continue; } - var matches = []; - buffer.scan(regex, match => matches.push(match)); - if (matches.length > 0) { iterator({filePath, matches}); } + const filePath = buffer.getPath() + if (!this.project.contains(filePath)) { continue } + var matches = [] + buffer.scan(regex, match => matches.push(match)) + if (matches.length > 0) { iterator({filePath, matches}) } } } @@ -1195,36 +1194,36 @@ Workspace = class Workspace extends Model { // with the existing behavior, instead of cancel() rejecting the promise, it should // resolve it with the special value 'cancelled'. At least the built-in find-and-replace // package relies on this behavior. - let isCancelled = false; - const cancellablePromise = new Promise(function(resolve, reject) { - const onSuccess = function() { + let isCancelled = false + const cancellablePromise = new Promise(function (resolve, reject) { + const onSuccess = function () { if (isCancelled) { - return resolve('cancelled'); + return resolve('cancelled') } else { - return resolve(null); + return resolve(null) } - }; + } - const onFailure = function() { - for (let promise of allSearches) { promise.cancel(); } - return reject(); - }; + const onFailure = function () { + for (let promise of allSearches) { promise.cancel() } + return reject() + } - return searchPromise.then(onSuccess, onFailure); - }); - cancellablePromise.cancel = function() { - isCancelled = true; + return searchPromise.then(onSuccess, onFailure) + }) + cancellablePromise.cancel = function () { + isCancelled = true // Note that cancelling all of the members of allSearches will cause all of the searches // to resolve, which causes searchPromise to resolve, which is ultimately what causes // cancellablePromise to resolve. - return allSearches.map((promise) => promise.cancel()); - }; + return allSearches.map((promise) => promise.cancel()) + } // Although this method claims to return a `Promise`, the `ResultsPaneView.onSearch()` // method in the find-and-replace package expects the object returned by this method to have a // `done()` method. Include a done() method until find-and-replace can be updated. - cancellablePromise.done = onSuccessOrFailure => cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure); - return cancellablePromise; + cancellablePromise.done = onSuccessOrFailure => cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure) + return cancellablePromise } // Public: Performs a replace across all the specified files in the project. @@ -1236,79 +1235,80 @@ Workspace = class Workspace extends Model { // * `options` {Object} with keys `filePath` and `replacements`. // // Returns a {Promise}. - replace(regex, replacementText, filePaths, iterator) { - return new Promise((function(resolve, reject) { - let buffer; + replace (regex, replacementText, filePaths, iterator) { + return new Promise((function (resolve, reject) { + let buffer const openPaths = ((() => { - const result = []; - for (buffer of this.project.getBuffers()) { result.push(buffer.getPath()); + const result = [] + for (buffer of this.project.getBuffers()) { + result.push(buffer.getPath()) } - return result; - })()); - const outOfProcessPaths = _.difference(filePaths, openPaths); + return result + })()) + const outOfProcessPaths = _.difference(filePaths, openPaths) - let inProcessFinished = !openPaths.length; - let outOfProcessFinished = !outOfProcessPaths.length; - const checkFinished = function() { - if (outOfProcessFinished && inProcessFinished) { return resolve(); } - }; + let inProcessFinished = !openPaths.length + let outOfProcessFinished = !outOfProcessPaths.length + const checkFinished = function () { + if (outOfProcessFinished && inProcessFinished) { return resolve() } + } if (!outOfProcessFinished.length) { - let flags = 'g'; - if (regex.ignoreCase) { flags += 'i'; } + let flags = 'g' + if (regex.ignoreCase) { flags += 'i' } - const task = Task.once(require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, function() { - outOfProcessFinished = true; - return checkFinished(); - }); + const task = Task.once(require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, function () { + outOfProcessFinished = true + return checkFinished() + }) - task.on('replace:path-replaced', iterator); - task.on('replace:file-error', function(error) { return iterator(null, error); }); + task.on('replace:path-replaced', iterator) + task.on('replace:file-error', function (error) { return iterator(null, error) }) } for (buffer of this.project.getBuffers()) { - if (!Array.from(filePaths).includes(buffer.getPath())) { continue; } - const replacements = buffer.replace(regex, replacementText, iterator); - if (replacements) { iterator({filePath: buffer.getPath(), replacements}); } + if (!Array.from(filePaths).includes(buffer.getPath())) { continue } + const replacements = buffer.replace(regex, replacementText, iterator) + if (replacements) { iterator({filePath: buffer.getPath(), replacements}) } } - inProcessFinished = true; - return checkFinished(); - }.bind(this))); + inProcessFinished = true + return checkFinished() + }.bind(this))) } - checkoutHeadRevision(editor) { + checkoutHeadRevision (editor) { if (editor.getPath()) { const checkoutHead = () => { return this.project.repositoryForDirectory(new Directory(editor.getDirectoryPath())) - .then(repository => repository != null ? repository.checkoutHeadForEditor(editor) : undefined); - }; + .then(repository => repository != null ? repository.checkoutHeadForEditor(editor) : undefined) + } if (this.config.get('editor.confirmCheckoutHeadRevision')) { return this.applicationDelegate.confirm({ message: 'Confirm Checkout HEAD Revision', - detailedMessage: `Are you sure you want to discard all changes to \"${editor.getFileName()}\" since the last Git commit?`, + detailedMessage: `Are you sure you want to discard all changes to "${editor.getFileName()}" since the last Git commit?`, buttons: { OK: checkoutHead, Cancel: null } - }); + }) } else { - return checkoutHead(); + return checkoutHead() } } else { - return Promise.resolve(false); + return Promise.resolve(false) } } -}; - -function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; } -function __guardMethod__(obj, methodName, transform) { + +function __guard__ (value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined +} +function __guardMethod__ (obj, methodName, transform) { if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { - return transform(obj, methodName); + return transform(obj, methodName) } else { - return undefined; + return undefined } } From 53ec839ae51f7491f3ee4539939fefa3ac9bc8fe Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Thu, 2 Mar 2017 15:40:57 -0800 Subject: [PATCH 050/194] Convert workspace to JavaScript: cleanup --- spec/workspace-spec.js | 1153 +++++++++++++++++++++------------------- src/workspace.js | 392 ++++++++------ 2 files changed, 841 insertions(+), 704 deletions(-) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index ba7da0fce..270f81526 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1,3 +1,5 @@ +'use strict' + /* global advanceClock, HTMLElement, waits */ const path = require('path') @@ -11,28 +13,33 @@ const fstream = require('fstream') const fs = require('fs-plus') const AtomEnvironment = require('../src/atom-environment') -describe('Workspace', function () { - let escapeStringRegex - let [workspace, setDocumentEdited] = Array.from([]) +describe('Workspace', () => { + let workspace + let setDocumentEdited - beforeEach(function () { - ({ workspace } = atom) + beforeEach(() => { + workspace = atom.workspace workspace.resetFontSize() spyOn(atom.applicationDelegate, 'confirm') setDocumentEdited = spyOn(atom.applicationDelegate, 'setWindowDocumentEdited') - atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('dir'))]) - return waits(1) + atom.project.setPaths([atom.project.getDirectories()[0].resolve('dir')]) + waits(1) }) afterEach(() => temp.cleanupSync()) - describe('serialization', function () { - const simulateReload = function () { + describe('serialization', () => { + const simulateReload = () => { const workspaceState = atom.workspace.serialize() const projectState = atom.project.serialize({isUnloading: true}) atom.workspace.destroy() atom.project.destroy() - atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom), applicationDelegate: atom.applicationDelegate}) + atom.project = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm.bind(atom), + applicationDelegate: atom.applicationDelegate + }) atom.project.deserialize(projectState) atom.workspace = new Workspace({ config: atom.config, @@ -49,8 +56,8 @@ describe('Workspace', function () { return atom.workspace.deserialize(workspaceState, atom.deserializers) } - describe('when the workspace contains text editors', () => - it('constructs the view with the same panes', function () { + describe('when the workspace contains text editors', () => { + it('constructs the view with the same panes', () => { const pane1 = atom.workspace.getActivePane() const pane2 = pane1.splitRight({copyActiveItem: true}) const pane3 = pane2.splitRight({copyActiveItem: true}) @@ -66,123 +73,127 @@ describe('Workspace', function () { atom.workspace.open('../sample.js').then(editor => pane3.activateItem(editor)) ) - runs(function () { + runs(() => { pane3.activeItem.setCursorScreenPosition([2, 4]) - return (pane4 = pane2.splitDown()) + pane4 = pane2.splitDown() }) waitsForPromise(() => atom.workspace.open('../sample.txt').then(editor => pane4.activateItem(editor)) ) - return runs(function () { + runs(() => { pane4.getActiveItem().setCursorScreenPosition([0, 2]) pane2.activate() simulateReload() expect(atom.workspace.getTextEditors().length).toBe(5) - const [editor1, editor2, untitledEditor, editor3, editor4] = Array.from(atom.workspace.getTextEditors()) - expect(editor1.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b'))) - expect(editor2.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('../sample.txt'))) + const [editor1, editor2, untitledEditor, editor3, editor4] = atom.workspace.getTextEditors() + const firstDirectory = atom.project.getDirectories()[0] + expect(firstDirectory).toBeDefined() + expect(editor1.getPath()).toBe(firstDirectory.resolve('b')) + expect(editor2.getPath()).toBe(firstDirectory.resolve('../sample.txt')) expect(editor2.getCursorScreenPosition()).toEqual([0, 2]) - expect(editor3.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('b'))) - expect(editor4.getPath()).toBe(__guard__(atom.project.getDirectories()[0], x3 => x3.resolve('../sample.js'))) + expect(editor3.getPath()).toBe(firstDirectory.resolve('b')) + expect(editor4.getPath()).toBe(firstDirectory.resolve('../sample.js')) expect(editor4.getCursorScreenPosition()).toEqual([2, 4]) expect(untitledEditor.getPath()).toBeUndefined() expect(untitledEditor.getText()).toBe('An untitled editor.') expect(atom.workspace.getActiveTextEditor().getPath()).toBe(editor3.getPath()) const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - return expect(document.title).toMatch(new RegExp(`^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}`)) }) }) - ) + }) - return describe('where there are no open panes or editors', () => - it('constructs the view with no open editors', function () { + describe('where there are no open panes or editors', () => { + it('constructs the view with no open editors', () => { atom.workspace.getActivePane().destroy() expect(atom.workspace.getTextEditors().length).toBe(0) simulateReload() - return expect(atom.workspace.getTextEditors().length).toBe(0) + expect(atom.workspace.getTextEditors().length).toBe(0) }) - ) + }) }) - describe('::open(uri, options)', function () { + describe('::open(uri, options)', () => { let openEvents = null - beforeEach(function () { + beforeEach(() => { openEvents = [] workspace.onDidOpen(event => openEvents.push(event)) - return spyOn(workspace.getActivePane(), 'activate').andCallThrough() + spyOn(workspace.getActivePane(), 'activate').andCallThrough() }) - describe("when the 'searchAllPanes' option is false (default)", function () { - describe('when called without a uri', () => - it('adds and activates an empty editor on the active pane', function () { - let [editor1, editor2] = Array.from([]) + describe("when the 'searchAllPanes' option is false (default)", () => { + describe('when called without a uri', () => { + it('adds and activates an empty editor on the active pane', () => { + let editor1 + let editor2 - waitsForPromise(() => workspace.open().then(editor => (editor1 = editor))) + waitsForPromise(() => workspace.open().then(editor => { editor1 = editor })) - runs(function () { + runs(() => { expect(editor1.getPath()).toBeUndefined() expect(workspace.getActivePane().items).toEqual([editor1]) expect(workspace.getActivePaneItem()).toBe(editor1) expect(workspace.getActivePane().activate).toHaveBeenCalled() expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor1, index: 0}]) - return (openEvents = []) + openEvents = [] }) - waitsForPromise(() => workspace.open().then(editor => (editor2 = editor))) + waitsForPromise(() => workspace.open().then(editor => { editor2 = editor })) - return runs(function () { + runs(() => { expect(editor2.getPath()).toBeUndefined() expect(workspace.getActivePane().items).toEqual([editor1, editor2]) expect(workspace.getActivePaneItem()).toBe(editor2) expect(workspace.getActivePane().activate).toHaveBeenCalled() - return expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}]) + expect(openEvents).toEqual([{uri: undefined, pane: workspace.getActivePane(), item: editor2, index: 1}]) }) }) - ) + }) - return describe('when called with a uri', function () { - describe('when the active pane already has an editor for the given uri', () => - it('activates the existing editor on the active pane', function () { + describe('when called with a uri', () => { + describe('when the active pane already has an editor for the given uri', () => { + it('activates the existing editor on the active pane', () => { let editor = null let editor1 = null let editor2 = null waitsForPromise(() => - workspace.open('a').then(function (o) { + workspace.open('a').then(o => { editor1 = o - return workspace.open('b').then(function (o) { + return workspace.open('b').then(o => { editor2 = o - return workspace.open('a').then(o => (editor = o)) + return workspace.open('a').then(o => { editor = o }) }) }) ) - return runs(function () { + runs(() => { expect(editor).toBe(editor1) expect(workspace.getActivePaneItem()).toBe(editor) expect(workspace.getActivePane().activate).toHaveBeenCalled() - - return expect(openEvents).toEqual([ + const firstDirectory = atom.project.getDirectories()[0] + expect(firstDirectory).toBeDefined() + expect(openEvents).toEqual([ { - uri: __guard__(atom.project.getDirectories()[0], x => x.resolve('a')), + uri: firstDirectory.resolve('a'), item: editor1, pane: atom.workspace.getActivePane(), index: 0 }, { - uri: __guard__(atom.project.getDirectories()[0], x1 => x1.resolve('b')), + uri: firstDirectory.resolve('b'), item: editor2, pane: atom.workspace.getActivePane(), index: 1 }, { - uri: __guard__(atom.project.getDirectories()[0], x2 => x2.resolve('a')), + uri: firstDirectory.resolve('a'), item: editor1, pane: atom.workspace.getActivePane(), index: 0 @@ -190,95 +201,97 @@ describe('Workspace', function () { ]) }) }) - ) + }) - return describe('when the active pane does not have an editor for the given uri', () => - it('adds and activates a new editor for the given path on the active pane', function () { + describe('when the active pane does not have an editor for the given uri', () => { + it('adds and activates a new editor for the given path on the active pane', () => { let editor = null - waitsForPromise(() => workspace.open('a').then(o => (editor = o))) + waitsForPromise(() => workspace.open('a').then(o => { editor = o })) - return runs(function () { - expect(editor.getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))) + runs(() => { + const firstDirectory = atom.project.getDirectories()[0] + expect(firstDirectory).toBeDefined() + expect(editor.getURI()).toBe(firstDirectory.resolve('a')) expect(workspace.getActivePaneItem()).toBe(editor) expect(workspace.getActivePane().items).toEqual([editor]) - return expect(workspace.getActivePane().activate).toHaveBeenCalled() + expect(workspace.getActivePane().activate).toHaveBeenCalled() }) }) - ) + }) }) }) - describe("when the 'searchAllPanes' option is true", function () { - describe('when an editor for the given uri is already open on an inactive pane', () => - it('activates the existing editor on the inactive pane, then activates that pane', function () { + describe("when the 'searchAllPanes' option is true", () => { + describe('when an editor for the given uri is already open on an inactive pane', () => { + it('activates the existing editor on the inactive pane, then activates that pane', () => { let editor1 = null let editor2 = null const pane1 = workspace.getActivePane() const pane2 = workspace.getActivePane().splitRight() - waitsForPromise(function () { + waitsForPromise(() => { pane1.activate() - return workspace.open('a').then(o => (editor1 = o)) + return workspace.open('a').then(o => { editor1 = o }) }) - waitsForPromise(function () { + waitsForPromise(() => { pane2.activate() - return workspace.open('b').then(o => (editor2 = o)) + return workspace.open('b').then(o => { editor2 = o }) }) runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)) waitsForPromise(() => workspace.open('a', {searchAllPanes: true})) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) - return expect(workspace.getActivePaneItem()).toBe(editor1) + expect(workspace.getActivePaneItem()).toBe(editor1) }) }) - ) + }) - return describe('when no editor for the given uri is open in any pane', () => - it('opens an editor for the given uri in the active pane', function () { + describe('when no editor for the given uri is open in any pane', () => { + it('opens an editor for the given uri in the active pane', () => { let editor = null - waitsForPromise(() => workspace.open('a', {searchAllPanes: true}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {searchAllPanes: true}).then(o => { editor = o })) - return runs(() => expect(workspace.getActivePaneItem()).toBe(editor)) + runs(() => expect(workspace.getActivePaneItem()).toBe(editor)) }) - ) + }) }) - describe("when the 'split' option is set", function () { - describe("when the 'split' option is 'left'", () => - it('opens the editor in the leftmost pane of the current pane axis', function () { + describe("when the 'split' option is set", () => { + describe("when the 'split' option is 'left'", () => { + it('opens the editor in the leftmost pane of the current pane axis', () => { const pane1 = workspace.getActivePane() const pane2 = pane1.splitRight() expect(workspace.getActivePane()).toBe(pane2) let editor = null - waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => { editor = o })) - runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) expect(pane1.items).toEqual([editor]) - return expect(pane2.items).toEqual([]) + expect(pane2.items).toEqual([]) }) // Focus right pane and reopen the file on the left - waitsForPromise(function () { + waitsForPromise(() => { pane2.focus() - return workspace.open('a', {split: 'left'}).then(o => (editor = o)) + return workspace.open('a', {split: 'left'}).then(o => { editor = o }) }) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) expect(pane1.items).toEqual([editor]) - return expect(pane2.items).toEqual([]) + expect(pane2.items).toEqual([]) }) }) - ) + }) - describe('when a pane axis is the leftmost sibling of the current pane', () => - it('opens the new item in the current pane', function () { + describe('when a pane axis is the leftmost sibling of the current pane', () => { + it('opens the new item in the current pane', () => { let editor = null const pane1 = workspace.getActivePane() const pane2 = pane1.splitLeft() @@ -286,44 +299,44 @@ describe('Workspace', function () { pane1.activate() expect(workspace.getActivePane()).toBe(pane1) - waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'left'}).then(o => { editor = o })) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) - return expect(pane1.items).toEqual([editor]) + expect(pane1.items).toEqual([editor]) }) }) - ) + }) - describe("when the 'split' option is 'right'", function () { - it('opens the editor in the rightmost pane of the current pane axis', function () { + describe("when the 'split' option is 'right'", () => { + it('opens the editor in the rightmost pane of the current pane axis', () => { let editor = null const pane1 = workspace.getActivePane() let pane2 = null - waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => { editor = o })) - runs(function () { + runs(() => { pane2 = workspace.getPanes().filter(p => p !== pane1)[0] expect(workspace.getActivePane()).toBe(pane2) expect(pane1.items).toEqual([]) - return expect(pane2.items).toEqual([editor]) + expect(pane2.items).toEqual([editor]) }) // Focus right pane and reopen the file on the right - waitsForPromise(function () { + waitsForPromise(() => { pane1.focus() - return workspace.open('a', {split: 'right'}).then(o => (editor = o)) + return workspace.open('a', {split: 'right'}).then(o => { editor = o }) }) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane2) expect(pane1.items).toEqual([]) - return expect(pane2.items).toEqual([editor]) + expect(pane2.items).toEqual([editor]) }) }) - return describe('when a pane axis is the rightmost sibling of the current pane', () => - it('opens the new item in a new pane split to the right of the current pane', function () { + describe('when a pane axis is the rightmost sibling of the current pane', () => { + it('opens the new item in a new pane split to the right of the current pane', () => { let editor = null const pane1 = workspace.getActivePane() const pane2 = pane1.splitRight() @@ -332,50 +345,50 @@ describe('Workspace', function () { expect(workspace.getActivePane()).toBe(pane1) let pane4 = null - waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'right'}).then(o => { editor = o })) - return runs(function () { + runs(() => { pane4 = workspace.getPanes().filter(p => p !== pane1)[0] expect(workspace.getActivePane()).toBe(pane4) expect(pane4.items).toEqual([editor]) expect(workspace.paneContainer.root.children[0]).toBe(pane1) - return expect(workspace.paneContainer.root.children[1]).toBe(pane4) + expect(workspace.paneContainer.root.children[1]).toBe(pane4) }) }) - ) + }) }) - describe("when the 'split' option is 'up'", () => - it('opens the editor in the topmost pane of the current pane axis', function () { + describe("when the 'split' option is 'up'", () => { + it('opens the editor in the topmost pane of the current pane axis', () => { const pane1 = workspace.getActivePane() const pane2 = pane1.splitDown() expect(workspace.getActivePane()).toBe(pane2) let editor = null - waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => { editor = o })) - runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) expect(pane1.items).toEqual([editor]) - return expect(pane2.items).toEqual([]) + expect(pane2.items).toEqual([]) }) // Focus bottom pane and reopen the file on the top - waitsForPromise(function () { + waitsForPromise(() => { pane2.focus() - return workspace.open('a', {split: 'up'}).then(o => (editor = o)) + return workspace.open('a', {split: 'up'}).then(o => { editor = o }) }) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) expect(pane1.items).toEqual([editor]) - return expect(pane2.items).toEqual([]) + expect(pane2.items).toEqual([]) }) }) - ) + }) - describe('when a pane axis is the topmost sibling of the current pane', () => - it('opens the new item in the current pane', function () { + describe('when a pane axis is the topmost sibling of the current pane', () => { + it('opens the new item in the current pane', () => { let editor = null const pane1 = workspace.getActivePane() const pane2 = pane1.splitUp() @@ -383,44 +396,44 @@ describe('Workspace', function () { pane1.activate() expect(workspace.getActivePane()).toBe(pane1) - waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'up'}).then(o => { editor = o })) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane1) - return expect(pane1.items).toEqual([editor]) + expect(pane1.items).toEqual([editor]) }) }) - ) + }) - return describe("when the 'split' option is 'down'", function () { - it('opens the editor in the bottommost pane of the current pane axis', function () { + describe("when the 'split' option is 'down'", () => { + it('opens the editor in the bottommost pane of the current pane axis', () => { let editor = null const pane1 = workspace.getActivePane() let pane2 = null - waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => { editor = o })) - runs(function () { + runs(() => { pane2 = workspace.getPanes().filter(p => p !== pane1)[0] expect(workspace.getActivePane()).toBe(pane2) expect(pane1.items).toEqual([]) - return expect(pane2.items).toEqual([editor]) + expect(pane2.items).toEqual([editor]) }) // Focus bottom pane and reopen the file on the right - waitsForPromise(function () { + waitsForPromise(() => { pane1.focus() - return workspace.open('a', {split: 'down'}).then(o => (editor = o)) + return workspace.open('a', {split: 'down'}).then(o => { editor = o }) }) - return runs(function () { + runs(() => { expect(workspace.getActivePane()).toBe(pane2) expect(pane1.items).toEqual([]) - return expect(pane2.items).toEqual([editor]) + expect(pane2.items).toEqual([editor]) }) }) - return describe('when a pane axis is the bottommost sibling of the current pane', () => - it('opens the new item in a new pane split to the bottom of the current pane', function () { + describe('when a pane axis is the bottommost sibling of the current pane', () => { + it('opens the new item in a new pane split to the bottom of the current pane', () => { let editor = null const pane1 = workspace.getActivePane() const pane2 = pane1.splitDown() @@ -428,22 +441,22 @@ describe('Workspace', function () { expect(workspace.getActivePane()).toBe(pane1) let pane4 = null - waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => (editor = o))) + waitsForPromise(() => workspace.open('a', {split: 'down'}).then(o => { editor = o })) - return runs(function () { + runs(() => { pane4 = workspace.getPanes().filter(p => p !== pane1)[0] expect(workspace.getActivePane()).toBe(pane4) expect(pane4.items).toEqual([editor]) expect(workspace.paneContainer.root.children[0]).toBe(pane1) - return expect(workspace.paneContainer.root.children[1]).toBe(pane2) + expect(workspace.paneContainer.root.children[1]).toBe(pane2) }) }) - ) + }) }) }) - describe('when an initialLine and initialColumn are specified', () => - it('moves the cursor to the indicated location', function () { + describe('when an initialLine and initialColumn are specified', () => { + it('moves the cursor to the indicated location', () => { waitsForPromise(() => workspace.open('a', {initialLine: 1, initialColumn: 5})) runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([1, 5])) @@ -466,84 +479,92 @@ describe('Workspace', function () { waitsForPromise(() => workspace.open('a', {initialLine: Infinity, initialColumn: Infinity})) - return runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 11])) + runs(() => expect(workspace.getActiveTextEditor().getCursorBufferPosition()).toEqual([2, 11])) }) - ) + }) - describe('when the file is over 2MB', () => - it('opens the editor with largeFileMode: true', function () { + describe('when the file is over 2MB', () => { + it('opens the editor with largeFileMode: true', () => { spyOn(fs, 'getSizeSync').andReturn(2 * 1048577) // 2MB let editor = null - waitsForPromise(() => workspace.open('sample.js').then(e => (editor = e))) + waitsForPromise(() => workspace.open('sample.js').then(e => { editor = e })) - return runs(() => expect(editor.largeFileMode).toBe(true)) + runs(() => expect(editor.largeFileMode).toBe(true)) }) - ) + }) - describe('when the file is over user-defined limit', function () { - const shouldPromptForFileOfSize = function (size, shouldPrompt) { + describe('when the file is over user-defined limit', () => { + const shouldPromptForFileOfSize = (size, shouldPrompt) => { spyOn(fs, 'getSizeSync').andReturn(size * 1048577) atom.applicationDelegate.confirm.andCallFake(() => selectedButtonIndex) atom.applicationDelegate.confirm() var selectedButtonIndex = 1 // cancel let editor = null - waitsForPromise(() => workspace.open('sample.js').then(e => (editor = e))) + waitsForPromise(() => workspace.open('sample.js').then(e => { editor = e })) if (shouldPrompt) { - runs(function () { + runs(() => { expect(editor).toBeUndefined() expect(atom.applicationDelegate.confirm).toHaveBeenCalled() atom.applicationDelegate.confirm.reset() - return (selectedButtonIndex = 0) + selectedButtonIndex = 0 }) // open the file - waitsForPromise(() => workspace.open('sample.js').then(e => (editor = e))) + waitsForPromise(() => workspace.open('sample.js').then(e => { editor = e })) - return runs(function () { + runs(() => { expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - return expect(editor.largeFileMode).toBe(true) + expect(editor.largeFileMode).toBe(true) }) } else { - return runs(() => expect(editor).not.toBeUndefined()) + runs(() => expect(editor).not.toBeUndefined()) } } - it('prompts the user to make sure they want to open a file this big', function () { + it('prompts the user to make sure they want to open a file this big', () => { atom.config.set('core.warnOnLargeFileLimit', 20) - return shouldPromptForFileOfSize(20, true) + shouldPromptForFileOfSize(20, true) }) - it("doesn't prompt on files below the limit", function () { + it("doesn't prompt on files below the limit", () => { atom.config.set('core.warnOnLargeFileLimit', 30) - return shouldPromptForFileOfSize(20, false) + shouldPromptForFileOfSize(20, false) }) - return it('prompts for smaller files with a lower limit', function () { + it('prompts for smaller files with a lower limit', () => { atom.config.set('core.warnOnLargeFileLimit', 5) - return shouldPromptForFileOfSize(10, true) + shouldPromptForFileOfSize(10, true) }) }) - describe('when passed a path that matches a custom opener', () => - it('returns the resource returned by the custom opener', function () { - const fooOpener = function (pathToOpen, options) { if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { return {foo: pathToOpen, options} } } - const barOpener = function (pathToOpen) { if (pathToOpen != null ? pathToOpen.match(/^bar:\/\//) : undefined) { return {bar: pathToOpen} } } + describe('when passed a path that matches a custom opener', () => { + it('returns the resource returned by the custom opener', () => { + const fooOpener = (pathToOpen, options) => { + if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { + return {foo: pathToOpen, options} + } + } + const barOpener = (pathToOpen) => { + if (pathToOpen != null ? pathToOpen.match(/^bar:\/\//) : undefined) { + return {bar: pathToOpen} + } + } workspace.addOpener(fooOpener) workspace.addOpener(barOpener) - waitsForPromise(function () { - const pathToOpen = __guard__(atom.project.getDirectories()[0], x => x.resolve('a.foo')) + waitsForPromise(() => { + const pathToOpen = atom.project.getDirectories()[0].resolve('a.foo') return workspace.open(pathToOpen, {hey: 'there'}).then(item => expect(item).toEqual({foo: pathToOpen, options: {hey: 'there'}})) }) - return waitsForPromise(() => + waitsForPromise(() => workspace.open('bar://baz').then(item => expect(item).toEqual({bar: 'bar://baz'}))) }) - ) + }) - it("adds the file to the application's recent documents list", function () { + it("adds the file to the application's recent documents list", () => { if (process.platform !== 'darwin') { return } // Feature only supported on macOS spyOn(atom.applicationDelegate, 'addRecentDocument') @@ -557,39 +578,39 @@ describe('Workspace', function () { waitsForPromise(() => workspace.open(__filename)) - return runs(() => expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith(__filename)) + runs(() => expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith(__filename)) }) - it('notifies ::onDidAddTextEditor observers', function () { + it('notifies ::onDidAddTextEditor observers', () => { const absolutePath = require.resolve('./fixtures/dir/a') const newEditorHandler = jasmine.createSpy('newEditorHandler') workspace.onDidAddTextEditor(newEditorHandler) let editor = null - waitsForPromise(() => workspace.open(absolutePath).then(e => (editor = e))) + waitsForPromise(() => workspace.open(absolutePath).then(e => { editor = e })) - return runs(() => expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor)) + runs(() => expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor)) }) - describe('when there is an error opening the file', function () { + describe('when there is an error opening the file', () => { let notificationSpy = null beforeEach(() => atom.notifications.onDidAddNotification(notificationSpy = jasmine.createSpy())) - describe('when a file does not exist', () => - it('creates an empty buffer for the specified path', function () { + describe('when a file does not exist', () => { + it('creates an empty buffer for the specified path', () => { waitsForPromise(() => workspace.open('not-a-file.md')) - return runs(function () { + runs(() => { const editor = workspace.getActiveTextEditor() expect(notificationSpy).not.toHaveBeenCalled() - return expect(editor.getPath()).toContain('not-a-file.md') + expect(editor.getPath()).toContain('not-a-file.md') }) }) - ) + }) - describe('when the user does not have access to the file', function () { + describe('when the user does not have access to the file', () => { beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function (path) { + spyOn(fs, 'openSync').andCallFake(path => { const error = new Error(`EACCES, permission denied '${path}'`) error.path = path error.code = 'EACCES' @@ -597,22 +618,22 @@ describe('Workspace', function () { }) ) - return it('creates a notification', function () { + it('creates a notification', () => { waitsForPromise(() => workspace.open('file1')) - return runs(function () { + runs(() => { expect(notificationSpy).toHaveBeenCalled() const notification = notificationSpy.mostRecentCall.args[0] expect(notification.getType()).toBe('warning') expect(notification.getMessage()).toContain('Permission denied') - return expect(notification.getMessage()).toContain('file1') + expect(notification.getMessage()).toContain('file1') }) }) }) - describe('when the the operation is not permitted', function () { + describe('when the the operation is not permitted', () => { beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function (path) { + spyOn(fs, 'openSync').andCallFake(path => { const error = new Error(`EPERM, operation not permitted '${path}'`) error.path = path error.code = 'EPERM' @@ -620,22 +641,22 @@ describe('Workspace', function () { }) ) - return it('creates a notification', function () { + it('creates a notification', () => { waitsForPromise(() => workspace.open('file1')) - return runs(function () { + runs(() => { expect(notificationSpy).toHaveBeenCalled() const notification = notificationSpy.mostRecentCall.args[0] expect(notification.getType()).toBe('warning') expect(notification.getMessage()).toContain('Unable to open') - return expect(notification.getMessage()).toContain('file1') + expect(notification.getMessage()).toContain('file1') }) }) }) - describe('when the the file is already open in windows', function () { + describe('when the the file is already open in windows', () => { beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function (path) { + spyOn(fs, 'openSync').andCallFake(path => { const error = new Error(`EBUSY, resource busy or locked '${path}'`) error.path = path error.code = 'EBUSY' @@ -643,42 +664,42 @@ describe('Workspace', function () { }) ) - return it('creates a notification', function () { + it('creates a notification', () => { waitsForPromise(() => workspace.open('file1')) - return runs(function () { + runs(() => { expect(notificationSpy).toHaveBeenCalled() const notification = notificationSpy.mostRecentCall.args[0] expect(notification.getType()).toBe('warning') expect(notification.getMessage()).toContain('Unable to open') - return expect(notification.getMessage()).toContain('file1') + expect(notification.getMessage()).toContain('file1') }) }) }) - return describe('when there is an unhandled error', function () { + describe('when there is an unhandled error', () => { beforeEach(() => - spyOn(fs, 'openSync').andCallFake(function (path) { + spyOn(fs, 'openSync').andCallFake(path => { throw new Error('I dont even know what is happening right now!!') }) ) - return it('creates a notification', function () { + it('creates a notification', () => { const open = () => workspace.open('file1', workspace.getActivePane()) - return expect(open).toThrow() + expect(open).toThrow() }) }) }) - describe('when the file is already open in pending state', () => - it('should terminate the pending state', function () { + describe('when the file is already open in pending state', () => { + it('should terminate the pending state', () => { let editor = null let pane = null waitsForPromise(() => - atom.workspace.open('sample.js', {pending: true}).then(function (o) { + atom.workspace.open('sample.js', {pending: true}).then(o => { editor = o - return (pane = atom.workspace.getActivePane()) + pane = atom.workspace.getActivePane() }) ) @@ -686,34 +707,34 @@ describe('Workspace', function () { waitsForPromise(() => atom.workspace.open('sample.js')) - return runs(() => expect(pane.getPendingItem()).toBeNull()) + runs(() => expect(pane.getPendingItem()).toBeNull()) }) - ) + }) - describe('when opening will switch from a pending tab to a permanent tab', () => - it('keeps the pending tab open', function () { + describe('when opening will switch from a pending tab to a permanent tab', () => { + it('keeps the pending tab open', () => { let editor1 = null let editor2 = null waitsForPromise(() => - atom.workspace.open('sample.txt').then(o => (editor1 = o)) + atom.workspace.open('sample.txt').then(o => { editor1 = o }) ) waitsForPromise(() => - atom.workspace.open('sample2.txt', {pending: true}).then(o => (editor2 = o)) + atom.workspace.open('sample2.txt', {pending: true}).then(o => { editor2 = o }) ) - return runs(function () { + runs(() => { const pane = atom.workspace.getActivePane() pane.activateItem(editor1) expect(pane.getItems().length).toBe(2) - return expect(pane.getItems()).toEqual([editor1, editor2]) + expect(pane.getItems()).toEqual([editor1, editor2]) }) }) - ) + }) - return describe('when replacing a pending item which is the last item in a second pane', () => - it('does not destroy the pane even if core.destroyEmptyPanes is on', function () { + describe('when replacing a pending item which is the last item in a second pane', () => { + it('does not destroy the pane even if core.destroyEmptyPanes is on', () => { atom.config.set('core.destroyEmptyPanes', true) let editor1 = null let editor2 = null @@ -721,46 +742,46 @@ describe('Workspace', function () { let rightPane = null waitsForPromise(() => - atom.workspace.open('sample.js', {pending: true, split: 'right'}).then(function (o) { + atom.workspace.open('sample.js', {pending: true, split: 'right'}).then(o => { editor1 = o rightPane = atom.workspace.getActivePane() - return spyOn(rightPane, 'destroyed') + spyOn(rightPane, 'destroyed') }) ) - runs(function () { + runs(() => { expect(leftPane).not.toBe(rightPane) expect(atom.workspace.getActivePane()).toBe(rightPane) expect(atom.workspace.getActivePane().getItems().length).toBe(1) - return expect(rightPane.getPendingItem()).toBe(editor1) + expect(rightPane.getPendingItem()).toBe(editor1) }) waitsForPromise(() => - atom.workspace.open('sample.txt', {pending: true}).then(o => (editor2 = o)) + atom.workspace.open('sample.txt', {pending: true}).then(o => { editor2 = o }) ) - return runs(function () { + runs(() => { expect(rightPane.getPendingItem()).toBe(editor2) - return expect(rightPane.destroyed.callCount).toBe(0) + expect(rightPane.destroyed.callCount).toBe(0) }) }) - ) + }) }) - describe('the grammar-used hook', () => - it('fires when opening a file or changing the grammar of an open file', function () { + describe('the grammar-used hook', () => { + it('fires when opening a file or changing the grammar of an open file', () => { let editor = null let javascriptGrammarUsed = false let coffeescriptGrammarUsed = false atom.packages.triggerDeferredActivationHooks() - runs(function () { - atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', () => (javascriptGrammarUsed = true)) - return atom.packages.onDidTriggerActivationHook('language-coffee-script:grammar-used', () => (coffeescriptGrammarUsed = true)) + runs(() => { + atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', () => { javascriptGrammarUsed = true }) + atom.packages.onDidTriggerActivationHook('language-coffee-script:grammar-used', () => { coffeescriptGrammarUsed = true }) }) - waitsForPromise(() => atom.workspace.open('sample.js', {autoIndent: false}).then(o => (editor = o))) + waitsForPromise(() => atom.workspace.open('sample.js', {autoIndent: false}).then(o => { editor = o })) waitsForPromise(() => atom.packages.activatePackage('language-javascript')) @@ -770,12 +791,12 @@ describe('Workspace', function () { runs(() => editor.setGrammar(atom.grammars.selectGrammar('.coffee'))) - return waitsFor(() => coffeescriptGrammarUsed) + waitsFor(() => coffeescriptGrammarUsed) }) - ) + }) - describe('::reopenItem()', () => - it("opens the uri associated with the last closed pane that isn't currently open", function () { + describe('::reopenItem()', () => { + it("opens the uri associated with the last closed pane that isn't currently open", () => { const pane = workspace.getActivePane() waitsForPromise(() => workspace.open('a').then(() => @@ -785,46 +806,49 @@ describe('Workspace', function () { ) ) - runs(function () { + runs(() => { // does not reopen items with no uri expect(workspace.getActivePaneItem().getURI()).toBeUndefined() - return pane.destroyActiveItem() + pane.destroyActiveItem() }) waitsForPromise(() => workspace.reopenItem()) - runs(function () { + const firstDirectory = atom.project.getDirectories()[0] + expect(firstDirectory).toBeDefined() + + runs(() => { expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined() // destroy all items - expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1'))) + expect(workspace.getActivePaneItem().getURI()).toBe(firstDirectory.resolve('file1')) pane.destroyActiveItem() - expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x1 => x1.resolve('b'))) + expect(workspace.getActivePaneItem().getURI()).toBe(firstDirectory.resolve('b')) pane.destroyActiveItem() - expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x2 => x2.resolve('a'))) + expect(workspace.getActivePaneItem().getURI()).toBe(firstDirectory.resolve('a')) pane.destroyActiveItem() // reopens items with uris - return expect(workspace.getActivePaneItem()).toBeUndefined() + expect(workspace.getActivePaneItem()).toBeUndefined() }) waitsForPromise(() => workspace.reopenItem()) - runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a')))) + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(firstDirectory.resolve('a'))) // does not reopen items that are already open waitsForPromise(() => workspace.open('b')) - runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('b')))) + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(firstDirectory.resolve('b'))) waitsForPromise(() => workspace.reopenItem()) - return runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('file1')))) + runs(() => expect(workspace.getActivePaneItem().getURI()).toBe(firstDirectory.resolve('file1'))) }) - ) + }) - describe('::increase/decreaseFontSize()', () => - it('increases/decreases the font size without going below 1', function () { + describe('::increase/decreaseFontSize()', () => { + it('increases/decreases the font size without going below 1', () => { atom.config.set('editor.fontSize', 1) workspace.increaseFontSize() expect(atom.config.get('editor.fontSize')).toBe(2) @@ -835,12 +859,12 @@ describe('Workspace', function () { workspace.decreaseFontSize() expect(atom.config.get('editor.fontSize')).toBe(1) workspace.decreaseFontSize() - return expect(atom.config.get('editor.fontSize')).toBe(1) + expect(atom.config.get('editor.fontSize')).toBe(1) }) - ) + }) - describe('::resetFontSize()', function () { - it("resets the font size to the window's starting font size", function () { + describe('::resetFontSize()', () => { + it("resets the font size to the window's starting font size", () => { const originalFontSize = atom.config.get('editor.fontSize') workspace.increaseFontSize() @@ -850,17 +874,17 @@ describe('Workspace', function () { workspace.decreaseFontSize() expect(atom.config.get('editor.fontSize')).toBe(originalFontSize - 1) workspace.resetFontSize() - return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) }) - it('does nothing if the font size has not been changed', function () { + it('does nothing if the font size has not been changed', () => { const originalFontSize = atom.config.get('editor.fontSize') workspace.resetFontSize() - return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) }) - return it("resets the font size when the editor's font size changes", function () { + it("resets the font size when the editor's font size changes", () => { const originalFontSize = atom.config.get('editor.fontSize') atom.config.set('editor.fontSize', originalFontSize + 1) @@ -868,28 +892,28 @@ describe('Workspace', function () { expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) atom.config.set('editor.fontSize', originalFontSize - 1) workspace.resetFontSize() - return expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) }) }) - describe('::openLicense()', () => - it('opens the license as plain-text in a buffer', function () { + describe('::openLicense()', () => { + it('opens the license as plain-text in a buffer', () => { waitsForPromise(() => workspace.openLicense()) - return runs(() => expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/)) + runs(() => expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/)) }) - ) + }) - describe('::isTextEditor(obj)', () => - it('returns true when the passed object is an instance of `TextEditor`', function () { + describe('::isTextEditor(obj)', () => { + it('returns true when the passed object is an instance of `TextEditor`', () => { expect(workspace.isTextEditor(new TextEditor())).toBe(true) - expect(workspace.isTextEditor({getText () { return null }})).toBe(false) + expect(workspace.isTextEditor({getText: () => null})).toBe(false) expect(workspace.isTextEditor(null)).toBe(false) - return expect(workspace.isTextEditor(undefined)).toBe(false) + expect(workspace.isTextEditor(undefined)).toBe(false) }) - ) + }) - describe('::observeTextEditors()', () => - it('invokes the observer with current and future text editors', function () { + describe('::observeTextEditors()', () => { + it('invokes the observer with current and future text editors', () => { const observed = [] waitsForPromise(() => workspace.open()) @@ -900,43 +924,43 @@ describe('Workspace', function () { waitsForPromise(() => workspace.open()) - return expect(observed).toEqual(workspace.getTextEditors()) + expect(observed).toEqual(workspace.getTextEditors()) }) - ) + }) - describe('when an editor is destroyed', () => - it('removes the editor', function () { + describe('when an editor is destroyed', () => { + it('removes the editor', () => { let editor = null - waitsForPromise(() => workspace.open('a').then(e => (editor = e))) + waitsForPromise(() => workspace.open('a').then(e => { editor = e })) - return runs(function () { + runs(() => { expect(workspace.getTextEditors()).toHaveLength(1) editor.destroy() - return expect(workspace.getTextEditors()).toHaveLength(0) + expect(workspace.getTextEditors()).toHaveLength(0) }) }) - ) + }) - describe('when an editor is copied because its pane is split', () => - it('sets up the new editor to be configured by the text editor registry', function () { + describe('when an editor is copied because its pane is split', () => { + it('sets up the new editor to be configured by the text editor registry', () => { waitsForPromise(() => atom.packages.activatePackage('language-javascript')) - return waitsForPromise(() => - workspace.open('a').then(function (editor) { + waitsForPromise(() => + workspace.open('a').then(editor => { atom.textEditors.setGrammarOverride(editor, 'source.js') expect(editor.getGrammar().name).toBe('JavaScript') workspace.getActivePane().splitRight({copyActiveItem: true}) const newEditor = workspace.getActiveTextEditor() expect(newEditor).not.toBe(editor) - return expect(newEditor.getGrammar().name).toBe('JavaScript') + expect(newEditor.getGrammar().name).toBe('JavaScript') }) ) }) - ) + }) - it('stores the active grammars used by all the open editors', function () { + it('stores the active grammars used by all the open editors', () => { waitsForPromise(() => atom.packages.activatePackage('language-javascript')) waitsForPromise(() => atom.packages.activatePackage('language-coffee-script')) @@ -945,7 +969,7 @@ describe('Workspace', function () { waitsForPromise(() => atom.workspace.open('sample.coffee')) - return runs(function () { + runs(function () { atom.workspace.getActiveTextEditor().setText(`\ i = /test/; #FIXME\ ` @@ -979,116 +1003,116 @@ i = /test/; #FIXME\ 'TODO' ]) - return atom2.destroy() + atom2.destroy() }) }) - describe('document.title', function () { - describe('when there is no item open', function () { + describe('document.title', () => { + describe('when there is no item open', () => { it('sets the title to the project path', () => expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0])))) - return it("sets the title to 'untitled' if there is no project path", function () { + it("sets the title to 'untitled' if there is no project path", () => { atom.project.setPaths([]) - return expect(document.title).toMatch(/^untitled/) + expect(document.title).toMatch(/^untitled/) }) }) - describe("when the active pane item's path is not inside a project path", function () { + describe("when the active pane item's path is not inside a project path", () => { beforeEach(() => waitsForPromise(() => atom.workspace.open('b').then(() => atom.project.setPaths([])) ) ) - it("sets the title to the pane item's title plus the item's path", function () { + it("sets the title to the pane item's title plus the item's path", () => { const item = atom.workspace.getActivePaneItem() const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))) - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - describe('when the title of the active pane item changes', () => - it("updates the window title based on the item's new title", function () { + describe('when the title of the active pane item changes', () => { + it("updates the window title based on the item's new title", () => { const editor = atom.workspace.getActivePaneItem() editor.buffer.setPath(path.join(temp.dir, 'hi')) const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(editor.getPath()))) - return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)) }) - ) + }) - describe("when the active pane's item changes", () => - it("updates the title to the new item's title plus the project path", function () { + describe("when the active pane's item changes", () => { + it("updates the title to the new item's title plus the project path", () => { atom.workspace.getActivePane().activateNextItem() const item = atom.workspace.getActivePaneItem() const pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath()))) - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - ) + }) - return describe("when an inactive pane's item changes", () => - it('does not update the title', function () { + describe("when an inactive pane's item changes", () => { + it('does not update the title', () => { const pane = atom.workspace.getActivePane() pane.splitRight() const initialTitle = document.title pane.activateNextItem() - return expect(document.title).toBe(initialTitle) + expect(document.title).toBe(initialTitle) }) - ) + }) }) - describe('when the active pane item is inside a project path', function () { + describe('when the active pane item is inside a project path', () => { beforeEach(() => waitsForPromise(() => atom.workspace.open('b')) ) - describe('when there is an active pane item', () => - it("sets the title to the pane item's title plus the project path", function () { + describe('when there is an active pane item', () => { + it("sets the title to the pane item's title plus the project path", () => { const item = atom.workspace.getActivePaneItem() const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - ) + }) - describe('when the title of the active pane item changes', () => - it("updates the window title based on the item's new title", function () { + describe('when the title of the active pane item changes', () => { + it("updates the window title based on the item's new title", () => { const editor = atom.workspace.getActivePaneItem() editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')) const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - return expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${editor.getTitle()} \\u2014 ${pathEscaped}`)) }) - ) + }) - describe("when the active pane's item changes", () => - it("updates the title to the new item's title plus the project path", function () { + describe("when the active pane's item changes", () => { + it("updates the title to the new item's title plus the project path", () => { atom.workspace.getActivePane().activateNextItem() const item = atom.workspace.getActivePaneItem() const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) - return expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) + expect(document.title).toMatch(new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`)) }) - ) + }) - describe('when the last pane item is removed', () => - it("updates the title to the project's first path", function () { + describe('when the last pane item is removed', () => { + it("updates the title to the project's first path", () => { atom.workspace.getActivePane().destroy() expect(atom.workspace.getActivePaneItem()).toBeUndefined() - return expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))) + expect(document.title).toMatch(escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))) }) - ) + }) - return describe("when an inactive pane's item changes", () => - it('does not update the title', function () { + describe("when an inactive pane's item changes", () => { + it('does not update the title', () => { const pane = atom.workspace.getActivePane() pane.splitRight() const initialTitle = document.title pane.activateNextItem() - return expect(document.title).toBe(initialTitle) + expect(document.title).toBe(initialTitle) }) - ) + }) }) - return describe('when the workspace is deserialized', function () { + describe('when the workspace is deserialized', () => { beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))) - return it("updates the title to contain the project's path", function () { + it("updates the title to contain the project's path", () => { document.title = null const atom2 = new AtomEnvironment({ @@ -1109,30 +1133,33 @@ i = /test/; #FIXME\ const pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch(new RegExp(`^${item.getLongTitle()} \\u2014 ${pathEscaped}`)) - return atom2.destroy() + atom2.destroy() }) }) }) - describe('document edited status', function () { - let [item1, item2] = Array.from([]) + describe('document edited status', () => { + let item1 + let item2 - beforeEach(function () { + beforeEach(() => { waitsForPromise(() => atom.workspace.open('a')) waitsForPromise(() => atom.workspace.open('b')) - return runs(() => ([item1, item2] = Array.from(atom.workspace.getPaneItems()))) + runs(() => { + [item1, item2] = atom.workspace.getPaneItems() + }) }) - it('calls setDocumentEdited when the active item changes', function () { + it('calls setDocumentEdited when the active item changes', () => { expect(atom.workspace.getActivePaneItem()).toBe(item2) item1.insertText('a') expect(item1.isModified()).toBe(true) atom.workspace.getActivePane().activateNextItem() - return expect(setDocumentEdited).toHaveBeenCalledWith(true) + expect(setDocumentEdited).toHaveBeenCalledWith(true) }) - return it("calls atom.setDocumentEdited when the active item's modified status changes", function () { + it("calls atom.setDocumentEdited when the active item's modified status changes", () => { expect(atom.workspace.getActivePaneItem()).toBe(item2) item2.insertText('a') advanceClock(item2.getBuffer().getStoppedChangingDelay()) @@ -1144,11 +1171,11 @@ i = /test/; #FIXME\ advanceClock(item2.getBuffer().getStoppedChangingDelay()) expect(item2.isModified()).toBe(false) - return expect(setDocumentEdited).toHaveBeenCalledWith(false) + expect(setDocumentEdited).toHaveBeenCalledWith(false) }) }) - describe('adding panels', function () { + describe('adding panels', () => { class TestItem {} // Don't use ES6 classes because then we'll have to call `super()` which we can't do with @@ -1165,8 +1192,8 @@ i = /test/; #FIXME\ atom.views.addViewProvider(TestItem, model => new TestItemElement().initialize(model)) ) - describe('::addLeftPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addLeftPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getLeftPanels().length).toBe(0) atom.workspace.panelContainers.left.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1179,12 +1206,12 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getLeftPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - describe('::addRightPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addRightPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getRightPanels().length).toBe(0) atom.workspace.panelContainers.right.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1197,12 +1224,12 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getRightPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - describe('::addTopPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addTopPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getTopPanels().length).toBe(0) atom.workspace.panelContainers.top.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1215,12 +1242,12 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getTopPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - describe('::addBottomPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addBottomPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getBottomPanels().length).toBe(0) atom.workspace.panelContainers.bottom.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1233,12 +1260,12 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getBottomPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - describe('::addHeaderPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addHeaderPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getHeaderPanels().length).toBe(0) atom.workspace.panelContainers.header.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1251,12 +1278,12 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getHeaderPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - describe('::addFooterPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addFooterPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getFooterPanels().length).toBe(0) atom.workspace.panelContainers.footer.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1269,12 +1296,12 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getFooterPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - describe('::addModalPanel(model)', () => - it('adds a panel to the correct panel container', function () { + describe('::addModalPanel(model)', () => { + it('adds a panel to the correct panel container', () => { let addPanelSpy expect(atom.workspace.getModalPanels().length).toBe(0) atom.workspace.panelContainers.modal.onDidAddPanel(addPanelSpy = jasmine.createSpy()) @@ -1287,36 +1314,36 @@ i = /test/; #FIXME\ const itemView = atom.views.getView(atom.workspace.getModalPanels()[0].getItem()) expect(itemView instanceof TestItemElement).toBe(true) - return expect(itemView.getModel()).toBe(model) + expect(itemView.getModel()).toBe(model) }) - ) + }) - return describe('::panelForItem(item)', () => - it('returns the panel associated with the item', function () { + describe('::panelForItem(item)', () => { + it('returns the panel associated with the item', () => { const item = new TestItem() const panel = atom.workspace.addLeftPanel({item}) const itemWithNoPanel = new TestItem() expect(atom.workspace.panelForItem(item)).toBe(panel) - return expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null) + expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null) }) - ) + }) }) - describe('::scan(regex, options, callback)', () => - describe('when called with a regex', function () { - it('calls the callback with all regex results in all files in the project', function () { + describe('::scan(regex, options, callback)', () => { + describe('when called with a regex', () => { + it('calls the callback with all regex results in all files in the project', () => { const results = [] waitsForPromise(() => atom.workspace.scan(/(a)+/, result => results.push(result)) ) - return runs(function () { + runs(() => { expect(results).toHaveLength(3) - expect(results[0].filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))) + expect(results[0].filePath).toBe(atom.project.getDirectories()[0].resolve('a')) expect(results[0].matches).toHaveLength(3) - return expect(results[0].matches[0]).toEqual({ + expect(results[0].matches[0]).toEqual({ matchText: 'aaa', lineText: 'aaa bbb', lineTextOffset: 0, @@ -1325,17 +1352,16 @@ i = /test/; #FIXME\ }) }) - it('works with with escaped literals (like $ and ^)', function () { + it('works with with escaped literals (like $ and ^)', () => { const results = [] waitsForPromise(() => atom.workspace.scan(/\$\w+/, result => results.push(result))) - return runs(function () { + runs(() => { expect(results.length).toBe(1) - const {filePath, matches} = results[0] - expect(filePath).toBe(__guard__(atom.project.getDirectories()[0], x => x.resolve('a'))) + expect(filePath).toBe(atom.project.getDirectories()[0].resolve('a')) expect(matches).toHaveLength(1) - return expect(matches[0]).toEqual({ + expect(matches[0]).toEqual({ matchText: '$bill', lineText: 'dollar$bill', lineTextOffset: 0, @@ -1344,72 +1370,75 @@ i = /test/; #FIXME\ }) }) - it('works on evil filenames', function () { + it('works on evil filenames', () => { atom.config.set('core.excludeVcsIgnoredPaths', false) platform.generateEvilFiles() atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')]) const paths = [] let matches = [] waitsForPromise(() => - atom.workspace.scan(/evil/, function (result) { + atom.workspace.scan(/evil/, result => { paths.push(result.filePath) - return (matches = matches.concat(result.matches)) + matches = matches.concat(result.matches) }) ) - return runs(function () { + runs(() => { _.each(matches, m => expect(m.matchText).toEqual('evil')) if (platform.isWindows()) { expect(paths.length).toBe(3) expect(paths[0]).toMatch(/a_file_with_utf8.txt$/) expect(paths[1]).toMatch(/file with spaces.txt$/) - return expect(path.basename(paths[2])).toBe('utfa\u0306.md') + expect(path.basename(paths[2])).toBe('utfa\u0306.md') } else { expect(paths.length).toBe(5) expect(paths[0]).toMatch(/a_file_with_utf8.txt$/) expect(paths[1]).toMatch(/file with spaces.txt$/) expect(paths[2]).toMatch(/goddam\nnewlines$/m) expect(paths[3]).toMatch(/quote".txt$/m) - return expect(path.basename(paths[4])).toBe('utfa\u0306.md') + expect(path.basename(paths[4])).toBe('utfa\u0306.md') } }) }) - it('ignores case if the regex includes the `i` flag', function () { + it('ignores case if the regex includes the `i` flag', () => { const results = [] waitsForPromise(() => atom.workspace.scan(/DOLLAR/i, result => results.push(result))) - return runs(() => expect(results).toHaveLength(1)) + runs(() => expect(results).toHaveLength(1)) }) - describe('when the core.excludeVcsIgnoredPaths config is truthy', function () { - let [projectPath, ignoredPath] = Array.from([]) + describe('when the core.excludeVcsIgnoredPaths config is truthy', () => { + let projectPath + let ignoredPath - beforeEach(function () { + beforeEach(() => { const sourceProjectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir') projectPath = path.join(temp.mkdirSync('atom')) const writerStream = fstream.Writer(projectPath) fstream.Reader(sourceProjectPath).pipe(writerStream) - waitsFor(function (done) { + waitsFor(done => { writerStream.on('close', done) - return writerStream.on('error', done) + writerStream.on('error', done) }) - return runs(function () { + runs(() => { fs.rename(path.join(projectPath, 'git.git'), path.join(projectPath, '.git')) ignoredPath = path.join(projectPath, 'ignored.txt') - return fs.writeFileSync(ignoredPath, 'this match should not be included') + fs.writeFileSync(ignoredPath, 'this match should not be included') }) }) - afterEach(function () { - if (fs.existsSync(projectPath)) { return fs.removeSync(projectPath) } + afterEach(() => { + if (fs.existsSync(projectPath)) { + fs.removeSync(projectPath) + } }) - return it('excludes ignored files', function () { + it('excludes ignored files', () => { atom.project.setPaths([projectPath]) atom.config.set('core.excludeVcsIgnoredPaths', true) const resultHandler = jasmine.createSpy('result found') @@ -1417,11 +1446,11 @@ i = /test/; #FIXME\ atom.workspace.scan(/match/, results => resultHandler()) ) - return runs(() => expect(resultHandler).not.toHaveBeenCalled()) + runs(() => expect(resultHandler).not.toHaveBeenCalled()) }) }) - it('includes only files when a directory filter is specified', function () { + it('includes only files when a directory filter is specified', () => { const projectPath = path.join(path.join(__dirname, 'fixtures', 'dir')) atom.project.setPaths([projectPath]) @@ -1430,20 +1459,20 @@ i = /test/; #FIXME\ const paths = [] let matches = [] waitsForPromise(() => - atom.workspace.scan(/aaa/, {paths: [`a-dir${path.sep}`]}, function (result) { + atom.workspace.scan(/aaa/, {paths: [`a-dir${path.sep}`]}, result => { paths.push(result.filePath) - return (matches = matches.concat(result.matches)) + matches = matches.concat(result.matches) }) ) - return runs(function () { + runs(() => { expect(paths.length).toBe(1) expect(paths[0]).toBe(filePath) - return expect(matches.length).toBe(1) + expect(matches.length).toBe(1) }) }) - it("includes files and folders that begin with a '.'", function () { + it("includes files and folders that begin with a '.'", () => { const projectPath = temp.mkdirSync('atom-spec-workspace') const filePath = path.join(projectPath, '.text') fs.writeFileSync(filePath, 'match this') @@ -1451,20 +1480,20 @@ i = /test/; #FIXME\ const paths = [] let matches = [] waitsForPromise(() => - atom.workspace.scan(/match this/, function (result) { + atom.workspace.scan(/match this/, result => { paths.push(result.filePath) - return (matches = matches.concat(result.matches)) + matches = matches.concat(result.matches) }) ) - return runs(function () { + runs(() => { expect(paths.length).toBe(1) expect(paths[0]).toBe(filePath) - return expect(matches.length).toBe(1) + expect(matches.length).toBe(1) }) }) - it('excludes values in core.ignoredNames', function () { + it('excludes values in core.ignoredNames', () => { const ignoredNames = atom.config.get('core.ignoredNames') ignoredNames.push('a') atom.config.set('core.ignoredNames', ignoredNames) @@ -1474,51 +1503,54 @@ i = /test/; #FIXME\ atom.workspace.scan(/dollar/, results => resultHandler()) ) - return runs(() => expect(resultHandler).not.toHaveBeenCalled()) + runs(() => expect(resultHandler).not.toHaveBeenCalled()) }) - it('scans buffer contents if the buffer is modified', function () { + it('scans buffer contents if the buffer is modified', () => { let editor = null const results = [] waitsForPromise(() => - atom.workspace.open('a').then(function (o) { + atom.workspace.open('a').then(o => { editor = o - return editor.setText('Elephant') + editor.setText('Elephant') }) ) waitsForPromise(() => atom.workspace.scan(/a|Elephant/, result => results.push(result))) - return runs(function () { + runs(() => { expect(results).toHaveLength(3) const resultForA = _.find(results, ({filePath}) => path.basename(filePath) === 'a') expect(resultForA.matches).toHaveLength(1) - return expect(resultForA.matches[0].matchText).toBe('Elephant') + expect(resultForA.matches[0].matchText).toBe('Elephant') }) }) - it('ignores buffers outside the project', function () { + it('ignores buffers outside the project', () => { let editor = null const results = [] waitsForPromise(() => - atom.workspace.open(temp.openSync().path).then(function (o) { + atom.workspace.open(temp.openSync().path).then(o => { editor = o - return editor.setText('Elephant') + editor.setText('Elephant') }) ) waitsForPromise(() => atom.workspace.scan(/Elephant/, result => results.push(result))) - return runs(() => expect(results).toHaveLength(0)) + runs(() => expect(results).toHaveLength(0)) }) - return describe('when the project has multiple root directories', function () { - let [dir1, dir2, file1, file2] = Array.from([]) + describe('when the project has multiple root directories', () => { + let dir1 + let dir2 + let file1 + let file2 - beforeEach(function () { - [dir1] = Array.from(atom.project.getPaths()) + beforeEach(() => { + dir1 = atom.project.getPaths()[0] file1 = path.join(dir1, 'a-dir', 'oh-git') dir2 = temp.mkdirSync('a-second-dir') @@ -1527,59 +1559,67 @@ i = /test/; #FIXME\ fs.mkdirSync(aDir2) fs.writeFileSync(file2, 'ccc aaaa') - return atom.project.addPath(dir2) + atom.project.addPath(dir2) }) - it("searches matching files in all of the project's root directories", function () { + it("searches matching files in all of the project's root directories", () => { const resultPaths = [] waitsForPromise(() => atom.workspace.scan(/aaaa/, ({filePath}) => resultPaths.push(filePath)) ) - return runs(() => expect(resultPaths.sort()).toEqual([file1, file2].sort())) + runs(() => expect(resultPaths.sort()).toEqual([file1, file2].sort())) }) - describe('when an inclusion path starts with the basename of a root directory', () => - it('interprets the inclusion path as starting from that directory', function () { - waitsForPromise(function () { + describe('when an inclusion path starts with the basename of a root directory', () => { + it('interprets the inclusion path as starting from that directory', () => { + waitsForPromise(() => { const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: ['dir']}, function ({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + .scan(/aaaa/, {paths: ['dir']}, ({filePath}) => { + if (!resultPaths.includes(filePath)) { + resultPaths.push(filePath) + } }) .then(() => expect(resultPaths).toEqual([file1])) }) - waitsForPromise(function () { + waitsForPromise(() => { const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: [path.join('dir', 'a-dir')]}, function ({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + .scan(/aaaa/, {paths: [path.join('dir', 'a-dir')]}, ({filePath}) => { + if (!resultPaths.includes(filePath)) { + resultPaths.push(filePath) + } }) .then(() => expect(resultPaths).toEqual([file1])) }) - waitsForPromise(function () { + waitsForPromise(() => { const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: [path.basename(dir2)]}, function ({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + .scan(/aaaa/, {paths: [path.basename(dir2)]}, ({filePath}) => { + if (!resultPaths.includes(filePath)) { + resultPaths.push(filePath) + } }) .then(() => expect(resultPaths).toEqual([file2])) }) - return waitsForPromise(function () { + waitsForPromise(() => { const resultPaths = [] return atom.workspace - .scan(/aaaa/, {paths: [path.join(path.basename(dir2), 'a-dir')]}, function ({filePath}) { - if (!Array.from(resultPaths).includes(filePath)) { return resultPaths.push(filePath) } + .scan(/aaaa/, {paths: [path.join(path.basename(dir2), 'a-dir')]}, ({filePath}) => { + if (!resultPaths.includes(filePath)) { + resultPaths.push(filePath) + } }) .then(() => expect(resultPaths).toEqual([file2])) }) }) - ) + }) - return describe('when a custom directory searcher is registered', function () { + describe('when a custom directory searcher is registered', () => { let fakeSearch = null // Function that is invoked once all of the fields on fakeSearch are set. let onFakeSearchCreated = null @@ -1588,11 +1628,13 @@ i = /test/; #FIXME\ constructor (options) { // Note that hoisting resolve and reject in this way is generally frowned upon. this.options = options - this.promise = new Promise((function (resolve, reject) { + this.promise = new Promise((resolve, reject) => { this.hoistedResolve = resolve this.hoistedReject = reject - return (typeof onFakeSearchCreated === 'function' ? onFakeSearchCreated(this) : undefined) - }.bind(this))) + if (typeof onFakeSearchCreated === 'function') { + onFakeSearchCreated(this) + } + }) } then (...args) { return this.promise.then.apply(this.promise, args) @@ -1601,22 +1643,25 @@ i = /test/; #FIXME\ this.cancelled = true // According to the spec for a DirectorySearcher, invoking `cancel()` should // resolve the thenable rather than reject it. - return this.hoistedResolve() + this.hoistedResolve() } } - beforeEach(function () { + beforeEach(() => { fakeSearch = null onFakeSearchCreated = null atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { canSearchDirectory (directory) { return directory.getPath() === dir1 }, - search (directory, regex, options) { return (fakeSearch = new FakeSearch(options)) } + search (directory, regex, options) { + fakeSearch = new FakeSearch(options) + return fakeSearch + } }) - return waitsFor(() => atom.workspace.directorySearchers.length > 0) + waitsFor(() => atom.workspace.directorySearchers.length > 0) }) - it('can override the DefaultDirectorySearcher on a per-directory basis', function () { + it('can override the DefaultDirectorySearcher on a per-directory basis', () => { const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt' const numPathsSearchedInDir2 = 1 const numPathsToPretendToSearchInCustomDirectorySearcher = 10 @@ -1631,10 +1676,10 @@ i = /test/; #FIXME\ } ] } - onFakeSearchCreated = function (fakeSearch) { + onFakeSearchCreated = fakeSearch => { fakeSearch.options.didMatch(searchResult) fakeSearch.options.didSearchPaths(numPathsToPretendToSearchInCustomDirectorySearcher) - return fakeSearch.hoistedResolve() + fakeSearch.hoistedResolve() } const resultPaths = [] @@ -1643,82 +1688,88 @@ i = /test/; #FIXME\ atom.workspace.scan(/aaaa/, {onPathsSearched}, ({filePath}) => resultPaths.push(filePath)) ) - return runs(function () { + runs(() => { expect(resultPaths.sort()).toEqual([foreignFilePath, file2].sort()) // onPathsSearched should be called once by each DirectorySearcher. The order is not // guaranteed, so we can only verify the total number of paths searched is correct // after the second call. expect(onPathsSearched.callCount).toBe(2) - return expect(onPathsSearched.mostRecentCall.args[0]).toBe( + expect(onPathsSearched.mostRecentCall.args[0]).toBe( numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2) }) }) - it('can be cancelled when the object returned by scan() has its cancel() method invoked', function () { - const thenable = atom.workspace.scan(/aaaa/, function () {}) + it('can be cancelled when the object returned by scan() has its cancel() method invoked', () => { + const thenable = atom.workspace.scan(/aaaa/, () => {}) let resultOfPromiseSearch = null waitsFor('fakeSearch to be defined', () => fakeSearch != null) - runs(function () { + runs(() => { expect(fakeSearch.cancelled).toBe(undefined) thenable.cancel() - return expect(fakeSearch.cancelled).toBe(true) + expect(fakeSearch.cancelled).toBe(true) }) - waitsForPromise(() => thenable.then(promiseResult => (resultOfPromiseSearch = promiseResult))) + waitsForPromise(() => thenable.then(promiseResult => { resultOfPromiseSearch = promiseResult })) - return runs(() => expect(resultOfPromiseSearch).toBe('cancelled')) + runs(() => expect(resultOfPromiseSearch).toBe('cancelled')) }) - return it('will have the side-effect of failing the overall search if it fails', function () { + it('will have the side-effect of failing the overall search if it fails', () => { // This provider's search should be cancelled when the first provider fails let cancelableSearch let fakeSearch2 = null atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', { canSearchDirectory (directory) { return directory.getPath() === dir2 }, - search (directory, regex, options) { return (fakeSearch2 = new FakeSearch(options)) } + search (directory, regex, options) { + fakeSearch2 = new FakeSearch(options) + return fakeSearch2 + } }) let didReject = false - const promise = cancelableSearch = atom.workspace.scan(/aaaa/, function () {}) + const promise = cancelableSearch = atom.workspace.scan(/aaaa/, () => {}) waitsFor('fakeSearch to be defined', () => fakeSearch != null) runs(() => fakeSearch.hoistedReject()) - waitsForPromise(() => cancelableSearch.catch(() => (didReject = true))) + waitsForPromise(() => cancelableSearch.catch(() => { didReject = true })) waitsFor(done => promise.then(null, done)) - return runs(function () { + runs(() => { expect(didReject).toBe(true) - return expect(fakeSearch2.cancelled).toBe(true) + expect(fakeSearch2.cancelled).toBe(true) }) }) }) }) }) - ) // Cancels other ongoing searches + }) // Cancels other ongoing searches - describe('::replace(regex, replacementText, paths, iterator)', function () { - let [filePath, commentFilePath, sampleContent, sampleCommentContent] = Array.from([]) + describe('::replace(regex, replacementText, paths, iterator)', () => { + let filePath + let commentFilePath + let sampleContent + let sampleCommentContent - beforeEach(function () { - atom.project.setPaths([__guard__(atom.project.getDirectories()[0], x => x.resolve('../'))]) + beforeEach(() => { + atom.project.setPaths([atom.project.getDirectories()[0].resolve('../')]) - filePath = __guard__(atom.project.getDirectories()[0], x1 => x1.resolve('sample.js')) - commentFilePath = __guard__(atom.project.getDirectories()[0], x2 => x2.resolve('sample-with-comments.js')) + filePath = atom.project.getDirectories()[0].resolve('sample.js') + commentFilePath = atom.project.getDirectories()[0].resolve('sample-with-comments.js') sampleContent = fs.readFileSync(filePath).toString() - return (sampleCommentContent = fs.readFileSync(commentFilePath).toString()) + sampleCommentContent = fs.readFileSync(commentFilePath).toString() }) - afterEach(function () { + afterEach(() => { fs.writeFileSync(filePath, sampleContent) - return fs.writeFileSync(commentFilePath, sampleCommentContent) + fs.writeFileSync(commentFilePath, sampleCommentContent) }) - describe("when a file doesn't exist", () => - it('calls back with an error', function () { + describe("when a file doesn't exist", () => { + it('calls back with an error', () => { const errors = [] const missingPath = path.resolve('/not-a-file.js') expect(fs.existsSync(missingPath)).toBeFalsy() @@ -1727,34 +1778,34 @@ i = /test/; #FIXME\ atom.workspace.replace(/items/gi, 'items', [missingPath], (result, error) => errors.push(error)) ) - return runs(function () { + runs(() => { expect(errors).toHaveLength(1) - return expect(errors[0].path).toBe(missingPath) + expect(errors[0].path).toBe(missingPath) }) }) - ) + }) - describe('when called with unopened files', () => - it('replaces properly', function () { + describe('when called with unopened files', () => { + it('replaces properly', () => { const results = [] waitsForPromise(() => atom.workspace.replace(/items/gi, 'items', [filePath], result => results.push(result)) ) - return runs(function () { + runs(() => { expect(results).toHaveLength(1) expect(results[0].filePath).toBe(filePath) - return expect(results[0].replacements).toBe(6) + expect(results[0].replacements).toBe(6) }) }) - ) + }) - return describe('when a buffer is already open', function () { - it('replaces properly and saves when not modified', function () { + describe('when a buffer is already open', () => { + it('replaces properly and saves when not modified', () => { let editor = null const results = [] - waitsForPromise(() => atom.workspace.open('sample.js').then(o => (editor = o))) + waitsForPromise(() => atom.workspace.open('sample.js').then(o => { editor = o })) runs(() => expect(editor.isModified()).toBeFalsy()) @@ -1762,16 +1813,16 @@ i = /test/; #FIXME\ atom.workspace.replace(/items/gi, 'items', [filePath], result => results.push(result)) ) - return runs(function () { + runs(() => { expect(results).toHaveLength(1) expect(results[0].filePath).toBe(filePath) expect(results[0].replacements).toBe(6) - return expect(editor.isModified()).toBeFalsy() + expect(editor.isModified()).toBeFalsy() }) }) - it('does not replace when the path is not specified', function () { + it('does not replace when the path is not specified', () => { const results = [] waitsForPromise(() => atom.workspace.open('sample-with-comments.js')) @@ -1780,72 +1831,72 @@ i = /test/; #FIXME\ atom.workspace.replace(/items/gi, 'items', [commentFilePath], result => results.push(result)) ) - return runs(function () { + runs(() => { expect(results).toHaveLength(1) - return expect(results[0].filePath).toBe(commentFilePath) + expect(results[0].filePath).toBe(commentFilePath) }) }) - return it('does NOT save when modified', function () { + it('does NOT save when modified', () => { let editor = null const results = [] - waitsForPromise(() => atom.workspace.open('sample.js').then(o => (editor = o))) + waitsForPromise(() => atom.workspace.open('sample.js').then(o => { editor = o })) - runs(function () { + runs(() => { editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg') - return expect(editor.isModified()).toBeTruthy() + expect(editor.isModified()).toBeTruthy() }) waitsForPromise(() => atom.workspace.replace(/items/gi, 'okthen', [filePath], result => results.push(result)) ) - return runs(function () { + runs(() => { expect(results).toHaveLength(1) expect(results[0].filePath).toBe(filePath) expect(results[0].replacements).toBe(6) - return expect(editor.isModified()).toBeTruthy() + expect(editor.isModified()).toBeTruthy() }) }) }) }) - describe('::saveActivePaneItem()', function () { + describe('::saveActivePaneItem()', () => { let editor = null beforeEach(() => - waitsForPromise(() => atom.workspace.open('sample.js').then(o => (editor = o))) + waitsForPromise(() => atom.workspace.open('sample.js').then(o => { editor = o })) ) - return describe('when there is an error', function () { - it('emits a warning notification when the file cannot be saved', function () { + describe('when there is an error', () => { + it('emits a warning notification when the file cannot be saved', () => { let addedSpy - spyOn(editor, 'save').andCallFake(function () { + spyOn(editor, 'save').andCallFake(() => { throw new Error("'/some/file' is a directory") }) atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) atom.workspace.saveActivePaneItem() expect(addedSpy).toHaveBeenCalled() - return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') + expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') }) - it('emits a warning notification when the directory cannot be written to', function () { + it('emits a warning notification when the directory cannot be written to', () => { let addedSpy - spyOn(editor, 'save').andCallFake(function () { + spyOn(editor, 'save').andCallFake(() => { throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'") }) atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) atom.workspace.saveActivePaneItem() expect(addedSpy).toHaveBeenCalled() - return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') + expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') }) - it('emits a warning notification when the user does not have permission', function () { + it('emits a warning notification when the user does not have permission', () => { let addedSpy - spyOn(editor, 'save').andCallFake(function () { + spyOn(editor, 'save').andCallFake(() => { const error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'") error.code = 'EACCES' error.path = '/Some/dir/and-a-file.js' @@ -1855,21 +1906,21 @@ i = /test/; #FIXME\ atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy()) atom.workspace.saveActivePaneItem() expect(addedSpy).toHaveBeenCalled() - return expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') + expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning') }) - it('emits a warning notification when the operation is not permitted', () => - spyOn(editor, 'save').andCallFake(function () { + it('emits a warning notification when the operation is not permitted', () => { + spyOn(editor, 'save').andCallFake(() => { const error = new Error("EPERM, operation not permitted '/Some/dir/and-a-file.js'") error.code = 'EPERM' error.path = '/Some/dir/and-a-file.js' throw error }) - ) + }) - it('emits a warning notification when the file is already open by another app', function () { + it('emits a warning notification when the file is already open by another app', () => { let addedSpy - spyOn(editor, 'save').andCallFake(function () { + spyOn(editor, 'save').andCallFake(() => { const error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'") error.code = 'EBUSY' error.path = '/Some/dir/and-a-file.js' @@ -1882,12 +1933,12 @@ i = /test/; #FIXME\ const notificaiton = addedSpy.mostRecentCall.args[0] expect(notificaiton.getType()).toBe('warning') - return expect(notificaiton.getMessage()).toContain('Unable to save') + expect(notificaiton.getMessage()).toContain('Unable to save') }) - it('emits a warning notification when the file system is read-only', function () { + it('emits a warning notification when the file system is read-only', () => { let addedSpy - spyOn(editor, 'save').andCallFake(function () { + spyOn(editor, 'save').andCallFake(() => { const error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'") error.code = 'EROFS' error.path = '/Some/dir/and-a-file.js' @@ -1900,27 +1951,27 @@ i = /test/; #FIXME\ const notification = addedSpy.mostRecentCall.args[0] expect(notification.getType()).toBe('warning') - return expect(notification.getMessage()).toContain('Unable to save') + expect(notification.getMessage()).toContain('Unable to save') }) - return it('emits a warning notification when the file cannot be saved', function () { - spyOn(editor, 'save').andCallFake(function () { + it('emits a warning notification when the file cannot be saved', () => { + spyOn(editor, 'save').andCallFake(() => { throw new Error('no one knows') }) const save = () => atom.workspace.saveActivePaneItem() - return expect(save).toThrow() + expect(save).toThrow() }) }) }) - describe('::closeActivePaneItemOrEmptyPaneOrWindow', function () { - beforeEach(function () { + describe('::closeActivePaneItemOrEmptyPaneOrWindow', () => { + beforeEach(() => { spyOn(atom, 'close') - return waitsForPromise(() => atom.workspace.open()) + waitsForPromise(() => atom.workspace.open()) }) - return it('closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane', function () { + it('closes the active pane item, or the active pane if it is empty, or the current window if there is only the empty root pane', () => { atom.config.set('core.destroyEmptyPanes', false) const pane1 = atom.workspace.getActivePane() @@ -1946,25 +1997,27 @@ i = /test/; #FIXME\ expect(atom.workspace.getPanes().length).toBe(1) atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - return expect(atom.close).toHaveBeenCalled() + expect(atom.close).toHaveBeenCalled() }) }) - describe('when the core.allowPendingPaneItems option is falsey', () => - it('does not open item with `pending: true` option as pending', function () { + describe('when the core.allowPendingPaneItems option is falsey', () => { + it('does not open item with `pending: true` option as pending', () => { let pane = null atom.config.set('core.allowPendingPaneItems', false) waitsForPromise(() => - atom.workspace.open('sample.js', {pending: true}).then(() => (pane = atom.workspace.getActivePane())) + atom.workspace.open('sample.js', {pending: true}).then(() => { + pane = atom.workspace.getActivePane() + }) ) - return runs(() => expect(pane.getPendingItem()).toBeFalsy()) + runs(() => expect(pane.getPendingItem()).toBeFalsy()) }) - ) + }) - describe('grammar activation', () => - it('notifies the workspace of which grammar is used', function () { + describe('grammar activation', () => { + it('notifies the workspace of which grammar is used', () => { atom.packages.triggerDeferredActivationHooks() const javascriptGrammarUsed = jasmine.createSpy('js grammar used') @@ -1980,7 +2033,7 @@ i = /test/; #FIXME\ waitsForPromise(() => atom.packages.activatePackage('language-c')) waitsForPromise(() => atom.workspace.open('sample-with-comments.js')) - return runs(function () { + runs(() => { // Hooks are triggered when opening new editors expect(javascriptGrammarUsed).toHaveBeenCalled() @@ -1991,43 +2044,39 @@ i = /test/; #FIXME\ // Hooks are triggered when editors are added in other ways. atom.workspace.getActivePane().splitRight({copyActiveItem: true}) atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.ruby')) - return expect(rubyGrammarUsed).toHaveBeenCalled() + expect(rubyGrammarUsed).toHaveBeenCalled() }) }) - ) + }) - describe('.checkoutHeadRevision()', function () { + describe('.checkoutHeadRevision()', () => { let editor = null - beforeEach(function () { + beforeEach(() => { atom.config.set('editor.confirmCheckoutHeadRevision', false) - return waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => (editor = o))) + waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => { editor = o })) }) - it('reverts to the version of its file checked into the project repository', function () { + it('reverts to the version of its file checked into the project repository', () => { editor.setCursorBufferPosition([0, 0]) editor.insertText('---\n') expect(editor.lineTextForBufferRow(0)).toBe('---') waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)) - return runs(() => expect(editor.lineTextForBufferRow(0)).toBe('')) + runs(() => expect(editor.lineTextForBufferRow(0)).toBe('')) }) - return describe("when there's no repository for the editor's file", () => - it("doesn't do anything", function () { + describe("when there's no repository for the editor's file", () => { + it("doesn't do anything", () => { editor = new TextEditor() editor.setText('stuff') atom.workspace.checkoutHeadRevision(editor) - return waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)) + waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor)) }) - ) + }) }) - - return (escapeStringRegex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')) }) -function __guard__ (value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined -} +const escapeStringRegex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') diff --git a/src/workspace.js b/src/workspace.js index 7ffe8f387..7282ed419 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -1,3 +1,5 @@ +'use strict' + const _ = require('underscore-plus') const url = require('url') const path = require('path') @@ -29,17 +31,27 @@ module.exports = class Workspace extends Model { this.updateDocumentEdited = this.updateDocumentEdited.bind(this) this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this) - ;({ - packageManager: this.packageManager, config: this.config, project: this.project, grammarRegistry: this.grammarRegistry, notificationManager: this.notificationManager, - viewRegistry: this.viewRegistry, grammarRegistry: this.grammarRegistry, applicationDelegate: this.applicationDelegate, assert: this.assert, - deserializerManager: this.deserializerManager, textEditorRegistry: this.textEditorRegistry - } = params) + this.packageManager = params.packageManager + this.config = params.config + this.project = params.project + this.notificationManager = params.notificationManager + this.viewRegistry = params.viewRegistry + this.grammarRegistry = params.grammarRegistry + this.applicationDelegate = params.applicationDelegate + this.assert = params.assert + this.deserializerManager = params.deserializerManager + this.textEditorRegistry = params.textEditorRegistry this.emitter = new Emitter() this.openers = [] this.destroyedItemURIs = [] - this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}) + this.paneContainer = new PaneContainer({ + config: this.config, + applicationDelegate: this.applicationDelegate, + notificationManager: this.notificationManager, + deserializerManager: this.deserializerManager + }) this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem) this.defaultDirectorySearcher = new DefaultDirectorySearcher() @@ -49,7 +61,7 @@ module.exports = class Workspace extends Model { // Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always // the newly created object. const realThis = this - this.buildTextEditor = function () { return Workspace.prototype.buildTextEditor.apply(realThis, arguments) } + this.buildTextEditor = () => Workspace.prototype.buildTextEditor.apply(realThis, arguments) this.panelContainers = { top: new PanelContainer({location: 'top'}), @@ -70,9 +82,14 @@ module.exports = class Workspace extends Model { this.emitter = new Emitter() this.paneContainer.destroy() - for (let panelContainer of this.panelContainers) { panelContainer.destroy() } + _.values(this.panelContainers).forEach(panelContainer => { panelContainer.destroy() }) - this.paneContainer = new PaneContainer({config: this.config, applicationDelegate: this.applicationDelegate, notificationManager: this.notificationManager, deserializerManager: this.deserializerManager}) + this.paneContainer = new PaneContainer({ + config: this.config, + applicationDelegate: this.applicationDelegate, + notificationManager: this.notificationManager, + deserializerManager: this.deserializerManager + }) this.paneContainer.onDidDestroyPaneItem(this.didDestroyPaneItem) this.panelContainers = { @@ -88,21 +105,22 @@ module.exports = class Workspace extends Model { this.originalFontSize = null this.openers = [] this.destroyedItemURIs = [] - return this.consumeServices(this.packageManager) + this.consumeServices(this.packageManager) } subscribeToEvents () { this.subscribeToActiveItem() this.subscribeToFontSize() - return this.subscribeToAddedItems() + this.subscribeToAddedItems() } consumeServices ({serviceHub}) { this.directorySearchers = [] - return serviceHub.consume( + serviceHub.consume( 'atom.directory-searcher', '^0.1.0', - provider => this.directorySearchers.unshift(provider)) + provider => this.directorySearchers.unshift(provider) + ) } // Called by the Serializable mixin during serialization. @@ -116,8 +134,13 @@ module.exports = class Workspace extends Model { } deserialize (state, deserializerManager) { - for (let packageName of state.packagesWithActiveGrammars != null ? state.packagesWithActiveGrammars : []) { - __guard__(this.packageManager.getLoadedPackage(packageName), x => x.loadGrammarsSync()) + const packagesWithActiveGrammars = + state.packagesWithActiveGrammars != null ? state.packagesWithActiveGrammars : [] + for (let packageName of packagesWithActiveGrammars) { + const pkg = this.packageManager.getLoadedPackage(packageName) + if (pkg != null) { + pkg.loadGrammarsSync() + } } if (state.destroyedItemURIs != null) { this.destroyedItemURIs = state.destroyedItemURIs @@ -127,7 +150,7 @@ module.exports = class Workspace extends Model { getPackageNamesWithActiveGrammars () { const packageNames = [] - var addGrammar = ({includedGrammarScopes, packageName} = {}) => { + const addGrammar = ({includedGrammarScopes, packageName} = {}) => { if (!packageName) { return } // Prevent cycles if (packageNames.indexOf(packageName) !== -1) { return } @@ -157,8 +180,7 @@ module.exports = class Workspace extends Model { this.updateDocumentEdited() this.project.onDidChangePaths(this.updateWindowTitle) - return this.observeActivePaneItem(item => { - let modifiedSubscription, titleSubscription + this.observeActivePaneItem(item => { this.updateWindowTitle() this.updateDocumentEdited() @@ -167,32 +189,37 @@ module.exports = class Workspace extends Model { } this.activeItemSubscriptions = new CompositeDisposable() - if (typeof (item != null ? item.onDidChangeTitle : undefined) === 'function') { + let modifiedSubscription, titleSubscription + + if (item != null && typeof item.onDidChangeTitle === 'function') { titleSubscription = item.onDidChangeTitle(this.updateWindowTitle) - } else if (typeof (item != null ? item.on : undefined) === 'function') { + } else if (item != null && typeof item.on === 'function') { titleSubscription = item.on('title-changed', this.updateWindowTitle) - if (typeof (titleSubscription != null ? titleSubscription.dispose : undefined) !== 'function') { - titleSubscription = new Disposable((function () { return item.off('title-changed', this.updateWindowTitle) }.bind(this))) + if (titleSubscription == null || typeof titleSubscription.dispose !== 'function') { + titleSubscription = new Disposable(() => { + item.off('title-changed', this.updateWindowTitle) + }) } } - if (typeof (item != null ? item.onDidChangeModified : undefined) === 'function') { + if (item != null && typeof item.onDidChangeModified === 'function') { modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited) - } else if (typeof ((item != null ? item.on : undefined) != null) === 'function') { + } else if (item != null && typeof item.on === 'function') { modifiedSubscription = item.on('modified-status-changed', this.updateDocumentEdited) - if (typeof (modifiedSubscription != null ? modifiedSubscription.dispose : undefined) !== 'function') { - modifiedSubscription = new Disposable((function () { return item.off('modified-status-changed', this.updateDocumentEdited) }.bind(this))) + if (modifiedSubscription == null || typeof modifiedSubscription.dispose !== 'function') { + modifiedSubscription = new Disposable(() => { + item.off('modified-status-changed', this.updateDocumentEdited) + }) } } if (titleSubscription != null) { this.activeItemSubscriptions.add(titleSubscription) } - if (modifiedSubscription != null) { return this.activeItemSubscriptions.add(modifiedSubscription) } - } - ) + if (modifiedSubscription != null) { this.activeItemSubscriptions.add(modifiedSubscription) } + }) } subscribeToAddedItems () { - return this.onDidAddPaneItem(({item, pane, index}) => { + this.onDidAddPaneItem(({item, pane, index}) => { if (item instanceof TextEditor) { const subscriptions = new CompositeDisposable( this.textEditorRegistry.add(item), @@ -200,8 +227,8 @@ module.exports = class Workspace extends Model { this.textEditorRegistry.maintainConfig(item), item.observeGrammar(this.handleGrammarUsed.bind(this)) ) - item.onDidDestroy(() => subscriptions.dispose()) - return this.emitter.emit('did-add-text-editor', {textEditor: item, pane, index}) + item.onDidDestroy(() => { subscriptions.dispose() }) + this.emitter.emit('did-add-text-editor', {textEditor: item, pane, index}) } }) } @@ -209,14 +236,22 @@ module.exports = class Workspace extends Model { // Updates the application's title and proxy icon based on whichever file is // open. updateWindowTitle () { - let item, itemPath, itemTitle, left, projectPath, representedPath + let itemPath, itemTitle, projectPath, representedPath const appName = 'Atom' - const projectPaths = (left = this.project.getPaths()) != null ? left : [] - if ((item = this.getActivePaneItem())) { - let left1 + const left = this.project.getPaths() + const projectPaths = left != null ? left : [] + const item = this.getActivePaneItem() + if (item) { itemPath = typeof item.getPath === 'function' ? item.getPath() : undefined - itemTitle = (left1 = (typeof item.getLongTitle === 'function' ? item.getLongTitle() : undefined)) != null ? left1 : (typeof item.getTitle === 'function' ? item.getTitle() : undefined) - projectPath = _.find(projectPaths, projectPath => (itemPath === projectPath) || (itemPath != null ? itemPath.startsWith(projectPath + path.sep) : undefined)) + const longTitle = typeof item.getLongTitle === 'function' ? item.getLongTitle() : undefined + itemTitle = longTitle == null + ? (typeof item.getTitle === 'function' ? item.getTitle() : undefined) + : longTitle + projectPath = _.find( + projectPaths, + projectPath => + (itemPath === projectPath) || (itemPath != null ? itemPath.startsWith(projectPath + path.sep) : undefined) + ) } if (itemTitle == null) { itemTitle = 'untitled' } if (projectPath == null) { projectPath = itemPath ? path.dirname(itemPath) : projectPaths[0] } @@ -241,15 +276,17 @@ module.exports = class Workspace extends Model { } document.title = titleParts.join(' \u2014 ') - return this.applicationDelegate.setRepresentedFilename(representedPath) + this.applicationDelegate.setRepresentedFilename(representedPath) } // On macOS, fades the application window's proxy icon when the current file // has been modified. updateDocumentEdited () { - let left - const modified = (left = __guardMethod__(this.getActivePaneItem(), 'isModified', o => o.isModified())) != null ? left : false - return this.applicationDelegate.setWindowDocumentEdited(modified) + const activePaneItem = this.getActivePaneItem() + const modified = activePaneItem != null && typeof activePaneItem.isModified === 'function' + ? activePaneItem.isModified() || false + : false + this.applicationDelegate.setWindowDocumentEdited(modified) } /* @@ -479,11 +516,10 @@ module.exports = class Workspace extends Model { // an existing item for the same URI. Defaults to `false`. // // Returns a {Promise} that resolves to the {TextEditor} for the file URI. - open (uri, options = {}) { - let pane + open (uri_, options = {}) { const { searchAllPanes } = options const { split } = options - uri = this.project.resolvePath(uri) + const uri = this.project.resolvePath(uri_) if (!atom.config.get('core.allowPendingPaneItems')) { options.pending = false @@ -495,22 +531,26 @@ module.exports = class Workspace extends Model { this.applicationDelegate.addRecentDocument(uri) } + let pane if (searchAllPanes) { pane = this.paneContainer.paneForURI(uri) } if (pane == null) { - pane = (() => { - switch (split) { - case 'left': - return this.getActivePane().findLeftmostSibling() - case 'right': - return this.getActivePane().findOrCreateRightmostSibling() - case 'up': - return this.getActivePane().findTopmostSibling() - case 'down': - return this.getActivePane().findOrCreateBottommostSibling() - default: - return this.getActivePane() - } - })() + switch (split) { + case 'left': + pane = this.getActivePane().findLeftmostSibling() + break + case 'right': + pane = this.getActivePane().findOrCreateRightmostSibling() + break + case 'up': + pane = this.getActivePane().findTopmostSibling() + break + case 'down': + pane = this.getActivePane().findOrCreateBottommostSibling() + break + default: + pane = this.getActivePane() + break + } } return this.openURIInPane(uri, pane, options) @@ -535,38 +575,54 @@ module.exports = class Workspace extends Model { // the containing pane. Defaults to `true`. // * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} // on containing pane. Defaults to `true`. - openSync (uri = '', options = {}) { + openSync (uri_ = '', options = {}) { const {initialLine, initialColumn} = options const activatePane = options.activatePane != null ? options.activatePane : true const activateItem = options.activateItem != null ? options.activateItem : true - uri = this.project.resolvePath(uri) + const uri = this.project.resolvePath(uri) let item = this.getActivePane().itemForURI(uri) - if (uri) { - for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options) } } } + if (uri && (item == null)) { + for (const opener of this.getOpeners()) { + item = opener(uri, options) + if (item) break + } + } + if (item == null) { + item = this.project.openSync(uri, {initialLine, initialColumn}) } - if (item == null) { item = this.project.openSync(uri, {initialLine, initialColumn}) } - if (activateItem) { this.getActivePane().activateItem(item) } + if (activateItem) { + this.getActivePane().activateItem(item) + } this.itemOpened(item) - if (activatePane) { this.getActivePane().activate() } + if (activatePane) { + this.getActivePane().activate() + } return item } openURIInPane (uri, pane, options = {}) { - let item const activatePane = options.activatePane != null ? options.activatePane : true const activateItem = options.activateItem != null ? options.activateItem : true + let item if (uri != null) { - if ((item = pane.itemForURI(uri))) { - if (!options.pending && (pane.getPendingItem() === item)) { pane.clearPendingItem() } + item = pane.itemForURI(uri) + if (item == null) { + for (let opener of this.getOpeners()) { + item = opener(uri, options) + if (item != null) break + } + } else if (!options.pending && (pane.getPendingItem() === item)) { + pane.clearPendingItem() } - for (let opener of this.getOpeners()) { if (!item) { if (item == null) { item = opener(uri, options) } } } } try { - if (item == null) { item = this.openTextFile(uri, options) } + if (item == null) { + item = this.openTextFile(uri, options) + } } catch (error) { switch (error.code) { case 'CANCELLED': @@ -574,8 +630,21 @@ module.exports = class Workspace extends Model { case 'EACCES': this.notificationManager.addWarning(`Permission denied '${error.path}'`) return Promise.resolve() - case 'EPERM': case 'EBUSY': case 'ENXIO': case 'EIO': case 'ENOTCONN': case 'UNKNOWN': case 'ECONNRESET': case 'EINVAL': case 'EMFILE': case 'ENOTDIR': case 'EAGAIN': - this.notificationManager.addWarning(`Unable to open '${error.path != null ? error.path : uri}'`, {detail: error.message}) + case 'EPERM': + case 'EBUSY': + case 'ENXIO': + case 'EIO': + case 'ENOTCONN': + case 'UNKNOWN': + case 'ECONNRESET': + case 'EINVAL': + case 'EMFILE': + case 'ENOTDIR': + case 'EAGAIN': + this.notificationManager.addWarning( + `Unable to open '${error.path != null ? error.path : uri}'`, + {detail: error.message} + ) return Promise.resolve() default: throw error @@ -585,18 +654,24 @@ module.exports = class Workspace extends Model { return Promise.resolve(item) .then(item => { let initialColumn - if (pane.isDestroyed()) { return item } + if (pane.isDestroyed()) { + return item + } this.itemOpened(item) - if (activateItem) { pane.activateItem(item, {pending: options.pending}) } - if (activatePane) { pane.activate() } + if (activateItem) { + pane.activateItem(item, {pending: options.pending}) + } + if (activatePane) { + pane.activate() + } let initialLine = initialColumn = 0 if (!Number.isNaN(options.initialLine)) { - ({ initialLine } = options) + initialLine = options.initialLine } if (!Number.isNaN(options.initialColumn)) { - ({ initialColumn } = options) + initialColumn = options.initialColumn } if ((initialLine >= 0) || (initialColumn >= 0)) { if (typeof item.setCursorBufferPosition === 'function') { @@ -619,7 +694,9 @@ module.exports = class Workspace extends Model { fs.closeSync(fs.openSync(filePath, 'r')) } catch (error) { // allow ENOENT errors to create an editor for paths that dont exist - if (error.code !== 'ENOENT') { throw error } + if (error.code !== 'ENOENT') { + throw error + } } } @@ -630,7 +707,8 @@ module.exports = class Workspace extends Model { const choice = this.applicationDelegate.confirm({ message: 'Atom will be unresponsive during the loading of very large files.', detailedMessage: 'Do you still want to load this file?', - buttons: ['Proceed', 'Cancel']}) + buttons: ['Proceed', 'Cancel'] + }) if (choice === 1) { const error = new Error() error.code = 'CANCELLED' @@ -638,15 +716,14 @@ module.exports = class Workspace extends Model { } } - return this.project.bufferForPath(filePath, options).then(buffer => { - return this.textEditorRegistry.build(Object.assign({buffer, largeFileMode, autoHeight: false}, options)) - } - ) + return this.project.bufferForPath(filePath, options) + .then(buffer => { + return this.textEditorRegistry.build(Object.assign({buffer, largeFileMode, autoHeight: false}, options)) + }) } handleGrammarUsed (grammar) { if (grammar == null) { return } - return this.packageManager.triggerActivationHook(`${grammar.packageName}:grammar-used`) } @@ -666,7 +743,7 @@ module.exports = class Workspace extends Model { this.textEditorRegistry.maintainGrammar(editor), this.textEditorRegistry.maintainConfig(editor) ) - editor.onDidDestroy(() => subscriptions.dispose()) + editor.onDidDestroy(() => { subscriptions.dispose() }) return editor } @@ -675,8 +752,8 @@ module.exports = class Workspace extends Model { // // Returns a {Promise} that is resolved when the item is opened reopenItem () { - let uri - if ((uri = this.destroyedItemURIs.pop())) { + const uri = this.destroyedItemURIs.pop() + if (uri) { return this.open(uri) } else { return Promise.resolve() @@ -714,7 +791,7 @@ module.exports = class Workspace extends Model { // can check the protocol for quux-preview and only handle those URIs that match. addOpener (opener) { this.openers.push(opener) - return new Disposable((function () { return _.remove(this.openers, opener) }.bind(this))) + return new Disposable(() => { _.remove(this.openers, opener) }) } getOpeners () { @@ -839,44 +916,50 @@ module.exports = class Workspace extends Model { // Destroy (close) the active pane. destroyActivePane () { - return __guard__(this.getActivePane(), x => x.destroy()) + const activePane = this.getActivePane() + if (activePane != null) { + activePane.destroy() + } } // Close the active pane item, or the active pane if it is empty, // or the current window if there is only the empty root pane. closeActivePaneItemOrEmptyPaneOrWindow () { if (this.getActivePaneItem() != null) { - return this.destroyActivePaneItem() + this.destroyActivePaneItem() } else if (this.getPanes().length > 1) { - return this.destroyActivePane() + this.destroyActivePane() } else if (this.config.get('core.closeEmptyWindows')) { - return atom.close() + atom.close() } } // Increase the editor font size by 1px. increaseFontSize () { - return this.config.set('editor.fontSize', this.config.get('editor.fontSize') + 1) + this.config.set('editor.fontSize', this.config.get('editor.fontSize') + 1) } // Decrease the editor font size by 1px. decreaseFontSize () { const fontSize = this.config.get('editor.fontSize') - if (fontSize > 1) { return this.config.set('editor.fontSize', fontSize - 1) } + if (fontSize > 1) { + this.config.set('editor.fontSize', fontSize - 1) + } } // Restore to the window's original editor font size. resetFontSize () { if (this.originalFontSize) { - return this.config.set('editor.fontSize', this.originalFontSize) + this.config.set('editor.fontSize', this.originalFontSize) } } subscribeToFontSize () { return this.config.onDidChange('editor.fontSize', ({oldValue}) => { - return this.originalFontSize != null ? this.originalFontSize : (this.originalFontSize = oldValue) - } - ) + if (this.originalFontSize == null) { + this.originalFontSize = oldValue + } + }) } // Removes the item's uri from the list of potential items to reopen. @@ -889,7 +972,7 @@ module.exports = class Workspace extends Model { } if (uri != null) { - return _.remove(this.destroyedItemURIs, uri) + _.remove(this.destroyedItemURIs, uri) } } @@ -903,14 +986,16 @@ module.exports = class Workspace extends Model { } if (uri != null) { - return this.destroyedItemURIs.push(uri) + this.destroyedItemURIs.push(uri) } } // Called by Model superclass when destroyed destroyed () { this.paneContainer.destroy() - return (this.activeItemSubscriptions != null ? this.activeItemSubscriptions.dispose() : undefined) + if (this.activeItemSubscriptions != null) { + this.activeItemSubscriptions.dispose() + } } /* @@ -1112,7 +1197,6 @@ module.exports = class Workspace extends Model { // Returns a {Promise} with a `cancel()` method that will cancel all // of the underlying searches that were started as part of this scan. scan (regex, options = {}, iterator) { - let directorySearcher, onPathsSearched if (_.isFunction(options)) { iterator = options options = {} @@ -1121,9 +1205,9 @@ module.exports = class Workspace extends Model { // Find a searcher for every Directory in the project. Each searcher that is matched // will be associated with an Array of Directory objects in the Map. const directoriesForSearcher = new Map() - for (let directory of this.project.getDirectories()) { + for (const directory of this.project.getDirectories()) { let searcher = this.defaultDirectorySearcher - for (directorySearcher of this.directorySearchers) { + for (const directorySearcher of this.directorySearchers) { if (directorySearcher.canSearchDirectory(directory)) { searcher = directorySearcher break @@ -1138,6 +1222,7 @@ module.exports = class Workspace extends Model { } // Define the onPathsSearched callback. + let onPathsSearched if (_.isFunction(options.onPathsSearched)) { // Maintain a map of directories to the number of search results. When notified of a new count, // replace the entry in the map and update the total. @@ -1167,26 +1252,33 @@ module.exports = class Workspace extends Model { exclusions: this.config.get('core.ignoredNames'), follow: this.config.get('core.followSymlinks'), didMatch: result => { - if (!this.project.isPathModified(result.filePath)) { return iterator(result) } + if (!this.project.isPathModified(result.filePath)) { + return iterator(result) + } }, didError (error) { return iterator(null, error) }, - didSearchPaths (count) { return onPathsSearched(searcher, count) } + didSearchPaths (count) { + return onPathsSearched(searcher, count) + } } - directorySearcher = searcher.search(directories, regex, searchOptions) - return allSearches.push(directorySearcher) - } - ) + const directorySearcher = searcher.search(directories, regex, searchOptions) + allSearches.push(directorySearcher) + }) const searchPromise = Promise.all(allSearches) for (let buffer of this.project.getBuffers()) { if (buffer.isModified()) { const filePath = buffer.getPath() - if (!this.project.contains(filePath)) { continue } + if (!this.project.contains(filePath)) { + continue + } var matches = [] buffer.scan(regex, match => matches.push(match)) - if (matches.length > 0) { iterator({filePath, matches}) } + if (matches.length > 0) { + iterator({filePath, matches}) + } } } @@ -1195,34 +1287,36 @@ module.exports = class Workspace extends Model { // resolve it with the special value 'cancelled'. At least the built-in find-and-replace // package relies on this behavior. let isCancelled = false - const cancellablePromise = new Promise(function (resolve, reject) { + const cancellablePromise = new Promise((resolve, reject) => { const onSuccess = function () { if (isCancelled) { - return resolve('cancelled') + resolve('cancelled') } else { - return resolve(null) + resolve(null) } } const onFailure = function () { for (let promise of allSearches) { promise.cancel() } - return reject() + reject() } - return searchPromise.then(onSuccess, onFailure) + searchPromise.then(onSuccess, onFailure) }) - cancellablePromise.cancel = function () { + cancellablePromise.cancel = () => { isCancelled = true // Note that cancelling all of the members of allSearches will cause all of the searches // to resolve, which causes searchPromise to resolve, which is ultimately what causes // cancellablePromise to resolve. - return allSearches.map((promise) => promise.cancel()) + allSearches.map((promise) => promise.cancel()) } // Although this method claims to return a `Promise`, the `ResultsPaneView.onSearch()` // method in the find-and-replace package expects the object returned by this method to have a // `done()` method. Include a done() method until find-and-replace can be updated. - cancellablePromise.done = onSuccessOrFailure => cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure) + cancellablePromise.done = onSuccessOrFailure => { + cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure) + } return cancellablePromise } @@ -1236,45 +1330,50 @@ module.exports = class Workspace extends Model { // // Returns a {Promise}. replace (regex, replacementText, filePaths, iterator) { - return new Promise((function (resolve, reject) { + return new Promise((resolve, reject) => { let buffer - const openPaths = ((() => { - const result = [] - for (buffer of this.project.getBuffers()) { - result.push(buffer.getPath()) - } - return result - })()) + const openPaths = this.project.getBuffers().map(buffer => buffer.getPath()) const outOfProcessPaths = _.difference(filePaths, openPaths) let inProcessFinished = !openPaths.length let outOfProcessFinished = !outOfProcessPaths.length - const checkFinished = function () { - if (outOfProcessFinished && inProcessFinished) { return resolve() } + const checkFinished = () => { + if (outOfProcessFinished && inProcessFinished) { + resolve() + } } if (!outOfProcessFinished.length) { let flags = 'g' if (regex.ignoreCase) { flags += 'i' } - const task = Task.once(require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, function () { - outOfProcessFinished = true - return checkFinished() - }) + const task = Task.once( + require.resolve('./replace-handler'), + outOfProcessPaths, + regex.source, + flags, + replacementText, + () => { + outOfProcessFinished = true + checkFinished() + } + ) task.on('replace:path-replaced', iterator) - task.on('replace:file-error', function (error) { return iterator(null, error) }) + task.on('replace:file-error', error => { iterator(null, error) }) } for (buffer of this.project.getBuffers()) { - if (!Array.from(filePaths).includes(buffer.getPath())) { continue } + if (!filePaths.includes(buffer.getPath())) { continue } const replacements = buffer.replace(regex, replacementText, iterator) - if (replacements) { iterator({filePath: buffer.getPath(), replacements}) } + if (replacements) { + iterator({filePath: buffer.getPath(), replacements}) + } } inProcessFinished = true - return checkFinished() - }.bind(this))) + checkFinished() + }) } checkoutHeadRevision (editor) { @@ -1285,7 +1384,7 @@ module.exports = class Workspace extends Model { } if (this.config.get('editor.confirmCheckoutHeadRevision')) { - return this.applicationDelegate.confirm({ + this.applicationDelegate.confirm({ message: 'Confirm Checkout HEAD Revision', detailedMessage: `Are you sure you want to discard all changes to "${editor.getFileName()}" since the last Git commit?`, buttons: { @@ -1301,14 +1400,3 @@ module.exports = class Workspace extends Model { } } } - -function __guard__ (value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined -} -function __guardMethod__ (obj, methodName, transform) { - if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { - return transform(obj, methodName) - } else { - return undefined - } -} From b5012bc347a92070ed4a8f175e377fc7c25f76b8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 10:41:43 +0100 Subject: [PATCH 051/194] :arrow_up: tabs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 10a5f57f6..bb7d5caa8 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "status-bar": "1.8.1", "styleguide": "0.49.3", "symbols-view": "0.114.0", - "tabs": "0.104.1", + "tabs": "0.104.2", "timecop": "0.36.0", "tree-view": "0.214.1", "update-package-dependencies": "0.10.0", From 24f02d161a9331244524bdc2344652205dc1ef5a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 10:48:34 +0100 Subject: [PATCH 052/194] :arrow_up: fuzzy-finder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb7d5caa8..501ad0252 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "encoding-selector": "0.23.2", "exception-reporting": "0.41.1", "find-and-replace": "0.207.0", - "fuzzy-finder": "1.4.1", + "fuzzy-finder": "1.5.0", "git-diff": "1.3.3", "go-to-line": "0.32.0", "grammar-selector": "0.49.3", From 5c3357049b65877bd45398aa1e062ba6b1201f42 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 10:49:21 +0100 Subject: [PATCH 053/194] :arrow_up: snippets --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 501ad0252..97bae403a 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "open-on-github": "1.2.1", "package-generator": "1.1.0", "settings-view": "0.247.2", - "snippets": "1.0.5", + "snippets": "1.1.1", "spell-check": "0.71.1", "status-bar": "1.8.1", "styleguide": "0.49.3", From 2f1fd15170c40522f981c5c8d4f7ac9bca5bffe7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 11:21:29 +0100 Subject: [PATCH 054/194] Don't assign snapshotResult.entryPointDirPath ...because we can use `process.resourcesPath` or `atom.getLoadSettings()` in packages that need to resolve the absolute path at runtime. --- static/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/index.js b/static/index.js index f407778dc..7021568db 100644 --- a/static/index.js +++ b/static/index.js @@ -48,7 +48,6 @@ } snapshotResult.setGlobals(global, process, window, document, require) - snapshotResult.entryPointDirPath = __dirname } const FileSystemBlobStore = useSnapshot ? snapshotResult.customRequire('../src/file-system-blob-store.js') : require('../src/file-system-blob-store') From 16e03a54d02f1a25320dd1cc51ea1998a14bef92 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 11:22:39 +0100 Subject: [PATCH 055/194] :arrow_up: archive_view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97bae403a..facc60dfa 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "solarized-dark-syntax": "1.1.2", "solarized-light-syntax": "1.1.2", "about": "1.7.4", - "archive-view": "0.62.2", + "archive-view": "0.63.0", "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.15.0", "autocomplete-html": "0.7.2", From c1a773ed9ea4a75aba79a355ca2cdfccc047a448 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 11:23:35 +0100 Subject: [PATCH 056/194] :arrow_up: update-package-dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index facc60dfa..d7cd4961d 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "tabs": "0.104.2", "timecop": "0.36.0", "tree-view": "0.214.1", - "update-package-dependencies": "0.10.0", + "update-package-dependencies": "0.11.0", "welcome": "0.36.2", "whitespace": "0.36.2", "wrap-guide": "0.39.1", From b2f1286afbf3db1901f8ae738eb74d24b1562ae8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 11:24:32 +0100 Subject: [PATCH 057/194] :arrow_up: wrap-guide --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d7cd4961d..771a31868 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "update-package-dependencies": "0.11.0", "welcome": "0.36.2", "whitespace": "0.36.2", - "wrap-guide": "0.39.1", + "wrap-guide": "0.40.0", "language-c": "0.56.0", "language-clojure": "0.22.2", "language-coffee-script": "0.48.4", From b25317e7acab954db1c7b3201f642bd0550cc539 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 11:26:40 +0100 Subject: [PATCH 058/194] :arrow_up: symbols-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 771a31868..cb5b651e5 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "spell-check": "0.71.1", "status-bar": "1.8.1", "styleguide": "0.49.3", - "symbols-view": "0.114.0", + "symbols-view": "0.115.0", "tabs": "0.104.2", "timecop": "0.36.0", "tree-view": "0.214.1", From 22ffe5f34cd265809403e0b52edd152f04dd6582 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 12:04:44 +0100 Subject: [PATCH 059/194] :arrow_up: settings-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb5b651e5..956c7f609 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "notifications": "0.66.2", "open-on-github": "1.2.1", "package-generator": "1.1.0", - "settings-view": "0.247.2", + "settings-view": "0.248.0", "snippets": "1.1.1", "spell-check": "0.71.1", "status-bar": "1.8.1", From 5d5e95562c0638bf2e3b84c38fc0c05bd0277987 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 12:07:32 +0100 Subject: [PATCH 060/194] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 956c7f609..4562a67e8 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "symbols-view": "0.115.0", "tabs": "0.104.2", "timecop": "0.36.0", - "tree-view": "0.214.1", + "tree-view": "0.215.0", "update-package-dependencies": "0.11.0", "welcome": "0.36.2", "whitespace": "0.36.2", From d80782355a73d83ad17c4c4ca981ca89c66cb380 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 12:09:51 +0100 Subject: [PATCH 061/194] :arrow_up: pathwatcher in all node modules and atom packages that use it * :arrow_up: text-buffer * :arrow_up: tree-view * :arrow_up: atom-keymap --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4562a67e8..c805362e6 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.13", "dependencies": { "async": "0.2.6", - "atom-keymap": "7.1.21", + "atom-keymap": "7.1.22", "atom-select-list": "0.0.15", "atom-ui": "0.4.1", "babel-core": "6.22.1", @@ -60,7 +60,7 @@ "normalize-package-data": "^2.0.0", "nslog": "^3", "oniguruma": "6.1.0", - "pathwatcher": "6.8.0", + "pathwatcher": "6.9.0", "postcss": "5.2.4", "postcss-selector-parser": "2.2.1", "property-accessors": "^1.1.3", @@ -76,7 +76,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "10.3.12", + "text-buffer": "10.4.1", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", @@ -136,7 +136,7 @@ "symbols-view": "0.115.0", "tabs": "0.104.2", "timecop": "0.36.0", - "tree-view": "0.215.0", + "tree-view": "0.215.1", "update-package-dependencies": "0.11.0", "welcome": "0.36.2", "whitespace": "0.36.2", From dd64470381fdd4252c6bbd0359770a1881334d36 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Fri, 3 Mar 2017 10:47:05 -0500 Subject: [PATCH 062/194] :arrow_up: about@1.7.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c805362e6..a96dc7ff9 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "one-light-syntax": "1.7.1", "solarized-dark-syntax": "1.1.2", "solarized-light-syntax": "1.1.2", - "about": "1.7.4", + "about": "1.7.5", "archive-view": "0.63.0", "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.15.0", From 679abe0d1e380451e295452ee86cf21fcee82e49 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Fri, 3 Mar 2017 10:48:51 -0500 Subject: [PATCH 063/194] :arrow_up: language-coffee-script@0.48.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a96dc7ff9..00bae5980 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "wrap-guide": "0.40.0", "language-c": "0.56.0", "language-clojure": "0.22.2", - "language-coffee-script": "0.48.4", + "language-coffee-script": "0.48.5", "language-csharp": "0.14.2", "language-css": "0.42.0", "language-gfm": "0.88.1", From 4334bd2fa41b05b4236965ba8b5df396f249faae Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 16:52:31 +0100 Subject: [PATCH 064/194] :arrow_up: fs-plus in all node modules and atom packages that use it * :arrow_up: exception-reporting * :arrow_up: status-bar * :arrow_up: symbols-view --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 00bae5980..ee7142f32 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", "first-mate": "6.3.0", - "fs-plus": "2.9.2", + "fs-plus": "2.10.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "4.1.2", @@ -112,7 +112,7 @@ "deprecation-cop": "0.56.2", "dev-live-reload": "0.47.0", "encoding-selector": "0.23.2", - "exception-reporting": "0.41.1", + "exception-reporting": "0.41.2", "find-and-replace": "0.207.0", "fuzzy-finder": "1.5.0", "git-diff": "1.3.3", @@ -131,9 +131,9 @@ "settings-view": "0.248.0", "snippets": "1.1.1", "spell-check": "0.71.1", - "status-bar": "1.8.1", + "status-bar": "1.8.2", "styleguide": "0.49.3", - "symbols-view": "0.115.0", + "symbols-view": "0.115.1", "tabs": "0.104.2", "timecop": "0.36.0", "tree-view": "0.215.1", From 4c0ace0e1b10bcafd69df8087386a22c7c6f86ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 17:07:19 +0100 Subject: [PATCH 065/194] :arrow_up: text-buffer to fix build errors Ref: https://github.com/atom/text-buffer/pull/218 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee7142f32..a76f858b7 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "10.4.1", + "text-buffer": "10.4.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From c06fd3f40224cf3bb1142b23bf63449ddcdaa62a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 18:50:52 +0100 Subject: [PATCH 066/194] :arrow_up: apm --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index ede2d1bd7..07931700d 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.15.3" + "atom-package-manager": "1.16.0" } } From 3c0638101825210575f3f15f44945c1d01b10f88 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 3 Mar 2017 12:00:45 -0800 Subject: [PATCH 067/194] Fix usage of dynamic arguments variable in an arrow function --- src/workspace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.js b/src/workspace.js index 7282ed419..f411cf823 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -61,7 +61,7 @@ module.exports = class Workspace extends Model { // Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always // the newly created object. const realThis = this - this.buildTextEditor = () => Workspace.prototype.buildTextEditor.apply(realThis, arguments) + this.buildTextEditor = (params) => Workspace.prototype.buildTextEditor.call(realThis, params) this.panelContainers = { top: new PanelContainer({location: 'top'}), From c8bcb6038037c5f427e4f2a1c80982b1255674dd Mon Sep 17 00:00:00 2001 From: simurai Date: Sat, 4 Mar 2017 09:20:42 +0900 Subject: [PATCH 068/194] :arrow_up: one-dark/light-ui@v1.9.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a76f858b7..554a35422 100644 --- a/package.json +++ b/package.json @@ -89,8 +89,8 @@ "atom-light-ui": "0.46.0", "base16-tomorrow-dark-theme": "1.5.0", "base16-tomorrow-light-theme": "1.5.0", - "one-dark-ui": "1.9.1", - "one-light-ui": "1.9.1", + "one-dark-ui": "1.9.2", + "one-light-ui": "1.9.2", "one-dark-syntax": "1.7.1", "one-light-syntax": "1.7.1", "solarized-dark-syntax": "1.1.2", From 07b6db7de9dbd75a59cfc3813aa83236450b0329 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 4 Mar 2017 13:40:47 +0100 Subject: [PATCH 069/194] :arrow_up: fs-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 554a35422..8ba31e259 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", "first-mate": "6.3.0", - "fs-plus": "2.10.0", + "fs-plus": "2.10.1", "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "4.1.2", From 6cde3242900fbf16988e9e5ff5a403b56eb658cf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 4 Mar 2017 14:33:57 +0100 Subject: [PATCH 070/194] Fix core renderer tests --- src/config.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.coffee b/src/config.coffee index e873a1348..e7d05da6d 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -839,7 +839,7 @@ class Config relativePath = sourcePath.substring(templateConfigDirPath.length + 1) destinationPath = path.join(@configDirPath, relativePath) queue.push({sourcePath, destinationPath}) - fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true) + fs.traverseTree(templateConfigDirPath, onConfigDirFile, ((path) -> true), (->)) loadUserConfig: -> return if @shouldNotAccessFileSystem() From 78935dddd5297e9da8361fb847ccc86c1e8bbbe7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 4 Mar 2017 14:40:02 +0100 Subject: [PATCH 071/194] :arrow_up: status-bar --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ba31e259..89499cf9a 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "settings-view": "0.248.0", "snippets": "1.1.1", "spell-check": "0.71.1", - "status-bar": "1.8.2", + "status-bar": "1.8.3", "styleguide": "0.49.3", "symbols-view": "0.115.1", "tabs": "0.104.2", From 8df511be5512bdc1fee7b315de1aab2768c7b720 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 4 Mar 2017 14:40:49 +0100 Subject: [PATCH 072/194] :arrow_up: symbols-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89499cf9a..97d523fa4 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "spell-check": "0.71.1", "status-bar": "1.8.3", "styleguide": "0.49.3", - "symbols-view": "0.115.1", + "symbols-view": "0.115.2", "tabs": "0.104.2", "timecop": "0.36.0", "tree-view": "0.215.1", From ed9a101de2803bff00e988673cad7935da6029f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 4 Mar 2017 15:39:38 +0100 Subject: [PATCH 073/194] Fix destroying a `PanelContainer` containing multiple panels Previously, when calling `destroy` on a `PanelContainer` containing multiple panels, Atom would throw a `Cannot read property 'destroy' of undefined` exception. This was due to iterating over the panels while at the same time destroying them, which caused the iterated array to be modified during the loop. With this commit we slice the array before iterating over it so that destroying a `PanelContainer` doesn't throw exceptions anymore. --- spec/panel-container-spec.coffee | 19 ++++++++++++++++++- src/panel-container.coffee | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/spec/panel-container-spec.coffee b/spec/panel-container-spec.coffee index 08eaea92b..fbf1c3446 100644 --- a/spec/panel-container-spec.coffee +++ b/spec/panel-container-spec.coffee @@ -5,7 +5,7 @@ describe "PanelContainer", -> [container] = [] class TestPanelItem - constructior: -> + constructor: -> beforeEach -> container = new PanelContainer @@ -39,6 +39,23 @@ describe "PanelContainer", -> panel1.destroy() expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) + describe "::destroy()", -> + it "destroys the container and all of its panels", -> + destroyedPanels = [] + + panel1 = new Panel(item: new TestPanelItem()) + panel1.onDidDestroy -> destroyedPanels.push(panel1) + container.addPanel(panel1) + + panel2 = new Panel(item: new TestPanelItem()) + panel2.onDidDestroy -> destroyedPanels.push(panel2) + container.addPanel(panel2) + + container.destroy() + + expect(container.getPanels().length).toBe(0) + expect(destroyedPanels).toEqual([panel1, panel2]) + describe "panel priority", -> describe 'left / top panel container', -> [initialPanel] = [] diff --git a/src/panel-container.coffee b/src/panel-container.coffee index 322773f69..d109210a7 100644 --- a/src/panel-container.coffee +++ b/src/panel-container.coffee @@ -34,7 +34,7 @@ class PanelContainer isModal: -> @location is 'modal' - getPanels: -> @panels + getPanels: -> @panels.slice() addPanel: (panel) -> @subscriptions.add panel.onDidDestroy(@panelDestroyed.bind(this)) From ab1fb2f217aa94cac98105316c7f164bfb6251bd Mon Sep 17 00:00:00 2001 From: Mikhail Fesenko Date: Sun, 5 Mar 2017 21:59:07 +0300 Subject: [PATCH 074/194] Fix path on macOS link --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index e9b6ff120..c555306b5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ In this directory you can only find very specific build and API level documentat Instructions for building Atom on various platforms from source. -* [macOS](./build-instructions/macos.md) +* [macOS](./build-instructions/macOS.md) * [Windows](./build-instructions/windows.md) * [Linux](./build-instructions/linux.md) * [FreeBSD](./build-instructions/freebsd.md) From 52492c1386b87d97d0d4d605f980b3bc2eab3121 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Sun, 5 Mar 2017 17:21:05 -0800 Subject: [PATCH 075/194] Ensure recent project list populated when no project loaded, fixes #13758 --- src/reopen-project-menu-manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js index 79acbba66..6f9e2884f 100644 --- a/src/reopen-project-menu-manager.js +++ b/src/reopen-project-menu-manager.js @@ -20,6 +20,7 @@ export default class ReopenProjectMenuManager { commands.add('atom-workspace', { 'application:reopen-project': this.reopenProjectCommand.bind(this) }) ) + this.update() this.applyWindowsJumpListRemovals() } From ef0619048e320fcc539f998b5202a42eacb50114 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Sun, 5 Mar 2017 17:49:01 -0800 Subject: [PATCH 076/194] Revert "Ensure recent project list populated when no project loaded, fixes #13758" This reverts commit 52492c1386b87d97d0d4d605f980b3bc2eab3121. --- src/reopen-project-menu-manager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js index 6f9e2884f..79acbba66 100644 --- a/src/reopen-project-menu-manager.js +++ b/src/reopen-project-menu-manager.js @@ -20,7 +20,6 @@ export default class ReopenProjectMenuManager { commands.add('atom-workspace', { 'application:reopen-project': this.reopenProjectCommand.bind(this) }) ) - this.update() this.applyWindowsJumpListRemovals() } From 134ef4f5a490b969dde141fcffba43f24d880b26 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 11:15:33 +0100 Subject: [PATCH 077/194] :arrow_up: fs-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97d523fa4..1d884c8dd 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", "first-mate": "6.3.0", - "fs-plus": "2.10.1", + "fs-plus": "^3.0.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "4.1.2", From 4f4fed6cb83ae24897b16412161af14aa9227359 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 12:57:27 +0100 Subject: [PATCH 078/194] Upgrade fs-plus on Atom packages and node modules --- package.json | 62 ++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 1d884c8dd..7814c79ff 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.13", "dependencies": { "async": "0.2.6", - "atom-keymap": "7.1.22", + "atom-keymap": "8.0.2", "atom-select-list": "0.0.15", "atom-ui": "0.4.1", "babel-core": "6.22.1", @@ -40,18 +40,18 @@ "devtron": "1.3.0", "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", - "first-mate": "6.3.0", + "first-mate": "7.0.2", "fs-plus": "^3.0.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", - "git-utils": "4.1.2", + "git-utils": "5.0.0", "glob": "^7.1.1", "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", "jquery": "2.1.4", "key-path-helpers": "^0.4.0", - "less-cache": "0.23", + "less-cache": "1.0.0", "line-top-index": "0.2.0", "marked": "^0.3.6", "minimatch": "^3.0.3", @@ -60,23 +60,23 @@ "normalize-package-data": "^2.0.0", "nslog": "^3", "oniguruma": "6.1.0", - "pathwatcher": "6.9.0", + "pathwatcher": "7.0.0", "postcss": "5.2.4", "postcss-selector-parser": "2.2.1", "property-accessors": "^1.1.3", "random-words": "0.0.1", "resolve": "^1.1.6", "runas": "^3.1", - "scandal": "2.2.2", + "scandal": "^3.0.0", "scoped-property-store": "^0.17.0", "scrollbar-style": "^3.2", - "season": "^5.4.1", + "season": "^6.0.0", "semver": "^4.3.3", "service-hub": "^0.7.2", "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "10.4.2", + "text-buffer": "11.0.0", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", @@ -96,47 +96,47 @@ "solarized-dark-syntax": "1.1.2", "solarized-light-syntax": "1.1.2", "about": "1.7.5", - "archive-view": "0.63.0", + "archive-view": "0.63.1", "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.15.0", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.34.2", "autocomplete-snippets": "1.11.0", "autoflow": "0.29.0", - "autosave": "0.24.0", + "autosave": "0.24.1", "background-tips": "0.26.1", "bookmarks": "0.44.2", - "bracket-matcher": "0.85.3", + "bracket-matcher": "0.85.4", "command-palette": "0.40.3", "dalek": "0.2.0", - "deprecation-cop": "0.56.2", - "dev-live-reload": "0.47.0", + "deprecation-cop": "0.56.4", + "dev-live-reload": "0.47.1", "encoding-selector": "0.23.2", - "exception-reporting": "0.41.2", - "find-and-replace": "0.207.0", - "fuzzy-finder": "1.5.0", - "git-diff": "1.3.3", + "exception-reporting": "0.41.3", + "find-and-replace": "0.207.1", + "fuzzy-finder": "1.5.1", + "git-diff": "1.3.4", "go-to-line": "0.32.0", "grammar-selector": "0.49.3", - "image-view": "0.61.1", + "image-view": "0.61.2", "incompatible-packages": "0.27.1", - "keybinding-resolver": "0.36.3", + "keybinding-resolver": "0.36.4", "line-ending-selector": "0.6.2", - "link": "0.31.2", - "markdown-preview": "0.159.7", - "metrics": "1.2.1", - "notifications": "0.66.2", + "link": "0.31.3", + "markdown-preview": "0.159.9", + "metrics": "1.2.2", + "notifications": "0.67.1", "open-on-github": "1.2.1", - "package-generator": "1.1.0", - "settings-view": "0.248.0", - "snippets": "1.1.1", + "package-generator": "1.1.1", + "settings-view": "0.248.2", + "snippets": "1.1.3", "spell-check": "0.71.1", - "status-bar": "1.8.3", - "styleguide": "0.49.3", - "symbols-view": "0.115.2", - "tabs": "0.104.2", + "status-bar": "1.8.4", + "styleguide": "0.49.4", + "symbols-view": "0.115.3", + "tabs": "0.104.3", "timecop": "0.36.0", - "tree-view": "0.215.1", + "tree-view": "0.215.2", "update-package-dependencies": "0.11.0", "welcome": "0.36.2", "whitespace": "0.36.2", From 025de31846af1eea852604be8d6c28d4bd973f2a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 23 Aug 2016 11:07:52 -0700 Subject: [PATCH 079/194] Avoid using less imports in nested scopes Apparently, these imports of 'octicon-utf-codes.less' within mixin definitions cause any subsequent imports of this file to become noops, because of the way less dedupes imports. The result is that the variables defined in that file are only available in the nested scope. This didn't happen in older versions of less because of bugs which have been fixed since less 2.7. Signed-off-by: Antonio Scandurra --- static/variables/octicon-mixins.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/variables/octicon-mixins.less b/static/variables/octicon-mixins.less index c58ce3a52..672a2cef6 100644 --- a/static/variables/octicon-mixins.less +++ b/static/variables/octicon-mixins.less @@ -1,3 +1,5 @@ +@import "octicon-utf-codes.less"; + .icon-size(@size) { font-size: @size; width: @size; @@ -17,7 +19,6 @@ } .octicon(@name, @size: 16px) { - @import "octicon-utf-codes.less"; &::before { .icon(@size); content: @@name @@ -25,7 +26,6 @@ } .mega-octicon(@name, @size: 32px) { - @import "octicon-utf-codes.less"; &::before { .icon(@size); content: @@name From 778db180dbb683e1c9e863c806321bdff13125bb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 13:34:43 +0100 Subject: [PATCH 080/194] Fix Workspace specs --- spec/workspace-spec.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 270f81526..5279092c0 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1347,7 +1347,9 @@ i = /test/; #FIXME\ matchText: 'aaa', lineText: 'aaa bbb', lineTextOffset: 0, - range: [[0, 0], [0, 3]] + range: [[0, 0], [0, 3]], + linesAfter: [], + linesBefore: [] }) }) }) @@ -1365,7 +1367,9 @@ i = /test/; #FIXME\ matchText: '$bill', lineText: 'dollar$bill', lineTextOffset: 0, - range: [[2, 6], [2, 11]] + range: [[2, 6], [2, 11]], + linesAfter: [], + linesBefore: [] }) }) }) From ebe725de4d27325c8fe5a7b9c5aedde77a1c5912 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 3 Mar 2017 16:57:15 +0100 Subject: [PATCH 081/194] Assign globals to snapshotResult only if present --- src/task.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/task.coffee b/src/task.coffee index 182d9df88..5c7b08b8a 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -70,7 +70,10 @@ class Task compileCachePath = require('./compile-cache').getCacheDirectory() taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');" bootstrap = """ - snapshotResult.setGlobals(global, process, global, {}, require) + if (typeof snapshotResult !== 'undefined') { + snapshotResult.setGlobals(global, process, global, {}, require) + } + CompileCache = #{compileCacheRequire} CompileCache.setCacheDirectory('#{compileCachePath}'); CompileCache.install(require) From 7fbe68e12e9332b46ebe552711550637f523b953 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:11:47 +0100 Subject: [PATCH 082/194] Exclude less from snapshot --- script/lib/generate-startup-snapshot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index fea976b11..f82630cf2 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -45,8 +45,9 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'internal.js') || relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'index.js') || - relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'index.js') || + relativePath == path.join('..', 'node_modules', 'less', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || + relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || relativePath == path.join('..', 'node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || relativePath == path.join('..', 'node_modules', 'superstring', 'index.js') || From f2ea38afbab54a52d92295d3cb10d4fc605f0203 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:12:14 +0100 Subject: [PATCH 083/194] Add electron-mksnapshot and use the downloaded bin to generate snapshots --- script/lib/check-chromedriver-version.js | 11 +++++++++++ script/lib/generate-startup-snapshot.js | 2 +- script/package.json | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/script/lib/check-chromedriver-version.js b/script/lib/check-chromedriver-version.js index 90bc220e5..6fd313fc7 100644 --- a/script/lib/check-chromedriver-version.js +++ b/script/lib/check-chromedriver-version.js @@ -7,16 +7,27 @@ const semver = require('semver') module.exports = function () { // Chromedriver should be specified as ~x.y where x and y match Electron major/minor const chromedriverVer = buildMetadata.dependencies['electron-chromedriver'] + const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot'] // Always use tilde on electron-chromedriver so that it can pick up the best patch vesion if (!chromedriverVer.startsWith('~')) { throw new Error(`electron-chromedriver version in script/package.json should start with a tilde to match latest patch version.`) } + if (!mksnapshotVer.startsWith('~')) { + throw new Error(`electron-mksnapshot version in script/package.json should start with a tilde to match latest patch version.`) + } + const electronVer = CONFIG.appMetadata.electronVersion if (!semver.satisfies(electronVer, chromedriverVer)) { throw new Error(`electron-chromedriver ${chromedriverVer} incompatible with electron ${electronVer}.\n` + 'Did you upgrade electron in package.json and forget to upgrade electron-chromedriver in ' + `script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`) } + + if (!semver.satisfies(electronVer, mksnapshotVer)) { + throw new Error(`electron-mksnapshot ${mksnapshotVer} incompatible with electron ${electronVer}.\n` + + 'Did you upgrade electron in package.json and forget to upgrade electron-mksnapshot in ' + + `script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`) + } } diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index f82630cf2..457956475 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -79,7 +79,7 @@ module.exports = function (packagedAppPath) { const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin') console.log(`Generating startup blob at "${generatedStartupBlobPath}"`) childProcess.execFileSync( - path.join(CONFIG.repositoryRootPath, 'electron', 'mksnapshot', `mksnapshot`), + path.join(CONFIG.repositoryRootPath, 'script', 'node_modules', 'electron-mksnapshot', 'bin', 'mksnapshot'), [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] ) diff --git a/script/package.json b/script/package.json index 56c656866..37950b49d 100644 --- a/script/package.json +++ b/script/package.json @@ -9,6 +9,7 @@ "donna": "1.0.13", "electron-chromedriver": "~1.3", "electron-link": "0.0.8", + "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", "fs-extra": "0.30.0", From 889f480c354e225237155531952ee089dc879254 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:20:52 +0100 Subject: [PATCH 084/194] Delete obsolete asar code --- script/lib/package-application.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/script/lib/package-application.js b/script/lib/package-application.js index 8c4127b4f..5e01e5543 100644 --- a/script/lib/package-application.js +++ b/script/lib/package-application.js @@ -95,20 +95,6 @@ function chmodNodeFiles (packagedAppPath) { childProcess.execSync(`find "${packagedAppPath}" -type f -name *.node -exec chmod a-x {} \\;`) } -function buildAsarUnpackGlobExpression () { - const unpack = [ - '*.node', - 'ctags-config', - 'ctags-darwin', - 'ctags-linux', - 'ctags-win32.exe', - path.join('**', 'node_modules', 'spellchecker', '**'), - path.join('**', 'resources', 'atom.png') - ] - - return `{${unpack.join(',')}}` -} - function getAppName () { if (process.platform === 'darwin') { return CONFIG.channel === 'beta' ? 'Atom Beta' : 'Atom' From f05e882911e2e5e6d20e9339fca03148ca36a59e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:21:12 +0100 Subject: [PATCH 085/194] Copy snapshot in the appropriate location on Linux and Windows --- script/lib/generate-startup-snapshot.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 457956475..6a349eeb4 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -83,10 +83,13 @@ module.exports = function (packagedAppPath) { [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] ) - const startupBlobDestinationPath = path.join( - packagedAppPath, - 'Contents', 'Frameworks', 'Electron Framework.framework', 'Resources', 'snapshot_blob.bin' - ) + let startupBlobDestinationPath + if (process.platform === 'darwin') { + startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin` + } else { + startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin') + } + console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) fs.unlinkSync(startupBlobDestinationPath) fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) From f0e7290b8c73db1d66de36f6bb7795a64ec69d4f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:36:33 +0100 Subject: [PATCH 086/194] Delete unnecessarily excluded modules from snapshot generator --- script/lib/generate-startup-snapshot.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 6a349eeb4..0021cf1ab 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -23,16 +23,13 @@ module.exports = function (packagedAppPath) { const relativePath = path.relative(baseDirPath, modulePath) return ( - modulePath.endsWith('.node') || modulePath === 'buffer-offset-index' || + modulePath.endsWith('.node') || coreModules.has(modulePath) || (relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) || relativePath == path.join('..', 'exports', 'atom.js') || relativePath == path.join('..', 'src', 'config-schema.js') || relativePath == path.join('..', 'src', 'electron-shims.js') || relativePath == path.join('..', 'src', 'safe-clipboard.js') || - relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || - relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'temp', 'lib', 'temp.js') || - relativePath == path.join('..', 'node_modules', 'archive-view', 'node_modules', 'tar', 'tar.js') || relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || relativePath == path.join('..', 'node_modules', 'cached-run-in-this-context', 'lib', 'main.js') || @@ -43,27 +40,19 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || - relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'internal.js') || relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || - relativePath == path.join('..', 'node_modules', 'marker-index', 'dist', 'native', 'marker-index.js') || relativePath == path.join('..', 'node_modules', 'superstring', 'index.js') || - relativePath == path.join('..', 'node_modules', 'mime', 'mime.js') || relativePath == path.join('..', 'node_modules', 'oniguruma', 'lib', 'oniguruma.js') || relativePath == path.join('..', 'node_modules', 'request', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || - relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'htmlparser2', 'lib', 'index.js') || - relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'request', 'index.js') || - relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'cookie.js') || - relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'tough-cookie', 'lib', 'memstore.js') || relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || relativePath == path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') || - relativePath == path.join('..', 'node_modules', 'styleguide', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || relativePath == path.join('..', 'node_modules', 'tar', 'tar.js') || relativePath == path.join('..', 'node_modules', 'temp', 'lib', 'temp.js') || relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') From e74737d38e8ab0a2eab434b83c0b33b595534e76 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:39:04 +0100 Subject: [PATCH 087/194] Fix ThemeManager specs --- spec/theme-manager-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index 795f1b43e..7c43ac2f3 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -194,10 +194,10 @@ describe "atom.themes", -> expect(element.getAttribute('source-path')).toEqualPath lessPath expect(element.textContent).toBe """ #header { - color: #4d926f; + color: #4D926F; } h2 { - color: #4d926f; + color: #4D926F; } """ From a403d817ba39e85372ec5af4ebd783c3911dece4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 14:48:53 +0100 Subject: [PATCH 088/194] Put back source map generation for babel files --- static/babelrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/static/babelrc.json b/static/babelrc.json index 5d887251a..11474dd8d 100644 --- a/static/babelrc.json +++ b/static/babelrc.json @@ -1,4 +1,5 @@ { + "sourceMap": "inline", "plugins": [ ["add-module-exports", {}], ["transform-async-to-generator", {}], From a9f91cc96e65a21627fdb85d0f4a96d9d22dee85 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 16:18:40 +0100 Subject: [PATCH 089/194] Delete test verifying that `GitRepository.open` works with file paths With the upgrade of git-utils libgit2 was updated too and the behavior tested by the deleted spec doesn't seem to be supported anymore. However, we believe we can delete this test because the only entry point for creating `GitRepository` instances is `repositoryForDirectorySync` in `GitRepositoryProvider`, which only allows `Directory` objects to be supplied as its input arguments. --- spec/git-repository-spec.coffee | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/git-repository-spec.coffee b/spec/git-repository-spec.coffee index 59e8c4c68..f1c433d76 100644 --- a/spec/git-repository-spec.coffee +++ b/spec/git-repository-spec.coffee @@ -31,11 +31,6 @@ describe "GitRepository", -> expect(-> new GitRepository(path.join(temp.dir, 'nogit.txt'))).toThrow() describe ".getPath()", -> - it "returns the repository path for a .git directory path with a file", -> - return if process.platform is 'win32' #Win32TestFailures - libgit2 does not detect files in .git folders - repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git', 'HEAD')) - expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git') - it "returns the repository path for a .git directory path with a directory", -> repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects')) expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git') From 1846f9e233e16c0015eb0a9504b992e016ec12ac Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 17:02:56 +0100 Subject: [PATCH 090/194] Invalidate compile cache on AppVeyor --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 3d8c0b274..921d14167 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,4 +51,3 @@ cache: - '%APPVEYOR_BUILD_FOLDER%\node_modules' - '%APPVEYOR_BUILD_FOLDER%\electron' - '%USERPROFILE%\.atom\.apm' - - '%USERPROFILE%\.atom\compile-cache' From 46b06a43368d509f04929523287909c1d51f29e6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 17:44:18 +0100 Subject: [PATCH 091/194] Put back `.atom/compile-cache` on AppVeyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 921d14167..3d8c0b274 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,3 +51,4 @@ cache: - '%APPVEYOR_BUILD_FOLDER%\node_modules' - '%APPVEYOR_BUILD_FOLDER%\electron' - '%USERPROFILE%\.atom\.apm' + - '%USERPROFILE%\.atom\compile-cache' From d64d99d2f74ecfd4db0c059055ba05ac40dab8ac Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 6 Mar 2017 11:47:09 -0500 Subject: [PATCH 092/194] :arrow_up: autocomplete-css@0.15.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97d523fa4..f3fd9d9cd 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "about": "1.7.5", "archive-view": "0.63.0", "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.15.0", + "autocomplete-css": "0.15.1", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.34.2", "autocomplete-snippets": "1.11.0", From 273dd5405ee61380a19c756e805812d522c2a032 Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:49:51 -0500 Subject: [PATCH 093/194] :arrow_up: language-xml@0.35.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3fd9d9cd..0cceb8760 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "language-text": "0.7.1", "language-todo": "0.29.1", "language-toml": "0.18.1", - "language-xml": "0.34.16", + "language-xml": "0.35.0", "language-yaml": "0.28.0" }, "private": true, From 5d6cd1e54291ea10fd62e837e2d401374ccb01ad Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:51:00 -0500 Subject: [PATCH 094/194] :arrow_up: language-property-list@0.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0cceb8760..f1a429063 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "language-objective-c": "0.15.1", "language-perl": "0.37.0", "language-php": "0.37.4", - "language-property-list": "0.9.0", + "language-property-list": "0.9.1", "language-python": "0.45.2", "language-ruby": "0.70.5", "language-ruby-on-rails": "0.25.2", From e97c8f8e6b673f6ae070cafaec142997a89b656d Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:51:54 -0500 Subject: [PATCH 095/194] :arrow_up: language-sass@0.58.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1a429063..e2bd39671 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "language-python": "0.45.2", "language-ruby": "0.70.5", "language-ruby-on-rails": "0.25.2", - "language-sass": "0.57.1", + "language-sass": "0.58.0", "language-shellscript": "0.25.0", "language-source": "0.9.0", "language-sql": "0.25.3", From eb9cf0330a04db8062a74c775d0406c458402c80 Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:53:02 -0500 Subject: [PATCH 096/194] :arrow_up: language-java@0.27.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e2bd39671..c6be559f9 100644 --- a/package.json +++ b/package.json @@ -151,10 +151,10 @@ "language-go": "0.43.1", "language-html": "0.47.2", "language-hyperlink": "0.16.1", - "language-java": "0.26.0", + "language-java": "0.27.0", "language-javascript": "0.126.1", "language-json": "0.18.3", - "language-less": "0.30.1", + "language-less": "0.31.0", "language-make": "0.22.3", "language-mustache": "0.13.1", "language-objective-c": "0.15.1", From 4e8ac61e54f426808378a09da76e7ed65192308e Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:53:42 -0500 Subject: [PATCH 097/194] :arrow_up: language-php@0.37.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6be559f9..9ecbfac5b 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "language-mustache": "0.13.1", "language-objective-c": "0.15.1", "language-perl": "0.37.0", - "language-php": "0.37.4", + "language-php": "0.37.5", "language-property-list": "0.9.1", "language-python": "0.45.2", "language-ruby": "0.70.5", From a58f732b0ad1f0c92b0354a7f74d5b06f87ac786 Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:54:30 -0500 Subject: [PATCH 098/194] :arrow_up: language-json@0.19.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ecbfac5b..a6ac8f639 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "language-hyperlink": "0.16.1", "language-java": "0.27.0", "language-javascript": "0.126.1", - "language-json": "0.18.3", + "language-json": "0.19.0", "language-less": "0.31.0", "language-make": "0.22.3", "language-mustache": "0.13.1", From 6c70984b29ecd85bc2719624f7063df496d58a03 Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:55:14 -0500 Subject: [PATCH 099/194] :arrow_up: language-yaml@0.29.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a6ac8f639..d1afaac99 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "language-todo": "0.29.1", "language-toml": "0.18.1", "language-xml": "0.35.0", - "language-yaml": "0.28.0" + "language-yaml": "0.29.0" }, "private": true, "scripts": { From d298a4e7c6f8d456460895b235784c2b6ee7b4d3 Mon Sep 17 00:00:00 2001 From: Wliu Date: Mon, 6 Mar 2017 11:56:19 -0500 Subject: [PATCH 100/194] :arrow_up: language-text@0.7.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1afaac99..fa571aa88 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "language-shellscript": "0.25.0", "language-source": "0.9.0", "language-sql": "0.25.3", - "language-text": "0.7.1", + "language-text": "0.7.2", "language-todo": "0.29.1", "language-toml": "0.18.1", "language-xml": "0.35.0", From 59c20484aa222af436040f022e0bb44cdee8d7e9 Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Mon, 6 Mar 2017 09:41:14 -0800 Subject: [PATCH 101/194] Always restore Reopen Project menu, fixes #13758 --- src/atom-environment.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index dc4318bec..d7fab1924 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -237,7 +237,7 @@ class AtomEnvironment extends Model @applicationDelegate.didChangeHistoryManager() unless e.reloaded @disposables.add @applicationDelegate.onDidChangeHistoryManager(=> @history.loadState()) - new ReopenProjectMenuManager({@menu, @commands, @history, @config, open: (paths) => @open(pathsToOpen: paths)}) + (new ReopenProjectMenuManager({@menu, @commands, @history, @config, open: (paths) => @open(pathsToOpen: paths)})).update() attachSaveStateListeners: -> saveState = _.debounce((=> From 76ae6b29b3c32207fa60cd8c217b0f74f5e25c3d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 19:58:03 +0100 Subject: [PATCH 102/194] :arrow_up: electron-link to handle cyclic requires correctly --- script/package.json | 2 +- static/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/package.json b/script/package.json index 37950b49d..c8106cc4d 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.8", + "electron-link": "0.0.10", "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", diff --git a/static/index.js b/static/index.js index 7021568db..70ef65d91 100644 --- a/static/index.js +++ b/static/index.js @@ -41,10 +41,10 @@ const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] if (!cachedModule) { - cachedModule = Module._load(module, this, false) + cachedModule = {exports: Module._load(module, this, false)} snapshotResult.customRequire.cache[relativeFilePath] = cachedModule } - return cachedModule + return cachedModule.exports } snapshotResult.setGlobals(global, process, window, document, require) From 24503806d7eb77c62c1da45398b2ad00a8178d27 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 6 Mar 2017 17:30:43 -0500 Subject: [PATCH 103/194] :arrow_up: language-c@0.57.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa571aa88..ed3756a38 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "welcome": "0.36.2", "whitespace": "0.36.2", "wrap-guide": "0.40.0", - "language-c": "0.56.0", + "language-c": "0.57.0", "language-clojure": "0.22.2", "language-coffee-script": "0.48.5", "language-csharp": "0.14.2", From 051e27dbcb733b66349873a96b2458fd88d6fed5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Mar 2017 20:03:40 +0100 Subject: [PATCH 104/194] Expose load settings to `BrowserWindow`s as JSON When accessing objects in the main process via the `remote` module, Electron returns proxy objects that are references to the original ones. This means that trying to access a remote object's property or function results in a synchronous message exchange with the main process. In Atom core we frequently access the load settings coming from the main process, especially during startup. This caused a lot of synchronous I/O which was blocking the renderer process for several milliseconds. With this commit, instead of exposing load settings as a JavaScript object, we serialize them to JSON in the main process and parse them back to a JavaScript object in the renderer processes. This allows us to get a full copy of the object locally and pay for I/O just once when retrieving load settings from the main process for the first time. --- src/get-window-load-settings.js | 2 +- src/main-process/atom-window.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/get-window-load-settings.js b/src/get-window-load-settings.js index 7ee465141..d35b24213 100644 --- a/src/get-window-load-settings.js +++ b/src/get-window-load-settings.js @@ -4,7 +4,7 @@ let windowLoadSettings = null module.exports = () => { if (!windowLoadSettings) { - windowLoadSettings = remote.getCurrentWindow().loadSettings + windowLoadSettings = JSON.parse(remote.getCurrentWindow().loadSettingsJSON) } return windowLoadSettings } diff --git a/src/main-process/atom-window.coffee b/src/main-process/atom-window.coffee index 03386d31a..da903d71e 100644 --- a/src/main-process/atom-window.coffee +++ b/src/main-process/atom-window.coffee @@ -83,7 +83,7 @@ class AtomWindow @representedDirectoryPaths = loadSettings.initialPaths @env = loadSettings.env if loadSettings.env? - @browserWindow.loadSettings = loadSettings + @browserWindow.loadSettingsJSON = JSON.stringify(loadSettings) @browserWindow.on 'window:loaded', => @emit 'window:loaded' From 727472af58246f21bde7da2d3e2a2c65d8ee1659 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 7 Mar 2017 09:35:11 +0100 Subject: [PATCH 105/194] Disable zoom in the main process This commit will register the `display-added` and `display-removed` events only once in the main process in order to disable zoom (see https://github.com/atom/atom/pull/11345) directly instead of unnecessarily paying for I/O in the renderer process during startup. --- src/application-delegate.coffee | 16 +--------------- src/atom-environment.coffee | 2 -- src/main-process/atom-application.coffee | 22 +++++++++++++++++++--- src/main-process/atom-window.coffee | 4 ++++ 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee index 766ba7aa8..cd7a4d128 100644 --- a/src/application-delegate.coffee +++ b/src/application-delegate.coffee @@ -1,5 +1,5 @@ _ = require 'underscore-plus' -{screen, ipcRenderer, remote, shell, webFrame} = require 'electron' +{ipcRenderer, remote, shell} = require 'electron' ipcHelpers = require './ipc-helpers' {Disposable} = require 'event-kit' getWindowLoadSettings = require './get-window-load-settings' @@ -254,20 +254,6 @@ class ApplicationDelegate openExternal: (url) -> shell.openExternal(url) - disableZoom: -> - outerCallback = -> - webFrame.setZoomLevelLimits(1, 1) - - outerCallback() - # Set the limits every time a display is added or removed, otherwise the - # configuration gets reset to the default, which allows zooming the - # webframe. - screen.on('display-added', outerCallback) - screen.on('display-removed', outerCallback) - new Disposable -> - screen.removeListener('display-added', outerCallback) - screen.removeListener('display-removed', outerCallback) - checkForUpdate: -> ipcRenderer.send('command', 'application:check-for-update') diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index d7fab1924..d834a355f 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -215,8 +215,6 @@ class AtomEnvironment extends Model @stylesElement = @styles.buildStylesElement() @document.head.appendChild(@stylesElement) - @disposables.add(@applicationDelegate.disableZoom()) - @keymaps.subscribeToFileReadFailure() @keymaps.loadBundledKeymaps() diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index 93e9e3395..295343100 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -6,8 +6,8 @@ StorageFolder = require '../storage-folder' Config = require '../config' FileRecoveryService = require './file-recovery-service' ipcHelpers = require '../ipc-helpers' -{BrowserWindow, Menu, app, dialog, ipcMain, shell} = require 'electron' -{CompositeDisposable} = require 'event-kit' +{BrowserWindow, Menu, app, dialog, ipcMain, shell, screen} = require 'electron' +{CompositeDisposable, Disposable} = require 'event-kit' fs = require 'fs-plus' path = require 'path' os = require 'os' @@ -89,7 +89,7 @@ class AtomApplication if process.platform is 'darwin' and @config.get('core.useCustomTitleBar') @config.unset('core.useCustomTitleBar') @config.set('core.titleBar', 'custom') - + @config.onDidChange 'core.titleBar', @promptForRestart.bind(this) @autoUpdateManager = new AutoUpdateManager( @@ -394,6 +394,8 @@ class AtomApplication @disposable.add ipcHelpers.on ipcMain, 'did-change-paths', => @saveState(false) + @disposable.add(@disableZoomOnDisplayChange()) + setupDockMenu: -> if process.platform is 'darwin' dockMenu = Menu.buildFromTemplate [ @@ -812,3 +814,17 @@ class AtomApplication args.push("--resource-path=#{@resourcePath}") app.relaunch({args}) app.quit() + + disableZoomOnDisplayChange: -> + outerCallback = => + for window in @windows + window.disableZoom() + + # Set the limits every time a display is added or removed, otherwise the + # configuration gets reset to the default, which allows zooming the + # webframe. + screen.on('display-added', outerCallback) + screen.on('display-removed', outerCallback) + new Disposable -> + screen.removeListener('display-added', outerCallback) + screen.removeListener('display-removed', outerCallback) diff --git a/src/main-process/atom-window.coffee b/src/main-process/atom-window.coffee index da903d71e..6013819e3 100644 --- a/src/main-process/atom-window.coffee +++ b/src/main-process/atom-window.coffee @@ -101,6 +101,7 @@ class AtomWindow hasPathToOpen = not (locationsToOpen.length is 1 and not locationsToOpen[0].pathToOpen?) @openLocations(locationsToOpen) if hasPathToOpen and not @isSpecWindow() + @disableZoom() @atomApplication.addWindow(this) @@ -303,3 +304,6 @@ class AtomWindow @atomApplication.saveState() copy: -> @browserWindow.copy() + + disableZoom: -> + @browserWindow.webContents.setZoomLevelLimits(1, 1) From 45d41ca69f00426f911c721882a6500bb83f1154 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 7 Mar 2017 09:40:20 +0100 Subject: [PATCH 106/194] Register enter/leave fullscreen events in the main process This will still notify render processes when such events are triggered without, however, incurring the additional cost of synchronously retrieving a `BrowserWindow` (and its properties) via `remote` during startup. --- src/application-delegate.coffee | 6 ++++++ src/main-process/atom-window.coffee | 6 ++++++ src/window-event-handler.coffee | 10 ++-------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee index cd7a4d128..b247fe7c2 100644 --- a/src/application-delegate.coffee +++ b/src/application-delegate.coffee @@ -80,6 +80,12 @@ class ApplicationDelegate setWindowFullScreen: (fullScreen=false) -> ipcHelpers.call('window-method', 'setFullScreen', fullScreen) + onDidEnterFullScreen: (callback) -> + ipcHelpers.on(ipcRenderer, 'did-enter-full-screen', callback) + + onDidLeaveFullScreen: (callback) -> + ipcHelpers.on(ipcRenderer, 'did-leave-full-screen', callback) + openWindowDevTools: -> # Defer DevTools interaction to the next tick, because using them during # event handling causes some wrong input events to be triggered on diff --git a/src/main-process/atom-window.coffee b/src/main-process/atom-window.coffee index 6013819e3..bbc235bc5 100644 --- a/src/main-process/atom-window.coffee +++ b/src/main-process/atom-window.coffee @@ -89,6 +89,12 @@ class AtomWindow @emit 'window:loaded' @resolveLoadedPromise() + @browserWindow.on 'enter-full-screen', => + @browserWindow.webContents.send('did-enter-full-screen') + + @browserWindow.on 'leave-full-screen', => + @browserWindow.webContents.send('did-leave-full-screen') + @browserWindow.loadURL url.format protocol: 'file' pathname: "#{@resourcePath}/static/index.html" diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 62ce4527a..95cd45de9 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -20,14 +20,8 @@ class WindowEventHandler @subscriptions.add listen(@document, 'click', 'a', @handleLinkClick) @subscriptions.add listen(@document, 'submit', 'form', @handleFormSubmit) - browserWindow = @applicationDelegate.getCurrentWindow() - browserWindow.on 'enter-full-screen', @handleEnterFullScreen - @subscriptions.add new Disposable => - browserWindow.removeListener('enter-full-screen', @handleEnterFullScreen) - - browserWindow.on 'leave-full-screen', @handleLeaveFullScreen - @subscriptions.add new Disposable => - browserWindow.removeListener('leave-full-screen', @handleLeaveFullScreen) + @subscriptions.add(@applicationDelegate.onDidEnterFullScreen(@handleEnterFullScreen)) + @subscriptions.add(@applicationDelegate.onDidLeaveFullScreen(@handleLeaveFullScreen)) @subscriptions.add @atomEnvironment.commands.add @window, 'window:toggle-full-screen': @handleWindowToggleFullScreen From cf9a5b13e3d875b87de5bf0e3446c4edf80565f1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 7 Mar 2017 10:37:41 +0100 Subject: [PATCH 107/194] Replace `localStorage` with `StateStore` in `HistoryManager` Instead of using `localStorage` to store and retrieve the project history, with this commit we will use `StateStore` so that we can retrieve state asynchronously without blocking Atom during startup. --- spec/history-manager-spec.js | 103 ++++++++++++++--------------- src/atom-environment.coffee | 15 +++-- src/history-manager.js | 56 ++++++++-------- src/reopen-project-menu-manager.js | 4 +- 4 files changed, 90 insertions(+), 88 deletions(-) diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index 425f1efe0..bc77cb9b8 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -4,27 +4,24 @@ import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers' import {Emitter, Disposable, CompositeDisposable} from 'event-kit' import {HistoryManager, HistoryProject} from '../src/history-manager' +import StateStore from '../src/state-store' describe("HistoryManager", () => { - let historyManager, commandRegistry, project, localStorage, stateStore + let historyManager, commandRegistry, project, stateStore let commandDisposable, projectDisposable - beforeEach(() => { + beforeEach(async () => { commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']) commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']) commandRegistry.add.andReturn(commandDisposable) - localStorage = jasmine.createSpyObj('LocalStorage', ['getItem', 'setItem']) - localStorage.items = { - history: JSON.stringify({ - projects: [ - { paths: ['/1', 'c:\\2'], lastOpened: new Date(2016, 9, 17, 17, 16, 23) }, - { paths: ['/test'], lastOpened: new Date(2016, 9, 17, 11, 12, 13) } - ] - }) - } - localStorage.getItem.andCallFake((key) => localStorage.items[key]) - localStorage.setItem.andCallFake((key, value) => localStorage.items[key] = value) + stateStore = new StateStore('history-manager-test', 1) + await stateStore.save('history-manager', { + projects: [ + {paths: ['/1', 'c:\\2'], lastOpened: new Date(2016, 9, 17, 17, 16, 23)}, + {paths: ['/test'], lastOpened: new Date(2016, 9, 17, 11, 12, 13)} + ] + }) projectDisposable = jasmine.createSpyObj('Disposable', ['dispose']) project = jasmine.createSpyObj('Project', ['onDidChangePaths']) @@ -33,7 +30,12 @@ describe("HistoryManager", () => { return projectDisposable }) - historyManager = new HistoryManager({project, commands:commandRegistry, localStorage}) + historyManager = new HistoryManager({stateStore, project, commands: commandRegistry}) + await historyManager.loadState() + }) + + afterEach(async () => { + await stateStore.clear() }) describe("constructor", () => { @@ -65,33 +67,28 @@ describe("HistoryManager", () => { }) describe("clearProjects", () => { - it("clears the list of projects", () => { + it("clears the list of projects", async () => { expect(historyManager.getProjects().length).not.toBe(0) - historyManager.clearProjects() + await historyManager.clearProjects() expect(historyManager.getProjects().length).toBe(0) }) - it("saves the state", () => { - expect(localStorage.setItem).not.toHaveBeenCalled() - historyManager.clearProjects() - expect(localStorage.setItem).toHaveBeenCalled() - expect(localStorage.setItem.calls[0].args[0]).toBe('history') + it("saves the state", async () => { + await historyManager.clearProjects() + const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry}) + await historyManager2.loadState() expect(historyManager.getProjects().length).toBe(0) }) - it("fires the onDidChangeProjects event", () => { - expect(localStorage.setItem).not.toHaveBeenCalled() - historyManager.clearProjects() - expect(localStorage.setItem).toHaveBeenCalled() - expect(localStorage.setItem.calls[0].args[0]).toBe('history') + it("fires the onDidChangeProjects event", async () => { + const didChangeSpy = jasmine.createSpy() + historyManager.onDidChangeProjects(didChangeSpy) + await historyManager.clearProjects() expect(historyManager.getProjects().length).toBe(0) + expect(didChangeSpy).toHaveBeenCalled() }) }) - it("loads state", () => { - expect(localStorage.getItem).toHaveBeenCalledWith('history') - }) - it("listens to project.onDidChangePaths adding a new project", () => { const start = new Date() project.didChangePathsListener(['/a/new', '/path/or/two']) @@ -112,61 +109,61 @@ describe("HistoryManager", () => { }) describe("loadState", () => { - it("defaults to an empty array if no state", () => { - localStorage.items.history = null - historyManager.loadState() + it("defaults to an empty array if no state", async () => { + await stateStore.clear() + await historyManager.loadState() expect(historyManager.getProjects()).toEqual([]) }) - it("defaults to an empty array if no projects", () => { - localStorage.items.history = JSON.stringify('') - historyManager.loadState() + it("defaults to an empty array if no projects", async () => { + await stateStore.save('history-manager', {}) + await historyManager.loadState() expect(historyManager.getProjects()).toEqual([]) }) }) describe("addProject", () => { - it("adds a new project to the end", () => { + it("adds a new project to the end", async () => { const date = new Date(2010, 10, 9, 8, 7, 6) - historyManager.addProject(['/a/b'], date) + await historyManager.addProject(['/a/b'], date) const projects = historyManager.getProjects() expect(projects.length).toBe(3) expect(projects[2].paths).toEqual(['/a/b']) expect(projects[2].lastOpened).toBe(date) }) - it("adds a new project to the start", () => { + it("adds a new project to the start", async () => { const date = new Date() - historyManager.addProject(['/so/new'], date) + await historyManager.addProject(['/so/new'], date) const projects = historyManager.getProjects() expect(projects.length).toBe(3) expect(projects[0].paths).toEqual(['/so/new']) expect(projects[0].lastOpened).toBe(date) }) - it("updates an existing project and moves it to the start", () => { + it("updates an existing project and moves it to the start", async () => { const date = new Date() - historyManager.addProject(['/test'], date) + await historyManager.addProject(['/test'], date) const projects = historyManager.getProjects() expect(projects.length).toBe(2) expect(projects[0].paths).toEqual(['/test']) expect(projects[0].lastOpened).toBe(date) }) - it("fires the onDidChangeProjects event when adding a project", () => { + it("fires the onDidChangeProjects event when adding a project", async () => { const didChangeSpy = jasmine.createSpy() const beforeCount = historyManager.getProjects().length historyManager.onDidChangeProjects(didChangeSpy) - historyManager.addProject(['/test-new'], new Date()) + await historyManager.addProject(['/test-new'], new Date()) expect(didChangeSpy).toHaveBeenCalled() expect(historyManager.getProjects().length).toBe(beforeCount + 1) }) - it("fires the onDidChangeProjects event when updating a project", () => { + it("fires the onDidChangeProjects event when updating a project", async () => { const didChangeSpy = jasmine.createSpy() const beforeCount = historyManager.getProjects().length historyManager.onDidChangeProjects(didChangeSpy) - historyManager.addProject(['/test'], new Date()) + await historyManager.addProject(['/test'], new Date()) expect(didChangeSpy).toHaveBeenCalled() expect(historyManager.getProjects().length).toBe(beforeCount) }) @@ -186,14 +183,12 @@ describe("HistoryManager", () => { }) describe("saveState" ,() => { - it("saves the state", () => { - historyManager.addProject(["/save/state"]) - historyManager.saveState() - expect(localStorage.setItem).toHaveBeenCalled() - expect(localStorage.setItem.calls[0].args[0]).toBe('history') - expect(localStorage.items['history']).toContain('/save/state') - historyManager.loadState() - expect(historyManager.getProjects()[0].paths).toEqual(['/save/state']) + it("saves the state", async () => { + await historyManager.addProject(["/save/state"]) + await historyManager.saveState() + const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry}) + await historyManager2.loadState() + expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state']) }) }) }) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index d834a355f..cf5df00d2 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -229,14 +229,12 @@ class AtomEnvironment extends Model @observeAutoHideMenuBar() - @history = new HistoryManager({@project, @commands, localStorage}) + @history = new HistoryManager({@project, @commands, @stateStore}) # Keep instances of HistoryManager in sync - @history.onDidChangeProjects (e) => + @disposables.add @history.onDidChangeProjects (e) => @applicationDelegate.didChangeHistoryManager() unless e.reloaded @disposables.add @applicationDelegate.onDidChangeHistoryManager(=> @history.loadState()) - (new ReopenProjectMenuManager({@menu, @commands, @history, @config, open: (paths) => @open(pathsToOpen: paths)})).update() - attachSaveStateListeners: -> saveState = _.debounce((=> window.requestIdleCallback => @saveState({isUnloading: false}) unless @unloaded @@ -714,7 +712,14 @@ class AtomEnvironment extends Model @openInitialEmptyEditorIfNecessary() - Promise.all([loadStatePromise, updateProcessEnvPromise]) + loadHistoryPromise = @history.loadState().then => + @reopenProjectMenuManager = new ReopenProjectMenuManager({ + @menu, @commands, @history, @config, + open: (paths) => @open(pathsToOpen: paths) + }) + @reopenProjectMenuManager.update() + + Promise.all([loadStatePromise, loadHistoryPromise, updateProcessEnvPromise]) serialize: (options) -> version: @constructor.version diff --git a/src/history-manager.js b/src/history-manager.js index f013957b9..5087c3bf9 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -1,6 +1,6 @@ /** @babel */ -import {Emitter} from 'event-kit' +import {Emitter, CompositeDisposable} from 'event-kit' // Extended: History manager for remembering which projects have been opened. // @@ -8,12 +8,17 @@ import {Emitter} from 'event-kit' // // The project history is used to enable the 'Reopen Project' menu. export class HistoryManager { - constructor ({project, commands, localStorage}) { - this.localStorage = localStorage - commands.add('atom-workspace', {'application:clear-project-history': this.clearProjects.bind(this)}) + constructor ({stateStore, project, commands}) { + this.stateStore = stateStore this.emitter = new Emitter() - this.loadState() - project.onDidChangePaths((projectPaths) => this.addProject(projectPaths)) + this.projects = [] + this.disposables = new CompositeDisposable() + this.disposables.add(commands.add('atom-workspace', {'application:clear-project-history': this.clearProjects.bind(this)})) + this.disposables.add(project.onDidChangePaths((projectPaths) => this.addProject(projectPaths))) + } + + destroy () { + this.disposables.dispose() } // Public: Obtain a list of previously opened projects. @@ -27,9 +32,12 @@ export class HistoryManager { // // Note: This is not a privacy function - other traces will still exist, // e.g. window state. - clearProjects () { + // + // Return a {Promise} that resolves when the history has been successfully + // cleared. + async clearProjects () { this.projects = [] - this.saveState() + await this.saveState() this.didChangeProjects() } @@ -46,7 +54,7 @@ export class HistoryManager { this.emitter.emit('did-change-projects', args || { reloaded: false }) } - addProject (paths, lastOpened) { + async addProject (paths, lastOpened) { if (paths.length === 0) return let project = this.getProject(paths) @@ -57,11 +65,11 @@ export class HistoryManager { project.lastOpened = lastOpened || new Date() this.projects.sort((a, b) => b.lastOpened - a.lastOpened) - this.saveState() + await this.saveState() this.didChangeProjects() } - removeProject (paths) { + async removeProject (paths) { if (paths.length === 0) return let project = this.getProject(paths) @@ -70,7 +78,7 @@ export class HistoryManager { let index = this.projects.indexOf(project) this.projects.splice(index, 1) - this.saveState() + await this.saveState() this.didChangeProjects() } @@ -84,31 +92,25 @@ export class HistoryManager { return null } - loadState () { - const state = JSON.parse(this.localStorage.getItem('history')) - if (state && state.projects) { - this.projects = state.projects.filter(p => Array.isArray(p.paths) && p.paths.length > 0).map(p => new HistoryProject(p.paths, new Date(p.lastOpened))) - this.didChangeProjects({ reloaded: true }) + async loadState () { + const history = await this.stateStore.load('history-manager') + if (history && history.projects) { + this.projects = history.projects.filter(p => Array.isArray(p.paths) && p.paths.length > 0).map(p => new HistoryProject(p.paths, new Date(p.lastOpened))) + this.didChangeProjects({reloaded: true}) } else { this.projects = [] } } - saveState () { - const state = JSON.stringify({ - projects: this.projects.map(p => ({ - paths: p.paths, lastOpened: p.lastOpened - })) - }) - this.localStorage.setItem('history', state) + async saveState () { + const projects = this.projects.map(p => ({paths: p.paths, lastOpened: p.lastOpened})) + await this.stateStore.save('history-manager', {projects}) } async importProjectHistory () { for (let project of await HistoryImporter.getAllProjects()) { - this.addProject(project.paths, project.lastOpened) + await this.addProject(project.paths, project.lastOpened) } - this.saveState() - this.didChangeProjects() } } diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js index 79acbba66..3f88e41f0 100644 --- a/src/reopen-project-menu-manager.js +++ b/src/reopen-project-menu-manager.js @@ -58,7 +58,7 @@ export default class ReopenProjectMenuManager { // Windows users can right-click Atom taskbar and remove project from the jump list. // We have to honor that or the group stops working. As we only get a partial list // each time we remove them from history entirely. - applyWindowsJumpListRemovals () { + async applyWindowsJumpListRemovals () { if (process.platform !== 'win32') return if (this.app === undefined) { this.app = require('remote').app @@ -68,7 +68,7 @@ export default class ReopenProjectMenuManager { if (removed.length === 0) return for (let project of this.historyManager.getProjects()) { if (removed.includes(ReopenProjectMenuManager.taskDescription(project.paths))) { - this.historyManager.removeProject(project.paths) + await this.historyManager.removeProject(project.paths) } } } From 6f9a536aca8f64346bda55624553c55845fc0e80 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 7 Mar 2017 11:04:45 -0500 Subject: [PATCH 108/194] :arrow_up: language-css@0.42.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed3756a38..8207d3307 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "language-clojure": "0.22.2", "language-coffee-script": "0.48.5", "language-csharp": "0.14.2", - "language-css": "0.42.0", + "language-css": "0.42.1", "language-gfm": "0.88.1", "language-git": "0.19.0", "language-go": "0.43.1", From 228b24b68bacfecd810910b5c06383c74390f2ef Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Tue, 7 Mar 2017 14:20:27 -0800 Subject: [PATCH 109/194] Convert panel container to JS: Decaffeinate --- spec/panel-container-element-spec.coffee | 137 -------------------- spec/panel-container-element-spec.js | 154 +++++++++++++++++++++++ spec/panel-container-spec.coffee | 113 ----------------- spec/panel-container-spec.js | 139 ++++++++++++++++++++ src/panel-container-element.coffee | 45 ------- src/panel-container-element.js | 59 +++++++++ src/panel-container.coffee | 71 ----------- src/panel-container.js | 92 ++++++++++++++ 8 files changed, 444 insertions(+), 366 deletions(-) delete mode 100644 spec/panel-container-element-spec.coffee create mode 100644 spec/panel-container-element-spec.js delete mode 100644 spec/panel-container-spec.coffee create mode 100644 spec/panel-container-spec.js delete mode 100644 src/panel-container-element.coffee create mode 100644 src/panel-container-element.js delete mode 100644 src/panel-container.coffee create mode 100644 src/panel-container.js diff --git a/spec/panel-container-element-spec.coffee b/spec/panel-container-element-spec.coffee deleted file mode 100644 index 55e6f7133..000000000 --- a/spec/panel-container-element-spec.coffee +++ /dev/null @@ -1,137 +0,0 @@ -Panel = require '../src/panel' -PanelContainer = require '../src/panel-container' - -describe "PanelContainerElement", -> - [jasmineContent, element, container] = [] - - class TestPanelContainerItem - constructior: -> - - class TestPanelContainerItemElement extends HTMLElement - createdCallback: -> - @classList.add('test-root') - initialize: (@model) -> - this - - TestPanelContainerItemElement = document.registerElement 'atom-test-container-item-element', prototype: TestPanelContainerItemElement.prototype - - beforeEach -> - jasmineContent = document.body.querySelector('#jasmine-content') - - atom.views.addViewProvider TestPanelContainerItem, (model) -> - new TestPanelContainerItemElement().initialize(model) - - container = new PanelContainer({location: 'left'}) - element = atom.views.getView(container) - jasmineContent.appendChild(element) - - it 'has a location class with value from the model', -> - expect(element).toHaveClass 'left' - - it 'removes the element when the container is destroyed', -> - expect(element.parentNode).toBe jasmineContent - container.destroy() - expect(element.parentNode).not.toBe jasmineContent - - describe "adding and removing panels", -> - it "allows panels to be inserted at any position", -> - panel1 = new Panel({item: new TestPanelContainerItem(), priority: 10}) - panel2 = new Panel({item: new TestPanelContainerItem(), priority: 5}) - panel3 = new Panel({item: new TestPanelContainerItem(), priority: 8}) - - container.addPanel(panel1) - container.addPanel(panel2) - container.addPanel(panel3) - - expect(element.childNodes[2].getModel()).toBe(panel1) - expect(element.childNodes[1].getModel()).toBe(panel3) - expect(element.childNodes[0].getModel()).toBe(panel2) - - describe "when the container is at the left location", -> - it "adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed", -> - expect(element.childNodes.length).toBe 0 - - panel1 = new Panel({item: new TestPanelContainerItem()}) - container.addPanel(panel1) - expect(element.childNodes.length).toBe 1 - expect(element.childNodes[0]).toHaveClass 'left' - expect(element.childNodes[0]).toHaveClass 'tool-panel' # legacy selector support - expect(element.childNodes[0]).toHaveClass 'panel-left' # legacy selector support - - expect(element.childNodes[0].tagName).toBe 'ATOM-PANEL' - - panel2 = new Panel({item: new TestPanelContainerItem()}) - container.addPanel(panel2) - expect(element.childNodes.length).toBe 2 - - expect(atom.views.getView(panel1).style.display).not.toBe 'none' - expect(atom.views.getView(panel2).style.display).not.toBe 'none' - - panel1.destroy() - expect(element.childNodes.length).toBe 1 - - panel2.destroy() - expect(element.childNodes.length).toBe 0 - - describe "when the container is at the bottom location", -> - beforeEach -> - container = new PanelContainer({location: 'bottom'}) - element = atom.views.getView(container) - jasmineContent.appendChild(element) - - it "adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed", -> - expect(element.childNodes.length).toBe 0 - - panel1 = new Panel({item: new TestPanelContainerItem(), className: 'one'}) - container.addPanel(panel1) - expect(element.childNodes.length).toBe 1 - expect(element.childNodes[0]).toHaveClass 'bottom' - expect(element.childNodes[0]).toHaveClass 'tool-panel' # legacy selector support - expect(element.childNodes[0]).toHaveClass 'panel-bottom' # legacy selector support - expect(element.childNodes[0].tagName).toBe 'ATOM-PANEL' - expect(atom.views.getView(panel1)).toHaveClass 'one' - - panel2 = new Panel({item: new TestPanelContainerItem(), className: 'two'}) - container.addPanel(panel2) - expect(element.childNodes.length).toBe 2 - expect(atom.views.getView(panel2)).toHaveClass 'two' - - panel1.destroy() - expect(element.childNodes.length).toBe 1 - - panel2.destroy() - expect(element.childNodes.length).toBe 0 - - describe "when the container is modal", -> - beforeEach -> - container = new PanelContainer({location: 'modal'}) - element = atom.views.getView(container) - jasmineContent.appendChild(element) - - it "allows only one panel to be visible at a time", -> - panel1 = new Panel({item: new TestPanelContainerItem()}) - container.addPanel(panel1) - - expect(atom.views.getView(panel1).style.display).not.toBe 'none' - - panel2 = new Panel({item: new TestPanelContainerItem()}) - container.addPanel(panel2) - - expect(atom.views.getView(panel1).style.display).toBe 'none' - expect(atom.views.getView(panel2).style.display).not.toBe 'none' - - panel1.show() - - expect(atom.views.getView(panel1).style.display).not.toBe 'none' - expect(atom.views.getView(panel2).style.display).toBe 'none' - - it "adds the 'modal' class to panels", -> - panel1 = new Panel({item: new TestPanelContainerItem()}) - container.addPanel(panel1) - - expect(atom.views.getView(panel1)).toHaveClass 'modal' - - # legacy selector support - expect(atom.views.getView(panel1)).not.toHaveClass 'tool-panel' - expect(atom.views.getView(panel1)).toHaveClass 'overlay' - expect(atom.views.getView(panel1)).toHaveClass 'from-top' diff --git a/spec/panel-container-element-spec.js b/spec/panel-container-element-spec.js new file mode 100644 index 000000000..167fd92ed --- /dev/null +++ b/spec/panel-container-element-spec.js @@ -0,0 +1,154 @@ +const Panel = require('../src/panel'); +const PanelContainer = require('../src/panel-container'); + +describe("PanelContainerElement", function() { + let [jasmineContent, element, container] = Array.from([]); + + class TestPanelContainerItem { + constructior() {} + } + + class TestPanelContainerItemElement extends HTMLElement { + createdCallback() { + return this.classList.add('test-root'); + } + initialize(model) { + this.model = model; + return this; + } + } + + TestPanelContainerItemElement = document.registerElement('atom-test-container-item-element', {prototype: TestPanelContainerItemElement.prototype}); + + beforeEach(function() { + jasmineContent = document.body.querySelector('#jasmine-content'); + + atom.views.addViewProvider(TestPanelContainerItem, model => new TestPanelContainerItemElement().initialize(model)); + + container = new PanelContainer({location: 'left'}); + element = atom.views.getView(container); + return jasmineContent.appendChild(element); + }); + + it('has a location class with value from the model', () => expect(element).toHaveClass('left')); + + it('removes the element when the container is destroyed', function() { + expect(element.parentNode).toBe(jasmineContent); + container.destroy(); + return expect(element.parentNode).not.toBe(jasmineContent); + }); + + describe("adding and removing panels", function() { + it("allows panels to be inserted at any position", function() { + const panel1 = new Panel({item: new TestPanelContainerItem(), priority: 10}); + const panel2 = new Panel({item: new TestPanelContainerItem(), priority: 5}); + const panel3 = new Panel({item: new TestPanelContainerItem(), priority: 8}); + + container.addPanel(panel1); + container.addPanel(panel2); + container.addPanel(panel3); + + expect(element.childNodes[2].getModel()).toBe(panel1); + expect(element.childNodes[1].getModel()).toBe(panel3); + return expect(element.childNodes[0].getModel()).toBe(panel2); + }); + + describe("when the container is at the left location", () => + it("adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed", function() { + expect(element.childNodes.length).toBe(0); + + const panel1 = new Panel({item: new TestPanelContainerItem()}); + container.addPanel(panel1); + expect(element.childNodes.length).toBe(1); + expect(element.childNodes[0]).toHaveClass('left'); + expect(element.childNodes[0]).toHaveClass('tool-panel'); // legacy selector support + expect(element.childNodes[0]).toHaveClass('panel-left'); // legacy selector support + + expect(element.childNodes[0].tagName).toBe('ATOM-PANEL'); + + const panel2 = new Panel({item: new TestPanelContainerItem()}); + container.addPanel(panel2); + expect(element.childNodes.length).toBe(2); + + expect(atom.views.getView(panel1).style.display).not.toBe('none'); + expect(atom.views.getView(panel2).style.display).not.toBe('none'); + + panel1.destroy(); + expect(element.childNodes.length).toBe(1); + + panel2.destroy(); + return expect(element.childNodes.length).toBe(0); + }) + ); + + return describe("when the container is at the bottom location", function() { + beforeEach(function() { + container = new PanelContainer({location: 'bottom'}); + element = atom.views.getView(container); + return jasmineContent.appendChild(element); + }); + + return it("adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed", function() { + expect(element.childNodes.length).toBe(0); + + const panel1 = new Panel({item: new TestPanelContainerItem(), className: 'one'}); + container.addPanel(panel1); + expect(element.childNodes.length).toBe(1); + expect(element.childNodes[0]).toHaveClass('bottom'); + expect(element.childNodes[0]).toHaveClass('tool-panel'); // legacy selector support + expect(element.childNodes[0]).toHaveClass('panel-bottom'); // legacy selector support + expect(element.childNodes[0].tagName).toBe('ATOM-PANEL'); + expect(atom.views.getView(panel1)).toHaveClass('one'); + + const panel2 = new Panel({item: new TestPanelContainerItem(), className: 'two'}); + container.addPanel(panel2); + expect(element.childNodes.length).toBe(2); + expect(atom.views.getView(panel2)).toHaveClass('two'); + + panel1.destroy(); + expect(element.childNodes.length).toBe(1); + + panel2.destroy(); + return expect(element.childNodes.length).toBe(0); + }); + }); + }); + + return describe("when the container is modal", function() { + beforeEach(function() { + container = new PanelContainer({location: 'modal'}); + element = atom.views.getView(container); + return jasmineContent.appendChild(element); + }); + + it("allows only one panel to be visible at a time", function() { + const panel1 = new Panel({item: new TestPanelContainerItem()}); + container.addPanel(panel1); + + expect(atom.views.getView(panel1).style.display).not.toBe('none'); + + const panel2 = new Panel({item: new TestPanelContainerItem()}); + container.addPanel(panel2); + + expect(atom.views.getView(panel1).style.display).toBe('none'); + expect(atom.views.getView(panel2).style.display).not.toBe('none'); + + panel1.show(); + + expect(atom.views.getView(panel1).style.display).not.toBe('none'); + return expect(atom.views.getView(panel2).style.display).toBe('none'); + }); + + return it("adds the 'modal' class to panels", function() { + const panel1 = new Panel({item: new TestPanelContainerItem()}); + container.addPanel(panel1); + + expect(atom.views.getView(panel1)).toHaveClass('modal'); + + // legacy selector support + expect(atom.views.getView(panel1)).not.toHaveClass('tool-panel'); + expect(atom.views.getView(panel1)).toHaveClass('overlay'); + return expect(atom.views.getView(panel1)).toHaveClass('from-top'); + }); + }); +}); diff --git a/spec/panel-container-spec.coffee b/spec/panel-container-spec.coffee deleted file mode 100644 index fbf1c3446..000000000 --- a/spec/panel-container-spec.coffee +++ /dev/null @@ -1,113 +0,0 @@ -Panel = require '../src/panel' -PanelContainer = require '../src/panel-container' - -describe "PanelContainer", -> - [container] = [] - - class TestPanelItem - constructor: -> - - beforeEach -> - container = new PanelContainer - - describe "::addPanel(panel)", -> - it 'emits an onDidAddPanel event with the index the panel was inserted at', -> - container.onDidAddPanel addPanelSpy = jasmine.createSpy() - - panel1 = new Panel(item: new TestPanelItem()) - container.addPanel(panel1) - expect(addPanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) - - panel2 = new Panel(item: new TestPanelItem()) - container.addPanel(panel2) - expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) - - describe "when a panel is destroyed", -> - it 'emits an onDidRemovePanel event with the index of the removed item', -> - container.onDidRemovePanel removePanelSpy = jasmine.createSpy() - - panel1 = new Panel(item: new TestPanelItem()) - container.addPanel(panel1) - panel2 = new Panel(item: new TestPanelItem()) - container.addPanel(panel2) - - expect(removePanelSpy).not.toHaveBeenCalled() - - panel2.destroy() - expect(removePanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) - - panel1.destroy() - expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) - - describe "::destroy()", -> - it "destroys the container and all of its panels", -> - destroyedPanels = [] - - panel1 = new Panel(item: new TestPanelItem()) - panel1.onDidDestroy -> destroyedPanels.push(panel1) - container.addPanel(panel1) - - panel2 = new Panel(item: new TestPanelItem()) - panel2.onDidDestroy -> destroyedPanels.push(panel2) - container.addPanel(panel2) - - container.destroy() - - expect(container.getPanels().length).toBe(0) - expect(destroyedPanels).toEqual([panel1, panel2]) - - describe "panel priority", -> - describe 'left / top panel container', -> - [initialPanel] = [] - beforeEach -> - # 'left' logic is the same as 'top' - container = new PanelContainer({location: 'left'}) - initialPanel = new Panel(item: new TestPanelItem()) - container.addPanel(initialPanel) - - describe 'when a panel with low priority is added', -> - it 'is inserted at the beginning of the list', -> - container.onDidAddPanel addPanelSpy = jasmine.createSpy() - panel = new Panel(item: new TestPanelItem(), priority: 0) - container.addPanel(panel) - - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - expect(container.getPanels()[0]).toBe panel - - describe 'when a panel with priority between two other panels is added', -> - it 'is inserted at the between the two panels', -> - panel = new Panel(item: new TestPanelItem(), priority: 1000) - container.addPanel(panel) - - container.onDidAddPanel addPanelSpy = jasmine.createSpy() - panel = new Panel(item: new TestPanelItem(), priority: 101) - container.addPanel(panel) - - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}) - expect(container.getPanels()[1]).toBe panel - - describe 'right / bottom panel container', -> - [initialPanel] = [] - beforeEach -> - # 'bottom' logic is the same as 'right' - container = new PanelContainer({location: 'right'}) - initialPanel = new Panel(item: new TestPanelItem()) - container.addPanel(initialPanel) - - describe 'when a panel with high priority is added', -> - it 'is inserted at the beginning of the list', -> - container.onDidAddPanel addPanelSpy = jasmine.createSpy() - panel = new Panel(item: new TestPanelItem(), priority: 1000) - container.addPanel(panel) - - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - expect(container.getPanels()[0]).toBe panel - - describe 'when a panel with low priority is added', -> - it 'is inserted at the end of the list', -> - container.onDidAddPanel addPanelSpy = jasmine.createSpy() - panel = new Panel(item: new TestPanelItem(), priority: 0) - container.addPanel(panel) - - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}) - expect(container.getPanels()[1]).toBe panel diff --git a/spec/panel-container-spec.js b/spec/panel-container-spec.js new file mode 100644 index 000000000..db155b3cb --- /dev/null +++ b/spec/panel-container-spec.js @@ -0,0 +1,139 @@ +const Panel = require('../src/panel'); +const PanelContainer = require('../src/panel-container'); + +describe("PanelContainer", function() { + let [container] = Array.from([]); + + class TestPanelItem { + constructor() {} + } + + beforeEach(() => container = new PanelContainer); + + describe("::addPanel(panel)", () => + it('emits an onDidAddPanel event with the index the panel was inserted at', function() { + let addPanelSpy; + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + + const panel1 = new Panel({item: new TestPanelItem()}); + container.addPanel(panel1); + expect(addPanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}); + + const panel2 = new Panel({item: new TestPanelItem()}); + container.addPanel(panel2); + return expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}); + }) + ); + + describe("when a panel is destroyed", () => + it('emits an onDidRemovePanel event with the index of the removed item', function() { + let removePanelSpy; + container.onDidRemovePanel(removePanelSpy = jasmine.createSpy()); + + const panel1 = new Panel({item: new TestPanelItem()}); + container.addPanel(panel1); + const panel2 = new Panel({item: new TestPanelItem()}); + container.addPanel(panel2); + + expect(removePanelSpy).not.toHaveBeenCalled(); + + panel2.destroy(); + expect(removePanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}); + + panel1.destroy(); + return expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}); + }) + ); + + describe("::destroy()", () => + it("destroys the container and all of its panels", function() { + const destroyedPanels = []; + + const panel1 = new Panel({item: new TestPanelItem()}); + panel1.onDidDestroy(() => destroyedPanels.push(panel1)); + container.addPanel(panel1); + + const panel2 = new Panel({item: new TestPanelItem()}); + panel2.onDidDestroy(() => destroyedPanels.push(panel2)); + container.addPanel(panel2); + + container.destroy(); + + expect(container.getPanels().length).toBe(0); + return expect(destroyedPanels).toEqual([panel1, panel2]); + }) + ); + + return describe("panel priority", function() { + describe('left / top panel container', function() { + let [initialPanel] = Array.from([]); + beforeEach(function() { + // 'left' logic is the same as 'top' + container = new PanelContainer({location: 'left'}); + initialPanel = new Panel({item: new TestPanelItem()}); + return container.addPanel(initialPanel); + }); + + describe('when a panel with low priority is added', () => + it('is inserted at the beginning of the list', function() { + let addPanelSpy; + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + const panel = new Panel({item: new TestPanelItem(), priority: 0}); + container.addPanel(panel); + + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + return expect(container.getPanels()[0]).toBe(panel); + }) + ); + + return describe('when a panel with priority between two other panels is added', () => + it('is inserted at the between the two panels', function() { + let addPanelSpy; + let panel = new Panel({item: new TestPanelItem(), priority: 1000}); + container.addPanel(panel); + + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + panel = new Panel({item: new TestPanelItem(), priority: 101}); + container.addPanel(panel); + + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}); + return expect(container.getPanels()[1]).toBe(panel); + }) + ); + }); + + return describe('right / bottom panel container', function() { + let [initialPanel] = Array.from([]); + beforeEach(function() { + // 'bottom' logic is the same as 'right' + container = new PanelContainer({location: 'right'}); + initialPanel = new Panel({item: new TestPanelItem()}); + return container.addPanel(initialPanel); + }); + + describe('when a panel with high priority is added', () => + it('is inserted at the beginning of the list', function() { + let addPanelSpy; + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + const panel = new Panel({item: new TestPanelItem(), priority: 1000}); + container.addPanel(panel); + + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); + return expect(container.getPanels()[0]).toBe(panel); + }) + ); + + return describe('when a panel with low priority is added', () => + it('is inserted at the end of the list', function() { + let addPanelSpy; + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + const panel = new Panel({item: new TestPanelItem(), priority: 0}); + container.addPanel(panel); + + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}); + return expect(container.getPanels()[1]).toBe(panel); + }) + ); + }); + }); +}); diff --git a/src/panel-container-element.coffee b/src/panel-container-element.coffee deleted file mode 100644 index b66a0cd90..000000000 --- a/src/panel-container-element.coffee +++ /dev/null @@ -1,45 +0,0 @@ -{CompositeDisposable} = require 'event-kit' - -class PanelContainerElement extends HTMLElement - createdCallback: -> - @subscriptions = new CompositeDisposable - - initialize: (@model, {@views}) -> - throw new Error("Must pass a views parameter when initializing PanelContainerElements") unless @views? - - @subscriptions.add @model.onDidAddPanel(@panelAdded.bind(this)) - @subscriptions.add @model.onDidDestroy(@destroyed.bind(this)) - @classList.add(@model.getLocation()) - this - - getModel: -> @model - - panelAdded: ({panel, index}) -> - panelElement = @views.getView(panel) - panelElement.classList.add(@model.getLocation()) - if @model.isModal() - panelElement.classList.add("overlay", "from-top") - else - panelElement.classList.add("tool-panel", "panel-#{@model.getLocation()}") - - if index >= @childNodes.length - @appendChild(panelElement) - else - referenceItem = @childNodes[index] - @insertBefore(panelElement, referenceItem) - - if @model.isModal() - @hideAllPanelsExcept(panel) - @subscriptions.add panel.onDidChangeVisible (visible) => - @hideAllPanelsExcept(panel) if visible - - destroyed: -> - @subscriptions.dispose() - @parentNode?.removeChild(this) - - hideAllPanelsExcept: (excludedPanel) -> - for panel in @model.getPanels() - panel.hide() unless panel is excludedPanel - return - -module.exports = PanelContainerElement = document.registerElement 'atom-panel-container', prototype: PanelContainerElement.prototype diff --git a/src/panel-container-element.js b/src/panel-container-element.js new file mode 100644 index 000000000..a6c133764 --- /dev/null +++ b/src/panel-container-element.js @@ -0,0 +1,59 @@ +const {CompositeDisposable} = require('event-kit'); + +class PanelContainerElement extends HTMLElement { + createdCallback() { + return this.subscriptions = new CompositeDisposable; + } + + initialize(model, {views}) { + this.model = model; + this.views = views; + if (this.views == null) { throw new Error("Must pass a views parameter when initializing PanelContainerElements"); } + + this.subscriptions.add(this.model.onDidAddPanel(this.panelAdded.bind(this))); + this.subscriptions.add(this.model.onDidDestroy(this.destroyed.bind(this))); + this.classList.add(this.model.getLocation()); + return this; + } + + getModel() { return this.model; } + + panelAdded({panel, index}) { + const panelElement = this.views.getView(panel); + panelElement.classList.add(this.model.getLocation()); + if (this.model.isModal()) { + panelElement.classList.add("overlay", "from-top"); + } else { + panelElement.classList.add("tool-panel", `panel-${this.model.getLocation()}`); + } + + if (index >= this.childNodes.length) { + this.appendChild(panelElement); + } else { + const referenceItem = this.childNodes[index]; + this.insertBefore(panelElement, referenceItem); + } + + if (this.model.isModal()) { + this.hideAllPanelsExcept(panel); + return this.subscriptions.add(panel.onDidChangeVisible(visible => { + if (visible) { return this.hideAllPanelsExcept(panel); } + } + ) + ); + } + } + + destroyed() { + this.subscriptions.dispose(); + return (this.parentNode != null ? this.parentNode.removeChild(this) : undefined); + } + + hideAllPanelsExcept(excludedPanel) { + for (let panel of this.model.getPanels()) { + if (panel !== excludedPanel) { panel.hide(); } + } + } +} + +module.exports = PanelContainerElement = document.registerElement('atom-panel-container', {prototype: PanelContainerElement.prototype}); diff --git a/src/panel-container.coffee b/src/panel-container.coffee deleted file mode 100644 index d109210a7..000000000 --- a/src/panel-container.coffee +++ /dev/null @@ -1,71 +0,0 @@ -{Emitter, CompositeDisposable} = require 'event-kit' - -module.exports = -class PanelContainer - constructor: ({@location}={}) -> - @emitter = new Emitter - @subscriptions = new CompositeDisposable - @panels = [] - - destroy: -> - panel.destroy() for panel in @getPanels() - @subscriptions.dispose() - @emitter.emit 'did-destroy', this - @emitter.dispose() - - ### - Section: Event Subscription - ### - - onDidAddPanel: (callback) -> - @emitter.on 'did-add-panel', callback - - onDidRemovePanel: (callback) -> - @emitter.on 'did-remove-panel', callback - - onDidDestroy: (callback) -> - @emitter.on 'did-destroy', callback - - ### - Section: Panels - ### - - getLocation: -> @location - - isModal: -> @location is 'modal' - - getPanels: -> @panels.slice() - - addPanel: (panel) -> - @subscriptions.add panel.onDidDestroy(@panelDestroyed.bind(this)) - - index = @getPanelIndex(panel) - if index is @panels.length - @panels.push(panel) - else - @panels.splice(index, 0, panel) - - @emitter.emit 'did-add-panel', {panel, index} - panel - - panelForItem: (item) -> - for panel in @panels - return panel if panel.getItem() is item - null - - panelDestroyed: (panel) -> - index = @panels.indexOf(panel) - if index > -1 - @panels.splice(index, 1) - @emitter.emit 'did-remove-panel', {panel, index} - - getPanelIndex: (panel) -> - priority = panel.getPriority() - if @location in ['bottom', 'right'] - for p, i in @panels by -1 - return i + 1 if priority < p.getPriority() - 0 - else - for p, i in @panels - return i if priority < p.getPriority() - @panels.length diff --git a/src/panel-container.js b/src/panel-container.js new file mode 100644 index 000000000..bfcf14bbd --- /dev/null +++ b/src/panel-container.js @@ -0,0 +1,92 @@ +let PanelContainer; +const {Emitter, CompositeDisposable} = require('event-kit'); + +module.exports = +PanelContainer = class PanelContainer { + constructor({location}={}) { + this.location = location; + this.emitter = new Emitter; + this.subscriptions = new CompositeDisposable; + this.panels = []; + } + + destroy() { + for (let panel of this.getPanels()) { panel.destroy(); } + this.subscriptions.dispose(); + this.emitter.emit('did-destroy', this); + return this.emitter.dispose(); + } + + /* + Section: Event Subscription + */ + + onDidAddPanel(callback) { + return this.emitter.on('did-add-panel', callback); + } + + onDidRemovePanel(callback) { + return this.emitter.on('did-remove-panel', callback); + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + /* + Section: Panels + */ + + getLocation() { return this.location; } + + isModal() { return this.location === 'modal'; } + + getPanels() { return this.panels.slice(); } + + addPanel(panel) { + this.subscriptions.add(panel.onDidDestroy(this.panelDestroyed.bind(this))); + + const index = this.getPanelIndex(panel); + if (index === this.panels.length) { + this.panels.push(panel); + } else { + this.panels.splice(index, 0, panel); + } + + this.emitter.emit('did-add-panel', {panel, index}); + return panel; + } + + panelForItem(item) { + for (let panel of this.panels) { + if (panel.getItem() === item) { return panel; } + } + return null; + } + + panelDestroyed(panel) { + const index = this.panels.indexOf(panel); + if (index > -1) { + this.panels.splice(index, 1); + return this.emitter.emit('did-remove-panel', {panel, index}); + } + } + + getPanelIndex(panel) { + let i, p; + const priority = panel.getPriority(); + if (['bottom', 'right'].includes(this.location)) { + for (i = this.panels.length - 1; i >= 0; i--) { + p = this.panels[i]; + if (priority < p.getPriority()) { return i + 1; } + } + return 0; + } else { + for (i = 0; i < this.panels.length; i++) { + p = this.panels[i]; + if (priority < p.getPriority()) { return i; } + } + return this.panels.length; + } + } +}; From f56865b6f81cc62246c83afceb11c1f36f8c2ed8 Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Tue, 7 Mar 2017 14:30:33 -0800 Subject: [PATCH 110/194] Convert panel container to JS: Lint --- spec/panel-container-element-spec.js | 226 ++++++++++++++------------- spec/panel-container-spec.js | 193 ++++++++++++----------- src/panel-container-element.js | 60 +++---- src/panel-container.js | 92 ++++++----- 4 files changed, 286 insertions(+), 285 deletions(-) diff --git a/spec/panel-container-element-spec.js b/spec/panel-container-element-spec.js index 167fd92ed..9bc779ba8 100644 --- a/spec/panel-container-element-spec.js +++ b/spec/panel-container-element-spec.js @@ -1,154 +1,156 @@ -const Panel = require('../src/panel'); -const PanelContainer = require('../src/panel-container'); +/* global HTMLElement */ -describe("PanelContainerElement", function() { - let [jasmineContent, element, container] = Array.from([]); +const Panel = require('../src/panel') +const PanelContainer = require('../src/panel-container') + +describe('PanelContainerElement', function () { + let [jasmineContent, element, container] = Array.from([]) class TestPanelContainerItem { - constructior() {} + constructior () {} } - class TestPanelContainerItemElement extends HTMLElement { - createdCallback() { - return this.classList.add('test-root'); + class TestPanelContainerItemElement_ extends HTMLElement { + createdCallback () { + return this.classList.add('test-root') } - initialize(model) { - this.model = model; - return this; + initialize (model) { + this.model = model + return this } } - TestPanelContainerItemElement = document.registerElement('atom-test-container-item-element', {prototype: TestPanelContainerItemElement.prototype}); + const TestPanelContainerItemElement = document.registerElement('atom-test-container-item-element', {prototype: TestPanelContainerItemElement_.prototype}) - beforeEach(function() { - jasmineContent = document.body.querySelector('#jasmine-content'); + beforeEach(function () { + jasmineContent = document.body.querySelector('#jasmine-content') - atom.views.addViewProvider(TestPanelContainerItem, model => new TestPanelContainerItemElement().initialize(model)); + atom.views.addViewProvider(TestPanelContainerItem, model => new TestPanelContainerItemElement().initialize(model)) - container = new PanelContainer({location: 'left'}); - element = atom.views.getView(container); - return jasmineContent.appendChild(element); - }); + container = new PanelContainer({location: 'left'}) + element = atom.views.getView(container) + return jasmineContent.appendChild(element) + }) - it('has a location class with value from the model', () => expect(element).toHaveClass('left')); + it('has a location class with value from the model', () => expect(element).toHaveClass('left')) - it('removes the element when the container is destroyed', function() { - expect(element.parentNode).toBe(jasmineContent); - container.destroy(); - return expect(element.parentNode).not.toBe(jasmineContent); - }); + it('removes the element when the container is destroyed', function () { + expect(element.parentNode).toBe(jasmineContent) + container.destroy() + return expect(element.parentNode).not.toBe(jasmineContent) + }) - describe("adding and removing panels", function() { - it("allows panels to be inserted at any position", function() { - const panel1 = new Panel({item: new TestPanelContainerItem(), priority: 10}); - const panel2 = new Panel({item: new TestPanelContainerItem(), priority: 5}); - const panel3 = new Panel({item: new TestPanelContainerItem(), priority: 8}); + describe('adding and removing panels', function () { + it('allows panels to be inserted at any position', function () { + const panel1 = new Panel({item: new TestPanelContainerItem(), priority: 10}) + const panel2 = new Panel({item: new TestPanelContainerItem(), priority: 5}) + const panel3 = new Panel({item: new TestPanelContainerItem(), priority: 8}) - container.addPanel(panel1); - container.addPanel(panel2); - container.addPanel(panel3); + container.addPanel(panel1) + container.addPanel(panel2) + container.addPanel(panel3) - expect(element.childNodes[2].getModel()).toBe(panel1); - expect(element.childNodes[1].getModel()).toBe(panel3); - return expect(element.childNodes[0].getModel()).toBe(panel2); - }); + expect(element.childNodes[2].getModel()).toBe(panel1) + expect(element.childNodes[1].getModel()).toBe(panel3) + return expect(element.childNodes[0].getModel()).toBe(panel2) + }) - describe("when the container is at the left location", () => - it("adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed", function() { - expect(element.childNodes.length).toBe(0); + describe('when the container is at the left location', () => + it('adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed', function () { + expect(element.childNodes.length).toBe(0) - const panel1 = new Panel({item: new TestPanelContainerItem()}); - container.addPanel(panel1); - expect(element.childNodes.length).toBe(1); - expect(element.childNodes[0]).toHaveClass('left'); - expect(element.childNodes[0]).toHaveClass('tool-panel'); // legacy selector support - expect(element.childNodes[0]).toHaveClass('panel-left'); // legacy selector support + const panel1 = new Panel({item: new TestPanelContainerItem()}) + container.addPanel(panel1) + expect(element.childNodes.length).toBe(1) + expect(element.childNodes[0]).toHaveClass('left') + expect(element.childNodes[0]).toHaveClass('tool-panel') // legacy selector support + expect(element.childNodes[0]).toHaveClass('panel-left') // legacy selector support - expect(element.childNodes[0].tagName).toBe('ATOM-PANEL'); + expect(element.childNodes[0].tagName).toBe('ATOM-PANEL') - const panel2 = new Panel({item: new TestPanelContainerItem()}); - container.addPanel(panel2); - expect(element.childNodes.length).toBe(2); + const panel2 = new Panel({item: new TestPanelContainerItem()}) + container.addPanel(panel2) + expect(element.childNodes.length).toBe(2) - expect(atom.views.getView(panel1).style.display).not.toBe('none'); - expect(atom.views.getView(panel2).style.display).not.toBe('none'); + expect(atom.views.getView(panel1).style.display).not.toBe('none') + expect(atom.views.getView(panel2).style.display).not.toBe('none') - panel1.destroy(); - expect(element.childNodes.length).toBe(1); + panel1.destroy() + expect(element.childNodes.length).toBe(1) - panel2.destroy(); - return expect(element.childNodes.length).toBe(0); + panel2.destroy() + return expect(element.childNodes.length).toBe(0) }) - ); + ) - return describe("when the container is at the bottom location", function() { - beforeEach(function() { - container = new PanelContainer({location: 'bottom'}); - element = atom.views.getView(container); - return jasmineContent.appendChild(element); - }); + return describe('when the container is at the bottom location', function () { + beforeEach(function () { + container = new PanelContainer({location: 'bottom'}) + element = atom.views.getView(container) + return jasmineContent.appendChild(element) + }) - return it("adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed", function() { - expect(element.childNodes.length).toBe(0); + return it('adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed', function () { + expect(element.childNodes.length).toBe(0) - const panel1 = new Panel({item: new TestPanelContainerItem(), className: 'one'}); - container.addPanel(panel1); - expect(element.childNodes.length).toBe(1); - expect(element.childNodes[0]).toHaveClass('bottom'); - expect(element.childNodes[0]).toHaveClass('tool-panel'); // legacy selector support - expect(element.childNodes[0]).toHaveClass('panel-bottom'); // legacy selector support - expect(element.childNodes[0].tagName).toBe('ATOM-PANEL'); - expect(atom.views.getView(panel1)).toHaveClass('one'); + const panel1 = new Panel({item: new TestPanelContainerItem(), className: 'one'}) + container.addPanel(panel1) + expect(element.childNodes.length).toBe(1) + expect(element.childNodes[0]).toHaveClass('bottom') + expect(element.childNodes[0]).toHaveClass('tool-panel') // legacy selector support + expect(element.childNodes[0]).toHaveClass('panel-bottom') // legacy selector support + expect(element.childNodes[0].tagName).toBe('ATOM-PANEL') + expect(atom.views.getView(panel1)).toHaveClass('one') - const panel2 = new Panel({item: new TestPanelContainerItem(), className: 'two'}); - container.addPanel(panel2); - expect(element.childNodes.length).toBe(2); - expect(atom.views.getView(panel2)).toHaveClass('two'); + const panel2 = new Panel({item: new TestPanelContainerItem(), className: 'two'}) + container.addPanel(panel2) + expect(element.childNodes.length).toBe(2) + expect(atom.views.getView(panel2)).toHaveClass('two') - panel1.destroy(); - expect(element.childNodes.length).toBe(1); + panel1.destroy() + expect(element.childNodes.length).toBe(1) - panel2.destroy(); - return expect(element.childNodes.length).toBe(0); - }); - }); - }); + panel2.destroy() + return expect(element.childNodes.length).toBe(0) + }) + }) + }) - return describe("when the container is modal", function() { - beforeEach(function() { - container = new PanelContainer({location: 'modal'}); - element = atom.views.getView(container); - return jasmineContent.appendChild(element); - }); + return describe('when the container is modal', function () { + beforeEach(function () { + container = new PanelContainer({location: 'modal'}) + element = atom.views.getView(container) + return jasmineContent.appendChild(element) + }) - it("allows only one panel to be visible at a time", function() { - const panel1 = new Panel({item: new TestPanelContainerItem()}); - container.addPanel(panel1); + it('allows only one panel to be visible at a time', function () { + const panel1 = new Panel({item: new TestPanelContainerItem()}) + container.addPanel(panel1) - expect(atom.views.getView(panel1).style.display).not.toBe('none'); + expect(atom.views.getView(panel1).style.display).not.toBe('none') - const panel2 = new Panel({item: new TestPanelContainerItem()}); - container.addPanel(panel2); + const panel2 = new Panel({item: new TestPanelContainerItem()}) + container.addPanel(panel2) - expect(atom.views.getView(panel1).style.display).toBe('none'); - expect(atom.views.getView(panel2).style.display).not.toBe('none'); + expect(atom.views.getView(panel1).style.display).toBe('none') + expect(atom.views.getView(panel2).style.display).not.toBe('none') - panel1.show(); + panel1.show() - expect(atom.views.getView(panel1).style.display).not.toBe('none'); - return expect(atom.views.getView(panel2).style.display).toBe('none'); - }); + expect(atom.views.getView(panel1).style.display).not.toBe('none') + return expect(atom.views.getView(panel2).style.display).toBe('none') + }) - return it("adds the 'modal' class to panels", function() { - const panel1 = new Panel({item: new TestPanelContainerItem()}); - container.addPanel(panel1); + return it("adds the 'modal' class to panels", function () { + const panel1 = new Panel({item: new TestPanelContainerItem()}) + container.addPanel(panel1) - expect(atom.views.getView(panel1)).toHaveClass('modal'); + expect(atom.views.getView(panel1)).toHaveClass('modal') // legacy selector support - expect(atom.views.getView(panel1)).not.toHaveClass('tool-panel'); - expect(atom.views.getView(panel1)).toHaveClass('overlay'); - return expect(atom.views.getView(panel1)).toHaveClass('from-top'); - }); - }); -}); + expect(atom.views.getView(panel1)).not.toHaveClass('tool-panel') + expect(atom.views.getView(panel1)).toHaveClass('overlay') + return expect(atom.views.getView(panel1)).toHaveClass('from-top') + }) + }) +}) diff --git a/spec/panel-container-spec.js b/spec/panel-container-spec.js index db155b3cb..4f22b7552 100644 --- a/spec/panel-container-spec.js +++ b/spec/panel-container-spec.js @@ -1,139 +1,138 @@ -const Panel = require('../src/panel'); -const PanelContainer = require('../src/panel-container'); +const Panel = require('../src/panel') +const PanelContainer = require('../src/panel-container') -describe("PanelContainer", function() { - let [container] = Array.from([]); +describe('PanelContainer', function () { + let [container] = Array.from([]) class TestPanelItem { - constructor() {} } - beforeEach(() => container = new PanelContainer); + beforeEach(() => (container = new PanelContainer())) - describe("::addPanel(panel)", () => - it('emits an onDidAddPanel event with the index the panel was inserted at', function() { - let addPanelSpy; - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); + describe('::addPanel(panel)', () => + it('emits an onDidAddPanel event with the index the panel was inserted at', function () { + let addPanelSpy + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) - const panel1 = new Panel({item: new TestPanelItem()}); - container.addPanel(panel1); - expect(addPanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}); + const panel1 = new Panel({item: new TestPanelItem()}) + container.addPanel(panel1) + expect(addPanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) - const panel2 = new Panel({item: new TestPanelItem()}); - container.addPanel(panel2); - return expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}); + const panel2 = new Panel({item: new TestPanelItem()}) + container.addPanel(panel2) + return expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) }) - ); + ) - describe("when a panel is destroyed", () => - it('emits an onDidRemovePanel event with the index of the removed item', function() { - let removePanelSpy; - container.onDidRemovePanel(removePanelSpy = jasmine.createSpy()); + describe('when a panel is destroyed', () => + it('emits an onDidRemovePanel event with the index of the removed item', function () { + let removePanelSpy + container.onDidRemovePanel(removePanelSpy = jasmine.createSpy()) - const panel1 = new Panel({item: new TestPanelItem()}); - container.addPanel(panel1); - const panel2 = new Panel({item: new TestPanelItem()}); - container.addPanel(panel2); + const panel1 = new Panel({item: new TestPanelItem()}) + container.addPanel(panel1) + const panel2 = new Panel({item: new TestPanelItem()}) + container.addPanel(panel2) - expect(removePanelSpy).not.toHaveBeenCalled(); + expect(removePanelSpy).not.toHaveBeenCalled() - panel2.destroy(); - expect(removePanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}); + panel2.destroy() + expect(removePanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) - panel1.destroy(); - return expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}); + panel1.destroy() + return expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) }) - ); + ) - describe("::destroy()", () => - it("destroys the container and all of its panels", function() { - const destroyedPanels = []; + describe('::destroy()', () => + it('destroys the container and all of its panels', function () { + const destroyedPanels = [] - const panel1 = new Panel({item: new TestPanelItem()}); - panel1.onDidDestroy(() => destroyedPanels.push(panel1)); - container.addPanel(panel1); + const panel1 = new Panel({item: new TestPanelItem()}) + panel1.onDidDestroy(() => destroyedPanels.push(panel1)) + container.addPanel(panel1) - const panel2 = new Panel({item: new TestPanelItem()}); - panel2.onDidDestroy(() => destroyedPanels.push(panel2)); - container.addPanel(panel2); + const panel2 = new Panel({item: new TestPanelItem()}) + panel2.onDidDestroy(() => destroyedPanels.push(panel2)) + container.addPanel(panel2) - container.destroy(); + container.destroy() - expect(container.getPanels().length).toBe(0); - return expect(destroyedPanels).toEqual([panel1, panel2]); + expect(container.getPanels().length).toBe(0) + return expect(destroyedPanels).toEqual([panel1, panel2]) }) - ); + ) - return describe("panel priority", function() { - describe('left / top panel container', function() { - let [initialPanel] = Array.from([]); - beforeEach(function() { + return describe('panel priority', function () { + describe('left / top panel container', function () { + let [initialPanel] = Array.from([]) + beforeEach(function () { // 'left' logic is the same as 'top' - container = new PanelContainer({location: 'left'}); - initialPanel = new Panel({item: new TestPanelItem()}); - return container.addPanel(initialPanel); - }); + container = new PanelContainer({location: 'left'}) + initialPanel = new Panel({item: new TestPanelItem()}) + return container.addPanel(initialPanel) + }) describe('when a panel with low priority is added', () => - it('is inserted at the beginning of the list', function() { - let addPanelSpy; - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); - const panel = new Panel({item: new TestPanelItem(), priority: 0}); - container.addPanel(panel); + it('is inserted at the beginning of the list', function () { + let addPanelSpy + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + const panel = new Panel({item: new TestPanelItem(), priority: 0}) + container.addPanel(panel) - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); - return expect(container.getPanels()[0]).toBe(panel); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) + return expect(container.getPanels()[0]).toBe(panel) }) - ); + ) return describe('when a panel with priority between two other panels is added', () => - it('is inserted at the between the two panels', function() { - let addPanelSpy; - let panel = new Panel({item: new TestPanelItem(), priority: 1000}); - container.addPanel(panel); + it('is inserted at the between the two panels', function () { + let addPanelSpy + let panel = new Panel({item: new TestPanelItem(), priority: 1000}) + container.addPanel(panel) - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); - panel = new Panel({item: new TestPanelItem(), priority: 101}); - container.addPanel(panel); + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + panel = new Panel({item: new TestPanelItem(), priority: 101}) + container.addPanel(panel) - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}); - return expect(container.getPanels()[1]).toBe(panel); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}) + return expect(container.getPanels()[1]).toBe(panel) }) - ); - }); + ) + }) - return describe('right / bottom panel container', function() { - let [initialPanel] = Array.from([]); - beforeEach(function() { + return describe('right / bottom panel container', function () { + let [initialPanel] = Array.from([]) + beforeEach(function () { // 'bottom' logic is the same as 'right' - container = new PanelContainer({location: 'right'}); - initialPanel = new Panel({item: new TestPanelItem()}); - return container.addPanel(initialPanel); - }); + container = new PanelContainer({location: 'right'}) + initialPanel = new Panel({item: new TestPanelItem()}) + return container.addPanel(initialPanel) + }) describe('when a panel with high priority is added', () => - it('is inserted at the beginning of the list', function() { - let addPanelSpy; - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); - const panel = new Panel({item: new TestPanelItem(), priority: 1000}); - container.addPanel(panel); + it('is inserted at the beginning of the list', function () { + let addPanelSpy + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + const panel = new Panel({item: new TestPanelItem(), priority: 1000}) + container.addPanel(panel) - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}); - return expect(container.getPanels()[0]).toBe(panel); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) + return expect(container.getPanels()[0]).toBe(panel) }) - ); + ) return describe('when a panel with low priority is added', () => - it('is inserted at the end of the list', function() { - let addPanelSpy; - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()); - const panel = new Panel({item: new TestPanelItem(), priority: 0}); - container.addPanel(panel); + it('is inserted at the end of the list', function () { + let addPanelSpy + container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + const panel = new Panel({item: new TestPanelItem(), priority: 0}) + container.addPanel(panel) - expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}); - return expect(container.getPanels()[1]).toBe(panel); + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}) + return expect(container.getPanels()[1]).toBe(panel) }) - ); - }); - }); -}); + ) + }) + }) +}) diff --git a/src/panel-container-element.js b/src/panel-container-element.js index a6c133764..1609b2bef 100644 --- a/src/panel-container-element.js +++ b/src/panel-container-element.js @@ -1,59 +1,61 @@ -const {CompositeDisposable} = require('event-kit'); +/* global HTMLElement */ + +const {CompositeDisposable} = require('event-kit') class PanelContainerElement extends HTMLElement { - createdCallback() { - return this.subscriptions = new CompositeDisposable; + createdCallback () { + return (this.subscriptions = new CompositeDisposable()) } - initialize(model, {views}) { - this.model = model; - this.views = views; - if (this.views == null) { throw new Error("Must pass a views parameter when initializing PanelContainerElements"); } + initialize (model, {views}) { + this.model = model + this.views = views + if (this.views == null) { throw new Error('Must pass a views parameter when initializing PanelContainerElements') } - this.subscriptions.add(this.model.onDidAddPanel(this.panelAdded.bind(this))); - this.subscriptions.add(this.model.onDidDestroy(this.destroyed.bind(this))); - this.classList.add(this.model.getLocation()); - return this; + this.subscriptions.add(this.model.onDidAddPanel(this.panelAdded.bind(this))) + this.subscriptions.add(this.model.onDidDestroy(this.destroyed.bind(this))) + this.classList.add(this.model.getLocation()) + return this } - getModel() { return this.model; } + getModel () { return this.model } - panelAdded({panel, index}) { - const panelElement = this.views.getView(panel); - panelElement.classList.add(this.model.getLocation()); + panelAdded ({panel, index}) { + const panelElement = this.views.getView(panel) + panelElement.classList.add(this.model.getLocation()) if (this.model.isModal()) { - panelElement.classList.add("overlay", "from-top"); + panelElement.classList.add('overlay', 'from-top') } else { - panelElement.classList.add("tool-panel", `panel-${this.model.getLocation()}`); + panelElement.classList.add('tool-panel', `panel-${this.model.getLocation()}`) } if (index >= this.childNodes.length) { - this.appendChild(panelElement); + this.appendChild(panelElement) } else { - const referenceItem = this.childNodes[index]; - this.insertBefore(panelElement, referenceItem); + const referenceItem = this.childNodes[index] + this.insertBefore(panelElement, referenceItem) } if (this.model.isModal()) { - this.hideAllPanelsExcept(panel); + this.hideAllPanelsExcept(panel) return this.subscriptions.add(panel.onDidChangeVisible(visible => { - if (visible) { return this.hideAllPanelsExcept(panel); } + if (visible) { return this.hideAllPanelsExcept(panel) } } ) - ); + ) } } - destroyed() { - this.subscriptions.dispose(); - return (this.parentNode != null ? this.parentNode.removeChild(this) : undefined); + destroyed () { + this.subscriptions.dispose() + return (this.parentNode != null ? this.parentNode.removeChild(this) : undefined) } - hideAllPanelsExcept(excludedPanel) { + hideAllPanelsExcept (excludedPanel) { for (let panel of this.model.getPanels()) { - if (panel !== excludedPanel) { panel.hide(); } + if (panel !== excludedPanel) { panel.hide() } } } } -module.exports = PanelContainerElement = document.registerElement('atom-panel-container', {prototype: PanelContainerElement.prototype}); +module.exports = document.registerElement('atom-panel-container', {prototype: PanelContainerElement.prototype}) diff --git a/src/panel-container.js b/src/panel-container.js index bfcf14bbd..cc473b189 100644 --- a/src/panel-container.js +++ b/src/panel-container.js @@ -1,92 +1,90 @@ -let PanelContainer; -const {Emitter, CompositeDisposable} = require('event-kit'); +const {Emitter, CompositeDisposable} = require('event-kit') -module.exports = -PanelContainer = class PanelContainer { - constructor({location}={}) { - this.location = location; - this.emitter = new Emitter; - this.subscriptions = new CompositeDisposable; - this.panels = []; +module.exports = class PanelContainer { + constructor ({location} = {}) { + this.location = location + this.emitter = new Emitter() + this.subscriptions = new CompositeDisposable() + this.panels = [] } - destroy() { - for (let panel of this.getPanels()) { panel.destroy(); } - this.subscriptions.dispose(); - this.emitter.emit('did-destroy', this); - return this.emitter.dispose(); + destroy () { + for (let panel of this.getPanels()) { panel.destroy() } + this.subscriptions.dispose() + this.emitter.emit('did-destroy', this) + return this.emitter.dispose() } /* Section: Event Subscription */ - onDidAddPanel(callback) { - return this.emitter.on('did-add-panel', callback); + onDidAddPanel (callback) { + return this.emitter.on('did-add-panel', callback) } - onDidRemovePanel(callback) { - return this.emitter.on('did-remove-panel', callback); + onDidRemovePanel (callback) { + return this.emitter.on('did-remove-panel', callback) } - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); + onDidDestroy (callback) { + return this.emitter.on('did-destroy', callback) } /* Section: Panels */ - getLocation() { return this.location; } + getLocation () { return this.location } - isModal() { return this.location === 'modal'; } + isModal () { return this.location === 'modal' } - getPanels() { return this.panels.slice(); } + getPanels () { return this.panels.slice() } - addPanel(panel) { - this.subscriptions.add(panel.onDidDestroy(this.panelDestroyed.bind(this))); + addPanel (panel) { + this.subscriptions.add(panel.onDidDestroy(this.panelDestroyed.bind(this))) - const index = this.getPanelIndex(panel); + const index = this.getPanelIndex(panel) if (index === this.panels.length) { - this.panels.push(panel); + this.panels.push(panel) } else { - this.panels.splice(index, 0, panel); + this.panels.splice(index, 0, panel) } - this.emitter.emit('did-add-panel', {panel, index}); - return panel; + this.emitter.emit('did-add-panel', {panel, index}) + return panel } - panelForItem(item) { + panelForItem (item) { for (let panel of this.panels) { - if (panel.getItem() === item) { return panel; } + if (panel.getItem() === item) { return panel } } - return null; + return null } - panelDestroyed(panel) { - const index = this.panels.indexOf(panel); + panelDestroyed (panel) { + const index = this.panels.indexOf(panel) if (index > -1) { - this.panels.splice(index, 1); - return this.emitter.emit('did-remove-panel', {panel, index}); + this.panels.splice(index, 1) + return this.emitter.emit('did-remove-panel', {panel, index}) } } - getPanelIndex(panel) { - let i, p; - const priority = panel.getPriority(); + getPanelIndex (panel) { + let i, p + const priority = panel.getPriority() if (['bottom', 'right'].includes(this.location)) { for (i = this.panels.length - 1; i >= 0; i--) { - p = this.panels[i]; - if (priority < p.getPriority()) { return i + 1; } + p = this.panels[i] + if (priority < p.getPriority()) { return i + 1 } } - return 0; + return 0 } else { for (i = 0; i < this.panels.length; i++) { - p = this.panels[i]; - if (priority < p.getPriority()) { return i; } + p = this.panels[i] + if (priority < p.getPriority()) { return i } } - return this.panels.length; + return this.panels.length } } -}; +} From d6a354d6ba807822583f6e07c36d26c481266c8d Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Tue, 7 Mar 2017 14:52:32 -0800 Subject: [PATCH 111/194] Convert panel container to JS: Cleanup --- spec/panel-container-element-spec.js | 65 +++++++++------- spec/panel-container-spec.js | 112 ++++++++++++++------------- src/panel-container-element.js | 20 +++-- src/panel-container.js | 15 ++-- 4 files changed, 115 insertions(+), 97 deletions(-) diff --git a/spec/panel-container-element-spec.js b/spec/panel-container-element-spec.js index 9bc779ba8..23da47663 100644 --- a/spec/panel-container-element-spec.js +++ b/spec/panel-container-element-spec.js @@ -1,18 +1,19 @@ +'use strict' + /* global HTMLElement */ const Panel = require('../src/panel') const PanelContainer = require('../src/panel-container') -describe('PanelContainerElement', function () { - let [jasmineContent, element, container] = Array.from([]) +describe('PanelContainerElement', () => { + let jasmineContent, element, container class TestPanelContainerItem { - constructior () {} } class TestPanelContainerItemElement_ extends HTMLElement { createdCallback () { - return this.classList.add('test-root') + this.classList.add('test-root') } initialize (model) { this.model = model @@ -20,28 +21,36 @@ describe('PanelContainerElement', function () { } } - const TestPanelContainerItemElement = document.registerElement('atom-test-container-item-element', {prototype: TestPanelContainerItemElement_.prototype}) + const TestPanelContainerItemElement = document.registerElement( + 'atom-test-container-item-element', + {prototype: TestPanelContainerItemElement_.prototype} + ) - beforeEach(function () { + beforeEach(() => { jasmineContent = document.body.querySelector('#jasmine-content') - atom.views.addViewProvider(TestPanelContainerItem, model => new TestPanelContainerItemElement().initialize(model)) + atom.views.addViewProvider( + TestPanelContainerItem, + model => new TestPanelContainerItemElement().initialize(model) + ) container = new PanelContainer({location: 'left'}) element = atom.views.getView(container) - return jasmineContent.appendChild(element) + jasmineContent.appendChild(element) }) - it('has a location class with value from the model', () => expect(element).toHaveClass('left')) + it('has a location class with value from the model', () => { + expect(element).toHaveClass('left') + }) - it('removes the element when the container is destroyed', function () { + it('removes the element when the container is destroyed', () => { expect(element.parentNode).toBe(jasmineContent) container.destroy() - return expect(element.parentNode).not.toBe(jasmineContent) + expect(element.parentNode).not.toBe(jasmineContent) }) - describe('adding and removing panels', function () { - it('allows panels to be inserted at any position', function () { + describe('adding and removing panels', () => { + it('allows panels to be inserted at any position', () => { const panel1 = new Panel({item: new TestPanelContainerItem(), priority: 10}) const panel2 = new Panel({item: new TestPanelContainerItem(), priority: 5}) const panel3 = new Panel({item: new TestPanelContainerItem(), priority: 8}) @@ -52,11 +61,11 @@ describe('PanelContainerElement', function () { expect(element.childNodes[2].getModel()).toBe(panel1) expect(element.childNodes[1].getModel()).toBe(panel3) - return expect(element.childNodes[0].getModel()).toBe(panel2) + expect(element.childNodes[0].getModel()).toBe(panel2) }) describe('when the container is at the left location', () => - it('adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed', function () { + it('adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed', () => { expect(element.childNodes.length).toBe(0) const panel1 = new Panel({item: new TestPanelContainerItem()}) @@ -79,18 +88,18 @@ describe('PanelContainerElement', function () { expect(element.childNodes.length).toBe(1) panel2.destroy() - return expect(element.childNodes.length).toBe(0) + expect(element.childNodes.length).toBe(0) }) ) - return describe('when the container is at the bottom location', function () { - beforeEach(function () { + describe('when the container is at the bottom location', () => { + beforeEach(() => { container = new PanelContainer({location: 'bottom'}) element = atom.views.getView(container) - return jasmineContent.appendChild(element) + jasmineContent.appendChild(element) }) - return it('adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed', function () { + it('adds atom-panel elements when a new panel is added to the container; removes them when the panels are destroyed', () => { expect(element.childNodes.length).toBe(0) const panel1 = new Panel({item: new TestPanelContainerItem(), className: 'one'}) @@ -111,19 +120,19 @@ describe('PanelContainerElement', function () { expect(element.childNodes.length).toBe(1) panel2.destroy() - return expect(element.childNodes.length).toBe(0) + expect(element.childNodes.length).toBe(0) }) }) }) - return describe('when the container is modal', function () { - beforeEach(function () { + describe('when the container is modal', () => { + beforeEach(() => { container = new PanelContainer({location: 'modal'}) element = atom.views.getView(container) - return jasmineContent.appendChild(element) + jasmineContent.appendChild(element) }) - it('allows only one panel to be visible at a time', function () { + it('allows only one panel to be visible at a time', () => { const panel1 = new Panel({item: new TestPanelContainerItem()}) container.addPanel(panel1) @@ -138,10 +147,10 @@ describe('PanelContainerElement', function () { panel1.show() expect(atom.views.getView(panel1).style.display).not.toBe('none') - return expect(atom.views.getView(panel2).style.display).toBe('none') + expect(atom.views.getView(panel2).style.display).toBe('none') }) - return it("adds the 'modal' class to panels", function () { + it("adds the 'modal' class to panels", () => { const panel1 = new Panel({item: new TestPanelContainerItem()}) container.addPanel(panel1) @@ -150,7 +159,7 @@ describe('PanelContainerElement', function () { // legacy selector support expect(atom.views.getView(panel1)).not.toHaveClass('tool-panel') expect(atom.views.getView(panel1)).toHaveClass('overlay') - return expect(atom.views.getView(panel1)).toHaveClass('from-top') + expect(atom.views.getView(panel1)).toHaveClass('from-top') }) }) }) diff --git a/spec/panel-container-spec.js b/spec/panel-container-spec.js index 4f22b7552..628904256 100644 --- a/spec/panel-container-spec.js +++ b/spec/panel-container-spec.js @@ -1,18 +1,22 @@ +'use strict' + const Panel = require('../src/panel') const PanelContainer = require('../src/panel-container') -describe('PanelContainer', function () { - let [container] = Array.from([]) +describe('PanelContainer', () => { + let container class TestPanelItem { } - beforeEach(() => (container = new PanelContainer())) + beforeEach(() => { + container = new PanelContainer() + }) - describe('::addPanel(panel)', () => - it('emits an onDidAddPanel event with the index the panel was inserted at', function () { - let addPanelSpy - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + describe('::addPanel(panel)', () => { + it('emits an onDidAddPanel event with the index the panel was inserted at', () => { + const addPanelSpy = jasmine.createSpy() + container.onDidAddPanel(addPanelSpy) const panel1 = new Panel({item: new TestPanelItem()}) container.addPanel(panel1) @@ -20,14 +24,14 @@ describe('PanelContainer', function () { const panel2 = new Panel({item: new TestPanelItem()}) container.addPanel(panel2) - return expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) + expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) }) - ) + }) - describe('when a panel is destroyed', () => - it('emits an onDidRemovePanel event with the index of the removed item', function () { - let removePanelSpy - container.onDidRemovePanel(removePanelSpy = jasmine.createSpy()) + describe('when a panel is destroyed', () => { + it('emits an onDidRemovePanel event with the index of the removed item', () => { + const removePanelSpy = jasmine.createSpy() + container.onDidRemovePanel(removePanelSpy) const panel1 = new Panel({item: new TestPanelItem()}) container.addPanel(panel1) @@ -40,99 +44,99 @@ describe('PanelContainer', function () { expect(removePanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1}) panel1.destroy() - return expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) + expect(removePanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0}) }) - ) + }) - describe('::destroy()', () => - it('destroys the container and all of its panels', function () { + describe('::destroy()', () => { + it('destroys the container and all of its panels', () => { const destroyedPanels = [] const panel1 = new Panel({item: new TestPanelItem()}) - panel1.onDidDestroy(() => destroyedPanels.push(panel1)) + panel1.onDidDestroy(() => { destroyedPanels.push(panel1) }) container.addPanel(panel1) const panel2 = new Panel({item: new TestPanelItem()}) - panel2.onDidDestroy(() => destroyedPanels.push(panel2)) + panel2.onDidDestroy(() => { destroyedPanels.push(panel2) }) container.addPanel(panel2) container.destroy() expect(container.getPanels().length).toBe(0) - return expect(destroyedPanels).toEqual([panel1, panel2]) + expect(destroyedPanels).toEqual([panel1, panel2]) }) - ) + }) - return describe('panel priority', function () { - describe('left / top panel container', function () { - let [initialPanel] = Array.from([]) - beforeEach(function () { + describe('panel priority', () => { + describe('left / top panel container', () => { + let initialPanel + beforeEach(() => { // 'left' logic is the same as 'top' container = new PanelContainer({location: 'left'}) initialPanel = new Panel({item: new TestPanelItem()}) - return container.addPanel(initialPanel) + container.addPanel(initialPanel) }) - describe('when a panel with low priority is added', () => - it('is inserted at the beginning of the list', function () { - let addPanelSpy - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + describe('when a panel with low priority is added', () => { + it('is inserted at the beginning of the list', () => { + const addPanelSpy = jasmine.createSpy() + container.onDidAddPanel(addPanelSpy) const panel = new Panel({item: new TestPanelItem(), priority: 0}) container.addPanel(panel) expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - return expect(container.getPanels()[0]).toBe(panel) + expect(container.getPanels()[0]).toBe(panel) }) - ) + }) - return describe('when a panel with priority between two other panels is added', () => - it('is inserted at the between the two panels', function () { - let addPanelSpy + describe('when a panel with priority between two other panels is added', () => { + it('is inserted at the between the two panels', () => { + const addPanelSpy = jasmine.createSpy() let panel = new Panel({item: new TestPanelItem(), priority: 1000}) container.addPanel(panel) - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + container.onDidAddPanel(addPanelSpy) panel = new Panel({item: new TestPanelItem(), priority: 101}) container.addPanel(panel) expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}) - return expect(container.getPanels()[1]).toBe(panel) + expect(container.getPanels()[1]).toBe(panel) }) - ) + }) }) - return describe('right / bottom panel container', function () { - let [initialPanel] = Array.from([]) - beforeEach(function () { + describe('right / bottom panel container', () => { + let initialPanel + beforeEach(() => { // 'bottom' logic is the same as 'right' container = new PanelContainer({location: 'right'}) initialPanel = new Panel({item: new TestPanelItem()}) - return container.addPanel(initialPanel) + container.addPanel(initialPanel) }) - describe('when a panel with high priority is added', () => - it('is inserted at the beginning of the list', function () { - let addPanelSpy - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + describe('when a panel with high priority is added', () => { + it('is inserted at the beginning of the list', () => { + const addPanelSpy = jasmine.createSpy() + container.onDidAddPanel(addPanelSpy) const panel = new Panel({item: new TestPanelItem(), priority: 1000}) container.addPanel(panel) expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) - return expect(container.getPanels()[0]).toBe(panel) + expect(container.getPanels()[0]).toBe(panel) }) - ) + }) - return describe('when a panel with low priority is added', () => - it('is inserted at the end of the list', function () { - let addPanelSpy - container.onDidAddPanel(addPanelSpy = jasmine.createSpy()) + describe('when a panel with low priority is added', () => { + it('is inserted at the end of the list', () => { + const addPanelSpy = jasmine.createSpy() + container.onDidAddPanel(addPanelSpy) const panel = new Panel({item: new TestPanelItem(), priority: 0}) container.addPanel(panel) expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 1}) - return expect(container.getPanels()[1]).toBe(panel) + expect(container.getPanels()[1]).toBe(panel) }) - ) + }) }) }) }) diff --git a/src/panel-container-element.js b/src/panel-container-element.js index 1609b2bef..dbc595186 100644 --- a/src/panel-container-element.js +++ b/src/panel-container-element.js @@ -1,16 +1,20 @@ +'use strict' + /* global HTMLElement */ const {CompositeDisposable} = require('event-kit') class PanelContainerElement extends HTMLElement { createdCallback () { - return (this.subscriptions = new CompositeDisposable()) + this.subscriptions = new CompositeDisposable() } initialize (model, {views}) { this.model = model this.views = views - if (this.views == null) { throw new Error('Must pass a views parameter when initializing PanelContainerElements') } + if (this.views == null) { + throw new Error('Must pass a views parameter when initializing PanelContainerElements') + } this.subscriptions.add(this.model.onDidAddPanel(this.panelAdded.bind(this))) this.subscriptions.add(this.model.onDidDestroy(this.destroyed.bind(this))) @@ -38,17 +42,17 @@ class PanelContainerElement extends HTMLElement { if (this.model.isModal()) { this.hideAllPanelsExcept(panel) - return this.subscriptions.add(panel.onDidChangeVisible(visible => { - if (visible) { return this.hideAllPanelsExcept(panel) } - } - ) - ) + this.subscriptions.add(panel.onDidChangeVisible(visible => { + if (visible) { this.hideAllPanelsExcept(panel) } + })) } } destroyed () { this.subscriptions.dispose() - return (this.parentNode != null ? this.parentNode.removeChild(this) : undefined) + if (this.parentNode != null) { + this.parentNode.removeChild(this) + } } hideAllPanelsExcept (excludedPanel) { diff --git a/src/panel-container.js b/src/panel-container.js index cc473b189..377b4cd97 100644 --- a/src/panel-container.js +++ b/src/panel-container.js @@ -1,3 +1,5 @@ +'use strict' + const {Emitter, CompositeDisposable} = require('event-kit') module.exports = class PanelContainer { @@ -12,7 +14,7 @@ module.exports = class PanelContainer { for (let panel of this.getPanels()) { panel.destroy() } this.subscriptions.dispose() this.emitter.emit('did-destroy', this) - return this.emitter.dispose() + this.emitter.dispose() } /* @@ -66,22 +68,21 @@ module.exports = class PanelContainer { const index = this.panels.indexOf(panel) if (index > -1) { this.panels.splice(index, 1) - return this.emitter.emit('did-remove-panel', {panel, index}) + this.emitter.emit('did-remove-panel', {panel, index}) } } getPanelIndex (panel) { - let i, p const priority = panel.getPriority() if (['bottom', 'right'].includes(this.location)) { - for (i = this.panels.length - 1; i >= 0; i--) { - p = this.panels[i] + for (let i = this.panels.length - 1; i >= 0; i--) { + const p = this.panels[i] if (priority < p.getPriority()) { return i + 1 } } return 0 } else { - for (i = 0; i < this.panels.length; i++) { - p = this.panels[i] + for (let i = 0; i < this.panels.length; i++) { + const p = this.panels[i] if (priority < p.getPriority()) { return i } } return this.panels.length From ede4a2972a859f7508cb98ce77e08bd48cf22639 Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Tue, 7 Mar 2017 15:49:22 -0800 Subject: [PATCH 112/194] Convert workspace element to JS: Decaffeinate --- spec/workspace-element-spec.coffee | 208 -------------------------- spec/workspace-element-spec.js | 228 +++++++++++++++++++++++++++++ src/workspace-element.coffee | 149 ------------------- src/workspace-element.js | 196 +++++++++++++++++++++++++ 4 files changed, 424 insertions(+), 357 deletions(-) delete mode 100644 spec/workspace-element-spec.coffee create mode 100644 spec/workspace-element-spec.js delete mode 100644 src/workspace-element.coffee create mode 100644 src/workspace-element.js diff --git a/spec/workspace-element-spec.coffee b/spec/workspace-element-spec.coffee deleted file mode 100644 index ec24242ac..000000000 --- a/spec/workspace-element-spec.coffee +++ /dev/null @@ -1,208 +0,0 @@ -{ipcRenderer} = require 'electron' -path = require 'path' -temp = require('temp').track() -{Disposable} = require 'event-kit' - -describe "WorkspaceElement", -> - afterEach -> - temp.cleanupSync() - - describe "when the workspace element is focused", -> - it "transfers focus to the active pane", -> - workspaceElement = atom.views.getView(atom.workspace) - jasmine.attachToDOM(workspaceElement) - activePaneElement = atom.views.getView(atom.workspace.getActivePane()) - document.body.focus() - expect(document.activeElement).not.toBe(activePaneElement) - workspaceElement.focus() - expect(document.activeElement).toBe(activePaneElement) - - describe "the scrollbar visibility class", -> - it "has a class based on the style of the scrollbar", -> - observeCallback = null - scrollbarStyle = require 'scrollbar-style' - spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake (cb) -> - observeCallback = cb - new Disposable(->) - - workspaceElement = atom.views.getView(atom.workspace) - observeCallback('legacy') - expect(workspaceElement.className).toMatch 'scrollbars-visible-always' - - observeCallback('overlay') - expect(workspaceElement).toHaveClass 'scrollbars-visible-when-scrolling' - - describe "editor font styling", -> - [editor, editorElement, workspaceElement] = [] - - beforeEach -> - waitsForPromise -> atom.workspace.open('sample.js') - - runs -> - workspaceElement = atom.views.getView(atom.workspace) - jasmine.attachToDOM(workspaceElement) - editor = atom.workspace.getActiveTextEditor() - editorElement = atom.views.getView(editor) - - it "updates the font-size based on the 'editor.fontSize' config value", -> - initialCharWidth = editor.getDefaultCharWidth() - expect(getComputedStyle(editorElement).fontSize).toBe atom.config.get('editor.fontSize') + 'px' - atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5) - expect(getComputedStyle(editorElement).fontSize).toBe atom.config.get('editor.fontSize') + 'px' - expect(editor.getDefaultCharWidth()).toBeGreaterThan initialCharWidth - - it "updates the font-family based on the 'editor.fontFamily' config value", -> - initialCharWidth = editor.getDefaultCharWidth() - fontFamily = atom.config.get('editor.fontFamily') - expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily - - atom.config.set('editor.fontFamily', 'sans-serif') - fontFamily = atom.config.get('editor.fontFamily') - expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily - expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth - - it "updates the line-height based on the 'editor.lineHeight' config value", -> - initialLineHeight = editor.getLineHeightInPixels() - atom.config.set('editor.lineHeight', '30px') - expect(getComputedStyle(editorElement).lineHeight).toBe atom.config.get('editor.lineHeight') - expect(editor.getLineHeightInPixels()).not.toBe initialLineHeight - - it "increases or decreases the font size when a ctrl-mousewheel event occurs", -> - atom.config.set('editor.zoomFontWhenCtrlScrolling', true) - atom.config.set('editor.fontSize', 12) - - # Zoom out - editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { - wheelDeltaY: -10, - ctrlKey: true - })) - expect(atom.config.get('editor.fontSize')).toBe(11) - - # Zoom in - editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { - wheelDeltaY: 10, - ctrlKey: true - })) - expect(atom.config.get('editor.fontSize')).toBe(12) - - # Not on an atom-text-editor - workspaceElement.dispatchEvent(new WheelEvent('mousewheel', { - wheelDeltaY: 10, - ctrlKey: true - })) - expect(atom.config.get('editor.fontSize')).toBe(12) - - # No ctrl key - editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { - wheelDeltaY: 10, - })) - expect(atom.config.get('editor.fontSize')).toBe(12) - - atom.config.set('editor.zoomFontWhenCtrlScrolling', false) - editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { - wheelDeltaY: 10, - ctrlKey: true - })) - expect(atom.config.get('editor.fontSize')).toBe(12) - - describe 'panel containers', -> - it 'inserts panel container elements in the correct places in the DOM', -> - workspaceElement = atom.views.getView(atom.workspace) - - leftContainer = workspaceElement.querySelector('atom-panel-container.left') - rightContainer = workspaceElement.querySelector('atom-panel-container.right') - expect(leftContainer.nextSibling).toBe workspaceElement.verticalAxis - expect(rightContainer.previousSibling).toBe workspaceElement.verticalAxis - - topContainer = workspaceElement.querySelector('atom-panel-container.top') - bottomContainer = workspaceElement.querySelector('atom-panel-container.bottom') - expect(topContainer.nextSibling).toBe workspaceElement.paneContainer - expect(bottomContainer.previousSibling).toBe workspaceElement.paneContainer - - headerContainer = workspaceElement.querySelector('atom-panel-container.header') - footerContainer = workspaceElement.querySelector('atom-panel-container.footer') - expect(headerContainer.nextSibling).toBe workspaceElement.horizontalAxis - expect(footerContainer.previousSibling).toBe workspaceElement.horizontalAxis - - modalContainer = workspaceElement.querySelector('atom-panel-container.modal') - expect(modalContainer.parentNode).toBe workspaceElement - - it 'stretches header/footer panels to the workspace width', -> - workspaceElement = atom.views.getView(atom.workspace) - jasmine.attachToDOM(workspaceElement) - expect(workspaceElement.offsetWidth).toBeGreaterThan(0) - - headerItem = document.createElement('div') - atom.workspace.addHeaderPanel({item: headerItem}) - expect(headerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) - - footerItem = document.createElement('div') - atom.workspace.addFooterPanel({item: footerItem}) - expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) - - it 'shrinks horizontal axis according to header/footer panels height', -> - workspaceElement = atom.views.getView(atom.workspace) - workspaceElement.style.height = '100px' - horizontalAxisElement = workspaceElement.querySelector('atom-workspace-axis.horizontal') - jasmine.attachToDOM(workspaceElement) - - originalHorizontalAxisHeight = horizontalAxisElement.offsetHeight - expect(workspaceElement.offsetHeight).toBeGreaterThan(0) - expect(originalHorizontalAxisHeight).toBeGreaterThan(0) - - headerItem = document.createElement('div') - headerItem.style.height = '10px' - atom.workspace.addHeaderPanel({item: headerItem}) - expect(headerItem.offsetHeight).toBeGreaterThan(0) - - footerItem = document.createElement('div') - footerItem.style.height = '15px' - atom.workspace.addFooterPanel({item: footerItem}) - expect(footerItem.offsetHeight).toBeGreaterThan(0) - - expect(horizontalAxisElement.offsetHeight).toEqual(originalHorizontalAxisHeight - headerItem.offsetHeight - footerItem.offsetHeight) - - describe "the 'window:toggle-invisibles' command", -> - it "shows/hides invisibles in all open and future editors", -> - workspaceElement = atom.views.getView(atom.workspace) - expect(atom.config.get('editor.showInvisibles')).toBe false - atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles') - expect(atom.config.get('editor.showInvisibles')).toBe true - atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles') - expect(atom.config.get('editor.showInvisibles')).toBe false - - describe "the 'window:run-package-specs' command", -> - it "runs the package specs for the active item's project path, or the first project path", -> - workspaceElement = atom.views.getView(atom.workspace) - spyOn(ipcRenderer, 'send') - - # No project paths. Don't try to run specs. - atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs") - - projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")] - atom.project.setPaths(projectPaths) - - # No active item. Use first project directory. - atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) - ipcRenderer.send.reset() - - # Active item doesn't implement ::getPath(). Use first project directory. - item = document.createElement("div") - atom.workspace.getActivePane().activateItem(item) - atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) - ipcRenderer.send.reset() - - # Active item has no path. Use first project directory. - item.getPath = -> null - atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")) - ipcRenderer.send.reset() - - # Active item has path. Use project path for item path. - item.getPath = -> path.join(projectPaths[1], "a-file.txt") - atom.commands.dispatch(workspaceElement, "window:run-package-specs") - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec")) - ipcRenderer.send.reset() diff --git a/spec/workspace-element-spec.js b/spec/workspace-element-spec.js new file mode 100644 index 000000000..f81276796 --- /dev/null +++ b/spec/workspace-element-spec.js @@ -0,0 +1,228 @@ +const {ipcRenderer} = require('electron'); +const path = require('path'); +const temp = require('temp').track(); +const {Disposable} = require('event-kit'); + +describe("WorkspaceElement", function() { + afterEach(() => temp.cleanupSync()); + + describe("when the workspace element is focused", () => + it("transfers focus to the active pane", function() { + const workspaceElement = atom.views.getView(atom.workspace); + jasmine.attachToDOM(workspaceElement); + const activePaneElement = atom.views.getView(atom.workspace.getActivePane()); + document.body.focus(); + expect(document.activeElement).not.toBe(activePaneElement); + workspaceElement.focus(); + return expect(document.activeElement).toBe(activePaneElement); + }) + ); + + describe("the scrollbar visibility class", () => + it("has a class based on the style of the scrollbar", function() { + let observeCallback = null; + const scrollbarStyle = require('scrollbar-style'); + spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake(function(cb) { + observeCallback = cb; + return new Disposable(function() {}); + }); + + const workspaceElement = atom.views.getView(atom.workspace); + observeCallback('legacy'); + expect(workspaceElement.className).toMatch('scrollbars-visible-always'); + + observeCallback('overlay'); + return expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling'); + }) + ); + + describe("editor font styling", function() { + let [editor, editorElement, workspaceElement] = Array.from([]); + + beforeEach(function() { + waitsForPromise(() => atom.workspace.open('sample.js')); + + return runs(function() { + workspaceElement = atom.views.getView(atom.workspace); + jasmine.attachToDOM(workspaceElement); + editor = atom.workspace.getActiveTextEditor(); + return editorElement = atom.views.getView(editor); + }); + }); + + it("updates the font-size based on the 'editor.fontSize' config value", function() { + const initialCharWidth = editor.getDefaultCharWidth(); + expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px'); + atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5); + expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px'); + return expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth); + }); + + it("updates the font-family based on the 'editor.fontFamily' config value", function() { + const initialCharWidth = editor.getDefaultCharWidth(); + let fontFamily = atom.config.get('editor.fontFamily'); + expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); + + atom.config.set('editor.fontFamily', 'sans-serif'); + fontFamily = atom.config.get('editor.fontFamily'); + expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); + return expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth); + }); + + it("updates the line-height based on the 'editor.lineHeight' config value", function() { + const initialLineHeight = editor.getLineHeightInPixels(); + atom.config.set('editor.lineHeight', '30px'); + expect(getComputedStyle(editorElement).lineHeight).toBe(atom.config.get('editor.lineHeight')); + return expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight); + }); + + return it("increases or decreases the font size when a ctrl-mousewheel event occurs", function() { + atom.config.set('editor.zoomFontWhenCtrlScrolling', true); + atom.config.set('editor.fontSize', 12); + + // Zoom out + editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { + wheelDeltaY: -10, + ctrlKey: true + })); + expect(atom.config.get('editor.fontSize')).toBe(11); + + // Zoom in + editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { + wheelDeltaY: 10, + ctrlKey: true + })); + expect(atom.config.get('editor.fontSize')).toBe(12); + + // Not on an atom-text-editor + workspaceElement.dispatchEvent(new WheelEvent('mousewheel', { + wheelDeltaY: 10, + ctrlKey: true + })); + expect(atom.config.get('editor.fontSize')).toBe(12); + + // No ctrl key + editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { + wheelDeltaY: 10, + })); + expect(atom.config.get('editor.fontSize')).toBe(12); + + atom.config.set('editor.zoomFontWhenCtrlScrolling', false); + editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { + wheelDeltaY: 10, + ctrlKey: true + })); + return expect(atom.config.get('editor.fontSize')).toBe(12); + }); + }); + + describe('panel containers', function() { + it('inserts panel container elements in the correct places in the DOM', function() { + const workspaceElement = atom.views.getView(atom.workspace); + + const leftContainer = workspaceElement.querySelector('atom-panel-container.left'); + const rightContainer = workspaceElement.querySelector('atom-panel-container.right'); + expect(leftContainer.nextSibling).toBe(workspaceElement.verticalAxis); + expect(rightContainer.previousSibling).toBe(workspaceElement.verticalAxis); + + const topContainer = workspaceElement.querySelector('atom-panel-container.top'); + const bottomContainer = workspaceElement.querySelector('atom-panel-container.bottom'); + expect(topContainer.nextSibling).toBe(workspaceElement.paneContainer); + expect(bottomContainer.previousSibling).toBe(workspaceElement.paneContainer); + + const headerContainer = workspaceElement.querySelector('atom-panel-container.header'); + const footerContainer = workspaceElement.querySelector('atom-panel-container.footer'); + expect(headerContainer.nextSibling).toBe(workspaceElement.horizontalAxis); + expect(footerContainer.previousSibling).toBe(workspaceElement.horizontalAxis); + + const modalContainer = workspaceElement.querySelector('atom-panel-container.modal'); + return expect(modalContainer.parentNode).toBe(workspaceElement); + }); + + it('stretches header/footer panels to the workspace width', function() { + const workspaceElement = atom.views.getView(atom.workspace); + jasmine.attachToDOM(workspaceElement); + expect(workspaceElement.offsetWidth).toBeGreaterThan(0); + + const headerItem = document.createElement('div'); + atom.workspace.addHeaderPanel({item: headerItem}); + expect(headerItem.offsetWidth).toEqual(workspaceElement.offsetWidth); + + const footerItem = document.createElement('div'); + atom.workspace.addFooterPanel({item: footerItem}); + return expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth); + }); + + return it('shrinks horizontal axis according to header/footer panels height', function() { + const workspaceElement = atom.views.getView(atom.workspace); + workspaceElement.style.height = '100px'; + const horizontalAxisElement = workspaceElement.querySelector('atom-workspace-axis.horizontal'); + jasmine.attachToDOM(workspaceElement); + + const originalHorizontalAxisHeight = horizontalAxisElement.offsetHeight; + expect(workspaceElement.offsetHeight).toBeGreaterThan(0); + expect(originalHorizontalAxisHeight).toBeGreaterThan(0); + + const headerItem = document.createElement('div'); + headerItem.style.height = '10px'; + atom.workspace.addHeaderPanel({item: headerItem}); + expect(headerItem.offsetHeight).toBeGreaterThan(0); + + const footerItem = document.createElement('div'); + footerItem.style.height = '15px'; + atom.workspace.addFooterPanel({item: footerItem}); + expect(footerItem.offsetHeight).toBeGreaterThan(0); + + return expect(horizontalAxisElement.offsetHeight).toEqual(originalHorizontalAxisHeight - headerItem.offsetHeight - footerItem.offsetHeight); + }); + }); + + describe("the 'window:toggle-invisibles' command", () => + it("shows/hides invisibles in all open and future editors", function() { + const workspaceElement = atom.views.getView(atom.workspace); + expect(atom.config.get('editor.showInvisibles')).toBe(false); + atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles'); + expect(atom.config.get('editor.showInvisibles')).toBe(true); + atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles'); + return expect(atom.config.get('editor.showInvisibles')).toBe(false); + }) + ); + + return describe("the 'window:run-package-specs' command", () => + it("runs the package specs for the active item's project path, or the first project path", function() { + const workspaceElement = atom.views.getView(atom.workspace); + spyOn(ipcRenderer, 'send'); + + // No project paths. Don't try to run specs. + atom.commands.dispatch(workspaceElement, "window:run-package-specs"); + expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs"); + + const projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]; + atom.project.setPaths(projectPaths); + + // No active item. Use first project directory. + atom.commands.dispatch(workspaceElement, "window:run-package-specs"); + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")); + ipcRenderer.send.reset(); + + // Active item doesn't implement ::getPath(). Use first project directory. + const item = document.createElement("div"); + atom.workspace.getActivePane().activateItem(item); + atom.commands.dispatch(workspaceElement, "window:run-package-specs"); + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")); + ipcRenderer.send.reset(); + + // Active item has no path. Use first project directory. + item.getPath = () => null; + atom.commands.dispatch(workspaceElement, "window:run-package-specs"); + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")); + ipcRenderer.send.reset(); + + // Active item has path. Use project path for item path. + item.getPath = () => path.join(projectPaths[1], "a-file.txt"); + atom.commands.dispatch(workspaceElement, "window:run-package-specs"); + expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec")); + return ipcRenderer.send.reset(); + }) + ); +}); diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee deleted file mode 100644 index 6defe33da..000000000 --- a/src/workspace-element.coffee +++ /dev/null @@ -1,149 +0,0 @@ -{ipcRenderer} = require 'electron' -path = require 'path' -fs = require 'fs-plus' -{CompositeDisposable} = require 'event-kit' -scrollbarStyle = require 'scrollbar-style' - -module.exports = -class WorkspaceElement extends HTMLElement - globalTextEditorStyleSheet: null - - attachedCallback: -> - @focus() - - detachedCallback: -> - @subscriptions.dispose() - - initializeContent: -> - @classList.add 'workspace' - @setAttribute 'tabindex', -1 - - @verticalAxis = document.createElement('atom-workspace-axis') - @verticalAxis.classList.add('vertical') - - @horizontalAxis = document.createElement('atom-workspace-axis') - @horizontalAxis.classList.add('horizontal') - @horizontalAxis.appendChild(@verticalAxis) - - @appendChild(@horizontalAxis) - - observeScrollbarStyle: -> - @subscriptions.add scrollbarStyle.observePreferredScrollbarStyle (style) => - switch style - when 'legacy' - @classList.remove('scrollbars-visible-when-scrolling') - @classList.add("scrollbars-visible-always") - when 'overlay' - @classList.remove('scrollbars-visible-always') - @classList.add("scrollbars-visible-when-scrolling") - - observeTextEditorFontConfig: -> - @updateGlobalTextEditorStyleSheet() - @subscriptions.add @config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this) - @subscriptions.add @config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this) - @subscriptions.add @config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this) - - updateGlobalTextEditorStyleSheet: -> - styleSheetSource = """ - atom-text-editor { - font-size: #{@config.get('editor.fontSize')}px; - font-family: #{@config.get('editor.fontFamily')}; - line-height: #{@config.get('editor.lineHeight')}; - } - """ - @styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles') - @views.performDocumentPoll() - - initialize: (@model, {@views, @workspace, @project, @config, @styles}) -> - throw new Error("Must pass a views parameter when initializing WorskpaceElements") unless @views? - throw new Error("Must pass a workspace parameter when initializing WorskpaceElements") unless @workspace? - throw new Error("Must pass a project parameter when initializing WorskpaceElements") unless @project? - throw new Error("Must pass a config parameter when initializing WorskpaceElements") unless @config? - throw new Error("Must pass a styles parameter when initializing WorskpaceElements") unless @styles? - - @subscriptions = new CompositeDisposable - @initializeContent() - @observeScrollbarStyle() - @observeTextEditorFontConfig() - - @paneContainer = @views.getView(@model.paneContainer) - @verticalAxis.appendChild(@paneContainer) - @addEventListener 'focus', @handleFocus.bind(this) - - @addEventListener 'mousewheel', @handleMousewheel.bind(this), true - - @panelContainers = - top: @views.getView(@model.panelContainers.top) - left: @views.getView(@model.panelContainers.left) - right: @views.getView(@model.panelContainers.right) - bottom: @views.getView(@model.panelContainers.bottom) - header: @views.getView(@model.panelContainers.header) - footer: @views.getView(@model.panelContainers.footer) - modal: @views.getView(@model.panelContainers.modal) - - @horizontalAxis.insertBefore(@panelContainers.left, @verticalAxis) - @horizontalAxis.appendChild(@panelContainers.right) - - @verticalAxis.insertBefore(@panelContainers.top, @paneContainer) - @verticalAxis.appendChild(@panelContainers.bottom) - - @insertBefore(@panelContainers.header, @horizontalAxis) - @appendChild(@panelContainers.footer) - - @appendChild(@panelContainers.modal) - - this - - getModel: -> @model - - handleMousewheel: (event) -> - if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling') and event.target.closest('atom-text-editor')? - if event.wheelDeltaY > 0 - @model.increaseFontSize() - else if event.wheelDeltaY < 0 - @model.decreaseFontSize() - event.preventDefault() - event.stopPropagation() - - handleFocus: (event) -> - @model.getActivePane().activate() - - focusPaneViewAbove: -> @paneContainer.focusPaneViewAbove() - - focusPaneViewBelow: -> @paneContainer.focusPaneViewBelow() - - focusPaneViewOnLeft: -> @paneContainer.focusPaneViewOnLeft() - - focusPaneViewOnRight: -> @paneContainer.focusPaneViewOnRight() - - moveActiveItemToPaneAbove: (params) -> @paneContainer.moveActiveItemToPaneAbove(params) - - moveActiveItemToPaneBelow: (params) -> @paneContainer.moveActiveItemToPaneBelow(params) - - moveActiveItemToPaneOnLeft: (params) -> @paneContainer.moveActiveItemToPaneOnLeft(params) - - moveActiveItemToPaneOnRight: (params) -> @paneContainer.moveActiveItemToPaneOnRight(params) - - runPackageSpecs: -> - if activePath = @workspace.getActivePaneItem()?.getPath?() - [projectPath] = @project.relativizePath(activePath) - else - [projectPath] = @project.getPaths() - if projectPath - specPath = path.join(projectPath, 'spec') - testPath = path.join(projectPath, 'test') - if not fs.existsSync(specPath) and fs.existsSync(testPath) - specPath = testPath - - ipcRenderer.send('run-package-specs', specPath) - - runBenchmarks: -> - if activePath = @workspace.getActivePaneItem()?.getPath?() - [projectPath] = @project.relativizePath(activePath) - else - [projectPath] = @project.getPaths() - - if projectPath - ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) - -module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype diff --git a/src/workspace-element.js b/src/workspace-element.js new file mode 100644 index 000000000..b24bb80f6 --- /dev/null +++ b/src/workspace-element.js @@ -0,0 +1,196 @@ +let WorkspaceElement; +const {ipcRenderer} = require('electron'); +const path = require('path'); +const fs = require('fs-plus'); +const {CompositeDisposable} = require('event-kit'); +const scrollbarStyle = require('scrollbar-style'); + +module.exports = +__initClass__(WorkspaceElement = class WorkspaceElement extends HTMLElement { + static initClass() { + this.prototype.globalTextEditorStyleSheet = null; + } + + attachedCallback() { + return this.focus(); + } + + detachedCallback() { + return this.subscriptions.dispose(); + } + + initializeContent() { + this.classList.add('workspace'); + this.setAttribute('tabindex', -1); + + this.verticalAxis = document.createElement('atom-workspace-axis'); + this.verticalAxis.classList.add('vertical'); + + this.horizontalAxis = document.createElement('atom-workspace-axis'); + this.horizontalAxis.classList.add('horizontal'); + this.horizontalAxis.appendChild(this.verticalAxis); + + return this.appendChild(this.horizontalAxis); + } + + observeScrollbarStyle() { + return this.subscriptions.add(scrollbarStyle.observePreferredScrollbarStyle(style => { + switch (style) { + case 'legacy': + this.classList.remove('scrollbars-visible-when-scrolling'); + return this.classList.add("scrollbars-visible-always"); + case 'overlay': + this.classList.remove('scrollbars-visible-always'); + return this.classList.add("scrollbars-visible-when-scrolling"); + } + } + ) + ); + } + + observeTextEditorFontConfig() { + this.updateGlobalTextEditorStyleSheet(); + this.subscriptions.add(this.config.onDidChange('editor.fontSize', this.updateGlobalTextEditorStyleSheet.bind(this))); + this.subscriptions.add(this.config.onDidChange('editor.fontFamily', this.updateGlobalTextEditorStyleSheet.bind(this))); + return this.subscriptions.add(this.config.onDidChange('editor.lineHeight', this.updateGlobalTextEditorStyleSheet.bind(this))); + } + + updateGlobalTextEditorStyleSheet() { + const styleSheetSource = `\ +atom-text-editor { + font-size: ${this.config.get('editor.fontSize')}px; + font-family: ${this.config.get('editor.fontFamily')}; + line-height: ${this.config.get('editor.lineHeight')}; +}\ +`; + this.styles.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles'}); + return this.views.performDocumentPoll(); + } + + initialize(model, {views, workspace, project, config, styles}) { + this.model = model; + this.views = views; + this.workspace = workspace; + this.project = project; + this.config = config; + this.styles = styles; + if (this.views == null) { throw new Error("Must pass a views parameter when initializing WorskpaceElements"); } + if (this.workspace == null) { throw new Error("Must pass a workspace parameter when initializing WorskpaceElements"); } + if (this.project == null) { throw new Error("Must pass a project parameter when initializing WorskpaceElements"); } + if (this.config == null) { throw new Error("Must pass a config parameter when initializing WorskpaceElements"); } + if (this.styles == null) { throw new Error("Must pass a styles parameter when initializing WorskpaceElements"); } + + this.subscriptions = new CompositeDisposable; + this.initializeContent(); + this.observeScrollbarStyle(); + this.observeTextEditorFontConfig(); + + this.paneContainer = this.views.getView(this.model.paneContainer); + this.verticalAxis.appendChild(this.paneContainer); + this.addEventListener('focus', this.handleFocus.bind(this)); + + this.addEventListener('mousewheel', this.handleMousewheel.bind(this), true); + + this.panelContainers = { + top: this.views.getView(this.model.panelContainers.top), + left: this.views.getView(this.model.panelContainers.left), + right: this.views.getView(this.model.panelContainers.right), + bottom: this.views.getView(this.model.panelContainers.bottom), + header: this.views.getView(this.model.panelContainers.header), + footer: this.views.getView(this.model.panelContainers.footer), + modal: this.views.getView(this.model.panelContainers.modal) + }; + + this.horizontalAxis.insertBefore(this.panelContainers.left, this.verticalAxis); + this.horizontalAxis.appendChild(this.panelContainers.right); + + this.verticalAxis.insertBefore(this.panelContainers.top, this.paneContainer); + this.verticalAxis.appendChild(this.panelContainers.bottom); + + this.insertBefore(this.panelContainers.header, this.horizontalAxis); + this.appendChild(this.panelContainers.footer); + + this.appendChild(this.panelContainers.modal); + + return this; + } + + getModel() { return this.model; } + + handleMousewheel(event) { + if (event.ctrlKey && this.config.get('editor.zoomFontWhenCtrlScrolling') && (event.target.closest('atom-text-editor') != null)) { + if (event.wheelDeltaY > 0) { + this.model.increaseFontSize(); + } else if (event.wheelDeltaY < 0) { + this.model.decreaseFontSize(); + } + event.preventDefault(); + return event.stopPropagation(); + } + } + + handleFocus(event) { + return this.model.getActivePane().activate(); + } + + focusPaneViewAbove() { return this.paneContainer.focusPaneViewAbove(); } + + focusPaneViewBelow() { return this.paneContainer.focusPaneViewBelow(); } + + focusPaneViewOnLeft() { return this.paneContainer.focusPaneViewOnLeft(); } + + focusPaneViewOnRight() { return this.paneContainer.focusPaneViewOnRight(); } + + moveActiveItemToPaneAbove(params) { return this.paneContainer.moveActiveItemToPaneAbove(params); } + + moveActiveItemToPaneBelow(params) { return this.paneContainer.moveActiveItemToPaneBelow(params); } + + moveActiveItemToPaneOnLeft(params) { return this.paneContainer.moveActiveItemToPaneOnLeft(params); } + + moveActiveItemToPaneOnRight(params) { return this.paneContainer.moveActiveItemToPaneOnRight(params); } + + runPackageSpecs() { + let activePath, projectPath; + if (activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath())) { + [projectPath] = Array.from(this.project.relativizePath(activePath)); + } else { + [projectPath] = Array.from(this.project.getPaths()); + } + if (projectPath) { + let specPath = path.join(projectPath, 'spec'); + const testPath = path.join(projectPath, 'test'); + if (!fs.existsSync(specPath) && fs.existsSync(testPath)) { + specPath = testPath; + } + + return ipcRenderer.send('run-package-specs', specPath); + } + } + + runBenchmarks() { + let activePath, projectPath; + if (activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath())) { + [projectPath] = Array.from(this.project.relativizePath(activePath)); + } else { + [projectPath] = Array.from(this.project.getPaths()); + } + + if (projectPath) { + return ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')); + } + } +}); + +module.exports = WorkspaceElement = document.registerElement('atom-workspace', {prototype: WorkspaceElement.prototype}); + +function __initClass__(c) { + c.initClass(); + return c; +} +function __guardMethod__(obj, methodName, transform) { + if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { + return transform(obj, methodName); + } else { + return undefined; + } +} \ No newline at end of file From ca4d55954b4b82dd96b76f8fef676ffed17cfbf5 Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Tue, 7 Mar 2017 15:56:57 -0800 Subject: [PATCH 113/194] Convert workspace element to JS: Lint --- spec/workspace-element-spec.js | 324 +++++++++++++++++---------------- src/workspace-element.js | 218 +++++++++++----------- 2 files changed, 273 insertions(+), 269 deletions(-) diff --git a/spec/workspace-element-spec.js b/spec/workspace-element-spec.js index f81276796..2a7fc0cab 100644 --- a/spec/workspace-element-spec.js +++ b/spec/workspace-element-spec.js @@ -1,228 +1,230 @@ -const {ipcRenderer} = require('electron'); -const path = require('path'); -const temp = require('temp').track(); -const {Disposable} = require('event-kit'); +/* global getComputedStyle, WheelEvent */ -describe("WorkspaceElement", function() { - afterEach(() => temp.cleanupSync()); +const {ipcRenderer} = require('electron') +const path = require('path') +const temp = require('temp').track() +const {Disposable} = require('event-kit') - describe("when the workspace element is focused", () => - it("transfers focus to the active pane", function() { - const workspaceElement = atom.views.getView(atom.workspace); - jasmine.attachToDOM(workspaceElement); - const activePaneElement = atom.views.getView(atom.workspace.getActivePane()); - document.body.focus(); - expect(document.activeElement).not.toBe(activePaneElement); - workspaceElement.focus(); - return expect(document.activeElement).toBe(activePaneElement); +describe('WorkspaceElement', function () { + afterEach(() => temp.cleanupSync()) + + describe('when the workspace element is focused', () => + it('transfers focus to the active pane', function () { + const workspaceElement = atom.views.getView(atom.workspace) + jasmine.attachToDOM(workspaceElement) + const activePaneElement = atom.views.getView(atom.workspace.getActivePane()) + document.body.focus() + expect(document.activeElement).not.toBe(activePaneElement) + workspaceElement.focus() + return expect(document.activeElement).toBe(activePaneElement) }) - ); + ) - describe("the scrollbar visibility class", () => - it("has a class based on the style of the scrollbar", function() { - let observeCallback = null; - const scrollbarStyle = require('scrollbar-style'); - spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake(function(cb) { - observeCallback = cb; - return new Disposable(function() {}); - }); + describe('the scrollbar visibility class', () => + it('has a class based on the style of the scrollbar', function () { + let observeCallback = null + const scrollbarStyle = require('scrollbar-style') + spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake(function (cb) { + observeCallback = cb + return new Disposable(function () {}) + }) - const workspaceElement = atom.views.getView(atom.workspace); - observeCallback('legacy'); - expect(workspaceElement.className).toMatch('scrollbars-visible-always'); + const workspaceElement = atom.views.getView(atom.workspace) + observeCallback('legacy') + expect(workspaceElement.className).toMatch('scrollbars-visible-always') - observeCallback('overlay'); - return expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling'); + observeCallback('overlay') + return expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling') }) - ); + ) - describe("editor font styling", function() { - let [editor, editorElement, workspaceElement] = Array.from([]); + describe('editor font styling', function () { + let [editor, editorElement, workspaceElement] = Array.from([]) - beforeEach(function() { - waitsForPromise(() => atom.workspace.open('sample.js')); + beforeEach(function () { + waitsForPromise(() => atom.workspace.open('sample.js')) - return runs(function() { - workspaceElement = atom.views.getView(atom.workspace); - jasmine.attachToDOM(workspaceElement); - editor = atom.workspace.getActiveTextEditor(); - return editorElement = atom.views.getView(editor); - }); - }); + return runs(function () { + workspaceElement = atom.views.getView(atom.workspace) + jasmine.attachToDOM(workspaceElement) + editor = atom.workspace.getActiveTextEditor() + return (editorElement = atom.views.getView(editor)) + }) + }) - it("updates the font-size based on the 'editor.fontSize' config value", function() { - const initialCharWidth = editor.getDefaultCharWidth(); - expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px'); - atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5); - expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px'); - return expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth); - }); + it("updates the font-size based on the 'editor.fontSize' config value", function () { + const initialCharWidth = editor.getDefaultCharWidth() + expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px') + atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5) + expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px') + return expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth) + }) - it("updates the font-family based on the 'editor.fontFamily' config value", function() { - const initialCharWidth = editor.getDefaultCharWidth(); - let fontFamily = atom.config.get('editor.fontFamily'); - expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); + it("updates the font-family based on the 'editor.fontFamily' config value", function () { + const initialCharWidth = editor.getDefaultCharWidth() + let fontFamily = atom.config.get('editor.fontFamily') + expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily) - atom.config.set('editor.fontFamily', 'sans-serif'); - fontFamily = atom.config.get('editor.fontFamily'); - expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); - return expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth); - }); + atom.config.set('editor.fontFamily', 'sans-serif') + fontFamily = atom.config.get('editor.fontFamily') + expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily) + return expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth) + }) - it("updates the line-height based on the 'editor.lineHeight' config value", function() { - const initialLineHeight = editor.getLineHeightInPixels(); - atom.config.set('editor.lineHeight', '30px'); - expect(getComputedStyle(editorElement).lineHeight).toBe(atom.config.get('editor.lineHeight')); - return expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight); - }); + it("updates the line-height based on the 'editor.lineHeight' config value", function () { + const initialLineHeight = editor.getLineHeightInPixels() + atom.config.set('editor.lineHeight', '30px') + expect(getComputedStyle(editorElement).lineHeight).toBe(atom.config.get('editor.lineHeight')) + return expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight) + }) - return it("increases or decreases the font size when a ctrl-mousewheel event occurs", function() { - atom.config.set('editor.zoomFontWhenCtrlScrolling', true); - atom.config.set('editor.fontSize', 12); + return it('increases or decreases the font size when a ctrl-mousewheel event occurs', function () { + atom.config.set('editor.zoomFontWhenCtrlScrolling', true) + atom.config.set('editor.fontSize', 12) // Zoom out editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { wheelDeltaY: -10, ctrlKey: true - })); - expect(atom.config.get('editor.fontSize')).toBe(11); + })) + expect(atom.config.get('editor.fontSize')).toBe(11) // Zoom in editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { wheelDeltaY: 10, ctrlKey: true - })); - expect(atom.config.get('editor.fontSize')).toBe(12); + })) + expect(atom.config.get('editor.fontSize')).toBe(12) // Not on an atom-text-editor workspaceElement.dispatchEvent(new WheelEvent('mousewheel', { wheelDeltaY: 10, ctrlKey: true - })); - expect(atom.config.get('editor.fontSize')).toBe(12); + })) + expect(atom.config.get('editor.fontSize')).toBe(12) // No ctrl key editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { - wheelDeltaY: 10, - })); - expect(atom.config.get('editor.fontSize')).toBe(12); + wheelDeltaY: 10 + })) + expect(atom.config.get('editor.fontSize')).toBe(12) - atom.config.set('editor.zoomFontWhenCtrlScrolling', false); + atom.config.set('editor.zoomFontWhenCtrlScrolling', false) editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', { wheelDeltaY: 10, ctrlKey: true - })); - return expect(atom.config.get('editor.fontSize')).toBe(12); - }); - }); + })) + return expect(atom.config.get('editor.fontSize')).toBe(12) + }) + }) - describe('panel containers', function() { - it('inserts panel container elements in the correct places in the DOM', function() { - const workspaceElement = atom.views.getView(atom.workspace); + describe('panel containers', function () { + it('inserts panel container elements in the correct places in the DOM', function () { + const workspaceElement = atom.views.getView(atom.workspace) - const leftContainer = workspaceElement.querySelector('atom-panel-container.left'); - const rightContainer = workspaceElement.querySelector('atom-panel-container.right'); - expect(leftContainer.nextSibling).toBe(workspaceElement.verticalAxis); - expect(rightContainer.previousSibling).toBe(workspaceElement.verticalAxis); + const leftContainer = workspaceElement.querySelector('atom-panel-container.left') + const rightContainer = workspaceElement.querySelector('atom-panel-container.right') + expect(leftContainer.nextSibling).toBe(workspaceElement.verticalAxis) + expect(rightContainer.previousSibling).toBe(workspaceElement.verticalAxis) - const topContainer = workspaceElement.querySelector('atom-panel-container.top'); - const bottomContainer = workspaceElement.querySelector('atom-panel-container.bottom'); - expect(topContainer.nextSibling).toBe(workspaceElement.paneContainer); - expect(bottomContainer.previousSibling).toBe(workspaceElement.paneContainer); + const topContainer = workspaceElement.querySelector('atom-panel-container.top') + const bottomContainer = workspaceElement.querySelector('atom-panel-container.bottom') + expect(topContainer.nextSibling).toBe(workspaceElement.paneContainer) + expect(bottomContainer.previousSibling).toBe(workspaceElement.paneContainer) - const headerContainer = workspaceElement.querySelector('atom-panel-container.header'); - const footerContainer = workspaceElement.querySelector('atom-panel-container.footer'); - expect(headerContainer.nextSibling).toBe(workspaceElement.horizontalAxis); - expect(footerContainer.previousSibling).toBe(workspaceElement.horizontalAxis); + const headerContainer = workspaceElement.querySelector('atom-panel-container.header') + const footerContainer = workspaceElement.querySelector('atom-panel-container.footer') + expect(headerContainer.nextSibling).toBe(workspaceElement.horizontalAxis) + expect(footerContainer.previousSibling).toBe(workspaceElement.horizontalAxis) - const modalContainer = workspaceElement.querySelector('atom-panel-container.modal'); - return expect(modalContainer.parentNode).toBe(workspaceElement); - }); + const modalContainer = workspaceElement.querySelector('atom-panel-container.modal') + return expect(modalContainer.parentNode).toBe(workspaceElement) + }) - it('stretches header/footer panels to the workspace width', function() { - const workspaceElement = atom.views.getView(atom.workspace); - jasmine.attachToDOM(workspaceElement); - expect(workspaceElement.offsetWidth).toBeGreaterThan(0); + it('stretches header/footer panels to the workspace width', function () { + const workspaceElement = atom.views.getView(atom.workspace) + jasmine.attachToDOM(workspaceElement) + expect(workspaceElement.offsetWidth).toBeGreaterThan(0) - const headerItem = document.createElement('div'); - atom.workspace.addHeaderPanel({item: headerItem}); - expect(headerItem.offsetWidth).toEqual(workspaceElement.offsetWidth); + const headerItem = document.createElement('div') + atom.workspace.addHeaderPanel({item: headerItem}) + expect(headerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) - const footerItem = document.createElement('div'); - atom.workspace.addFooterPanel({item: footerItem}); - return expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth); - }); + const footerItem = document.createElement('div') + atom.workspace.addFooterPanel({item: footerItem}) + return expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) + }) - return it('shrinks horizontal axis according to header/footer panels height', function() { - const workspaceElement = atom.views.getView(atom.workspace); - workspaceElement.style.height = '100px'; - const horizontalAxisElement = workspaceElement.querySelector('atom-workspace-axis.horizontal'); - jasmine.attachToDOM(workspaceElement); + return it('shrinks horizontal axis according to header/footer panels height', function () { + const workspaceElement = atom.views.getView(atom.workspace) + workspaceElement.style.height = '100px' + const horizontalAxisElement = workspaceElement.querySelector('atom-workspace-axis.horizontal') + jasmine.attachToDOM(workspaceElement) - const originalHorizontalAxisHeight = horizontalAxisElement.offsetHeight; - expect(workspaceElement.offsetHeight).toBeGreaterThan(0); - expect(originalHorizontalAxisHeight).toBeGreaterThan(0); + const originalHorizontalAxisHeight = horizontalAxisElement.offsetHeight + expect(workspaceElement.offsetHeight).toBeGreaterThan(0) + expect(originalHorizontalAxisHeight).toBeGreaterThan(0) - const headerItem = document.createElement('div'); - headerItem.style.height = '10px'; - atom.workspace.addHeaderPanel({item: headerItem}); - expect(headerItem.offsetHeight).toBeGreaterThan(0); + const headerItem = document.createElement('div') + headerItem.style.height = '10px' + atom.workspace.addHeaderPanel({item: headerItem}) + expect(headerItem.offsetHeight).toBeGreaterThan(0) - const footerItem = document.createElement('div'); - footerItem.style.height = '15px'; - atom.workspace.addFooterPanel({item: footerItem}); - expect(footerItem.offsetHeight).toBeGreaterThan(0); + const footerItem = document.createElement('div') + footerItem.style.height = '15px' + atom.workspace.addFooterPanel({item: footerItem}) + expect(footerItem.offsetHeight).toBeGreaterThan(0) - return expect(horizontalAxisElement.offsetHeight).toEqual(originalHorizontalAxisHeight - headerItem.offsetHeight - footerItem.offsetHeight); - }); - }); + return expect(horizontalAxisElement.offsetHeight).toEqual(originalHorizontalAxisHeight - headerItem.offsetHeight - footerItem.offsetHeight) + }) + }) describe("the 'window:toggle-invisibles' command", () => - it("shows/hides invisibles in all open and future editors", function() { - const workspaceElement = atom.views.getView(atom.workspace); - expect(atom.config.get('editor.showInvisibles')).toBe(false); - atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles'); - expect(atom.config.get('editor.showInvisibles')).toBe(true); - atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles'); - return expect(atom.config.get('editor.showInvisibles')).toBe(false); + it('shows/hides invisibles in all open and future editors', function () { + const workspaceElement = atom.views.getView(atom.workspace) + expect(atom.config.get('editor.showInvisibles')).toBe(false) + atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles') + expect(atom.config.get('editor.showInvisibles')).toBe(true) + atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles') + return expect(atom.config.get('editor.showInvisibles')).toBe(false) }) - ); + ) return describe("the 'window:run-package-specs' command", () => - it("runs the package specs for the active item's project path, or the first project path", function() { - const workspaceElement = atom.views.getView(atom.workspace); - spyOn(ipcRenderer, 'send'); + it("runs the package specs for the active item's project path, or the first project path", function () { + const workspaceElement = atom.views.getView(atom.workspace) + spyOn(ipcRenderer, 'send') // No project paths. Don't try to run specs. - atom.commands.dispatch(workspaceElement, "window:run-package-specs"); - expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs"); + atom.commands.dispatch(workspaceElement, 'window:run-package-specs') + expect(ipcRenderer.send).not.toHaveBeenCalledWith('run-package-specs') - const projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]; - atom.project.setPaths(projectPaths); + const projectPaths = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')] + atom.project.setPaths(projectPaths) // No active item. Use first project directory. - atom.commands.dispatch(workspaceElement, "window:run-package-specs"); - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")); - ipcRenderer.send.reset(); + atom.commands.dispatch(workspaceElement, 'window:run-package-specs') + expect(ipcRenderer.send).toHaveBeenCalledWith('run-package-specs', path.join(projectPaths[0], 'spec')) + ipcRenderer.send.reset() // Active item doesn't implement ::getPath(). Use first project directory. - const item = document.createElement("div"); - atom.workspace.getActivePane().activateItem(item); - atom.commands.dispatch(workspaceElement, "window:run-package-specs"); - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")); - ipcRenderer.send.reset(); + const item = document.createElement('div') + atom.workspace.getActivePane().activateItem(item) + atom.commands.dispatch(workspaceElement, 'window:run-package-specs') + expect(ipcRenderer.send).toHaveBeenCalledWith('run-package-specs', path.join(projectPaths[0], 'spec')) + ipcRenderer.send.reset() // Active item has no path. Use first project directory. - item.getPath = () => null; - atom.commands.dispatch(workspaceElement, "window:run-package-specs"); - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec")); - ipcRenderer.send.reset(); + item.getPath = () => null + atom.commands.dispatch(workspaceElement, 'window:run-package-specs') + expect(ipcRenderer.send).toHaveBeenCalledWith('run-package-specs', path.join(projectPaths[0], 'spec')) + ipcRenderer.send.reset() // Active item has path. Use project path for item path. - item.getPath = () => path.join(projectPaths[1], "a-file.txt"); - atom.commands.dispatch(workspaceElement, "window:run-package-specs"); - expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec")); - return ipcRenderer.send.reset(); + item.getPath = () => path.join(projectPaths[1], 'a-file.txt') + atom.commands.dispatch(workspaceElement, 'window:run-package-specs') + expect(ipcRenderer.send).toHaveBeenCalledWith('run-package-specs', path.join(projectPaths[1], 'spec')) + return ipcRenderer.send.reset() }) - ); -}); + ) +}) diff --git a/src/workspace-element.js b/src/workspace-element.js index b24bb80f6..465c4be34 100644 --- a/src/workspace-element.js +++ b/src/workspace-element.js @@ -1,95 +1,97 @@ -let WorkspaceElement; -const {ipcRenderer} = require('electron'); -const path = require('path'); -const fs = require('fs-plus'); -const {CompositeDisposable} = require('event-kit'); -const scrollbarStyle = require('scrollbar-style'); +/* global HTMLElement */ + +let WorkspaceElement +const {ipcRenderer} = require('electron') +const path = require('path') +const fs = require('fs-plus') +const {CompositeDisposable} = require('event-kit') +const scrollbarStyle = require('scrollbar-style') module.exports = __initClass__(WorkspaceElement = class WorkspaceElement extends HTMLElement { - static initClass() { - this.prototype.globalTextEditorStyleSheet = null; + static initClass () { + this.prototype.globalTextEditorStyleSheet = null } - attachedCallback() { - return this.focus(); + attachedCallback () { + return this.focus() } - detachedCallback() { - return this.subscriptions.dispose(); + detachedCallback () { + return this.subscriptions.dispose() } - initializeContent() { - this.classList.add('workspace'); - this.setAttribute('tabindex', -1); + initializeContent () { + this.classList.add('workspace') + this.setAttribute('tabindex', -1) - this.verticalAxis = document.createElement('atom-workspace-axis'); - this.verticalAxis.classList.add('vertical'); + this.verticalAxis = document.createElement('atom-workspace-axis') + this.verticalAxis.classList.add('vertical') - this.horizontalAxis = document.createElement('atom-workspace-axis'); - this.horizontalAxis.classList.add('horizontal'); - this.horizontalAxis.appendChild(this.verticalAxis); + this.horizontalAxis = document.createElement('atom-workspace-axis') + this.horizontalAxis.classList.add('horizontal') + this.horizontalAxis.appendChild(this.verticalAxis) - return this.appendChild(this.horizontalAxis); + return this.appendChild(this.horizontalAxis) } - observeScrollbarStyle() { + observeScrollbarStyle () { return this.subscriptions.add(scrollbarStyle.observePreferredScrollbarStyle(style => { switch (style) { case 'legacy': - this.classList.remove('scrollbars-visible-when-scrolling'); - return this.classList.add("scrollbars-visible-always"); + this.classList.remove('scrollbars-visible-when-scrolling') + return this.classList.add('scrollbars-visible-always') case 'overlay': - this.classList.remove('scrollbars-visible-always'); - return this.classList.add("scrollbars-visible-when-scrolling"); + this.classList.remove('scrollbars-visible-always') + return this.classList.add('scrollbars-visible-when-scrolling') } } ) - ); + ) } - observeTextEditorFontConfig() { - this.updateGlobalTextEditorStyleSheet(); - this.subscriptions.add(this.config.onDidChange('editor.fontSize', this.updateGlobalTextEditorStyleSheet.bind(this))); - this.subscriptions.add(this.config.onDidChange('editor.fontFamily', this.updateGlobalTextEditorStyleSheet.bind(this))); - return this.subscriptions.add(this.config.onDidChange('editor.lineHeight', this.updateGlobalTextEditorStyleSheet.bind(this))); + observeTextEditorFontConfig () { + this.updateGlobalTextEditorStyleSheet() + this.subscriptions.add(this.config.onDidChange('editor.fontSize', this.updateGlobalTextEditorStyleSheet.bind(this))) + this.subscriptions.add(this.config.onDidChange('editor.fontFamily', this.updateGlobalTextEditorStyleSheet.bind(this))) + return this.subscriptions.add(this.config.onDidChange('editor.lineHeight', this.updateGlobalTextEditorStyleSheet.bind(this))) } - updateGlobalTextEditorStyleSheet() { + updateGlobalTextEditorStyleSheet () { const styleSheetSource = `\ atom-text-editor { - font-size: ${this.config.get('editor.fontSize')}px; - font-family: ${this.config.get('editor.fontFamily')}; - line-height: ${this.config.get('editor.lineHeight')}; + font-size: ${this.config.get('editor.fontSize')}px + font-family: ${this.config.get('editor.fontFamily')} + line-height: ${this.config.get('editor.lineHeight')} }\ -`; - this.styles.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles'}); - return this.views.performDocumentPoll(); +` + this.styles.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles'}) + return this.views.performDocumentPoll() } - initialize(model, {views, workspace, project, config, styles}) { - this.model = model; - this.views = views; - this.workspace = workspace; - this.project = project; - this.config = config; - this.styles = styles; - if (this.views == null) { throw new Error("Must pass a views parameter when initializing WorskpaceElements"); } - if (this.workspace == null) { throw new Error("Must pass a workspace parameter when initializing WorskpaceElements"); } - if (this.project == null) { throw new Error("Must pass a project parameter when initializing WorskpaceElements"); } - if (this.config == null) { throw new Error("Must pass a config parameter when initializing WorskpaceElements"); } - if (this.styles == null) { throw new Error("Must pass a styles parameter when initializing WorskpaceElements"); } + initialize (model, {views, workspace, project, config, styles}) { + this.model = model + this.views = views + this.workspace = workspace + this.project = project + this.config = config + this.styles = styles + if (this.views == null) { throw new Error('Must pass a views parameter when initializing WorskpaceElements') } + if (this.workspace == null) { throw new Error('Must pass a workspace parameter when initializing WorskpaceElements') } + if (this.project == null) { throw new Error('Must pass a project parameter when initializing WorskpaceElements') } + if (this.config == null) { throw new Error('Must pass a config parameter when initializing WorskpaceElements') } + if (this.styles == null) { throw new Error('Must pass a styles parameter when initializing WorskpaceElements') } - this.subscriptions = new CompositeDisposable; - this.initializeContent(); - this.observeScrollbarStyle(); - this.observeTextEditorFontConfig(); + this.subscriptions = new CompositeDisposable() + this.initializeContent() + this.observeScrollbarStyle() + this.observeTextEditorFontConfig() - this.paneContainer = this.views.getView(this.model.paneContainer); - this.verticalAxis.appendChild(this.paneContainer); - this.addEventListener('focus', this.handleFocus.bind(this)); + this.paneContainer = this.views.getView(this.model.paneContainer) + this.verticalAxis.appendChild(this.paneContainer) + this.addEventListener('focus', this.handleFocus.bind(this)) - this.addEventListener('mousewheel', this.handleMousewheel.bind(this), true); + this.addEventListener('mousewheel', this.handleMousewheel.bind(this), true) this.panelContainers = { top: this.views.getView(this.model.panelContainers.top), @@ -99,98 +101,98 @@ atom-text-editor { header: this.views.getView(this.model.panelContainers.header), footer: this.views.getView(this.model.panelContainers.footer), modal: this.views.getView(this.model.panelContainers.modal) - }; + } - this.horizontalAxis.insertBefore(this.panelContainers.left, this.verticalAxis); - this.horizontalAxis.appendChild(this.panelContainers.right); + this.horizontalAxis.insertBefore(this.panelContainers.left, this.verticalAxis) + this.horizontalAxis.appendChild(this.panelContainers.right) - this.verticalAxis.insertBefore(this.panelContainers.top, this.paneContainer); - this.verticalAxis.appendChild(this.panelContainers.bottom); + this.verticalAxis.insertBefore(this.panelContainers.top, this.paneContainer) + this.verticalAxis.appendChild(this.panelContainers.bottom) - this.insertBefore(this.panelContainers.header, this.horizontalAxis); - this.appendChild(this.panelContainers.footer); + this.insertBefore(this.panelContainers.header, this.horizontalAxis) + this.appendChild(this.panelContainers.footer) - this.appendChild(this.panelContainers.modal); + this.appendChild(this.panelContainers.modal) - return this; + return this } - getModel() { return this.model; } + getModel () { return this.model } - handleMousewheel(event) { + handleMousewheel (event) { if (event.ctrlKey && this.config.get('editor.zoomFontWhenCtrlScrolling') && (event.target.closest('atom-text-editor') != null)) { if (event.wheelDeltaY > 0) { - this.model.increaseFontSize(); + this.model.increaseFontSize() } else if (event.wheelDeltaY < 0) { - this.model.decreaseFontSize(); + this.model.decreaseFontSize() } - event.preventDefault(); - return event.stopPropagation(); + event.preventDefault() + return event.stopPropagation() } } - handleFocus(event) { - return this.model.getActivePane().activate(); + handleFocus (event) { + return this.model.getActivePane().activate() } - focusPaneViewAbove() { return this.paneContainer.focusPaneViewAbove(); } + focusPaneViewAbove () { return this.paneContainer.focusPaneViewAbove() } - focusPaneViewBelow() { return this.paneContainer.focusPaneViewBelow(); } + focusPaneViewBelow () { return this.paneContainer.focusPaneViewBelow() } - focusPaneViewOnLeft() { return this.paneContainer.focusPaneViewOnLeft(); } + focusPaneViewOnLeft () { return this.paneContainer.focusPaneViewOnLeft() } - focusPaneViewOnRight() { return this.paneContainer.focusPaneViewOnRight(); } + focusPaneViewOnRight () { return this.paneContainer.focusPaneViewOnRight() } - moveActiveItemToPaneAbove(params) { return this.paneContainer.moveActiveItemToPaneAbove(params); } + moveActiveItemToPaneAbove (params) { return this.paneContainer.moveActiveItemToPaneAbove(params) } - moveActiveItemToPaneBelow(params) { return this.paneContainer.moveActiveItemToPaneBelow(params); } + moveActiveItemToPaneBelow (params) { return this.paneContainer.moveActiveItemToPaneBelow(params) } - moveActiveItemToPaneOnLeft(params) { return this.paneContainer.moveActiveItemToPaneOnLeft(params); } + moveActiveItemToPaneOnLeft (params) { return this.paneContainer.moveActiveItemToPaneOnLeft(params) } - moveActiveItemToPaneOnRight(params) { return this.paneContainer.moveActiveItemToPaneOnRight(params); } + moveActiveItemToPaneOnRight (params) { return this.paneContainer.moveActiveItemToPaneOnRight(params) } - runPackageSpecs() { - let activePath, projectPath; - if (activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath())) { - [projectPath] = Array.from(this.project.relativizePath(activePath)); + runPackageSpecs () { + let activePath, projectPath + if ((activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath()))) { + [projectPath] = Array.from(this.project.relativizePath(activePath)) } else { - [projectPath] = Array.from(this.project.getPaths()); + [projectPath] = Array.from(this.project.getPaths()) } if (projectPath) { - let specPath = path.join(projectPath, 'spec'); - const testPath = path.join(projectPath, 'test'); + let specPath = path.join(projectPath, 'spec') + const testPath = path.join(projectPath, 'test') if (!fs.existsSync(specPath) && fs.existsSync(testPath)) { - specPath = testPath; + specPath = testPath } - return ipcRenderer.send('run-package-specs', specPath); + return ipcRenderer.send('run-package-specs', specPath) } } - runBenchmarks() { - let activePath, projectPath; - if (activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath())) { - [projectPath] = Array.from(this.project.relativizePath(activePath)); + runBenchmarks () { + let activePath, projectPath + if ((activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath()))) { + [projectPath] = Array.from(this.project.relativizePath(activePath)) } else { - [projectPath] = Array.from(this.project.getPaths()); + [projectPath] = Array.from(this.project.getPaths()) } if (projectPath) { - return ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')); + return ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) } } -}); +}) -module.exports = WorkspaceElement = document.registerElement('atom-workspace', {prototype: WorkspaceElement.prototype}); +module.exports = WorkspaceElement = document.registerElement('atom-workspace', {prototype: WorkspaceElement.prototype}) -function __initClass__(c) { - c.initClass(); - return c; +function __initClass__ (c) { + c.initClass() + return c } -function __guardMethod__(obj, methodName, transform) { +function __guardMethod__ (obj, methodName, transform) { if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { - return transform(obj, methodName); + return transform(obj, methodName) } else { - return undefined; + return undefined } -} \ No newline at end of file +} From 3017b28ef7bb4a411e0a3de4802cce2f8b197532 Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Tue, 7 Mar 2017 16:31:00 -0800 Subject: [PATCH 114/194] Convert workspace element to JS: Cleanup --- spec/workspace-element-spec.js | 84 +++++++++++++------------- src/workspace-element.js | 104 ++++++++++++++------------------- 2 files changed, 88 insertions(+), 100 deletions(-) diff --git a/spec/workspace-element-spec.js b/spec/workspace-element-spec.js index 2a7fc0cab..a7f6957a0 100644 --- a/spec/workspace-element-spec.js +++ b/spec/workspace-element-spec.js @@ -1,3 +1,5 @@ +'use strict' + /* global getComputedStyle, WheelEvent */ const {ipcRenderer} = require('electron') @@ -5,28 +7,28 @@ const path = require('path') const temp = require('temp').track() const {Disposable} = require('event-kit') -describe('WorkspaceElement', function () { - afterEach(() => temp.cleanupSync()) +describe('WorkspaceElement', () => { + afterEach(() => { temp.cleanupSync() }) - describe('when the workspace element is focused', () => - it('transfers focus to the active pane', function () { + describe('when the workspace element is focused', () => { + it('transfers focus to the active pane', () => { const workspaceElement = atom.views.getView(atom.workspace) jasmine.attachToDOM(workspaceElement) const activePaneElement = atom.views.getView(atom.workspace.getActivePane()) document.body.focus() expect(document.activeElement).not.toBe(activePaneElement) workspaceElement.focus() - return expect(document.activeElement).toBe(activePaneElement) + expect(document.activeElement).toBe(activePaneElement) }) - ) + }) - describe('the scrollbar visibility class', () => - it('has a class based on the style of the scrollbar', function () { - let observeCallback = null + describe('the scrollbar visibility class', () => { + it('has a class based on the style of the scrollbar', () => { + let observeCallback const scrollbarStyle = require('scrollbar-style') - spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake(function (cb) { + spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake(cb => { observeCallback = cb - return new Disposable(function () {}) + return new Disposable(() => {}) }) const workspaceElement = atom.views.getView(atom.workspace) @@ -34,33 +36,33 @@ describe('WorkspaceElement', function () { expect(workspaceElement.className).toMatch('scrollbars-visible-always') observeCallback('overlay') - return expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling') + expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling') }) - ) + }) - describe('editor font styling', function () { - let [editor, editorElement, workspaceElement] = Array.from([]) + describe('editor font styling', () => { + let editor, editorElement, workspaceElement - beforeEach(function () { + beforeEach(() => { waitsForPromise(() => atom.workspace.open('sample.js')) - return runs(function () { + runs(() => { workspaceElement = atom.views.getView(atom.workspace) jasmine.attachToDOM(workspaceElement) editor = atom.workspace.getActiveTextEditor() - return (editorElement = atom.views.getView(editor)) + editorElement = atom.views.getView(editor) }) }) - it("updates the font-size based on the 'editor.fontSize' config value", function () { + it("updates the font-size based on the 'editor.fontSize' config value", () => { const initialCharWidth = editor.getDefaultCharWidth() expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px') atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5) expect(getComputedStyle(editorElement).fontSize).toBe(atom.config.get('editor.fontSize') + 'px') - return expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth) + expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth) }) - it("updates the font-family based on the 'editor.fontFamily' config value", function () { + it("updates the font-family based on the 'editor.fontFamily' config value", () => { const initialCharWidth = editor.getDefaultCharWidth() let fontFamily = atom.config.get('editor.fontFamily') expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily) @@ -68,17 +70,17 @@ describe('WorkspaceElement', function () { atom.config.set('editor.fontFamily', 'sans-serif') fontFamily = atom.config.get('editor.fontFamily') expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily) - return expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth) + expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth) }) - it("updates the line-height based on the 'editor.lineHeight' config value", function () { + it("updates the line-height based on the 'editor.lineHeight' config value", () => { const initialLineHeight = editor.getLineHeightInPixels() atom.config.set('editor.lineHeight', '30px') expect(getComputedStyle(editorElement).lineHeight).toBe(atom.config.get('editor.lineHeight')) - return expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight) + expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight) }) - return it('increases or decreases the font size when a ctrl-mousewheel event occurs', function () { + it('increases or decreases the font size when a ctrl-mousewheel event occurs', () => { atom.config.set('editor.zoomFontWhenCtrlScrolling', true) atom.config.set('editor.fontSize', 12) @@ -114,12 +116,12 @@ describe('WorkspaceElement', function () { wheelDeltaY: 10, ctrlKey: true })) - return expect(atom.config.get('editor.fontSize')).toBe(12) + expect(atom.config.get('editor.fontSize')).toBe(12) }) }) - describe('panel containers', function () { - it('inserts panel container elements in the correct places in the DOM', function () { + describe('panel containers', () => { + it('inserts panel container elements in the correct places in the DOM', () => { const workspaceElement = atom.views.getView(atom.workspace) const leftContainer = workspaceElement.querySelector('atom-panel-container.left') @@ -138,10 +140,10 @@ describe('WorkspaceElement', function () { expect(footerContainer.previousSibling).toBe(workspaceElement.horizontalAxis) const modalContainer = workspaceElement.querySelector('atom-panel-container.modal') - return expect(modalContainer.parentNode).toBe(workspaceElement) + expect(modalContainer.parentNode).toBe(workspaceElement) }) - it('stretches header/footer panels to the workspace width', function () { + it('stretches header/footer panels to the workspace width', () => { const workspaceElement = atom.views.getView(atom.workspace) jasmine.attachToDOM(workspaceElement) expect(workspaceElement.offsetWidth).toBeGreaterThan(0) @@ -152,10 +154,10 @@ describe('WorkspaceElement', function () { const footerItem = document.createElement('div') atom.workspace.addFooterPanel({item: footerItem}) - return expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) + expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) }) - return it('shrinks horizontal axis according to header/footer panels height', function () { + it('shrinks horizontal axis according to header/footer panels height', () => { const workspaceElement = atom.views.getView(atom.workspace) workspaceElement.style.height = '100px' const horizontalAxisElement = workspaceElement.querySelector('atom-workspace-axis.horizontal') @@ -175,23 +177,23 @@ describe('WorkspaceElement', function () { atom.workspace.addFooterPanel({item: footerItem}) expect(footerItem.offsetHeight).toBeGreaterThan(0) - return expect(horizontalAxisElement.offsetHeight).toEqual(originalHorizontalAxisHeight - headerItem.offsetHeight - footerItem.offsetHeight) + expect(horizontalAxisElement.offsetHeight).toEqual(originalHorizontalAxisHeight - headerItem.offsetHeight - footerItem.offsetHeight) }) }) - describe("the 'window:toggle-invisibles' command", () => - it('shows/hides invisibles in all open and future editors', function () { + describe("the 'window:toggle-invisibles' command", () => { + it('shows/hides invisibles in all open and future editors', () => { const workspaceElement = atom.views.getView(atom.workspace) expect(atom.config.get('editor.showInvisibles')).toBe(false) atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles') expect(atom.config.get('editor.showInvisibles')).toBe(true) atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles') - return expect(atom.config.get('editor.showInvisibles')).toBe(false) + expect(atom.config.get('editor.showInvisibles')).toBe(false) }) - ) + }) - return describe("the 'window:run-package-specs' command", () => - it("runs the package specs for the active item's project path, or the first project path", function () { + describe("the 'window:run-package-specs' command", () => { + it("runs the package specs for the active item's project path, or the first project path", () => { const workspaceElement = atom.views.getView(atom.workspace) spyOn(ipcRenderer, 'send') @@ -224,7 +226,7 @@ describe('WorkspaceElement', function () { item.getPath = () => path.join(projectPaths[1], 'a-file.txt') atom.commands.dispatch(workspaceElement, 'window:run-package-specs') expect(ipcRenderer.send).toHaveBeenCalledWith('run-package-specs', path.join(projectPaths[1], 'spec')) - return ipcRenderer.send.reset() + ipcRenderer.send.reset() }) - ) + }) }) diff --git a/src/workspace-element.js b/src/workspace-element.js index 465c4be34..65333e7d3 100644 --- a/src/workspace-element.js +++ b/src/workspace-element.js @@ -1,24 +1,20 @@ +'use strict' + /* global HTMLElement */ -let WorkspaceElement const {ipcRenderer} = require('electron') const path = require('path') const fs = require('fs-plus') const {CompositeDisposable} = require('event-kit') const scrollbarStyle = require('scrollbar-style') -module.exports = -__initClass__(WorkspaceElement = class WorkspaceElement extends HTMLElement { - static initClass () { - this.prototype.globalTextEditorStyleSheet = null - } - +class WorkspaceElement extends HTMLElement { attachedCallback () { - return this.focus() + this.focus() } detachedCallback () { - return this.subscriptions.dispose() + this.subscriptions.dispose() } initializeContent () { @@ -32,41 +28,39 @@ __initClass__(WorkspaceElement = class WorkspaceElement extends HTMLElement { this.horizontalAxis.classList.add('horizontal') this.horizontalAxis.appendChild(this.verticalAxis) - return this.appendChild(this.horizontalAxis) + this.appendChild(this.horizontalAxis) } observeScrollbarStyle () { - return this.subscriptions.add(scrollbarStyle.observePreferredScrollbarStyle(style => { + this.subscriptions.add(scrollbarStyle.observePreferredScrollbarStyle(style => { switch (style) { case 'legacy': this.classList.remove('scrollbars-visible-when-scrolling') - return this.classList.add('scrollbars-visible-always') + this.classList.add('scrollbars-visible-always') + break case 'overlay': this.classList.remove('scrollbars-visible-always') - return this.classList.add('scrollbars-visible-when-scrolling') + this.classList.add('scrollbars-visible-when-scrolling') + break } - } - ) - ) + })) } observeTextEditorFontConfig () { this.updateGlobalTextEditorStyleSheet() this.subscriptions.add(this.config.onDidChange('editor.fontSize', this.updateGlobalTextEditorStyleSheet.bind(this))) this.subscriptions.add(this.config.onDidChange('editor.fontFamily', this.updateGlobalTextEditorStyleSheet.bind(this))) - return this.subscriptions.add(this.config.onDidChange('editor.lineHeight', this.updateGlobalTextEditorStyleSheet.bind(this))) + this.subscriptions.add(this.config.onDidChange('editor.lineHeight', this.updateGlobalTextEditorStyleSheet.bind(this))) } updateGlobalTextEditorStyleSheet () { - const styleSheetSource = `\ -atom-text-editor { - font-size: ${this.config.get('editor.fontSize')}px - font-family: ${this.config.get('editor.fontFamily')} - line-height: ${this.config.get('editor.lineHeight')} -}\ -` + const styleSheetSource = `atom-text-editor { + font-size: ${this.config.get('editor.fontSize')}px; + font-family: ${this.config.get('editor.fontFamily')}; + line-height: ${this.config.get('editor.lineHeight')}; +}` this.styles.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles'}) - return this.views.performDocumentPoll() + this.views.performDocumentPoll() } initialize (model, {views, workspace, project, config, styles}) { @@ -127,36 +121,38 @@ atom-text-editor { this.model.decreaseFontSize() } event.preventDefault() - return event.stopPropagation() + event.stopPropagation() } } handleFocus (event) { - return this.model.getActivePane().activate() + this.model.getActivePane().activate() } - focusPaneViewAbove () { return this.paneContainer.focusPaneViewAbove() } + focusPaneViewAbove () { this.paneContainer.focusPaneViewAbove() } - focusPaneViewBelow () { return this.paneContainer.focusPaneViewBelow() } + focusPaneViewBelow () { this.paneContainer.focusPaneViewBelow() } - focusPaneViewOnLeft () { return this.paneContainer.focusPaneViewOnLeft() } + focusPaneViewOnLeft () { this.paneContainer.focusPaneViewOnLeft() } - focusPaneViewOnRight () { return this.paneContainer.focusPaneViewOnRight() } + focusPaneViewOnRight () { this.paneContainer.focusPaneViewOnRight() } - moveActiveItemToPaneAbove (params) { return this.paneContainer.moveActiveItemToPaneAbove(params) } + moveActiveItemToPaneAbove (params) { this.paneContainer.moveActiveItemToPaneAbove(params) } - moveActiveItemToPaneBelow (params) { return this.paneContainer.moveActiveItemToPaneBelow(params) } + moveActiveItemToPaneBelow (params) { this.paneContainer.moveActiveItemToPaneBelow(params) } - moveActiveItemToPaneOnLeft (params) { return this.paneContainer.moveActiveItemToPaneOnLeft(params) } + moveActiveItemToPaneOnLeft (params) { this.paneContainer.moveActiveItemToPaneOnLeft(params) } - moveActiveItemToPaneOnRight (params) { return this.paneContainer.moveActiveItemToPaneOnRight(params) } + moveActiveItemToPaneOnRight (params) { this.paneContainer.moveActiveItemToPaneOnRight(params) } runPackageSpecs () { - let activePath, projectPath - if ((activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath()))) { - [projectPath] = Array.from(this.project.relativizePath(activePath)) + const activePaneItem = this.workspace.getActivePaneItem() + const activePath = activePaneItem && typeof activePaneItem.getPath === 'function' ? activePaneItem.getPath() : null + let projectPath + if (activePath != null) { + [projectPath] = this.project.relativizePath(activePath) } else { - [projectPath] = Array.from(this.project.getPaths()) + [projectPath] = this.project.getPaths() } if (projectPath) { let specPath = path.join(projectPath, 'spec') @@ -165,34 +161,24 @@ atom-text-editor { specPath = testPath } - return ipcRenderer.send('run-package-specs', specPath) + ipcRenderer.send('run-package-specs', specPath) } } runBenchmarks () { - let activePath, projectPath - if ((activePath = __guardMethod__(this.workspace.getActivePaneItem(), 'getPath', o => o.getPath()))) { - [projectPath] = Array.from(this.project.relativizePath(activePath)) + const activePaneItem = this.workspace.getActivePaneItem() + const activePath = activePaneItem && typeof activePaneItem.getPath === 'function' ? activePaneItem.getPath() : null + let projectPath + if (activePath) { + [projectPath] = this.project.relativizePath(activePath) } else { - [projectPath] = Array.from(this.project.getPaths()) + [projectPath] = this.project.getPaths() } if (projectPath) { - return ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) + ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) } } -}) - -module.exports = WorkspaceElement = document.registerElement('atom-workspace', {prototype: WorkspaceElement.prototype}) - -function __initClass__ (c) { - c.initClass() - return c -} -function __guardMethod__ (obj, methodName, transform) { - if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { - return transform(obj, methodName) - } else { - return undefined - } } + +module.exports = document.registerElement('atom-workspace', {prototype: WorkspaceElement.prototype}) From 5858a22fe4205e64c0180f9d0c4fea10a6423530 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Mar 2017 19:37:25 -0700 Subject: [PATCH 115/194] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8207d3307..0059bdbf6 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "dev-live-reload": "0.47.0", "encoding-selector": "0.23.2", "exception-reporting": "0.41.2", - "find-and-replace": "0.207.0", + "find-and-replace": "0.207.2", "fuzzy-finder": "1.5.0", "git-diff": "1.3.3", "go-to-line": "0.32.0", From 80dd64c14906fc425b8227bdf356f6af095d0388 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 7 Mar 2017 22:32:04 -0500 Subject: [PATCH 116/194] :arrow_up: language-sql@0.25.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0059bdbf6..841cfc1be 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "language-sass": "0.58.0", "language-shellscript": "0.25.0", "language-source": "0.9.0", - "language-sql": "0.25.3", + "language-sql": "0.25.4", "language-text": "0.7.2", "language-todo": "0.29.1", "language-toml": "0.18.1", From 414ad13b0977e6497319a855263bcb163de5e02b Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 8 Mar 2017 13:41:29 +0900 Subject: [PATCH 117/194] :arrow_up: atom-package-manager@v1.16.1 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 07931700d..e57d07aee 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.16.0" + "atom-package-manager": "1.16.1" } } From 43868bb939781037e33fa67922c8454b05189bcc Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 8 Mar 2017 15:36:41 +0900 Subject: [PATCH 118/194] Revert ":arrow_up: atom-package-manager@v1.16.1" This reverts commit 414ad13b0977e6497319a855263bcb163de5e02b. --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index e57d07aee..07931700d 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.16.1" + "atom-package-manager": "1.16.0" } } From 40071265cb70e5a76093e902c5449ac0c80a1eed Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 8 Mar 2017 22:27:09 +0900 Subject: [PATCH 119/194] :arrow_up: apm, take II --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 07931700d..e57d07aee 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.16.0" + "atom-package-manager": "1.16.1" } } From 982074061e675ab607186f3173c426eafa2a0035 Mon Sep 17 00:00:00 2001 From: Hubot Date: Wed, 8 Mar 2017 14:18:49 -0600 Subject: [PATCH 120/194] 1.17.0-dev --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 841cfc1be..96864fd81 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.16.0-dev", + "version": "1.17.0-dev", "description": "A hackable text editor for the 21st Century.", "main": "./src/main-process/main.js", "repository": { From 4a1f98cb6ccd59fc89399ebb45b2f8a0a1778727 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 8 Mar 2017 15:50:08 -0800 Subject: [PATCH 121/194] Create autoUpdateManager in AtomApplication constructor --- src/main-process/atom-application.coffee | 9 ++++++--- src/main-process/auto-update-manager.coffee | 5 ++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index 93e9e3395..d21ebae45 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -73,6 +73,11 @@ class AtomApplication @config.load() @fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, "recovery")) @storageFolder = new StorageFolder(process.env.ATOM_HOME) + @autoUpdateManager = new AutoUpdateManager( + @version, + options.test or options.benchmark or options.benchmarkTest, + @config + ) @disposable = new CompositeDisposable @handleEvents() @@ -92,9 +97,7 @@ class AtomApplication @config.onDidChange 'core.titleBar', @promptForRestart.bind(this) - @autoUpdateManager = new AutoUpdateManager( - @version, options.test or options.benchmark or options.benchmarkTest, @resourcePath, @config - ) + process.nextTick => @autoUpdateManager.initialize() @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) diff --git a/src/main-process/auto-update-manager.coffee b/src/main-process/auto-update-manager.coffee index ff29dd3d6..2ff2852cb 100644 --- a/src/main-process/auto-update-manager.coffee +++ b/src/main-process/auto-update-manager.coffee @@ -14,12 +14,11 @@ module.exports = class AutoUpdateManager Object.assign @prototype, EventEmitter.prototype - constructor: (@version, @testMode, resourcePath, @config) -> + constructor: (@version, @testMode, @config) -> @state = IdleState @iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') - process.nextTick => @setupAutoUpdater() - setupAutoUpdater: -> + initialize: -> if process.platform is 'win32' archSuffix = if process.arch is 'ia32' then '' else '-' + process.arch @feedUrl = "https://atom.io/api/updates#{archSuffix}?version=#{@version}" From 82c49001f4c3e043ae43575190eed0ae40263b9d Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 8 Mar 2017 17:21:58 -0800 Subject: [PATCH 122/194] :arrow_up: legal-eagle@0.14.0 --- script/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/package.json b/script/package.json index 87c43f261..89beaa492 100644 --- a/script/package.json +++ b/script/package.json @@ -13,7 +13,7 @@ "fs-extra": "0.30.0", "glob": "7.0.3", "joanna": "0.0.8", - "legal-eagle": "0.13.0", + "legal-eagle": "0.14.0", "lodash.template": "4.4.0", "minidump": "0.9.0", "mkdirp": "0.5.1", From ac7bb27e220a78b2876e7aff397274cf390bb699 Mon Sep 17 00:00:00 2001 From: Amy Troschinetz Date: Fri, 20 May 2016 17:52:14 -0500 Subject: [PATCH 123/194] Select the replaced text post replacement. --- spec/text-editor-spec.coffee | 12 +++++++++--- src/text-editor.coffee | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 81c69f63f..3cb771ec7 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4868,8 +4868,8 @@ describe "TextEditor", -> editor.replaceSelectedText {}, -> '123' expect(buffer.lineForRow(0)).toBe '123var quicksort = function () {' - editor.replaceSelectedText {selectWordIfEmpty: true}, -> 'var' editor.setCursorBufferPosition([0]) + editor.replaceSelectedText {selectWordIfEmpty: true}, -> 'var' expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {' editor.setCursorBufferPosition([10]) @@ -4882,6 +4882,12 @@ describe "TextEditor", -> editor.replaceSelectedText {}, -> 'ia' expect(buffer.lineForRow(0)).toBe 'via quicksort = function () {' + it "replaces the selected text and selects the replacement text", -> + editor.setSelectedBufferRange([[0, 4], [0, 9]]) + editor.replaceSelectedText {}, -> 'whatnot' + expect(buffer.lineForRow(0)).toBe 'var whatnotsort = function () {' + expect(editor.getSelectedBufferRange()).toEqual [[0, 4], [0, 11]] + describe ".transpose()", -> it "swaps two characters", -> editor.buffer.setText("abc") @@ -4902,7 +4908,7 @@ describe "TextEditor", -> editor.setCursorScreenPosition([0, 1]) editor.upperCase() expect(editor.lineTextForBufferRow(0)).toBe 'ABC' - expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]] + expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 3]] describe "when there is a selection", -> it "upper cases the current selection", -> @@ -4919,7 +4925,7 @@ describe "TextEditor", -> editor.setCursorScreenPosition([0, 1]) editor.lowerCase() expect(editor.lineTextForBufferRow(0)).toBe 'abc' - expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]] + expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 3]] describe "when there is a selection", -> it "lower cases the current selection", -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8095632fd..96f30ddf9 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1328,12 +1328,12 @@ class TextEditor extends Model replaceSelectedText: (options={}, fn) -> {selectWordIfEmpty} = options @mutateSelectedText (selection) -> - range = selection.getBufferRange() + selection.getBufferRange() if selectWordIfEmpty and selection.isEmpty() selection.selectWord() text = selection.getText() selection.deleteSelectedText() - selection.insertText(fn(text)) + range = selection.insertText(fn(text)) selection.setBufferRange(range) # Split multi-line selections into one selection per line. From 4b3e2ccca4e08ae41f23f9f7dd31305ffd7105de Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Thu, 9 Mar 2017 15:14:06 -0800 Subject: [PATCH 124/194] Remove the buildTextEditor binding The comment was actually no longer accurate since arrow functions aren't newable. After doing an audit, @maxbrunsfeld determined that it could just be removed. --- src/workspace.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/workspace.js b/src/workspace.js index f411cf823..8ff8aa51d 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -57,12 +57,6 @@ module.exports = class Workspace extends Model { this.defaultDirectorySearcher = new DefaultDirectorySearcher() this.consumeServices(this.packageManager) - // One cannot simply .bind here since it could be used as a component with - // Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always - // the newly created object. - const realThis = this - this.buildTextEditor = (params) => Workspace.prototype.buildTextEditor.call(realThis, params) - this.panelContainers = { top: new PanelContainer({location: 'top'}), left: new PanelContainer({location: 'left'}), From f92e6da88dd350382838d6a06e0fa56a85428ad8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Mar 2017 20:36:06 -0700 Subject: [PATCH 125/194] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96864fd81..ee422861f 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "10.4.2", + "text-buffer": "10.5.0", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From e85ed1dc7f6a87e90f1e075e745b37540613e37d Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Thu, 9 Mar 2017 14:25:16 -0800 Subject: [PATCH 126/194] Refactor open; separate creation and placement of items This is a refactoring that sets up the work for #13878 (docks). Because items will be able to dictate their preferred locations, we need to detangle the item creation from their placement. --- src/workspace.js | 108 +++++++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/src/workspace.js b/src/workspace.js index 8ff8aa51d..a386b5ff7 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -511,9 +511,8 @@ module.exports = class Workspace extends Model { // // Returns a {Promise} that resolves to the {TextEditor} for the file URI. open (uri_, options = {}) { - const { searchAllPanes } = options - const { split } = options const uri = this.project.resolvePath(uri_) + const {searchAllPanes, split} = options if (!atom.config.get('core.allowPendingPaneItems')) { options.pending = false @@ -526,7 +525,7 @@ module.exports = class Workspace extends Model { } let pane - if (searchAllPanes) { pane = this.paneContainer.paneForURI(uri) } + if (searchAllPanes) { pane = this.paneForURI(uri) } if (pane == null) { switch (split) { case 'left': @@ -547,7 +546,16 @@ module.exports = class Workspace extends Model { } } - return this.openURIInPane(uri, pane, options) + let item + if (uri != null) { + item = pane.itemForURI(uri) + } + if (item == null) { + item = this.createItemForURI(uri, options) + } + + return Promise.resolve(item) + .then(item => this.openItem(item, Object.assign({pane, uri}, options))) } // Open Atom's license in the active pane. @@ -597,26 +605,28 @@ module.exports = class Workspace extends Model { } openURIInPane (uri, pane, options = {}) { - const activatePane = options.activatePane != null ? options.activatePane : true - const activateItem = options.activateItem != null ? options.activateItem : true - let item if (uri != null) { item = pane.itemForURI(uri) - if (item == null) { - for (let opener of this.getOpeners()) { - item = opener(uri, options) - if (item != null) break - } - } else if (!options.pending && (pane.getPendingItem() === item)) { - pane.clearPendingItem() + } + if (item == null) { + item = this.createItemForURI(uri, options) + } + return Promise.resolve(item) + .then(item => this.openItem(item, Object.assign({pane, uri}, options))) + } + + // Returns a {Promise} that resolves to the {TextEditor} (or other item) for the given URI. + createItemForURI (uri, options) { + if (uri != null) { + for (let opener of this.getOpeners()) { + const item = opener(uri, options) + if (item != null) return Promise.resolve(item) } } try { - if (item == null) { - item = this.openTextFile(uri, options) - } + return this.openTextFile(uri, options) } catch (error) { switch (error.code) { case 'CANCELLED': @@ -644,40 +654,46 @@ module.exports = class Workspace extends Model { throw error } } + } - return Promise.resolve(item) - .then(item => { - let initialColumn - if (pane.isDestroyed()) { - return item - } + openItem (item, options = {}) { + const {pane} = options - this.itemOpened(item) - if (activateItem) { - pane.activateItem(item, {pending: options.pending}) - } - if (activatePane) { - pane.activate() - } + if (item == null) return undefined + if (pane.isDestroyed()) return item - let initialLine = initialColumn = 0 - if (!Number.isNaN(options.initialLine)) { - initialLine = options.initialLine - } - if (!Number.isNaN(options.initialColumn)) { - initialColumn = options.initialColumn - } - if ((initialLine >= 0) || (initialColumn >= 0)) { - if (typeof item.setCursorBufferPosition === 'function') { - item.setCursorBufferPosition([initialLine, initialColumn]) - } - } + if (!options.pending && (pane.getPendingItem() === item)) { + pane.clearPendingItem() + } - const index = pane.getActiveItemIndex() - this.emitter.emit('did-open', {uri, pane, item, index}) - return item + const activatePane = options.activatePane != null ? options.activatePane : true + const activateItem = options.activateItem != null ? options.activateItem : true + this.itemOpened(item) + if (activateItem) { + pane.activateItem(item, {pending: options.pending}) + } + if (activatePane) { + pane.activate() + } + + let initialColumn = 0 + let initialLine = 0 + if (!Number.isNaN(options.initialLine)) { + initialLine = options.initialLine + } + if (!Number.isNaN(options.initialColumn)) { + initialColumn = options.initialColumn + } + if ((initialLine >= 0) || (initialColumn >= 0)) { + if (typeof item.setCursorBufferPosition === 'function') { + item.setCursorBufferPosition([initialLine, initialColumn]) } - ) + } + + const index = pane.getActiveItemIndex() + const uri = options.uri == null && typeof item.getURI === 'function' ? item.getURI() : options.uri + this.emitter.emit('did-open', {uri, pane, item, index}) + return item } openTextFile (uri, options) { From 33f96bf6cb1723024dcf02f802e147f635364979 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 09:01:10 +0100 Subject: [PATCH 127/194] Fix workspace-spec.js --- spec/workspace-spec.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 5279092c0..270f81526 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1347,9 +1347,7 @@ i = /test/; #FIXME\ matchText: 'aaa', lineText: 'aaa bbb', lineTextOffset: 0, - range: [[0, 0], [0, 3]], - linesAfter: [], - linesBefore: [] + range: [[0, 0], [0, 3]] }) }) }) @@ -1367,9 +1365,7 @@ i = /test/; #FIXME\ matchText: '$bill', lineText: 'dollar$bill', lineTextOffset: 0, - range: [[2, 6], [2, 11]], - linesAfter: [], - linesBefore: [] + range: [[2, 6], [2, 11]] }) }) }) From e0d65bbcae2461c1fd1c8df6f7eb110e666fade9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 09:04:49 +0100 Subject: [PATCH 128/194] :arrow_up: apm --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index e57d07aee..5e3dcb1e0 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.16.1" + "atom-package-manager": "1.17.0" } } From 6aacd3d76e314d6ebc0780644843bbddd5cbc851 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 09:25:20 +0100 Subject: [PATCH 129/194] Copy atom.png from the appropriate location on Linux --- script/lib/create-debian-package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/create-debian-package.js b/script/lib/create-debian-package.js index 03196816d..fd3e07b92 100644 --- a/script/lib/create-debian-package.js +++ b/script/lib/create-debian-package.js @@ -92,7 +92,7 @@ module.exports = function (packagedAppPath) { console.log(`Copying icon into "${debianPackageIconsDirPath}"`) fs.copySync( - path.join(packagedAppPath, 'resources', 'app.asar.unpacked', 'resources', 'atom.png'), + path.join(packagedAppPath, 'resources', 'app', 'resources', 'atom.png'), path.join(debianPackageIconsDirPath, `${atomExecutableName}.png`) ) From 5f9b62c603756684c3620e6416506fdc8a5a1fd1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 09:31:59 +0100 Subject: [PATCH 130/194] :green_heart: --- spec/workspace-spec.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 270f81526..43e4fbae4 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1347,7 +1347,9 @@ i = /test/; #FIXME\ matchText: 'aaa', lineText: 'aaa bbb', lineTextOffset: 0, - range: [[0, 0], [0, 3]] + range: [[0, 0], [0, 3]], + leadingContextLines: [], + trailingContextLines: [] }) }) }) @@ -1365,7 +1367,9 @@ i = /test/; #FIXME\ matchText: '$bill', lineText: 'dollar$bill', lineTextOffset: 0, - range: [[2, 6], [2, 11]] + range: [[2, 6], [2, 11]], + leadingContextLines: [], + trailingContextLines: [] }) }) }) From 6ac01d278137b2d50127decb5889e9bb466d6d00 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 09:39:14 +0100 Subject: [PATCH 131/194] Cache snapshot cache on CIs --- .travis.yml | 1 + appveyor.yml | 1 + circle.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 06416e8e0..6c3f25c49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ cache: - apm/node_modules - script/node_modules - ~/.atom/compile-cache + - ~/.atom/snapshot-cache notifications: email: diff --git a/appveyor.yml b/appveyor.yml index 3d8c0b274..e94e75441 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -52,3 +52,4 @@ cache: - '%APPVEYOR_BUILD_FOLDER%\electron' - '%USERPROFILE%\.atom\.apm' - '%USERPROFILE%\.atom\compile-cache' + - '%USERPROFILE%\.atom\snapshot-cache' diff --git a/circle.yml b/circle.yml index c264754d4..5890c45e9 100644 --- a/circle.yml +++ b/circle.yml @@ -29,6 +29,7 @@ dependencies: - script/node_modules - node_modules - ~/.atom/compile-cache + - ~/.atom/snapshot-cache test: override: From 3d40f55747fd905a61393d2630377e7eead4fd69 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 10:00:09 +0100 Subject: [PATCH 132/194] Fix lint errors --- src/main-process/main.js | 2 +- src/native-compile-cache.js | 1 - static/index.js | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main-process/main.js b/src/main-process/main.js index 0614f19d2..d63de0677 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -1,5 +1,5 @@ if (typeof snapshotResult !== 'undefined') { - snapshotResult.setGlobals(global, process, global, {}, require) + snapshotResult.setGlobals(global, process, global, {}, require) // eslint-disable-line no-undef } const startTime = Date.now() diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 6a9f69053..b1867ff55 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -36,7 +36,6 @@ class NativeCompileCache { overrideModuleCompile () { let self = this - let resolvedArgv = null // Here we override Node's module.js // (https://github.com/atom/node/blob/atom/lib/module.js#L378), changing // only the bits that affect compilation in order to use the cached one. diff --git a/static/index.js b/static/index.js index 70ef65d91..f7d2d4c2f 100644 --- a/static/index.js +++ b/static/index.js @@ -39,21 +39,21 @@ Module.prototype.require = function (module) { const absoluteFilePath = Module._resolveFilename(module, this, false) const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) - let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] + let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] // eslint-disable-line no-undef if (!cachedModule) { cachedModule = {exports: Module._load(module, this, false)} - snapshotResult.customRequire.cache[relativeFilePath] = cachedModule + snapshotResult.customRequire.cache[relativeFilePath] = cachedModule // eslint-disable-line no-undef } return cachedModule.exports } - snapshotResult.setGlobals(global, process, window, document, require) + snapshotResult.setGlobals(global, process, window, document, require) // eslint-disable-line no-undef } - const FileSystemBlobStore = useSnapshot ? snapshotResult.customRequire('../src/file-system-blob-store.js') : require('../src/file-system-blob-store') + const FileSystemBlobStore = useSnapshot ? snapshotResult.customRequire('../src/file-system-blob-store.js') : require('../src/file-system-blob-store') // eslint-disable-line no-undef blobStore = FileSystemBlobStore.load(path.join(process.env.ATOM_HOME, 'blob-store')) - const NativeCompileCache = useSnapshot ? snapshotResult.customRequire('../src/native-compile-cache.js') : require('../src/native-compile-cache') + const NativeCompileCache = useSnapshot ? snapshotResult.customRequire('../src/native-compile-cache.js') : require('../src/native-compile-cache') // eslint-disable-line no-undef NativeCompileCache.setCacheStore(blobStore) NativeCompileCache.setV8Version(process.versions.v8) NativeCompileCache.install() @@ -85,21 +85,21 @@ } function setupWindow () { - const CompileCache = useSnapshot ? snapshotResult.customRequire('../src/compile-cache.js') : require('../src/compile-cache') + const CompileCache = useSnapshot ? snapshotResult.customRequire('../src/compile-cache.js') : require('../src/compile-cache') // eslint-disable-line no-undef CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) CompileCache.install(require) - const ModuleCache = useSnapshot ? snapshotResult.customRequire('../src/module-cache.js') : require('../src/module-cache') + const ModuleCache = useSnapshot ? snapshotResult.customRequire('../src/module-cache.js') : require('../src/module-cache') // eslint-disable-line no-undef ModuleCache.register(getWindowLoadSettings()) - const startCrashReporter = useSnapshot ? snapshotResult.customRequire('../src/crash-reporter-start.js') : require('../src/crash-reporter-start') + const startCrashReporter = useSnapshot ? snapshotResult.customRequire('../src/crash-reporter-start.js') : require('../src/crash-reporter-start') // eslint-disable-line no-undef startCrashReporter({_version: getWindowLoadSettings().appVersion}) - const CSON = useSnapshot ? snapshotResult.customRequire('../node_modules/season/lib/cson.js') : require('season') + const CSON = useSnapshot ? snapshotResult.customRequire('../node_modules/season/lib/cson.js') : require('season') // eslint-disable-line no-undef CSON.setCacheDir(path.join(CompileCache.getCacheDirectory(), 'cson')) const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) - const initialize = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) + const initialize = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) // eslint-disable-line no-undef return initialize({blobStore: blobStore}).then(function () { electron.ipcRenderer.send('window-command', 'window:loaded') }) From 1d774ea20a47f8531effa218cf216e1e9b599e70 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 11:18:21 +0100 Subject: [PATCH 133/194] :arrow_up: electron-link for Windows support --- script/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/package.json b/script/package.json index dd31befc7..4de509dca 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.10", + "electron-link": "0.0.13", "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", From bfac08ab3f9a826b82ae57a5b34ee817a0f84162 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 12:02:41 +0100 Subject: [PATCH 134/194] :arrow_up: electron-link to handle backslashes on Windows correctly --- script/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/package.json b/script/package.json index 4de509dca..b87e794ea 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.13", + "electron-link": "0.0.17", "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", From 7e761d39afc75f171887ced38947ea8238466fe0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 14:37:37 +0100 Subject: [PATCH 135/194] Fall back to local storage when no history can be found --- spec/history-manager-spec.js | 6 +++--- src/atom-environment.coffee | 2 +- src/history-manager.js | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index bc77cb9b8..ed6aac626 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -30,7 +30,7 @@ describe("HistoryManager", () => { return projectDisposable }) - historyManager = new HistoryManager({stateStore, project, commands: commandRegistry}) + historyManager = new HistoryManager({stateStore, localStorage: window.localStorage, project, commands: commandRegistry}) await historyManager.loadState() }) @@ -75,7 +75,7 @@ describe("HistoryManager", () => { it("saves the state", async () => { await historyManager.clearProjects() - const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry}) + const historyManager2 = new HistoryManager({stateStore, localStorage: window.localStorage, project, commands: commandRegistry}) await historyManager2.loadState() expect(historyManager.getProjects().length).toBe(0) }) @@ -186,7 +186,7 @@ describe("HistoryManager", () => { it("saves the state", async () => { await historyManager.addProject(["/save/state"]) await historyManager.saveState() - const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry}) + const historyManager2 = new HistoryManager({stateStore, localStorage: window.localStorage, project, commands: commandRegistry}) await historyManager2.loadState() expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state']) }) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index cf5df00d2..6d9b3333f 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -229,7 +229,7 @@ class AtomEnvironment extends Model @observeAutoHideMenuBar() - @history = new HistoryManager({@project, @commands, @stateStore}) + @history = new HistoryManager({@project, @commands, @stateStore, localStorage: window.localStorage}) # Keep instances of HistoryManager in sync @disposables.add @history.onDidChangeProjects (e) => @applicationDelegate.didChangeHistoryManager() unless e.reloaded diff --git a/src/history-manager.js b/src/history-manager.js index 5087c3bf9..084bdc386 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -8,8 +8,9 @@ import {Emitter, CompositeDisposable} from 'event-kit' // // The project history is used to enable the 'Reopen Project' menu. export class HistoryManager { - constructor ({stateStore, project, commands}) { + constructor ({stateStore, localStorage, project, commands}) { this.stateStore = stateStore + this.localStorage = localStorage this.emitter = new Emitter() this.projects = [] this.disposables = new CompositeDisposable() @@ -93,7 +94,11 @@ export class HistoryManager { } async loadState () { - const history = await this.stateStore.load('history-manager') + let history = await this.stateStore.load('history-manager') + if (!history) { + history = JSON.parse(this.localStorage.getItem('history')) + } + if (history && history.projects) { this.projects = history.projects.filter(p => Array.isArray(p.paths) && p.paths.length > 0).map(p => new HistoryProject(p.paths, new Date(p.lastOpened))) this.didChangeProjects({reloaded: true}) From 337ad58434a6a3d20ffd2d261e49e3c0abce7409 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 14:37:49 +0100 Subject: [PATCH 136/194] :fire: HistoryManager.prototype.importProjectHistory --- src/history-manager.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/history-manager.js b/src/history-manager.js index 084bdc386..82ccd781b 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -111,12 +111,6 @@ export class HistoryManager { const projects = this.projects.map(p => ({paths: p.paths, lastOpened: p.lastOpened})) await this.stateStore.save('history-manager', {projects}) } - - async importProjectHistory () { - for (let project of await HistoryImporter.getAllProjects()) { - await this.addProject(project.paths, project.lastOpened) - } - } } function arrayEquivalent (a, b) { From 79e6a6ff3ca7293d2c7605c68c39773264960ed7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 14:45:34 +0100 Subject: [PATCH 137/194] :fire: HistoryImporter --- src/history-manager.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/history-manager.js b/src/history-manager.js index 82ccd781b..e3dd46653 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -133,32 +133,3 @@ export class HistoryProject { set lastOpened (lastOpened) { this._lastOpened = lastOpened } get lastOpened () { return this._lastOpened } } - -class HistoryImporter { - static async getStateStoreCursor () { - const db = await atom.stateStore.dbPromise - const store = db.transaction(['states']).objectStore('states') - return store.openCursor() - } - - static async getAllProjects (stateStore) { - const request = await HistoryImporter.getStateStoreCursor() - return new Promise((resolve, reject) => { - const rows = [] - request.onerror = reject - request.onsuccess = event => { - const cursor = event.target.result - if (cursor) { - let project = cursor.value.value.project - let storedAt = cursor.value.storedAt - if (project && project.paths && storedAt) { - rows.push(new HistoryProject(project.paths, new Date(Date.parse(storedAt)))) - } - cursor.continue() - } else { - resolve(rows) - } - } - }) - } -} From b228d2de4f5644a10647afd49a336b4510d6ec06 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 15:04:13 +0100 Subject: [PATCH 138/194] :arrow_up: electron-link --- script/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/package.json b/script/package.json index b87e794ea..9441213ac 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.17", + "electron-link": "0.0.18", "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", From 1e040c62d99590588bae7f61c048496506303530 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 16:08:02 +0100 Subject: [PATCH 139/194] Add `AtomEnvironment.prototype.resolveProxy(url)` This new private API will allow settings-view to communicate asynchronously with the main process to resolve proxies, instead of using `remote` and blocking the renderer process during startup. --- src/application-delegate.coffee | 11 +++++++++++ src/atom-environment.coffee | 11 +++++++++++ src/main-process/atom-application.coffee | 3 +++ 3 files changed, 25 insertions(+) diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee index b247fe7c2..e69efa458 100644 --- a/src/application-delegate.coffee +++ b/src/application-delegate.coffee @@ -277,3 +277,14 @@ class ApplicationDelegate emitDidSavePath: (path) -> ipcRenderer.sendSync('did-save-path', path) + + resolveProxy: (requestId, url) -> + ipcRenderer.send('resolve-proxy', requestId, url) + + onDidResolveProxy: (callback) -> + outerCallback = (event, requestId, proxy) -> + callback(requestId, proxy) + + ipcRenderer.on('did-resolve-proxy', outerCallback) + new Disposable -> + ipcRenderer.removeListener('did-resolve-proxy', outerCallback) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 6d9b3333f..3fe5ba96c 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -133,6 +133,7 @@ class AtomEnvironment extends Model constructor: (params={}) -> {@blobStore, @applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + @nextProxyRequestId = 0 @unloaded = false @loadTime = null {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() @@ -993,6 +994,16 @@ class AtomEnvironment extends Model return + resolveProxy: (url) -> + return new Promise (resolve, reject) => + requestId = @nextProxyRequestId++ + disposable = @applicationDelegate.onDidResolveProxy (id, proxy) => + if id is requestId + disposable.dispose() + resolve(proxy) + + @applicationDelegate.resolveProxy(requestId, url) + # Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner. Promise.prototype.done = (callback) -> deprecate("Atom now uses ES6 Promises instead of Q. Call promise.then instead of promise.done") diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index 0e364393a..e6f40379d 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -283,6 +283,9 @@ class AtomApplication @disposable.add ipcHelpers.on ipcMain, 'restart-application', => @restart() + @disposable.add ipcHelpers.on ipcMain, 'resolve-proxy', (event, requestId, url) -> + event.sender.session.resolveProxy url, (proxy) -> event.sender.send('did-resolve-proxy', requestId, proxy) + @disposable.add ipcHelpers.on ipcMain, 'did-change-history-manager', (event) => for atomWindow in @windows webContents = atomWindow.browserWindow.webContents From 69ea645b8579b1ea5e483bdc089f7e080153b480 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 16:31:20 +0100 Subject: [PATCH 140/194] Fix lint errors --- src/atom-environment.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 3fe5ba96c..e45e11a10 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -997,7 +997,7 @@ class AtomEnvironment extends Model resolveProxy: (url) -> return new Promise (resolve, reject) => requestId = @nextProxyRequestId++ - disposable = @applicationDelegate.onDidResolveProxy (id, proxy) => + disposable = @applicationDelegate.onDidResolveProxy (id, proxy) -> if id is requestId disposable.dispose() resolve(proxy) From c27ee0286c396fec985e588ac84366f72e176852 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 16:47:06 +0100 Subject: [PATCH 141/194] :arrow_up: settings-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d85374b6f..46f5dc165 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "notifications": "0.67.1", "open-on-github": "1.2.1", "package-generator": "1.1.1", - "settings-view": "0.248.2", + "settings-view": "0.249.0", "snippets": "1.1.3", "spell-check": "0.71.1", "status-bar": "1.8.4", From b128b5de485d9e4b62c008b3eab1f74a5230b559 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 7 Mar 2017 17:56:36 +0100 Subject: [PATCH 142/194] Temporarily add faster script to build Atom --- script/lib/fast-clean-output-directory.js | 19 +++++++++ script/lib/fast-copy-assets.js | 31 ++++++++++++++ script/lib/fast-transpile-babel-paths.js | 36 ++++++++++++++++ .../lib/fast-transpile-coffee-script-paths.js | 28 +++++++++++++ script/tdd | 42 +++++++++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 script/lib/fast-clean-output-directory.js create mode 100644 script/lib/fast-copy-assets.js create mode 100644 script/lib/fast-transpile-babel-paths.js create mode 100644 script/lib/fast-transpile-coffee-script-paths.js create mode 100755 script/tdd diff --git a/script/lib/fast-clean-output-directory.js b/script/lib/fast-clean-output-directory.js new file mode 100644 index 000000000..b1053eba2 --- /dev/null +++ b/script/lib/fast-clean-output-directory.js @@ -0,0 +1,19 @@ +const fs = require('fs-extra') +const path = require('path') +const CONFIG = require('../config') + +module.exports = function () { + let srcPaths = [ + path.join('benchmarks', 'benchmark-runner.js'), + path.join('dot-atom'), + path.join('exports'), + path.join('package.json'), + path.join('static'), + path.join('src'), + path.join('vendor') + ] + + for (const srcPath of srcPaths) { + fs.removeSync(path.join(CONFIG.intermediateAppPath, srcPath)) + } +} diff --git a/script/lib/fast-copy-assets.js b/script/lib/fast-copy-assets.js new file mode 100644 index 000000000..1602a7af8 --- /dev/null +++ b/script/lib/fast-copy-assets.js @@ -0,0 +1,31 @@ +// This module exports a function that copies all the static assets into the +// appropriate location in the build output directory. + +'use strict' + +const path = require('path') +const fs = require('fs-extra') +const CONFIG = require('../config') +const glob = require('glob') +const includePathInPackagedApp = require('./include-path-in-packaged-app') + +module.exports = function () { + console.log(`Copying assets to ${CONFIG.intermediateAppPath}`); + let srcPaths = [ + path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'), + path.join(CONFIG.repositoryRootPath, 'dot-atom'), + path.join(CONFIG.repositoryRootPath, 'exports'), + path.join(CONFIG.repositoryRootPath, 'package.json'), + path.join(CONFIG.repositoryRootPath, 'static'), + path.join(CONFIG.repositoryRootPath, 'src'), + path.join(CONFIG.repositoryRootPath, 'vendor') + ] + for (let srcPath of srcPaths) { + fs.copySync(srcPath, computeDestinationPath(srcPath), {filter: includePathInPackagedApp}) + } +} + +function computeDestinationPath (srcPath) { + const relativePath = path.relative(CONFIG.repositoryRootPath, srcPath) + return path.join(CONFIG.intermediateAppPath, relativePath) +} diff --git a/script/lib/fast-transpile-babel-paths.js b/script/lib/fast-transpile-babel-paths.js new file mode 100644 index 000000000..a1901e7a0 --- /dev/null +++ b/script/lib/fast-transpile-babel-paths.js @@ -0,0 +1,36 @@ +'use strict' + +const CompileCache = require('../../src/compile-cache') +const fs = require('fs') +const glob = require('glob') +const path = require('path') + +const CONFIG = require('../config') +const BABEL_OPTIONS = require('../../static/babelrc.json') +const BABEL_PREFIXES = [ + "'use babel'", + '"use babel"', + '/** @babel */', + '/* @flow */' +] +const PREFIX_LENGTH = Math.max.apply(null, BABEL_PREFIXES.map(prefix => prefix.length)) +const BUFFER = Buffer(PREFIX_LENGTH) + +module.exports = function () { + console.log(`Transpiling Babel paths in ${CONFIG.intermediateAppPath}`) + for (let path of getPathsToTranspile()) { + transpileBabelPath(path) + } +} + +function getPathsToTranspile () { + let paths = [] + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) + return paths +} + +function transpileBabelPath (path) { + fs.writeFileSync(path, CompileCache.addPathToCache(path, CONFIG.atomHomeDirPath)) +} diff --git a/script/lib/fast-transpile-coffee-script-paths.js b/script/lib/fast-transpile-coffee-script-paths.js new file mode 100644 index 000000000..7e080691f --- /dev/null +++ b/script/lib/fast-transpile-coffee-script-paths.js @@ -0,0 +1,28 @@ +'use strict' + +const CompileCache = require('../../src/compile-cache') +const fs = require('fs') +const glob = require('glob') +const path = require('path') + +const CONFIG = require('../config') + +module.exports = function () { + console.log(`Transpiling CoffeeScript paths in ${CONFIG.intermediateAppPath}`) + for (let path of getPathsToTranspile()) { + transpileCoffeeScriptPath(path) + } +} + +function getPathsToTranspile () { + let paths = [] + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.coffee'))) + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'spec', '*.coffee'))) + return paths +} + +function transpileCoffeeScriptPath (coffeePath) { + const jsPath = coffeePath.replace(/coffee$/g, 'js') + fs.writeFileSync(jsPath, CompileCache.addPathToCache(coffeePath, CONFIG.atomHomeDirPath)) + fs.unlinkSync(coffeePath) +} diff --git a/script/tdd b/script/tdd new file mode 100755 index 000000000..db78ef3e4 --- /dev/null +++ b/script/tdd @@ -0,0 +1,42 @@ +#!/usr/bin/env node + +'use strict' + +// Needed so we can require src/module-cache.coffee during generateModuleCache +require('coffee-script/register') +require('colors') + +const yargs = require('yargs') +const argv = yargs + .usage('Usage: $0 [options]') + .help('help') + .describe('code-sign', 'Code-sign executables (macOS and Windows only)') + .describe('create-windows-installer', 'Create installer (Windows only)') + .describe('create-debian-package', 'Create .deb package (Linux only)') + .describe('create-rpm-package', 'Create .rpm package (Linux only)') + .describe('compress-artifacts', 'Compress Atom binaries (and symbols on macOS)') + .describe('install', 'Install Atom') + .wrap(yargs.terminalWidth()) + .argv + +const cleanOutputDirectory = require('./lib/fast-clean-output-directory') +const copyAssets = require('./lib/fast-copy-assets') +const generateMetadata = require('./lib/generate-metadata') +const generateModuleCache = require('./lib/generate-module-cache') +const generateStartupSnapshot = require('./lib/generate-startup-snapshot') +const packageApplication = require('./lib/package-application') +const transpileBabelPaths = require('./lib/fast-transpile-babel-paths') +const transpileCoffeeScriptPaths = require('./lib/fast-transpile-coffee-script-paths') + +process.on('unhandledRejection', function (e) { + console.error(e.stack || e) + process.exit(1) +}) + +cleanOutputDirectory() +copyAssets() +transpileBabelPaths() +transpileCoffeeScriptPaths() +generateModuleCache() +generateMetadata() +packageApplication().then(generateStartupSnapshot) From 44e3573fcba4832c75eafd52426a35f06c7bf337 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 7 Mar 2017 18:07:48 +0100 Subject: [PATCH 143/194] Start moving AtomEnvironment instantiation bits inside the snapshot --- src/atom-environment.coffee | 22 ++++++++++++---------- src/config.coffee | 6 ++++-- src/initialize-application-window.coffee | 18 +++++++++++------- src/view-registry.coffee | 12 +++++++++--- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 7594f5de9..04f0a23e8 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -131,15 +131,22 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@applicationDelegate, @window, @document, @blobStore, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@applicationDelegate, @clipboard, @enablePersistence, onlyLoadBaseStyleSheets} = params @nextProxyRequestId = 0 @unloaded = false @loadTime = null - {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() - @emitter = new Emitter @disposables = new CompositeDisposable + @deserializers = new DeserializerManager(this) + @deserializeTimings = {} + @views = new ViewRegistry(this) + @notifications = new NotificationManager + @config = new Config({notificationManager: @notifications, @enablePersistence}) + + initialize: (params={}) -> + {@applicationDelegate, @window, @document, @blobStore, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() @stateStore = new StateStore('AtomEnvironments', 1) @@ -147,14 +154,9 @@ class AtomEnvironment extends Model @getStorageFolder().clear() @stateStore.clear() - @deserializers = new DeserializerManager(this) - @deserializeTimings = {} + @views.initialize() - @views = new ViewRegistry(this) - - @notifications = new NotificationManager - - @config = new Config({@configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence}) + @config.initialize({@configDirPath, resourcePath}) @setConfigSchema() @keymaps = new KeymapManager({@configDirPath, resourcePath, notificationManager: @notifications}) diff --git a/src/config.coffee b/src/config.coffee index e7d05da6d..575e95a72 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -398,11 +398,13 @@ class Config value # Created during initialization, available as `atom.config` - constructor: ({@configDirPath, @resourcePath, @notificationManager, @enablePersistence}={}) -> + constructor: ({@notificationManager, @enablePersistence}={}) -> + @clear() + + initialize: ({@configDirPath, @resourcePath}) -> if @enablePersistence? @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson']) @configFilePath ?= path.join(@configDirPath, 'config.cson') - @clear() clear: -> @emitter = new Emitter diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index f0ea0ed12..076b1bf32 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -55,6 +55,15 @@ require('welcome') require('whitespace') require('wrap-guide') +clipboard = new Clipboard +TextEditor.setClipboard(clipboard) + +window.atom = new AtomEnvironment({ + clipboard, + applicationDelegate: new ApplicationDelegate, + enablePersistence: true +}) + # Like sands through the hourglass, so are the days of our lives. module.exports = ({blobStore}) -> {updateProcessEnv} = require('./update-process-env') @@ -73,14 +82,9 @@ module.exports = ({blobStore}) -> # Make React faster process.env.NODE_ENV ?= 'production' unless devMode - clipboard = new Clipboard - TextEditor.setClipboard(clipboard) - - window.atom = new AtomEnvironment({ - window, document, clipboard, blobStore, - applicationDelegate: new ApplicationDelegate, + window.atom.initialize({ + window, document, blobStore, configDirPath: process.env.ATOM_HOME, - enablePersistence: true, env: process.env }) diff --git a/src/view-registry.coffee b/src/view-registry.coffee index e2cd986dc..5a8e72a4c 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -54,9 +54,12 @@ class ViewRegistry minimumPollInterval: 200 constructor: (@atomEnvironment) -> - @observer = new MutationObserver(@requestDocumentPoll) + @polling = false @clear() + initialize: -> + @observer = new MutationObserver(@requestDocumentPoll) + clear: -> @views = new WeakMap @providers = [] @@ -267,10 +270,13 @@ class ViewRegistry startPollingDocument: -> window.addEventListener('resize', @requestDocumentPoll) @observer.observe(document, {subtree: true, childList: true, attributes: true}) + @polling = true stopPollingDocument: -> - window.removeEventListener('resize', @requestDocumentPoll) - @observer.disconnect() + if @polling + window.removeEventListener('resize', @requestDocumentPoll) + @observer.disconnect() + @polling = false requestDocumentPoll: => if @animationFrameRequest? From c61df7254847e3168fb8f9be9357334255a6e1c5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 7 Mar 2017 18:30:38 +0100 Subject: [PATCH 144/194] Snapshot config, keymaps, tooltips, commands, grammars and styles --- script/lib/generate-startup-snapshot.js | 1 - src/atom-environment.coffee | 39 ++++++++++++++++-------- src/config-schema.js | 12 +------- src/main-process/atom-application.coffee | 3 +- src/style-manager.js | 13 +++++--- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 0021cf1ab..be3f06839 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -27,7 +27,6 @@ module.exports = function (packagedAppPath) { coreModules.has(modulePath) || (relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) || relativePath == path.join('..', 'exports', 'atom.js') || - relativePath == path.join('..', 'src', 'config-schema.js') || relativePath == path.join('..', 'src', 'electron-shims.js') || relativePath == path.join('..', 'src', 'safe-clipboard.js') || relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 04f0a23e8..99ce4d9d2 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -13,6 +13,7 @@ StateStore = require './state-store' StorageFolder = require './storage-folder' registerDefaultCommands = require './register-default-commands' {updateProcessEnv} = require './update-process-env' +ConfigSchema = require './config-schema' DeserializerManager = require './deserializer-manager' ViewRegistry = require './view-registry' @@ -142,10 +143,18 @@ class AtomEnvironment extends Model @deserializeTimings = {} @views = new ViewRegistry(this) @notifications = new NotificationManager + @config = new Config({notificationManager: @notifications, @enablePersistence}) + @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} + + @keymaps = new KeymapManager({notificationManager: @notifications}) + @tooltips = new TooltipManager(keymapManager: @keymaps, viewRegistry: @views) + @commands = new CommandRegistry + @grammars = new GrammarRegistry({@config}) + @styles = new StyleManager() initialize: (params={}) -> - {@applicationDelegate, @window, @document, @blobStore, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@window, @document, @blobStore, @configDirPath, onlyLoadBaseStyleSheets} = params {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() @stateStore = new StateStore('AtomEnvironments', 1) @@ -157,18 +166,24 @@ class AtomEnvironment extends Model @views.initialize() @config.initialize({@configDirPath, resourcePath}) - @setConfigSchema() + @projectHomeSchema = { + type: 'object', + properties: { + projectHome: { + type: 'string', + default: path.join(fs.getHomeDirectory(), 'github'), + description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' + } + } + } + @config.setSchema('core', @projectHomeSchema) - @keymaps = new KeymapManager({@configDirPath, resourcePath, notificationManager: @notifications}) + @keymaps.configDirPath = @configDirPath + @keymaps.resourcePath = resourcePath - @tooltips = new TooltipManager(keymapManager: @keymaps, viewRegistry: @views) - - @commands = new CommandRegistry @commands.attach(@window) - @grammars = new GrammarRegistry({@config}) - - @styles = new StyleManager({@configDirPath}) + @styles.initialize({@configDirPath}) @packages = new PackageManager({ devMode, @configDirPath, resourcePath, safeMode, @config, styleManager: @styles, @@ -248,9 +263,6 @@ class AtomEnvironment extends Model @document.removeEventListener('mousedown', saveState, true) @document.removeEventListener('keydown', saveState, true) - setConfigSchema: -> - @config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))} - registerDefaultDeserializers: -> @deserializers.add(Workspace) @deserializers.add(PaneContainer) @@ -303,7 +315,8 @@ class AtomEnvironment extends Model @registerDefaultDeserializers() @config.clear() - @setConfigSchema() + @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} + @config.setSchema('core', @projectHomeSchema) @keymaps.clear() @keymaps.loadBundledKeymaps() diff --git a/src/config-schema.js b/src/config-schema.js index 41b5ecbb6..b19624a38 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -1,8 +1,3 @@ -/** @babel */ - -import path from 'path' -import fs from 'fs-plus' - // This is loaded by atom-environment.coffee. See // https://atom.io/docs/api/latest/Config for more information about config // schemas. @@ -58,11 +53,6 @@ const configSchema = { }, description: 'Names of UI and syntax themes which will be used when Atom starts.' }, - projectHome: { - type: 'string', - default: path.join(fs.getHomeDirectory(), 'github'), - description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' - }, audioBeep: { type: 'boolean', default: true, @@ -506,4 +496,4 @@ if (process.platform === 'darwin') { } } -export default configSchema +module.exports = configSchema diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index e6f40379d..de7d7289c 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -68,7 +68,8 @@ class AtomApplication @pidsToOpenWindows = {} @windows = [] - @config = new Config({configDirPath: process.env.ATOM_HOME, @resourcePath, enablePersistence: true}) + @config = new Config({enablePersistence: true}) + @config.initialize({configDirPath: process.env.ATOM_HOME, @resourcePath}) @config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))} @config.load() @fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, "recovery")) diff --git a/src/style-manager.js b/src/style-manager.js index 0a0b401d3..718c1ee74 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -13,17 +13,20 @@ const DEPRECATED_SYNTAX_SELECTORS = require('./deprecated-syntax-selectors') // own, but is instead subscribed to by individual `` elements, // which clone and attach style elements in different contexts. module.exports = class StyleManager { - constructor ({configDirPath}) { - this.configDirPath = configDirPath - if (this.configDirPath != null) { - this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager') - } + constructor () { this.emitter = new Emitter() this.styleElements = [] this.styleElementsBySourcePath = {} this.deprecationsBySourcePath = {} } + initialize ({configDirPath}) { + this.configDirPath = configDirPath + if (this.configDirPath != null) { + this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager') + } + } + /* Section: Event Subscription */ From c4d0944c0da51e0a2d88273ff58a47c469d07be8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Mar 2017 10:02:35 +0100 Subject: [PATCH 145/194] Snapshot more objects --- package.json | 1 + src/atom-environment.coffee | 105 ++++++++++++++------------- src/auto-update-manager.js | 12 +-- src/command-installer.coffee | 4 +- src/command-registry.coffee | 9 ++- src/context-menu-manager.coffee | 29 +++++--- src/history-manager.js | 11 ++- src/keymap-extensions.coffee | 6 +- src/menu-manager.coffee | 9 ++- src/package-manager.coffee | 17 +++-- src/pane-container.coffee | 4 +- src/register-default-commands.coffee | 14 ++-- src/theme-manager.coffee | 4 +- src/window-event-handler.coffee | 37 +++++----- src/workspace.js | 68 +++++++++-------- 15 files changed, 182 insertions(+), 148 deletions(-) diff --git a/package.json b/package.json index f972b4890..e7fdbbb58 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "normalize-package-data": "^2.0.0", "nslog": "^3", "oniguruma": "6.1.0", + "parserlib": "^1.1.1", "pathwatcher": "7.0.0", "postcss": "5.2.4", "postcss-selector-parser": "2.2.1", diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 99ce4d9d2..602ede421 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -152,6 +152,51 @@ class AtomEnvironment extends Model @commands = new CommandRegistry @grammars = new GrammarRegistry({@config}) @styles = new StyleManager() + @packages = new PackageManager({ + @config, styleManager: @styles, + commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications, + grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views + }) + @themes = new ThemeManager({ + packageManager: @packages, @config, styleManager: @styles, + notificationManager: @notifications, viewRegistry: @views + }) + @menu = new MenuManager({keymapManager: @keymaps, packageManager: @packages}) + @contextMenu = new ContextMenuManager({keymapManager: @keymaps}) + @packages.setMenuManager(@menu) + @packages.setContextMenuManager(@contextMenu) + @packages.setThemeManager(@themes) + + @project = new Project({notificationManager: @notifications, packageManager: @packages, @config, @applicationDelegate}) + @commandInstaller = new CommandInstaller(@applicationDelegate) + + @textEditors = new TextEditorRegistry({ + @config, grammarRegistry: @grammars, assert: @assert.bind(this), + packageManager: @packages + }) + + @workspace = new Workspace({ + @config, @project, packageManager: @packages, grammarRegistry: @grammars, deserializerManager: @deserializers, + notificationManager: @notifications, @applicationDelegate, viewRegistry: @views, assert: @assert.bind(this), + textEditorRegistry: @textEditors, + }) + + @themes.workspace = @workspace + + @autoUpdater = new AutoUpdateManager({@applicationDelegate}) + + @keymaps.loadBundledKeymaps() + @registerDefaultCommands() + @registerDefaultOpeners() + @registerDefaultDeserializers() + @registerDefaultViewProviders() + + @windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate}) + + @history = new HistoryManager({@project, @commands}) + # Keep instances of HistoryManager in sync + @disposables.add @history.onDidChangeProjects (e) => + @applicationDelegate.didChangeHistoryManager() unless e.reloaded initialize: (params={}) -> {@window, @document, @blobStore, @configDirPath, onlyLoadBaseStyleSheets} = params @@ -184,44 +229,14 @@ class AtomEnvironment extends Model @commands.attach(@window) @styles.initialize({@configDirPath}) + @packages.initialize({devMode, @configDirPath, resourcePath, safeMode}) + @themes.initialize({@configDirPath, resourcePath, safeMode}) - @packages = new PackageManager({ - devMode, @configDirPath, resourcePath, safeMode, @config, styleManager: @styles, - commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications, - grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views - }) - - @themes = new ThemeManager({ - packageManager: @packages, @configDirPath, resourcePath, safeMode, @config, - styleManager: @styles, notificationManager: @notifications, viewRegistry: @views - }) - - @menu = new MenuManager({resourcePath, keymapManager: @keymaps, packageManager: @packages}) - - @contextMenu = new ContextMenuManager({resourcePath, devMode, keymapManager: @keymaps}) - - @packages.setMenuManager(@menu) - @packages.setContextMenuManager(@contextMenu) - @packages.setThemeManager(@themes) - - @project = new Project({notificationManager: @notifications, packageManager: @packages, @config, @applicationDelegate}) - - @commandInstaller = new CommandInstaller(@getVersion(), @applicationDelegate) - - @textEditors = new TextEditorRegistry({ - @config, grammarRegistry: @grammars, assert: @assert.bind(this), - packageManager: @packages - }) - - @workspace = new Workspace({ - @config, @project, packageManager: @packages, grammarRegistry: @grammars, deserializerManager: @deserializers, - notificationManager: @notifications, @applicationDelegate, viewRegistry: @views, assert: @assert.bind(this), - textEditorRegistry: @textEditors, - }) - - @themes.workspace = @workspace - - @autoUpdater = new AutoUpdateManager({@applicationDelegate}) + @menu.initialize({resourcePath}) + @contextMenu.initialize({resourcePath, devMode}) + @commandInstaller.initialize(@getVersion()) + @workspace.initialize() + @autoUpdater.initialize() @config.load() @@ -234,23 +249,14 @@ class AtomEnvironment extends Model @document.head.appendChild(@stylesElement) @keymaps.subscribeToFileReadFailure() - @keymaps.loadBundledKeymaps() - - @registerDefaultCommands() - @registerDefaultOpeners() - @registerDefaultDeserializers() - @registerDefaultViewProviders() @installUncaughtErrorHandler() @attachSaveStateListeners() - @installWindowEventHandler() + @windowEventHandler.initialize(@window, @document) @observeAutoHideMenuBar() - @history = new HistoryManager({@project, @commands, @stateStore, localStorage: window.localStorage}) - # Keep instances of HistoryManager in sync - @disposables.add @history.onDidChangeProjects (e) => - @applicationDelegate.didChangeHistoryManager() unless e.reloaded + @history.initialize(@stateStore, @window.localStorage) @disposables.add @applicationDelegate.onDidChangeHistoryManager(=> @history.loadState()) attachSaveStateListeners: -> @@ -787,9 +793,6 @@ class AtomEnvironment extends Model uninstallUncaughtErrorHandler: -> @window.onerror = @previousWindowErrorHandler - installWindowEventHandler: -> - @windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate, @window, @document}) - uninstallWindowEventHandler: -> @windowEventHandler?.unsubscribe() diff --git a/src/auto-update-manager.js b/src/auto-update-manager.js index fb6325a26..111147f32 100644 --- a/src/auto-update-manager.js +++ b/src/auto-update-manager.js @@ -7,21 +7,23 @@ export default class AutoUpdateManager { this.applicationDelegate = applicationDelegate this.subscriptions = new CompositeDisposable() this.emitter = new Emitter() + } + initialize () { this.subscriptions.add( - applicationDelegate.onDidBeginCheckingForUpdate(() => { + this.applicationDelegate.onDidBeginCheckingForUpdate(() => { this.emitter.emit('did-begin-checking-for-update') }), - applicationDelegate.onDidBeginDownloadingUpdate(() => { + this.applicationDelegate.onDidBeginDownloadingUpdate(() => { this.emitter.emit('did-begin-downloading-update') }), - applicationDelegate.onDidCompleteDownloadingUpdate((details) => { + this.applicationDelegate.onDidCompleteDownloadingUpdate((details) => { this.emitter.emit('did-complete-downloading-update', details) }), - applicationDelegate.onUpdateNotAvailable(() => { + this.applicationDelegate.onUpdateNotAvailable(() => { this.emitter.emit('update-not-available') }), - applicationDelegate.onUpdateError(() => { + this.applicationDelegate.onUpdateError(() => { this.emitter.emit('update-error') }) ) diff --git a/src/command-installer.coffee b/src/command-installer.coffee index e37e6a0e6..7873014fa 100644 --- a/src/command-installer.coffee +++ b/src/command-installer.coffee @@ -26,7 +26,9 @@ symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) -> module.exports = class CommandInstaller - constructor: (@appVersion, @applicationDelegate) -> + constructor: (@applicationDelegate) -> + + initialize: (@appVersion) -> getInstallDirectory: -> "/usr/local/bin" diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 056446203..269714876 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -91,23 +91,26 @@ class CommandRegistry # # Returns a {Disposable} on which `.dispose()` can be called to remove the # added command handler(s). - add: (target, commandName, callback) -> + add: (target, commandName, callback, throwOnInvalidSelector = true) -> if typeof commandName is 'object' commands = commandName disposable = new CompositeDisposable for commandName, callback of commands - disposable.add @add(target, commandName, callback) + disposable.add @add(target, commandName, callback, throwOnInvalidSelector) return disposable if typeof callback isnt 'function' throw new Error("Can't register a command with non-function callback.") if typeof target is 'string' - validateSelector(target) + validateSelector(target) if throwOnInvalidSelector @addSelectorBasedListener(target, commandName, callback) else @addInlineListener(target, commandName, callback) + addBundled: (target, commandName, callback) -> + @add(target, commandName, callback, false) + addSelectorBasedListener: (selector, commandName, callback) -> @selectorBasedListenersByCommandName[commandName] ?= [] listenersForCommand = @selectorBasedListenersByCommandName[commandName] diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 4dc54cede..c15ca20a6 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -40,15 +40,17 @@ platformContextMenu = require('../package.json')?._atomMenu?['context-menu'] # {::add} for more information. module.exports = class ContextMenuManager - constructor: ({@resourcePath, @devMode, @keymapManager}) -> + constructor: ({@keymapManager}) -> @definitions = {'.overlayer': []} # TODO: Remove once color picker package stops touching private data @clear() @keymapManager.onDidLoadBundledKeymaps => @loadPlatformItems() + initialize: ({@resourcePath, @devMode}) -> + loadPlatformItems: -> if platformContextMenu? - @add(platformContextMenu) + @add(platformContextMenu, false) else menusDirPath = path.join(@resourcePath, 'menus') platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json']) @@ -107,11 +109,11 @@ class ContextMenuManager # # Returns a {Disposable} on which `.dispose()` can be called to remove the # added menu items. - add: (itemsBySelector) -> + add: (itemsBySelector, throwOnInvalidSelector = true) -> addedItemSets = [] for selector, items of itemsBySelector - validateSelector(selector) + validateSelector(selector) if throwOnInvalidSelector itemSet = new ContextMenuItemSet(selector, items) addedItemSets.push(itemSet) @itemSets.push(itemSet) @@ -206,14 +208,17 @@ class ContextMenuManager clear: -> @activeElement = null @itemSets = [] - @add 'atom-workspace': [{ - label: 'Inspect Element' - command: 'application:inspect' - devMode: true - created: (event) -> - {pageX, pageY} = event - @commandDetail = {x: pageX, y: pageY} - }] + inspectElement = { + 'atom-workspace': [{ + label: 'Inspect Element' + command: 'application:inspect' + devMode: true + created: (event) -> + {pageX, pageY} = event + @commandDetail = {x: pageX, y: pageY} + }] + } + @add(inspectElement, false) class ContextMenuItemSet constructor: (@selector, @items) -> diff --git a/src/history-manager.js b/src/history-manager.js index e3dd46653..8260e75bc 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -8,16 +8,19 @@ import {Emitter, CompositeDisposable} from 'event-kit' // // The project history is used to enable the 'Reopen Project' menu. export class HistoryManager { - constructor ({stateStore, localStorage, project, commands}) { - this.stateStore = stateStore - this.localStorage = localStorage + constructor ({project, commands}) { this.emitter = new Emitter() this.projects = [] this.disposables = new CompositeDisposable() - this.disposables.add(commands.add('atom-workspace', {'application:clear-project-history': this.clearProjects.bind(this)})) + this.disposables.add(commands.addBundled('atom-workspace', {'application:clear-project-history': this.clearProjects.bind(this)})) this.disposables.add(project.onDidChangePaths((projectPaths) => this.addProject(projectPaths))) } + initialize (stateStore, localStorage) { + this.stateStore = stateStore + this.localStorage = localStorage + } + destroy () { this.disposables.dispose() } diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index bf8302f4c..76b2e8958 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -12,12 +12,12 @@ KeymapManager::onDidLoadUserKeymap = (callback) -> @emitter.on 'did-load-user-keymap', callback KeymapManager::loadBundledKeymaps = -> - keymapsPath = path.join(@resourcePath, 'keymaps') if bundledKeymaps? for keymapName, keymap of bundledKeymaps - keymapPath = path.join(keymapsPath, keymapName) - @add(keymapPath, keymap) + keymapPath = "/#{keymapName}" + @add(keymapPath, keymap, 0, false) else + keymapsPath = path.join(@resourcePath, 'keymaps') @loadKeymap(keymapsPath) @emitter.emit 'did-load-bundled-keymaps' diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index b6ed7fd1a..18dd49c5a 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -60,12 +60,17 @@ platformMenu = require('../package.json')?._atomMenu?.menu module.exports = class MenuManager constructor: ({@resourcePath, @keymapManager, @packageManager}) -> + @initialized = false @pendingUpdateOperation = null @template = [] @keymapManager.onDidLoadBundledKeymaps => @loadPlatformItems() - @keymapManager.onDidReloadKeymap => @update() @packageManager.onDidActivateInitialPackages => @sortPackagesMenu() + initialize: ({@resourcePath}) -> + @keymapManager.onDidReloadKeymap => @update() + @update() + @initialized = true + # Public: Adds the given items to the application menu. # # ## Examples @@ -89,7 +94,7 @@ class MenuManager add: (items) -> items = _.deepClone(items) @merge(@template, item) for item in items - @update() + @update() if @initialized new Disposable => @remove(items) remove: (items) -> diff --git a/src/package-manager.coffee b/src/package-manager.coffee index fb4f7a658..a7b068bbf 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -30,9 +30,8 @@ module.exports = class PackageManager constructor: (params) -> { - configDirPath, @devMode, safeMode, @resourcePath, @config, @styleManager, - @notificationManager, @keymapManager, @commandRegistry, @grammarRegistry, - @deserializerManager, @viewRegistry + @config, @styleManager, @notificationManager, @keymapManager, + @commandRegistry, @grammarRegistry, @deserializerManager, @viewRegistry } = params @emitter = new Emitter @@ -40,11 +39,6 @@ class PackageManager @packageDirPaths = [] @deferredActivationHooks = [] @triggeredActivationHooks = new Set() - if configDirPath? and not safeMode - if @devMode - @packageDirPaths.push(path.join(configDirPath, "dev", "packages")) - @packageDirPaths.push(path.join(configDirPath, "packages")) - @packagesCache = require('../package.json')?._atomPackages ? {} @initialPackagesLoaded = false @initialPackagesActivated = false @@ -57,6 +51,13 @@ class PackageManager @packageActivators = [] @registerPackageActivator(this, ['atom', 'textmate']) + initialize: (params) -> + {configDirPath, @devMode, safeMode, @resourcePath} = params + if configDirPath? and not safeMode + if @devMode + @packageDirPaths.push(path.join(configDirPath, "dev", "packages")) + @packageDirPaths.push(path.join(configDirPath, "packages")) + setContextMenuManager: (@contextMenuManager) -> setMenuManager: (@menuManager) -> diff --git a/src/pane-container.coffee b/src/pane-container.coffee index fc092122e..20d14389d 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -21,9 +21,11 @@ class PaneContainer extends Model @setRoot(new Pane({container: this, @config, applicationDelegate, notificationManager, deserializerManager})) @setActivePane(@getRoot()) - @monitorActivePaneItem() @monitorPaneItems() + initialize: -> + @monitorActivePaneItem() + serialize: (params) -> deserializer: 'PaneContainer' version: @serializationVersion diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 8196d9237..f3ede7987 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -2,7 +2,7 @@ Grim = require 'grim' module.exports = ({commandRegistry, commandInstaller, config, notificationManager, project, clipboard}) -> - commandRegistry.add 'atom-workspace', + commandRegistry.addBundled 'atom-workspace', 'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem() 'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem() 'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack() @@ -79,10 +79,10 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'core:save-as': -> @getModel().saveActivePaneItemAs() if process.platform is 'darwin' - commandRegistry.add 'atom-workspace', 'window:install-shell-commands', -> + commandRegistry.addBundled 'atom-workspace', 'window:install-shell-commands', -> commandInstaller.installShellCommandsInteractively() - commandRegistry.add 'atom-pane', + commandRegistry.addBundled 'atom-pane', 'pane:save-items': -> @getModel().saveItems() 'pane:split-left': -> @getModel().splitLeft() 'pane:split-right': -> @getModel().splitRight() @@ -101,7 +101,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'pane:increase-size': -> @getModel().increaseSize() 'pane:decrease-size': -> @getModel().decreaseSize() - commandRegistry.add 'atom-text-editor', stopEventPropagation( + commandRegistry.addBundled 'atom-text-editor', stopEventPropagation( 'core:undo': -> @undo() 'core:redo': -> @redo() 'core:move-left': -> @moveLeft() @@ -142,7 +142,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'editor:select-line': -> @selectLinesContainingCursors() ) - commandRegistry.add 'atom-text-editor', stopEventPropagationAndGroupUndo(config, + commandRegistry.addBundled 'atom-text-editor', stopEventPropagationAndGroupUndo(config, 'core:backspace': -> @backspace() 'core:delete': -> @delete() 'core:cut': -> @cutSelectedText() @@ -165,7 +165,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'editor:copy-selection': -> @copyOnlySelectedText() ) - commandRegistry.add 'atom-text-editor:not([mini])', stopEventPropagation( + commandRegistry.addBundled 'atom-text-editor:not([mini])', stopEventPropagation( 'core:move-up': -> @moveUp() 'core:move-down': -> @moveDown() 'core:move-to-top': -> @moveToTop() @@ -203,7 +203,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'editor:scroll-to-cursor': -> @scrollToCursorPosition() ) - commandRegistry.add 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(config, + commandRegistry.addBundled 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(config, 'editor:indent': -> @indent() 'editor:auto-indent': -> @autoIndentSelectedRows() 'editor:indent-selected-rows': -> @indentSelectedRows() diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index 58297b2db..f035889a2 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -9,7 +9,7 @@ fs = require 'fs-plus' # An instance of this class is always available as the `atom.themes` global. module.exports = class ThemeManager - constructor: ({@packageManager, @resourcePath, @configDirPath, @safeMode, @config, @styleManager, @notificationManager, @viewRegistry}) -> + constructor: ({@packageManager, @config, @styleManager, @notificationManager, @viewRegistry}) -> @emitter = new Emitter @styleSheetDisposablesBySourcePath = {} @lessCache = null @@ -18,6 +18,8 @@ class ThemeManager @packageManager.onDidActivateInitialPackages => @onDidChangeActiveThemes => @packageManager.reloadActivePackageStyleSheets() + initialize: ({@resourcePath, @configDirPath, @safeMode}) -> + ### Section: Event Subscription ### diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 95cd45de9..5d6ce4686 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -4,10 +4,27 @@ listen = require './delegated-listener' # Handles low-level events related to the @window. module.exports = class WindowEventHandler - constructor: ({@atomEnvironment, @applicationDelegate, @window, @document}) -> + constructor: ({@atomEnvironment, @applicationDelegate}) -> @reloadRequested = false @subscriptions = new CompositeDisposable + @handleNativeKeybindings() + + initialize: (@window, @document) -> + @subscriptions.add @atomEnvironment.commands.add @window, + 'window:toggle-full-screen': @handleWindowToggleFullScreen + 'window:close': @handleWindowClose + 'window:reload': @handleWindowReload + 'window:toggle-dev-tools': @handleWindowToggleDevTools + + if process.platform in ['win32', 'linux'] + @subscriptions.add @atomEnvironment.commands.add @window, + 'window:toggle-menu-bar': @handleWindowToggleMenuBar + + @subscriptions.add @atomEnvironment.commands.add @document, + 'core:focus-next': @handleFocusNext + 'core:focus-previous': @handleFocusPrevious + @addEventListener(@window, 'beforeunload', @handleWindowBeforeunload) @addEventListener(@window, 'focus', @handleWindowFocus) @addEventListener(@window, 'blur', @handleWindowBlur) @@ -23,27 +40,11 @@ class WindowEventHandler @subscriptions.add(@applicationDelegate.onDidEnterFullScreen(@handleEnterFullScreen)) @subscriptions.add(@applicationDelegate.onDidLeaveFullScreen(@handleLeaveFullScreen)) - @subscriptions.add @atomEnvironment.commands.add @window, - 'window:toggle-full-screen': @handleWindowToggleFullScreen - 'window:close': @handleWindowClose - 'window:reload': @handleWindowReload - 'window:toggle-dev-tools': @handleWindowToggleDevTools - - if process.platform in ['win32', 'linux'] - @subscriptions.add @atomEnvironment.commands.add @window, - 'window:toggle-menu-bar': @handleWindowToggleMenuBar - - @subscriptions.add @atomEnvironment.commands.add @document, - 'core:focus-next': @handleFocusNext - 'core:focus-previous': @handleFocusPrevious - - @handleNativeKeybindings() - # Wire commands that should be handled by Chromium for elements with the # `.native-key-bindings` class. handleNativeKeybindings: -> bindCommandToAction = (command, action) => - @subscriptions.add @atomEnvironment.commands.add '.native-key-bindings', command, (event) => + @subscriptions.add @atomEnvironment.commands.addBundled '.native-key-bindings', command, (event) => @applicationDelegate.getCurrentWindow().webContents[action]() bindCommandToAction('core:copy', 'copy') diff --git a/src/workspace.js b/src/workspace.js index 8ff8aa51d..fd89aa992 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -30,6 +30,7 @@ module.exports = class Workspace extends Model { this.updateWindowTitle = this.updateWindowTitle.bind(this) this.updateDocumentEdited = this.updateDocumentEdited.bind(this) this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this) + this.didChangeActivePaneItem = this.didChangeActivePaneItem.bind(this) this.packageManager = params.packageManager this.config = params.config @@ -70,6 +71,11 @@ module.exports = class Workspace extends Model { this.subscribeToEvents() } + initialize () { + this.paneContainer.initialize() + this.didChangeActivePaneItem() + } + reset (packageManager) { this.packageManager = packageManager this.emitter.dispose() @@ -170,46 +176,44 @@ module.exports = class Workspace extends Model { } subscribeToActiveItem () { + this.project.onDidChangePaths(this.updateWindowTitle) + this.onDidChangeActivePaneItem(this.didChangeActivePaneItem) + } + + didChangeActivePaneItem (item) { this.updateWindowTitle() this.updateDocumentEdited() - this.project.onDidChangePaths(this.updateWindowTitle) + if (this.activeItemSubscriptions != null) { + this.activeItemSubscriptions.dispose() + } + this.activeItemSubscriptions = new CompositeDisposable() - this.observeActivePaneItem(item => { - this.updateWindowTitle() - this.updateDocumentEdited() + let modifiedSubscription, titleSubscription - if (this.activeItemSubscriptions != null) { - this.activeItemSubscriptions.dispose() + if (item != null && typeof item.onDidChangeTitle === 'function') { + titleSubscription = item.onDidChangeTitle(this.updateWindowTitle) + } else if (item != null && typeof item.on === 'function') { + titleSubscription = item.on('title-changed', this.updateWindowTitle) + if (titleSubscription == null || typeof titleSubscription.dispose !== 'function') { + titleSubscription = new Disposable(() => { + item.off('title-changed', this.updateWindowTitle) + }) } - this.activeItemSubscriptions = new CompositeDisposable() + } - let modifiedSubscription, titleSubscription - - if (item != null && typeof item.onDidChangeTitle === 'function') { - titleSubscription = item.onDidChangeTitle(this.updateWindowTitle) - } else if (item != null && typeof item.on === 'function') { - titleSubscription = item.on('title-changed', this.updateWindowTitle) - if (titleSubscription == null || typeof titleSubscription.dispose !== 'function') { - titleSubscription = new Disposable(() => { - item.off('title-changed', this.updateWindowTitle) - }) - } + if (item != null && typeof item.onDidChangeModified === 'function') { + modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited) + } else if (item != null && typeof item.on === 'function') { + modifiedSubscription = item.on('modified-status-changed', this.updateDocumentEdited) + if (modifiedSubscription == null || typeof modifiedSubscription.dispose !== 'function') { + modifiedSubscription = new Disposable(() => { + item.off('modified-status-changed', this.updateDocumentEdited) + }) } + } - if (item != null && typeof item.onDidChangeModified === 'function') { - modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited) - } else if (item != null && typeof item.on === 'function') { - modifiedSubscription = item.on('modified-status-changed', this.updateDocumentEdited) - if (modifiedSubscription == null || typeof modifiedSubscription.dispose !== 'function') { - modifiedSubscription = new Disposable(() => { - item.off('modified-status-changed', this.updateDocumentEdited) - }) - } - } - - if (titleSubscription != null) { this.activeItemSubscriptions.add(titleSubscription) } - if (modifiedSubscription != null) { this.activeItemSubscriptions.add(modifiedSubscription) } - }) + if (titleSubscription != null) { this.activeItemSubscriptions.add(titleSubscription) } + if (modifiedSubscription != null) { this.activeItemSubscriptions.add(modifiedSubscription) } } subscribeToAddedItems () { From f5a19a6904e363f26626e29dcdacd45bfdd39217 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Mar 2017 12:39:04 +0100 Subject: [PATCH 146/194] Move StateStore into the snapshot --- src/atom-environment.coffee | 8 ++++---- src/history-manager.js | 6 +++--- src/state-store.js | 41 ++++++++++++++++++++++--------------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 602ede421..134cf55d2 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -144,6 +144,8 @@ class AtomEnvironment extends Model @views = new ViewRegistry(this) @notifications = new NotificationManager + @stateStore = new StateStore('AtomEnvironments', 1) + @config = new Config({notificationManager: @notifications, @enablePersistence}) @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} @@ -193,7 +195,7 @@ class AtomEnvironment extends Model @windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate}) - @history = new HistoryManager({@project, @commands}) + @history = new HistoryManager({@project, @commands, @stateStore}) # Keep instances of HistoryManager in sync @disposables.add @history.onDidChangeProjects (e) => @applicationDelegate.didChangeHistoryManager() unless e.reloaded @@ -202,8 +204,6 @@ class AtomEnvironment extends Model {@window, @document, @blobStore, @configDirPath, onlyLoadBaseStyleSheets} = params {devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings() - @stateStore = new StateStore('AtomEnvironments', 1) - if clearWindowState @getStorageFolder().clear() @stateStore.clear() @@ -256,7 +256,7 @@ class AtomEnvironment extends Model @observeAutoHideMenuBar() - @history.initialize(@stateStore, @window.localStorage) + @history.initialize(@window.localStorage) @disposables.add @applicationDelegate.onDidChangeHistoryManager(=> @history.loadState()) attachSaveStateListeners: -> diff --git a/src/history-manager.js b/src/history-manager.js index 8260e75bc..17ae73d75 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -8,7 +8,8 @@ import {Emitter, CompositeDisposable} from 'event-kit' // // The project history is used to enable the 'Reopen Project' menu. export class HistoryManager { - constructor ({project, commands}) { + constructor ({project, commands, stateStore}) { + this.stateStore = stateStore this.emitter = new Emitter() this.projects = [] this.disposables = new CompositeDisposable() @@ -16,8 +17,7 @@ export class HistoryManager { this.disposables.add(project.onDidChangePaths((projectPaths) => this.addProject(projectPaths))) } - initialize (stateStore, localStorage) { - this.stateStore = stateStore + initialize (localStorage) { this.localStorage = localStorage } diff --git a/src/state-store.js b/src/state-store.js index b192d8b04..e16857580 100644 --- a/src/state-store.js +++ b/src/state-store.js @@ -4,22 +4,31 @@ module.exports = class StateStore { constructor (databaseName, version) { this.connected = false - this.dbPromise = new Promise((resolve) => { - let dbOpenRequest = indexedDB.open(databaseName, version) - dbOpenRequest.onupgradeneeded = (event) => { - let db = event.target.result - db.createObjectStore('states') - } - dbOpenRequest.onsuccess = () => { - this.connected = true - resolve(dbOpenRequest.result) - } - dbOpenRequest.onerror = (error) => { - console.error('Could not connect to indexedDB', error) - this.connected = false - resolve(null) - } - }) + this.databaseName = databaseName + this.version = version + } + + get dbPromise () { + if (!this._dbPromise) { + this._dbPromise = new Promise((resolve) => { + const dbOpenRequest = indexedDB.open(this.databaseName, this.version) + dbOpenRequest.onupgradeneeded = (event) => { + let db = event.target.result + db.createObjectStore('states') + } + dbOpenRequest.onsuccess = () => { + this.connected = true + resolve(dbOpenRequest.result) + } + dbOpenRequest.onerror = (error) => { + console.error('Could not connect to indexedDB', error) + this.connected = false + resolve(null) + } + }) + } + + return this._dbPromise } isConnected () { From 05eb737a94dfef69c8a295d22bdf94c8c59ef50e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Mar 2017 15:28:19 +0100 Subject: [PATCH 147/194] Set config schema entirely in the snapshot --- src/atom-environment.coffee | 17 +++++------------ src/config.coffee | 4 +++- src/main-process/atom-application.coffee | 10 ++++++++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 134cf55d2..08f39f2d3 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -210,18 +210,12 @@ class AtomEnvironment extends Model @views.initialize() - @config.initialize({@configDirPath, resourcePath}) - @projectHomeSchema = { - type: 'object', - properties: { - projectHome: { - type: 'string', - default: path.join(fs.getHomeDirectory(), 'github'), - description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' - } - } + ConfigSchema.projectHome = { + type: 'string', + default: path.join(fs.getHomeDirectory(), 'github'), + description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' } - @config.setSchema('core', @projectHomeSchema) + @config.initialize({@configDirPath, resourcePath, projectHomeSchema: ConfigSchema.projectHome}) @keymaps.configDirPath = @configDirPath @keymaps.resourcePath = resourcePath @@ -322,7 +316,6 @@ class AtomEnvironment extends Model @config.clear() @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} - @config.setSchema('core', @projectHomeSchema) @keymaps.clear() @keymaps.loadBundledKeymaps() diff --git a/src/config.coffee b/src/config.coffee index 575e95a72..46f3c8629 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -401,11 +401,13 @@ class Config constructor: ({@notificationManager, @enablePersistence}={}) -> @clear() - initialize: ({@configDirPath, @resourcePath}) -> + initialize: ({@configDirPath, @resourcePath, projectHomeSchema}) -> if @enablePersistence? @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson']) @configFilePath ?= path.join(@configDirPath, 'config.cson') + @schema.properties.core.properties.projectHome = projectHomeSchema + clear: -> @emitter = new Emitter @schema = diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index de7d7289c..1df5b9e03 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -17,6 +17,7 @@ url = require 'url' _ = require 'underscore-plus' FindParentDir = null Resolve = null +ConfigSchema = require '../config-schema' LocationSuffixRegExp = /(:\d+)(:\d+)?$/ @@ -69,8 +70,13 @@ class AtomApplication @windows = [] @config = new Config({enablePersistence: true}) - @config.initialize({configDirPath: process.env.ATOM_HOME, @resourcePath}) - @config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))} + @config.setSchema null, {type: 'object', properties: _.clone(ConfigSchema)} + ConfigSchema.projectHome = { + type: 'string', + default: path.join(fs.getHomeDirectory(), 'github'), + description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' + } + @config.initialize({configDirPath: process.env.ATOM_HOME, @resourcePath, projectHomeSchema: ConfigSchema.projectHome}) @config.load() @fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, "recovery")) @storageFolder = new StorageFolder(process.env.ATOM_HOME) From 6eca9d470955c97ebe1767bc420ac6f8a778ee0f Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Fri, 10 Mar 2017 09:54:28 -0800 Subject: [PATCH 148/194] Ensure Windows builds fail when Squirrel packaging fails --- script/lib/create-windows-installer.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/script/lib/create-windows-installer.js b/script/lib/create-windows-installer.js index 8a0dc0f61..22a22701c 100644 --- a/script/lib/create-windows-installer.js +++ b/script/lib/create-windows-installer.js @@ -10,7 +10,7 @@ const spawnSync = require('./spawn-sync') const CONFIG = require('../config') -module.exports = function (packagedAppPath, codeSign) { +module.exports = (packagedAppPath, codeSign) => { const archSuffix = process.arch === 'ia32' ? '' : '-' + process.arch const options = { appDirectory: packagedAppPath, @@ -23,7 +23,7 @@ module.exports = function (packagedAppPath, codeSign) { } const signing = codeSign && (process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL || process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) - let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH; + let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH if (signing) { if (!certPath) { @@ -42,7 +42,7 @@ module.exports = function (packagedAppPath, codeSign) { console.log('Skipping code-signing. Specify the --code-sign option and provide a ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable to perform code-signing'.gray) } - const cleanUp = function () { + const cleanUp = () => { if (fs.existsSync(certPath) && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) { console.log(`Deleting certificate at ${certPath}`) fs.removeSync(certPath) @@ -57,7 +57,7 @@ module.exports = function (packagedAppPath, codeSign) { } // Squirrel signs its own copy of the executables but we need them for the portable ZIP - const extractSignedExes = function() { + const extractSignedExes = () => { if (signing) { for (let nupkgPath of glob.sync(`${CONFIG.buildOutputPath}/*-full.nupkg`)) { if (nupkgPath.includes(CONFIG.appMetadata.version)) { @@ -73,12 +73,9 @@ module.exports = function (packagedAppPath, codeSign) { console.log(`Creating Windows Installer for ${packagedAppPath}`) return electronInstaller.createWindowsInstaller(options) - .then(extractSignedExes, function (error) { - console.log(`Extracting signed executables failed:\n${error}`) - cleanUp() - }) - .then(cleanUp, function (error) { - console.log(`Windows installer creation failed:\n${error}`) + .then(extractSignedExes) + .then(cleanUp, error => { cleanUp() + return Promise.reject(error) }) } From 865294f3c8b70d97f0db8b69fca06fc032b04feb Mon Sep 17 00:00:00 2001 From: Dirk Thomas Date: Sun, 29 Jan 2017 10:33:07 -0800 Subject: [PATCH 149/194] pass through line count options --- src/default-directory-searcher.coffee | 5 ++++- src/scan-handler.coffee | 4 ++-- src/text-editor.coffee | 12 +++++++++++- src/workspace.js | 6 ++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/default-directory-searcher.coffee b/src/default-directory-searcher.coffee index 6b8ffe3e3..3955d38e3 100644 --- a/src/default-directory-searcher.coffee +++ b/src/default-directory-searcher.coffee @@ -11,13 +11,16 @@ class DirectorySearch excludeVcsIgnores: options.excludeVcsIgnores globalExclusions: options.exclusions follow: options.follow + searchOptions = + leadingContextLineCount: options.leadingContextLineCount + trailingContextLineCount: options.trailingContextLineCount @task = new Task(require.resolve('./scan-handler')) @task.on 'scan:result-found', options.didMatch @task.on 'scan:file-error', options.didError @task.on 'scan:paths-searched', options.didSearchPaths @promise = new Promise (resolve, reject) => @task.on('task:cancelled', reject) - @task.start rootPaths, regex.source, scanHandlerOptions, => + @task.start rootPaths, regex.source, scanHandlerOptions, searchOptions, => @task.terminate() resolve() diff --git a/src/scan-handler.coffee b/src/scan-handler.coffee index 8ee8f715e..db2e8299b 100644 --- a/src/scan-handler.coffee +++ b/src/scan-handler.coffee @@ -2,13 +2,13 @@ path = require "path" async = require "async" {PathSearcher, PathScanner, search} = require 'scandal' -module.exports = (rootPaths, regexSource, options) -> +module.exports = (rootPaths, regexSource, options, searchOptions={}) -> callback = @async() PATHS_COUNTER_SEARCHED_CHUNK = 50 pathsSearched = 0 - searcher = new PathSearcher() + searcher = new PathSearcher(searchOptions) searcher.on 'file-error', ({code, path, message}) -> emit('scan:file-error', {code, path, message}) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8095632fd..73fb7f954 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2826,6 +2826,11 @@ class TextEditor extends Model # {::backwardsScanInBufferRange} to avoid tripping over your own changes. # # * `regex` A {RegExp} to search for. + # * `options` (optional) {Object} + # * `leadingContextLineCount` {Number} default `0`; The number of lines + # before the matched line to include in the results object. + # * `trailingContextLineCount` {Number} default `0`; The number of lines + # after the matched line to include in the results object. # * `iterator` A {Function} that's called on each match # * `object` {Object} # * `match` The current regular expression match. @@ -2833,7 +2838,12 @@ class TextEditor extends Model # * `range` The {Range} of the match. # * `stop` Call this {Function} to terminate the scan. # * `replace` Call this {Function} with a {String} to replace the match. - scan: (regex, iterator) -> @buffer.scan(regex, iterator) + scan: (regex, options={}, iterator) -> + if _.isFunction(options) + iterator = options + options = {} + + @buffer.scan(regex, options, iterator) # Essential: Scan regular expression matches in a given range, calling the given # iterator function on each match. diff --git a/src/workspace.js b/src/workspace.js index a386b5ff7..6c5daba12 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -1202,6 +1202,10 @@ module.exports = class Workspace extends Model { // * `paths` An {Array} of glob patterns to search within. // * `onPathsSearched` (optional) {Function} to be periodically called // with number of paths searched. + // * `leadingContextLineCount` {Number} default `0`; The number of lines + // before the matched line to include in the results object. + // * `trailingContextLineCount` {Number} default `0`; The number of lines + // after the matched line to include in the results object. // * `iterator` {Function} callback on each file found. // // Returns a {Promise} with a `cancel()` method that will cancel all @@ -1261,6 +1265,8 @@ module.exports = class Workspace extends Model { excludeVcsIgnores: this.config.get('core.excludeVcsIgnoredPaths'), exclusions: this.config.get('core.ignoredNames'), follow: this.config.get('core.followSymlinks'), + leadingContextLineCount: options.leadingContextLineCount || 0, + trailingContextLineCount: options.trailingContextLineCount || 0, didMatch: result => { if (!this.project.isPathModified(result.filePath)) { return iterator(result) From 159ab1c435d546fc2a04cee3498a92d6e8a5906f Mon Sep 17 00:00:00 2001 From: Dirk Thomas Date: Thu, 9 Mar 2017 10:03:05 -0800 Subject: [PATCH 150/194] :arrow_up: scandal 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46f5dc165..b23e6e6fc 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "random-words": "0.0.1", "resolve": "^1.1.6", "runas": "^3.1", - "scandal": "^3.0.0", + "scandal": "^3.1.0", "scoped-property-store": "^0.17.0", "scrollbar-style": "^3.2", "season": "^6.0.0", From 348553049adc80c3a0bedaba100ba063b5a73b03 Mon Sep 17 00:00:00 2001 From: Dirk Thomas Date: Thu, 9 Mar 2017 19:29:45 -0800 Subject: [PATCH 151/194] update specs --- spec/workspace-spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 43e4fbae4..b04d8cbd7 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1336,7 +1336,9 @@ i = /test/; #FIXME\ it('calls the callback with all regex results in all files in the project', () => { const results = [] waitsForPromise(() => - atom.workspace.scan(/(a)+/, result => results.push(result)) + atom.workspace.scan( + /(a)+/, {leadingContextLineCount: 1, trailingContextLineCount: 1}, + result => results.push(result)) ) runs(() => { @@ -1349,14 +1351,16 @@ i = /test/; #FIXME\ lineTextOffset: 0, range: [[0, 0], [0, 3]], leadingContextLines: [], - trailingContextLines: [] + trailingContextLines: ['cc aa cc'] }) }) }) it('works with with escaped literals (like $ and ^)', () => { const results = [] - waitsForPromise(() => atom.workspace.scan(/\$\w+/, result => results.push(result))) + waitsForPromise(() => atom.workspace.scan( + /\$\w+/, {leadingContextLineCount: 1, trailingContextLineCount: 1}, + result => results.push(result))) runs(() => { expect(results.length).toBe(1) @@ -1368,7 +1372,7 @@ i = /test/; #FIXME\ lineText: 'dollar$bill', lineTextOffset: 0, range: [[2, 6], [2, 11]], - leadingContextLines: [], + leadingContextLines: ['cc aa cc'], trailingContextLines: [] }) }) From a792fa378fc931df48d49808ae1687d82f0a6e42 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 10 Mar 2017 19:47:37 +0100 Subject: [PATCH 152/194] :arrow_up: service-hub --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46f5dc165..95956bb1c 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "scrollbar-style": "^3.2", "season": "^6.0.0", "semver": "^4.3.3", - "service-hub": "^0.7.2", + "service-hub": "^0.7.3", "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", From 7408fc87317ad128da66eda6fa191ffe89ed17e4 Mon Sep 17 00:00:00 2001 From: liuderchi Date: Fri, 10 Mar 2017 23:51:49 +0800 Subject: [PATCH 153/194] :art: add 'Run Benchmark' menu item for win32 and linux platform --- menus/linux.cson | 1 + menus/win32.cson | 1 + 2 files changed, 2 insertions(+) diff --git a/menus/linux.cson b/menus/linux.cson index 94fb90a30..2a1ca47f8 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -137,6 +137,7 @@ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } + { label: 'Run &Benchmarks', command: 'window:run-benchmarks' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/menus/win32.cson b/menus/win32.cson index 70bb1487d..553b6017e 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -136,6 +136,7 @@ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } + { label: 'Run &Benchmarks', command: 'window:run-benchmarks' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } From 8bd409ede3049784ac92b1860e242193ce693862 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 11 Mar 2017 18:50:13 +0100 Subject: [PATCH 154/194] Fix dev mode --- src/atom-environment.coffee | 11 ++++++++--- src/keymap-extensions.coffee | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 08f39f2d3..a60c1febb 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -187,7 +187,9 @@ class AtomEnvironment extends Model @autoUpdater = new AutoUpdateManager({@applicationDelegate}) - @keymaps.loadBundledKeymaps() + if @keymaps.canLoadBundledKeymapsFromMemory() + @keymaps.loadBundledKeymaps() + @registerDefaultCommands() @registerDefaultOpeners() @registerDefaultDeserializers() @@ -217,8 +219,13 @@ class AtomEnvironment extends Model } @config.initialize({@configDirPath, resourcePath, projectHomeSchema: ConfigSchema.projectHome}) + @menu.initialize({resourcePath}) + @contextMenu.initialize({resourcePath, devMode}) + @keymaps.configDirPath = @configDirPath @keymaps.resourcePath = resourcePath + unless @keymaps.canLoadBundledKeymapsFromMemory() + @keymaps.loadBundledKeymaps() @commands.attach(@window) @@ -226,8 +233,6 @@ class AtomEnvironment extends Model @packages.initialize({devMode, @configDirPath, resourcePath, safeMode}) @themes.initialize({@configDirPath, resourcePath, safeMode}) - @menu.initialize({resourcePath}) - @contextMenu.initialize({resourcePath, devMode}) @commandInstaller.initialize(@getVersion()) @workspace.initialize() @autoUpdater.initialize() diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index 76b2e8958..93320ab89 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -11,6 +11,9 @@ KeymapManager::onDidLoadBundledKeymaps = (callback) -> KeymapManager::onDidLoadUserKeymap = (callback) -> @emitter.on 'did-load-user-keymap', callback +KeymapManager::canLoadBundledKeymapsFromMemory = -> + bundledKeymaps? + KeymapManager::loadBundledKeymaps = -> if bundledKeymaps? for keymapName, keymap of bundledKeymaps From fbcbfc48893974f766b374e0458bca4782fa3363 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 11 Mar 2017 18:56:29 +0100 Subject: [PATCH 155/194] Validate selectors only in devMode --- src/atom-environment.coffee | 1 + src/context-menu-manager.coffee | 2 +- src/keymap-extensions.coffee | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index a60c1febb..9cda07865 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -224,6 +224,7 @@ class AtomEnvironment extends Model @keymaps.configDirPath = @configDirPath @keymaps.resourcePath = resourcePath + @keymaps.devMode = devMode unless @keymaps.canLoadBundledKeymapsFromMemory() @keymaps.loadBundledKeymaps() diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index c15ca20a6..8a25373b0 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -50,7 +50,7 @@ class ContextMenuManager loadPlatformItems: -> if platformContextMenu? - @add(platformContextMenu, false) + @add(platformContextMenu, @devMode ? false) else menusDirPath = path.join(@resourcePath, 'menus') platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json']) diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index 93320ab89..d1bcbc356 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -18,7 +18,7 @@ KeymapManager::loadBundledKeymaps = -> if bundledKeymaps? for keymapName, keymap of bundledKeymaps keymapPath = "/#{keymapName}" - @add(keymapPath, keymap, 0, false) + @add(keymapPath, keymap, 0, @devMode ? false) else keymapsPath = path.join(@resourcePath, 'keymaps') @loadKeymap(keymapsPath) From 284f2c62e55e69afe3f255247f670b1066bf9396 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 11 Mar 2017 19:05:28 +0100 Subject: [PATCH 156/194] Start fixing tests --- spec/atom-environment-spec.coffee | 6 ++++-- spec/auto-update-manager-spec.js | 5 ++--- spec/command-installer-spec.coffee | 12 ++++++++---- spec/context-menu-manager-spec.coffee | 3 ++- spec/history-manager-spec.js | 3 ++- src/initialize-benchmark-window.js | 6 ++++-- src/initialize-test-window.coffee | 4 +++- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee index d1eabf2c8..1d299acd2 100644 --- a/spec/atom-environment-spec.coffee +++ b/spec/atom-environment-spec.coffee @@ -340,7 +340,8 @@ describe "AtomEnvironment", -> it "saves the BlobStore so it can be loaded after reload", -> configDirPath = temp.mkdirSync('atom-spec-environment') fakeBlobStore = jasmine.createSpyObj("blob store", ["save"]) - atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true, configDirPath, blobStore: fakeBlobStore, window, document}) + atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true}) + atomEnvironment.initialize({configDirPath, blobStore: fakeBlobStore, window, document}) atomEnvironment.unloadEditorWindow() @@ -357,7 +358,8 @@ describe "AtomEnvironment", -> head: document.createElement('head') body: document.createElement('body') } - atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document: fakeDocument}) + atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate}) + atomEnvironment.initialize({window, document: fakeDocument}) spyOn(atomEnvironment.packages, 'getAvailablePackagePaths').andReturn [] spyOn(atomEnvironment, 'displayWindow').andReturn Promise.resolve() atomEnvironment.startEditorWindow() diff --git a/spec/auto-update-manager-spec.js b/spec/auto-update-manager-spec.js index b38e7827c..780c9816b 100644 --- a/spec/auto-update-manager-spec.js +++ b/spec/auto-update-manager-spec.js @@ -11,9 +11,8 @@ describe('AutoUpdateManager (renderer)', () => { let autoUpdateManager beforeEach(() => { - autoUpdateManager = new AutoUpdateManager({ - applicationDelegate: atom.applicationDelegate - }) + autoUpdateManager = new AutoUpdateManager({applicationDelegate: atom.applicationDelegate}) + autoUpdateManager.initialize() }) afterEach(() => { diff --git a/spec/command-installer-spec.coffee b/spec/command-installer-spec.coffee index a1cf194a8..f0994fc08 100644 --- a/spec/command-installer-spec.coffee +++ b/spec/command-installer-spec.coffee @@ -25,7 +25,8 @@ describe "CommandInstaller on #darwin", -> it "shows an error dialog when installing commands interactively fails", -> appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"]) - installer = new CommandInstaller("2.0.2", appDelegate) + installer = new CommandInstaller(appDelegate) + installer.initialize("2.0.2") spyOn(installer, "installAtomCommand").andCallFake (__, callback) -> callback(new Error("an error")) installer.installShellCommandsInteractively() @@ -48,7 +49,8 @@ describe "CommandInstaller on #darwin", -> it "shows a success dialog when installing commands interactively succeeds", -> appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"]) - installer = new CommandInstaller("2.0.2", appDelegate) + installer = new CommandInstaller(appDelegate) + installer.initialize("2.0.2") spyOn(installer, "installAtomCommand").andCallFake (__, callback) -> callback() spyOn(installer, "installApmCommand").andCallFake (__, callback) -> callback() @@ -61,7 +63,8 @@ describe "CommandInstaller on #darwin", -> describe "when using a stable version of atom", -> beforeEach -> - installer = new CommandInstaller("2.0.2") + installer = new CommandInstaller() + installer.initialize("2.0.2") it "symlinks the atom command as 'atom'", -> installedAtomPath = path.join(installationPath, 'atom') @@ -91,7 +94,8 @@ describe "CommandInstaller on #darwin", -> describe "when using a beta version of atom", -> beforeEach -> - installer = new CommandInstaller("2.2.0-beta.0") + installer = new CommandInstaller() + installer.initialize("2.2.0-beta.0") it "symlinks the atom command as 'atom-beta'", -> installedAtomPath = path.join(installationPath, 'atom-beta') diff --git a/spec/context-menu-manager-spec.coffee b/spec/context-menu-manager-spec.coffee index c39f33cea..f99d7b004 100644 --- a/spec/context-menu-manager-spec.coffee +++ b/spec/context-menu-manager-spec.coffee @@ -5,7 +5,8 @@ describe "ContextMenuManager", -> beforeEach -> {resourcePath} = atom.getLoadSettings() - contextMenu = new ContextMenuManager({resourcePath, keymapManager: atom.keymaps}) + contextMenu = new ContextMenuManager({keymapManager: atom.keymaps}) + contextMenu.initialize({resourcePath}) parent = document.createElement("div") child = document.createElement("div") diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index ed6aac626..a4b6d4c1b 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -30,7 +30,8 @@ describe("HistoryManager", () => { return projectDisposable }) - historyManager = new HistoryManager({stateStore, localStorage: window.localStorage, project, commands: commandRegistry}) + historyManager = new HistoryManager({stateStore, project, commands: commandRegistry}) + historyManager.initialize({localStorage: window.localStorage}) await historyManager.loadState() }) diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index a223e0b03..a8f1aafe6 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -56,14 +56,16 @@ export default async function () { TextEditor.setClipboard(clipboard) const applicationDelegate = new ApplicationDelegate() - global.atom = new AtomEnvironment({ + const environmentParams = { applicationDelegate, window, document, clipboard, configDirPath: process.env.ATOM_HOME, enablePersistence: false - }) + } + global.atom = new AtomEnvironment(environmentParams) + global.atom.initialize(environmentParams) // Prevent benchmarks from modifying application menus global.atom.menu.sendToBrowserProcess = function () { } diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee index 794db3174..e87586374 100644 --- a/src/initialize-test-window.coffee +++ b/src/initialize-test-window.coffee @@ -79,7 +79,9 @@ module.exports = ({blobStore}) -> params.clipboard = clipboard unless params.hasOwnProperty("clipboard") params.blobStore = blobStore unless params.hasOwnProperty("blobStore") params.onlyLoadBaseStyleSheets = true unless params.hasOwnProperty("onlyLoadBaseStyleSheets") - new AtomEnvironment(params) + atomEnvironment = new AtomEnvironment(params) + atomEnvironment.initialize(params) + atomEnvironment promise = testRunner({ logFile, headless, testPaths, buildAtomEnvironment, buildDefaultApplicationDelegate, legacyTestRunner From b1704ee7e6e7a14ab4b7632c298ce2db1734555b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 11 Mar 2017 19:20:16 +0100 Subject: [PATCH 157/194] Fix Workspace specs --- spec/pane-container-spec.coffee | 20 ++++++++++++++++++++ spec/workspace-spec.js | 9 +++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 84c6c4fc9..07b9f4763 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -23,6 +23,7 @@ describe "PaneContainer", -> serialize: -> deserializer: 'Item' containerA = new PaneContainer(params) + containerA.initialize() pane1A = containerA.getActivePane() pane1A.addItem(new Item) pane2A = pane1A.splitRight(items: [new Item]) @@ -33,6 +34,7 @@ describe "PaneContainer", -> expect(pane3A.focused).toBe true containerB = new PaneContainer(params) + containerB.initialize() containerB.deserialize(containerA.serialize(), atom.deserializers) [pane1B, pane2B, pane3B] = containerB.getPanes() expect(pane3B.focused).toBe true @@ -42,6 +44,7 @@ describe "PaneContainer", -> expect(containerA.getActivePane()).toBe pane3A containerB = new PaneContainer(params) + containerB.initialize() containerB.deserialize(containerA.serialize(), atom.deserializers) [pane1B, pane2B, pane3B] = containerB.getPanes() expect(containerB.getActivePane()).toBe pane3B @@ -51,6 +54,7 @@ describe "PaneContainer", -> state = containerA.serialize() state.activePaneId = -22 containerB = new PaneContainer(params) + containerB.initialize() containerB.deserialize(state, atom.deserializers) expect(containerB.getActivePane()).toBe containerB.getPanes()[0] @@ -62,6 +66,7 @@ describe "PaneContainer", -> it "leaves the empty panes intact", -> state = containerA.serialize() containerB = new PaneContainer(params) + containerB.initialize() containerB.deserialize(state, atom.deserializers) [leftPane, column] = containerB.getRoot().getChildren() [topPane, bottomPane] = column.getChildren() @@ -76,6 +81,7 @@ describe "PaneContainer", -> state = containerA.serialize() containerB = new PaneContainer(params) + containerB.initialize() containerB.deserialize(state, atom.deserializers) [leftPane, rightPane] = containerB.getRoot().getChildren() @@ -84,6 +90,7 @@ describe "PaneContainer", -> it "does not allow the root pane to be destroyed", -> container = new PaneContainer(params) + container.initialize() container.getRoot().destroy() expect(container.getRoot()).toBeDefined() expect(container.getRoot().isDestroyed()).toBe false @@ -93,6 +100,7 @@ describe "PaneContainer", -> beforeEach -> container = new PaneContainer(params) + container.initialize() pane1 = container.getRoot() it "returns the first pane if no pane has been made active", -> @@ -122,6 +130,7 @@ describe "PaneContainer", -> beforeEach -> container = new PaneContainer(params) + container.initialize() container.getRoot().addItems([new Object, new Object]) container.getRoot().splitRight(items: [new Object, new Object]) [pane1, pane2] = container.getPanes() @@ -144,6 +153,7 @@ describe "PaneContainer", -> beforeEach -> container = new PaneContainer(root: new Pane(items: [new Object, new Object])) + container.initialize() container.getRoot().splitRight(items: [new Object, new Object]) [pane1, pane2] = container.getPanes() @@ -165,6 +175,7 @@ describe "PaneContainer", -> describe "::observePanes()", -> it "invokes observers with all current and future panes", -> container = new PaneContainer(params) + container.initialize() container.getRoot().splitRight() [pane1, pane2] = container.getPanes() @@ -179,6 +190,7 @@ describe "PaneContainer", -> describe "::observePaneItems()", -> it "invokes observers with all current and future pane items", -> container = new PaneContainer(params) + container.initialize() container.getRoot().addItems([new Object, new Object]) container.getRoot().splitRight(items: [new Object]) [pane1, pane2] = container.getPanes() @@ -199,6 +211,7 @@ describe "PaneContainer", -> getURI: -> 'test' container = new PaneContainer(params) + container.initialize() container.getRoot().splitRight() [pane1, pane2] = container.getPanes() pane1.addItem(new TestItem) @@ -219,6 +232,7 @@ describe "PaneContainer", -> describe "::onDidAddPane(callback)", -> it "invokes the given callback when panes are added", -> container = new PaneContainer(params) + container.initialize() events = [] container.onDidAddPane (event) -> expect(event.pane in container.getPanes()).toBe true @@ -238,6 +252,7 @@ describe "PaneContainer", -> isDestroyed: -> @_isDestroyed container = new PaneContainer(params) + container.initialize() events = [] container.onWillDestroyPane (event) -> itemsDestroyed = (item.isDestroyed() for item in event.pane.getItems()) @@ -254,6 +269,7 @@ describe "PaneContainer", -> describe "::onDidDestroyPane(callback)", -> it "invokes the given callback when panes are destroyed", -> container = new PaneContainer(params) + container.initialize() events = [] container.onDidDestroyPane (event) -> expect(event.pane in container.getPanes()).toBe false @@ -270,6 +286,7 @@ describe "PaneContainer", -> it "invokes the given callback when the container is destroyed", -> container = new PaneContainer(params) + container.initialize() events = [] container.onDidDestroyPane (event) -> expect(event.pane in container.getPanes()).toBe false @@ -286,6 +303,7 @@ describe "PaneContainer", -> describe "::onWillDestroyPaneItem() and ::onDidDestroyPaneItem", -> it "invokes the given callbacks when an item will be destroyed on any pane", -> container = new PaneContainer(params) + container.initialize() pane1 = container.getRoot() item1 = new Object item2 = new Object @@ -313,6 +331,7 @@ describe "PaneContainer", -> describe "::saveAll()", -> it "saves all modified pane items", -> container = new PaneContainer(params) + container.initialize() pane1 = container.getRoot() pane2 = pane1.splitRight() @@ -354,6 +373,7 @@ describe "PaneContainer", -> copy: -> new TestItem(@id) container = new PaneContainer(params) + container.initialize() pane1 = container.getRoot() item1 = new TestItem('1') pane2 = pane1.splitRight(items: [item1]) diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 43e4fbae4..17346b74d 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -53,6 +53,7 @@ describe('Workspace', () => { assert: atom.assert.bind(atom), textEditorRegistry: atom.textEditors }) + atom.workspace.initialize() return atom.workspace.deserialize(workspaceState, atom.deserializers) } @@ -975,8 +976,8 @@ i = /test/; #FIXME\ ` ) - const atom2 = new AtomEnvironment({ - applicationDelegate: atom.applicationDelegate, + const atom2 = new AtomEnvironment({applicationDelegate: atom.applicationDelegate}) + atom2.initialize({ window: document.createElement('div'), document: Object.assign( document.createElement('div'), @@ -1115,8 +1116,8 @@ i = /test/; #FIXME\ it("updates the title to contain the project's path", () => { document.title = null - const atom2 = new AtomEnvironment({ - applicationDelegate: atom.applicationDelegate, + const atom2 = new AtomEnvironment({applicationDelegate: atom.applicationDelegate}) + atom2.initialize({ window: document.createElement('div'), document: Object.assign( document.createElement('div'), From ea440148d86e44dfcbe8d22325138402d994fb85 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sun, 12 Mar 2017 11:49:48 +0100 Subject: [PATCH 158/194] Fix remaining tests --- spec/history-manager-spec.js | 10 +++++----- spec/menu-manager-spec.coffee | 7 ++----- spec/view-registry-spec.coffee | 1 + spec/window-event-handler-spec.coffee | 3 ++- src/atom-environment.coffee | 5 +++++ 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index a4b6d4c1b..ed07446eb 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -12,8 +12,8 @@ describe("HistoryManager", () => { beforeEach(async () => { commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']) - commandRegistry.add.andReturn(commandDisposable) + commandRegistry = jasmine.createSpyObj('CommandRegistry', ['addBundled']) + commandRegistry.addBundled.andReturn(commandDisposable) stateStore = new StateStore('history-manager-test', 1) await stateStore.save('history-manager', { @@ -31,7 +31,7 @@ describe("HistoryManager", () => { }) historyManager = new HistoryManager({stateStore, project, commands: commandRegistry}) - historyManager.initialize({localStorage: window.localStorage}) + historyManager.initialize(window.localStorage) await historyManager.loadState() }) @@ -41,8 +41,8 @@ describe("HistoryManager", () => { describe("constructor", () => { it("registers the 'clear-project-history' command function", () => { - expect(commandRegistry.add).toHaveBeenCalled() - const cmdCall = commandRegistry.add.calls[0] + expect(commandRegistry.addBundled).toHaveBeenCalled() + const cmdCall = commandRegistry.addBundled.calls[0] expect(cmdCall.args.length).toBe(2) expect(cmdCall.args[0]).toBe('atom-workspace') expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe('function') diff --git a/spec/menu-manager-spec.coffee b/spec/menu-manager-spec.coffee index 2db6f35a0..926d855a6 100644 --- a/spec/menu-manager-spec.coffee +++ b/spec/menu-manager-spec.coffee @@ -5,11 +5,8 @@ describe "MenuManager", -> menu = null beforeEach -> - menu = new MenuManager( - resourcePath: atom.getLoadSettings().resourcePath - keymapManager: atom.keymaps - packageManager: atom.packages - ) + menu = new MenuManager({keymapManager: atom.keymaps, packageManager: atom.packages}) + menu.initialize({resourcePath: atom.getLoadSettings().resourcePath}) describe "::add(items)", -> it "can add new menus that can be removed with the returned disposable", -> diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index 68a482b48..d805d17cb 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -5,6 +5,7 @@ describe "ViewRegistry", -> beforeEach -> registry = new ViewRegistry + registry.initialize() afterEach -> registry.clearDocumentRequests() diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee index e9a7894c3..d4387f23a 100644 --- a/spec/window-event-handler-spec.coffee +++ b/spec/window-event-handler-spec.coffee @@ -15,7 +15,8 @@ describe "WindowEventHandler", -> loadSettings.initialPath = initialPath loadSettings atom.project.destroy() - windowEventHandler = new WindowEventHandler({atomEnvironment: atom, applicationDelegate: atom.applicationDelegate, window, document}) + windowEventHandler = new WindowEventHandler({atomEnvironment: atom, applicationDelegate: atom.applicationDelegate}) + windowEventHandler.initialize(window, document) afterEach -> windowEventHandler.unsubscribe() diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 9cda07865..13f8ae3c9 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -792,8 +792,13 @@ class AtomEnvironment extends Model uninstallUncaughtErrorHandler: -> @window.onerror = @previousWindowErrorHandler + installWindowEventHandler: -> + @windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate}) + @windowEventHandler.initialize(@window, @document) + uninstallWindowEventHandler: -> @windowEventHandler?.unsubscribe() + @windowEventHandler = null ### Section: Messaging the User From 0d29004723c478ee2acaa8475dedf8f833819d02 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Mar 2017 19:11:53 +0100 Subject: [PATCH 159/194] Speed up FileSystemBlobStore.load by not storing invalidation keys This was unneeded because we can simply compute the cache key by concatenating the v8 version and the file's contents. --- spec/file-system-blob-store-spec.coffee | 84 ++++++++++++------------- spec/native-compile-cache-spec.coffee | 25 +++----- src/file-system-blob-store.js | 21 ++----- src/native-compile-cache.js | 9 ++- 4 files changed, 58 insertions(+), 81 deletions(-) diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee index 70e4d2b8d..ff1c81eb6 100644 --- a/spec/file-system-blob-store-spec.coffee +++ b/spec/file-system-blob-store-spec.coffee @@ -14,79 +14,75 @@ describe "FileSystemBlobStore", -> fs.removeSync(storageDirectory) it "is empty when the file doesn't exist", -> - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("foo")).toBeUndefined() + expect(blobStore.get("bar")).toBeUndefined() it "allows to read and write buffers from/to memory without persisting them", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.set("foo", new Buffer("foo")) + blobStore.set("bar", new Buffer("bar")) - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) + expect(blobStore.get("foo")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar")).toEqual(new Buffer("bar")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() + expect(blobStore.get("baz")).toBeUndefined() + expect(blobStore.get("qux")).toBeUndefined() it "persists buffers when saved and retrieves them on load, giving priority to in-memory ones", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.set("foo", new Buffer("foo")) + blobStore.set("bar", new Buffer("bar")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() + expect(blobStore.get("foo")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar")).toEqual(new Buffer("bar")) + expect(blobStore.get("baz")).toBeUndefined() + expect(blobStore.get("qux")).toBeUndefined() - blobStore.set("foo", "new-key", new Buffer("changed")) + blobStore.set("foo", new Buffer("changed")) - expect(blobStore.get("foo", "new-key")).toEqual(new Buffer("changed")) - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() + expect(blobStore.get("foo")).toEqual(new Buffer("changed")) it "persists in-memory and previously stored buffers, and deletes unused keys when saved", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.set("foo", new Buffer("foo")) + blobStore.set("bar", new Buffer("bar")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.set("bar", "invalidation-key-3", new Buffer("changed")) - blobStore.set("qux", "invalidation-key-4", new Buffer("qux")) + blobStore.set("bar", new Buffer("changed")) + blobStore.set("qux", new Buffer("qux")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-3")).toEqual(new Buffer("changed")) - expect(blobStore.get("qux", "invalidation-key-4")).toEqual(new Buffer("qux")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() - expect(blobStore.get("qux", "unexisting-key")).toBeUndefined() + expect(blobStore.get("foo")).toBeUndefined() + expect(blobStore.get("bar")).toEqual(new Buffer("changed")) + expect(blobStore.get("qux")).toEqual(new Buffer("qux")) it "allows to delete keys from both memory and stored buffers", -> - blobStore.set("a", "invalidation-key-1", new Buffer("a")) - blobStore.set("b", "invalidation-key-2", new Buffer("b")) + blobStore.set("a", new Buffer("a")) + blobStore.set("b", new Buffer("b")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.get("a", "invalidation-key-1") # prevent the key from being deleted on save - blobStore.set("b", "invalidation-key-3", new Buffer("b")) - blobStore.set("c", "invalidation-key-4", new Buffer("c")) + blobStore.get("a") # prevent the key from being deleted on save + blobStore.set("b", new Buffer("b")) + blobStore.set("c", new Buffer("c")) blobStore.delete("b") blobStore.delete("c") blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("a")) - expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() - expect(blobStore.get("b", "invalidation-key-3")).toBeUndefined() - expect(blobStore.get("c", "invalidation-key-4")).toBeUndefined() + expect(blobStore.get("a")).toEqual(new Buffer("a")) + expect(blobStore.get("b")).toBeUndefined() + expect(blobStore.get("b")).toBeUndefined() + expect(blobStore.get("c")).toBeUndefined() it "ignores errors when loading an invalid blob store", -> - blobStore.set("a", "invalidation-key-1", new Buffer("a")) - blobStore.set("b", "invalidation-key-2", new Buffer("b")) + blobStore.set("a", new Buffer("a")) + blobStore.set("b", new Buffer("b")) blobStore.save() # Simulate corruption @@ -96,14 +92,14 @@ describe "FileSystemBlobStore", -> blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("a")).toBeUndefined() + expect(blobStore.get("b")).toBeUndefined() - blobStore.set("a", "invalidation-key-1", new Buffer("x")) - blobStore.set("b", "invalidation-key-2", new Buffer("y")) + blobStore.set("a", new Buffer("x")) + blobStore.set("b", new Buffer("y")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("x")) - expect(blobStore.get("b", "invalidation-key-2")).toEqual(new Buffer("y")) + expect(blobStore.get("a")).toEqual(new Buffer("x")) + expect(blobStore.get("b")).toEqual(new Buffer("y")) diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee index 1531deaf9..a43cbe815 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -9,16 +9,18 @@ describe "NativeCompileCache", -> beforeEach -> cachedFiles = [] fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]) - fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> - fakeCacheStore.get(cacheKey, invalidationKey)? - fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> + + fakeCacheStore.has.andCallFake (cacheKey) -> + fakeCacheStore.get(cacheKey)? + + fakeCacheStore.get.andCallFake (cacheKey) -> for entry in cachedFiles by -1 continue if entry.cacheKey isnt cacheKey - continue if entry.invalidationKey isnt invalidationKey return entry.cacheBuffer return - fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) -> - cachedFiles.push({cacheKey, invalidationKey, cacheBuffer}) + + fakeCacheStore.set.andCallFake (cacheKey, cacheBuffer) -> + cachedFiles.push({cacheKey, cacheBuffer}) nativeCompileCache.setCacheStore(fakeCacheStore) nativeCompileCache.setV8Version("a-v8-version") @@ -29,13 +31,10 @@ describe "NativeCompileCache", -> fn2 = require('./fixtures/native-cache/file-2') expect(cachedFiles.length).toBe(2) - - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn1()).toBe(1) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-2')) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) expect(fn2()).toBe(2) @@ -51,7 +50,6 @@ describe "NativeCompileCache", -> fn4 = require('./fixtures/native-cache/file-4') expect(cachedFiles.length).toBe(1) - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn4()).toBe("file-4") @@ -61,8 +59,6 @@ describe "NativeCompileCache", -> fn4 = require('./fixtures/native-cache/file-4') expect(cachedFiles.length).toBe(2) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) - expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) @@ -79,7 +75,6 @@ describe "NativeCompileCache", -> fn5 = require('./fixtures/native-cache/file-5') expect(cachedFiles.length).toBe(1) - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn5()).toBe("file-5") @@ -89,8 +84,6 @@ describe "NativeCompileCache", -> fn5 = require('./fixtures/native-cache/file-5') expect(cachedFiles.length).toBe(2) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) - expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) @@ -100,5 +93,5 @@ describe "NativeCompileCache", -> fn3 = require('./fixtures/native-cache/file-3') - expect(fakeCacheStore.delete).toHaveBeenCalledWith(require.resolve('./fixtures/native-cache/file-3')) + expect(fakeCacheStore.delete).toHaveBeenCalled() expect(fn3()).toBe(3) diff --git a/src/file-system-blob-store.js b/src/file-system-blob-store.js index 828e23e94..67a959735 100644 --- a/src/file-system-blob-store.js +++ b/src/file-system-blob-store.js @@ -14,14 +14,12 @@ class FileSystemBlobStore { constructor (directory) { this.blobFilename = path.join(directory, 'BLOB') this.blobMapFilename = path.join(directory, 'MAP') - this.invalidationKeysFilename = path.join(directory, 'INVKEYS') this.lockFilename = path.join(directory, 'LOCK') this.reset() } reset () { this.inMemoryBlobs = new Map() - this.invalidationKeys = {} this.storedBlob = new Buffer(0) this.storedBlobMap = {} this.usedKeys = new Set() @@ -34,14 +32,10 @@ class FileSystemBlobStore { if (!fs.existsSync(this.blobFilename)) { return } - if (!fs.existsSync(this.invalidationKeysFilename)) { - return - } try { this.storedBlob = fs.readFileSync(this.blobFilename) this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) - this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename)) } catch (e) { this.reset() } @@ -51,7 +45,6 @@ class FileSystemBlobStore { let dump = this.getDump() let blobToStore = Buffer.concat(dump[0]) let mapToStore = JSON.stringify(dump[1]) - let invalidationKeysToStore = JSON.stringify(this.invalidationKeys) let acquiredLock = false try { @@ -60,7 +53,6 @@ class FileSystemBlobStore { fs.writeFileSync(this.blobFilename, blobToStore) fs.writeFileSync(this.blobMapFilename, mapToStore) - fs.writeFileSync(this.invalidationKeysFilename, invalidationKeysToStore) } catch (error) { // Swallow the exception silently only if we fail to acquire the lock. if (error.code !== 'EEXIST') { @@ -73,22 +65,19 @@ class FileSystemBlobStore { } } - has (key, invalidationKey) { - let containsKey = this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) - let isValid = this.invalidationKeys[key] === invalidationKey - return containsKey && isValid + has (key) { + return this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) } - get (key, invalidationKey) { - if (this.has(key, invalidationKey)) { + get (key) { + if (this.has(key)) { this.usedKeys.add(key) return this.getFromMemory(key) || this.getFromStorage(key) } } - set (key, invalidationKey, buffer) { + set (key, buffer) { this.usedKeys.add(key) - this.invalidationKeys[key] = invalidationKey return this.inMemoryBlobs.set(key, buffer) } diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index b1867ff55..09a62b186 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -60,11 +60,10 @@ class NativeCompileCache { // create wrapper function let wrapper = Module.wrap(content) - let cacheKey = filename - let invalidationKey = computeHash(wrapper + self.v8Version) + let cacheKey = computeHash(wrapper + self.v8Version) let compiledWrapper = null - if (self.cacheStore.has(cacheKey, invalidationKey)) { - let buffer = self.cacheStore.get(cacheKey, invalidationKey) + if (self.cacheStore.has(cacheKey)) { + let buffer = self.cacheStore.get(cacheKey) let compilationResult = cachedVm.runInThisContextCached(wrapper, filename, buffer) compiledWrapper = compilationResult.result if (compilationResult.wasRejected) { @@ -79,7 +78,7 @@ class NativeCompileCache { throw err } if (compilationResult.cacheBuffer) { - self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) + self.cacheStore.set(cacheKey, compilationResult.cacheBuffer) } compiledWrapper = compilationResult.result } From 56aae269e9cc9e5be65ee13a482b427d8520719b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Mar 2017 15:03:34 +0100 Subject: [PATCH 160/194] :arrow_up: autocomplete-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f398dad46..b0577caa5 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.15.1", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.34.2", + "autocomplete-plus": "2.35.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.29.0", "autosave": "0.24.1", From 4f87dad60453710649d6c1f99014f2d0af9506c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Mar 2017 18:01:04 +0100 Subject: [PATCH 161/194] :arrow_up: incompatible-packages --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b0577caa5..2aebbcb25 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "go-to-line": "0.32.0", "grammar-selector": "0.49.3", "image-view": "0.61.2", - "incompatible-packages": "0.27.1", + "incompatible-packages": "0.27.2", "keybinding-resolver": "0.36.4", "line-ending-selector": "0.6.2", "link": "0.31.3", From 09b9e57cb4ab4f2fb58ad17f52f3149bbed7cb28 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Mar 2017 11:57:26 -0700 Subject: [PATCH 162/194] :arrow_up: Upgrade to Electron 1.3.14 with snapshot patch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f972b4890..71ce9bb07 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/atom/atom/issues" }, "license": "MIT", - "electronVersion": "1.3.13", + "electronVersion": "1.3.14", "dependencies": { "async": "0.2.6", "atom-keymap": "8.0.2", From 52fb79172c1a2d9723108f5a309c958ae41878fb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Mar 2017 12:17:50 -0700 Subject: [PATCH 163/194] Dowloading -> Downloading --- script/lib/download-file-from-github.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/download-file-from-github.js b/script/lib/download-file-from-github.js index 2969ea2dc..13e04e99e 100644 --- a/script/lib/download-file-from-github.js +++ b/script/lib/download-file-from-github.js @@ -5,7 +5,7 @@ const path = require('path') const syncRequest = require('sync-request') module.exports = function (downloadURL, destinationPath) { - console.log(`Dowloading file from GitHub Repository to ${destinationPath}`) + console.log(`Downloading file from GitHub Repository to ${destinationPath}`) const response = syncRequest('GET', downloadURL, { 'headers': {'Accept': 'application/vnd.github.v3.raw', 'User-Agent': 'Atom Build'} }) From 7caeb3d8529f6e0e29bebf1c29297d660825eb9a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Mar 2017 09:49:05 +0100 Subject: [PATCH 164/194] Clear compile-cache on AppVeyor --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e94e75441..7c243fec9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,5 +51,4 @@ cache: - '%APPVEYOR_BUILD_FOLDER%\node_modules' - '%APPVEYOR_BUILD_FOLDER%\electron' - '%USERPROFILE%\.atom\.apm' - - '%USERPROFILE%\.atom\compile-cache' - '%USERPROFILE%\.atom\snapshot-cache' From d95a5f1d3c94463b3e9b0d491f52572672daf828 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Mar 2017 10:38:13 +0100 Subject: [PATCH 165/194] Revert "Clear compile-cache on AppVeyor" This reverts commit 7caeb3d8529f6e0e29bebf1c29297d660825eb9a. --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 7c243fec9..e94e75441 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,4 +51,5 @@ cache: - '%APPVEYOR_BUILD_FOLDER%\node_modules' - '%APPVEYOR_BUILD_FOLDER%\electron' - '%USERPROFILE%\.atom\.apm' + - '%USERPROFILE%\.atom\compile-cache' - '%USERPROFILE%\.atom\snapshot-cache' From b2983f63bab0b74988a2a6bc58157a8c9d616d8f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Mar 2017 13:26:29 +0100 Subject: [PATCH 166/194] Replace backward with forward slashes when requiring files on Windows --- static/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/index.js b/static/index.js index f7d2d4c2f..6297eb5ef 100644 --- a/static/index.js +++ b/static/index.js @@ -38,7 +38,10 @@ } else if (useSnapshot) { Module.prototype.require = function (module) { const absoluteFilePath = Module._resolveFilename(module, this, false) - const relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) + let relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) + if (process.platform === 'win32') { + relativeFilePath = relativeFilePath.replace(/\\/g, '/') + } let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] // eslint-disable-line no-undef if (!cachedModule) { cachedModule = {exports: Module._load(module, this, false)} From 9ff213d27ba60329cd76eb4d4f38e2c78257547e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Mar 2017 10:13:41 +0100 Subject: [PATCH 167/194] :arrow_up: autocomplete-html --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2aebbcb25..8d31fc6f3 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "archive-view": "0.63.1", "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.15.1", - "autocomplete-html": "0.7.2", + "autocomplete-html": "0.7.3", "autocomplete-plus": "2.35.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.29.0", From 6da748ee6702ece78d26b5e4dcc5a5a4301f047b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Mar 2017 10:52:09 +0100 Subject: [PATCH 168/194] :arrow_up: autocomplete-atom-api and autocomplete-css --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8d31fc6f3..1a8fc774c 100644 --- a/package.json +++ b/package.json @@ -97,8 +97,8 @@ "solarized-light-syntax": "1.1.2", "about": "1.7.5", "archive-view": "0.63.1", - "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.15.1", + "autocomplete-atom-api": "0.10.1", + "autocomplete-css": "0.15.2", "autocomplete-html": "0.7.3", "autocomplete-plus": "2.35.0", "autocomplete-snippets": "1.11.0", From ad6a5ac3bf23620e17308093928d6436a749c8c3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Mar 2017 15:57:44 +0100 Subject: [PATCH 169/194] Don't send `did-resolve-proxy` messages to destroyed windows This should fix some main process test failures we are observing on AppVeyor. --- src/main-process/atom-application.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index e6f40379d..1faab1b78 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -284,7 +284,9 @@ class AtomApplication @restart() @disposable.add ipcHelpers.on ipcMain, 'resolve-proxy', (event, requestId, url) -> - event.sender.session.resolveProxy url, (proxy) -> event.sender.send('did-resolve-proxy', requestId, proxy) + event.sender.session.resolveProxy url, (proxy) -> + unless event.sender.isDestroyed() + event.sender.send('did-resolve-proxy', requestId, proxy) @disposable.add ipcHelpers.on ipcMain, 'did-change-history-manager', (event) => for atomWindow in @windows From df1210cc3fc3b72529dac6a1b13e6327d5ec7cac Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 16 Mar 2017 09:34:19 -0700 Subject: [PATCH 170/194] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 699559ac7..5c7dd601b 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "dev-live-reload": "0.47.1", "encoding-selector": "0.23.2", "exception-reporting": "0.41.3", - "find-and-replace": "0.207.2", + "find-and-replace": "0.207.3", "fuzzy-finder": "1.5.1", "git-diff": "1.3.4", "go-to-line": "0.32.0", From 306968aa53fa8ce2d186768f8f67fef8bcab7494 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Mar 2017 13:20:32 -0600 Subject: [PATCH 171/194] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c7dd601b..86509a786 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "11.3.0", + "text-buffer": "11.4.0", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From e3596ce7a925265fec7440e66ee8ff13ec833711 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Mar 2017 21:09:18 +0100 Subject: [PATCH 172/194] :fire: Remove parserlib --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 64cbe4468..86509a786 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "normalize-package-data": "^2.0.0", "nslog": "^3", "oniguruma": "6.1.0", - "parserlib": "^1.1.1", "pathwatcher": "7.0.0", "postcss": "5.2.4", "postcss-selector-parser": "2.2.1", From c1dbf488d9e560a0f32adaf2b2b0803348a3d47c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Mar 2017 21:09:51 +0100 Subject: [PATCH 173/194] Delete unused build scripts --- script/lib/fast-clean-output-directory.js | 19 --------- script/lib/fast-copy-assets.js | 31 -------------- script/lib/fast-transpile-babel-paths.js | 36 ---------------- .../lib/fast-transpile-coffee-script-paths.js | 28 ------------- script/tdd | 42 ------------------- 5 files changed, 156 deletions(-) delete mode 100644 script/lib/fast-clean-output-directory.js delete mode 100644 script/lib/fast-copy-assets.js delete mode 100644 script/lib/fast-transpile-babel-paths.js delete mode 100644 script/lib/fast-transpile-coffee-script-paths.js delete mode 100755 script/tdd diff --git a/script/lib/fast-clean-output-directory.js b/script/lib/fast-clean-output-directory.js deleted file mode 100644 index b1053eba2..000000000 --- a/script/lib/fast-clean-output-directory.js +++ /dev/null @@ -1,19 +0,0 @@ -const fs = require('fs-extra') -const path = require('path') -const CONFIG = require('../config') - -module.exports = function () { - let srcPaths = [ - path.join('benchmarks', 'benchmark-runner.js'), - path.join('dot-atom'), - path.join('exports'), - path.join('package.json'), - path.join('static'), - path.join('src'), - path.join('vendor') - ] - - for (const srcPath of srcPaths) { - fs.removeSync(path.join(CONFIG.intermediateAppPath, srcPath)) - } -} diff --git a/script/lib/fast-copy-assets.js b/script/lib/fast-copy-assets.js deleted file mode 100644 index 1602a7af8..000000000 --- a/script/lib/fast-copy-assets.js +++ /dev/null @@ -1,31 +0,0 @@ -// This module exports a function that copies all the static assets into the -// appropriate location in the build output directory. - -'use strict' - -const path = require('path') -const fs = require('fs-extra') -const CONFIG = require('../config') -const glob = require('glob') -const includePathInPackagedApp = require('./include-path-in-packaged-app') - -module.exports = function () { - console.log(`Copying assets to ${CONFIG.intermediateAppPath}`); - let srcPaths = [ - path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'), - path.join(CONFIG.repositoryRootPath, 'dot-atom'), - path.join(CONFIG.repositoryRootPath, 'exports'), - path.join(CONFIG.repositoryRootPath, 'package.json'), - path.join(CONFIG.repositoryRootPath, 'static'), - path.join(CONFIG.repositoryRootPath, 'src'), - path.join(CONFIG.repositoryRootPath, 'vendor') - ] - for (let srcPath of srcPaths) { - fs.copySync(srcPath, computeDestinationPath(srcPath), {filter: includePathInPackagedApp}) - } -} - -function computeDestinationPath (srcPath) { - const relativePath = path.relative(CONFIG.repositoryRootPath, srcPath) - return path.join(CONFIG.intermediateAppPath, relativePath) -} diff --git a/script/lib/fast-transpile-babel-paths.js b/script/lib/fast-transpile-babel-paths.js deleted file mode 100644 index a1901e7a0..000000000 --- a/script/lib/fast-transpile-babel-paths.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' - -const CompileCache = require('../../src/compile-cache') -const fs = require('fs') -const glob = require('glob') -const path = require('path') - -const CONFIG = require('../config') -const BABEL_OPTIONS = require('../../static/babelrc.json') -const BABEL_PREFIXES = [ - "'use babel'", - '"use babel"', - '/** @babel */', - '/* @flow */' -] -const PREFIX_LENGTH = Math.max.apply(null, BABEL_PREFIXES.map(prefix => prefix.length)) -const BUFFER = Buffer(PREFIX_LENGTH) - -module.exports = function () { - console.log(`Transpiling Babel paths in ${CONFIG.intermediateAppPath}`) - for (let path of getPathsToTranspile()) { - transpileBabelPath(path) - } -} - -function getPathsToTranspile () { - let paths = [] - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) - return paths -} - -function transpileBabelPath (path) { - fs.writeFileSync(path, CompileCache.addPathToCache(path, CONFIG.atomHomeDirPath)) -} diff --git a/script/lib/fast-transpile-coffee-script-paths.js b/script/lib/fast-transpile-coffee-script-paths.js deleted file mode 100644 index 7e080691f..000000000 --- a/script/lib/fast-transpile-coffee-script-paths.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict' - -const CompileCache = require('../../src/compile-cache') -const fs = require('fs') -const glob = require('glob') -const path = require('path') - -const CONFIG = require('../config') - -module.exports = function () { - console.log(`Transpiling CoffeeScript paths in ${CONFIG.intermediateAppPath}`) - for (let path of getPathsToTranspile()) { - transpileCoffeeScriptPath(path) - } -} - -function getPathsToTranspile () { - let paths = [] - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.coffee'))) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'spec', '*.coffee'))) - return paths -} - -function transpileCoffeeScriptPath (coffeePath) { - const jsPath = coffeePath.replace(/coffee$/g, 'js') - fs.writeFileSync(jsPath, CompileCache.addPathToCache(coffeePath, CONFIG.atomHomeDirPath)) - fs.unlinkSync(coffeePath) -} diff --git a/script/tdd b/script/tdd deleted file mode 100755 index db78ef3e4..000000000 --- a/script/tdd +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env node - -'use strict' - -// Needed so we can require src/module-cache.coffee during generateModuleCache -require('coffee-script/register') -require('colors') - -const yargs = require('yargs') -const argv = yargs - .usage('Usage: $0 [options]') - .help('help') - .describe('code-sign', 'Code-sign executables (macOS and Windows only)') - .describe('create-windows-installer', 'Create installer (Windows only)') - .describe('create-debian-package', 'Create .deb package (Linux only)') - .describe('create-rpm-package', 'Create .rpm package (Linux only)') - .describe('compress-artifacts', 'Compress Atom binaries (and symbols on macOS)') - .describe('install', 'Install Atom') - .wrap(yargs.terminalWidth()) - .argv - -const cleanOutputDirectory = require('./lib/fast-clean-output-directory') -const copyAssets = require('./lib/fast-copy-assets') -const generateMetadata = require('./lib/generate-metadata') -const generateModuleCache = require('./lib/generate-module-cache') -const generateStartupSnapshot = require('./lib/generate-startup-snapshot') -const packageApplication = require('./lib/package-application') -const transpileBabelPaths = require('./lib/fast-transpile-babel-paths') -const transpileCoffeeScriptPaths = require('./lib/fast-transpile-coffee-script-paths') - -process.on('unhandledRejection', function (e) { - console.error(e.stack || e) - process.exit(1) -}) - -cleanOutputDirectory() -copyAssets() -transpileBabelPaths() -transpileCoffeeScriptPaths() -generateModuleCache() -generateMetadata() -packageApplication().then(generateStartupSnapshot) From ab17e4042bd67683ebdfb29c2c7d40b0c8d00609 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 16 Mar 2017 17:19:24 -0400 Subject: [PATCH 174/194] :arrow_up: language-coffee-script@0.48.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86509a786..96ca72f50 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "wrap-guide": "0.40.0", "language-c": "0.57.0", "language-clojure": "0.22.2", - "language-coffee-script": "0.48.5", + "language-coffee-script": "0.48.6", "language-csharp": "0.14.2", "language-css": "0.42.1", "language-gfm": "0.88.1", From cbe60bd53ddc7fe6c72ec8ddb0b1cffde0fcae9a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 12:28:22 +0100 Subject: [PATCH 175/194] Use the generated snapshot source map in `source-map-support` This will report the correct file and line numbers on stack traces instead of always showing `:absoluteLineNumber`. As a result, it will also fix the `notifications` package, which will be able again to identify which package threw an exception and to create an issue on its repository. --- script/lib/generate-startup-snapshot.js | 16 ++++++++++++---- script/package.json | 2 +- src/atom-environment.coffee | 12 +++++++++--- src/compile-cache.js | 13 +++++++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 0021cf1ab..b32336a8b 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -58,12 +58,12 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ) } - }).then((snapshotScriptContent) => { - fs.writeFileSync(snapshotScriptPath, snapshotScriptContent) + }).then(({snapshotScript, sourceMap}) => { + fs.writeFileSync(snapshotScriptPath, snapshotScript) process.stdout.write('\n') console.log('Verifying if snapshot can be executed via `mksnapshot`') - vm.runInNewContext(snapshotScriptContent, undefined, {filename: snapshotScriptPath, displayErrors: true}) + vm.runInNewContext(snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true}) const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin') console.log(`Generating startup blob at "${generatedStartupBlobPath}"`) @@ -72,15 +72,23 @@ module.exports = function (packagedAppPath) { [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] ) - let startupBlobDestinationPath + let startupBlobDestinationPath, startupBlobSourceMapDestinationPath if (process.platform === 'darwin') { startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin` + startupBlobSourceMapDestinationPath = path.join(packagedAppPath, 'Contents', 'Resources', 'snapshot_sourcemap.json') + } else if (process.platform === 'linux') { + startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin') + startupBlobSourceMapDestinationPath = path.join(packagedAppPath, 'resources', 'snapshot_sourcemap.json') } else { startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin') + startupBlobSourceMapDestinationPath = path.join(packagedAppPath, 'resources', 'snapshot_sourcemap.json') } console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) fs.unlinkSync(startupBlobDestinationPath) fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) + + console.log(`Moving generated startup blob sourcemap into "${startupBlobSourceMapDestinationPath}"`) + fs.writeFileSync(startupBlobSourceMapDestinationPath, sourceMap) }) } diff --git a/script/package.json b/script/package.json index 9441213ac..107415ea5 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.18", + "electron-link": "0.0.19", "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 7594f5de9..65b47a74e 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -755,9 +755,15 @@ class AtomEnvironment extends Model @lastUncaughtError = Array::slice.call(arguments) [message, url, line, column, originalError] = @lastUncaughtError - {line, column} = mapSourcePosition({source: url, line, column}) + {line, column, source} = mapSourcePosition({source: url, line, column}) - eventObject = {message, url, line, column, originalError} + mappedURL = '' + if url is '' + mappedURL = source + else + mappedURL = url + + eventObject = {message, originalUrl: url, url: mappedURL, line, column, originalError} openDevTools = true eventObject.preventDefault = -> openDevTools = false @@ -767,7 +773,7 @@ class AtomEnvironment extends Model if openDevTools @openDevTools().then => @executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")') - @emitter.emit 'did-throw-error', {message, url, line, column, originalError} + @emitter.emit 'did-throw-error', {message, originalURL: url, url: mappedURL, line, column, originalError} uninstallUncaughtErrorHandler: -> @window.onerror = @previousWindowErrorHandler diff --git a/src/compile-cache.js b/src/compile-cache.js index 9b1966fc8..36275c5ed 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -113,6 +113,7 @@ function writeCachedJavascript (relativeCachePath, code) { } var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg +let snapshotSourceMap = null exports.install = function (nodeRequire) { sourceMapSupport.install({ @@ -122,6 +123,18 @@ exports.install = function (nodeRequire) { // source-map-support module, but we've overridden it to read the javascript // code from our cache directory. retrieveSourceMap: function (filePath) { + if (filePath === '') { + if (snapshotSourceMap == null) { + const snapshotSourceMapContent = fs.readFileSync(path.join(process.resourcesPath, 'snapshot_sourcemap.json'), 'utf8') + snapshotSourceMap = JSON.parse(snapshotSourceMapContent) + } + + return { + map: snapshotSourceMap, + url: path.join(process.resourcesPath, 'app', 'static', 'index.js') + } + } + if (!cacheDirectory || !fs.isFileSync(filePath)) { return null } From bade347e01920e4be7b94d7ed1988f60135e4e98 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 12:40:38 +0100 Subject: [PATCH 176/194] Revert "Upgrade to Babel 6 and apply fewer transformations when transpiling babel files" --- package.json | 13 +----------- script/package.json | 1 + .../file-recovery-service.test.js | 2 +- src/babel.js | 20 ++++--------------- static/babelrc.json | 17 ++++------------ 5 files changed, 11 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 96ca72f50..a74913fd8 100644 --- a/package.json +++ b/package.json @@ -18,18 +18,7 @@ "atom-keymap": "8.0.2", "atom-select-list": "0.0.15", "atom-ui": "0.4.1", - "babel-core": "6.22.1", - "babel-plugin-add-module-exports": "0.2.1", - "babel-plugin-transform-async-to-generator": "6.22.0", - "babel-plugin-transform-class-properties": "6.23.0", - "babel-plugin-transform-decorators-legacy": "1.3.4", - "babel-plugin-transform-do-expressions": "6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "6.23.0", - "babel-plugin-transform-export-extensions": "6.22.0", - "babel-plugin-transform-flow-strip-types": "6.22.0", - "babel-plugin-transform-function-bind": "6.22.0", - "babel-plugin-transform-object-rest-spread": "6.23.0", - "babel-plugin-transform-react-jsx": "6.23.0", + "babel-core": "5.8.38", "cached-run-in-this-context": "0.4.1", "chai": "3.5.0", "chart.js": "^2.3.0", diff --git a/script/package.json b/script/package.json index 9441213ac..9a4bcb84a 100644 --- a/script/package.json +++ b/script/package.json @@ -3,6 +3,7 @@ "description": "Atom build scripts", "dependencies": { "async": "2.0.1", + "babel-core": "5.8.38", "coffeelint": "1.15.7", "colors": "1.1.2", "csslint": "1.0.2", diff --git a/spec/main-process/file-recovery-service.test.js b/spec/main-process/file-recovery-service.test.js index 4821dbc9b..862b7f428 100644 --- a/spec/main-process/file-recovery-service.test.js +++ b/spec/main-process/file-recovery-service.test.js @@ -112,7 +112,7 @@ describe("FileRecoveryService", () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "content") - fs.chmodSync(filePath, 0o444) + fs.chmodSync(filePath, 0444) let logs = [] this.stub(console, 'log', (message) => logs.push(message)) diff --git a/src/babel.js b/src/babel.js index d72b29ffd..a944f2e8c 100644 --- a/src/babel.js +++ b/src/babel.js @@ -6,7 +6,6 @@ var defaultOptions = require('../static/babelrc.json') var babel = null var babelVersionDirectory = null -var options = null var PREFIXES = [ '/** @babel */', @@ -48,27 +47,16 @@ exports.compile = function (sourceCode, filePath) { var noop = function () {} Logger.prototype.debug = noop Logger.prototype.verbose = noop - - options = {ast: false, babelrc: false} - for (var key in defaultOptions) { - if (key === 'plugins') { - const plugins = [] - for (const [pluginName, pluginOptions] of defaultOptions[key]) { - plugins.push([require.resolve(`babel-plugin-${pluginName}`), pluginOptions]) - } - options[key] = plugins - } else { - options[key] = defaultOptions[key] - } - } } if (process.platform === 'win32') { filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/') } - options.filename = filePath - + var options = {filename: filePath} + for (var key in defaultOptions) { + options[key] = defaultOptions[key] + } return babel.transform(sourceCode, options).code } diff --git a/static/babelrc.json b/static/babelrc.json index 11474dd8d..26b70dc41 100644 --- a/static/babelrc.json +++ b/static/babelrc.json @@ -1,16 +1,7 @@ { + "breakConfig": true, "sourceMap": "inline", - "plugins": [ - ["add-module-exports", {}], - ["transform-async-to-generator", {}], - ["transform-decorators-legacy", {}], - ["transform-class-properties", {}], - ["transform-es2015-modules-commonjs", {"strictMode": false}], - ["transform-export-extensions", {}], - ["transform-do-expressions", {}], - ["transform-function-bind", {}], - ["transform-object-rest-spread", {}], - ["transform-flow-strip-types", {}], - ["transform-react-jsx", {}] - ] + "blacklist": ["es6.forOf", "useStrict"], + "optional": ["asyncToGenerator"], + "stage": 0 } From 0c8e515f9f3544f3e4bea7db41752d9efc6c7646 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 13:16:23 +0100 Subject: [PATCH 177/194] Don't change on{Will,Did}ThrowError events parameter --- src/atom-environment.coffee | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 65b47a74e..da4f768a2 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -757,13 +757,10 @@ class AtomEnvironment extends Model {line, column, source} = mapSourcePosition({source: url, line, column}) - mappedURL = '' if url is '' - mappedURL = source - else - mappedURL = url + url = source - eventObject = {message, originalUrl: url, url: mappedURL, line, column, originalError} + eventObject = {message, url, line, column, originalError} openDevTools = true eventObject.preventDefault = -> openDevTools = false @@ -773,7 +770,7 @@ class AtomEnvironment extends Model if openDevTools @openDevTools().then => @executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")') - @emitter.emit 'did-throw-error', {message, originalURL: url, url: mappedURL, line, column, originalError} + @emitter.emit 'did-throw-error', {message, url, line, column, originalError} uninstallUncaughtErrorHandler: -> @window.onerror = @previousWindowErrorHandler From ccbb276bf50cff02dfe9a1f9a548ce5239665dc8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 13:25:00 +0100 Subject: [PATCH 178/194] Bake source maps into the snapshot --- script/lib/generate-startup-snapshot.js | 12 ++---------- script/package.json | 2 +- src/compile-cache.js | 11 +++-------- src/main-process/start.js | 2 +- src/task.coffee | 2 +- static/index.js | 2 +- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index b32336a8b..cca844eda 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -58,7 +58,7 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ) } - }).then(({snapshotScript, sourceMap}) => { + }).then((snapshotScript) => { fs.writeFileSync(snapshotScriptPath, snapshotScript) process.stdout.write('\n') @@ -72,23 +72,15 @@ module.exports = function (packagedAppPath) { [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] ) - let startupBlobDestinationPath, startupBlobSourceMapDestinationPath + let startupBlobDestinationPath if (process.platform === 'darwin') { startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin` - startupBlobSourceMapDestinationPath = path.join(packagedAppPath, 'Contents', 'Resources', 'snapshot_sourcemap.json') - } else if (process.platform === 'linux') { - startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin') - startupBlobSourceMapDestinationPath = path.join(packagedAppPath, 'resources', 'snapshot_sourcemap.json') } else { startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin') - startupBlobSourceMapDestinationPath = path.join(packagedAppPath, 'resources', 'snapshot_sourcemap.json') } console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) fs.unlinkSync(startupBlobDestinationPath) fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) - - console.log(`Moving generated startup blob sourcemap into "${startupBlobSourceMapDestinationPath}"`) - fs.writeFileSync(startupBlobSourceMapDestinationPath, sourceMap) }) } diff --git a/script/package.json b/script/package.json index 107415ea5..4129cb1a6 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,7 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", - "electron-link": "0.0.19", + "electron-link": "0.0.20", "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", diff --git a/src/compile-cache.js b/src/compile-cache.js index 36275c5ed..b9a7d6cdd 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -115,7 +115,7 @@ function writeCachedJavascript (relativeCachePath, code) { var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg let snapshotSourceMap = null -exports.install = function (nodeRequire) { +exports.install = function (resourcesPath, nodeRequire) { sourceMapSupport.install({ handleUncaughtExceptions: false, @@ -124,14 +124,9 @@ exports.install = function (nodeRequire) { // code from our cache directory. retrieveSourceMap: function (filePath) { if (filePath === '') { - if (snapshotSourceMap == null) { - const snapshotSourceMapContent = fs.readFileSync(path.join(process.resourcesPath, 'snapshot_sourcemap.json'), 'utf8') - snapshotSourceMap = JSON.parse(snapshotSourceMapContent) - } - return { - map: snapshotSourceMap, - url: path.join(process.resourcesPath, 'app', 'static', 'index.js') + map: snapshotResult.sourceMap, + url: path.join(resourcesPath, 'app', 'static', 'index.js') } } diff --git a/src/main-process/start.js b/src/main-process/start.js index 368370939..29658d805 100644 --- a/src/main-process/start.js +++ b/src/main-process/start.js @@ -83,5 +83,5 @@ function handleStartupEventWithSquirrel () { function setupCompileCache () { const CompileCache = require('../compile-cache') CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - CompileCache.install(require) + CompileCache.install(process.resourcesPath, require) } diff --git a/src/task.coffee b/src/task.coffee index 5c7b08b8a..7a03310b5 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -76,7 +76,7 @@ class Task CompileCache = #{compileCacheRequire} CompileCache.setCacheDirectory('#{compileCachePath}'); - CompileCache.install(require) + CompileCache.install("#{process.resourcesPath}", require) #{taskBootstrapRequire} """ bootstrap = bootstrap.replace(/\\/g, "\\\\") diff --git a/static/index.js b/static/index.js index 6297eb5ef..ad7d096e1 100644 --- a/static/index.js +++ b/static/index.js @@ -90,7 +90,7 @@ function setupWindow () { const CompileCache = useSnapshot ? snapshotResult.customRequire('../src/compile-cache.js') : require('../src/compile-cache') // eslint-disable-line no-undef CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - CompileCache.install(require) + CompileCache.install(process.resourcesPath, require) const ModuleCache = useSnapshot ? snapshotResult.customRequire('../src/module-cache.js') : require('../src/module-cache') // eslint-disable-line no-undef ModuleCache.register(getWindowLoadSettings()) From 54a813eda91803066fc1bd4e91ba591931588750 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 13:31:21 +0100 Subject: [PATCH 179/194] Exclude graceful-fs from snapshot --- script/lib/generate-startup-snapshot.js | 1 + 1 file changed, 1 insertion(+) diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 0021cf1ab..25d09e287 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -39,6 +39,7 @@ module.exports = function (packagedAppPath) { relativePath == path.join('..', 'node_modules', 'debug', 'node.js') || relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || + relativePath == path.join('..', 'node_modules', 'graceful-fs', 'graceful-fs.js') || relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'index.js') || relativePath == path.join('..', 'node_modules', 'less', 'index.js') || From 7534fbbeb7fd0cdcfb3a42faa4d311fda4d49608 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 14:58:36 +0100 Subject: [PATCH 180/194] Fix lint errors --- src/compile-cache.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index b9a7d6cdd..3483f8509 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -113,7 +113,6 @@ function writeCachedJavascript (relativeCachePath, code) { } var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg -let snapshotSourceMap = null exports.install = function (resourcesPath, nodeRequire) { sourceMapSupport.install({ @@ -125,7 +124,7 @@ exports.install = function (resourcesPath, nodeRequire) { retrieveSourceMap: function (filePath) { if (filePath === '') { return { - map: snapshotResult.sourceMap, + map: snapshotResult.sourceMap, // eslint-disable-line no-undef url: path.join(resourcesPath, 'app', 'static', 'index.js') } } From ae48ab11864cbc076887b32808234a15e4ed159a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 15:26:59 +0100 Subject: [PATCH 181/194] :arrow_up: spell-check --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a74913fd8..03204c319 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "package-generator": "1.1.1", "settings-view": "0.249.0", "snippets": "1.1.3", - "spell-check": "0.71.1", + "spell-check": "0.71.2", "status-bar": "1.8.4", "styleguide": "0.49.4", "symbols-view": "0.115.3", From 16d1859bb96f7db8c72777cc854422c4eedc6b30 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 16:00:58 +0100 Subject: [PATCH 182/194] :arrow_up: spell-check --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03204c319..633c2e540 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "package-generator": "1.1.1", "settings-view": "0.249.0", "snippets": "1.1.3", - "spell-check": "0.71.2", + "spell-check": "0.71.3", "status-bar": "1.8.4", "styleguide": "0.49.4", "symbols-view": "0.115.3", From d94c8811f39c9cad307d43451ef617dc7f045dee Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 16:19:25 +0100 Subject: [PATCH 183/194] Replace CommandRegistry.addBundled with a boolean param in .add --- spec/history-manager-spec.js | 8 +- src/command-registry.coffee | 4 +- src/history-manager.js | 2 +- src/register-default-commands.coffee | 453 +++++++++++++++------------ src/window-event-handler.coffee | 8 +- 5 files changed, 257 insertions(+), 218 deletions(-) diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index ed07446eb..2bb2a376b 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -12,8 +12,8 @@ describe("HistoryManager", () => { beforeEach(async () => { commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - commandRegistry = jasmine.createSpyObj('CommandRegistry', ['addBundled']) - commandRegistry.addBundled.andReturn(commandDisposable) + commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']) + commandRegistry.add.andReturn(commandDisposable) stateStore = new StateStore('history-manager-test', 1) await stateStore.save('history-manager', { @@ -41,8 +41,8 @@ describe("HistoryManager", () => { describe("constructor", () => { it("registers the 'clear-project-history' command function", () => { - expect(commandRegistry.addBundled).toHaveBeenCalled() - const cmdCall = commandRegistry.addBundled.calls[0] + expect(commandRegistry.add).toHaveBeenCalled() + const cmdCall = commandRegistry.add.calls[0] expect(cmdCall.args.length).toBe(2) expect(cmdCall.args[0]).toBe('atom-workspace') expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe('function') diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 269714876..a419ac08c 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -94,6 +94,7 @@ class CommandRegistry add: (target, commandName, callback, throwOnInvalidSelector = true) -> if typeof commandName is 'object' commands = commandName + throwOnInvalidSelector = callback disposable = new CompositeDisposable for commandName, callback of commands disposable.add @add(target, commandName, callback, throwOnInvalidSelector) @@ -108,9 +109,6 @@ class CommandRegistry else @addInlineListener(target, commandName, callback) - addBundled: (target, commandName, callback) -> - @add(target, commandName, callback, false) - addSelectorBasedListener: (selector, commandName, callback) -> @selectorBasedListenersByCommandName[commandName] ?= [] listenersForCommand = @selectorBasedListenersByCommandName[commandName] diff --git a/src/history-manager.js b/src/history-manager.js index 17ae73d75..cd151e660 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -13,7 +13,7 @@ export class HistoryManager { this.emitter = new Emitter() this.projects = [] this.disposables = new CompositeDisposable() - this.disposables.add(commands.addBundled('atom-workspace', {'application:clear-project-history': this.clearProjects.bind(this)})) + this.disposables.add(commands.add('atom-workspace', {'application:clear-project-history': this.clearProjects.bind(this)}, false)) this.disposables.add(project.onDidChangePaths((projectPaths) => this.addProject(projectPaths))) } diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index f3ede7987..3b3849f12 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -2,223 +2,260 @@ Grim = require 'grim' module.exports = ({commandRegistry, commandInstaller, config, notificationManager, project, clipboard}) -> - commandRegistry.addBundled 'atom-workspace', - 'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem() - 'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem() - 'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack() - 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem() - 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem() - 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0) - 'pane:show-item-2': -> @getModel().getActivePane().activateItemAtIndex(1) - 'pane:show-item-3': -> @getModel().getActivePane().activateItemAtIndex(2) - 'pane:show-item-4': -> @getModel().getActivePane().activateItemAtIndex(3) - 'pane:show-item-5': -> @getModel().getActivePane().activateItemAtIndex(4) - 'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5) - 'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6) - 'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7) - 'pane:show-item-9': -> @getModel().getActivePane().activateLastItem() - 'pane:move-item-right': -> @getModel().getActivePane().moveItemRight() - 'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft() - 'window:increase-font-size': -> @getModel().increaseFontSize() - 'window:decrease-font-size': -> @getModel().decreaseFontSize() - 'window:reset-font-size': -> @getModel().resetFontSize() - 'application:about': -> ipcRenderer.send('command', 'application:about') - 'application:show-preferences': -> ipcRenderer.send('command', 'application:show-settings') - 'application:show-settings': -> ipcRenderer.send('command', 'application:show-settings') - 'application:quit': -> ipcRenderer.send('command', 'application:quit') - 'application:hide': -> ipcRenderer.send('command', 'application:hide') - 'application:hide-other-applications': -> ipcRenderer.send('command', 'application:hide-other-applications') - 'application:install-update': -> ipcRenderer.send('command', 'application:install-update') - 'application:unhide-all-applications': -> ipcRenderer.send('command', 'application:unhide-all-applications') - 'application:new-window': -> ipcRenderer.send('command', 'application:new-window') - 'application:new-file': -> ipcRenderer.send('command', 'application:new-file') - 'application:open': -> - defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0] - ipcRenderer.send('open-command', 'application:open', defaultPath) - 'application:open-file': -> - defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0] - ipcRenderer.send('open-command', 'application:open-file', defaultPath) - 'application:open-folder': -> - defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0] - ipcRenderer.send('open-command', 'application:open-folder', defaultPath) - 'application:open-dev': -> ipcRenderer.send('command', 'application:open-dev') - 'application:open-safe': -> ipcRenderer.send('command', 'application:open-safe') - 'application:add-project-folder': -> atom.addProjectFolder() - 'application:minimize': -> ipcRenderer.send('command', 'application:minimize') - 'application:zoom': -> ipcRenderer.send('command', 'application:zoom') - 'application:bring-all-windows-to-front': -> ipcRenderer.send('command', 'application:bring-all-windows-to-front') - 'application:open-your-config': -> ipcRenderer.send('command', 'application:open-your-config') - 'application:open-your-init-script': -> ipcRenderer.send('command', 'application:open-your-init-script') - 'application:open-your-keymap': -> ipcRenderer.send('command', 'application:open-your-keymap') - 'application:open-your-snippets': -> ipcRenderer.send('command', 'application:open-your-snippets') - 'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet') - 'application:open-license': -> @getModel().openLicense() - 'window:run-package-specs': -> @runPackageSpecs() - 'window:run-benchmarks': -> @runBenchmarks() - 'window:focus-next-pane': -> @getModel().activateNextPane() - 'window:focus-previous-pane': -> @getModel().activatePreviousPane() - 'window:focus-pane-above': -> @focusPaneViewAbove() - 'window:focus-pane-below': -> @focusPaneViewBelow() - 'window:focus-pane-on-left': -> @focusPaneViewOnLeft() - 'window:focus-pane-on-right': -> @focusPaneViewOnRight() - 'window:move-active-item-to-pane-above': -> @moveActiveItemToPaneAbove() - 'window:move-active-item-to-pane-below': -> @moveActiveItemToPaneBelow() - 'window:move-active-item-to-pane-on-left': -> @moveActiveItemToPaneOnLeft() - 'window:move-active-item-to-pane-on-right': -> @moveActiveItemToPaneOnRight() - 'window:copy-active-item-to-pane-above': -> @moveActiveItemToPaneAbove(keepOriginal: true) - 'window:copy-active-item-to-pane-below': -> @moveActiveItemToPaneBelow(keepOriginal: true) - 'window:copy-active-item-to-pane-on-left': -> @moveActiveItemToPaneOnLeft(keepOriginal: true) - 'window:copy-active-item-to-pane-on-right': -> @moveActiveItemToPaneOnRight(keepOriginal: true) - 'window:save-all': -> @getModel().saveAll() - 'window:toggle-invisibles': -> config.set("editor.showInvisibles", not config.get("editor.showInvisibles")) - 'window:log-deprecation-warnings': -> Grim.logDeprecations() - 'window:toggle-auto-indent': -> config.set("editor.autoIndent", not config.get("editor.autoIndent")) - 'pane:reopen-closed-item': -> @getModel().reopenItem() - 'core:close': -> @getModel().closeActivePaneItemOrEmptyPaneOrWindow() - 'core:save': -> @getModel().saveActivePaneItem() - 'core:save-as': -> @getModel().saveActivePaneItemAs() + commandRegistry.add( + 'atom-workspace', + { + 'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem() + 'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem() + 'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack() + 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem() + 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem() + 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0) + 'pane:show-item-2': -> @getModel().getActivePane().activateItemAtIndex(1) + 'pane:show-item-3': -> @getModel().getActivePane().activateItemAtIndex(2) + 'pane:show-item-4': -> @getModel().getActivePane().activateItemAtIndex(3) + 'pane:show-item-5': -> @getModel().getActivePane().activateItemAtIndex(4) + 'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5) + 'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6) + 'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7) + 'pane:show-item-9': -> @getModel().getActivePane().activateLastItem() + 'pane:move-item-right': -> @getModel().getActivePane().moveItemRight() + 'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft() + 'window:increase-font-size': -> @getModel().increaseFontSize() + 'window:decrease-font-size': -> @getModel().decreaseFontSize() + 'window:reset-font-size': -> @getModel().resetFontSize() + 'application:about': -> ipcRenderer.send('command', 'application:about') + 'application:show-preferences': -> ipcRenderer.send('command', 'application:show-settings') + 'application:show-settings': -> ipcRenderer.send('command', 'application:show-settings') + 'application:quit': -> ipcRenderer.send('command', 'application:quit') + 'application:hide': -> ipcRenderer.send('command', 'application:hide') + 'application:hide-other-applications': -> ipcRenderer.send('command', 'application:hide-other-applications') + 'application:install-update': -> ipcRenderer.send('command', 'application:install-update') + 'application:unhide-all-applications': -> ipcRenderer.send('command', 'application:unhide-all-applications') + 'application:new-window': -> ipcRenderer.send('command', 'application:new-window') + 'application:new-file': -> ipcRenderer.send('command', 'application:new-file') + 'application:open': -> + defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0] + ipcRenderer.send('open-command', 'application:open', defaultPath) + 'application:open-file': -> + defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0] + ipcRenderer.send('open-command', 'application:open-file', defaultPath) + 'application:open-folder': -> + defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0] + ipcRenderer.send('open-command', 'application:open-folder', defaultPath) + 'application:open-dev': -> ipcRenderer.send('command', 'application:open-dev') + 'application:open-safe': -> ipcRenderer.send('command', 'application:open-safe') + 'application:add-project-folder': -> atom.addProjectFolder() + 'application:minimize': -> ipcRenderer.send('command', 'application:minimize') + 'application:zoom': -> ipcRenderer.send('command', 'application:zoom') + 'application:bring-all-windows-to-front': -> ipcRenderer.send('command', 'application:bring-all-windows-to-front') + 'application:open-your-config': -> ipcRenderer.send('command', 'application:open-your-config') + 'application:open-your-init-script': -> ipcRenderer.send('command', 'application:open-your-init-script') + 'application:open-your-keymap': -> ipcRenderer.send('command', 'application:open-your-keymap') + 'application:open-your-snippets': -> ipcRenderer.send('command', 'application:open-your-snippets') + 'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet') + 'application:open-license': -> @getModel().openLicense() + 'window:run-package-specs': -> @runPackageSpecs() + 'window:run-benchmarks': -> @runBenchmarks() + 'window:focus-next-pane': -> @getModel().activateNextPane() + 'window:focus-previous-pane': -> @getModel().activatePreviousPane() + 'window:focus-pane-above': -> @focusPaneViewAbove() + 'window:focus-pane-below': -> @focusPaneViewBelow() + 'window:focus-pane-on-left': -> @focusPaneViewOnLeft() + 'window:focus-pane-on-right': -> @focusPaneViewOnRight() + 'window:move-active-item-to-pane-above': -> @moveActiveItemToPaneAbove() + 'window:move-active-item-to-pane-below': -> @moveActiveItemToPaneBelow() + 'window:move-active-item-to-pane-on-left': -> @moveActiveItemToPaneOnLeft() + 'window:move-active-item-to-pane-on-right': -> @moveActiveItemToPaneOnRight() + 'window:copy-active-item-to-pane-above': -> @moveActiveItemToPaneAbove(keepOriginal: true) + 'window:copy-active-item-to-pane-below': -> @moveActiveItemToPaneBelow(keepOriginal: true) + 'window:copy-active-item-to-pane-on-left': -> @moveActiveItemToPaneOnLeft(keepOriginal: true) + 'window:copy-active-item-to-pane-on-right': -> @moveActiveItemToPaneOnRight(keepOriginal: true) + 'window:save-all': -> @getModel().saveAll() + 'window:toggle-invisibles': -> config.set("editor.showInvisibles", not config.get("editor.showInvisibles")) + 'window:log-deprecation-warnings': -> Grim.logDeprecations() + 'window:toggle-auto-indent': -> config.set("editor.autoIndent", not config.get("editor.autoIndent")) + 'pane:reopen-closed-item': -> @getModel().reopenItem() + 'core:close': -> @getModel().closeActivePaneItemOrEmptyPaneOrWindow() + 'core:save': -> @getModel().saveActivePaneItem() + 'core:save-as': -> @getModel().saveActivePaneItemAs() + }, + false + ) + if process.platform is 'darwin' - commandRegistry.addBundled 'atom-workspace', 'window:install-shell-commands', -> - commandInstaller.installShellCommandsInteractively() + commandRegistry.add( + 'atom-workspace', + 'window:install-shell-commands', + (-> commandInstaller.installShellCommandsInteractively()), + false + ) - commandRegistry.addBundled 'atom-pane', - 'pane:save-items': -> @getModel().saveItems() - 'pane:split-left': -> @getModel().splitLeft() - 'pane:split-right': -> @getModel().splitRight() - 'pane:split-up': -> @getModel().splitUp() - 'pane:split-down': -> @getModel().splitDown() - 'pane:split-left-and-copy-active-item': -> @getModel().splitLeft(copyActiveItem: true) - 'pane:split-right-and-copy-active-item': -> @getModel().splitRight(copyActiveItem: true) - 'pane:split-up-and-copy-active-item': -> @getModel().splitUp(copyActiveItem: true) - 'pane:split-down-and-copy-active-item': -> @getModel().splitDown(copyActiveItem: true) - 'pane:split-left-and-move-active-item': -> @getModel().splitLeft(moveActiveItem: true) - 'pane:split-right-and-move-active-item': -> @getModel().splitRight(moveActiveItem: true) - 'pane:split-up-and-move-active-item': -> @getModel().splitUp(moveActiveItem: true) - 'pane:split-down-and-move-active-item': -> @getModel().splitDown(moveActiveItem: true) - 'pane:close': -> @getModel().close() - 'pane:close-other-items': -> @getModel().destroyInactiveItems() - 'pane:increase-size': -> @getModel().increaseSize() - 'pane:decrease-size': -> @getModel().decreaseSize() - - commandRegistry.addBundled 'atom-text-editor', stopEventPropagation( - 'core:undo': -> @undo() - 'core:redo': -> @redo() - 'core:move-left': -> @moveLeft() - 'core:move-right': -> @moveRight() - 'core:select-left': -> @selectLeft() - 'core:select-right': -> @selectRight() - 'core:select-up': -> @selectUp() - 'core:select-down': -> @selectDown() - 'core:select-all': -> @selectAll() - 'editor:select-word': -> @selectWordsContainingCursors() - 'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @consolidateSelections() - 'editor:move-to-beginning-of-next-paragraph': -> @moveToBeginningOfNextParagraph() - 'editor:move-to-beginning-of-previous-paragraph': -> @moveToBeginningOfPreviousParagraph() - 'editor:move-to-beginning-of-screen-line': -> @moveToBeginningOfScreenLine() - 'editor:move-to-beginning-of-line': -> @moveToBeginningOfLine() - 'editor:move-to-end-of-screen-line': -> @moveToEndOfScreenLine() - 'editor:move-to-end-of-line': -> @moveToEndOfLine() - 'editor:move-to-first-character-of-line': -> @moveToFirstCharacterOfLine() - 'editor:move-to-beginning-of-word': -> @moveToBeginningOfWord() - 'editor:move-to-end-of-word': -> @moveToEndOfWord() - 'editor:move-to-beginning-of-next-word': -> @moveToBeginningOfNextWord() - 'editor:move-to-previous-word-boundary': -> @moveToPreviousWordBoundary() - 'editor:move-to-next-word-boundary': -> @moveToNextWordBoundary() - 'editor:move-to-previous-subword-boundary': -> @moveToPreviousSubwordBoundary() - 'editor:move-to-next-subword-boundary': -> @moveToNextSubwordBoundary() - 'editor:select-to-beginning-of-next-paragraph': -> @selectToBeginningOfNextParagraph() - 'editor:select-to-beginning-of-previous-paragraph': -> @selectToBeginningOfPreviousParagraph() - 'editor:select-to-end-of-line': -> @selectToEndOfLine() - 'editor:select-to-beginning-of-line': -> @selectToBeginningOfLine() - 'editor:select-to-end-of-word': -> @selectToEndOfWord() - 'editor:select-to-beginning-of-word': -> @selectToBeginningOfWord() - 'editor:select-to-beginning-of-next-word': -> @selectToBeginningOfNextWord() - 'editor:select-to-next-word-boundary': -> @selectToNextWordBoundary() - 'editor:select-to-previous-word-boundary': -> @selectToPreviousWordBoundary() - 'editor:select-to-next-subword-boundary': -> @selectToNextSubwordBoundary() - 'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary() - 'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine() - 'editor:select-line': -> @selectLinesContainingCursors() + commandRegistry.add( + 'atom-pane', + { + 'pane:save-items': -> @getModel().saveItems() + 'pane:split-left': -> @getModel().splitLeft() + 'pane:split-right': -> @getModel().splitRight() + 'pane:split-up': -> @getModel().splitUp() + 'pane:split-down': -> @getModel().splitDown() + 'pane:split-left-and-copy-active-item': -> @getModel().splitLeft(copyActiveItem: true) + 'pane:split-right-and-copy-active-item': -> @getModel().splitRight(copyActiveItem: true) + 'pane:split-up-and-copy-active-item': -> @getModel().splitUp(copyActiveItem: true) + 'pane:split-down-and-copy-active-item': -> @getModel().splitDown(copyActiveItem: true) + 'pane:split-left-and-move-active-item': -> @getModel().splitLeft(moveActiveItem: true) + 'pane:split-right-and-move-active-item': -> @getModel().splitRight(moveActiveItem: true) + 'pane:split-up-and-move-active-item': -> @getModel().splitUp(moveActiveItem: true) + 'pane:split-down-and-move-active-item': -> @getModel().splitDown(moveActiveItem: true) + 'pane:close': -> @getModel().close() + 'pane:close-other-items': -> @getModel().destroyInactiveItems() + 'pane:increase-size': -> @getModel().increaseSize() + 'pane:decrease-size': -> @getModel().decreaseSize() + }, + false ) - commandRegistry.addBundled 'atom-text-editor', stopEventPropagationAndGroupUndo(config, - 'core:backspace': -> @backspace() - 'core:delete': -> @delete() - 'core:cut': -> @cutSelectedText() - 'core:copy': -> @copySelectedText() - 'core:paste': -> @pasteText() - 'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary() - 'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary() - 'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord() - 'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine() - 'editor:delete-to-end-of-line': -> @deleteToEndOfLine() - 'editor:delete-to-end-of-word': -> @deleteToEndOfWord() - 'editor:delete-to-beginning-of-subword': -> @deleteToBeginningOfSubword() - 'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword() - 'editor:delete-line': -> @deleteLine() - 'editor:cut-to-end-of-line': -> @cutToEndOfLine() - 'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine() - 'editor:transpose': -> @transpose() - 'editor:upper-case': -> @upperCase() - 'editor:lower-case': -> @lowerCase() - 'editor:copy-selection': -> @copyOnlySelectedText() + commandRegistry.add( + 'atom-text-editor', + stopEventPropagation({ + 'core:undo': -> @undo() + 'core:redo': -> @redo() + 'core:move-left': -> @moveLeft() + 'core:move-right': -> @moveRight() + 'core:select-left': -> @selectLeft() + 'core:select-right': -> @selectRight() + 'core:select-up': -> @selectUp() + 'core:select-down': -> @selectDown() + 'core:select-all': -> @selectAll() + 'editor:select-word': -> @selectWordsContainingCursors() + 'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @consolidateSelections() + 'editor:move-to-beginning-of-next-paragraph': -> @moveToBeginningOfNextParagraph() + 'editor:move-to-beginning-of-previous-paragraph': -> @moveToBeginningOfPreviousParagraph() + 'editor:move-to-beginning-of-screen-line': -> @moveToBeginningOfScreenLine() + 'editor:move-to-beginning-of-line': -> @moveToBeginningOfLine() + 'editor:move-to-end-of-screen-line': -> @moveToEndOfScreenLine() + 'editor:move-to-end-of-line': -> @moveToEndOfLine() + 'editor:move-to-first-character-of-line': -> @moveToFirstCharacterOfLine() + 'editor:move-to-beginning-of-word': -> @moveToBeginningOfWord() + 'editor:move-to-end-of-word': -> @moveToEndOfWord() + 'editor:move-to-beginning-of-next-word': -> @moveToBeginningOfNextWord() + 'editor:move-to-previous-word-boundary': -> @moveToPreviousWordBoundary() + 'editor:move-to-next-word-boundary': -> @moveToNextWordBoundary() + 'editor:move-to-previous-subword-boundary': -> @moveToPreviousSubwordBoundary() + 'editor:move-to-next-subword-boundary': -> @moveToNextSubwordBoundary() + 'editor:select-to-beginning-of-next-paragraph': -> @selectToBeginningOfNextParagraph() + 'editor:select-to-beginning-of-previous-paragraph': -> @selectToBeginningOfPreviousParagraph() + 'editor:select-to-end-of-line': -> @selectToEndOfLine() + 'editor:select-to-beginning-of-line': -> @selectToBeginningOfLine() + 'editor:select-to-end-of-word': -> @selectToEndOfWord() + 'editor:select-to-beginning-of-word': -> @selectToBeginningOfWord() + 'editor:select-to-beginning-of-next-word': -> @selectToBeginningOfNextWord() + 'editor:select-to-next-word-boundary': -> @selectToNextWordBoundary() + 'editor:select-to-previous-word-boundary': -> @selectToPreviousWordBoundary() + 'editor:select-to-next-subword-boundary': -> @selectToNextSubwordBoundary() + 'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary() + 'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine() + 'editor:select-line': -> @selectLinesContainingCursors() + }), + false ) - commandRegistry.addBundled 'atom-text-editor:not([mini])', stopEventPropagation( - 'core:move-up': -> @moveUp() - 'core:move-down': -> @moveDown() - 'core:move-to-top': -> @moveToTop() - 'core:move-to-bottom': -> @moveToBottom() - 'core:page-up': -> @pageUp() - 'core:page-down': -> @pageDown() - 'core:select-to-top': -> @selectToTop() - 'core:select-to-bottom': -> @selectToBottom() - 'core:select-page-up': -> @selectPageUp() - 'core:select-page-down': -> @selectPageDown() - 'editor:add-selection-below': -> @addSelectionBelow() - 'editor:add-selection-above': -> @addSelectionAbove() - 'editor:split-selections-into-lines': -> @splitSelectionsIntoLines() - 'editor:toggle-soft-tabs': -> @toggleSoftTabs() - 'editor:toggle-soft-wrap': -> @toggleSoftWrapped() - 'editor:fold-all': -> @foldAll() - 'editor:unfold-all': -> @unfoldAll() - 'editor:fold-current-row': -> @foldCurrentRow() - 'editor:unfold-current-row': -> @unfoldCurrentRow() - 'editor:fold-selection': -> @foldSelectedLines() - 'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0) - 'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1) - 'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2) - 'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3) - 'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4) - 'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5) - 'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6) - 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7) - 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8) - 'editor:log-cursor-scope': -> showCursorScope(@getCursorScope(), notificationManager) - 'editor:copy-path': -> copyPathToClipboard(this, project, clipboard, false) - 'editor:copy-project-path': -> copyPathToClipboard(this, project, clipboard, true) - 'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide')) - 'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers')) - 'editor:scroll-to-cursor': -> @scrollToCursorPosition() + commandRegistry.add( + 'atom-text-editor', + stopEventPropagationAndGroupUndo( + config, + { + 'core:backspace': -> @backspace() + 'core:delete': -> @delete() + 'core:cut': -> @cutSelectedText() + 'core:copy': -> @copySelectedText() + 'core:paste': -> @pasteText() + 'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary() + 'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary() + 'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord() + 'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine() + 'editor:delete-to-end-of-line': -> @deleteToEndOfLine() + 'editor:delete-to-end-of-word': -> @deleteToEndOfWord() + 'editor:delete-to-beginning-of-subword': -> @deleteToBeginningOfSubword() + 'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword() + 'editor:delete-line': -> @deleteLine() + 'editor:cut-to-end-of-line': -> @cutToEndOfLine() + 'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine() + 'editor:transpose': -> @transpose() + 'editor:upper-case': -> @upperCase() + 'editor:lower-case': -> @lowerCase() + 'editor:copy-selection': -> @copyOnlySelectedText() + } + ), + false ) - commandRegistry.addBundled 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(config, - 'editor:indent': -> @indent() - 'editor:auto-indent': -> @autoIndentSelectedRows() - 'editor:indent-selected-rows': -> @indentSelectedRows() - 'editor:outdent-selected-rows': -> @outdentSelectedRows() - 'editor:newline': -> @insertNewline() - 'editor:newline-below': -> @insertNewlineBelow() - 'editor:newline-above': -> @insertNewlineAbove() - 'editor:toggle-line-comments': -> @toggleLineCommentsInSelection() - 'editor:checkout-head-revision': -> atom.workspace.checkoutHeadRevision(this) - 'editor:move-line-up': -> @moveLineUp() - 'editor:move-line-down': -> @moveLineDown() - 'editor:move-selection-left': -> @moveSelectionLeft() - 'editor:move-selection-right': -> @moveSelectionRight() - 'editor:duplicate-lines': -> @duplicateLines() - 'editor:join-lines': -> @joinLines() + commandRegistry.add( + 'atom-text-editor:not([mini])', + stopEventPropagation({ + 'core:move-up': -> @moveUp() + 'core:move-down': -> @moveDown() + 'core:move-to-top': -> @moveToTop() + 'core:move-to-bottom': -> @moveToBottom() + 'core:page-up': -> @pageUp() + 'core:page-down': -> @pageDown() + 'core:select-to-top': -> @selectToTop() + 'core:select-to-bottom': -> @selectToBottom() + 'core:select-page-up': -> @selectPageUp() + 'core:select-page-down': -> @selectPageDown() + 'editor:add-selection-below': -> @addSelectionBelow() + 'editor:add-selection-above': -> @addSelectionAbove() + 'editor:split-selections-into-lines': -> @splitSelectionsIntoLines() + 'editor:toggle-soft-tabs': -> @toggleSoftTabs() + 'editor:toggle-soft-wrap': -> @toggleSoftWrapped() + 'editor:fold-all': -> @foldAll() + 'editor:unfold-all': -> @unfoldAll() + 'editor:fold-current-row': -> @foldCurrentRow() + 'editor:unfold-current-row': -> @unfoldCurrentRow() + 'editor:fold-selection': -> @foldSelectedLines() + 'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0) + 'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1) + 'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2) + 'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3) + 'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4) + 'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5) + 'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6) + 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7) + 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8) + 'editor:log-cursor-scope': -> showCursorScope(@getCursorScope(), notificationManager) + 'editor:copy-path': -> copyPathToClipboard(this, project, clipboard, false) + 'editor:copy-project-path': -> copyPathToClipboard(this, project, clipboard, true) + 'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide')) + 'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers')) + 'editor:scroll-to-cursor': -> @scrollToCursorPosition() + }), + false + ) + + commandRegistry.add( + 'atom-text-editor:not([mini])', + stopEventPropagationAndGroupUndo( + config, + { + 'editor:indent': -> @indent() + 'editor:auto-indent': -> @autoIndentSelectedRows() + 'editor:indent-selected-rows': -> @indentSelectedRows() + 'editor:outdent-selected-rows': -> @outdentSelectedRows() + 'editor:newline': -> @insertNewline() + 'editor:newline-below': -> @insertNewlineBelow() + 'editor:newline-above': -> @insertNewlineAbove() + 'editor:toggle-line-comments': -> @toggleLineCommentsInSelection() + 'editor:checkout-head-revision': -> atom.workspace.checkoutHeadRevision(this) + 'editor:move-line-up': -> @moveLineUp() + 'editor:move-line-down': -> @moveLineDown() + 'editor:move-selection-left': -> @moveSelectionLeft() + 'editor:move-selection-right': -> @moveSelectionRight() + 'editor:duplicate-lines': -> @duplicateLines() + 'editor:join-lines': -> @joinLines() + } + ), + false ) stopEventPropagation = (commandListeners) -> diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 5d6ce4686..aac0f0ba5 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -44,8 +44,12 @@ class WindowEventHandler # `.native-key-bindings` class. handleNativeKeybindings: -> bindCommandToAction = (command, action) => - @subscriptions.add @atomEnvironment.commands.addBundled '.native-key-bindings', command, (event) => - @applicationDelegate.getCurrentWindow().webContents[action]() + @subscriptions.add @atomEnvironment.commands.add( + '.native-key-bindings', + command, + ((event) => @applicationDelegate.getCurrentWindow().webContents[action]()), + false + ) bindCommandToAction('core:copy', 'copy') bindCommandToAction('core:paste', 'paste') From 7ca204dc4bf8f66877424c43dac5c2739949e055 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 16:34:43 +0100 Subject: [PATCH 184/194] :arrow_up: atom-keymap --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a74913fd8..e97c42032 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.14", "dependencies": { "async": "0.2.6", - "atom-keymap": "8.0.2", + "atom-keymap": "8.1.0", "atom-select-list": "0.0.15", "atom-ui": "0.4.1", "babel-core": "5.8.38", From 934c377af650ecc06b09defb7d9f8de026227bb9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 17:04:11 +0100 Subject: [PATCH 185/194] Fix tests --- spec/history-manager-spec.js | 2 +- src/workspace.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index 2bb2a376b..7e62a69f4 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -43,7 +43,7 @@ describe("HistoryManager", () => { it("registers the 'clear-project-history' command function", () => { expect(commandRegistry.add).toHaveBeenCalled() const cmdCall = commandRegistry.add.calls[0] - expect(cmdCall.args.length).toBe(2) + expect(cmdCall.args.length).toBe(3) expect(cmdCall.args[0]).toBe('atom-workspace') expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe('function') }) diff --git a/src/workspace.js b/src/workspace.js index 8aaf3fe43..1916c24c6 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -106,6 +106,7 @@ module.exports = class Workspace extends Model { this.openers = [] this.destroyedItemURIs = [] this.consumeServices(this.packageManager) + this.initialize() } subscribeToEvents () { From 784d5043cf2b373f60bc6cf52fec1d4286a30d8b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Mar 2017 17:40:08 +0100 Subject: [PATCH 186/194] Set also defaultSettings.core.projectHome when initializing Config --- src/config.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.coffee b/src/config.coffee index 46f3c8629..f0628ffee 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -407,6 +407,7 @@ class Config @configFilePath ?= path.join(@configDirPath, 'config.cson') @schema.properties.core.properties.projectHome = projectHomeSchema + @defaultSettings.core.projectHome = projectHomeSchema.default clear: -> @emitter = new Emitter From 9fe5ddd13b1bb5d1520978d2356561741a45a120 Mon Sep 17 00:00:00 2001 From: Wliu Date: Fri, 17 Mar 2017 16:04:33 -0400 Subject: [PATCH 187/194] :arrow_up: language-less@0.32.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 633c2e540..0dab451fb 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "language-java": "0.27.0", "language-javascript": "0.126.1", "language-json": "0.19.0", - "language-less": "0.31.0", + "language-less": "0.32.0", "language-make": "0.22.3", "language-mustache": "0.13.1", "language-objective-c": "0.15.1", From c16c7a2a468f39b0ef15eac07c3c1b423be2a774 Mon Sep 17 00:00:00 2001 From: Wliu Date: Fri, 17 Mar 2017 16:04:51 -0400 Subject: [PATCH 188/194] :arrow_up: language-sass@0.59.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0dab451fb..a8336974c 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "language-python": "0.45.2", "language-ruby": "0.70.5", "language-ruby-on-rails": "0.25.2", - "language-sass": "0.58.0", + "language-sass": "0.59.0", "language-shellscript": "0.25.0", "language-source": "0.9.0", "language-sql": "0.25.4", From f58a467cc537738523481244c3086cc61a0418d9 Mon Sep 17 00:00:00 2001 From: Wliu Date: Fri, 17 Mar 2017 16:05:12 -0400 Subject: [PATCH 189/194] :arrow_up: autocomplete-css@0.16.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8336974c..554c07b22 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "about": "1.7.5", "archive-view": "0.63.1", "autocomplete-atom-api": "0.10.1", - "autocomplete-css": "0.15.2", + "autocomplete-css": "0.16.0", "autocomplete-html": "0.7.3", "autocomplete-plus": "2.35.0", "autocomplete-snippets": "1.11.0", From 17611889d366493db561b43dab89af84864bbebd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2017 09:56:40 +0100 Subject: [PATCH 190/194] Use `core/` instead of `` for keymaps loaded during snapshot --- src/keymap-extensions.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index d1bcbc356..02b1a8a97 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -17,7 +17,7 @@ KeymapManager::canLoadBundledKeymapsFromMemory = -> KeymapManager::loadBundledKeymaps = -> if bundledKeymaps? for keymapName, keymap of bundledKeymaps - keymapPath = "/#{keymapName}" + keymapPath = "core/#{keymapName}" @add(keymapPath, keymap, 0, @devMode ? false) else keymapsPath = path.join(@resourcePath, 'keymaps') From 650ec8a94c0ff7db640f00f6cf32bb63c3f055fb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2017 09:57:58 +0100 Subject: [PATCH 191/194] :arrow_up: keybinding-resolver --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 554c07b22..91e63921f 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "grammar-selector": "0.49.3", "image-view": "0.61.2", "incompatible-packages": "0.27.2", - "keybinding-resolver": "0.36.4", + "keybinding-resolver": "0.37.0", "line-ending-selector": "0.6.2", "link": "0.31.3", "markdown-preview": "0.159.9", From 89b338f819d08edec9072956fd35e4146c02fc4e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 20 Mar 2017 11:25:38 -0700 Subject: [PATCH 192/194] :arrow_up: packages in preparation for docks feature --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 91e63921f..7f05ded98 100644 --- a/package.json +++ b/package.json @@ -93,17 +93,17 @@ "autocomplete-snippets": "1.11.0", "autoflow": "0.29.0", "autosave": "0.24.1", - "background-tips": "0.26.1", + "background-tips": "0.26.2", "bookmarks": "0.44.2", "bracket-matcher": "0.85.4", "command-palette": "0.40.3", "dalek": "0.2.0", - "deprecation-cop": "0.56.4", + "deprecation-cop": "0.56.5", "dev-live-reload": "0.47.1", "encoding-selector": "0.23.2", "exception-reporting": "0.41.3", "find-and-replace": "0.207.3", - "fuzzy-finder": "1.5.1", + "fuzzy-finder": "1.5.2", "git-diff": "1.3.4", "go-to-line": "0.32.0", "grammar-selector": "0.49.3", @@ -112,24 +112,24 @@ "keybinding-resolver": "0.37.0", "line-ending-selector": "0.6.2", "link": "0.31.3", - "markdown-preview": "0.159.9", + "markdown-preview": "0.159.10", "metrics": "1.2.2", "notifications": "0.67.1", "open-on-github": "1.2.1", "package-generator": "1.1.1", - "settings-view": "0.249.0", + "settings-view": "0.249.1", "snippets": "1.1.3", "spell-check": "0.71.3", "status-bar": "1.8.4", "styleguide": "0.49.4", "symbols-view": "0.115.3", - "tabs": "0.104.3", + "tabs": "0.104.4", "timecop": "0.36.0", - "tree-view": "0.215.2", + "tree-view": "0.215.3", "update-package-dependencies": "0.11.0", "welcome": "0.36.2", "whitespace": "0.36.2", - "wrap-guide": "0.40.0", + "wrap-guide": "0.40.1", "language-c": "0.57.0", "language-clojure": "0.22.2", "language-coffee-script": "0.48.6", From d861e3a39e60bc519c7996e816ff5e9e09374e92 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 20 Mar 2017 16:53:36 -0400 Subject: [PATCH 193/194] :arrow_up: autocomplete-css@0.16.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f05ded98..cad51c8ad 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "about": "1.7.5", "archive-view": "0.63.1", "autocomplete-atom-api": "0.10.1", - "autocomplete-css": "0.16.0", + "autocomplete-css": "0.16.1", "autocomplete-html": "0.7.3", "autocomplete-plus": "2.35.0", "autocomplete-snippets": "1.11.0", From bc14ff58557b51dad7271b54b88f3d614e865712 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 20 Mar 2017 20:40:04 -0400 Subject: [PATCH 194/194] :arrow_up: language-ruby@0.71.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cad51c8ad..53870e424 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "language-php": "0.37.5", "language-property-list": "0.9.1", "language-python": "0.45.2", - "language-ruby": "0.70.5", + "language-ruby": "0.71.0", "language-ruby-on-rails": "0.25.2", "language-sass": "0.59.0", "language-shellscript": "0.25.0",