diff --git a/.eslintignore b/.eslintignore index 13da670a0..2c1ef701c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,4 @@ -spec/fixtures/**/*.js +**/spec/fixtures/**/*.js node_modules /vendor/ /out/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 3050b2b9e..62995e39c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,7 +25,7 @@ "rules": { "standard/no-callback-literal": ["off"], "node/no-deprecated-api": ["off"], - "prettier/prettier": ["off"] // disable prettier rules for now. + "prettier/prettier": ["error"] }, "overrides": [ { @@ -41,4 +41,4 @@ } } ] -} \ No newline at end of file +} diff --git a/.prettierrc b/.prettierrc index b2095be81..544138be4 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,3 @@ { - "semi": false, "singleQuote": true } diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index 10586818d..c1e6e524e 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -1,72 +1,75 @@ -const Chart = require('chart.js') -const glob = require('glob') -const fs = require('fs-plus') -const path = require('path') +const Chart = require('chart.js'); +const glob = require('glob'); +const fs = require('fs-plus'); +const path = require('path'); -module.exports = async ({test, benchmarkPaths}) => { - document.body.style.backgroundColor = '#ffffff' - document.body.style.overflow = 'auto' +module.exports = async ({ test, benchmarkPaths }) => { + document.body.style.backgroundColor = '#ffffff'; + document.body.style.overflow = 'auto'; - let paths = [] + let paths = []; for (const benchmarkPath of benchmarkPaths) { if (fs.isDirectorySync(benchmarkPath)) { - paths = paths.concat(glob.sync(path.join(benchmarkPath, '**', '*.bench.js'))) + paths = paths.concat( + glob.sync(path.join(benchmarkPath, '**', '*.bench.js')) + ); } else { - paths.push(benchmarkPath) + paths.push(benchmarkPath); } } while (paths.length > 0) { - const benchmark = require(paths.shift())({test}) - let results + const benchmark = require(paths.shift())({ test }); + let results; if (benchmark instanceof Promise) { - results = await benchmark + results = await benchmark; } else { - results = benchmark + results = benchmark; } - const dataByBenchmarkName = {} - for (const {name, duration, x} of results) { - dataByBenchmarkName[name] = dataByBenchmarkName[name] || {points: []} - dataByBenchmarkName[name].points.push({x, y: duration}) + const dataByBenchmarkName = {}; + for (const { name, duration, x } of results) { + dataByBenchmarkName[name] = dataByBenchmarkName[name] || { points: [] }; + dataByBenchmarkName[name].points.push({ x, y: duration }); } - const benchmarkContainer = document.createElement('div') - document.body.appendChild(benchmarkContainer) + const benchmarkContainer = document.createElement('div'); + document.body.appendChild(benchmarkContainer); for (const key in dataByBenchmarkName) { - const data = dataByBenchmarkName[key] + const data = dataByBenchmarkName[key]; if (data.points.length > 1) { - const canvas = document.createElement('canvas') - benchmarkContainer.appendChild(canvas) + const canvas = document.createElement('canvas'); + benchmarkContainer.appendChild(canvas); // eslint-disable-next-line no-new new Chart(canvas, { type: 'line', data: { - datasets: [{label: key, fill: false, data: data.points}] + datasets: [{ label: key, fill: false, data: data.points }] }, options: { showLines: false, - scales: {xAxes: [{type: 'linear', position: 'bottom'}]} + scales: { xAxes: [{ type: 'linear', position: 'bottom' }] } } - }) + }); - const textualOutput = `${key}:\n\n` + data.points.map((p) => `${p.x}\t${p.y}`).join('\n') - console.log(textualOutput) + const textualOutput = + `${key}:\n\n` + data.points.map(p => `${p.x}\t${p.y}`).join('\n'); + console.log(textualOutput); } else { - const title = document.createElement('h2') - title.textContent = key - benchmarkContainer.appendChild(title) - const duration = document.createElement('p') - duration.textContent = `${data.points[0].y}ms` - benchmarkContainer.appendChild(duration) + const title = document.createElement('h2'); + title.textContent = key; + benchmarkContainer.appendChild(title); + const duration = document.createElement('p'); + duration.textContent = `${data.points[0].y}ms`; + benchmarkContainer.appendChild(duration); - const textualOutput = `${key}: ${data.points[0].y}` - console.log(textualOutput) + const textualOutput = `${key}: ${data.points[0].y}`; + console.log(textualOutput); } - await global.atom.reset() + await global.atom.reset(); } } - return 0 -} + return 0; +}; diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index ff564e5ca..eba748a54 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -1,88 +1,100 @@ -const {TextEditor, TextBuffer} = require('atom') +const { TextEditor, TextBuffer } = require('atom'); -const MIN_SIZE_IN_KB = 0 * 1024 -const MAX_SIZE_IN_KB = 10 * 1024 -const SIZE_STEP_IN_KB = 1024 -const LINE_TEXT = 'Lorem ipsum dolor sit amet\n' -const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_TEXT.length)) +const MIN_SIZE_IN_KB = 0 * 1024; +const MAX_SIZE_IN_KB = 10 * 1024; +const SIZE_STEP_IN_KB = 1024; +const LINE_TEXT = 'Lorem ipsum dolor sit amet\n'; +const TEXT = LINE_TEXT.repeat( + Math.ceil((MAX_SIZE_IN_KB * 1024) / LINE_TEXT.length) +); -module.exports = async ({test}) => { - const data = [] +module.exports = async ({ test }) => { + const data = []; - document.body.appendChild(atom.workspace.getElement()) + document.body.appendChild(atom.workspace.getElement()); - atom.packages.loadPackages() - await atom.packages.activate() + atom.packages.loadPackages(); + await atom.packages.activate(); for (let pane of atom.workspace.getPanes()) { - pane.destroy() + pane.destroy(); } - for (let sizeInKB = MIN_SIZE_IN_KB; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_IN_KB) { - const text = TEXT.slice(0, sizeInKB * 1024) - console.log(text.length / 1024) + for ( + let sizeInKB = MIN_SIZE_IN_KB; + sizeInKB < MAX_SIZE_IN_KB; + sizeInKB += SIZE_STEP_IN_KB + ) { + const text = TEXT.slice(0, sizeInKB * 1024); + console.log(text.length / 1024); - let t0 = window.performance.now() - const buffer = new TextBuffer({text}) - const editor = new TextEditor({buffer, autoHeight: false, largeFileMode: true}) - atom.grammars.autoAssignLanguageMode(buffer) - atom.workspace.getActivePane().activateItem(editor) - let t1 = window.performance.now() + let t0 = window.performance.now(); + const buffer = new TextBuffer({ text }); + const editor = new TextEditor({ + buffer, + autoHeight: false, + largeFileMode: true + }); + atom.grammars.autoAssignLanguageMode(buffer); + atom.workspace.getActivePane().activateItem(editor); + let t1 = window.performance.now(); data.push({ name: 'Opening a large file', x: sizeInKB, duration: t1 - t0 - }) + }); - const tickDurations = [] + const tickDurations = []; for (let i = 0; i < 20; i++) { - await timeout(50) - t0 = window.performance.now() - await timeout(0) - t1 = window.performance.now() - tickDurations[i] = t1 - t0 + await timeout(50); + t0 = window.performance.now(); + await timeout(0); + t1 = window.performance.now(); + tickDurations[i] = t1 - t0; } data.push({ name: 'Max time event loop was blocked after opening a large file', x: sizeInKB, duration: Math.max(...tickDurations) - }) + }); - t0 = window.performance.now() - editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({ - top: 100, - left: 30 - })) - t1 = window.performance.now() + t0 = window.performance.now(); + editor.setCursorScreenPosition( + editor.element.screenPositionForPixelPosition({ + top: 100, + left: 30 + }) + ); + t1 = window.performance.now(); data.push({ name: 'Clicking the editor after opening a large file', x: sizeInKB, duration: t1 - t0 - }) + }); - t0 = window.performance.now() - editor.element.setScrollTop(editor.element.getScrollTop() + 100) - t1 = window.performance.now() + t0 = window.performance.now(); + editor.element.setScrollTop(editor.element.getScrollTop() + 100); + t1 = window.performance.now(); data.push({ name: 'Scrolling down after opening a large file', x: sizeInKB, duration: t1 - t0 - }) + }); - editor.destroy() - buffer.destroy() - await timeout(10000) + editor.destroy(); + buffer.destroy(); + await timeout(10000); } - atom.workspace.getElement().remove() + atom.workspace.getElement().remove(); - return data -} + return data; +}; -function timeout (duration) { - return new Promise((resolve) => setTimeout(resolve, duration)) +function timeout(duration) { + return new Promise(resolve => setTimeout(resolve, duration)); } diff --git a/benchmarks/text-editor-long-lines.bench.js b/benchmarks/text-editor-long-lines.bench.js index 92a9b9b9e..ac2f788a6 100644 --- a/benchmarks/text-editor-long-lines.bench.js +++ b/benchmarks/text-editor-long-lines.bench.js @@ -1,95 +1,105 @@ -const path = require('path') -const fs = require('fs') -const {TextEditor, TextBuffer} = require('atom') +const path = require('path'); +const fs = require('fs'); +const { TextEditor, TextBuffer } = require('atom'); -const SIZES_IN_KB = [ - 512, - 1024, - 2048 -] -const REPEATED_TEXT = fs.readFileSync(path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'), 'utf8').replace(/\n/g, '') -const TEXT = REPEATED_TEXT.repeat(Math.ceil(SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024 / REPEATED_TEXT.length)) +const SIZES_IN_KB = [512, 1024, 2048]; +const REPEATED_TEXT = fs + .readFileSync( + path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'), + 'utf8' + ) + .replace(/\n/g, ''); +const TEXT = REPEATED_TEXT.repeat( + Math.ceil((SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024) / REPEATED_TEXT.length) +); -module.exports = async ({test}) => { - const data = [] +module.exports = async ({ test }) => { + const data = []; - const workspaceElement = atom.workspace.getElement() - document.body.appendChild(workspaceElement) + const workspaceElement = atom.workspace.getElement(); + document.body.appendChild(workspaceElement); - atom.packages.loadPackages() - await atom.packages.activate() + atom.packages.loadPackages(); + await atom.packages.activate(); console.log(atom.getLoadSettings().resourcePath); for (let pane of atom.workspace.getPanes()) { - pane.destroy() + pane.destroy(); } for (const sizeInKB of SIZES_IN_KB) { - const text = TEXT.slice(0, sizeInKB * 1024) - console.log(text.length / 1024) + const text = TEXT.slice(0, sizeInKB * 1024); + console.log(text.length / 1024); - let t0 = window.performance.now() - const buffer = new TextBuffer({text}) - const editor = new TextEditor({buffer, autoHeight: false, largeFileMode: true}) - atom.grammars.assignLanguageMode(buffer, 'source.js') - atom.workspace.getActivePane().activateItem(editor) - let t1 = window.performance.now() + let t0 = window.performance.now(); + const buffer = new TextBuffer({ text }); + const editor = new TextEditor({ + buffer, + autoHeight: false, + largeFileMode: true + }); + atom.grammars.assignLanguageMode(buffer, 'source.js'); + atom.workspace.getActivePane().activateItem(editor); + let t1 = window.performance.now(); data.push({ name: 'Opening a large single-line file', x: sizeInKB, duration: t1 - t0 - }) + }); - const tickDurations = [] + const tickDurations = []; for (let i = 0; i < 20; i++) { - await timeout(50) - t0 = window.performance.now() - await timeout(0) - t1 = window.performance.now() - tickDurations[i] = t1 - t0 + await timeout(50); + t0 = window.performance.now(); + await timeout(0); + t1 = window.performance.now(); + tickDurations[i] = t1 - t0; } data.push({ - name: 'Max time event loop was blocked after opening a large single-line file', + name: + 'Max time event loop was blocked after opening a large single-line file', x: sizeInKB, duration: Math.max(...tickDurations) - }) + }); - t0 = window.performance.now() - editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({ - top: 100, - left: 30 - })) - t1 = window.performance.now() + t0 = window.performance.now(); + editor.setCursorScreenPosition( + editor.element.screenPositionForPixelPosition({ + top: 100, + left: 30 + }) + ); + t1 = window.performance.now(); data.push({ name: 'Clicking the editor after opening a large single-line file', x: sizeInKB, duration: t1 - t0 - }) + }); - t0 = window.performance.now() - editor.element.setScrollTop(editor.element.getScrollTop() + 100) - t1 = window.performance.now() + t0 = window.performance.now(); + editor.element.setScrollTop(editor.element.getScrollTop() + 100); + t1 = window.performance.now(); data.push({ name: 'Scrolling down after opening a large single-line file', x: sizeInKB, duration: t1 - t0 - }) + }); - editor.destroy() - buffer.destroy() - await timeout(10000) + editor.destroy(); + buffer.destroy(); + await timeout(10000); } - workspaceElement.remove() + workspaceElement.remove(); - return data -} + return data; +}; -function timeout (duration) { - return new Promise((resolve) => setTimeout(resolve, duration)) +function timeout(duration) { + return new Promise(resolve => setTimeout(resolve, duration)); } diff --git a/exports/atom.js b/exports/atom.js index 359f0174e..c70d0406a 100644 --- a/exports/atom.js +++ b/exports/atom.js @@ -1,12 +1,12 @@ -const TextBuffer = require('text-buffer') -const {Point, Range} = TextBuffer -const {File, Directory} = require('pathwatcher') -const {Emitter, Disposable, CompositeDisposable} = require('event-kit') -const BufferedNodeProcess = require('../src/buffered-node-process') -const BufferedProcess = require('../src/buffered-process') -const GitRepository = require('../src/git-repository') -const Notification = require('../src/notification') -const {watchPath} = require('../src/path-watcher') +const TextBuffer = require('text-buffer'); +const { Point, Range } = TextBuffer; +const { File, Directory } = require('pathwatcher'); +const { Emitter, Disposable, CompositeDisposable } = require('event-kit'); +const BufferedNodeProcess = require('../src/buffered-node-process'); +const BufferedProcess = require('../src/buffered-process'); +const GitRepository = require('../src/git-repository'); +const Notification = require('../src/notification'); +const { watchPath } = require('../src/path-watcher'); const atomExport = { BufferedNodeProcess, @@ -22,23 +22,23 @@ const atomExport = { Disposable, CompositeDisposable, watchPath -} +}; // Shell integration is required by both Squirrel and Settings-View if (process.platform === 'win32') { Object.defineProperty(atomExport, 'WinShell', { enumerable: true, - get () { - return require('../src/main-process/win-shell') + get() { + return require('../src/main-process/win-shell'); } - }) + }); } // The following classes can't be used from a Task handler and should therefore // only be exported when not running as a child node process if (process.type === 'renderer') { - atomExport.Task = require('../src/task') - atomExport.TextEditor = require('../src/text-editor') + atomExport.Task = require('../src/task'); + atomExport.TextEditor = require('../src/text-editor'); } -module.exports = atomExport +module.exports = atomExport; diff --git a/exports/clipboard.js b/exports/clipboard.js index 3594b3342..020ce94e8 100644 --- a/exports/clipboard.js +++ b/exports/clipboard.js @@ -1,7 +1,9 @@ -module.exports = require('electron').clipboard +module.exports = require('electron').clipboard; -const Grim = require('grim') -Grim.deprecate('Use `require("electron").clipboard` instead of `require("clipboard")`') +const Grim = require('grim'); +Grim.deprecate( + 'Use `require("electron").clipboard` instead of `require("clipboard")`' +); // Ensure each package that requires this shim causes a deprecation warning -delete require.cache[__filename] +delete require.cache[__filename]; diff --git a/exports/ipc.js b/exports/ipc.js index 9d1b1e3ee..54f57bb0d 100644 --- a/exports/ipc.js +++ b/exports/ipc.js @@ -1,7 +1,9 @@ -module.exports = require('electron').ipcRenderer +module.exports = require('electron').ipcRenderer; -const Grim = require('grim') -Grim.deprecate('Use `require("electron").ipcRenderer` instead of `require("ipc")`') +const Grim = require('grim'); +Grim.deprecate( + 'Use `require("electron").ipcRenderer` instead of `require("ipc")`' +); // Ensure each package that requires this shim causes a deprecation warning -delete require.cache[__filename] +delete require.cache[__filename]; diff --git a/exports/remote.js b/exports/remote.js index 379ee43f6..4e527dbca 100644 --- a/exports/remote.js +++ b/exports/remote.js @@ -1,7 +1,9 @@ -module.exports = require('electron').remote +module.exports = require('electron').remote; -const Grim = require('grim') -Grim.deprecate('Use `require("electron").remote` instead of `require("remote")`') +const Grim = require('grim'); +Grim.deprecate( + 'Use `require("electron").remote` instead of `require("remote")`' +); // Ensure each package that requires this shim causes a deprecation warning -delete require.cache[__filename] +delete require.cache[__filename]; diff --git a/exports/shell.js b/exports/shell.js index 2424455a6..5fd73e772 100644 --- a/exports/shell.js +++ b/exports/shell.js @@ -1,7 +1,7 @@ -module.exports = require('electron').shell +module.exports = require('electron').shell; -const Grim = require('grim') -Grim.deprecate('Use `require("electron").shell` instead of `require("shell")`') +const Grim = require('grim'); +Grim.deprecate('Use `require("electron").shell` instead of `require("shell")`'); // Ensure each package that requires this shim causes a deprecation warning -delete require.cache[__filename] +delete require.cache[__filename]; diff --git a/exports/web-frame.js b/exports/web-frame.js index 0c97debfd..b639ec9aa 100644 --- a/exports/web-frame.js +++ b/exports/web-frame.js @@ -1,7 +1,9 @@ -module.exports = require('electron').webFrame +module.exports = require('electron').webFrame; -const Grim = require('grim') -Grim.deprecate('Use `require("electron").webFrame` instead of `require("web-frame")`') +const Grim = require('grim'); +Grim.deprecate( + 'Use `require("electron").webFrame` instead of `require("web-frame")`' +); // Ensure each package that requires this shim causes a deprecation warning -delete require.cache[__filename] +delete require.cache[__filename]; diff --git a/packages/about/lib/about.js b/packages/about/lib/about.js index 1fe660e9f..1ec4d8a72 100644 --- a/packages/about/lib/about.js +++ b/packages/about/lib/about.js @@ -1,67 +1,67 @@ -const { CompositeDisposable, Emitter } = require('atom') -const AboutView = require('./components/about-view') +const { CompositeDisposable, Emitter } = require('atom'); +const AboutView = require('./components/about-view'); // Deferred requires -let shell +let shell; module.exports = class About { - constructor (initialState) { - this.subscriptions = new CompositeDisposable() - this.emitter = new Emitter() + constructor(initialState) { + this.subscriptions = new CompositeDisposable(); + this.emitter = new Emitter(); - this.state = initialState + this.state = initialState; this.views = { aboutView: null - } + }; this.subscriptions.add( atom.workspace.addOpener(uriToOpen => { if (uriToOpen === this.state.uri) { - return this.deserialize() + return this.deserialize(); } }) - ) + ); this.subscriptions.add( atom.commands.add('atom-workspace', 'about:view-release-notes', () => { - shell = shell || require('electron').shell + shell = shell || require('electron').shell; shell.openExternal( this.state.updateManager.getReleaseNotesURLForCurrentVersion() - ) + ); }) - ) + ); } - destroy () { - if (this.views.aboutView) this.views.aboutView.destroy() - this.views.aboutView = null + destroy() { + if (this.views.aboutView) this.views.aboutView.destroy(); + this.views.aboutView = null; - if (this.state.updateManager) this.state.updateManager.dispose() - this.setState({ updateManager: null }) + if (this.state.updateManager) this.state.updateManager.dispose(); + this.setState({ updateManager: null }); - this.subscriptions.dispose() + this.subscriptions.dispose(); } - setState (newState) { + setState(newState) { if (newState && typeof newState === 'object') { - let { state } = this - this.state = Object.assign({}, state, newState) + let { state } = this; + this.state = Object.assign({}, state, newState); - this.didChange() + this.didChange(); } } - didChange () { - this.emitter.emit('did-change') + didChange() { + this.emitter.emit('did-change'); } - onDidChange (callback) { - this.emitter.on('did-change', callback) + onDidChange(callback) { + this.emitter.on('did-change', callback); } - deserialize (state) { + deserialize(state) { if (!this.views.aboutView) { - this.setState(state) + this.setState(state); this.views.aboutView = new AboutView({ uri: this.state.uri, @@ -71,14 +71,14 @@ module.exports = class About { currentChromeVersion: this.state.currentChromeVersion, currentNodeVersion: this.state.currentNodeVersion, availableVersion: this.state.updateManager.getAvailableVersion() - }) - this.handleStateChanges() + }); + this.handleStateChanges(); } - return this.views.aboutView + return this.views.aboutView; } - handleStateChanges () { + handleStateChanges() { this.onDidChange(() => { if (this.views.aboutView) { this.views.aboutView.update({ @@ -88,12 +88,12 @@ module.exports = class About { currentChromeVersion: this.state.currentChromeVersion, currentNodeVersion: this.state.currentNodeVersion, availableVersion: this.state.updateManager.getAvailableVersion() - }) + }); } - }) + }); this.state.updateManager.onDidChange(() => { - this.didChange() - }) + this.didChange(); + }); } -} +}; diff --git a/packages/about/lib/components/about-status-bar.js b/packages/about/lib/components/about-status-bar.js index 4529b6ae8..1456c7bdc 100644 --- a/packages/about/lib/components/about-status-bar.js +++ b/packages/about/lib/components/about-status-bar.js @@ -1,38 +1,38 @@ -const { CompositeDisposable } = require('atom') -const etch = require('etch') -const EtchComponent = require('../etch-component') +const { CompositeDisposable } = require('atom'); +const etch = require('etch'); +const EtchComponent = require('../etch-component'); -const $ = etch.dom +const $ = etch.dom; module.exports = class AboutStatusBar extends EtchComponent { - constructor () { - super() - this.subscriptions = new CompositeDisposable() + constructor() { + super(); + this.subscriptions = new CompositeDisposable(); this.subscriptions.add( atom.tooltips.add(this.element, { title: 'An update will be installed the next time Atom is relaunched.

Click the squirrel icon for more information.' }) - ) + ); } - handleClick () { - atom.workspace.open('atom://about') + handleClick() { + atom.workspace.open('atom://about'); } - render () { + render() { return $.div( { className: 'about-release-notes inline-block', onclick: this.handleClick.bind(this) }, $.span({ type: 'button', className: 'icon icon-squirrel' }) - ) + ); } - destroy () { - super.destroy() - this.subscriptions.dispose() + destroy() { + super.destroy(); + this.subscriptions.dispose(); } -} +}; diff --git a/packages/about/lib/components/about-view.js b/packages/about/lib/components/about-view.js index 34fb7cba9..b98118551 100644 --- a/packages/about/lib/components/about-view.js +++ b/packages/about/lib/components/about-view.js @@ -1,77 +1,77 @@ -const { Disposable } = require('atom') -const etch = require('etch') -const shell = require('shell') -const AtomLogo = require('./atom-logo') -const EtchComponent = require('../etch-component') -const UpdateView = require('./update-view') +const { Disposable } = require('atom'); +const etch = require('etch'); +const shell = require('shell'); +const AtomLogo = require('./atom-logo'); +const EtchComponent = require('../etch-component'); +const UpdateView = require('./update-view'); -const $ = etch.dom +const $ = etch.dom; module.exports = class AboutView extends EtchComponent { - handleAtomVersionClick (e) { - e.preventDefault() - atom.clipboard.write(this.props.currentAtomVersion) + handleAtomVersionClick(e) { + e.preventDefault(); + atom.clipboard.write(this.props.currentAtomVersion); } - handleElectronVersionClick (e) { - e.preventDefault() - atom.clipboard.write(this.props.currentElectronVersion) + handleElectronVersionClick(e) { + e.preventDefault(); + atom.clipboard.write(this.props.currentElectronVersion); } - handleChromeVersionClick (e) { - e.preventDefault() - atom.clipboard.write(this.props.currentChromeVersion) + handleChromeVersionClick(e) { + e.preventDefault(); + atom.clipboard.write(this.props.currentChromeVersion); } - handleNodeVersionClick (e) { - e.preventDefault() - atom.clipboard.write(this.props.currentNodeVersion) + handleNodeVersionClick(e) { + e.preventDefault(); + atom.clipboard.write(this.props.currentNodeVersion); } - handleReleaseNotesClick (e) { - e.preventDefault() + handleReleaseNotesClick(e) { + e.preventDefault(); shell.openExternal( this.props.updateManager.getReleaseNotesURLForAvailableVersion() - ) + ); } - handleLicenseClick (e) { - e.preventDefault() + handleLicenseClick(e) { + e.preventDefault(); atom.commands.dispatch( atom.views.getView(atom.workspace), 'application:open-license' - ) + ); } - handleTermsOfUseClick (e) { - e.preventDefault() - shell.openExternal('https://atom.io/terms') + handleTermsOfUseClick(e) { + e.preventDefault(); + shell.openExternal('https://atom.io/terms'); } - handleHowToUpdateClick (e) { - e.preventDefault() + handleHowToUpdateClick(e) { + e.preventDefault(); shell.openExternal( 'https://flight-manual.atom.io/getting-started/sections/installing-atom/' - ) + ); } - handleShowMoreClick (e) { - e.preventDefault() - var showMoreDiv = document.querySelector('.show-more') - var showMoreText = document.querySelector('.about-more-expand') + handleShowMoreClick(e) { + e.preventDefault(); + var showMoreDiv = document.querySelector('.show-more'); + var showMoreText = document.querySelector('.about-more-expand'); switch (showMoreText.textContent) { case 'Show more': - showMoreDiv.classList.toggle('hidden') - showMoreText.textContent = 'Hide' - break + showMoreDiv.classList.toggle('hidden'); + showMoreText.textContent = 'Hide'; + break; case 'Hide': - showMoreDiv.classList.toggle('hidden') - showMoreText.textContent = 'Show more' - break + showMoreDiv.classList.toggle('hidden'); + showMoreText.textContent = 'Show more'; + break; } } - render () { + render() { return $.div( { className: 'pane-item native-key-bindings about' }, $.div( @@ -204,29 +204,29 @@ module.exports = class AboutView extends EtchComponent { 'Atom Community' ) ) - ) + ); } - serialize () { + serialize() { return { deserializer: this.constructor.name, uri: this.props.uri - } + }; } - onDidChangeTitle () { - return new Disposable() + onDidChangeTitle() { + return new Disposable(); } - onDidChangeModified () { - return new Disposable() + onDidChangeModified() { + return new Disposable(); } - getTitle () { - return 'About' + getTitle() { + return 'About'; } - getIconName () { - return 'info' + getIconName() { + return 'info'; } -} +}; diff --git a/packages/about/lib/components/atom-logo.js b/packages/about/lib/components/atom-logo.js index bd50fc7a2..9dcb28dd8 100644 --- a/packages/about/lib/components/atom-logo.js +++ b/packages/about/lib/components/atom-logo.js @@ -1,10 +1,10 @@ -const etch = require('etch') -const EtchComponent = require('../etch-component') +const etch = require('etch'); +const EtchComponent = require('../etch-component'); -const $ = etch.dom +const $ = etch.dom; module.exports = class AtomLogo extends EtchComponent { - render () { + render() { return $.svg( { className: 'about-logo', @@ -74,6 +74,6 @@ module.exports = class AtomLogo extends EtchComponent { ) ) ) - ) + ); } -} +}; diff --git a/packages/about/lib/components/update-view.js b/packages/about/lib/components/update-view.js index 4399b58b4..2475d28cc 100644 --- a/packages/about/lib/components/update-view.js +++ b/packages/about/lib/components/update-view.js @@ -1,46 +1,46 @@ -const etch = require('etch') -const EtchComponent = require('../etch-component') -const UpdateManager = require('../update-manager') +const etch = require('etch'); +const EtchComponent = require('../etch-component'); +const UpdateManager = require('../update-manager'); -const $ = etch.dom +const $ = etch.dom; module.exports = class UpdateView extends EtchComponent { - constructor (props) { - super(props) + constructor(props) { + super(props); if ( this.props.updateManager.getAutoUpdatesEnabled() && this.props.updateManager.getState() === UpdateManager.State.Idle ) { - this.props.updateManager.checkForUpdate() + this.props.updateManager.checkForUpdate(); } } - handleAutoUpdateCheckbox (e) { - atom.config.set('core.automaticallyUpdate', e.target.checked) + handleAutoUpdateCheckbox(e) { + atom.config.set('core.automaticallyUpdate', e.target.checked); } - shouldUpdateActionButtonBeDisabled () { - let { state } = this.props.updateManager + shouldUpdateActionButtonBeDisabled() { + let { state } = this.props.updateManager; return ( state === UpdateManager.State.CheckingForUpdate || state === UpdateManager.State.DownloadingUpdate - ) + ); } - executeUpdateAction () { + executeUpdateAction() { if ( this.props.updateManager.state === UpdateManager.State.UpdateAvailableToInstall ) { - this.props.updateManager.restartAndInstallUpdate() + this.props.updateManager.restartAndInstallUpdate(); } else { - this.props.updateManager.checkForUpdate() + this.props.updateManager.checkForUpdate(); } } - renderUpdateStatus () { - let updateStatus = '' + renderUpdateStatus() { + let updateStatus = ''; switch (this.props.updateManager.state) { case UpdateManager.State.Idle: @@ -52,8 +52,8 @@ module.exports = class UpdateView extends EtchComponent { this.props.updateManager.getAutoUpdatesEnabled() ? 'Atom will check for updates automatically' : 'Automatic updates are disabled please check manually' - ) - break + ); + break; case UpdateManager.State.CheckingForUpdate: updateStatus = $.div( { className: 'about-updates-item app-checking-for-updates' }, @@ -61,15 +61,15 @@ module.exports = class UpdateView extends EtchComponent { { className: 'about-updates-label icon icon-search' }, 'Checking for updates...' ) - ) - break + ); + break; case UpdateManager.State.DownloadingUpdate: updateStatus = $.div( { className: 'about-updates-item app-downloading-update' }, $.span({ className: 'loading loading-spinner-tiny inline-block' }), $.span({ className: 'about-updates-label' }, 'Downloading update') - ) - break + ); + break; case UpdateManager.State.UpdateAvailableToInstall: updateStatus = $.div( { className: 'about-updates-item app-update-available-to-install' }, @@ -88,8 +88,8 @@ module.exports = class UpdateView extends EtchComponent { }, 'Release Notes' ) - ) - break + ); + break; case UpdateManager.State.UpToDate: updateStatus = $.div( { className: 'about-updates-item app-up-to-date' }, @@ -98,8 +98,8 @@ module.exports = class UpdateView extends EtchComponent { { className: 'about-updates-label is-strong' }, 'Atom is up to date!' ) - ) - break + ); + break; case UpdateManager.State.Unsupported: updateStatus = $.div( { className: 'about-updates-item app-unsupported' }, @@ -114,8 +114,8 @@ module.exports = class UpdateView extends EtchComponent { }, 'How to update' ) - ) - break + ); + break; case UpdateManager.State.Error: updateStatus = $.div( { className: 'about-updates-item app-update-error' }, @@ -124,14 +124,14 @@ module.exports = class UpdateView extends EtchComponent { { className: 'about-updates-label app-error-message is-strong' }, this.props.updateManager.getErrorMessage() ) - ) - break + ); + break; } - return updateStatus + return updateStatus; } - render () { + render() { return $.div( { className: 'about-updates group-start' }, $.div( @@ -176,6 +176,6 @@ module.exports = class UpdateView extends EtchComponent { $.span({}, 'Automatically download updates') ) ) - ) + ); } -} +}; diff --git a/packages/about/lib/etch-component.js b/packages/about/lib/etch-component.js index 71ea85c88..fc6c1bd1b 100644 --- a/packages/about/lib/etch-component.js +++ b/packages/about/lib/etch-component.js @@ -1,15 +1,15 @@ -const etch = require('etch') +const etch = require('etch'); /* Public: Abstract class for handling the initialization boilerplate of an Etch component. */ module.exports = class EtchComponent { - constructor (props) { - this.props = props + constructor(props) { + this.props = props; - etch.initialize(this) - EtchComponent.setScheduler(atom.views) + etch.initialize(this); + EtchComponent.setScheduler(atom.views); } /* @@ -17,8 +17,8 @@ module.exports = class EtchComponent { Returns a {Scheduler} */ - static getScheduler () { - return etch.getScheduler() + static getScheduler() { + return etch.getScheduler(); } /* @@ -26,8 +26,8 @@ module.exports = class EtchComponent { * `scheduler` {Scheduler} */ - static setScheduler (scheduler) { - etch.setScheduler(scheduler) + static setScheduler(scheduler) { + etch.setScheduler(scheduler); } /* @@ -37,20 +37,20 @@ module.exports = class EtchComponent { * `props` an {Object} representing the properties you want to update */ - update (props) { - let oldProps = this.props - this.props = Object.assign({}, oldProps, props) - return etch.update(this) + update(props) { + let oldProps = this.props; + this.props = Object.assign({}, oldProps, props); + return etch.update(this); } /* Public: Destroys the component, removing it from the DOM. */ - destroy () { - etch.destroy(this) + destroy() { + etch.destroy(this); } - render () { - throw new Error('Etch components must implement a `render` method') + render() { + throw new Error('Etch components must implement a `render` method'); } -} +}; diff --git a/packages/about/lib/main.js b/packages/about/lib/main.js index ec1420e7f..b0cf2cbd4 100644 --- a/packages/about/lib/main.js +++ b/packages/about/lib/main.js @@ -1,26 +1,26 @@ -const { CompositeDisposable } = require('atom') -const semver = require('semver') -const UpdateManager = require('./update-manager') -const About = require('./about') -const StatusBarView = require('./components/about-status-bar') -let updateManager +const { CompositeDisposable } = require('atom'); +const semver = require('semver'); +const UpdateManager = require('./update-manager'); +const About = require('./about'); +const StatusBarView = require('./components/about-status-bar'); +let updateManager; // The local storage key for the available update version. -const AvailableUpdateVersion = 'about:version-available' -const AboutURI = 'atom://about' +const AvailableUpdateVersion = 'about:version-available'; +const AboutURI = 'atom://about'; module.exports = { - activate () { - this.subscriptions = new CompositeDisposable() + activate() { + this.subscriptions = new CompositeDisposable(); - this.createModel() + this.createModel(); - let availableVersion = window.localStorage.getItem(AvailableUpdateVersion) + let availableVersion = window.localStorage.getItem(AvailableUpdateVersion); if ( atom.getReleaseChannel() === 'dev' || (availableVersion && semver.lte(availableVersion, atom.getVersion())) ) { - this.clearUpdateState() + this.clearUpdateState(); } this.subscriptions.add( @@ -32,48 +32,48 @@ module.exports = { window.localStorage.setItem( AvailableUpdateVersion, updateManager.getAvailableVersion() - ) - this.showStatusBarIfNeeded() + ); + this.showStatusBarIfNeeded(); } }) - ) + ); this.subscriptions.add( atom.commands.add('atom-workspace', 'about:clear-update-state', () => { - this.clearUpdateState() + this.clearUpdateState(); }) - ) + ); }, - deactivate () { - this.model.destroy() - if (this.statusBarTile) this.statusBarTile.destroy() + deactivate() { + this.model.destroy(); + if (this.statusBarTile) this.statusBarTile.destroy(); if (updateManager) { - updateManager.dispose() - updateManager = undefined + updateManager.dispose(); + updateManager = undefined; } }, - clearUpdateState () { - window.localStorage.removeItem(AvailableUpdateVersion) + clearUpdateState() { + window.localStorage.removeItem(AvailableUpdateVersion); }, - consumeStatusBar (statusBar) { - this.statusBar = statusBar - this.showStatusBarIfNeeded() + consumeStatusBar(statusBar) { + this.statusBar = statusBar; + this.showStatusBarIfNeeded(); }, - deserializeAboutView (state) { + deserializeAboutView(state) { if (!this.model) { - this.createModel() + this.createModel(); } - return this.model.deserialize(state) + return this.model.deserialize(state); }, - createModel () { - updateManager = updateManager || new UpdateManager() + createModel() { + updateManager = updateManager || new UpdateManager(); this.model = new About({ uri: AboutURI, @@ -82,28 +82,28 @@ module.exports = { currentChromeVersion: process.versions.chrome, currentNodeVersion: process.version, updateManager: updateManager - }) + }); }, - isUpdateAvailable () { - let availableVersion = window.localStorage.getItem(AvailableUpdateVersion) - return availableVersion && semver.gt(availableVersion, atom.getVersion()) + isUpdateAvailable() { + let availableVersion = window.localStorage.getItem(AvailableUpdateVersion); + return availableVersion && semver.gt(availableVersion, atom.getVersion()); }, - showStatusBarIfNeeded () { + showStatusBarIfNeeded() { if (this.isUpdateAvailable() && this.statusBar) { - let statusBarView = new StatusBarView() + let statusBarView = new StatusBarView(); if (this.statusBarTile) { - this.statusBarTile.destroy() + this.statusBarTile.destroy(); } this.statusBarTile = this.statusBar.addRightTile({ item: statusBarView, priority: -100 - }) + }); - return this.statusBarTile + return this.statusBarTile; } } -} +}; diff --git a/packages/about/lib/update-manager.js b/packages/about/lib/update-manager.js index bae72753d..eb30ce4d3 100644 --- a/packages/about/lib/update-manager.js +++ b/packages/about/lib/update-manager.js @@ -1,46 +1,46 @@ -const { Emitter, CompositeDisposable } = require('atom') +const { Emitter, CompositeDisposable } = require('atom'); -const Unsupported = 'unsupported' -const Idle = 'idle' -const CheckingForUpdate = 'checking' -const DownloadingUpdate = 'downloading' -const UpdateAvailableToInstall = 'update-available' -const UpToDate = 'no-update-available' -const ErrorState = 'error' +const Unsupported = 'unsupported'; +const Idle = 'idle'; +const CheckingForUpdate = 'checking'; +const DownloadingUpdate = 'downloading'; +const UpdateAvailableToInstall = 'update-available'; +const UpToDate = 'no-update-available'; +const ErrorState = 'error'; let UpdateManager = class UpdateManager { - constructor () { - this.emitter = new Emitter() - this.currentVersion = atom.getVersion() - this.availableVersion = atom.getVersion() - this.resetState() - this.listenForAtomEvents() + constructor() { + this.emitter = new Emitter(); + this.currentVersion = atom.getVersion(); + this.availableVersion = atom.getVersion(); + this.resetState(); + this.listenForAtomEvents(); } - listenForAtomEvents () { - this.subscriptions = new CompositeDisposable() + listenForAtomEvents() { + this.subscriptions = new CompositeDisposable(); this.subscriptions.add( atom.autoUpdater.onDidBeginCheckingForUpdate(() => { - this.setState(CheckingForUpdate) + this.setState(CheckingForUpdate); }), atom.autoUpdater.onDidBeginDownloadingUpdate(() => { - this.setState(DownloadingUpdate) + this.setState(DownloadingUpdate); }), atom.autoUpdater.onDidCompleteDownloadingUpdate(({ releaseVersion }) => { - this.setAvailableVersion(releaseVersion) + this.setAvailableVersion(releaseVersion); }), atom.autoUpdater.onUpdateNotAvailable(() => { - this.setState(UpToDate) + this.setState(UpToDate); }), atom.autoUpdater.onUpdateError(() => { - this.setState(ErrorState) + this.setState(ErrorState); }), atom.config.observe('core.automaticallyUpdate', value => { - this.autoUpdatesEnabled = value - this.emitDidChange() + this.autoUpdatesEnabled = value; + this.emitDidChange(); }) - ) + ); // TODO: When https://github.com/atom/electron/issues/4587 is closed we can add this support. // atom.autoUpdater.onUpdateAvailable => @@ -48,95 +48,95 @@ let UpdateManager = class UpdateManager { // @updateAvailable.addClass('is-shown') } - dispose () { - this.subscriptions.dispose() + dispose() { + this.subscriptions.dispose(); } - onDidChange (callback) { - return this.emitter.on('did-change', callback) + onDidChange(callback) { + return this.emitter.on('did-change', callback); } - emitDidChange () { - this.emitter.emit('did-change') + emitDidChange() { + this.emitter.emit('did-change'); } - getAutoUpdatesEnabled () { + getAutoUpdatesEnabled() { return ( this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported - ) + ); } - setAutoUpdatesEnabled (enabled) { - return atom.config.set('core.automaticallyUpdate', enabled) + setAutoUpdatesEnabled(enabled) { + return atom.config.set('core.automaticallyUpdate', enabled); } - getErrorMessage () { - return atom.autoUpdater.getErrorMessage() + getErrorMessage() { + return atom.autoUpdater.getErrorMessage(); } - getState () { - return this.state + getState() { + return this.state; } - setState (state) { - this.state = state - this.emitDidChange() + setState(state) { + this.state = state; + this.emitDidChange(); } - resetState () { + resetState() { this.state = atom.autoUpdater.platformSupportsUpdates() ? atom.autoUpdater.getState() - : Unsupported - this.emitDidChange() + : Unsupported; + this.emitDidChange(); } - getAvailableVersion () { - return this.availableVersion + getAvailableVersion() { + return this.availableVersion; } - setAvailableVersion (version) { - this.availableVersion = version + setAvailableVersion(version) { + this.availableVersion = version; if (this.availableVersion !== this.currentVersion) { - this.state = UpdateAvailableToInstall + this.state = UpdateAvailableToInstall; } else { - this.state = UpToDate + this.state = UpToDate; } - this.emitDidChange() + this.emitDidChange(); } - checkForUpdate () { - atom.autoUpdater.checkForUpdate() + checkForUpdate() { + atom.autoUpdater.checkForUpdate(); } - restartAndInstallUpdate () { - atom.autoUpdater.restartAndInstallUpdate() + restartAndInstallUpdate() { + atom.autoUpdater.restartAndInstallUpdate(); } - getReleaseNotesURLForCurrentVersion () { - return this.getReleaseNotesURLForVersion(this.currentVersion) + getReleaseNotesURLForCurrentVersion() { + return this.getReleaseNotesURLForVersion(this.currentVersion); } - getReleaseNotesURLForAvailableVersion () { - return this.getReleaseNotesURLForVersion(this.availableVersion) + getReleaseNotesURLForAvailableVersion() { + return this.getReleaseNotesURLForVersion(this.availableVersion); } - getReleaseNotesURLForVersion (appVersion) { + getReleaseNotesURLForVersion(appVersion) { // Dev versions will not have a releases page if (appVersion.indexOf('dev') > -1) { - return 'https://atom.io/releases' + return 'https://atom.io/releases'; } if (!appVersion.startsWith('v')) { - appVersion = `v${appVersion}` + appVersion = `v${appVersion}`; } const releaseRepo = - appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom' - return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}` + appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'; + return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}`; } -} +}; UpdateManager.State = { Unsupported: Unsupported, @@ -146,6 +146,6 @@ UpdateManager.State = { UpdateAvailableToInstall: UpdateAvailableToInstall, UpToDate: UpToDate, Error: ErrorState -} +}; -module.exports = UpdateManager +module.exports = UpdateManager; diff --git a/packages/about/spec/about-spec.js b/packages/about/spec/about-spec.js index 21aa4c5eb..5fe78a135 100644 --- a/packages/about/spec/about-spec.js +++ b/packages/about/spec/about-spec.js @@ -1,28 +1,28 @@ describe('About', () => { - let workspaceElement + let workspaceElement; beforeEach(async () => { - let storage = {} + let storage = {}; spyOn(window.localStorage, 'setItem').andCallFake((key, value) => { - storage[key] = value - }) + storage[key] = value; + }); spyOn(window.localStorage, 'getItem').andCallFake(key => { - return storage[key] - }) + return storage[key]; + }); - workspaceElement = atom.views.getView(atom.workspace) - await atom.packages.activatePackage('about') - }) + workspaceElement = atom.views.getView(atom.workspace); + await atom.packages.activatePackage('about'); + }); it('deserializes correctly', () => { let deserializedAboutView = atom.deserializers.deserialize({ deserializer: 'AboutView', uri: 'atom://about' - }) + }); - expect(deserializedAboutView).toBeTruthy() - }) + expect(deserializedAboutView).toBeTruthy(); + }); describe('when the about:about-atom command is triggered', () => { it('shows the About Atom view', async () => { @@ -30,70 +30,70 @@ describe('About', () => { // `toBeVisible()` matchers to work. Anything testing visibility or focus // requires that the workspaceElement is on the DOM. Tests that attach the // workspaceElement to the DOM are generally slower than those off DOM. - jasmine.attachToDOM(workspaceElement) + jasmine.attachToDOM(workspaceElement); - expect(workspaceElement.querySelector('.about')).not.toExist() - await atom.workspace.open('atom://about') + expect(workspaceElement.querySelector('.about')).not.toExist(); + await atom.workspace.open('atom://about'); - let aboutElement = workspaceElement.querySelector('.about') - expect(aboutElement).toBeVisible() - }) - }) + let aboutElement = workspaceElement.querySelector('.about'); + expect(aboutElement).toBeVisible(); + }); + }); describe('when the Atom version number is clicked', () => { it('copies the version number to the clipboard', async () => { - await atom.workspace.open('atom://about') + await atom.workspace.open('atom://about'); - let aboutElement = workspaceElement.querySelector('.about') - let versionContainer = aboutElement.querySelector('.atom') - versionContainer.click() - expect(atom.clipboard.read()).toBe(atom.getVersion()) - }) - }) + let aboutElement = workspaceElement.querySelector('.about'); + let versionContainer = aboutElement.querySelector('.atom'); + versionContainer.click(); + expect(atom.clipboard.read()).toBe(atom.getVersion()); + }); + }); describe('when the show more link is clicked', () => { it('expands to show additional version numbers', async () => { - await atom.workspace.open('atom://about') - jasmine.attachToDOM(workspaceElement) + await atom.workspace.open('atom://about'); + jasmine.attachToDOM(workspaceElement); - let aboutElement = workspaceElement.querySelector('.about') - let showMoreElement = aboutElement.querySelector('.show-more-expand') - let moreInfoElement = workspaceElement.querySelector('.show-more') - showMoreElement.click() - expect(moreInfoElement).toBeVisible() - }) - }) + let aboutElement = workspaceElement.querySelector('.about'); + let showMoreElement = aboutElement.querySelector('.show-more-expand'); + let moreInfoElement = workspaceElement.querySelector('.show-more'); + showMoreElement.click(); + expect(moreInfoElement).toBeVisible(); + }); + }); describe('when the Electron version number is clicked', () => { it('copies the version number to the clipboard', async () => { - await atom.workspace.open('atom://about') + await atom.workspace.open('atom://about'); - let aboutElement = workspaceElement.querySelector('.about') - let versionContainer = aboutElement.querySelector('.electron') - versionContainer.click() - expect(atom.clipboard.read()).toBe(process.versions.electron) - }) - }) + let aboutElement = workspaceElement.querySelector('.about'); + let versionContainer = aboutElement.querySelector('.electron'); + versionContainer.click(); + expect(atom.clipboard.read()).toBe(process.versions.electron); + }); + }); describe('when the Chrome version number is clicked', () => { it('copies the version number to the clipboard', async () => { - await atom.workspace.open('atom://about') + await atom.workspace.open('atom://about'); - let aboutElement = workspaceElement.querySelector('.about') - let versionContainer = aboutElement.querySelector('.chrome') - versionContainer.click() - expect(atom.clipboard.read()).toBe(process.versions.chrome) - }) - }) + let aboutElement = workspaceElement.querySelector('.about'); + let versionContainer = aboutElement.querySelector('.chrome'); + versionContainer.click(); + expect(atom.clipboard.read()).toBe(process.versions.chrome); + }); + }); describe('when the Node version number is clicked', () => { it('copies the version number to the clipboard', async () => { - await atom.workspace.open('atom://about') + await atom.workspace.open('atom://about'); - let aboutElement = workspaceElement.querySelector('.about') - let versionContainer = aboutElement.querySelector('.node') - versionContainer.click() - expect(atom.clipboard.read()).toBe(process.version) - }) - }) -}) + let aboutElement = workspaceElement.querySelector('.about'); + let versionContainer = aboutElement.querySelector('.node'); + versionContainer.click(); + expect(atom.clipboard.read()).toBe(process.version); + }); + }); +}); diff --git a/packages/about/spec/about-status-bar-spec.js b/packages/about/spec/about-status-bar-spec.js index a67c0511c..157842f3e 100644 --- a/packages/about/spec/about-status-bar-spec.js +++ b/packages/about/spec/about-status-bar-spec.js @@ -1,179 +1,183 @@ -const { conditionPromise } = require('./helpers/async-spec-helpers') -const MockUpdater = require('./mocks/updater') +const { conditionPromise } = require('./helpers/async-spec-helpers'); +const MockUpdater = require('./mocks/updater'); describe('the status bar', () => { - let atomVersion - let workspaceElement + let atomVersion; + let workspaceElement; beforeEach(async () => { - let storage = {} + let storage = {}; spyOn(window.localStorage, 'setItem').andCallFake((key, value) => { - storage[key] = value - }) + storage[key] = value; + }); spyOn(window.localStorage, 'getItem').andCallFake(key => { - return storage[key] - }) + return storage[key]; + }); spyOn(atom, 'getVersion').andCallFake(() => { - return atomVersion - }) + return atomVersion; + }); - workspaceElement = atom.views.getView(atom.workspace) + workspaceElement = atom.views.getView(atom.workspace); - await atom.packages.activatePackage('status-bar') - await atom.workspace.open('sample.js') - }) + await atom.packages.activatePackage('status-bar'); + await atom.workspace.open('sample.js'); + }); afterEach(async () => { - await atom.packages.deactivatePackage('about') - await atom.packages.deactivatePackage('status-bar') - }) + await atom.packages.deactivatePackage('about'); + await atom.packages.deactivatePackage('status-bar'); + }); - describe('on a stable version', function () { + describe('on a stable version', function() { beforeEach(async () => { - atomVersion = '1.2.3' + atomVersion = '1.2.3'; - await atom.packages.activatePackage('about') - }) + await atom.packages.activatePackage('about'); + }); describe('with no update', () => { it('does not show the view', () => { - expect(workspaceElement).not.toContain('.about-release-notes') - }) - }) + expect(workspaceElement).not.toContain('.about-release-notes'); + }); + }); describe('with an update', () => { it('shows the view when the update finishes downloading', () => { - MockUpdater.finishDownloadingUpdate('42.0.0') - expect(workspaceElement).toContain('.about-release-notes') - }) + MockUpdater.finishDownloadingUpdate('42.0.0'); + expect(workspaceElement).toContain('.about-release-notes'); + }); describe('clicking on the status', () => { it('opens the about page', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') - workspaceElement.querySelector('.about-release-notes').click() - await conditionPromise(() => workspaceElement.querySelector('.about')) - expect(workspaceElement.querySelector('.about')).toExist() - }) - }) + MockUpdater.finishDownloadingUpdate('42.0.0'); + workspaceElement.querySelector('.about-release-notes').click(); + await conditionPromise(() => + workspaceElement.querySelector('.about') + ); + expect(workspaceElement.querySelector('.about')).toExist(); + }); + }); it('continues to show the squirrel until Atom is updated to the new version', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') - expect(workspaceElement).toContain('.about-release-notes') + MockUpdater.finishDownloadingUpdate('42.0.0'); + expect(workspaceElement).toContain('.about-release-notes'); - await atom.packages.deactivatePackage('about') - expect(workspaceElement).not.toContain('.about-release-notes') + await atom.packages.deactivatePackage('about'); + expect(workspaceElement).not.toContain('.about-release-notes'); - await atom.packages.activatePackage('about') - await Promise.resolve() // Service consumption hooks are deferred until the next tick - expect(workspaceElement).toContain('.about-release-notes') + await atom.packages.activatePackage('about'); + await Promise.resolve(); // Service consumption hooks are deferred until the next tick + expect(workspaceElement).toContain('.about-release-notes'); - await atom.packages.deactivatePackage('about') - expect(workspaceElement).not.toContain('.about-release-notes') + await atom.packages.deactivatePackage('about'); + expect(workspaceElement).not.toContain('.about-release-notes'); - atomVersion = '42.0.0' - await atom.packages.activatePackage('about') + atomVersion = '42.0.0'; + await atom.packages.activatePackage('about'); - await Promise.resolve() // Service consumption hooks are deferred until the next tick - expect(workspaceElement).not.toContain('.about-release-notes') - }) + await Promise.resolve(); // Service consumption hooks are deferred until the next tick + expect(workspaceElement).not.toContain('.about-release-notes'); + }); it('does not show the view if Atom is updated to a newer version than notified', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') + MockUpdater.finishDownloadingUpdate('42.0.0'); - await atom.packages.deactivatePackage('about') + await atom.packages.deactivatePackage('about'); - atomVersion = '43.0.0' - await atom.packages.activatePackage('about') + atomVersion = '43.0.0'; + await atom.packages.activatePackage('about'); - await Promise.resolve() // Service consumption hooks are deferred until the next tick - expect(workspaceElement).not.toContain('.about-release-notes') - }) - }) - }) + await Promise.resolve(); // Service consumption hooks are deferred until the next tick + expect(workspaceElement).not.toContain('.about-release-notes'); + }); + }); + }); - describe('on a beta version', function () { + describe('on a beta version', function() { beforeEach(async () => { - atomVersion = '1.2.3-beta4' + atomVersion = '1.2.3-beta4'; - await atom.packages.activatePackage('about') - }) + await atom.packages.activatePackage('about'); + }); describe('with no update', () => { it('does not show the view', () => { - expect(workspaceElement).not.toContain('.about-release-notes') - }) - }) + expect(workspaceElement).not.toContain('.about-release-notes'); + }); + }); describe('with an update', () => { it('shows the view when the update finishes downloading', () => { - MockUpdater.finishDownloadingUpdate('42.0.0') - expect(workspaceElement).toContain('.about-release-notes') - }) + MockUpdater.finishDownloadingUpdate('42.0.0'); + expect(workspaceElement).toContain('.about-release-notes'); + }); describe('clicking on the status', () => { it('opens the about page', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') - workspaceElement.querySelector('.about-release-notes').click() - await conditionPromise(() => workspaceElement.querySelector('.about')) - expect(workspaceElement.querySelector('.about')).toExist() - }) - }) + MockUpdater.finishDownloadingUpdate('42.0.0'); + workspaceElement.querySelector('.about-release-notes').click(); + await conditionPromise(() => + workspaceElement.querySelector('.about') + ); + expect(workspaceElement.querySelector('.about')).toExist(); + }); + }); it('continues to show the squirrel until Atom is updated to the new version', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') - expect(workspaceElement).toContain('.about-release-notes') + MockUpdater.finishDownloadingUpdate('42.0.0'); + expect(workspaceElement).toContain('.about-release-notes'); - await atom.packages.deactivatePackage('about') - expect(workspaceElement).not.toContain('.about-release-notes') + await atom.packages.deactivatePackage('about'); + expect(workspaceElement).not.toContain('.about-release-notes'); - await atom.packages.activatePackage('about') - await Promise.resolve() // Service consumption hooks are deferred until the next tick - expect(workspaceElement).toContain('.about-release-notes') + await atom.packages.activatePackage('about'); + await Promise.resolve(); // Service consumption hooks are deferred until the next tick + expect(workspaceElement).toContain('.about-release-notes'); - await atom.packages.deactivatePackage('about') - expect(workspaceElement).not.toContain('.about-release-notes') + await atom.packages.deactivatePackage('about'); + expect(workspaceElement).not.toContain('.about-release-notes'); - atomVersion = '42.0.0' - await atom.packages.activatePackage('about') + atomVersion = '42.0.0'; + await atom.packages.activatePackage('about'); - await Promise.resolve() // Service consumption hooks are deferred until the next tick - expect(workspaceElement).not.toContain('.about-release-notes') - }) + await Promise.resolve(); // Service consumption hooks are deferred until the next tick + expect(workspaceElement).not.toContain('.about-release-notes'); + }); it('does not show the view if Atom is updated to a newer version than notified', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') + MockUpdater.finishDownloadingUpdate('42.0.0'); - await atom.packages.deactivatePackage('about') + await atom.packages.deactivatePackage('about'); - atomVersion = '43.0.0' - await atom.packages.activatePackage('about') + atomVersion = '43.0.0'; + await atom.packages.activatePackage('about'); - await Promise.resolve() // Service consumption hooks are deferred until the next tick - expect(workspaceElement).not.toContain('.about-release-notes') - }) - }) - }) + await Promise.resolve(); // Service consumption hooks are deferred until the next tick + expect(workspaceElement).not.toContain('.about-release-notes'); + }); + }); + }); - describe('on a development version', function () { + describe('on a development version', function() { beforeEach(async () => { - atomVersion = '1.2.3-dev-0123abcd' + atomVersion = '1.2.3-dev-0123abcd'; - await atom.packages.activatePackage('about') - }) + await atom.packages.activatePackage('about'); + }); describe('with no update', () => { it('does not show the view', () => { - expect(workspaceElement).not.toContain('.about-release-notes') - }) - }) + expect(workspaceElement).not.toContain('.about-release-notes'); + }); + }); describe('with a previously downloaded update', () => { it('does not show the view', () => { - window.localStorage.setItem('about:version-available', '42.0.0') + window.localStorage.setItem('about:version-available', '42.0.0'); - expect(workspaceElement).not.toContain('.about-release-notes') - }) - }) - }) -}) + expect(workspaceElement).not.toContain('.about-release-notes'); + }); + }); + }); +}); diff --git a/packages/about/spec/helpers/async-spec-helpers.js b/packages/about/spec/helpers/async-spec-helpers.js index 58e17d323..c73ac5062 100644 --- a/packages/about/spec/helpers/async-spec-helpers.js +++ b/packages/about/spec/helpers/async-spec-helpers.js @@ -1,26 +1,26 @@ /** @babel */ -const { now } = Date -const { setTimeout } = global +const { now } = Date; +const { setTimeout } = global; -export async function conditionPromise (condition) { - const startTime = now() +export async function conditionPromise(condition) { + const startTime = now(); while (true) { - await timeoutPromise(100) + await timeoutPromise(100); if (await condition()) { - return + return; } if (now() - startTime > 5000) { - throw new Error('Timed out waiting on condition') + throw new Error('Timed out waiting on condition'); } } } -export function timeoutPromise (timeout) { - return new Promise(function (resolve) { - setTimeout(resolve, timeout) - }) +export function timeoutPromise(timeout) { + return new Promise(function(resolve) { + setTimeout(resolve, timeout); + }); } diff --git a/packages/about/spec/mocks/updater.js b/packages/about/spec/mocks/updater.js index 6c5e1f19d..e0f0a311d 100644 --- a/packages/about/spec/mocks/updater.js +++ b/packages/about/spec/mocks/updater.js @@ -1,23 +1,23 @@ module.exports = { - updateError () { - atom.autoUpdater.emitter.emit('update-error') + updateError() { + atom.autoUpdater.emitter.emit('update-error'); }, - checkForUpdate () { - atom.autoUpdater.emitter.emit('did-begin-checking-for-update') + checkForUpdate() { + atom.autoUpdater.emitter.emit('did-begin-checking-for-update'); }, - updateNotAvailable () { - atom.autoUpdater.emitter.emit('update-not-available') + updateNotAvailable() { + atom.autoUpdater.emitter.emit('update-not-available'); }, - downloadUpdate () { - atom.autoUpdater.emitter.emit('did-begin-downloading-update') + downloadUpdate() { + atom.autoUpdater.emitter.emit('did-begin-downloading-update'); }, - finishDownloadingUpdate (releaseVersion) { + finishDownloadingUpdate(releaseVersion) { atom.autoUpdater.emitter.emit('did-complete-downloading-update', { releaseVersion - }) + }); } -} +}; diff --git a/packages/about/spec/update-manager-spec.js b/packages/about/spec/update-manager-spec.js index cc395949e..ef42fec9b 100644 --- a/packages/about/spec/update-manager-spec.js +++ b/packages/about/spec/update-manager-spec.js @@ -1,32 +1,32 @@ -const UpdateManager = require('../lib/update-manager') +const UpdateManager = require('../lib/update-manager'); describe('UpdateManager', () => { - let updateManager + let updateManager; beforeEach(() => { - updateManager = new UpdateManager() - }) + updateManager = new UpdateManager(); + }); describe('::getReleaseNotesURLForVersion', () => { it('returns atom.io releases when dev version', () => { expect( updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d') - ).toContain('atom.io/releases') - }) + ).toContain('atom.io/releases'); + }); it('returns the page for the release when not a dev version', () => { expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain( 'atom/atom/releases/tag/v1.7.0' - ) + ); expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain( 'atom/atom/releases/tag/v1.7.0' - ) + ); expect( updateManager.getReleaseNotesURLForVersion('1.7.0-beta10') - ).toContain('atom/atom/releases/tag/v1.7.0-beta10') + ).toContain('atom/atom/releases/tag/v1.7.0-beta10'); expect( updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10') - ).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10') - }) - }) -}) + ).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10'); + }); + }); +}); diff --git a/packages/about/spec/update-view-spec.js b/packages/about/spec/update-view-spec.js index 8e6587b59..83620e4e5 100644 --- a/packages/about/spec/update-view-spec.js +++ b/packages/about/spec/update-view-spec.js @@ -1,385 +1,387 @@ -const { shell } = require('electron') -const main = require('../lib/main') -const AboutView = require('../lib/components/about-view') -const UpdateView = require('../lib/components/update-view') -const MockUpdater = require('./mocks/updater') +const { shell } = require('electron'); +const main = require('../lib/main'); +const AboutView = require('../lib/components/about-view'); +const UpdateView = require('../lib/components/update-view'); +const MockUpdater = require('./mocks/updater'); describe('UpdateView', () => { - let aboutElement - let updateManager - let workspaceElement - let scheduler + let aboutElement; + let updateManager; + let workspaceElement; + let scheduler; beforeEach(async () => { - let storage = {} + let storage = {}; spyOn(window.localStorage, 'setItem').andCallFake((key, value) => { - storage[key] = value - }) + storage[key] = value; + }); spyOn(window.localStorage, 'getItem').andCallFake(key => { - return storage[key] - }) + return storage[key]; + }); - workspaceElement = atom.views.getView(atom.workspace) - await atom.packages.activatePackage('about') - spyOn(atom.autoUpdater, 'getState').andReturn('idle') - spyOn(atom.autoUpdater, 'checkForUpdate') - spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true) - }) + workspaceElement = atom.views.getView(atom.workspace); + await atom.packages.activatePackage('about'); + spyOn(atom.autoUpdater, 'getState').andReturn('idle'); + spyOn(atom.autoUpdater, 'checkForUpdate'); + spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true); + }); describe('when the About page is open', () => { beforeEach(async () => { - jasmine.attachToDOM(workspaceElement) - await atom.workspace.open('atom://about') - aboutElement = workspaceElement.querySelector('.about') - updateManager = main.model.state.updateManager - scheduler = AboutView.getScheduler() - }) + jasmine.attachToDOM(workspaceElement); + await atom.workspace.open('atom://about'); + aboutElement = workspaceElement.querySelector('.about'); + updateManager = main.model.state.updateManager; + scheduler = AboutView.getScheduler(); + }); describe('when the updates are not supported by the platform', () => { beforeEach(async () => { - atom.autoUpdater.platformSupportsUpdates.andReturn(false) - updateManager.resetState() - await scheduler.getNextUpdatePromise() - }) + atom.autoUpdater.platformSupportsUpdates.andReturn(false); + updateManager.resetState(); + await scheduler.getNextUpdatePromise(); + }); it('hides the auto update UI and shows the update instructions link', async () => { expect( aboutElement.querySelector('.about-update-action-button') - ).not.toBeVisible() + ).not.toBeVisible(); expect( aboutElement.querySelector('.about-auto-updates') - ).not.toBeVisible() - }) + ).not.toBeVisible(); + }); it('opens the update instructions page when the instructions link is clicked', async () => { - spyOn(shell, 'openExternal') + spyOn(shell, 'openExternal'); let link = aboutElement.querySelector( '.app-unsupported .about-updates-instructions' - ) - link.click() + ); + link.click(); - let args = shell.openExternal.mostRecentCall.args - expect(shell.openExternal).toHaveBeenCalled() - expect(args[0]).toContain('installing-atom') - }) - }) + let args = shell.openExternal.mostRecentCall.args; + expect(shell.openExternal).toHaveBeenCalled(); + expect(args[0]).toContain('installing-atom'); + }); + }); describe('when updates are supported by the platform', () => { beforeEach(async () => { - atom.autoUpdater.platformSupportsUpdates.andReturn(true) - updateManager.resetState() - await scheduler.getNextUpdatePromise() - }) + atom.autoUpdater.platformSupportsUpdates.andReturn(true); + updateManager.resetState(); + await scheduler.getNextUpdatePromise(); + }); it('shows the auto update UI', () => { - expect(aboutElement.querySelector('.about-updates')).toBeVisible() - }) + expect(aboutElement.querySelector('.about-updates')).toBeVisible(); + }); it('shows the correct panels when the app checks for updates and there is no update available', async () => { expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); - MockUpdater.checkForUpdate() - await scheduler.getNextUpdatePromise() - expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible() + MockUpdater.checkForUpdate(); + await scheduler.getNextUpdatePromise(); + expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible(); expect( aboutElement.querySelector('.app-checking-for-updates') - ).toBeVisible() + ).toBeVisible(); - MockUpdater.updateNotAvailable() - await scheduler.getNextUpdatePromise() - expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible() + MockUpdater.updateNotAvailable(); + await scheduler.getNextUpdatePromise(); + expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible(); expect( aboutElement.querySelector('.app-checking-for-updates') - ).not.toBeVisible() - }) + ).not.toBeVisible(); + }); it('shows the correct panels when the app checks for updates and encounters an error', async () => { expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); - MockUpdater.checkForUpdate() - await scheduler.getNextUpdatePromise() - expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible() + MockUpdater.checkForUpdate(); + await scheduler.getNextUpdatePromise(); + expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible(); expect( aboutElement.querySelector('.app-checking-for-updates') - ).toBeVisible() + ).toBeVisible(); - spyOn(atom.autoUpdater, 'getErrorMessage').andReturn('an error message') - MockUpdater.updateError() - await scheduler.getNextUpdatePromise() - expect(aboutElement.querySelector('.app-update-error')).toBeVisible() + spyOn(atom.autoUpdater, 'getErrorMessage').andReturn( + 'an error message' + ); + MockUpdater.updateError(); + await scheduler.getNextUpdatePromise(); + expect(aboutElement.querySelector('.app-update-error')).toBeVisible(); expect( aboutElement.querySelector('.app-error-message').textContent - ).toBe('an error message') + ).toBe('an error message'); expect( aboutElement.querySelector('.app-checking-for-updates') - ).not.toBeVisible() + ).not.toBeVisible(); expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(false) + ).toBe(false); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Check now') - }) + ).toBe('Check now'); + }); it('shows the correct panels and button states when the app checks for updates and an update is downloaded', async () => { expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(false) + ).toBe(false); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Check now') + ).toBe('Check now'); - MockUpdater.checkForUpdate() - await scheduler.getNextUpdatePromise() + MockUpdater.checkForUpdate(); + await scheduler.getNextUpdatePromise(); - expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible() + expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible(); expect( aboutElement.querySelector('.app-checking-for-updates') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(true) + ).toBe(true); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Check now') + ).toBe('Check now'); - MockUpdater.downloadUpdate() - await scheduler.getNextUpdatePromise() + MockUpdater.downloadUpdate(); + await scheduler.getNextUpdatePromise(); expect( aboutElement.querySelector('.app-checking-for-updates') - ).not.toBeVisible() + ).not.toBeVisible(); expect( aboutElement.querySelector('.app-downloading-update') - ).toBeVisible() + ).toBeVisible(); // TODO: at some point it would be nice to be able to cancel an update download, and then this would be a cancel button expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(true) + ).toBe(true); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Check now') + ).toBe('Check now'); - MockUpdater.finishDownloadingUpdate('42.0.0') - await scheduler.getNextUpdatePromise() + MockUpdater.finishDownloadingUpdate('42.0.0'); + await scheduler.getNextUpdatePromise(); expect( aboutElement.querySelector('.app-downloading-update') - ).not.toBeVisible() + ).not.toBeVisible(); expect( aboutElement.querySelector('.app-update-available-to-install') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector( '.app-update-available-to-install .about-updates-version' ).textContent - ).toBe('42.0.0') + ).toBe('42.0.0'); expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(false) + ).toBe(false); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Restart and install') - }) + ).toBe('Restart and install'); + }); it('opens the release notes for the downloaded release when the release notes link are clicked', async () => { - MockUpdater.finishDownloadingUpdate('1.2.3') - await scheduler.getNextUpdatePromise() + MockUpdater.finishDownloadingUpdate('1.2.3'); + await scheduler.getNextUpdatePromise(); - spyOn(shell, 'openExternal') + spyOn(shell, 'openExternal'); let link = aboutElement.querySelector( '.app-update-available-to-install .about-updates-release-notes' - ) - link.click() + ); + link.click(); - let args = shell.openExternal.mostRecentCall.args - expect(shell.openExternal).toHaveBeenCalled() - expect(args[0]).toContain('/v1.2.3') - }) + let args = shell.openExternal.mostRecentCall.args; + expect(shell.openExternal).toHaveBeenCalled(); + expect(args[0]).toContain('/v1.2.3'); + }); it('executes checkForUpdate() when the check for update button is clicked', () => { - let button = aboutElement.querySelector('.about-update-action-button') - button.click() - expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled() - }) + let button = aboutElement.querySelector('.about-update-action-button'); + button.click(); + expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled(); + }); it('executes restartAndInstallUpdate() when the restart and install button is clicked', async () => { - spyOn(atom.autoUpdater, 'restartAndInstallUpdate') - MockUpdater.finishDownloadingUpdate('42.0.0') - await scheduler.getNextUpdatePromise() + spyOn(atom.autoUpdater, 'restartAndInstallUpdate'); + MockUpdater.finishDownloadingUpdate('42.0.0'); + await scheduler.getNextUpdatePromise(); - let button = aboutElement.querySelector('.about-update-action-button') - button.click() - expect(atom.autoUpdater.restartAndInstallUpdate).toHaveBeenCalled() - }) + let button = aboutElement.querySelector('.about-update-action-button'); + button.click(); + expect(atom.autoUpdater.restartAndInstallUpdate).toHaveBeenCalled(); + }); it("starts in the same state as atom's AutoUpdateManager", async () => { - atom.autoUpdater.getState.andReturn('downloading') - updateManager.resetState() + atom.autoUpdater.getState.andReturn('downloading'); + updateManager.resetState(); - await scheduler.getNextUpdatePromise() + await scheduler.getNextUpdatePromise(); expect( aboutElement.querySelector('.app-checking-for-updates') - ).not.toBeVisible() + ).not.toBeVisible(); expect( aboutElement.querySelector('.app-downloading-update') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(true) + ).toBe(true); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Check now') - }) + ).toBe('Check now'); + }); describe('when core.automaticallyUpdate is toggled', () => { beforeEach(async () => { - expect(atom.config.get('core.automaticallyUpdate')).toBe(true) - atom.autoUpdater.checkForUpdate.reset() - }) + expect(atom.config.get('core.automaticallyUpdate')).toBe(true); + atom.autoUpdater.checkForUpdate.reset(); + }); it('shows the auto update UI', async () => { expect( aboutElement.querySelector('.about-auto-updates input').checked - ).toBe(true) + ).toBe(true); expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-default-update-message') .textContent - ).toBe('Atom will check for updates automatically') + ).toBe('Atom will check for updates automatically'); - atom.config.set('core.automaticallyUpdate', false) - await scheduler.getNextUpdatePromise() + atom.config.set('core.automaticallyUpdate', false); + await scheduler.getNextUpdatePromise(); expect( aboutElement.querySelector('.about-auto-updates input').checked - ).toBe(false) + ).toBe(false); expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-default-update-message') .textContent - ).toBe('Automatic updates are disabled please check manually') - }) + ).toBe('Automatic updates are disabled please check manually'); + }); it('updates config and the UI when the checkbox is used to toggle', async () => { expect( aboutElement.querySelector('.about-auto-updates input').checked - ).toBe(true) + ).toBe(true); - aboutElement.querySelector('.about-auto-updates input').click() - await scheduler.getNextUpdatePromise() + aboutElement.querySelector('.about-auto-updates input').click(); + await scheduler.getNextUpdatePromise(); - expect(atom.config.get('core.automaticallyUpdate')).toBe(false) + expect(atom.config.get('core.automaticallyUpdate')).toBe(false); expect( aboutElement.querySelector('.about-auto-updates input').checked - ).toBe(false) + ).toBe(false); expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-default-update-message') .textContent - ).toBe('Automatic updates are disabled please check manually') + ).toBe('Automatic updates are disabled please check manually'); - aboutElement.querySelector('.about-auto-updates input').click() - await scheduler.getNextUpdatePromise() + aboutElement.querySelector('.about-auto-updates input').click(); + await scheduler.getNextUpdatePromise(); - expect(atom.config.get('core.automaticallyUpdate')).toBe(true) + expect(atom.config.get('core.automaticallyUpdate')).toBe(true); expect( aboutElement.querySelector('.about-auto-updates input').checked - ).toBe(true) + ).toBe(true); expect( aboutElement.querySelector('.about-default-update-message') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector('.about-default-update-message') .textContent - ).toBe('Atom will check for updates automatically') - }) + ).toBe('Atom will check for updates automatically'); + }); - describe('checking for updates', function () { + describe('checking for updates', function() { afterEach(() => { - this.updateView = null - }) + this.updateView = null; + }); it('checks for update when the about page is shown', () => { - expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled() + expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled(); this.updateView = new UpdateView({ updateManager: updateManager, availableVersion: '9999.0.0', viewUpdateReleaseNotes: () => {} - }) + }); - expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled() - }) + expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled(); + }); it('does not check for update when the about page is shown and the update manager is not in the idle state', () => { - atom.autoUpdater.getState.andReturn('downloading') - updateManager.resetState() - expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled() + atom.autoUpdater.getState.andReturn('downloading'); + updateManager.resetState(); + expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled(); this.updateView = new UpdateView({ updateManager: updateManager, availableVersion: '9999.0.0', viewUpdateReleaseNotes: () => {} - }) + }); - expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled() - }) + expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled(); + }); it('does not check for update when the about page is shown and auto updates are turned off', () => { - atom.config.set('core.automaticallyUpdate', false) - expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled() + atom.config.set('core.automaticallyUpdate', false); + expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled(); this.updateView = new UpdateView({ updateManager: updateManager, availableVersion: '9999.0.0', viewUpdateReleaseNotes: () => {} - }) + }); - expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled() - }) - }) - }) - }) - }) + expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled(); + }); + }); + }); + }); + }); describe('when the About page is not open and an update is downloaded', () => { it('should display the new version when it is opened', async () => { - MockUpdater.finishDownloadingUpdate('42.0.0') + MockUpdater.finishDownloadingUpdate('42.0.0'); - jasmine.attachToDOM(workspaceElement) - await atom.workspace.open('atom://about') - aboutElement = workspaceElement.querySelector('.about') - updateManager = main.model.state.updateManager - scheduler = AboutView.getScheduler() + jasmine.attachToDOM(workspaceElement); + await atom.workspace.open('atom://about'); + aboutElement = workspaceElement.querySelector('.about'); + updateManager = main.model.state.updateManager; + scheduler = AboutView.getScheduler(); expect( aboutElement.querySelector('.app-update-available-to-install') - ).toBeVisible() + ).toBeVisible(); expect( aboutElement.querySelector( '.app-update-available-to-install .about-updates-version' ).textContent - ).toBe('42.0.0') + ).toBe('42.0.0'); expect( aboutElement.querySelector('.about-update-action-button').disabled - ).toBe(false) + ).toBe(false); expect( aboutElement.querySelector('.about-update-action-button').textContent - ).toBe('Restart and install') - }) - }) -}) + ).toBe('Restart and install'); + }); + }); +}); diff --git a/packages/dalek/lib/dalek.js b/packages/dalek/lib/dalek.js index 3f1944a0c..335536517 100644 --- a/packages/dalek/lib/dalek.js +++ b/packages/dalek/lib/dalek.js @@ -1,56 +1,56 @@ /** @babel */ -const fs = require('fs') -const path = require('path') +const fs = require('fs'); +const path = require('path'); module.exports = { - async enumerate () { + async enumerate() { if (atom.inDevMode()) { - return [] + return []; } - const duplicatePackages = [] - const names = atom.packages.getAvailablePackageNames() + const duplicatePackages = []; + const names = atom.packages.getAvailablePackageNames(); for (let name of names) { if (atom.packages.isBundledPackage(name)) { const isDuplicatedPackage = await this.isInstalledAsCommunityPackage( name - ) + ); if (isDuplicatedPackage) { - duplicatePackages.push(name) + duplicatePackages.push(name); } } } - return duplicatePackages + return duplicatePackages; }, - async isInstalledAsCommunityPackage (name) { - const availablePackagePaths = atom.packages.getPackageDirPaths() + async isInstalledAsCommunityPackage(name) { + const availablePackagePaths = atom.packages.getPackageDirPaths(); for (let packagePath of availablePackagePaths) { - const candidate = path.join(packagePath, name) + const candidate = path.join(packagePath, name); if (fs.existsSync(candidate)) { - const realPath = await this.realpath(candidate) + const realPath = await this.realpath(candidate); if (realPath === candidate) { - return true + return true; } } } - return false + return false; }, - realpath (path) { + realpath(path) { return new Promise((resolve, reject) => { - fs.realpath(path, function (error, realpath) { + fs.realpath(path, function(error, realpath) { if (error) { - reject(error) + reject(error); } else { - resolve(realpath) + resolve(realpath); } - }) - }) + }); + }); } -} +}; diff --git a/packages/dalek/lib/main.js b/packages/dalek/lib/main.js index 8ec35d9ca..db80204b8 100644 --- a/packages/dalek/lib/main.js +++ b/packages/dalek/lib/main.js @@ -1,19 +1,19 @@ /** @babel */ -const dalek = require('./dalek') -const Grim = require('grim') +const dalek = require('./dalek'); +const Grim = require('grim'); module.exports = { - activate () { + activate() { atom.packages.onDidActivateInitialPackages(async () => { - const duplicates = await dalek.enumerate() + const duplicates = await dalek.enumerate(); for (let i = 0; i < duplicates.length; i++) { - const duplicate = duplicates[i] + const duplicate = duplicates[i]; Grim.deprecate( `You have the core package "${duplicate}" installed as a community package. See https://github.com/atom/atom/blob/master/packages/dalek/README.md for how this causes problems and instructions on how to correct the situation.`, { packageName: duplicate } - ) + ); } - }) + }); } -} +}; diff --git a/packages/dalek/test/dalek.test.js b/packages/dalek/test/dalek.test.js index ff1ba394c..ed4a7086f 100644 --- a/packages/dalek/test/dalek.test.js +++ b/packages/dalek/test/dalek.test.js @@ -1,21 +1,21 @@ /** @babel */ -const assert = require('assert') -const fs = require('fs') -const sinon = require('sinon') -const path = require('path') +const assert = require('assert'); +const fs = require('fs'); +const sinon = require('sinon'); +const path = require('path'); -const dalek = require('../lib/dalek') +const dalek = require('../lib/dalek'); -describe('dalek', function () { - describe('enumerate', function () { - let availablePackages = {} - let realPaths = {} - let bundledPackages = [] - let packageDirPaths = [] - let sandbox = null +describe('dalek', function() { + describe('enumerate', function() { + let availablePackages = {}; + let realPaths = {}; + let bundledPackages = []; + let packageDirPaths = []; + let sandbox = null; - beforeEach(function () { + beforeEach(function() { availablePackages = { 'an-unduplicated-installed-package': path.join( 'Users', @@ -36,66 +36,68 @@ describe('dalek', function () { 'node_modules', 'unduplicated-package' ) - } + }; - atom.devMode = false - bundledPackages = ['duplicated-package', 'unduplicated-package'] - packageDirPaths = [path.join('Users', 'username', '.atom', 'packages')] - sandbox = sinon.sandbox.create() + atom.devMode = false; + bundledPackages = ['duplicated-package', 'unduplicated-package']; + packageDirPaths = [path.join('Users', 'username', '.atom', 'packages')]; + sandbox = sinon.sandbox.create(); sandbox .stub(dalek, 'realpath') - .callsFake(filePath => Promise.resolve(realPaths[filePath] || filePath)) + .callsFake(filePath => + Promise.resolve(realPaths[filePath] || filePath) + ); sandbox.stub(atom.packages, 'isBundledPackage').callsFake(packageName => { - return bundledPackages.includes(packageName) - }) + return bundledPackages.includes(packageName); + }); sandbox .stub(atom.packages, 'getAvailablePackageNames') - .callsFake(() => Object.keys(availablePackages)) + .callsFake(() => Object.keys(availablePackages)); sandbox.stub(atom.packages, 'getPackageDirPaths').callsFake(() => { - return packageDirPaths - }) + return packageDirPaths; + }); sandbox.stub(fs, 'existsSync').callsFake(candidate => { return ( Object.values(availablePackages).includes(candidate) && !candidate.includes(atom.getLoadSettings().resourcePath) - ) - }) - }) + ); + }); + }); - afterEach(function () { - sandbox.restore() - }) + afterEach(function() { + sandbox.restore(); + }); - it('returns a list of duplicate names', async function () { - assert.deepEqual(await dalek.enumerate(), ['duplicated-package']) - }) + it('returns a list of duplicate names', async function() { + assert.deepEqual(await dalek.enumerate(), ['duplicated-package']); + }); - describe('when in dev mode', function () { - beforeEach(function () { - atom.devMode = true - }) + describe('when in dev mode', function() { + beforeEach(function() { + atom.devMode = true; + }); - it('always returns an empty list', async function () { - assert.deepEqual(await dalek.enumerate(), []) - }) - }) + it('always returns an empty list', async function() { + assert.deepEqual(await dalek.enumerate(), []); + }); + }); - describe('when a package is symlinked into the package directory', async function () { - beforeEach(function () { - const realPath = path.join('Users', 'username', 'duplicated-package') + describe('when a package is symlinked into the package directory', async function() { + beforeEach(function() { + const realPath = path.join('Users', 'username', 'duplicated-package'); const packagePath = path.join( 'Users', 'username', '.atom', 'packages', 'duplicated-package' - ) - realPaths[packagePath] = realPath - }) + ); + realPaths[packagePath] = realPath; + }); - it('is not included in the list of duplicate names', async function () { - assert.deepEqual(await dalek.enumerate(), []) - }) - }) - }) -}) + it('is not included in the list of duplicate names', async function() { + assert.deepEqual(await dalek.enumerate(), []); + }); + }); + }); +}); diff --git a/packages/dalek/test/runner.js b/packages/dalek/test/runner.js index 7b155fa67..39083303a 100644 --- a/packages/dalek/test/runner.js +++ b/packages/dalek/test/runner.js @@ -1,2 +1,2 @@ -const createRunner = require('atom-mocha-test-runner').createRunner -module.exports = createRunner({ testSuffixes: ['test.js'] }) +const createRunner = require('atom-mocha-test-runner').createRunner; +module.exports = createRunner({ testSuffixes: ['test.js'] }); diff --git a/packages/deprecation-cop/lib/deprecation-cop-view.js b/packages/deprecation-cop/lib/deprecation-cop-view.js index 0531a2631..b26139b23 100644 --- a/packages/deprecation-cop/lib/deprecation-cop-view.js +++ b/packages/deprecation-cop/lib/deprecation-cop-view.js @@ -1,88 +1,88 @@ /** @babel */ /** @jsx etch.dom */ -import _ from 'underscore-plus' -import { CompositeDisposable } from 'atom' -import etch from 'etch' -import fs from 'fs-plus' -import Grim from 'grim' -import marked from 'marked' -import path from 'path' -import shell from 'shell' +import _ from 'underscore-plus'; +import { CompositeDisposable } from 'atom'; +import etch from 'etch'; +import fs from 'fs-plus'; +import Grim from 'grim'; +import marked from 'marked'; +import path from 'path'; +import shell from 'shell'; export default class DeprecationCopView { - constructor ({ uri }) { - this.uri = uri - this.subscriptions = new CompositeDisposable() + constructor({ uri }) { + this.uri = uri; + this.subscriptions = new CompositeDisposable(); this.subscriptions.add( Grim.on('updated', () => { - etch.update(this) + etch.update(this); }) - ) + ); // TODO: Remove conditional when the new StyleManager deprecation APIs reach stable. if (atom.styles.onDidUpdateDeprecations) { this.subscriptions.add( atom.styles.onDidUpdateDeprecations(() => { - etch.update(this) + etch.update(this); }) - ) + ); } - etch.initialize(this) + etch.initialize(this); this.subscriptions.add( atom.commands.add(this.element, { 'core:move-up': () => { - this.scrollUp() + this.scrollUp(); }, 'core:move-down': () => { - this.scrollDown() + this.scrollDown(); }, 'core:page-up': () => { - this.pageUp() + this.pageUp(); }, 'core:page-down': () => { - this.pageDown() + this.pageDown(); }, 'core:move-to-top': () => { - this.scrollToTop() + this.scrollToTop(); }, 'core:move-to-bottom': () => { - this.scrollToBottom() + this.scrollToBottom(); } }) - ) + ); } - serialize () { + serialize() { return { deserializer: this.constructor.name, uri: this.getURI(), version: 1 - } + }; } - destroy () { - this.subscriptions.dispose() - return etch.destroy(this) + destroy() { + this.subscriptions.dispose(); + return etch.destroy(this); } - update () { - return etch.update(this) + update() { + return etch.update(this); } - render () { + render() { return (
-
-
-
+
+
+
-
+
Deprecated calls
-
    +
      {this.renderDeprecatedCalls()}
    -
    +
    Deprecated selectors
    -
      +
        {this.renderDeprecatedSelectors()}
- ) + ); } - renderDeprecatedCalls () { - const deprecationsByPackageName = this.getDeprecatedCallsByPackageName() - const packageNames = Object.keys(deprecationsByPackageName) + renderDeprecatedCalls() { + const deprecationsByPackageName = this.getDeprecatedCallsByPackageName(); + const packageNames = Object.keys(deprecationsByPackageName); if (packageNames.length === 0) { - return
  • No deprecated calls
  • + return
  • No deprecated calls
  • ; } else { return packageNames.sort().map(packageName => ( -
  • +
  • event.target.parentElement.classList.toggle('collapsed') } > - {packageName || 'atom core'} + {packageName || 'atom core'} {` (${_.pluralize( deprecationsByPackageName[packageName].length, 'deprecation' )})`}
    -
      + - )) + )); } } - renderDeprecatedSelectors () { - const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName() - const packageNames = Object.keys(deprecationsByPackageName) + renderDeprecatedSelectors() { + const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName(); + const packageNames = Object.keys(deprecationsByPackageName); if (packageNames.length === 0) { - return
    • No deprecated selectors
    • + return
    • No deprecated selectors
    • ; } else { return packageNames.map(packageName => ( -
    • +
    • event.target.parentElement.classList.toggle('collapsed') } > - {packageName} + {packageName}
      -
        +
          {this.renderPackageActionsIfNeeded(packageName)} {deprecationsByPackageName[packageName].map( ({ packagePath, sourcePath, deprecation }) => { const relativeSourcePath = path.relative( packagePath, sourcePath - ) - const issueTitle = `Deprecated selector in \`${relativeSourcePath}\`` + ); + const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``; const issueBody = `In \`${relativeSourcePath}\`: \n\n${ deprecation.message - }` + }`; return ( -
        • +
        • { - event.preventDefault() - this.openLocation(sourcePath) + event.preventDefault(); + this.openLocation(sourcePath); }} > {relativeSourcePath} -
            -
          • - +
              +
            • +
              {this.renderSelectorIssueURLIfNeeded( @@ -227,138 +227,138 @@ export default class DeprecationCopView {
          • - ) + ); } )}
        • - )) + )); } } - renderPackageActionsIfNeeded (packageName) { + renderPackageActionsIfNeeded(packageName) { if (packageName && atom.packages.getLoadedPackage(packageName)) { return ( -
          -
          +
          +
          - ) + ); } else { - return '' + return ''; } } - encodeURI (str) { + encodeURI(str) { return encodeURI(str) .replace(/#/g, '%23') .replace(/;/g, '%3B') - .replace(/%20/g, '+') + .replace(/%20/g, '+'); } - renderSelectorIssueURLIfNeeded (packageName, issueTitle, issueBody) { - const repoURL = this.getRepoURL(packageName) + renderSelectorIssueURLIfNeeded(packageName, issueTitle, issueBody) { + const repoURL = this.getRepoURL(packageName); if (repoURL) { const issueURL = `${repoURL}/issues/new?title=${this.encodeURI( issueTitle - )}&body=${this.encodeURI(issueBody)}` + )}&body=${this.encodeURI(issueBody)}`; return ( -
          +
          - ) + ); } else { - return '' + return ''; } } - renderIssueURLIfNeeded (packageName, deprecation, issueURL) { + renderIssueURLIfNeeded(packageName, deprecation, issueURL) { if (packageName && issueURL) { - const repoURL = this.getRepoURL(packageName) - const issueTitle = `${deprecation.getOriginName()} is deprecated.` + const repoURL = this.getRepoURL(packageName); + const issueTitle = `${deprecation.getOriginName()} is deprecated.`; return ( -
          +
          - ) + ); } else { - return '' + return ''; } } - buildIssueURL (packageName, deprecation, stack) { - const repoURL = this.getRepoURL(packageName) + buildIssueURL(packageName, deprecation, stack) { + const repoURL = this.getRepoURL(packageName); if (repoURL) { - const title = `${deprecation.getOriginName()} is deprecated.` + const title = `${deprecation.getOriginName()} is deprecated.`; const stacktrace = stack .map(({ functionName, location }) => `${functionName} (${location})`) - .join('\n') - const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\`` + .join('\n'); + const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\``; return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI( body - )}` + )}`; } else { - return null + return null; } } - async openIssueURL (repoURL, issueURL, issueTitle) { - const issue = await this.findSimilarIssue(repoURL, issueTitle) + async openIssueURL(repoURL, issueURL, issueTitle) { + const issue = await this.findSimilarIssue(repoURL, issueTitle); if (issue) { - shell.openExternal(issue.html_url) + shell.openExternal(issue.html_url); } else if (process.platform === 'win32') { // Windows will not launch URLs greater than ~2000 bytes so we need to shrink it - shell.openExternal((await this.shortenURL(issueURL)) || issueURL) + shell.openExternal((await this.shortenURL(issueURL)) || issueURL); } else { - shell.openExternal(issueURL) + shell.openExternal(issueURL); } } - async findSimilarIssue (repoURL, issueTitle) { - const url = 'https://api.github.com/search/issues' - const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, '') - const query = `${issueTitle} repo:${repo}` + async findSimilarIssue(repoURL, issueTitle) { + const url = 'https://api.github.com/search/issues'; + const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, ''); + const query = `${issueTitle} repo:${repo}`; const response = await window.fetch( `${url}?q=${encodeURI(query)}&sort=created`, { @@ -368,45 +368,45 @@ export default class DeprecationCopView { 'Content-Type': 'application/json' } } - ) + ); if (response.ok) { - const data = await response.json() + const data = await response.json(); if (data.items) { - const issues = {} + const issues = {}; for (const issue of data.items) { if (issue.title.includes(issueTitle) && !issues[issue.state]) { - issues[issue.state] = issue + issues[issue.state] = issue; } } - return issues.open || issues.closed + return issues.open || issues.closed; } } } - async shortenURL (url) { - let encodedUrl = encodeURIComponent(url).substr(0, 5000) // is.gd has 5000 char limit + async shortenURL(url) { + let encodedUrl = encodeURIComponent(url).substr(0, 5000); // is.gd has 5000 char limit let incompletePercentEncoding = encodedUrl.indexOf( '%', encodedUrl.length - 2 - ) + ); if (incompletePercentEncoding >= 0) { // Handle an incomplete % encoding cut-off - encodedUrl = encodedUrl.substr(0, incompletePercentEncoding) + encodedUrl = encodedUrl.substr(0, incompletePercentEncoding); } let result = await fetch('https://is.gd/create.php?format=simple', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `url=${encodedUrl}` - }) + }); - return result.text() + return result.text(); } - getRepoURL (packageName) { - const loadedPackage = atom.packages.getLoadedPackage(packageName) + getRepoURL(packageName) { + const loadedPackage = atom.packages.getLoadedPackage(packageName); if ( loadedPackage && loadedPackage.metadata && @@ -414,171 +414,171 @@ export default class DeprecationCopView { ) { const url = loadedPackage.metadata.repository.url || - loadedPackage.metadata.repository - return url.replace(/\.git$/, '') + loadedPackage.metadata.repository; + return url.replace(/\.git$/, ''); } else { - return null + return null; } } - getDeprecatedCallsByPackageName () { - const deprecatedCalls = Grim.getDeprecations() - deprecatedCalls.sort((a, b) => b.getCallCount() - a.getCallCount()) - const deprecatedCallsByPackageName = {} + getDeprecatedCallsByPackageName() { + const deprecatedCalls = Grim.getDeprecations(); + deprecatedCalls.sort((a, b) => b.getCallCount() - a.getCallCount()); + const deprecatedCallsByPackageName = {}; for (const deprecation of deprecatedCalls) { - const stacks = deprecation.getStacks() - stacks.sort((a, b) => b.callCount - a.callCount) + const stacks = deprecation.getStacks(); + stacks.sort((a, b) => b.callCount - a.callCount); for (const stack of stacks) { - let packageName = null + let packageName = null; if (stack.metadata && stack.metadata.packageName) { - packageName = stack.metadata.packageName + packageName = stack.metadata.packageName; } else { - packageName = (this.getPackageName(stack) || '').toLowerCase() + packageName = (this.getPackageName(stack) || '').toLowerCase(); } deprecatedCallsByPackageName[packageName] = - deprecatedCallsByPackageName[packageName] || [] - deprecatedCallsByPackageName[packageName].push({ deprecation, stack }) + deprecatedCallsByPackageName[packageName] || []; + deprecatedCallsByPackageName[packageName].push({ deprecation, stack }); } } - return deprecatedCallsByPackageName + return deprecatedCallsByPackageName; } - getDeprecatedSelectorsByPackageName () { - const deprecatedSelectorsByPackageName = {} + getDeprecatedSelectorsByPackageName() { + const deprecatedSelectorsByPackageName = {}; if (atom.styles.getDeprecations) { - const deprecatedSelectorsBySourcePath = atom.styles.getDeprecations() + const deprecatedSelectorsBySourcePath = atom.styles.getDeprecations(); for (const sourcePath of Object.keys(deprecatedSelectorsBySourcePath)) { - const deprecation = deprecatedSelectorsBySourcePath[sourcePath] - const components = sourcePath.split(path.sep) - const packagesComponentIndex = components.indexOf('packages') - let packageName = null - let packagePath = null + const deprecation = deprecatedSelectorsBySourcePath[sourcePath]; + const components = sourcePath.split(path.sep); + const packagesComponentIndex = components.indexOf('packages'); + let packageName = null; + let packagePath = null; if (packagesComponentIndex === -1) { - packageName = 'Other' // could be Atom Core or the personal style sheet - packagePath = '' + packageName = 'Other'; // could be Atom Core or the personal style sheet + packagePath = ''; } else { - packageName = components[packagesComponentIndex + 1] + packageName = components[packagesComponentIndex + 1]; packagePath = components .slice(0, packagesComponentIndex + 1) - .join(path.sep) + .join(path.sep); } deprecatedSelectorsByPackageName[packageName] = - deprecatedSelectorsByPackageName[packageName] || [] + deprecatedSelectorsByPackageName[packageName] || []; deprecatedSelectorsByPackageName[packageName].push({ packagePath, sourcePath, deprecation - }) + }); } } - return deprecatedSelectorsByPackageName + return deprecatedSelectorsByPackageName; } - getPackageName (stack) { - const packagePaths = this.getPackagePathsByPackageName() + getPackageName(stack) { + const packagePaths = this.getPackagePathsByPackageName(); for (const [packageName, packagePath] of packagePaths) { if ( packagePath.includes('.atom/dev/packages') || packagePath.includes('.atom/packages') ) { - packagePaths.set(packageName, fs.absolute(packagePath)) + packagePaths.set(packageName, fs.absolute(packagePath)); } } for (let i = 1; i < stack.length; i++) { - const { fileName } = stack[i] + const { fileName } = stack[i]; // Empty when it was run from the dev console if (!fileName) { - return null + return null; } // Continue to next stack entry if call is in node_modules if (fileName.includes(`${path.sep}node_modules${path.sep}`)) { - continue + continue; } for (const [packageName, packagePath] of packagePaths) { - const relativePath = path.relative(packagePath, fileName) + const relativePath = path.relative(packagePath, fileName); if (!/^\.\./.test(relativePath)) { - return packageName + return packageName; } } if (atom.getUserInitScriptPath() === fileName) { - return `Your local ${path.basename(fileName)} file` + return `Your local ${path.basename(fileName)} file`; } } - return null + return null; } - getPackagePathsByPackageName () { + getPackagePathsByPackageName() { if (this.packagePathsByPackageName) { - return this.packagePathsByPackageName + return this.packagePathsByPackageName; } else { - this.packagePathsByPackageName = new Map() + this.packagePathsByPackageName = new Map(); for (const pack of atom.packages.getLoadedPackages()) { - this.packagePathsByPackageName.set(pack.name, pack.path) + this.packagePathsByPackageName.set(pack.name, pack.path); } - return this.packagePathsByPackageName + return this.packagePathsByPackageName; } } - checkForUpdates () { - atom.workspace.open('atom://config/updates') + checkForUpdates() { + atom.workspace.open('atom://config/updates'); } - disablePackage (packageName) { + disablePackage(packageName) { if (packageName) { - atom.packages.disablePackage(packageName) + atom.packages.disablePackage(packageName); } } - openLocation (location) { - let pathToOpen = location.replace('file://', '') + openLocation(location) { + let pathToOpen = location.replace('file://', ''); if (process.platform === 'win32') { - pathToOpen = pathToOpen.replace(/^\//, '') + pathToOpen = pathToOpen.replace(/^\//, ''); } - atom.open({ pathsToOpen: [pathToOpen] }) + atom.open({ pathsToOpen: [pathToOpen] }); } - getURI () { - return this.uri + getURI() { + return this.uri; } - getTitle () { - return 'Deprecation Cop' + getTitle() { + return 'Deprecation Cop'; } - getIconName () { - return 'alert' + getIconName() { + return 'alert'; } - scrollUp () { - this.element.scrollTop -= document.body.offsetHeight / 20 + scrollUp() { + this.element.scrollTop -= document.body.offsetHeight / 20; } - scrollDown () { - this.element.scrollTop += document.body.offsetHeight / 20 + scrollDown() { + this.element.scrollTop += document.body.offsetHeight / 20; } - pageUp () { - this.element.scrollTop -= this.element.offsetHeight + pageUp() { + this.element.scrollTop -= this.element.offsetHeight; } - pageDown () { - this.element.scrollTop += this.element.offsetHeight + pageDown() { + this.element.scrollTop += this.element.offsetHeight; } - scrollToTop () { - this.element.scrollTop = 0 + scrollToTop() { + this.element.scrollTop = 0; } - scrollToBottom () { - this.element.scrollTop = this.element.scrollHeight + scrollToBottom() { + this.element.scrollTop = this.element.scrollHeight; } } diff --git a/packages/deprecation-cop/lib/main.js b/packages/deprecation-cop/lib/main.js index 3dd94da6d..bb3f42491 100644 --- a/packages/deprecation-cop/lib/main.js +++ b/packages/deprecation-cop/lib/main.js @@ -1,54 +1,54 @@ -const { Disposable, CompositeDisposable } = require('atom') -const DeprecationCopView = require('./deprecation-cop-view') -const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view') -const ViewURI = 'atom://deprecation-cop' +const { Disposable, CompositeDisposable } = require('atom'); +const DeprecationCopView = require('./deprecation-cop-view'); +const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view'); +const ViewURI = 'atom://deprecation-cop'; class DeprecationCopPackage { - activate () { - this.disposables = new CompositeDisposable() + activate() { + this.disposables = new CompositeDisposable(); this.disposables.add( atom.workspace.addOpener(uri => { if (uri === ViewURI) { - return this.deserializeDeprecationCopView({ uri }) + return this.deserializeDeprecationCopView({ uri }); } }) - ) + ); this.disposables.add( atom.commands.add('atom-workspace', 'deprecation-cop:view', () => { - atom.workspace.open(ViewURI) + atom.workspace.open(ViewURI); }) - ) + ); } - deactivate () { - this.disposables.dispose() - const pane = atom.workspace.paneForURI(ViewURI) + deactivate() { + this.disposables.dispose(); + const pane = atom.workspace.paneForURI(ViewURI); if (pane) { - pane.destroyItem(pane.itemForURI(ViewURI)) + pane.destroyItem(pane.itemForURI(ViewURI)); } } - deserializeDeprecationCopView (state) { - return new DeprecationCopView(state) + deserializeDeprecationCopView(state) { + return new DeprecationCopView(state); } - consumeStatusBar (statusBar) { - const statusBarView = new DeprecationCopStatusBarView() + consumeStatusBar(statusBar) { + const statusBarView = new DeprecationCopStatusBarView(); const statusBarTile = statusBar.addRightTile({ item: statusBarView, priority: 150 - }) + }); this.disposables.add( new Disposable(() => { - statusBarView.destroy() + statusBarView.destroy(); }) - ) + ); this.disposables.add( new Disposable(() => { - statusBarTile.destroy() + statusBarTile.destroy(); }) - ) + ); } } -module.exports = new DeprecationCopPackage() +module.exports = new DeprecationCopPackage(); diff --git a/packages/dev-live-reload/lib/base-theme-watcher.js b/packages/dev-live-reload/lib/base-theme-watcher.js index e18a974f6..7956bcdf6 100644 --- a/packages/dev-live-reload/lib/base-theme-watcher.js +++ b/packages/dev-live-reload/lib/base-theme-watcher.js @@ -1,31 +1,31 @@ -const fs = require('fs-plus') -const path = require('path') -const Watcher = require('./watcher') +const fs = require('fs-plus'); +const path = require('path'); +const Watcher = require('./watcher'); module.exports = class BaseThemeWatcher extends Watcher { - constructor () { - super() + constructor() { + super(); this.stylesheetsPath = path.dirname( atom.themes.resolveStylesheet('../static/atom.less') - ) - this.watch() + ); + this.watch(); } - watch () { + watch() { const filePaths = fs .readdirSync(this.stylesheetsPath) - .filter(filePath => path.extname(filePath).includes('less')) + .filter(filePath => path.extname(filePath).includes('less')); for (const filePath of filePaths) { - this.watchFile(path.join(this.stylesheetsPath, filePath)) + this.watchFile(path.join(this.stylesheetsPath, filePath)); } } - loadStylesheet () { - this.loadAllStylesheets() + loadStylesheet() { + this.loadAllStylesheets(); } - loadAllStylesheets () { - atom.themes.reloadBaseStylesheets() + loadAllStylesheets() { + atom.themes.reloadBaseStylesheets(); } -} +}; diff --git a/packages/dev-live-reload/lib/main.js b/packages/dev-live-reload/lib/main.js index a486231cf..1f1f6f26c 100644 --- a/packages/dev-live-reload/lib/main.js +++ b/packages/dev-live-reload/lib/main.js @@ -1,30 +1,30 @@ module.exports = { - activate (state) { - if (!atom.inDevMode() || atom.inSpecMode()) return + activate(state) { + if (!atom.inDevMode() || atom.inSpecMode()) return; if (atom.packages.hasActivatedInitialPackages()) { - this.startWatching() + this.startWatching(); } else { this.activatedDisposable = atom.packages.onDidActivateInitialPackages( () => this.startWatching() - ) + ); } }, - deactivate () { - if (this.activatedDisposable) this.activatedDisposable.dispose() - if (this.commandDisposable) this.commandDisposable.dispose() - if (this.uiWatcher) this.uiWatcher.destroy() + deactivate() { + if (this.activatedDisposable) this.activatedDisposable.dispose(); + if (this.commandDisposable) this.commandDisposable.dispose(); + if (this.uiWatcher) this.uiWatcher.destroy(); }, - startWatching () { - const UIWatcher = require('./ui-watcher') - this.uiWatcher = new UIWatcher({ themeManager: atom.themes }) + startWatching() { + const UIWatcher = require('./ui-watcher'); + this.uiWatcher = new UIWatcher({ themeManager: atom.themes }); this.commandDisposable = atom.commands.add( 'atom-workspace', 'dev-live-reload:reload-all', () => this.uiWatcher.reloadAll() - ) - if (this.activatedDisposable) this.activatedDisposable.dispose() + ); + if (this.activatedDisposable) this.activatedDisposable.dispose(); } -} +}; diff --git a/packages/dev-live-reload/lib/package-watcher.js b/packages/dev-live-reload/lib/package-watcher.js index 2cb8096dd..e3c1b6d9c 100644 --- a/packages/dev-live-reload/lib/package-watcher.js +++ b/packages/dev-live-reload/lib/package-watcher.js @@ -1,49 +1,50 @@ -const fs = require('fs-plus') +const fs = require('fs-plus'); -const Watcher = require('./watcher') +const Watcher = require('./watcher'); module.exports = class PackageWatcher extends Watcher { - static supportsPackage (pack, type) { - if (pack.getType() === type && pack.getStylesheetPaths().length) return true - return false + static supportsPackage(pack, type) { + if (pack.getType() === type && pack.getStylesheetPaths().length) + return true; + return false; } - constructor (pack) { - super() - this.pack = pack - this.watch() + constructor(pack) { + super(); + this.pack = pack; + this.watch(); } - watch () { - const watchedPaths = [] + watch() { + const watchedPaths = []; const watchPath = stylesheet => { - if (!watchedPaths.includes(stylesheet)) this.watchFile(stylesheet) - watchedPaths.push(stylesheet) - } + if (!watchedPaths.includes(stylesheet)) this.watchFile(stylesheet); + watchedPaths.push(stylesheet); + }; - const stylesheetsPath = this.pack.getStylesheetsPath() + const stylesheetsPath = this.pack.getStylesheetsPath(); if (fs.isDirectorySync(stylesheetsPath)) { - this.watchDirectory(stylesheetsPath) + this.watchDirectory(stylesheetsPath); } - const stylesheetPaths = new Set(this.pack.getStylesheetPaths()) - const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath) - const onFolder = () => true - fs.traverseTreeSync(stylesheetsPath, onFile, onFolder) + const stylesheetPaths = new Set(this.pack.getStylesheetPaths()); + const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath); + const onFolder = () => true; + fs.traverseTreeSync(stylesheetsPath, onFile, onFolder); for (let stylesheet of stylesheetPaths) { - watchPath(stylesheet) + watchPath(stylesheet); } } - loadStylesheet (pathName) { - if (pathName.includes('variables')) this.emitGlobalsChanged() - this.loadAllStylesheets() + loadStylesheet(pathName) { + if (pathName.includes('variables')) this.emitGlobalsChanged(); + this.loadAllStylesheets(); } - loadAllStylesheets () { - console.log('Reloading package', this.pack.name) - this.pack.reloadStylesheets() + loadAllStylesheets() { + console.log('Reloading package', this.pack.name); + this.pack.reloadStylesheets(); } -} +}; diff --git a/packages/dev-live-reload/lib/ui-watcher.js b/packages/dev-live-reload/lib/ui-watcher.js index eb5ac2635..241add302 100644 --- a/packages/dev-live-reload/lib/ui-watcher.js +++ b/packages/dev-live-reload/lib/ui-watcher.js @@ -1,114 +1,114 @@ -const { CompositeDisposable } = require('atom') +const { CompositeDisposable } = require('atom'); -const BaseThemeWatcher = require('./base-theme-watcher') -const PackageWatcher = require('./package-watcher') +const BaseThemeWatcher = require('./base-theme-watcher'); +const PackageWatcher = require('./package-watcher'); module.exports = class UIWatcher { - constructor () { - this.subscriptions = new CompositeDisposable() - this.reloadAll = this.reloadAll.bind(this) - this.watchers = [] - this.baseTheme = this.createWatcher(new BaseThemeWatcher()) - this.watchPackages() + constructor() { + this.subscriptions = new CompositeDisposable(); + this.reloadAll = this.reloadAll.bind(this); + this.watchers = []; + this.baseTheme = this.createWatcher(new BaseThemeWatcher()); + this.watchPackages(); } - watchPackages () { - this.watchedThemes = new Map() - this.watchedPackages = new Map() + watchPackages() { + this.watchedThemes = new Map(); + this.watchedPackages = new Map(); for (const theme of atom.themes.getActiveThemes()) { - this.watchTheme(theme) + this.watchTheme(theme); } for (const pack of atom.packages.getActivePackages()) { - this.watchPackage(pack) + this.watchPackage(pack); } - this.watchForPackageChanges() + this.watchForPackageChanges(); } - watchForPackageChanges () { + watchForPackageChanges() { this.subscriptions.add( atom.themes.onDidChangeActiveThemes(() => { // We need to destroy all theme watchers as all theme packages are destroyed // when a theme changes. for (const theme of this.watchedThemes.values()) { - theme.destroy() + theme.destroy(); } - this.watchedThemes.clear() + this.watchedThemes.clear(); // Rewatch everything! for (const theme of atom.themes.getActiveThemes()) { - this.watchTheme(theme) + this.watchTheme(theme); } }) - ) + ); this.subscriptions.add( atom.packages.onDidActivatePackage(pack => this.watchPackage(pack)) - ) + ); this.subscriptions.add( atom.packages.onDidDeactivatePackage(pack => { // This only handles packages - onDidChangeActiveThemes handles themes - const watcher = this.watchedPackages.get(pack.name) - if (watcher) watcher.destroy() - this.watchedPackages.delete(pack.name) + const watcher = this.watchedPackages.get(pack.name); + if (watcher) watcher.destroy(); + this.watchedPackages.delete(pack.name); }) - ) + ); } - watchTheme (theme) { + watchTheme(theme) { if (PackageWatcher.supportsPackage(theme, 'theme')) { this.watchedThemes.set( theme.name, this.createWatcher(new PackageWatcher(theme)) - ) + ); } } - watchPackage (pack) { + watchPackage(pack) { if (PackageWatcher.supportsPackage(pack, 'atom')) { this.watchedPackages.set( pack.name, this.createWatcher(new PackageWatcher(pack)) - ) + ); } } - createWatcher (watcher) { + createWatcher(watcher) { watcher.onDidChangeGlobals(() => { - console.log('Global changed, reloading all styles') - this.reloadAll() - }) + console.log('Global changed, reloading all styles'); + this.reloadAll(); + }); watcher.onDidDestroy(() => this.watchers.splice(this.watchers.indexOf(watcher), 1) - ) - this.watchers.push(watcher) - return watcher + ); + this.watchers.push(watcher); + return watcher; } - reloadAll () { - this.baseTheme.loadAllStylesheets() + reloadAll() { + this.baseTheme.loadAllStylesheets(); for (const pack of atom.packages.getActivePackages()) { if (PackageWatcher.supportsPackage(pack, 'atom')) { - pack.reloadStylesheets() + pack.reloadStylesheets(); } } for (const theme of atom.themes.getActiveThemes()) { if (PackageWatcher.supportsPackage(theme, 'theme')) { - theme.reloadStylesheets() + theme.reloadStylesheets(); } } } - destroy () { - this.subscriptions.dispose() - this.baseTheme.destroy() + destroy() { + this.subscriptions.dispose(); + this.baseTheme.destroy(); for (const pack of this.watchedPackages.values()) { - pack.destroy() + pack.destroy(); } for (const theme of this.watchedThemes.values()) { - theme.destroy() + theme.destroy(); } } -} +}; diff --git a/packages/dev-live-reload/lib/watcher.js b/packages/dev-live-reload/lib/watcher.js index b1b3631a5..5237e1ea7 100644 --- a/packages/dev-live-reload/lib/watcher.js +++ b/packages/dev-live-reload/lib/watcher.js @@ -1,74 +1,74 @@ -const { CompositeDisposable, File, Directory, Emitter } = require('atom') -const path = require('path') +const { CompositeDisposable, File, Directory, Emitter } = require('atom'); +const path = require('path'); module.exports = class Watcher { - constructor () { - this.destroy = this.destroy.bind(this) - this.emitter = new Emitter() - this.disposables = new CompositeDisposable() - this.entities = [] // Used for specs + constructor() { + this.destroy = this.destroy.bind(this); + this.emitter = new Emitter(); + this.disposables = new CompositeDisposable(); + this.entities = []; // Used for specs } - onDidDestroy (callback) { - this.emitter.on('did-destroy', callback) + onDidDestroy(callback) { + this.emitter.on('did-destroy', callback); } - onDidChangeGlobals (callback) { - this.emitter.on('did-change-globals', callback) + onDidChangeGlobals(callback) { + this.emitter.on('did-change-globals', callback); } - destroy () { - this.disposables.dispose() - this.entities = null - this.emitter.emit('did-destroy') - this.emitter.dispose() + destroy() { + this.disposables.dispose(); + this.entities = null; + this.emitter.emit('did-destroy'); + this.emitter.dispose(); } - watch () { + watch() { // override me } - loadStylesheet (stylesheetPath) { + loadStylesheet(stylesheetPath) { // override me } - loadAllStylesheets () { + loadAllStylesheets() { // override me } - emitGlobalsChanged () { - this.emitter.emit('did-change-globals') + emitGlobalsChanged() { + this.emitter.emit('did-change-globals'); } - watchDirectory (directoryPath) { - if (this.isInAsarArchive(directoryPath)) return - const entity = new Directory(directoryPath) - this.disposables.add(entity.onDidChange(() => this.loadAllStylesheets())) - this.entities.push(entity) + watchDirectory(directoryPath) { + if (this.isInAsarArchive(directoryPath)) return; + const entity = new Directory(directoryPath); + this.disposables.add(entity.onDidChange(() => this.loadAllStylesheets())); + this.entities.push(entity); } - watchGlobalFile (filePath) { - const entity = new File(filePath) - this.disposables.add(entity.onDidChange(() => this.emitGlobalsChanged())) - this.entities.push(entity) + watchGlobalFile(filePath) { + const entity = new File(filePath); + this.disposables.add(entity.onDidChange(() => this.emitGlobalsChanged())); + this.entities.push(entity); } - watchFile (filePath) { - if (this.isInAsarArchive(filePath)) return - const reloadFn = () => this.loadStylesheet(entity.getPath()) + watchFile(filePath) { + if (this.isInAsarArchive(filePath)) return; + const reloadFn = () => this.loadStylesheet(entity.getPath()); - const entity = new File(filePath) - this.disposables.add(entity.onDidChange(reloadFn)) - this.disposables.add(entity.onDidDelete(reloadFn)) - this.disposables.add(entity.onDidRename(reloadFn)) - this.entities.push(entity) + const entity = new File(filePath); + this.disposables.add(entity.onDidChange(reloadFn)); + this.disposables.add(entity.onDidDelete(reloadFn)); + this.disposables.add(entity.onDidRename(reloadFn)); + this.entities.push(entity); } - isInAsarArchive (pathToCheck) { - const { resourcePath } = atom.getLoadSettings() + isInAsarArchive(pathToCheck) { + const { resourcePath } = atom.getLoadSettings(); return ( pathToCheck.startsWith(`${resourcePath}${path.sep}`) && path.extname(resourcePath) === '.asar' - ) + ); } -} +}; diff --git a/packages/dev-live-reload/spec/async-spec-helpers.js b/packages/dev-live-reload/spec/async-spec-helpers.js index e78205989..1cb8d3fe6 100644 --- a/packages/dev-live-reload/spec/async-spec-helpers.js +++ b/packages/dev-live-reload/spec/async-spec-helpers.js @@ -1,26 +1,26 @@ /** @babel */ -export async function conditionPromise ( +export async function conditionPromise( condition, description = 'anonymous condition' ) { - const startTime = Date.now() + const startTime = Date.now(); while (true) { - await timeoutPromise(100) + await timeoutPromise(100); if (await condition()) { - return + return; } if (Date.now() - startTime > 5000) { - throw new Error('Timed out waiting on ' + description) + throw new Error('Timed out waiting on ' + description); } } } -export function timeoutPromise (timeout) { - return new Promise(function (resolve) { - global.setTimeout(resolve, timeout) - }) +export function timeoutPromise(timeout) { + return new Promise(function(resolve) { + global.setTimeout(resolve, timeout); + }); } diff --git a/packages/dev-live-reload/spec/dev-live-reload-spec.js b/packages/dev-live-reload/spec/dev-live-reload-spec.js index 7351e0f99..802c4c69c 100644 --- a/packages/dev-live-reload/spec/dev-live-reload-spec.js +++ b/packages/dev-live-reload/spec/dev-live-reload-spec.js @@ -1,128 +1,128 @@ describe('Dev Live Reload', () => { describe('package activation', () => { - let [pack, mainModule] = [] + let [pack, mainModule] = []; beforeEach(() => { - pack = atom.packages.loadPackage('dev-live-reload') - pack.requireMainModule() - mainModule = pack.mainModule - spyOn(mainModule, 'startWatching') - }) + pack = atom.packages.loadPackage('dev-live-reload'); + pack.requireMainModule(); + mainModule = pack.mainModule; + spyOn(mainModule, 'startWatching'); + }); describe('when the window is not in dev mode', () => { - beforeEach(() => spyOn(atom, 'inDevMode').andReturn(false)) + beforeEach(() => spyOn(atom, 'inDevMode').andReturn(false)); it('does not watch files', async () => { - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); - await atom.packages.activatePackage('dev-live-reload') - expect(mainModule.startWatching).not.toHaveBeenCalled() - }) - }) + await atom.packages.activatePackage('dev-live-reload'); + expect(mainModule.startWatching).not.toHaveBeenCalled(); + }); + }); describe('when the window is in spec mode', () => { - beforeEach(() => spyOn(atom, 'inSpecMode').andReturn(true)) + beforeEach(() => spyOn(atom, 'inSpecMode').andReturn(true)); it('does not watch files', async () => { - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); - await atom.packages.activatePackage('dev-live-reload') - expect(mainModule.startWatching).not.toHaveBeenCalled() - }) - }) + await atom.packages.activatePackage('dev-live-reload'); + expect(mainModule.startWatching).not.toHaveBeenCalled(); + }); + }); describe('when the window is in dev mode', () => { beforeEach(() => { - spyOn(atom, 'inDevMode').andReturn(true) - spyOn(atom, 'inSpecMode').andReturn(false) - }) + spyOn(atom, 'inDevMode').andReturn(true); + spyOn(atom, 'inSpecMode').andReturn(false); + }); it('watches files', async () => { - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); - await atom.packages.activatePackage('dev-live-reload') - expect(mainModule.startWatching).toHaveBeenCalled() - }) - }) + await atom.packages.activatePackage('dev-live-reload'); + expect(mainModule.startWatching).toHaveBeenCalled(); + }); + }); describe('when the window is in both dev mode and spec mode', () => { beforeEach(() => { - spyOn(atom, 'inDevMode').andReturn(true) - spyOn(atom, 'inSpecMode').andReturn(true) - }) + spyOn(atom, 'inDevMode').andReturn(true); + spyOn(atom, 'inSpecMode').andReturn(true); + }); it('does not watch files', async () => { - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); - await atom.packages.activatePackage('dev-live-reload') - expect(mainModule.startWatching).not.toHaveBeenCalled() - }) - }) + await atom.packages.activatePackage('dev-live-reload'); + expect(mainModule.startWatching).not.toHaveBeenCalled(); + }); + }); describe('when the package is activated before initial packages have been activated', () => { beforeEach(() => { - spyOn(atom, 'inDevMode').andReturn(true) - spyOn(atom, 'inSpecMode').andReturn(false) - }) + spyOn(atom, 'inDevMode').andReturn(true); + spyOn(atom, 'inSpecMode').andReturn(false); + }); it('waits until all initial packages have been activated before watching files', async () => { - await atom.packages.activatePackage('dev-live-reload') - expect(mainModule.startWatching).not.toHaveBeenCalled() + await atom.packages.activatePackage('dev-live-reload'); + expect(mainModule.startWatching).not.toHaveBeenCalled(); - atom.packages.emitter.emit('did-activate-initial-packages') - expect(mainModule.startWatching).toHaveBeenCalled() - }) - }) - }) + atom.packages.emitter.emit('did-activate-initial-packages'); + expect(mainModule.startWatching).toHaveBeenCalled(); + }); + }); + }); describe('package deactivation', () => { beforeEach(() => { - spyOn(atom, 'inDevMode').andReturn(true) - spyOn(atom, 'inSpecMode').andReturn(false) - }) + spyOn(atom, 'inDevMode').andReturn(true); + spyOn(atom, 'inSpecMode').andReturn(false); + }); it('stops watching all files', async () => { - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); const { mainModule } = await atom.packages.activatePackage( 'dev-live-reload' - ) - expect(mainModule.uiWatcher).not.toBeNull() + ); + expect(mainModule.uiWatcher).not.toBeNull(); - spyOn(mainModule.uiWatcher, 'destroy') + spyOn(mainModule.uiWatcher, 'destroy'); - await atom.packages.deactivatePackage('dev-live-reload') - expect(mainModule.uiWatcher.destroy).toHaveBeenCalled() - }) + await atom.packages.deactivatePackage('dev-live-reload'); + expect(mainModule.uiWatcher.destroy).toHaveBeenCalled(); + }); it('unsubscribes from the onDidActivateInitialPackages subscription if it is disabled before all initial packages are activated', async () => { const { mainModule } = await atom.packages.activatePackage( 'dev-live-reload' - ) - expect(mainModule.activatedDisposable.disposed).toBe(false) + ); + expect(mainModule.activatedDisposable.disposed).toBe(false); - await atom.packages.deactivatePackage('dev-live-reload') - expect(mainModule.activatedDisposable.disposed).toBe(true) + await atom.packages.deactivatePackage('dev-live-reload'); + expect(mainModule.activatedDisposable.disposed).toBe(true); - spyOn(mainModule, 'startWatching') - atom.packages.emitter.emit('did-activate-initial-packages') - expect(mainModule.startWatching).not.toHaveBeenCalled() - }) + spyOn(mainModule, 'startWatching'); + atom.packages.emitter.emit('did-activate-initial-packages'); + expect(mainModule.startWatching).not.toHaveBeenCalled(); + }); it('removes its commands', async () => { - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) - await atom.packages.activatePackage('dev-live-reload') + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); + await atom.packages.activatePackage('dev-live-reload'); expect( atom.commands .findCommands({ target: atom.views.getView(atom.workspace) }) .filter(command => command.name.startsWith('dev-live-reload')).length - ).toBeGreaterThan(0) + ).toBeGreaterThan(0); - await atom.packages.deactivatePackage('dev-live-reload') + await atom.packages.deactivatePackage('dev-live-reload'); expect( atom.commands .findCommands({ target: atom.views.getView(atom.workspace) }) .filter(command => command.name.startsWith('dev-live-reload')).length - ).toBe(0) - }) - }) -}) + ).toBe(0); + }); + }); +}); diff --git a/packages/dev-live-reload/spec/ui-watcher-spec.js b/packages/dev-live-reload/spec/ui-watcher-spec.js index 0027dd5c3..9fdee3526 100644 --- a/packages/dev-live-reload/spec/ui-watcher-spec.js +++ b/packages/dev-live-reload/spec/ui-watcher-spec.js @@ -1,260 +1,262 @@ -const path = require('path') +const path = require('path'); -const UIWatcher = require('../lib/ui-watcher') +const UIWatcher = require('../lib/ui-watcher'); -const { conditionPromise } = require('./async-spec-helpers') +const { conditionPromise } = require('./async-spec-helpers'); describe('UIWatcher', () => { - let uiWatcher = null + let uiWatcher = null; beforeEach(() => atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures')) - ) + ); - afterEach(() => uiWatcher && uiWatcher.destroy()) + afterEach(() => uiWatcher && uiWatcher.destroy()); describe("when a base theme's file changes", () => { beforeEach(() => { spyOn(atom.themes, 'resolveStylesheet').andReturn( path.join(__dirname, 'fixtures', 'static', 'atom.less') - ) - uiWatcher = new UIWatcher() - }) + ); + uiWatcher = new UIWatcher(); + }); it('reloads all the base styles', () => { - spyOn(atom.themes, 'reloadBaseStylesheets') + spyOn(atom.themes, 'reloadBaseStylesheets'); expect(uiWatcher.baseTheme.entities[0].getPath()).toContain( `${path.sep}static${path.sep}` - ) + ); - uiWatcher.baseTheme.entities[0].emitter.emit('did-change') - expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled() - }) - }) + uiWatcher.baseTheme.entities[0].emitter.emit('did-change'); + expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled(); + }); + }); it("watches all the style sheets in the theme's styles folder", async () => { const packagePath = path.join( __dirname, 'fixtures', 'package-with-styles-folder' - ) + ); - await atom.packages.activatePackage(packagePath) - uiWatcher = new UIWatcher() + await atom.packages.activatePackage(packagePath); + uiWatcher = new UIWatcher(); - const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1] + const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1]; - expect(lastWatcher.entities.length).toBe(4) + expect(lastWatcher.entities.length).toBe(4); expect(lastWatcher.entities[0].getPath()).toBe( path.join(packagePath, 'styles') - ) + ); expect(lastWatcher.entities[1].getPath()).toBe( path.join(packagePath, 'styles', '3.css') - ) + ); expect(lastWatcher.entities[2].getPath()).toBe( path.join(packagePath, 'styles', 'sub', '1.css') - ) + ); expect(lastWatcher.entities[3].getPath()).toBe( path.join(packagePath, 'styles', 'sub', '2.less') - ) - }) + ); + }); describe('when a package stylesheet file changes', async () => { beforeEach(async () => { await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'package-with-styles-manifest') - ) - uiWatcher = new UIWatcher() - }) + ); + uiWatcher = new UIWatcher(); + }); it('reloads all package styles', () => { - const pack = atom.packages.getActivePackages()[0] - spyOn(pack, 'reloadStylesheets') + const pack = atom.packages.getActivePackages()[0]; + spyOn(pack, 'reloadStylesheets'); - uiWatcher.watchers[uiWatcher.watchers.length - 1].entities[1].emitter.emit('did-change') + uiWatcher.watchers[ + uiWatcher.watchers.length - 1 + ].entities[1].emitter.emit('did-change'); - expect(pack.reloadStylesheets).toHaveBeenCalled() - }) - }) + expect(pack.reloadStylesheets).toHaveBeenCalled(); + }); + }); describe('when a package does not have a stylesheet', () => { beforeEach(async () => { - await atom.packages.activatePackage('package-with-index') - uiWatcher = new UIWatcher() - }) + await atom.packages.activatePackage('package-with-index'); + uiWatcher = new UIWatcher(); + }); it('does not create a PackageWatcher', () => { - expect(uiWatcher.watchedPackages['package-with-index']).toBeUndefined() - }) - }) + expect(uiWatcher.watchedPackages['package-with-index']).toBeUndefined(); + }); + }); describe('when a package global file changes', () => { beforeEach(async () => { atom.config.set('core.themes', [ 'theme-with-ui-variables', 'theme-with-multiple-imported-files' - ]) + ]); - await atom.themes.activateThemes() - uiWatcher = new UIWatcher() - }) + await atom.themes.activateThemes(); + uiWatcher = new UIWatcher(); + }); - afterEach(() => atom.themes.deactivateThemes()) + afterEach(() => atom.themes.deactivateThemes()); it('reloads every package when the variables file changes', () => { - let varEntity + let varEntity; for (const theme of atom.themes.getActiveThemes()) { - spyOn(theme, 'reloadStylesheets') + spyOn(theme, 'reloadStylesheets'); } for (const entity of uiWatcher.watchedThemes.get( 'theme-with-multiple-imported-files' ).entities) { - if (entity.getPath().indexOf('variables') > -1) varEntity = entity + if (entity.getPath().indexOf('variables') > -1) varEntity = entity; } - varEntity.emitter.emit('did-change') + varEntity.emitter.emit('did-change'); for (const theme of atom.themes.getActiveThemes()) { - expect(theme.reloadStylesheets).toHaveBeenCalled() + expect(theme.reloadStylesheets).toHaveBeenCalled(); } - }) - }) + }); + }); describe('watcher lifecycle', () => { it('starts watching a package if it is activated after initial startup', async () => { - uiWatcher = new UIWatcher() - expect(uiWatcher.watchedPackages.size).toBe(0) + uiWatcher = new UIWatcher(); + expect(uiWatcher.watchedPackages.size).toBe(0); await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'package-with-styles-folder') - ) + ); expect( uiWatcher.watchedPackages.get('package-with-styles-folder') - ).not.toBeUndefined() - }) + ).not.toBeUndefined(); + }); it('unwatches a package after it is deactivated', async () => { await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'package-with-styles-folder') - ) - uiWatcher = new UIWatcher() + ); + uiWatcher = new UIWatcher(); const watcher = uiWatcher.watchedPackages.get( 'package-with-styles-folder' - ) - expect(watcher).not.toBeUndefined() + ); + expect(watcher).not.toBeUndefined(); - const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy') - watcher.onDidDestroy(watcherDestructionSpy) + const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy'); + watcher.onDidDestroy(watcherDestructionSpy); - await atom.packages.deactivatePackage('package-with-styles-folder') + await atom.packages.deactivatePackage('package-with-styles-folder'); expect( uiWatcher.watchedPackages.get('package-with-styles-folder') - ).toBeUndefined() - expect(uiWatcher.watchedPackages.size).toBe(0) - expect(watcherDestructionSpy).toHaveBeenCalled() - }) + ).toBeUndefined(); + expect(uiWatcher.watchedPackages.size).toBe(0); + expect(watcherDestructionSpy).toHaveBeenCalled(); + }); it('does not watch activated packages after the UI watcher has been destroyed', async () => { - uiWatcher = new UIWatcher() - uiWatcher.destroy() + uiWatcher = new UIWatcher(); + uiWatcher.destroy(); await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'package-with-styles-folder') - ) - expect(uiWatcher.watchedPackages.size).toBe(0) - }) - }) + ); + expect(uiWatcher.watchedPackages.size).toBe(0); + }); + }); describe('minimal theme packages', () => { - let pack = null + let pack = null; beforeEach(async () => { atom.config.set('core.themes', [ 'theme-with-syntax-variables', 'theme-with-index-less' - ]) - await atom.themes.activateThemes() - uiWatcher = new UIWatcher() - pack = atom.themes.getActiveThemes()[0] - }) + ]); + await atom.themes.activateThemes(); + uiWatcher = new UIWatcher(); + pack = atom.themes.getActiveThemes()[0]; + }); - afterEach(() => atom.themes.deactivateThemes()) + afterEach(() => atom.themes.deactivateThemes()); it('watches themes without a styles directory', () => { - spyOn(pack, 'reloadStylesheets') - spyOn(atom.themes, 'reloadBaseStylesheets') + spyOn(pack, 'reloadStylesheets'); + spyOn(atom.themes, 'reloadBaseStylesheets'); - const watcher = uiWatcher.watchedThemes.get('theme-with-index-less') + const watcher = uiWatcher.watchedThemes.get('theme-with-index-less'); - expect(watcher.entities.length).toBe(1) + expect(watcher.entities.length).toBe(1); - watcher.entities[0].emitter.emit('did-change') - expect(pack.reloadStylesheets).toHaveBeenCalled() - expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled() - }) - }) + watcher.entities[0].emitter.emit('did-change'); + expect(pack.reloadStylesheets).toHaveBeenCalled(); + expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled(); + }); + }); describe('theme packages', () => { - let pack = null + let pack = null; beforeEach(async () => { atom.config.set('core.themes', [ 'theme-with-syntax-variables', 'theme-with-multiple-imported-files' - ]) + ]); - await atom.themes.activateThemes() - uiWatcher = new UIWatcher() - pack = atom.themes.getActiveThemes()[0] - }) + await atom.themes.activateThemes(); + uiWatcher = new UIWatcher(); + pack = atom.themes.getActiveThemes()[0]; + }); - afterEach(() => atom.themes.deactivateThemes()) + afterEach(() => atom.themes.deactivateThemes()); it('reloads the theme when anything within the theme changes', () => { - spyOn(pack, 'reloadStylesheets') - spyOn(atom.themes, 'reloadBaseStylesheets') + spyOn(pack, 'reloadStylesheets'); + spyOn(atom.themes, 'reloadBaseStylesheets'); const watcher = uiWatcher.watchedThemes.get( 'theme-with-multiple-imported-files' - ) + ); - expect(watcher.entities.length).toBe(6) + expect(watcher.entities.length).toBe(6); - watcher.entities[2].emitter.emit('did-change') - expect(pack.reloadStylesheets).toHaveBeenCalled() - expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled() + watcher.entities[2].emitter.emit('did-change'); + expect(pack.reloadStylesheets).toHaveBeenCalled(); + expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled(); - watcher.entities[watcher.entities.length - 1].emitter.emit('did-change') - expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled() - }) + watcher.entities[watcher.entities.length - 1].emitter.emit('did-change'); + expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled(); + }); it('unwatches when a theme is deactivated', async () => { - jasmine.useRealClock() + jasmine.useRealClock(); - atom.config.set('core.themes', []) + atom.config.set('core.themes', []); await conditionPromise( () => !uiWatcher.watchedThemes['theme-with-multiple-imported-files'] - ) - }) + ); + }); it('watches a new theme when it is deactivated', async () => { - jasmine.useRealClock() + jasmine.useRealClock(); atom.config.set('core.themes', [ 'theme-with-syntax-variables', 'theme-with-package-file' - ]) + ]); await conditionPromise(() => uiWatcher.watchedThemes.get('theme-with-package-file') - ) + ); - pack = atom.themes.getActiveThemes()[0] - spyOn(pack, 'reloadStylesheets') + pack = atom.themes.getActiveThemes()[0]; + spyOn(pack, 'reloadStylesheets'); - expect(pack.name).toBe('theme-with-package-file') + expect(pack.name).toBe('theme-with-package-file'); - const watcher = uiWatcher.watchedThemes.get('theme-with-package-file') - watcher.entities[2].emitter.emit('did-change') - expect(pack.reloadStylesheets).toHaveBeenCalled() - }) - }) -}) + const watcher = uiWatcher.watchedThemes.get('theme-with-package-file'); + watcher.entities[2].emitter.emit('did-change'); + expect(pack.reloadStylesheets).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/exception-reporting/lib/main.js b/packages/exception-reporting/lib/main.js index 5893638f4..95c756e03 100644 --- a/packages/exception-reporting/lib/main.js +++ b/packages/exception-reporting/lib/main.js @@ -1,57 +1,57 @@ /** @babel */ -import { CompositeDisposable } from 'atom' +import { CompositeDisposable } from 'atom'; -let reporter +let reporter; -function getReporter () { +function getReporter() { if (!reporter) { - const Reporter = require('./reporter') - reporter = new Reporter() + const Reporter = require('./reporter'); + reporter = new Reporter(); } - return reporter + return reporter; } export default { - activate () { - this.subscriptions = new CompositeDisposable() + activate() { + this.subscriptions = new CompositeDisposable(); if (!atom.config.get('exception-reporting.userId')) { - atom.config.set('exception-reporting.userId', require('node-uuid').v4()) + atom.config.set('exception-reporting.userId', require('node-uuid').v4()); } this.subscriptions.add( atom.onDidThrowError(({ message, url, line, column, originalError }) => { try { - getReporter().reportUncaughtException(originalError) + getReporter().reportUncaughtException(originalError); } catch (secondaryException) { try { console.error( 'Error reporting uncaught exception', secondaryException - ) - getReporter().reportUncaughtException(secondaryException) + ); + getReporter().reportUncaughtException(secondaryException); } catch (error) {} } }) - ) + ); if (atom.onDidFailAssertion != null) { this.subscriptions.add( atom.onDidFailAssertion(error => { try { - getReporter().reportFailedAssertion(error) + getReporter().reportFailedAssertion(error); } catch (secondaryException) { try { console.error( 'Error reporting assertion failure', secondaryException - ) - getReporter().reportUncaughtException(secondaryException) + ); + getReporter().reportUncaughtException(secondaryException); } catch (error) {} } }) - ) + ); } } -} +}; diff --git a/packages/exception-reporting/lib/reporter.js b/packages/exception-reporting/lib/reporter.js index e4becff04..3befa0d6b 100644 --- a/packages/exception-reporting/lib/reporter.js +++ b/packages/exception-reporting/lib/reporter.js @@ -1,31 +1,31 @@ /** @babel */ -import os from 'os' -import stackTrace from 'stack-trace' -import fs from 'fs-plus' -import path from 'path' +import os from 'os'; +import stackTrace from 'stack-trace'; +import fs from 'fs-plus'; +import path from 'path'; -const API_KEY = '7ddca14cb60cbd1cd12d1b252473b076' -const LIB_VERSION = require('../package.json')['version'] -const StackTraceCache = new WeakMap() +const API_KEY = '7ddca14cb60cbd1cd12d1b252473b076'; +const LIB_VERSION = require('../package.json')['version']; +const StackTraceCache = new WeakMap(); export default class Reporter { - constructor (params = {}) { - this.request = params.request || window.fetch + constructor(params = {}) { + this.request = params.request || window.fetch; this.alwaysReport = params.hasOwnProperty('alwaysReport') ? params.alwaysReport - : false + : false; this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors') ? params.reportPreviousErrors - : true + : true; this.resourcePath = this.normalizePath( params.resourcePath || process.resourcesPath - ) - this.reportedErrors = [] - this.reportedAssertionFailures = [] + ); + this.reportedErrors = []; + this.reportedAssertionFailures = []; } - buildNotificationJSON (error, params) { + buildNotificationJSON(error, params) { return { apiKey: API_KEY, notifier: { @@ -51,18 +51,18 @@ export default class Reporter { metaData: error.metadata } ] - } + }; } - buildExceptionJSON (error, projectRoot) { + buildExceptionJSON(error, projectRoot) { return { errorClass: error.constructor.name, message: error.message, stacktrace: this.buildStackTraceJSON(error, projectRoot) - } + }; } - buildStackTraceJSON (error, projectRoot) { + buildStackTraceJSON(error, projectRoot) { return this.parseStackTrace(error).map(callSite => { return { file: this.scrubPath(callSite.getFileName()), @@ -71,110 +71,110 @@ export default class Reporter { lineNumber: callSite.getLineNumber(), columnNumber: callSite.getColumnNumber(), inProject: !/node_modules/.test(callSite.getFileName()) - } - }) + }; + }); } - normalizePath (pathToNormalize) { + normalizePath(pathToNormalize) { return pathToNormalize .replace('file:///', '') // Sometimes it's a uri - .replace(/\\/g, '/') // Unify path separators across Win/macOS/Linux + .replace(/\\/g, '/'); // Unify path separators across Win/macOS/Linux } - scrubPath (pathToScrub) { - const absolutePath = this.normalizePath(pathToScrub) + scrubPath(pathToScrub) { + const absolutePath = this.normalizePath(pathToScrub); if (this.isBundledFile(absolutePath)) { - return this.normalizePath(path.relative(this.resourcePath, absolutePath)) + return this.normalizePath(path.relative(this.resourcePath, absolutePath)); } else { return absolutePath .replace(this.normalizePath(fs.getHomeDirectory()), '~') // Remove users home dir - .replace(/.*(\/packages\/.*)/, '$1') // Remove everything before app.asar or packages + .replace(/.*(\/packages\/.*)/, '$1'); // Remove everything before app.asar or packages } } - getDefaultNotificationParams () { + getDefaultNotificationParams() { return { userId: atom.config.get('exception-reporting.userId'), appVersion: atom.getVersion(), releaseStage: this.getReleaseChannel(atom.getVersion()), projectRoot: atom.getLoadSettings().resourcePath, osVersion: `${os.platform()}-${os.arch()}-${os.release()}` - } + }; } - getReleaseChannel (version) { + getReleaseChannel(version) { return version.indexOf('beta') > -1 ? 'beta' : version.indexOf('dev') > -1 ? 'dev' - : 'stable' + : 'stable'; } - performRequest (json) { + performRequest(json) { this.request.call(null, 'https://notify.bugsnag.com', { method: 'POST', headers: new Headers({ 'Content-Type': 'application/json' }), body: JSON.stringify(json) - }) + }); } - shouldReport (error) { - if (this.alwaysReport) return true // Used in specs - if (atom.config.get('core.telemetryConsent') !== 'limited') return false - if (atom.inDevMode()) return false + shouldReport(error) { + if (this.alwaysReport) return true; // Used in specs + if (atom.config.get('core.telemetryConsent') !== 'limited') return false; + if (atom.inDevMode()) return false; - const topFrame = this.parseStackTrace(error)[0] - const fileName = topFrame ? topFrame.getFileName() : null + const topFrame = this.parseStackTrace(error)[0]; + const fileName = topFrame ? topFrame.getFileName() : null; return ( fileName && (this.isBundledFile(fileName) || this.isTeletypeFile(fileName)) - ) + ); } - parseStackTrace (error) { - let callSites = StackTraceCache.get(error) + parseStackTrace(error) { + let callSites = StackTraceCache.get(error); if (callSites) { - return callSites + return callSites; } else { - callSites = stackTrace.parse(error) - StackTraceCache.set(error, callSites) - return callSites + callSites = stackTrace.parse(error); + StackTraceCache.set(error, callSites); + return callSites; } } - requestPrivateMetadataConsent (error, message, reportFn) { - let notification, dismissSubscription + requestPrivateMetadataConsent(error, message, reportFn) { + let notification, dismissSubscription; - function reportWithoutPrivateMetadata () { + function reportWithoutPrivateMetadata() { if (dismissSubscription) { - dismissSubscription.dispose() + dismissSubscription.dispose(); } - delete error.privateMetadata - delete error.privateMetadataDescription - reportFn(error) + delete error.privateMetadata; + delete error.privateMetadataDescription; + reportFn(error); if (notification) { - notification.dismiss() + notification.dismiss(); } } - function reportWithPrivateMetadata () { + function reportWithPrivateMetadata() { if (error.metadata == null) { - error.metadata = {} + error.metadata = {}; } for (let key in error.privateMetadata) { - let value = error.privateMetadata[key] - error.metadata[key] = value + let value = error.privateMetadata[key]; + error.metadata[key] = value; } - reportWithoutPrivateMetadata() + reportWithoutPrivateMetadata(); } - const name = error.privateMetadataRequestName + const name = error.privateMetadataRequestName; if (name != null) { if (localStorage.getItem(`private-metadata-request:${name}`)) { - return reportWithoutPrivateMetadata(error) + return reportWithoutPrivateMetadata(error); } else { - localStorage.setItem(`private-metadata-request:${name}`, true) + localStorage.setItem(`private-metadata-request:${name}`, true); } } @@ -193,51 +193,51 @@ export default class Reporter { onDidClick: reportWithPrivateMetadata } ] - }) + }); dismissSubscription = notification.onDidDismiss( reportWithoutPrivateMetadata - ) + ); } - addPackageMetadata (error) { - let activePackages = atom.packages.getActivePackages() - const availablePackagePaths = atom.packages.getPackageDirPaths() + addPackageMetadata(error) { + let activePackages = atom.packages.getActivePackages(); + const availablePackagePaths = atom.packages.getPackageDirPaths(); if (activePackages.length > 0) { - let userPackages = {} - let bundledPackages = {} + let userPackages = {}; + let bundledPackages = {}; for (let pack of atom.packages.getActivePackages()) { if (availablePackagePaths.includes(path.dirname(pack.path))) { - userPackages[pack.name] = pack.metadata.version + userPackages[pack.name] = pack.metadata.version; } else { - bundledPackages[pack.name] = pack.metadata.version + bundledPackages[pack.name] = pack.metadata.version; } } if (error.metadata == null) { - error.metadata = {} + error.metadata = {}; } - error.metadata.bundledPackages = bundledPackages - error.metadata.userPackages = userPackages + error.metadata.bundledPackages = bundledPackages; + error.metadata.userPackages = userPackages; } } - addPreviousErrorsMetadata (error) { - if (!this.reportPreviousErrors) return - if (!error.metadata) error.metadata = {} + addPreviousErrorsMetadata(error) { + if (!this.reportPreviousErrors) return; + if (!error.metadata) error.metadata = {}; error.metadata.previousErrors = this.reportedErrors.map( error => error.message - ) + ); error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map( error => error.message - ) + ); } - reportUncaughtException (error) { - if (!this.shouldReport(error)) return + reportUncaughtException(error) { + if (!this.shouldReport(error)) return; - this.addPackageMetadata(error) - this.addPreviousErrorsMetadata(error) + this.addPackageMetadata(error); + this.addPreviousErrorsMetadata(error); if ( error.privateMetadata != null && @@ -247,21 +247,21 @@ export default class Reporter { error, 'The Atom team would like to collect the following information to resolve this error:', error => this.reportUncaughtException(error) - ) - return + ); + return; } - let params = this.getDefaultNotificationParams() - params.severity = 'error' - this.performRequest(this.buildNotificationJSON(error, params)) - this.reportedErrors.push(error) + let params = this.getDefaultNotificationParams(); + params.severity = 'error'; + this.performRequest(this.buildNotificationJSON(error, params)); + this.reportedErrors.push(error); } - reportFailedAssertion (error) { - if (!this.shouldReport(error)) return + reportFailedAssertion(error) { + if (!this.shouldReport(error)) return; - this.addPackageMetadata(error) - this.addPreviousErrorsMetadata(error) + this.addPackageMetadata(error); + this.addPreviousErrorsMetadata(error); if ( error.privateMetadata != null && @@ -271,32 +271,32 @@ export default class Reporter { error, 'The Atom team would like to collect some information to resolve an unexpected condition:', error => this.reportFailedAssertion(error) - ) - return + ); + return; } - let params = this.getDefaultNotificationParams() - params.severity = 'warning' - this.performRequest(this.buildNotificationJSON(error, params)) - this.reportedAssertionFailures.push(error) + let params = this.getDefaultNotificationParams(); + params.severity = 'warning'; + this.performRequest(this.buildNotificationJSON(error, params)); + this.reportedAssertionFailures.push(error); } // Used in specs - setRequestFunction (requestFunction) { - this.request = requestFunction + setRequestFunction(requestFunction) { + this.request = requestFunction; } - isBundledFile (fileName) { - return this.normalizePath(fileName).indexOf(this.resourcePath) === 0 + isBundledFile(fileName) { + return this.normalizePath(fileName).indexOf(this.resourcePath) === 0; } - isTeletypeFile (fileName) { - const teletypePath = atom.packages.resolvePackagePath('teletype') + isTeletypeFile(fileName) { + const teletypePath = atom.packages.resolvePackagePath('teletype'); return ( teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0 - ) + ); } } -Reporter.API_KEY = API_KEY -Reporter.LIB_VERSION = LIB_VERSION +Reporter.API_KEY = API_KEY; +Reporter.LIB_VERSION = LIB_VERSION; diff --git a/packages/exception-reporting/spec/reporter-spec.js b/packages/exception-reporting/spec/reporter-spec.js index 615868f3b..24b7070c1 100644 --- a/packages/exception-reporting/spec/reporter-spec.js +++ b/packages/exception-reporting/spec/reporter-spec.js @@ -1,76 +1,76 @@ -const Reporter = require('../lib/reporter') -const semver = require('semver') -const os = require('os') -const path = require('path') -const fs = require('fs-plus') -let osVersion = `${os.platform()}-${os.arch()}-${os.release()}` +const Reporter = require('../lib/reporter'); +const semver = require('semver'); +const os = require('os'); +const path = require('path'); +const fs = require('fs-plus'); +let osVersion = `${os.platform()}-${os.arch()}-${os.release()}`; let getReleaseChannel = version => { return version.indexOf('beta') > -1 ? 'beta' : version.indexOf('dev') > -1 ? 'dev' - : 'stable' -} + : 'stable'; +}; describe('Reporter', () => { let reporter, requests, initialStackTraceLimit, initialFsGetHomeDirectory, - mockActivePackages + mockActivePackages; beforeEach(() => { reporter = new Reporter({ request: (url, options) => requests.push(Object.assign({ url }, options)), alwaysReport: true, reportPreviousErrors: false - }) - requests = [] - mockActivePackages = [] + }); + requests = []; + mockActivePackages = []; spyOn(atom.packages, 'getActivePackages').andCallFake( () => mockActivePackages - ) + ); - initialStackTraceLimit = Error.stackTraceLimit - Error.stackTraceLimit = 1 + initialStackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 1; - initialFsGetHomeDirectory = fs.getHomeDirectory - }) + initialFsGetHomeDirectory = fs.getHomeDirectory; + }); afterEach(() => { - fs.getHomeDirectory = initialFsGetHomeDirectory - Error.stackTraceLimit = initialStackTraceLimit - }) + fs.getHomeDirectory = initialFsGetHomeDirectory; + Error.stackTraceLimit = initialStackTraceLimit; + }); describe('.reportUncaughtException(error)', () => { it('posts errors originated inside Atom Core to BugSnag', () => { - const repositoryRootPath = path.join(__dirname, '..') + const repositoryRootPath = path.join(__dirname, '..'); reporter = new Reporter({ request: (url, options) => requests.push(Object.assign({ url }, options)), alwaysReport: true, reportPreviousErrors: false, resourcePath: repositoryRootPath - }) + }); - let error = new Error() - Error.captureStackTrace(error) - reporter.reportUncaughtException(error) + let error = new Error(); + Error.captureStackTrace(error); + reporter.reportUncaughtException(error); let [lineNumber, columnNumber] = error.stack .match(/.js:(\d+):(\d+)/) .slice(1) - .map(s => parseInt(s)) + .map(s => parseInt(s)); - expect(requests.length).toBe(1) - let [request] = requests - expect(request.method).toBe('POST') - expect(request.url).toBe('https://notify.bugsnag.com') - expect(request.headers.get('Content-Type')).toBe('application/json') - let body = JSON.parse(request.body) + expect(requests.length).toBe(1); + let [request] = requests; + expect(request.method).toBe('POST'); + expect(request.url).toBe('https://notify.bugsnag.com'); + expect(request.headers.get('Content-Type')).toBe('application/json'); + let body = JSON.parse(request.body); // Delete `inProject` field because tests may fail when run as part of Atom core // (i.e. when this test file will be located under `node_modules/exception-reporting/spec`) - delete body.events[0].exceptions[0].stacktrace[0].inProject + delete body.events[0].exceptions[0].stacktrace[0].inProject; expect(body).toEqual({ apiKey: Reporter.API_KEY, @@ -109,29 +109,29 @@ describe('Reporter', () => { } } ] - }) - }) + }); + }); it('posts errors originated outside Atom Core to BugSnag', () => { - fs.getHomeDirectory = () => path.join(__dirname, '..', '..') + fs.getHomeDirectory = () => path.join(__dirname, '..', '..'); - let error = new Error() - Error.captureStackTrace(error) - reporter.reportUncaughtException(error) + let error = new Error(); + Error.captureStackTrace(error); + reporter.reportUncaughtException(error); let [lineNumber, columnNumber] = error.stack .match(/.js:(\d+):(\d+)/) .slice(1) - .map(s => parseInt(s)) + .map(s => parseInt(s)); - expect(requests.length).toBe(1) - let [request] = requests - expect(request.method).toBe('POST') - expect(request.url).toBe('https://notify.bugsnag.com') - expect(request.headers.get('Content-Type')).toBe('application/json') - let body = JSON.parse(request.body) + expect(requests.length).toBe(1); + let [request] = requests; + expect(request.method).toBe('POST'); + expect(request.url).toBe('https://notify.bugsnag.com'); + expect(request.headers.get('Content-Type')).toBe('application/json'); + let body = JSON.parse(request.body); // Delete `inProject` field because tests may fail when run as part of Atom core // (i.e. when this test file will be located under `node_modules/exception-reporting/spec`) - delete body.events[0].exceptions[0].stacktrace[0].inProject + delete body.events[0].exceptions[0].stacktrace[0].inProject; expect(body).toEqual({ apiKey: Reporter.API_KEY, @@ -170,84 +170,84 @@ describe('Reporter', () => { } } ] - }) - }) + }); + }); describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => { - let [error, notification] = [] + let [error, notification] = []; beforeEach(() => { - atom.notifications.clear() - spyOn(atom.notifications, 'addInfo').andCallThrough() + atom.notifications.clear(); + spyOn(atom.notifications, 'addInfo').andCallThrough(); - error = new Error() - Error.captureStackTrace(error) + error = new Error(); + Error.captureStackTrace(error); - error.metadata = { foo: 'bar' } - error.privateMetadata = { baz: 'quux' } - error.privateMetadataDescription = 'The contents of baz' - }) + error.metadata = { foo: 'bar' }; + error.privateMetadata = { baz: 'quux' }; + error.privateMetadataDescription = 'The contents of baz'; + }); it('posts a notification asking for consent', () => { - reporter.reportUncaughtException(error) - expect(atom.notifications.addInfo).toHaveBeenCalled() - }) + reporter.reportUncaughtException(error); + expect(atom.notifications.addInfo).toHaveBeenCalled(); + }); it('submits the error with the private metadata if the user consents', () => { - spyOn(reporter, 'reportUncaughtException').andCallThrough() - reporter.reportUncaughtException(error) - reporter.reportUncaughtException.reset() + spyOn(reporter, 'reportUncaughtException').andCallThrough(); + reporter.reportUncaughtException(error); + reporter.reportUncaughtException.reset(); - notification = atom.notifications.getNotifications()[0] + notification = atom.notifications.getNotifications()[0]; - let notificationOptions = atom.notifications.addInfo.argsForCall[0][1] - expect(notificationOptions.buttons[1].text).toMatch(/Yes/) + let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]; + expect(notificationOptions.buttons[1].text).toMatch(/Yes/); - notificationOptions.buttons[1].onDidClick() - expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error) - expect(reporter.reportUncaughtException.callCount).toBe(1) - expect(error.privateMetadata).toBeUndefined() - expect(error.privateMetadataDescription).toBeUndefined() - expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' }) + notificationOptions.buttons[1].onDidClick(); + expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error); + expect(reporter.reportUncaughtException.callCount).toBe(1); + expect(error.privateMetadata).toBeUndefined(); + expect(error.privateMetadataDescription).toBeUndefined(); + expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' }); - expect(notification.isDismissed()).toBe(true) - }) + expect(notification.isDismissed()).toBe(true); + }); it('submits the error without the private metadata if the user does not consent', () => { - spyOn(reporter, 'reportUncaughtException').andCallThrough() - reporter.reportUncaughtException(error) - reporter.reportUncaughtException.reset() + spyOn(reporter, 'reportUncaughtException').andCallThrough(); + reporter.reportUncaughtException(error); + reporter.reportUncaughtException.reset(); - notification = atom.notifications.getNotifications()[0] + notification = atom.notifications.getNotifications()[0]; - let notificationOptions = atom.notifications.addInfo.argsForCall[0][1] - expect(notificationOptions.buttons[0].text).toMatch(/No/) + let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]; + expect(notificationOptions.buttons[0].text).toMatch(/No/); - notificationOptions.buttons[0].onDidClick() - expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error) - expect(reporter.reportUncaughtException.callCount).toBe(1) - expect(error.privateMetadata).toBeUndefined() - expect(error.privateMetadataDescription).toBeUndefined() - expect(error.metadata).toEqual({ foo: 'bar' }) + notificationOptions.buttons[0].onDidClick(); + expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error); + expect(reporter.reportUncaughtException.callCount).toBe(1); + expect(error.privateMetadata).toBeUndefined(); + expect(error.privateMetadataDescription).toBeUndefined(); + expect(error.metadata).toEqual({ foo: 'bar' }); - expect(notification.isDismissed()).toBe(true) - }) + expect(notification.isDismissed()).toBe(true); + }); it('submits the error without the private metadata if the user dismisses the notification', () => { - spyOn(reporter, 'reportUncaughtException').andCallThrough() - reporter.reportUncaughtException(error) - reporter.reportUncaughtException.reset() + spyOn(reporter, 'reportUncaughtException').andCallThrough(); + reporter.reportUncaughtException(error); + reporter.reportUncaughtException.reset(); - notification = atom.notifications.getNotifications()[0] - notification.dismiss() + notification = atom.notifications.getNotifications()[0]; + notification.dismiss(); - expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error) - expect(reporter.reportUncaughtException.callCount).toBe(1) - expect(error.privateMetadata).toBeUndefined() - expect(error.privateMetadataDescription).toBeUndefined() - expect(error.metadata).toEqual({ foo: 'bar' }) - }) - }) + expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error); + expect(reporter.reportUncaughtException.callCount).toBe(1); + expect(error.privateMetadata).toBeUndefined(); + expect(error.privateMetadataDescription).toBeUndefined(); + expect(error.metadata).toEqual({ foo: 'bar' }); + }); + }); it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => { mockActivePackages = [ @@ -273,71 +273,71 @@ describe('Reporter', () => { '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2', metadata: { version: '1.2.0' } } - ] + ]; - const packageDirPaths = ['/Users/user/.atom/packages'] + const packageDirPaths = ['/Users/user/.atom/packages']; - spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths) + spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths); - let error = new Error() - Error.captureStackTrace(error) - reporter.reportUncaughtException(error) + let error = new Error(); + Error.captureStackTrace(error); + reporter.reportUncaughtException(error); expect(error.metadata.userPackages).toEqual({ 'user-1': '1.0.0', 'user-2': '1.2.0' - }) + }); expect(error.metadata.bundledPackages).toEqual({ 'bundled-1': '1.0.0', 'bundled-2': '1.2.0' - }) - }) + }); + }); it('adds previous error messages and assertion failures to the reported metadata', () => { - reporter.reportPreviousErrors = true + reporter.reportPreviousErrors = true; - reporter.reportUncaughtException(new Error('A')) - reporter.reportUncaughtException(new Error('B')) - reporter.reportFailedAssertion(new Error('X')) - reporter.reportFailedAssertion(new Error('Y')) + reporter.reportUncaughtException(new Error('A')); + reporter.reportUncaughtException(new Error('B')); + reporter.reportFailedAssertion(new Error('X')); + reporter.reportFailedAssertion(new Error('Y')); - reporter.reportUncaughtException(new Error('C')) + reporter.reportUncaughtException(new Error('C')); - expect(requests.length).toBe(5) + expect(requests.length).toBe(5); - const lastRequest = requests[requests.length - 1] - const body = JSON.parse(lastRequest.body) + const lastRequest = requests[requests.length - 1]; + const body = JSON.parse(lastRequest.body); - console.log(body) - expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B']) + console.log(body); + expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B']); expect(body.events[0].metaData.previousAssertionFailures).toEqual([ 'X', 'Y' - ]) - }) - }) + ]); + }); + }); describe('.reportFailedAssertion(error)', () => { it('posts warnings to bugsnag', () => { - fs.getHomeDirectory = () => path.join(__dirname, '..', '..') + fs.getHomeDirectory = () => path.join(__dirname, '..', '..'); - let error = new Error() - Error.captureStackTrace(error) - reporter.reportFailedAssertion(error) + let error = new Error(); + Error.captureStackTrace(error); + reporter.reportFailedAssertion(error); let [lineNumber, columnNumber] = error.stack .match(/.js:(\d+):(\d+)/) .slice(1) - .map(s => parseInt(s)) + .map(s => parseInt(s)); - expect(requests.length).toBe(1) - let [request] = requests - expect(request.method).toBe('POST') - expect(request.url).toBe('https://notify.bugsnag.com') - expect(request.headers.get('Content-Type')).toBe('application/json') - let body = JSON.parse(request.body) + expect(requests.length).toBe(1); + let [request] = requests; + expect(request.method).toBe('POST'); + expect(request.url).toBe('https://notify.bugsnag.com'); + expect(request.headers.get('Content-Type')).toBe('application/json'); + let body = JSON.parse(request.body); // Delete `inProject` field because tests may fail when run as part of Atom core // (i.e. when this test file will be located under `node_modules/exception-reporting/spec`) - delete body.events[0].exceptions[0].stacktrace[0].inProject + delete body.events[0].exceptions[0].stacktrace[0].inProject; expect(body).toEqual({ apiKey: Reporter.API_KEY, @@ -376,112 +376,112 @@ describe('Reporter', () => { } } ] - }) - }) + }); + }); describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => { - let [error, notification] = [] + let [error, notification] = []; beforeEach(() => { - atom.notifications.clear() - spyOn(atom.notifications, 'addInfo').andCallThrough() + atom.notifications.clear(); + spyOn(atom.notifications, 'addInfo').andCallThrough(); - error = new Error() - Error.captureStackTrace(error) + error = new Error(); + Error.captureStackTrace(error); - error.metadata = { foo: 'bar' } - error.privateMetadata = { baz: 'quux' } - error.privateMetadataDescription = 'The contents of baz' - }) + error.metadata = { foo: 'bar' }; + error.privateMetadata = { baz: 'quux' }; + error.privateMetadataDescription = 'The contents of baz'; + }); it('posts a notification asking for consent', () => { - reporter.reportFailedAssertion(error) - expect(atom.notifications.addInfo).toHaveBeenCalled() - }) + reporter.reportFailedAssertion(error); + expect(atom.notifications.addInfo).toHaveBeenCalled(); + }); it('submits the error with the private metadata if the user consents', () => { - spyOn(reporter, 'reportFailedAssertion').andCallThrough() - reporter.reportFailedAssertion(error) - reporter.reportFailedAssertion.reset() + spyOn(reporter, 'reportFailedAssertion').andCallThrough(); + reporter.reportFailedAssertion(error); + reporter.reportFailedAssertion.reset(); - notification = atom.notifications.getNotifications()[0] + notification = atom.notifications.getNotifications()[0]; - let notificationOptions = atom.notifications.addInfo.argsForCall[0][1] - expect(notificationOptions.buttons[1].text).toMatch(/Yes/) + let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]; + expect(notificationOptions.buttons[1].text).toMatch(/Yes/); - notificationOptions.buttons[1].onDidClick() - expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error) - expect(reporter.reportFailedAssertion.callCount).toBe(1) - expect(error.privateMetadata).toBeUndefined() - expect(error.privateMetadataDescription).toBeUndefined() - expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' }) + notificationOptions.buttons[1].onDidClick(); + expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error); + expect(reporter.reportFailedAssertion.callCount).toBe(1); + expect(error.privateMetadata).toBeUndefined(); + expect(error.privateMetadataDescription).toBeUndefined(); + expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' }); - expect(notification.isDismissed()).toBe(true) - }) + expect(notification.isDismissed()).toBe(true); + }); it('submits the error without the private metadata if the user does not consent', () => { - spyOn(reporter, 'reportFailedAssertion').andCallThrough() - reporter.reportFailedAssertion(error) - reporter.reportFailedAssertion.reset() + spyOn(reporter, 'reportFailedAssertion').andCallThrough(); + reporter.reportFailedAssertion(error); + reporter.reportFailedAssertion.reset(); - notification = atom.notifications.getNotifications()[0] + notification = atom.notifications.getNotifications()[0]; - let notificationOptions = atom.notifications.addInfo.argsForCall[0][1] - expect(notificationOptions.buttons[0].text).toMatch(/No/) + let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]; + expect(notificationOptions.buttons[0].text).toMatch(/No/); - notificationOptions.buttons[0].onDidClick() - expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error) - expect(reporter.reportFailedAssertion.callCount).toBe(1) - expect(error.privateMetadata).toBeUndefined() - expect(error.privateMetadataDescription).toBeUndefined() - expect(error.metadata).toEqual({ foo: 'bar' }) + notificationOptions.buttons[0].onDidClick(); + expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error); + expect(reporter.reportFailedAssertion.callCount).toBe(1); + expect(error.privateMetadata).toBeUndefined(); + expect(error.privateMetadataDescription).toBeUndefined(); + expect(error.metadata).toEqual({ foo: 'bar' }); - expect(notification.isDismissed()).toBe(true) - }) + expect(notification.isDismissed()).toBe(true); + }); it('submits the error without the private metadata if the user dismisses the notification', () => { - spyOn(reporter, 'reportFailedAssertion').andCallThrough() - reporter.reportFailedAssertion(error) - reporter.reportFailedAssertion.reset() + spyOn(reporter, 'reportFailedAssertion').andCallThrough(); + reporter.reportFailedAssertion(error); + reporter.reportFailedAssertion.reset(); - notification = atom.notifications.getNotifications()[0] - notification.dismiss() + notification = atom.notifications.getNotifications()[0]; + notification.dismiss(); - expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error) - expect(reporter.reportFailedAssertion.callCount).toBe(1) - expect(error.privateMetadata).toBeUndefined() - expect(error.privateMetadataDescription).toBeUndefined() - expect(error.metadata).toEqual({ foo: 'bar' }) - }) + expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error); + expect(reporter.reportFailedAssertion.callCount).toBe(1); + expect(error.privateMetadata).toBeUndefined(); + expect(error.privateMetadataDescription).toBeUndefined(); + expect(error.metadata).toEqual({ foo: 'bar' }); + }); it("only notifies the user once for a given 'privateMetadataRequestName'", () => { - let fakeStorage = {} + let fakeStorage = {}; spyOn(global.localStorage, 'setItem').andCallFake( (key, value) => (fakeStorage[key] = value) - ) + ); spyOn(global.localStorage, 'getItem').andCallFake( key => fakeStorage[key] - ) + ); - error.privateMetadataRequestName = 'foo' + error.privateMetadataRequestName = 'foo'; - reporter.reportFailedAssertion(error) - expect(atom.notifications.addInfo).toHaveBeenCalled() - atom.notifications.addInfo.reset() + reporter.reportFailedAssertion(error); + expect(atom.notifications.addInfo).toHaveBeenCalled(); + atom.notifications.addInfo.reset(); - reporter.reportFailedAssertion(error) - expect(atom.notifications.addInfo).not.toHaveBeenCalled() + reporter.reportFailedAssertion(error); + expect(atom.notifications.addInfo).not.toHaveBeenCalled(); - let error2 = new Error() - Error.captureStackTrace(error2) - error2.privateMetadataDescription = 'Something about you' - error2.privateMetadata = { baz: 'quux' } - error2.privateMetadataRequestName = 'bar' + let error2 = new Error(); + Error.captureStackTrace(error2); + error2.privateMetadataDescription = 'Something about you'; + error2.privateMetadata = { baz: 'quux' }; + error2.privateMetadataRequestName = 'bar'; - reporter.reportFailedAssertion(error2) - expect(atom.notifications.addInfo).toHaveBeenCalled() - }) - }) + reporter.reportFailedAssertion(error2); + expect(atom.notifications.addInfo).toHaveBeenCalled(); + }); + }); it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => { mockActivePackages = [ @@ -507,46 +507,46 @@ describe('Reporter', () => { '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2', metadata: { version: '1.2.0' } } - ] + ]; - const packageDirPaths = ['/Users/user/.atom/packages'] + const packageDirPaths = ['/Users/user/.atom/packages']; - spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths) + spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths); - let error = new Error() - Error.captureStackTrace(error) - reporter.reportFailedAssertion(error) + let error = new Error(); + Error.captureStackTrace(error); + reporter.reportFailedAssertion(error); expect(error.metadata.userPackages).toEqual({ 'user-1': '1.0.0', 'user-2': '1.2.0' - }) + }); expect(error.metadata.bundledPackages).toEqual({ 'bundled-1': '1.0.0', 'bundled-2': '1.2.0' - }) - }) + }); + }); it('adds previous error messages and assertion failures to the reported metadata', () => { - reporter.reportPreviousErrors = true + reporter.reportPreviousErrors = true; - reporter.reportUncaughtException(new Error('A')) - reporter.reportUncaughtException(new Error('B')) - reporter.reportFailedAssertion(new Error('X')) - reporter.reportFailedAssertion(new Error('Y')) + reporter.reportUncaughtException(new Error('A')); + reporter.reportUncaughtException(new Error('B')); + reporter.reportFailedAssertion(new Error('X')); + reporter.reportFailedAssertion(new Error('Y')); - reporter.reportFailedAssertion(new Error('C')) + reporter.reportFailedAssertion(new Error('C')); - expect(requests.length).toBe(5) + expect(requests.length).toBe(5); - const lastRequest = requests[requests.length - 1] - const body = JSON.parse(lastRequest.body) + const lastRequest = requests[requests.length - 1]; + const body = JSON.parse(lastRequest.body); - expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B']) + expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B']); expect(body.events[0].metaData.previousAssertionFailures).toEqual([ 'X', 'Y' - ]) - }) - }) -}) + ]); + }); + }); +}); diff --git a/packages/git-diff/lib/diff-list-view.js b/packages/git-diff/lib/diff-list-view.js index 22f7c7d23..27c5c2f28 100644 --- a/packages/git-diff/lib/diff-list-view.js +++ b/packages/git-diff/lib/diff-list-view.js @@ -1,89 +1,89 @@ -const SelectListView = require('atom-select-list') -const { repositoryForPath } = require('./helpers') +const SelectListView = require('atom-select-list'); +const { repositoryForPath } = require('./helpers'); module.exports = class DiffListView { - constructor () { + constructor() { this.selectListView = new SelectListView({ emptyMessage: 'No diffs in file', items: [], filterKeyForItem: diff => diff.lineText, elementForItem: diff => { - const li = document.createElement('li') - li.classList.add('two-lines') + const li = document.createElement('li'); + li.classList.add('two-lines'); - const primaryLine = document.createElement('div') - primaryLine.classList.add('primary-line') - primaryLine.textContent = diff.lineText - li.appendChild(primaryLine) + const primaryLine = document.createElement('div'); + primaryLine.classList.add('primary-line'); + primaryLine.textContent = diff.lineText; + li.appendChild(primaryLine); - const secondaryLine = document.createElement('div') - secondaryLine.classList.add('secondary-line') + const secondaryLine = document.createElement('div'); + secondaryLine.classList.add('secondary-line'); secondaryLine.textContent = `-${diff.oldStart},${diff.oldLines} +${ diff.newStart - },${diff.newLines}` - li.appendChild(secondaryLine) + },${diff.newLines}`; + li.appendChild(secondaryLine); - return li + return li; }, didConfirmSelection: diff => { - this.cancel() - const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart + this.cancel(); + const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart; this.editor.setCursorBufferPosition([bufferRow, 0], { autoscroll: true - }) - this.editor.moveToFirstCharacterOfLine() + }); + this.editor.moveToFirstCharacterOfLine(); }, didCancelSelection: () => { - this.cancel() + this.cancel(); } - }) - this.selectListView.element.classList.add('diff-list-view') + }); + this.selectListView.element.classList.add('diff-list-view'); this.panel = atom.workspace.addModalPanel({ item: this.selectListView, visible: false - }) + }); } - attach () { - this.previouslyFocusedElement = document.activeElement - this.selectListView.reset() - this.panel.show() - this.selectListView.focus() + attach() { + this.previouslyFocusedElement = document.activeElement; + this.selectListView.reset(); + this.panel.show(); + this.selectListView.focus(); } - cancel () { - this.panel.hide() + cancel() { + this.panel.hide(); if (this.previouslyFocusedElement) { - this.previouslyFocusedElement.focus() - this.previouslyFocusedElement = null + this.previouslyFocusedElement.focus(); + this.previouslyFocusedElement = null; } } - destroy () { - this.cancel() - this.panel.destroy() - return this.selectListView.destroy() + destroy() { + this.cancel(); + this.panel.destroy(); + return this.selectListView.destroy(); } - async toggle () { - const editor = atom.workspace.getActiveTextEditor() + async toggle() { + const editor = atom.workspace.getActiveTextEditor(); if (this.panel.isVisible()) { - this.cancel() + this.cancel(); } else if (editor) { - this.editor = editor - const repository = repositoryForPath(this.editor.getPath()) + this.editor = editor; + const repository = repositoryForPath(this.editor.getPath()); let diffs = repository ? repository.getLineDiffs(this.editor.getPath(), this.editor.getText()) - : [] - if (!diffs) diffs = [] + : []; + if (!diffs) diffs = []; for (let diff of diffs) { - const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart - const lineText = this.editor.lineTextForBufferRow(bufferRow) - diff.lineText = lineText ? lineText.trim() : '' + const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart; + const lineText = this.editor.lineTextForBufferRow(bufferRow); + diff.lineText = lineText ? lineText.trim() : ''; } - await this.selectListView.update({ items: diffs }) - this.attach() + await this.selectListView.update({ items: diffs }); + this.attach(); } } -} +}; diff --git a/packages/git-diff/lib/git-diff-view.js b/packages/git-diff/lib/git-diff-view.js index 99d57b483..7cb7320f9 100644 --- a/packages/git-diff/lib/git-diff-view.js +++ b/packages/git-diff/lib/git-diff-view.js @@ -1,21 +1,21 @@ -const { CompositeDisposable } = require('atom') -const { repositoryForPath } = require('./helpers') +const { CompositeDisposable } = require('atom'); +const { repositoryForPath } = require('./helpers'); -const MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024 +const MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024; module.exports = class GitDiffView { - constructor (editor) { - this.updateDiffs = this.updateDiffs.bind(this) - this.editor = editor - this.subscriptions = new CompositeDisposable() - this.decorations = {} - this.markers = [] + constructor(editor) { + this.updateDiffs = this.updateDiffs.bind(this); + this.editor = editor; + this.subscriptions = new CompositeDisposable(); + this.decorations = {}; + this.markers = []; } - start () { - const editorElement = this.editor.getElement() + start() { + const editorElement = this.editor.getElement(); - this.subscribeToRepository() + this.subscribeToRepository(); this.subscriptions.add( this.editor.onDidStopChanging(this.updateDiffs), @@ -35,29 +35,29 @@ module.exports = class GitDiffView { ), editorElement.onDidAttach(() => this.updateIconDecoration()), this.editor.onDidDestroy(() => { - this.cancelUpdate() - this.removeDecorations() - this.subscriptions.dispose() + this.cancelUpdate(); + this.removeDecorations(); + this.subscriptions.dispose(); }) - ) + ); - this.updateIconDecoration() - this.scheduleUpdate() + this.updateIconDecoration(); + this.scheduleUpdate(); } - moveToNextDiff () { - const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1 - let nextDiffLineNumber = null - let firstDiffLineNumber = null + moveToNextDiff() { + const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1; + let nextDiffLineNumber = null; + let firstDiffLineNumber = null; if (this.diffs) { for (const { newStart } of this.diffs) { if (newStart > cursorLineNumber) { - if (nextDiffLineNumber == null) nextDiffLineNumber = newStart - 1 - nextDiffLineNumber = Math.min(newStart - 1, nextDiffLineNumber) + if (nextDiffLineNumber == null) nextDiffLineNumber = newStart - 1; + nextDiffLineNumber = Math.min(newStart - 1, nextDiffLineNumber); } - if (firstDiffLineNumber == null) firstDiffLineNumber = newStart - 1 - firstDiffLineNumber = Math.min(newStart - 1, firstDiffLineNumber) + if (firstDiffLineNumber == null) firstDiffLineNumber = newStart - 1; + firstDiffLineNumber = Math.min(newStart - 1, firstDiffLineNumber); } } @@ -66,39 +66,39 @@ module.exports = class GitDiffView { atom.config.get('git-diff.wrapAroundOnMoveToDiff') && nextDiffLineNumber == null ) { - nextDiffLineNumber = firstDiffLineNumber + nextDiffLineNumber = firstDiffLineNumber; } - this.moveToLineNumber(nextDiffLineNumber) + this.moveToLineNumber(nextDiffLineNumber); } - updateIconDecoration () { - const gutter = this.editor.getElement().querySelector('.gutter') + updateIconDecoration() { + const gutter = this.editor.getElement().querySelector('.gutter'); if (gutter) { if ( atom.config.get('editor.showLineNumbers') && atom.config.get('git-diff.showIconsInEditorGutter') ) { - gutter.classList.add('git-diff-icon') + gutter.classList.add('git-diff-icon'); } else { - gutter.classList.remove('git-diff-icon') + gutter.classList.remove('git-diff-icon'); } } } - moveToPreviousDiff () { - const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1 - let previousDiffLineNumber = -1 - let lastDiffLineNumber = -1 + moveToPreviousDiff() { + const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1; + let previousDiffLineNumber = -1; + let lastDiffLineNumber = -1; if (this.diffs) { for (const { newStart } of this.diffs) { if (newStart < cursorLineNumber) { previousDiffLineNumber = Math.max( newStart - 1, previousDiffLineNumber - ) + ); } - lastDiffLineNumber = Math.max(newStart - 1, lastDiffLineNumber) + lastDiffLineNumber = Math.max(newStart - 1, lastDiffLineNumber); } } @@ -107,87 +107,87 @@ module.exports = class GitDiffView { atom.config.get('git-diff.wrapAroundOnMoveToDiff') && previousDiffLineNumber === -1 ) { - previousDiffLineNumber = lastDiffLineNumber + previousDiffLineNumber = lastDiffLineNumber; } - this.moveToLineNumber(previousDiffLineNumber) + this.moveToLineNumber(previousDiffLineNumber); } - moveToLineNumber (lineNumber) { + moveToLineNumber(lineNumber) { if (lineNumber != null && lineNumber >= 0) { - this.editor.setCursorBufferPosition([lineNumber, 0]) - this.editor.moveToFirstCharacterOfLine() + this.editor.setCursorBufferPosition([lineNumber, 0]); + this.editor.moveToFirstCharacterOfLine(); } } - subscribeToRepository () { - this.repository = repositoryForPath(this.editor.getPath()) + subscribeToRepository() { + this.repository = repositoryForPath(this.editor.getPath()); if (this.repository) { this.subscriptions.add( this.repository.onDidChangeStatuses(() => { - this.scheduleUpdate() + this.scheduleUpdate(); }) - ) + ); this.subscriptions.add( this.repository.onDidChangeStatus(changedPath => { - if (changedPath === this.editor.getPath()) this.scheduleUpdate() + if (changedPath === this.editor.getPath()) this.scheduleUpdate(); }) - ) + ); } } - cancelUpdate () { - clearImmediate(this.immediateId) + cancelUpdate() { + clearImmediate(this.immediateId); } - scheduleUpdate () { - this.cancelUpdate() - this.immediateId = setImmediate(this.updateDiffs) + scheduleUpdate() { + this.cancelUpdate(); + this.immediateId = setImmediate(this.updateDiffs); } - updateDiffs () { - if (this.editor.isDestroyed()) return - this.removeDecorations() - const path = this.editor && this.editor.getPath() + updateDiffs() { + if (this.editor.isDestroyed()) return; + this.removeDecorations(); + const path = this.editor && this.editor.getPath(); if ( path && this.editor.getBuffer().getLength() < MAX_BUFFER_LENGTH_TO_DIFF ) { this.diffs = this.repository && - this.repository.getLineDiffs(path, this.editor.getText()) - if (this.diffs) this.addDecorations(this.diffs) + this.repository.getLineDiffs(path, this.editor.getText()); + if (this.diffs) this.addDecorations(this.diffs); } } - addDecorations (diffs) { + addDecorations(diffs) { for (const { newStart, oldLines, newLines } of diffs) { - const startRow = newStart - 1 - const endRow = newStart + newLines - 1 + const startRow = newStart - 1; + const endRow = newStart + newLines - 1; if (oldLines === 0 && newLines > 0) { - this.markRange(startRow, endRow, 'git-line-added') + this.markRange(startRow, endRow, 'git-line-added'); } else if (newLines === 0 && oldLines > 0) { if (startRow < 0) { - this.markRange(0, 0, 'git-previous-line-removed') + this.markRange(0, 0, 'git-previous-line-removed'); } else { - this.markRange(startRow, startRow, 'git-line-removed') + this.markRange(startRow, startRow, 'git-line-removed'); } } else { - this.markRange(startRow, endRow, 'git-line-modified') + this.markRange(startRow, endRow, 'git-line-modified'); } } } - removeDecorations () { - for (let marker of this.markers) marker.destroy() - this.markers = [] + removeDecorations() { + for (let marker of this.markers) marker.destroy(); + this.markers = []; } - markRange (startRow, endRow, klass) { + markRange(startRow, endRow, klass) { const marker = this.editor.markBufferRange([[startRow, 0], [endRow, 0]], { invalidate: 'never' - }) - this.editor.decorateMarker(marker, { type: 'line-number', class: klass }) - this.markers.push(marker) + }); + this.editor.decorateMarker(marker, { type: 'line-number', class: klass }); + this.markers.push(marker); } -} +}; diff --git a/packages/git-diff/lib/helpers.js b/packages/git-diff/lib/helpers.js index 07b95c0f1..30b38911a 100644 --- a/packages/git-diff/lib/helpers.js +++ b/packages/git-diff/lib/helpers.js @@ -1,11 +1,11 @@ -exports.repositoryForPath = function (goalPath) { - const directories = atom.project.getDirectories() - const repositories = atom.project.getRepositories() +exports.repositoryForPath = function(goalPath) { + const directories = atom.project.getDirectories(); + const repositories = atom.project.getRepositories(); for (let i = 0; i < directories.length; i++) { - const directory = directories[i] + const directory = directories[i]; if (goalPath === directory.getPath() || directory.contains(goalPath)) { - return repositories[i] + return repositories[i]; } } - return null -} + return null; +}; diff --git a/packages/git-diff/lib/main.js b/packages/git-diff/lib/main.js index 72597b93c..93aee760f 100644 --- a/packages/git-diff/lib/main.js +++ b/packages/git-diff/lib/main.js @@ -1,32 +1,32 @@ -const GitDiffView = require('./git-diff-view') -const DiffListView = require('./diff-list-view') +const GitDiffView = require('./git-diff-view'); +const DiffListView = require('./diff-list-view'); -let diffListView = null +let diffListView = null; module.exports = { - activate () { - const watchedEditors = new WeakSet() + activate() { + const watchedEditors = new WeakSet(); atom.workspace.observeTextEditors(editor => { - if (watchedEditors.has(editor)) return + if (watchedEditors.has(editor)) return; - new GitDiffView(editor).start() + new GitDiffView(editor).start(); atom.commands.add( atom.views.getView(editor), 'git-diff:toggle-diff-list', () => { - if (diffListView == null) diffListView = new DiffListView() - diffListView.toggle() + if (diffListView == null) diffListView = new DiffListView(); + diffListView.toggle(); } - ) + ); - watchedEditors.add(editor) - editor.onDidDestroy(() => watchedEditors.delete(editor)) - }) + watchedEditors.add(editor); + editor.onDidDestroy(() => watchedEditors.delete(editor)); + }); }, - deactivate () { - if (diffListView) diffListView.destroy() - diffListView = null + deactivate() { + if (diffListView) diffListView.destroy(); + diffListView = null; } -} +}; diff --git a/packages/git-diff/spec/diff-list-view-spec.js b/packages/git-diff/spec/diff-list-view-spec.js index 8f98e68c0..25e4f1e53 100644 --- a/packages/git-diff/spec/diff-list-view-spec.js +++ b/packages/git-diff/spec/diff-list-view-spec.js @@ -1,49 +1,51 @@ -const path = require('path') -const fs = require('fs-plus') -const temp = require('temp') +const path = require('path'); +const fs = require('fs-plus'); +const temp = require('temp'); describe('git-diff:toggle-diff-list', () => { - let diffListView, editor + let diffListView, editor; beforeEach(() => { - const projectPath = temp.mkdirSync('git-diff-spec-') - fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath) + const projectPath = temp.mkdirSync('git-diff-spec-'); + fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath); fs.moveSync( path.join(projectPath, 'git.git'), path.join(projectPath, '.git') - ) - atom.project.setPaths([projectPath]) + ); + atom.project.setPaths([projectPath]); - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); - waitsForPromise(() => atom.packages.activatePackage('git-diff')) + waitsForPromise(() => atom.packages.activatePackage('git-diff')); - waitsForPromise(() => atom.workspace.open('sample.js')) + waitsForPromise(() => atom.workspace.open('sample.js')); runs(() => { - editor = atom.workspace.getActiveTextEditor() - editor.setCursorBufferPosition([8, 30]) - editor.insertText('a') - atom.commands.dispatch(editor.getElement(), 'git-diff:toggle-diff-list') - }) + editor = atom.workspace.getActiveTextEditor(); + editor.setCursorBufferPosition([8, 30]); + editor.insertText('a'); + atom.commands.dispatch(editor.getElement(), 'git-diff:toggle-diff-list'); + }); waitsFor(() => { - diffListView = document.querySelector('.diff-list-view') - return diffListView && diffListView.querySelectorAll('li').length > 0 - }) - }) + diffListView = document.querySelector('.diff-list-view'); + return diffListView && diffListView.querySelectorAll('li').length > 0; + }); + }); it('shows a list of all diff hunks', () => { - diffListView = document.querySelector('.diff-list-view ol') - expect(diffListView.textContent).toBe('while (items.length > 0) {a-9,1 +9,1') - }) + diffListView = document.querySelector('.diff-list-view ol'); + expect(diffListView.textContent).toBe( + 'while (items.length > 0) {a-9,1 +9,1' + ); + }); it('moves the cursor to the selected hunk', () => { - editor.setCursorBufferPosition([0, 0]) + editor.setCursorBufferPosition([0, 0]); atom.commands.dispatch( document.querySelector('.diff-list-view'), 'core:confirm' - ) - expect(editor.getCursorBufferPosition()).toEqual([8, 4]) - }) -}) + ); + expect(editor.getCursorBufferPosition()).toEqual([8, 4]); + }); +}); diff --git a/packages/git-diff/spec/git-diff-spec.js b/packages/git-diff/spec/git-diff-spec.js index badb15f76..bdae69f35 100644 --- a/packages/git-diff/spec/git-diff-spec.js +++ b/packages/git-diff/spec/git-diff-spec.js @@ -1,244 +1,246 @@ -const path = require('path') -const fs = require('fs-plus') -const temp = require('temp') +const path = require('path'); +const fs = require('fs-plus'); +const temp = require('temp'); describe('GitDiff package', () => { - let editor, editorElement, projectPath + let editor, editorElement, projectPath; beforeEach(() => { - spyOn(window, 'setImmediate').andCallFake(fn => fn()) + spyOn(window, 'setImmediate').andCallFake(fn => fn()); - projectPath = temp.mkdirSync('git-diff-spec-') - const otherPath = temp.mkdirSync('some-other-path-') + projectPath = temp.mkdirSync('git-diff-spec-'); + const otherPath = temp.mkdirSync('some-other-path-'); - fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath) + fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath); fs.moveSync( path.join(projectPath, 'git.git'), path.join(projectPath, '.git') - ) - atom.project.setPaths([otherPath, projectPath]) + ); + atom.project.setPaths([otherPath, projectPath]); - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); waitsForPromise(() => atom.workspace.open(path.join(projectPath, 'sample.js')) - ) + ); runs(() => { - editor = atom.workspace.getActiveTextEditor() - editorElement = editor.getElement() - }) + editor = atom.workspace.getActiveTextEditor(); + editorElement = editor.getElement(); + }); - waitsForPromise(() => atom.packages.activatePackage('git-diff')) - }) + waitsForPromise(() => atom.packages.activatePackage('git-diff')); + }); describe('when the editor has modified lines', () => { it('highlights the modified lines', () => { expect(editorElement.querySelectorAll('.git-line-modified').length).toBe( 0 - ) - editor.insertText('a') - advanceClock(editor.getBuffer().stoppedChangingDelay) + ); + editor.insertText('a'); + advanceClock(editor.getBuffer().stoppedChangingDelay); expect(editorElement.querySelectorAll('.git-line-modified').length).toBe( 1 - ) + ); expect(editorElement.querySelector('.git-line-modified')).toHaveData( 'buffer-row', 0 - ) - }) - }) + ); + }); + }); describe('when the editor has added lines', () => { it('highlights the added lines', () => { - expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0) - editor.moveToEndOfLine() - editor.insertNewline() - editor.insertText('a') - advanceClock(editor.getBuffer().stoppedChangingDelay) - expect(editorElement.querySelectorAll('.git-line-added').length).toBe(1) + expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0); + editor.moveToEndOfLine(); + editor.insertNewline(); + editor.insertText('a'); + advanceClock(editor.getBuffer().stoppedChangingDelay); + expect(editorElement.querySelectorAll('.git-line-added').length).toBe(1); expect(editorElement.querySelector('.git-line-added')).toHaveData( 'buffer-row', 1 - ) - }) - }) + ); + }); + }); describe('when the editor has removed lines', () => { it('highlights the line preceeding the deleted lines', () => { - expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0) - editor.setCursorBufferPosition([5]) - editor.deleteLine() - advanceClock(editor.getBuffer().stoppedChangingDelay) - expect(editorElement.querySelectorAll('.git-line-removed').length).toBe(1) + expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0); + editor.setCursorBufferPosition([5]); + editor.deleteLine(); + advanceClock(editor.getBuffer().stoppedChangingDelay); + expect(editorElement.querySelectorAll('.git-line-removed').length).toBe( + 1 + ); expect(editorElement.querySelector('.git-line-removed')).toHaveData( 'buffer-row', 4 - ) - }) - }) + ); + }); + }); describe('when the editor has removed the first line', () => { it('highlights the line preceeding the deleted lines', () => { - expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0) - editor.setCursorBufferPosition([0, 0]) - editor.deleteLine() - advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0); + editor.setCursorBufferPosition([0, 0]); + editor.deleteLine(); + advanceClock(editor.getBuffer().stoppedChangingDelay); expect( editorElement.querySelectorAll('.git-previous-line-removed').length - ).toBe(1) + ).toBe(1); expect( editorElement.querySelector('.git-previous-line-removed') - ).toHaveData('buffer-row', 0) - }) - }) + ).toHaveData('buffer-row', 0); + }); + }); describe('when a modified line is restored to the HEAD version contents', () => { it('removes the diff highlight', () => { expect(editorElement.querySelectorAll('.git-line-modified').length).toBe( 0 - ) - editor.insertText('a') - advanceClock(editor.getBuffer().stoppedChangingDelay) + ); + editor.insertText('a'); + advanceClock(editor.getBuffer().stoppedChangingDelay); expect(editorElement.querySelectorAll('.git-line-modified').length).toBe( 1 - ) - editor.backspace() - advanceClock(editor.getBuffer().stoppedChangingDelay) + ); + editor.backspace(); + advanceClock(editor.getBuffer().stoppedChangingDelay); expect(editorElement.querySelectorAll('.git-line-modified').length).toBe( 0 - ) - }) - }) + ); + }); + }); describe('when a modified file is opened', () => { it('highlights the changed lines', () => { fs.writeFileSync( path.join(projectPath, 'sample.txt'), 'Some different text.' - ) - let nextTick = false + ); + let nextTick = false; waitsForPromise(() => atom.workspace.open(path.join(projectPath, 'sample.txt')) - ) + ); runs(() => { - editorElement = atom.workspace.getActiveTextEditor().getElement() - }) + editorElement = atom.workspace.getActiveTextEditor().getElement(); + }); setImmediate(() => { - nextTick = true - }) + nextTick = true; + }); - waitsFor(() => nextTick) + waitsFor(() => nextTick); runs(() => { expect( editorElement.querySelectorAll('.git-line-modified').length - ).toBe(1) + ).toBe(1); expect(editorElement.querySelector('.git-line-modified')).toHaveData( 'buffer-row', 0 - ) - }) - }) - }) + ); + }); + }); + }); describe('when the project paths change', () => { it("doesn't try to use the destroyed git repository", () => { - editor.deleteLine() - atom.project.setPaths([temp.mkdirSync('no-repository')]) - advanceClock(editor.getBuffer().stoppedChangingDelay) - }) - }) + editor.deleteLine(); + atom.project.setPaths([temp.mkdirSync('no-repository')]); + advanceClock(editor.getBuffer().stoppedChangingDelay); + }); + }); describe('move-to-next-diff/move-to-previous-diff events', () => { it('moves the cursor to first character of the next/previous diff line', () => { - editor.insertText('a') - editor.setCursorBufferPosition([5]) - editor.deleteLine() - advanceClock(editor.getBuffer().stoppedChangingDelay) + editor.insertText('a'); + editor.setCursorBufferPosition([5]); + editor.deleteLine(); + advanceClock(editor.getBuffer().stoppedChangingDelay); - editor.setCursorBufferPosition([0]) - atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff') - expect(editor.getCursorBufferPosition()).toEqual([4, 4]) + editor.setCursorBufferPosition([0]); + atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff'); + expect(editor.getCursorBufferPosition()).toEqual([4, 4]); - atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff') - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) + atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff'); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); it('wraps around to the first/last diff in the file', () => { - editor.insertText('a') - editor.setCursorBufferPosition([5]) - editor.deleteLine() - advanceClock(editor.getBuffer().stoppedChangingDelay) + editor.insertText('a'); + editor.setCursorBufferPosition([5]); + editor.deleteLine(); + advanceClock(editor.getBuffer().stoppedChangingDelay); - editor.setCursorBufferPosition([0]) - atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff') - expect(editor.getCursorBufferPosition()).toEqual([4, 4]) + editor.setCursorBufferPosition([0]); + atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff'); + expect(editor.getCursorBufferPosition()).toEqual([4, 4]); - atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff') - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff'); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); - atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff') - expect(editor.getCursorBufferPosition()).toEqual([4, 4]) - }) + atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff'); + expect(editor.getCursorBufferPosition()).toEqual([4, 4]); + }); describe('when the wrapAroundOnMoveToDiff config option is false', () => { beforeEach(() => atom.config.set('git-diff.wrapAroundOnMoveToDiff', false) - ) + ); it('does not wraps around to the first/last diff in the file', () => { - editor.insertText('a') - editor.setCursorBufferPosition([5]) - editor.deleteLine() - advanceClock(editor.getBuffer().stoppedChangingDelay) + editor.insertText('a'); + editor.setCursorBufferPosition([5]); + editor.deleteLine(); + advanceClock(editor.getBuffer().stoppedChangingDelay); - editor.setCursorBufferPosition([0]) - atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff') - expect(editor.getCursorBufferPosition()).toEqual([4, 4]) + editor.setCursorBufferPosition([0]); + atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff'); + expect(editor.getCursorBufferPosition()).toEqual([4, 4]); - atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff') - expect(editor.getCursorBufferPosition()).toEqual([4, 4]) + atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff'); + expect(editor.getCursorBufferPosition()).toEqual([4, 4]); - atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff') - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff'); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); - atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff') - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) - }) - }) + atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff'); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); + }); + }); describe('when the showIconsInEditorGutter config option is true', () => { beforeEach(() => { - atom.config.set('git-diff.showIconsInEditorGutter', true) - }) + atom.config.set('git-diff.showIconsInEditorGutter', true); + }); it('the gutter has a git-diff-icon class', () => expect(editorElement.querySelector('.gutter')).toHaveClass( 'git-diff-icon' - )) + )); it('keeps the git-diff-icon class when editor.showLineNumbers is toggled', () => { - atom.config.set('editor.showLineNumbers', false) + atom.config.set('editor.showLineNumbers', false); expect(editorElement.querySelector('.gutter')).not.toHaveClass( 'git-diff-icon' - ) + ); - atom.config.set('editor.showLineNumbers', true) + atom.config.set('editor.showLineNumbers', true); expect(editorElement.querySelector('.gutter')).toHaveClass( 'git-diff-icon' - ) - }) + ); + }); it('removes the git-diff-icon class when the showIconsInEditorGutter config option set to false', () => { - atom.config.set('git-diff.showIconsInEditorGutter', false) + atom.config.set('git-diff.showIconsInEditorGutter', false); expect(editorElement.querySelector('.gutter')).not.toHaveClass( 'git-diff-icon' - ) - }) - }) -}) + ); + }); + }); +}); diff --git a/packages/go-to-line/lib/go-to-line-view.js b/packages/go-to-line/lib/go-to-line-view.js index 93d174dbc..34c59a466 100644 --- a/packages/go-to-line/lib/go-to-line-view.js +++ b/packages/go-to-line/lib/go-to-line-view.js @@ -1,111 +1,111 @@ -'use babel' +'use babel'; -import { Point, TextEditor } from 'atom' +import { Point, TextEditor } from 'atom'; class GoToLineView { - constructor () { - this.miniEditor = new TextEditor({ mini: true }) - this.miniEditor.element.addEventListener('blur', this.close.bind(this)) + constructor() { + this.miniEditor = new TextEditor({ mini: true }); + this.miniEditor.element.addEventListener('blur', this.close.bind(this)); - this.message = document.createElement('div') - this.message.classList.add('message') + this.message = document.createElement('div'); + this.message.classList.add('message'); - this.element = document.createElement('div') - this.element.classList.add('go-to-line') - this.element.appendChild(this.miniEditor.element) - this.element.appendChild(this.message) + this.element = document.createElement('div'); + this.element.classList.add('go-to-line'); + this.element.appendChild(this.miniEditor.element); + this.element.appendChild(this.message); this.panel = atom.workspace.addModalPanel({ item: this, visible: false - }) + }); atom.commands.add('atom-text-editor', 'go-to-line:toggle', () => { - this.toggle() - return false - }) + this.toggle(); + return false; + }); atom.commands.add(this.miniEditor.element, 'core:confirm', () => { - this.navigate() - }) + this.navigate(); + }); atom.commands.add(this.miniEditor.element, 'core:cancel', () => { - this.close() - }) + this.close(); + }); this.miniEditor.onWillInsertText(arg => { if (arg.text.match(/[^0-9:]/)) { - arg.cancel() + arg.cancel(); } - }) + }); this.miniEditor.onDidChange(() => { - this.navigate({ keepOpen: true }) - }) + this.navigate({ keepOpen: true }); + }); } - toggle () { - this.panel.isVisible() ? this.close() : this.open() + toggle() { + this.panel.isVisible() ? this.close() : this.open(); } - close () { - if (!this.panel.isVisible()) return - this.miniEditor.setText('') - this.panel.hide() + close() { + if (!this.panel.isVisible()) return; + this.miniEditor.setText(''); + this.panel.hide(); if (this.miniEditor.element.hasFocus()) { - this.restoreFocus() + this.restoreFocus(); } } - navigate (options = {}) { - const lineNumber = this.miniEditor.getText() - const editor = atom.workspace.getActiveTextEditor() + navigate(options = {}) { + const lineNumber = this.miniEditor.getText(); + const editor = atom.workspace.getActiveTextEditor(); if (!options.keepOpen) { - this.close() + this.close(); } - if (!editor || !lineNumber.length) return + if (!editor || !lineNumber.length) return; - const currentRow = editor.getCursorBufferPosition().row - const rowLineNumber = lineNumber.split(/:+/)[0] || '' + const currentRow = editor.getCursorBufferPosition().row; + const rowLineNumber = lineNumber.split(/:+/)[0] || ''; const row = - rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow - const columnLineNumber = lineNumber.split(/:+/)[1] || '' + rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow; + const columnLineNumber = lineNumber.split(/:+/)[1] || ''; const column = - columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1 + columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1; - const position = new Point(row, column) - editor.setCursorBufferPosition(position) - editor.unfoldBufferRow(row) + const position = new Point(row, column); + editor.setCursorBufferPosition(position); + editor.unfoldBufferRow(row); if (column < 0) { - editor.moveToFirstCharacterOfLine() + editor.moveToFirstCharacterOfLine(); } editor.scrollToBufferPosition(position, { center: true - }) + }); } - storeFocusedElement () { - this.previouslyFocusedElement = document.activeElement - return this.previouslyFocusedElement + storeFocusedElement() { + this.previouslyFocusedElement = document.activeElement; + return this.previouslyFocusedElement; } - restoreFocus () { + restoreFocus() { if ( this.previouslyFocusedElement && this.previouslyFocusedElement.parentElement ) { - return this.previouslyFocusedElement.focus() + return this.previouslyFocusedElement.focus(); } - atom.views.getView(atom.workspace).focus() + atom.views.getView(atom.workspace).focus(); } - open () { - if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return - this.storeFocusedElement() - this.panel.show() + open() { + if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return; + this.storeFocusedElement(); + this.panel.show(); this.message.textContent = - 'Enter a or : to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7' - this.miniEditor.element.focus() + 'Enter a or : to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'; + this.miniEditor.element.focus(); } } export default { - activate () { - return new GoToLineView() + activate() { + return new GoToLineView(); } -} +}; diff --git a/packages/go-to-line/spec/go-to-line-spec.js b/packages/go-to-line/spec/go-to-line-spec.js index b3440b00f..3f9ae304c 100644 --- a/packages/go-to-line/spec/go-to-line-spec.js +++ b/packages/go-to-line/spec/go-to-line-spec.js @@ -1,169 +1,169 @@ -'use babel' +'use babel'; /* eslint-env jasmine */ -import GoToLineView from '../lib/go-to-line-view' +import GoToLineView from '../lib/go-to-line-view'; describe('GoToLine', () => { - let editor = null - let editorView = null - let goToLine = null + let editor = null; + let editorView = null; + let goToLine = null; beforeEach(() => { waitsForPromise(() => { - return atom.workspace.open('sample.js') - }) + return atom.workspace.open('sample.js'); + }); runs(() => { - const workspaceElement = atom.views.getView(atom.workspace) - workspaceElement.style.height = '200px' - workspaceElement.style.width = '1000px' - jasmine.attachToDOM(workspaceElement) - editor = atom.workspace.getActiveTextEditor() - editorView = atom.views.getView(editor) - goToLine = GoToLineView.activate() - editor.setCursorBufferPosition([1, 0]) - }) - }) + const workspaceElement = atom.views.getView(atom.workspace); + workspaceElement.style.height = '200px'; + workspaceElement.style.width = '1000px'; + jasmine.attachToDOM(workspaceElement); + editor = atom.workspace.getActiveTextEditor(); + editorView = atom.views.getView(editor); + goToLine = GoToLineView.activate(); + editor.setCursorBufferPosition([1, 0]); + }); + }); describe('when go-to-line:toggle is triggered', () => { it('adds a modal panel', () => { - expect(goToLine.panel.isVisible()).toBeFalsy() - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - }) - }) + expect(goToLine.panel.isVisible()).toBeFalsy(); + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + }); + }); describe('when entering a line number', () => { it('only allows 0-9 and the colon character to be entered in the mini editor', () => { - expect(goToLine.miniEditor.getText()).toBe('') - goToLine.miniEditor.insertText('a') - expect(goToLine.miniEditor.getText()).toBe('') - goToLine.miniEditor.insertText('path/file.txt:56') - expect(goToLine.miniEditor.getText()).toBe('') - goToLine.miniEditor.insertText(':') - expect(goToLine.miniEditor.getText()).toBe(':') - goToLine.miniEditor.setText('') - goToLine.miniEditor.insertText('4') - expect(goToLine.miniEditor.getText()).toBe('4') - }) - }) + expect(goToLine.miniEditor.getText()).toBe(''); + goToLine.miniEditor.insertText('a'); + expect(goToLine.miniEditor.getText()).toBe(''); + goToLine.miniEditor.insertText('path/file.txt:56'); + expect(goToLine.miniEditor.getText()).toBe(''); + goToLine.miniEditor.insertText(':'); + expect(goToLine.miniEditor.getText()).toBe(':'); + goToLine.miniEditor.setText(''); + goToLine.miniEditor.insertText('4'); + expect(goToLine.miniEditor.getText()).toBe('4'); + }); + }); describe('when typing line numbers (auto-navigation)', () => { it('automatically scrolls to the desired line', () => { - goToLine.miniEditor.insertText('19') - expect(editor.getCursorBufferPosition()).toEqual([18, 0]) - }) - }) + goToLine.miniEditor.insertText('19'); + expect(editor.getCursorBufferPosition()).toEqual([18, 0]); + }); + }); describe('when typing line and column numbers (auto-navigation)', () => { it('automatically scrolls to the desired line and column', () => { - goToLine.miniEditor.insertText('3:8') - expect(editor.getCursorBufferPosition()).toEqual([2, 7]) - }) - }) + goToLine.miniEditor.insertText('3:8'); + expect(editor.getCursorBufferPosition()).toEqual([2, 7]); + }); + }); describe('when entering a line number and column number', () => { it('moves the cursor to the column number of the line specified', () => { - expect(goToLine.miniEditor.getText()).toBe('') - goToLine.miniEditor.insertText('3:14') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(editor.getCursorBufferPosition()).toEqual([2, 13]) - }) + expect(goToLine.miniEditor.getText()).toBe(''); + goToLine.miniEditor.insertText('3:14'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(editor.getCursorBufferPosition()).toEqual([2, 13]); + }); it('centers the selected line', () => { - goToLine.miniEditor.insertText('45:4') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - const rowsPerPage = editor.getRowsPerPage() - const currentRow = editor.getCursorBufferPosition().row - 1 + goToLine.miniEditor.insertText('45:4'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + const rowsPerPage = editor.getRowsPerPage(); + const currentRow = editor.getCursorBufferPosition().row - 1; expect(editor.getFirstVisibleScreenRow()).toBe( currentRow - Math.ceil(rowsPerPage / 2) - ) + ); expect(editor.getLastVisibleScreenRow()).toBe( currentRow + Math.floor(rowsPerPage / 2) - ) - }) - }) + ); + }); + }); describe('when entering a line number greater than the number of rows in the buffer', () => { it('moves the cursor position to the first character of the last line', () => { - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - expect(goToLine.miniEditor.getText()).toBe('') - goToLine.miniEditor.insertText('78') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(goToLine.panel.isVisible()).toBeFalsy() - expect(editor.getCursorBufferPosition()).toEqual([77, 0]) - }) - }) + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + expect(goToLine.miniEditor.getText()).toBe(''); + goToLine.miniEditor.insertText('78'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(goToLine.panel.isVisible()).toBeFalsy(); + expect(editor.getCursorBufferPosition()).toEqual([77, 0]); + }); + }); describe('when entering a column number greater than the number in the specified line', () => { it('moves the cursor position to the last character of the specified line', () => { - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - expect(goToLine.miniEditor.getText()).toBe('') - goToLine.miniEditor.insertText('3:43') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(goToLine.panel.isVisible()).toBeFalsy() - expect(editor.getCursorBufferPosition()).toEqual([2, 39]) - }) - }) + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + expect(goToLine.miniEditor.getText()).toBe(''); + goToLine.miniEditor.insertText('3:43'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(goToLine.panel.isVisible()).toBeFalsy(); + expect(editor.getCursorBufferPosition()).toEqual([2, 39]); + }); + }); describe('when core:confirm is triggered', () => { describe('when a line number has been entered', () => { it('moves the cursor to the first character of the line', () => { - goToLine.miniEditor.insertText('3') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(editor.getCursorBufferPosition()).toEqual([2, 4]) - }) - }) + goToLine.miniEditor.insertText('3'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(editor.getCursorBufferPosition()).toEqual([2, 4]); + }); + }); describe('when the line number entered is nested within foldes', () => { it('unfolds all folds containing the given row', () => { - expect(editor.indentationForBufferRow(9)).toEqual(3) - editor.foldAll() - expect(editor.screenRowForBufferRow(9)).toEqual(0) - goToLine.miniEditor.insertText('10') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(editor.getCursorBufferPosition()).toEqual([9, 6]) - }) - }) - }) + expect(editor.indentationForBufferRow(9)).toEqual(3); + editor.foldAll(); + expect(editor.screenRowForBufferRow(9)).toEqual(0); + goToLine.miniEditor.insertText('10'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(editor.getCursorBufferPosition()).toEqual([9, 6]); + }); + }); + }); describe('when no line number has been entered', () => { it('closes the view and does not update the cursor position', () => { - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(goToLine.panel.isVisible()).toBeFalsy() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - }) - }) + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(goToLine.panel.isVisible()).toBeFalsy(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + }); + }); describe('when no line number has been entered, but a column number has been entered', () => { it('navigates to the column of the current line', () => { - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - goToLine.miniEditor.insertText('4:1') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(goToLine.panel.isVisible()).toBeFalsy() - expect(editor.getCursorBufferPosition()).toEqual([3, 0]) - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - goToLine.miniEditor.insertText(':19') - atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm') - expect(goToLine.panel.isVisible()).toBeFalsy() - expect(editor.getCursorBufferPosition()).toEqual([3, 18]) - }) - }) + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + goToLine.miniEditor.insertText('4:1'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(goToLine.panel.isVisible()).toBeFalsy(); + expect(editor.getCursorBufferPosition()).toEqual([3, 0]); + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + goToLine.miniEditor.insertText(':19'); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm'); + expect(goToLine.panel.isVisible()).toBeFalsy(); + expect(editor.getCursorBufferPosition()).toEqual([3, 18]); + }); + }); describe('when core:cancel is triggered', () => { it('closes the view and does not update the cursor position', () => { - atom.commands.dispatch(editorView, 'go-to-line:toggle') - expect(goToLine.panel.isVisible()).toBeTruthy() - atom.commands.dispatch(goToLine.miniEditor.element, 'core:cancel') - expect(goToLine.panel.isVisible()).toBeFalsy() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - }) - }) -}) + atom.commands.dispatch(editorView, 'go-to-line:toggle'); + expect(goToLine.panel.isVisible()).toBeTruthy(); + atom.commands.dispatch(goToLine.miniEditor.element, 'core:cancel'); + expect(goToLine.panel.isVisible()).toBeFalsy(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + }); + }); +}); diff --git a/packages/grammar-selector/lib/grammar-list-view.js b/packages/grammar-selector/lib/grammar-list-view.js index d6812fcfc..686d640c1 100644 --- a/packages/grammar-selector/lib/grammar-list-view.js +++ b/packages/grammar-selector/lib/grammar-list-view.js @@ -1,103 +1,103 @@ -const SelectListView = require('atom-select-list') +const SelectListView = require('atom-select-list'); module.exports = class GrammarListView { - constructor () { - this.autoDetect = { name: 'Auto Detect' } + constructor() { + this.autoDetect = { name: 'Auto Detect' }; this.selectListView = new SelectListView({ itemsClassList: ['mark-active'], items: [], filterKeyForItem: grammar => grammar.name, elementForItem: grammar => { - const grammarName = grammar.name || grammar.scopeName - const element = document.createElement('li') + const grammarName = grammar.name || grammar.scopeName; + const element = document.createElement('li'); if (grammar === this.currentGrammar) { - element.classList.add('active') + element.classList.add('active'); } - element.textContent = grammarName - element.dataset.grammar = grammarName + element.textContent = grammarName; + element.dataset.grammar = grammarName; - const div = document.createElement('div') - div.classList.add('pull-right') + const div = document.createElement('div'); + div.classList.add('pull-right'); if (grammar.scopeName) { - const scopeName = document.createElement('scopeName') - scopeName.classList.add('key-binding') // It will be styled the same as the keybindings in the command palette - scopeName.textContent = grammar.scopeName - div.appendChild(scopeName) - element.appendChild(div) + const scopeName = document.createElement('scopeName'); + scopeName.classList.add('key-binding'); // It will be styled the same as the keybindings in the command palette + scopeName.textContent = grammar.scopeName; + div.appendChild(scopeName); + element.appendChild(div); } - return element + return element; }, didConfirmSelection: grammar => { - this.cancel() + this.cancel(); if (grammar === this.autoDetect) { - atom.textEditors.clearGrammarOverride(this.editor) + atom.textEditors.clearGrammarOverride(this.editor); } else { - atom.textEditors.setGrammarOverride(this.editor, grammar.scopeName) + atom.textEditors.setGrammarOverride(this.editor, grammar.scopeName); } }, didCancelSelection: () => { - this.cancel() + this.cancel(); } - }) - this.selectListView.element.classList.add('grammar-selector') + }); + this.selectListView.element.classList.add('grammar-selector'); } - destroy () { - this.cancel() - return this.selectListView.destroy() + destroy() { + this.cancel(); + return this.selectListView.destroy(); } - cancel () { + cancel() { if (this.panel != null) { - this.panel.destroy() + this.panel.destroy(); } - this.panel = null - this.currentGrammar = null + this.panel = null; + this.currentGrammar = null; if (this.previouslyFocusedElement) { - this.previouslyFocusedElement.focus() - this.previouslyFocusedElement = null + this.previouslyFocusedElement.focus(); + this.previouslyFocusedElement = null; } } - attach () { - this.previouslyFocusedElement = document.activeElement + attach() { + this.previouslyFocusedElement = document.activeElement; if (this.panel == null) { - this.panel = atom.workspace.addModalPanel({ item: this.selectListView }) + this.panel = atom.workspace.addModalPanel({ item: this.selectListView }); } - this.selectListView.focus() - this.selectListView.reset() + this.selectListView.focus(); + this.selectListView.reset(); } - async toggle () { + async toggle() { if (this.panel != null) { - this.cancel() + this.cancel(); } else if (atom.workspace.getActiveTextEditor()) { - this.editor = atom.workspace.getActiveTextEditor() - this.currentGrammar = this.editor.getGrammar() + this.editor = atom.workspace.getActiveTextEditor(); + this.currentGrammar = this.editor.getGrammar(); if (this.currentGrammar === atom.grammars.nullGrammar) { - this.currentGrammar = this.autoDetect + this.currentGrammar = this.autoDetect; } const grammars = atom.grammars.getGrammars().filter(grammar => { - return grammar !== atom.grammars.nullGrammar && grammar.name - }) + return grammar !== atom.grammars.nullGrammar && grammar.name; + }); grammars.sort((a, b) => { if (a.scopeName === 'text.plain') { - return -1 + return -1; } else if (b.scopeName === 'text.plain') { - return 1 + return 1; } else if (a.name) { - return a.name.localeCompare(b.name) + return a.name.localeCompare(b.name); } else if (a.scopeName) { - return a.scopeName.localeCompare(b.scopeName) + return a.scopeName.localeCompare(b.scopeName); } else { - return 1 + return 1; } - }) - grammars.unshift(this.autoDetect) - await this.selectListView.update({ items: grammars }) - this.attach() + }); + grammars.unshift(this.autoDetect); + await this.selectListView.update({ items: grammars }); + this.attach(); } } -} +}; diff --git a/packages/grammar-selector/lib/grammar-status-view.js b/packages/grammar-selector/lib/grammar-status-view.js index d7f13930b..5f0a3b8e5 100644 --- a/packages/grammar-selector/lib/grammar-status-view.js +++ b/packages/grammar-selector/lib/grammar-status-view.js @@ -1,114 +1,114 @@ -const { Disposable } = require('atom') +const { Disposable } = require('atom'); module.exports = class GrammarStatusView { - constructor (statusBar) { - this.statusBar = statusBar - this.element = document.createElement('grammar-selector-status') - this.element.classList.add('grammar-status', 'inline-block') - this.grammarLink = document.createElement('a') - this.grammarLink.classList.add('inline-block') - this.element.appendChild(this.grammarLink) + constructor(statusBar) { + this.statusBar = statusBar; + this.element = document.createElement('grammar-selector-status'); + this.element.classList.add('grammar-status', 'inline-block'); + this.grammarLink = document.createElement('a'); + this.grammarLink.classList.add('inline-block'); + this.element.appendChild(this.grammarLink); this.activeItemSubscription = atom.workspace.observeActiveTextEditor( this.subscribeToActiveTextEditor.bind(this) - ) + ); this.configSubscription = atom.config.observe( 'grammar-selector.showOnRightSideOfStatusBar', this.attach.bind(this) - ) + ); const clickHandler = event => { - event.preventDefault() + event.preventDefault(); atom.commands.dispatch( atom.views.getView(atom.workspace.getActiveTextEditor()), 'grammar-selector:show' - ) - } - this.element.addEventListener('click', clickHandler) + ); + }; + this.element.addEventListener('click', clickHandler); this.clickSubscription = new Disposable(() => { - this.element.removeEventListener('click', clickHandler) - }) + this.element.removeEventListener('click', clickHandler); + }); } - attach () { + attach() { if (this.tile) { - this.tile.destroy() + this.tile.destroy(); } this.tile = atom.config.get('grammar-selector.showOnRightSideOfStatusBar') ? this.statusBar.addRightTile({ item: this.element, priority: 10 }) - : this.statusBar.addLeftTile({ item: this.element, priority: 10 }) + : this.statusBar.addLeftTile({ item: this.element, priority: 10 }); } - destroy () { + destroy() { if (this.activeItemSubscription) { - this.activeItemSubscription.dispose() + this.activeItemSubscription.dispose(); } if (this.grammarSubscription) { - this.grammarSubscription.dispose() + this.grammarSubscription.dispose(); } if (this.clickSubscription) { - this.clickSubscription.dispose() + this.clickSubscription.dispose(); } if (this.configSubscription) { - this.configSubscription.dispose() + this.configSubscription.dispose(); } if (this.tile) { - this.tile.destroy() + this.tile.destroy(); } if (this.tooltip) { - this.tooltip.dispose() + this.tooltip.dispose(); } } - subscribeToActiveTextEditor () { + subscribeToActiveTextEditor() { if (this.grammarSubscription) { - this.grammarSubscription.dispose() - this.grammarSubscription = null + this.grammarSubscription.dispose(); + this.grammarSubscription = null; } - const editor = atom.workspace.getActiveTextEditor() + const editor = atom.workspace.getActiveTextEditor(); if (editor) { this.grammarSubscription = editor.onDidChangeGrammar( this.updateGrammarText.bind(this) - ) + ); } - this.updateGrammarText() + this.updateGrammarText(); } - updateGrammarText () { + updateGrammarText() { atom.views.updateDocument(() => { - const editor = atom.workspace.getActiveTextEditor() - const grammar = editor ? editor.getGrammar() : null + const editor = atom.workspace.getActiveTextEditor(); + const grammar = editor ? editor.getGrammar() : null; if (this.tooltip) { - this.tooltip.dispose() - this.tooltip = null + this.tooltip.dispose(); + this.tooltip = null; } if (grammar) { - let grammarName = null + let grammarName = null; if (grammar === atom.grammars.nullGrammar) { - grammarName = 'Plain Text' + grammarName = 'Plain Text'; } else { - grammarName = grammar.name || grammar.scopeName + grammarName = grammar.name || grammar.scopeName; } - this.grammarLink.textContent = grammarName - this.grammarLink.dataset.grammar = grammarName - this.element.style.display = '' + this.grammarLink.textContent = grammarName; + this.grammarLink.dataset.grammar = grammarName; + this.element.style.display = ''; this.tooltip = atom.tooltips.add(this.element, { title: `File uses the ${grammarName} grammar` - }) + }); } else { - this.element.style.display = 'none' + this.element.style.display = 'none'; } - }) + }); } -} +}; diff --git a/packages/grammar-selector/lib/main.js b/packages/grammar-selector/lib/main.js index 428108dd4..520189df3 100644 --- a/packages/grammar-selector/lib/main.js +++ b/packages/grammar-selector/lib/main.js @@ -1,35 +1,35 @@ -const GrammarListView = require('./grammar-list-view') -const GrammarStatusView = require('./grammar-status-view') +const GrammarListView = require('./grammar-list-view'); +const GrammarStatusView = require('./grammar-status-view'); -let commandDisposable = null -let grammarListView = null -let grammarStatusView = null +let commandDisposable = null; +let grammarListView = null; +let grammarStatusView = null; module.exports = { - activate () { + activate() { commandDisposable = atom.commands.add( 'atom-text-editor', 'grammar-selector:show', () => { - if (!grammarListView) grammarListView = new GrammarListView() - grammarListView.toggle() + if (!grammarListView) grammarListView = new GrammarListView(); + grammarListView.toggle(); } - ) + ); }, - deactivate () { - if (commandDisposable) commandDisposable.dispose() - commandDisposable = null + deactivate() { + if (commandDisposable) commandDisposable.dispose(); + commandDisposable = null; - if (grammarStatusView) grammarStatusView.destroy() - grammarStatusView = null + if (grammarStatusView) grammarStatusView.destroy(); + grammarStatusView = null; - if (grammarListView) grammarListView.destroy() - grammarListView = null + if (grammarListView) grammarListView.destroy(); + grammarListView = null; }, - consumeStatusBar (statusBar) { - grammarStatusView = new GrammarStatusView(statusBar) - grammarStatusView.attach() + consumeStatusBar(statusBar) { + grammarStatusView = new GrammarStatusView(statusBar); + grammarStatusView.attach(); } -} +}; diff --git a/packages/grammar-selector/spec/grammar-selector-spec.js b/packages/grammar-selector/spec/grammar-selector-spec.js index 400fd6241..d6effb6b4 100644 --- a/packages/grammar-selector/spec/grammar-selector-spec.js +++ b/packages/grammar-selector/spec/grammar-selector-spec.js @@ -1,226 +1,226 @@ -const path = require('path') -const SelectListView = require('atom-select-list') +const path = require('path'); +const SelectListView = require('atom-select-list'); describe('GrammarSelector', () => { - let [editor, textGrammar, jsGrammar] = [] + let [editor, textGrammar, jsGrammar] = []; beforeEach(async () => { - jasmine.attachToDOM(atom.views.getView(atom.workspace)) - atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false) + jasmine.attachToDOM(atom.views.getView(atom.workspace)); + atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false); - await atom.packages.activatePackage('status-bar') - await atom.packages.activatePackage('grammar-selector') - await atom.packages.activatePackage('language-text') - await atom.packages.activatePackage('language-javascript') + await atom.packages.activatePackage('status-bar'); + await atom.packages.activatePackage('grammar-selector'); + await atom.packages.activatePackage('language-text'); + await atom.packages.activatePackage('language-javascript'); await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'language-with-no-name') - ) + ); - editor = await atom.workspace.open('sample.js') + editor = await atom.workspace.open('sample.js'); - textGrammar = atom.grammars.grammarForScopeName('text.plain') - expect(textGrammar).toBeTruthy() - jsGrammar = atom.grammars.grammarForScopeName('source.js') - expect(jsGrammar).toBeTruthy() - expect(editor.getGrammar()).toBe(jsGrammar) - }) + textGrammar = atom.grammars.grammarForScopeName('text.plain'); + expect(textGrammar).toBeTruthy(); + jsGrammar = atom.grammars.grammarForScopeName('source.js'); + expect(jsGrammar).toBeTruthy(); + expect(editor.getGrammar()).toBe(jsGrammar); + }); describe('when grammar-selector:show is triggered', () => it('displays a list of all the available grammars', async () => { - atom.commands.dispatch(editor.getElement(), 'grammar-selector:show') - await SelectListView.getScheduler().getNextUpdatePromise() + atom.commands.dispatch(editor.getElement(), 'grammar-selector:show'); + await SelectListView.getScheduler().getNextUpdatePromise(); - const grammarView = atom.workspace.getModalPanels()[0].getItem().element + const grammarView = atom.workspace.getModalPanels()[0].getItem().element; // TODO: Remove once Atom 1.23 reaches stable if (parseFloat(atom.getVersion()) >= 1.23) { // Do not take into account the two JS regex grammars or language-with-no-name expect(grammarView.querySelectorAll('li').length).toBe( atom.grammars.grammars.length - 3 - ) + ); } else { expect(grammarView.querySelectorAll('li').length).toBe( atom.grammars.grammars.length - 1 - ) + ); } expect(grammarView.querySelectorAll('li')[0].textContent).toBe( 'Auto Detect' - ) - expect(grammarView.textContent.includes('source.a')).toBe(false) + ); + expect(grammarView.textContent.includes('source.a')).toBe(false); grammarView .querySelectorAll('li') .forEach(li => expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name) - ) - })) + ); + })); describe('when a grammar is selected', () => it('sets the new grammar on the editor', async () => { - atom.commands.dispatch(editor.getElement(), 'grammar-selector:show') - await SelectListView.getScheduler().getNextUpdatePromise() + atom.commands.dispatch(editor.getElement(), 'grammar-selector:show'); + await SelectListView.getScheduler().getNextUpdatePromise(); - const grammarView = atom.workspace.getModalPanels()[0].getItem() - grammarView.props.didConfirmSelection(textGrammar) - expect(editor.getGrammar()).toBe(textGrammar) - })) + const grammarView = atom.workspace.getModalPanels()[0].getItem(); + grammarView.props.didConfirmSelection(textGrammar); + expect(editor.getGrammar()).toBe(textGrammar); + })); describe('when auto-detect is selected', () => it('restores the auto-detected grammar on the editor', async () => { - atom.commands.dispatch(editor.getElement(), 'grammar-selector:show') - await SelectListView.getScheduler().getNextUpdatePromise() + atom.commands.dispatch(editor.getElement(), 'grammar-selector:show'); + await SelectListView.getScheduler().getNextUpdatePromise(); - let grammarView = atom.workspace.getModalPanels()[0].getItem() - grammarView.props.didConfirmSelection(textGrammar) - expect(editor.getGrammar()).toBe(textGrammar) + let grammarView = atom.workspace.getModalPanels()[0].getItem(); + grammarView.props.didConfirmSelection(textGrammar); + expect(editor.getGrammar()).toBe(textGrammar); - atom.commands.dispatch(editor.getElement(), 'grammar-selector:show') - await SelectListView.getScheduler().getNextUpdatePromise() + atom.commands.dispatch(editor.getElement(), 'grammar-selector:show'); + await SelectListView.getScheduler().getNextUpdatePromise(); - grammarView = atom.workspace.getModalPanels()[0].getItem() - grammarView.props.didConfirmSelection(grammarView.items[0]) - expect(editor.getGrammar()).toBe(jsGrammar) - })) + grammarView = atom.workspace.getModalPanels()[0].getItem(); + grammarView.props.didConfirmSelection(grammarView.items[0]); + expect(editor.getGrammar()).toBe(jsGrammar); + })); describe("when the editor's current grammar is the null grammar", () => it('displays Auto Detect as the selected grammar', async () => { - editor.setGrammar(atom.grammars.nullGrammar) - atom.commands.dispatch(editor.getElement(), 'grammar-selector:show') - await SelectListView.getScheduler().getNextUpdatePromise() + editor.setGrammar(atom.grammars.nullGrammar); + atom.commands.dispatch(editor.getElement(), 'grammar-selector:show'); + await SelectListView.getScheduler().getNextUpdatePromise(); - const grammarView = atom.workspace.getModalPanels()[0].getItem().element + const grammarView = atom.workspace.getModalPanels()[0].getItem().element; expect(grammarView.querySelector('li.active').textContent).toBe( 'Auto Detect' - ) - })) + ); + })); describe('when editor is untitled', () => it('sets the new grammar on the editor', async () => { - editor = await atom.workspace.open() - expect(editor.getGrammar()).not.toBe(jsGrammar) + editor = await atom.workspace.open(); + expect(editor.getGrammar()).not.toBe(jsGrammar); - atom.commands.dispatch(editor.getElement(), 'grammar-selector:show') - await SelectListView.getScheduler().getNextUpdatePromise() + atom.commands.dispatch(editor.getElement(), 'grammar-selector:show'); + await SelectListView.getScheduler().getNextUpdatePromise(); - const grammarView = atom.workspace.getModalPanels()[0].getItem() - grammarView.props.didConfirmSelection(jsGrammar) - expect(editor.getGrammar()).toBe(jsGrammar) - })) + const grammarView = atom.workspace.getModalPanels()[0].getItem(); + grammarView.props.didConfirmSelection(jsGrammar); + expect(editor.getGrammar()).toBe(jsGrammar); + })); describe('Status bar grammar label', () => { - let [grammarStatus, grammarTile, statusBar] = [] + let [grammarStatus, grammarTile, statusBar] = []; beforeEach(async () => { - statusBar = document.querySelector('status-bar') - ;[grammarTile] = statusBar.getLeftTiles().slice(-1) - grammarStatus = grammarTile.getItem() + statusBar = document.querySelector('status-bar'); + [grammarTile] = statusBar.getLeftTiles().slice(-1); + grammarStatus = grammarTile.getItem(); // Wait for status bar service hook to fire while (!grammarStatus || !grammarStatus.textContent) { - await atom.views.getNextUpdatePromise() - grammarStatus = document.querySelector('.grammar-status') + await atom.views.getNextUpdatePromise(); + grammarStatus = document.querySelector('.grammar-status'); } - }) + }); it('displays the name of the current grammar', () => { - expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript') + expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript'); expect(getTooltipText(grammarStatus)).toBe( 'File uses the JavaScript grammar' - ) - }) + ); + }); it('displays Plain Text when the current grammar is the null grammar', async () => { - editor.setGrammar(atom.grammars.nullGrammar) - await atom.views.getNextUpdatePromise() + editor.setGrammar(atom.grammars.nullGrammar); + await atom.views.getNextUpdatePromise(); - expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text') - expect(grammarStatus).toBeVisible() + expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text'); + expect(grammarStatus).toBeVisible(); expect(getTooltipText(grammarStatus)).toBe( 'File uses the Plain Text grammar' - ) + ); - editor.setGrammar(atom.grammars.grammarForScopeName('source.js')) - await atom.views.getNextUpdatePromise() + editor.setGrammar(atom.grammars.grammarForScopeName('source.js')); + await atom.views.getNextUpdatePromise(); - expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript') - expect(grammarStatus).toBeVisible() - }) + expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript'); + expect(grammarStatus).toBeVisible(); + }); it('hides the label when the current grammar is null', async () => { - jasmine.attachToDOM(editor.getElement()) - spyOn(editor, 'getGrammar').andReturn(null) - editor.setGrammar(atom.grammars.nullGrammar) - await atom.views.getNextUpdatePromise() - expect(grammarStatus.offsetHeight).toBe(0) - }) + jasmine.attachToDOM(editor.getElement()); + spyOn(editor, 'getGrammar').andReturn(null); + editor.setGrammar(atom.grammars.nullGrammar); + await atom.views.getNextUpdatePromise(); + expect(grammarStatus.offsetHeight).toBe(0); + }); describe('when the grammar-selector.showOnRightSideOfStatusBar setting changes', () => it('moves the item to the preferred side of the status bar', () => { expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain( grammarStatus - ) + ); expect( statusBar.getRightTiles().map(tile => tile.getItem()) - ).not.toContain(grammarStatus) + ).not.toContain(grammarStatus); - atom.config.set('grammar-selector.showOnRightSideOfStatusBar', true) + atom.config.set('grammar-selector.showOnRightSideOfStatusBar', true); expect( statusBar.getLeftTiles().map(tile => tile.getItem()) - ).not.toContain(grammarStatus) + ).not.toContain(grammarStatus); expect(statusBar.getRightTiles().map(tile => tile.getItem())).toContain( grammarStatus - ) + ); - atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false) + atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false); expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain( grammarStatus - ) + ); expect( statusBar.getRightTiles().map(tile => tile.getItem()) - ).not.toContain(grammarStatus) - })) + ).not.toContain(grammarStatus); + })); describe("when the editor's grammar changes", () => it('displays the new grammar of the editor', async () => { - editor.setGrammar(atom.grammars.grammarForScopeName('text.plain')) - await atom.views.getNextUpdatePromise() + editor.setGrammar(atom.grammars.grammarForScopeName('text.plain')); + await atom.views.getNextUpdatePromise(); - expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text') + expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text'); expect(getTooltipText(grammarStatus)).toBe( 'File uses the Plain Text grammar' - ) + ); - editor.setGrammar(atom.grammars.grammarForScopeName('source.a')) - await atom.views.getNextUpdatePromise() + editor.setGrammar(atom.grammars.grammarForScopeName('source.a')); + await atom.views.getNextUpdatePromise(); - expect(grammarStatus.querySelector('a').textContent).toBe('source.a') + expect(grammarStatus.querySelector('a').textContent).toBe('source.a'); expect(getTooltipText(grammarStatus)).toBe( 'File uses the source.a grammar' - ) - })) + ); + })); describe('when clicked', () => it('shows the grammar selector modal', () => { - const eventHandler = jasmine.createSpy('eventHandler') + const eventHandler = jasmine.createSpy('eventHandler'); atom.commands.add( editor.getElement(), 'grammar-selector:show', eventHandler - ) - grammarStatus.click() - expect(eventHandler).toHaveBeenCalled() - })) + ); + grammarStatus.click(); + expect(eventHandler).toHaveBeenCalled(); + })); describe('when the package is deactivated', () => it('removes the view', () => { - spyOn(grammarTile, 'destroy') - atom.packages.deactivatePackage('grammar-selector') - expect(grammarTile.destroy).toHaveBeenCalled() - })) - }) -}) + spyOn(grammarTile, 'destroy'); + atom.packages.deactivatePackage('grammar-selector'); + expect(grammarTile.destroy).toHaveBeenCalled(); + })); + }); +}); -function getTooltipText (element) { - const [tooltip] = atom.tooltips.findTooltips(element) - return tooltip.getTitle() +function getTooltipText(element) { + const [tooltip] = atom.tooltips.findTooltips(element); + return tooltip.getTitle(); } diff --git a/packages/incompatible-packages/lib/incompatible-packages-component.js b/packages/incompatible-packages/lib/incompatible-packages-component.js index 478e9682d..1172a3d52 100644 --- a/packages/incompatible-packages/lib/incompatible-packages-component.js +++ b/packages/incompatible-packages/lib/incompatible-packages-component.js @@ -1,239 +1,239 @@ /** @babel */ /** @jsx etch.dom */ -import etch from 'etch' +import etch from 'etch'; -import VIEW_URI from './view-uri' -const REBUILDING = 'rebuilding' -const REBUILD_FAILED = 'rebuild-failed' -const REBUILD_SUCCEEDED = 'rebuild-succeeded' +import VIEW_URI from './view-uri'; +const REBUILDING = 'rebuilding'; +const REBUILD_FAILED = 'rebuild-failed'; +const REBUILD_SUCCEEDED = 'rebuild-succeeded'; export default class IncompatiblePackagesComponent { - constructor (packageManager) { - this.rebuildStatuses = new Map() - this.rebuildFailureOutputs = new Map() - this.rebuildInProgress = false - this.rebuiltPackageCount = 0 - this.packageManager = packageManager - this.loaded = false - etch.initialize(this) + constructor(packageManager) { + this.rebuildStatuses = new Map(); + this.rebuildFailureOutputs = new Map(); + this.rebuildInProgress = false; + this.rebuiltPackageCount = 0; + this.packageManager = packageManager; + this.loaded = false; + etch.initialize(this); if (this.packageManager.getActivePackages().length > 0) { - this.populateIncompatiblePackages() + this.populateIncompatiblePackages(); } else { - global.setImmediate(this.populateIncompatiblePackages.bind(this)) + global.setImmediate(this.populateIncompatiblePackages.bind(this)); } this.element.addEventListener('click', event => { if (event.target === this.refs.rebuildButton) { - this.rebuildIncompatiblePackages() + this.rebuildIncompatiblePackages(); } else if (event.target === this.refs.reloadButton) { - atom.reload() + atom.reload(); } else if (event.target.classList.contains('view-settings')) { atom.workspace.open( `atom://config/packages/${event.target.package.name}` - ) + ); } - }) + }); } - update () {} + update() {} - render () { + render() { if (!this.loaded) { - return
          Loading...
          + return
          Loading...
          ; } return (
          {this.renderHeading()} {this.renderIncompatiblePackageList()}
          - ) + ); } - renderHeading () { + renderHeading() { if (this.incompatiblePackages.length > 0) { if (this.rebuiltPackageCount > 0) { let alertClass = this.rebuiltPackageCount === this.incompatiblePackages.length ? 'alert-success icon-check' - : 'alert-warning icon-bug' + : 'alert-warning icon-bug'; return (
          {this.rebuiltPackageCount} of {this.incompatiblePackages.length}{' '} packages were rebuilt successfully. Reload Atom to activate them. -
          - ) + ); } else { return ( -
          +
          Some installed packages could not be loaded because they contain native modules that were compiled for an earlier version of Atom.
          - ) + ); } } else { return ( -
          +
          None of your packages contain incompatible native modules.
          - ) + ); } } - renderIncompatiblePackageList () { + renderIncompatiblePackageList() { return (
          {this.incompatiblePackages.map( this.renderIncompatiblePackage.bind(this) )}
          - ) + ); } - renderIncompatiblePackage (pack) { - let rebuildStatus = this.rebuildStatuses.get(pack) + renderIncompatiblePackage(pack) { + let rebuildStatus = this.rebuildStatuses.get(pack); return (
          {this.renderRebuildStatusIndicator(rebuildStatus)} -

          +

          {pack.name} {pack.metadata.version}

          {rebuildStatus ? this.renderRebuildOutput(pack) : this.renderIncompatibleModules(pack)}
          - ) + ); } - renderRebuildStatusIndicator (rebuildStatus) { + renderRebuildStatusIndicator(rebuildStatus) { if (rebuildStatus === REBUILDING) { return ( -
          +
          Rebuilding
          - ) + ); } else if (rebuildStatus === REBUILD_SUCCEEDED) { return ( -
          +
          Rebuild Succeeded
          - ) + ); } else if (rebuildStatus === REBUILD_FAILED) { return ( -
          +
          Rebuild Failed
          - ) + ); } else { - return '' + return ''; } } - renderRebuildOutput (pack) { + renderRebuildOutput(pack) { if (this.rebuildStatuses.get(pack) === REBUILD_FAILED) { - return
          {this.rebuildFailureOutputs.get(pack)}
          + return
          {this.rebuildFailureOutputs.get(pack)}
          ; } else { - return '' + return ''; } } - renderIncompatibleModules (pack) { + renderIncompatibleModules(pack) { return (
            {pack.incompatibleModules.map(nativeModule => (
          • -
            +
            {nativeModule.name}@{nativeModule.version || 'unknown'} –{' '} - {nativeModule.error} + {nativeModule.error}
          • ))}
          - ) + ); } - populateIncompatiblePackages () { + populateIncompatiblePackages() { this.incompatiblePackages = this.packageManager .getLoadedPackages() - .filter(pack => !pack.isCompatible()) + .filter(pack => !pack.isCompatible()); for (let pack of this.incompatiblePackages) { - let buildFailureOutput = pack.getBuildFailureOutput() + let buildFailureOutput = pack.getBuildFailureOutput(); if (buildFailureOutput) { - this.setPackageStatus(pack, REBUILD_FAILED) - this.setRebuildFailureOutput(pack, buildFailureOutput) + this.setPackageStatus(pack, REBUILD_FAILED); + this.setRebuildFailureOutput(pack, buildFailureOutput); } } - this.loaded = true - etch.update(this) + this.loaded = true; + etch.update(this); } - async rebuildIncompatiblePackages () { - this.rebuildInProgress = true - let rebuiltPackageCount = 0 + async rebuildIncompatiblePackages() { + this.rebuildInProgress = true; + let rebuiltPackageCount = 0; for (let pack of this.incompatiblePackages) { - this.setPackageStatus(pack, REBUILDING) - let { code, stderr } = await pack.rebuild() + this.setPackageStatus(pack, REBUILDING); + let { code, stderr } = await pack.rebuild(); if (code === 0) { - this.setPackageStatus(pack, REBUILD_SUCCEEDED) - rebuiltPackageCount++ + this.setPackageStatus(pack, REBUILD_SUCCEEDED); + rebuiltPackageCount++; } else { - this.setRebuildFailureOutput(pack, stderr) - this.setPackageStatus(pack, REBUILD_FAILED) + this.setRebuildFailureOutput(pack, stderr); + this.setPackageStatus(pack, REBUILD_FAILED); } } - this.rebuildInProgress = false - this.rebuiltPackageCount = rebuiltPackageCount - etch.update(this) + this.rebuildInProgress = false; + this.rebuiltPackageCount = rebuiltPackageCount; + etch.update(this); } - setPackageStatus (pack, status) { - this.rebuildStatuses.set(pack, status) - etch.update(this) + setPackageStatus(pack, status) { + this.rebuildStatuses.set(pack, status); + etch.update(this); } - setRebuildFailureOutput (pack, output) { - this.rebuildFailureOutputs.set(pack, output) - etch.update(this) + setRebuildFailureOutput(pack, output) { + this.rebuildFailureOutputs.set(pack, output); + etch.update(this); } - getTitle () { - return 'Incompatible Packages' + getTitle() { + return 'Incompatible Packages'; } - getURI () { - return VIEW_URI + getURI() { + return VIEW_URI; } - getIconName () { - return 'package' + getIconName() { + return 'package'; } - serialize () { - return { deserializer: 'IncompatiblePackagesComponent' } + serialize() { + return { deserializer: 'IncompatiblePackagesComponent' }; } } diff --git a/packages/incompatible-packages/lib/main.js b/packages/incompatible-packages/lib/main.js index f66549a7c..a4028437b 100644 --- a/packages/incompatible-packages/lib/main.js +++ b/packages/incompatible-packages/lib/main.js @@ -1,56 +1,56 @@ /** @babel */ -import { Disposable, CompositeDisposable } from 'atom' -import VIEW_URI from './view-uri' +import { Disposable, CompositeDisposable } from 'atom'; +import VIEW_URI from './view-uri'; -let disposables = null +let disposables = null; -export function activate () { - disposables = new CompositeDisposable() +export function activate() { + disposables = new CompositeDisposable(); disposables.add( atom.workspace.addOpener(uri => { if (uri === VIEW_URI) { - return deserializeIncompatiblePackagesComponent() + return deserializeIncompatiblePackagesComponent(); } }) - ) + ); disposables.add( atom.commands.add('atom-workspace', { 'incompatible-packages:view': () => { - atom.workspace.open(VIEW_URI) + atom.workspace.open(VIEW_URI); } }) - ) + ); } -export function deactivate () { - disposables.dispose() +export function deactivate() { + disposables.dispose(); } -export function consumeStatusBar (statusBar) { - let incompatibleCount = 0 +export function consumeStatusBar(statusBar) { + let incompatibleCount = 0; for (let pack of atom.packages.getLoadedPackages()) { - if (!pack.isCompatible()) incompatibleCount++ + if (!pack.isCompatible()) incompatibleCount++; } if (incompatibleCount > 0) { - let icon = createIcon(incompatibleCount) - let tile = statusBar.addRightTile({ item: icon, priority: 200 }) + let icon = createIcon(incompatibleCount); + let tile = statusBar.addRightTile({ item: icon, priority: 200 }); icon.element.addEventListener('click', () => { - atom.commands.dispatch(icon.element, 'incompatible-packages:view') - }) - disposables.add(new Disposable(() => tile.destroy())) + atom.commands.dispatch(icon.element, 'incompatible-packages:view'); + }); + disposables.add(new Disposable(() => tile.destroy())); } } -export function deserializeIncompatiblePackagesComponent () { - const IncompatiblePackagesComponent = require('./incompatible-packages-component') - return new IncompatiblePackagesComponent(atom.packages) +export function deserializeIncompatiblePackagesComponent() { + const IncompatiblePackagesComponent = require('./incompatible-packages-component'); + return new IncompatiblePackagesComponent(atom.packages); } -function createIcon (count) { - const StatusIconComponent = require('./status-icon-component') - return new StatusIconComponent({ count }) +function createIcon(count) { + const StatusIconComponent = require('./status-icon-component'); + return new StatusIconComponent({ count }); } diff --git a/packages/incompatible-packages/lib/status-icon-component.js b/packages/incompatible-packages/lib/status-icon-component.js index 21cc2a742..7fc68ceb5 100644 --- a/packages/incompatible-packages/lib/status-icon-component.js +++ b/packages/incompatible-packages/lib/status-icon-component.js @@ -1,22 +1,22 @@ /** @babel */ /** @jsx etch.dom */ -import etch from 'etch' +import etch from 'etch'; export default class StatusIconComponent { - constructor ({ count }) { - this.count = count - etch.initialize(this) + constructor({ count }) { + this.count = count; + etch.initialize(this); } - update () {} + update() {} - render () { + render() { return ( -
          - - {this.count} +
          + + {this.count}
          - ) + ); } } diff --git a/packages/incompatible-packages/lib/view-uri.js b/packages/incompatible-packages/lib/view-uri.js index de66f3ac6..25f6e5f77 100644 --- a/packages/incompatible-packages/lib/view-uri.js +++ b/packages/incompatible-packages/lib/view-uri.js @@ -1,3 +1,3 @@ /** @babel */ -export default 'atom://incompatible-packages' +export default 'atom://incompatible-packages'; diff --git a/packages/incompatible-packages/spec/incompatible-packages-component-spec.js b/packages/incompatible-packages/spec/incompatible-packages-component-spec.js index 1039cabe5..a621b5626 100644 --- a/packages/incompatible-packages/spec/incompatible-packages-component-spec.js +++ b/packages/incompatible-packages/spec/incompatible-packages-component-spec.js @@ -1,25 +1,25 @@ /** @babel */ -import etch from 'etch' -import IncompatiblePackagesComponent from '../lib/incompatible-packages-component' +import etch from 'etch'; +import IncompatiblePackagesComponent from '../lib/incompatible-packages-component'; describe('IncompatiblePackagesComponent', () => { - let packages, etchScheduler + let packages, etchScheduler; beforeEach(() => { - etchScheduler = etch.getScheduler() + etchScheduler = etch.getScheduler(); packages = [ { name: 'incompatible-1', - isCompatible () { - return false + isCompatible() { + return false; }, - rebuild: function () { - return new Promise(resolve => (this.resolveRebuild = resolve)) + rebuild: function() { + return new Promise(resolve => (this.resolveRebuild = resolve)); }, - getBuildFailureOutput () { - return null + getBuildFailureOutput() { + return null; }, path: '/Users/joe/.atom/packages/incompatible-1', metadata: { @@ -33,14 +33,14 @@ describe('IncompatiblePackagesComponent', () => { }, { name: 'incompatible-2', - isCompatible () { - return false + isCompatible() { + return false; }, - rebuild () { - return new Promise(resolve => (this.resolveRebuild = resolve)) + rebuild() { + return new Promise(resolve => (this.resolveRebuild = resolve)); }, - getBuildFailureOutput () { - return null + getBuildFailureOutput() { + return null; }, path: '/Users/joe/.atom/packages/incompatible-2', metadata: { @@ -53,14 +53,14 @@ describe('IncompatiblePackagesComponent', () => { }, { name: 'compatible', - isCompatible () { - return true + isCompatible() { + return true; }, - rebuild () { - throw new Error('Should not rebuild a compatible package') + rebuild() { + throw new Error('Should not rebuild a compatible package'); }, - getBuildFailureOutput () { - return null + getBuildFailureOutput() { + return null; }, path: '/Users/joe/.atom/packages/b', metadata: { @@ -69,8 +69,8 @@ describe('IncompatiblePackagesComponent', () => { }, incompatibleModules: [] } - ] - }) + ]; + }); describe('when packages have not finished loading', () => { it('delays rendering incompatible packages until the end of the tick', () => { @@ -78,69 +78,71 @@ describe('IncompatiblePackagesComponent', () => { let component = new IncompatiblePackagesComponent({ getActivePackages: () => [], getLoadedPackages: () => packages - }) - let { element } = component + }); + let { element } = component; expect( element.querySelectorAll('.incompatible-package').length - ).toEqual(0) + ).toEqual(0); - await etchScheduler.getNextUpdatePromise() + await etchScheduler.getNextUpdatePromise(); expect( element.querySelectorAll('.incompatible-package').length - ).toBeGreaterThan(0) - }) - }) - }) + ).toBeGreaterThan(0); + }); + }); + }); describe('when there are no incompatible packages', () => { it('does not render incompatible packages or the rebuild button', () => { waitsForPromise(async () => { - expect(packages[2].isCompatible()).toBe(true) - let compatiblePackages = [packages[2]] + expect(packages[2].isCompatible()).toBe(true); + let compatiblePackages = [packages[2]]; let component = new IncompatiblePackagesComponent({ getActivePackages: () => compatiblePackages, getLoadedPackages: () => compatiblePackages - }) - let { element } = component + }); + let { element } = component; - await etchScheduler.getNextUpdatePromise() + await etchScheduler.getNextUpdatePromise(); - expect(element.querySelectorAll('.incompatible-package').length).toBe(0) - expect(element.querySelector('button')).toBeNull() - }) - }) - }) + expect(element.querySelectorAll('.incompatible-package').length).toBe( + 0 + ); + expect(element.querySelector('button')).toBeNull(); + }); + }); + }); describe('when some packages previously failed to rebuild', () => { it('renders them with failed build status and error output', () => { waitsForPromise(async () => { - packages[1].getBuildFailureOutput = function () { - return 'The build failed' - } + packages[1].getBuildFailureOutput = function() { + return 'The build failed'; + }; let component = new IncompatiblePackagesComponent({ getActivePackages: () => packages, getLoadedPackages: () => packages - }) - let { element } = component + }); + let { element } = component; - await etchScheduler.getNextUpdatePromise() + await etchScheduler.getNextUpdatePromise(); let packageElement = element.querySelector( '.incompatible-package:nth-child(2)' - ) + ); expect(packageElement.querySelector('.badge').textContent).toBe( 'Rebuild Failed' - ) + ); expect(packageElement.querySelector('pre').textContent).toBe( 'The build failed' - ) - }) - }) - }) + ); + }); + }); + }); describe('when there are incompatible packages', () => { it('renders incompatible packages and the rebuild button', () => { @@ -148,17 +150,17 @@ describe('IncompatiblePackagesComponent', () => { let component = new IncompatiblePackagesComponent({ getActivePackages: () => packages, getLoadedPackages: () => packages - }) - let { element } = component + }); + let { element } = component; - await etchScheduler.getNextUpdatePromise() + await etchScheduler.getNextUpdatePromise(); expect( element.querySelectorAll('.incompatible-package').length - ).toEqual(2) - expect(element.querySelector('button')).not.toBeNull() - }) - }) + ).toEqual(2); + expect(element.querySelector('button')).not.toBeNull(); + }); + }); describe('when the "Rebuild All" button is clicked', () => { it("rebuilds every incompatible package, updating each package's view with status", () => { @@ -166,94 +168,94 @@ describe('IncompatiblePackagesComponent', () => { let component = new IncompatiblePackagesComponent({ getActivePackages: () => packages, getLoadedPackages: () => packages - }) - let { element } = component - jasmine.attachToDOM(element) + }); + let { element } = component; + jasmine.attachToDOM(element); - await etchScheduler.getNextUpdatePromise() + await etchScheduler.getNextUpdatePromise(); component.refs.rebuildButton.dispatchEvent( new CustomEvent('click', { bubbles: true }) - ) - await etchScheduler.getNextUpdatePromise() // view update + ); + await etchScheduler.getNextUpdatePromise(); // view update - expect(component.refs.rebuildButton.disabled).toBe(true) + expect(component.refs.rebuildButton.disabled).toBe(true); - expect(packages[0].resolveRebuild).toBeDefined() + expect(packages[0].resolveRebuild).toBeDefined(); expect( element.querySelector('.incompatible-package:nth-child(1) .badge') .textContent - ).toBe('Rebuilding') + ).toBe('Rebuilding'); expect( element.querySelector('.incompatible-package:nth-child(2) .badge') - ).toBeNull() + ).toBeNull(); - packages[0].resolveRebuild({ code: 0 }) // simulate rebuild success - await etchScheduler.getNextUpdatePromise() // view update + packages[0].resolveRebuild({ code: 0 }); // simulate rebuild success + await etchScheduler.getNextUpdatePromise(); // view update - expect(packages[1].resolveRebuild).toBeDefined() + expect(packages[1].resolveRebuild).toBeDefined(); expect( element.querySelector('.incompatible-package:nth-child(1) .badge') .textContent - ).toBe('Rebuild Succeeded') + ).toBe('Rebuild Succeeded'); expect( element.querySelector('.incompatible-package:nth-child(2) .badge') .textContent - ).toBe('Rebuilding') + ).toBe('Rebuilding'); packages[1].resolveRebuild({ code: 12, stderr: 'This is an error from the test!' - }) // simulate rebuild failure - await etchScheduler.getNextUpdatePromise() // view update + }); // simulate rebuild failure + await etchScheduler.getNextUpdatePromise(); // view update expect( element.querySelector('.incompatible-package:nth-child(1) .badge') .textContent - ).toBe('Rebuild Succeeded') + ).toBe('Rebuild Succeeded'); expect( element.querySelector('.incompatible-package:nth-child(2) .badge') .textContent - ).toBe('Rebuild Failed') + ).toBe('Rebuild Failed'); expect( element.querySelector('.incompatible-package:nth-child(2) pre') .textContent - ).toBe('This is an error from the test!') - }) - }) + ).toBe('This is an error from the test!'); + }); + }); it('displays a prompt to reload Atom when the packages finish rebuilding', () => { waitsForPromise(async () => { let component = new IncompatiblePackagesComponent({ getActivePackages: () => packages, getLoadedPackages: () => packages - }) - let { element } = component - jasmine.attachToDOM(element) - await etchScheduler.getNextUpdatePromise() // view update + }); + let { element } = component; + jasmine.attachToDOM(element); + await etchScheduler.getNextUpdatePromise(); // view update component.refs.rebuildButton.dispatchEvent( new CustomEvent('click', { bubbles: true }) - ) - expect(packages[0].resolveRebuild({ code: 0 })) - await new Promise(global.setImmediate) - expect(packages[1].resolveRebuild({ code: 0 })) + ); + expect(packages[0].resolveRebuild({ code: 0 })); + await new Promise(global.setImmediate); + expect(packages[1].resolveRebuild({ code: 0 })); - await etchScheduler.getNextUpdatePromise() // view update + await etchScheduler.getNextUpdatePromise(); // view update - expect(component.refs.reloadButton).toBeDefined() - expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/) + expect(component.refs.reloadButton).toBeDefined(); + expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/); - spyOn(atom, 'reload') + spyOn(atom, 'reload'); component.refs.reloadButton.dispatchEvent( new CustomEvent('click', { bubbles: true }) - ) - expect(atom.reload).toHaveBeenCalled() - }) - }) - }) + ); + expect(atom.reload).toHaveBeenCalled(); + }); + }); + }); describe('when the "Package Settings" button is clicked', () => { it('opens the settings panel for the package', () => { @@ -261,21 +263,21 @@ describe('IncompatiblePackagesComponent', () => { let component = new IncompatiblePackagesComponent({ getActivePackages: () => packages, getLoadedPackages: () => packages - }) - let { element } = component - jasmine.attachToDOM(element) + }); + let { element } = component; + jasmine.attachToDOM(element); - await etchScheduler.getNextUpdatePromise() + await etchScheduler.getNextUpdatePromise(); - spyOn(atom.workspace, 'open') + spyOn(atom.workspace, 'open'); element .querySelector('.incompatible-package:nth-child(2) button') - .dispatchEvent(new CustomEvent('click', { bubbles: true })) + .dispatchEvent(new CustomEvent('click', { bubbles: true })); expect(atom.workspace.open).toHaveBeenCalledWith( 'atom://config/packages/incompatible-2' - ) - }) - }) - }) - }) -}) + ); + }); + }); + }); + }); +}); diff --git a/packages/incompatible-packages/spec/incompatible-packages-spec.js b/packages/incompatible-packages/spec/incompatible-packages-spec.js index c4e3587d8..f5b111c7b 100644 --- a/packages/incompatible-packages/spec/incompatible-packages-spec.js +++ b/packages/incompatible-packages/spec/incompatible-packages-spec.js @@ -1,81 +1,83 @@ /** @babel */ -import path from 'path' -import IncompatiblePackagesComponent from '../lib/incompatible-packages-component' -import StatusIconComponent from '../lib/status-icon-component' +import path from 'path'; +import IncompatiblePackagesComponent from '../lib/incompatible-packages-component'; +import StatusIconComponent from '../lib/status-icon-component'; // This exists only so that CI passes on both Atom 1.6 and Atom 1.8+. -function findStatusBar () { +function findStatusBar() { if (typeof atom.workspace.getFooterPanels === 'function') { - const footerPanels = atom.workspace.getFooterPanels() + const footerPanels = atom.workspace.getFooterPanels(); if (footerPanels.length > 0) { - return footerPanels[0].getItem() + return footerPanels[0].getItem(); } } - return atom.workspace.getBottomPanels()[0].getItem() + return atom.workspace.getBottomPanels()[0].getItem(); } describe('Incompatible packages', () => { - let statusBar + let statusBar; beforeEach(() => { - atom.views.getView(atom.workspace) + atom.views.getView(atom.workspace); - waitsForPromise(() => atom.packages.activatePackage('status-bar')) + waitsForPromise(() => atom.packages.activatePackage('status-bar')); runs(() => { - statusBar = findStatusBar() - }) - }) + statusBar = findStatusBar(); + }); + }); describe('when there are packages with incompatible native modules', () => { beforeEach(() => { let incompatiblePackage = atom.packages.loadPackage( path.join(__dirname, 'fixtures', 'incompatible-package') - ) - spyOn(incompatiblePackage, 'isCompatible').andReturn(false) - incompatiblePackage.incompatibleModules = [] + ); + spyOn(incompatiblePackage, 'isCompatible').andReturn(false); + incompatiblePackage.incompatibleModules = []; waitsForPromise(() => atom.packages.activatePackage('incompatible-packages') - ) + ); - waits(1) - }) + waits(1); + }); it('adds an icon to the status bar', () => { - let statusBarIcon = statusBar.getRightTiles()[0].getItem() - expect(statusBarIcon.constructor).toBe(StatusIconComponent) - }) + let statusBarIcon = statusBar.getRightTiles()[0].getItem(); + expect(statusBarIcon.constructor).toBe(StatusIconComponent); + }); describe('clicking the icon', () => { it('displays the incompatible packages view in a pane', () => { - let statusBarIcon = statusBar.getRightTiles()[0].getItem() - statusBarIcon.element.dispatchEvent(new MouseEvent('click')) + let statusBarIcon = statusBar.getRightTiles()[0].getItem(); + statusBarIcon.element.dispatchEvent(new MouseEvent('click')); - let activePaneItem - waitsFor(() => (activePaneItem = atom.workspace.getActivePaneItem())) + let activePaneItem; + waitsFor(() => (activePaneItem = atom.workspace.getActivePaneItem())); runs(() => { - expect(activePaneItem.constructor).toBe(IncompatiblePackagesComponent) - }) - }) - }) - }) + expect(activePaneItem.constructor).toBe( + IncompatiblePackagesComponent + ); + }); + }); + }); + }); describe('when there are no packages with incompatible native modules', () => { beforeEach(() => { waitsForPromise(() => atom.packages.activatePackage('incompatible-packages') - ) - }) + ); + }); it('does not add an icon to the status bar', () => { let statusBarItemClasses = statusBar .getRightTiles() - .map(tile => tile.getItem().className) + .map(tile => tile.getItem().className); - expect(statusBarItemClasses).not.toContain('incompatible-packages') - }) - }) -}) + expect(statusBarItemClasses).not.toContain('incompatible-packages'); + }); + }); +}); diff --git a/packages/line-ending-selector/lib/helpers.js b/packages/line-ending-selector/lib/helpers.js index 3d1802c01..982be0c75 100644 --- a/packages/line-ending-selector/lib/helpers.js +++ b/packages/line-ending-selector/lib/helpers.js @@ -1,7 +1,7 @@ -'use babel' +'use babel'; export default { - getProcessPlatform () { - return process.platform + getProcessPlatform() { + return process.platform; } -} +}; diff --git a/packages/line-ending-selector/lib/main.js b/packages/line-ending-selector/lib/main.js index c3e07ff05..906adfcdd 100644 --- a/packages/line-ending-selector/lib/main.js +++ b/packages/line-ending-selector/lib/main.js @@ -1,26 +1,26 @@ -'use babel' +'use babel'; -import _ from 'underscore-plus' -import { CompositeDisposable, Disposable } from 'atom' -import SelectListView from 'atom-select-list' -import StatusBarItem from './status-bar-item' -import helpers from './helpers' +import _ from 'underscore-plus'; +import { CompositeDisposable, Disposable } from 'atom'; +import SelectListView from 'atom-select-list'; +import StatusBarItem from './status-bar-item'; +import helpers from './helpers'; -const LineEndingRegExp = /\r\n|\n/g +const LineEndingRegExp = /\r\n|\n/g; // the following regular expression is executed natively via the `substring` package, // where `\A` corresponds to the beginning of the string. // More info: https://github.com/atom/line-ending-selector/pull/56 // eslint-disable-next-line no-useless-escape -const LFRegExp = /(\A|[^\r])\n/g -const CRLFRegExp = /\r\n/g +const LFRegExp = /(\A|[^\r])\n/g; +const CRLFRegExp = /\r\n/g; -let disposables = null -let modalPanel = null -let lineEndingListView = null +let disposables = null; +let modalPanel = null; +let lineEndingListView = null; -export function activate () { - disposables = new CompositeDisposable() +export function activate() { + disposables = new CompositeDisposable(); disposables.add( atom.commands.add('atom-text-editor', { @@ -36,161 +36,161 @@ export function activate () { setLineEnding( atom.workspace.getActiveTextEditor(), lineEnding.value - ) - modalPanel.hide() + ); + modalPanel.hide(); }, didCancelSelection: () => { - modalPanel.hide() + modalPanel.hide(); }, elementForItem: lineEnding => { - const element = document.createElement('li') - element.textContent = lineEnding.name - return element + const element = document.createElement('li'); + element.textContent = lineEnding.name; + return element; } - }) + }); modalPanel = atom.workspace.addModalPanel({ item: lineEndingListView - }) + }); disposables.add( new Disposable(() => { - lineEndingListView.destroy() - modalPanel.destroy() - modalPanel = null + lineEndingListView.destroy(); + modalPanel.destroy(); + modalPanel = null; }) - ) + ); } - lineEndingListView.reset() - modalPanel.show() - lineEndingListView.focus() + lineEndingListView.reset(); + modalPanel.show(); + lineEndingListView.focus(); }, 'line-ending-selector:convert-to-LF': event => { - const editorElement = event.target.closest('atom-text-editor') - setLineEnding(editorElement.getModel(), '\n') + const editorElement = event.target.closest('atom-text-editor'); + setLineEnding(editorElement.getModel(), '\n'); }, 'line-ending-selector:convert-to-CRLF': event => { - const editorElement = event.target.closest('atom-text-editor') - setLineEnding(editorElement.getModel(), '\r\n') + const editorElement = event.target.closest('atom-text-editor'); + setLineEnding(editorElement.getModel(), '\r\n'); } }) - ) + ); } -export function deactivate () { - disposables.dispose() +export function deactivate() { + disposables.dispose(); } -export function consumeStatusBar (statusBar) { - let statusBarItem = new StatusBarItem() - let currentBufferDisposable = null - let tooltipDisposable = null +export function consumeStatusBar(statusBar) { + let statusBarItem = new StatusBarItem(); + let currentBufferDisposable = null; + let tooltipDisposable = null; const updateTile = _.debounce(buffer => { getLineEndings(buffer).then(lineEndings => { if (lineEndings.size === 0) { - let defaultLineEnding = getDefaultLineEnding() - buffer.setPreferredLineEnding(defaultLineEnding) - lineEndings = new Set().add(defaultLineEnding) + let defaultLineEnding = getDefaultLineEnding(); + buffer.setPreferredLineEnding(defaultLineEnding); + lineEndings = new Set().add(defaultLineEnding); } - statusBarItem.setLineEndings(lineEndings) - }) - }, 0) + statusBarItem.setLineEndings(lineEndings); + }); + }, 0); disposables.add( atom.workspace.observeActiveTextEditor(editor => { - if (currentBufferDisposable) currentBufferDisposable.dispose() + if (currentBufferDisposable) currentBufferDisposable.dispose(); if (editor && editor.getBuffer) { - let buffer = editor.getBuffer() - updateTile(buffer) + let buffer = editor.getBuffer(); + updateTile(buffer); currentBufferDisposable = buffer.onDidChange(({ oldText, newText }) => { if (!statusBarItem.hasLineEnding('\n')) { if (newText.indexOf('\n') >= 0) { - updateTile(buffer) + updateTile(buffer); } } else if (!statusBarItem.hasLineEnding('\r\n')) { if (newText.indexOf('\r\n') >= 0) { - updateTile(buffer) + updateTile(buffer); } } else if (oldText.indexOf('\n')) { - updateTile(buffer) + updateTile(buffer); } - }) + }); } else { - statusBarItem.setLineEndings(new Set()) - currentBufferDisposable = null + statusBarItem.setLineEndings(new Set()); + currentBufferDisposable = null; } if (tooltipDisposable) { - disposables.remove(tooltipDisposable) - tooltipDisposable.dispose() + disposables.remove(tooltipDisposable); + tooltipDisposable.dispose(); } tooltipDisposable = atom.tooltips.add(statusBarItem.element, { - title () { - return `File uses ${statusBarItem.description()} line endings` + title() { + return `File uses ${statusBarItem.description()} line endings`; } - }) - disposables.add(tooltipDisposable) + }); + disposables.add(tooltipDisposable); }) - ) + ); disposables.add( new Disposable(() => { - if (currentBufferDisposable) currentBufferDisposable.dispose() + if (currentBufferDisposable) currentBufferDisposable.dispose(); }) - ) + ); statusBarItem.onClick(() => { - const editor = atom.workspace.getActiveTextEditor() + const editor = atom.workspace.getActiveTextEditor(); atom.commands.dispatch( atom.views.getView(editor), 'line-ending-selector:show' - ) - }) + ); + }); - let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 }) - disposables.add(new Disposable(() => tile.destroy())) + let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 }); + disposables.add(new Disposable(() => tile.destroy())); } -function getDefaultLineEnding () { +function getDefaultLineEnding() { switch (atom.config.get('line-ending-selector.defaultLineEnding')) { case 'LF': - return '\n' + return '\n'; case 'CRLF': - return '\r\n' + return '\r\n'; case 'OS Default': default: - return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n' + return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n'; } } -function getLineEndings (buffer) { +function getLineEndings(buffer) { if (typeof buffer.find === 'function') { return Promise.all([buffer.find(LFRegExp), buffer.find(CRLFRegExp)]).then( ([hasLF, hasCRLF]) => { - const result = new Set() - if (hasLF) result.add('\n') - if (hasCRLF) result.add('\r\n') - return result + const result = new Set(); + if (hasLF) result.add('\n'); + if (hasCRLF) result.add('\r\n'); + return result; } - ) + ); } else { return new Promise(resolve => { - const result = new Set() + const result = new Set(); for (let i = 0; i < buffer.getLineCount() - 1; i++) { - result.add(buffer.lineEndingForRow(i)) + result.add(buffer.lineEndingForRow(i)); } - resolve(result) - }) + resolve(result); + }); } } -function setLineEnding (item, lineEnding) { +function setLineEnding(item, lineEnding) { if (item && item.getBuffer) { - let buffer = item.getBuffer() - buffer.setPreferredLineEnding(lineEnding) - buffer.setText(buffer.getText().replace(LineEndingRegExp, lineEnding)) + let buffer = item.getBuffer(); + buffer.setPreferredLineEnding(lineEnding); + buffer.setText(buffer.getText().replace(LineEndingRegExp, lineEnding)); } } diff --git a/packages/line-ending-selector/lib/status-bar-item.js b/packages/line-ending-selector/lib/status-bar-item.js index 1684bc370..02c297aff 100644 --- a/packages/line-ending-selector/lib/status-bar-item.js +++ b/packages/line-ending-selector/lib/status-bar-item.js @@ -1,57 +1,57 @@ -const { Emitter } = require('atom') +const { Emitter } = require('atom'); module.exports = class StatusBarItem { - constructor () { - this.element = document.createElement('a') - this.element.className = 'line-ending-tile inline-block' - this.emitter = new Emitter() - this.setLineEndings(new Set()) + constructor() { + this.element = document.createElement('a'); + this.element.className = 'line-ending-tile inline-block'; + this.emitter = new Emitter(); + this.setLineEndings(new Set()); } - setLineEndings (lineEndings) { - this.lineEndings = lineEndings - this.element.textContent = lineEndingName(lineEndings) - this.emitter.emit('did-change') + setLineEndings(lineEndings) { + this.lineEndings = lineEndings; + this.element.textContent = lineEndingName(lineEndings); + this.emitter.emit('did-change'); } - onDidChange (callback) { - return this.emitter.on('did-change', callback) + onDidChange(callback) { + return this.emitter.on('did-change', callback); } - hasLineEnding (lineEnding) { - return this.lineEndings.has(lineEnding) + hasLineEnding(lineEnding) { + return this.lineEndings.has(lineEnding); } - description () { - return lineEndingDescription(this.lineEndings) + description() { + return lineEndingDescription(this.lineEndings); } - onClick (callback) { - this.element.addEventListener('click', callback) + onClick(callback) { + this.element.addEventListener('click', callback); } -} +}; -function lineEndingName (lineEndings) { +function lineEndingName(lineEndings) { if (lineEndings.size > 1) { - return 'Mixed' + return 'Mixed'; } else if (lineEndings.has('\n')) { - return 'LF' + return 'LF'; } else if (lineEndings.has('\r\n')) { - return 'CRLF' + return 'CRLF'; } else { - return '' + return ''; } } -function lineEndingDescription (lineEndings) { +function lineEndingDescription(lineEndings) { switch (lineEndingName(lineEndings)) { case 'Mixed': - return 'mixed' + return 'mixed'; case 'LF': - return 'LF (Unix)' + return 'LF (Unix)'; case 'CRLF': - return 'CRLF (Windows)' + return 'CRLF (Windows)'; default: - return 'unknown' + return 'unknown'; } } diff --git a/packages/line-ending-selector/spec/line-ending-selector-spec.js b/packages/line-ending-selector/spec/line-ending-selector-spec.js index afc8f9db0..0bf3964fa 100644 --- a/packages/line-ending-selector/spec/line-ending-selector-spec.js +++ b/packages/line-ending-selector/spec/line-ending-selector-spec.js @@ -1,374 +1,378 @@ -const helpers = require('../lib/helpers') -const { TextEditor } = require('atom') +const helpers = require('../lib/helpers'); +const { TextEditor } = require('atom'); describe('line ending selector', () => { - let lineEndingTile + let lineEndingTile; beforeEach(() => { - jasmine.useRealClock() + jasmine.useRealClock(); waitsForPromise(() => { - return atom.packages.activatePackage('status-bar') - }) + return atom.packages.activatePackage('status-bar'); + }); waitsForPromise(() => { - return atom.packages.activatePackage('line-ending-selector') - }) + return atom.packages.activatePackage('line-ending-selector'); + }); - waits(1) + waits(1); runs(() => { - const statusBar = atom.workspace.getFooterPanels()[0].getItem() - lineEndingTile = statusBar.getRightTiles()[0].getItem() - expect(lineEndingTile.element.className).toMatch(/line-ending-tile/) - expect(lineEndingTile.element.textContent).toBe('') - }) - }) + const statusBar = atom.workspace.getFooterPanels()[0].getItem(); + lineEndingTile = statusBar.getRightTiles()[0].getItem(); + expect(lineEndingTile.element.className).toMatch(/line-ending-tile/); + expect(lineEndingTile.element.textContent).toBe(''); + }); + }); describe('Commands', () => { - let editor, editorElement + let editor, editorElement; beforeEach(() => { waitsForPromise(() => { return atom.workspace.open('mixed-endings.md').then(e => { - editor = e - editorElement = atom.views.getView(editor) - jasmine.attachToDOM(editorElement) - }) - }) - }) + editor = e; + editorElement = atom.views.getView(editor); + jasmine.attachToDOM(editorElement); + }); + }); + }); describe('When "line-ending-selector:convert-to-LF" is run', () => { it('converts the file to LF line endings', () => { - editorElement.focus() + editorElement.focus(); atom.commands.dispatch( document.activeElement, 'line-ending-selector:convert-to-LF' - ) - expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n') - }) - }) + ); + expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n'); + }); + }); describe('When "line-ending-selector:convert-to-LF" is run', () => { it('converts the file to CRLF line endings', () => { - editorElement.focus() + editorElement.focus(); atom.commands.dispatch( document.activeElement, 'line-ending-selector:convert-to-CRLF' - ) - expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n') - }) - }) - }) + ); + expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n'); + }); + }); + }); describe('Status bar tile', () => { describe('when an empty file is opened', () => { it('uses the default line endings for the platform', () => { waitsFor(done => { - spyOn(helpers, 'getProcessPlatform').andReturn('win32') + spyOn(helpers, 'getProcessPlatform').andReturn('win32'); atom.workspace.open('').then(editor => { const subscription = lineEndingTile.onDidChange(() => { - subscription.dispose() - expect(lineEndingTile.element.textContent).toBe('CRLF') - expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n') + subscription.dispose(); + expect(lineEndingTile.element.textContent).toBe('CRLF'); + expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n'); expect(getTooltipText(lineEndingTile.element)).toBe( 'File uses CRLF (Windows) line endings' - ) + ); - done() - }) - }) - }) + done(); + }); + }); + }); waitsFor(done => { - helpers.getProcessPlatform.andReturn('darwin') + helpers.getProcessPlatform.andReturn('darwin'); atom.workspace.open('').then(editor => { const subscription = lineEndingTile.onDidChange(() => { - subscription.dispose() - expect(lineEndingTile.element.textContent).toBe('LF') - expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n') + subscription.dispose(); + expect(lineEndingTile.element.textContent).toBe('LF'); + expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n'); expect(getTooltipText(lineEndingTile.element)).toBe( 'File uses LF (Unix) line endings' - ) + ); - done() - }) - }) - }) - }) + done(); + }); + }); + }); + }); describe('when the "defaultLineEnding" setting is set to "LF"', () => { beforeEach(() => { - atom.config.set('line-ending-selector.defaultLineEnding', 'LF') - }) + atom.config.set('line-ending-selector.defaultLineEnding', 'LF'); + }); it('uses LF line endings, regardless of the platform', () => { waitsFor(done => { - spyOn(helpers, 'getProcessPlatform').andReturn('win32') + spyOn(helpers, 'getProcessPlatform').andReturn('win32'); atom.workspace.open('').then(editor => { lineEndingTile.onDidChange(() => { - expect(lineEndingTile.element.textContent).toBe('LF') - expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n') - done() - }) - }) - }) - }) - }) + expect(lineEndingTile.element.textContent).toBe('LF'); + expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n'); + done(); + }); + }); + }); + }); + }); describe('when the "defaultLineEnding" setting is set to "CRLF"', () => { beforeEach(() => { - atom.config.set('line-ending-selector.defaultLineEnding', 'CRLF') - }) + atom.config.set('line-ending-selector.defaultLineEnding', 'CRLF'); + }); it('uses CRLF line endings, regardless of the platform', () => { waitsFor(done => { atom.workspace.open('').then(editor => { lineEndingTile.onDidChange(() => { - expect(lineEndingTile.element.textContent).toBe('CRLF') - expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n') - done() - }) - }) - }) - }) - }) - }) + expect(lineEndingTile.element.textContent).toBe('CRLF'); + expect(editor.getBuffer().getPreferredLineEnding()).toBe( + '\r\n' + ); + done(); + }); + }); + }); + }); + }); + }); describe('when a file is opened that contains only CRLF line endings', () => { it('displays "CRLF" as the line ending', () => { waitsFor(done => { atom.workspace.open('windows-endings.md').then(() => { lineEndingTile.onDidChange(() => { - expect(lineEndingTile.element.textContent).toBe('CRLF') - done() - }) - }) - }) - }) - }) + expect(lineEndingTile.element.textContent).toBe('CRLF'); + done(); + }); + }); + }); + }); + }); describe('when a file is opened that contains only LF line endings', () => { it('displays "LF" as the line ending', () => { waitsFor(done => { atom.workspace.open('unix-endings.md').then(editor => { lineEndingTile.onDidChange(() => { - expect(lineEndingTile.element.textContent).toBe('LF') - expect(editor.getBuffer().getPreferredLineEnding()).toBe(null) - done() - }) - }) - }) - }) - }) + expect(lineEndingTile.element.textContent).toBe('LF'); + expect(editor.getBuffer().getPreferredLineEnding()).toBe(null); + done(); + }); + }); + }); + }); + }); describe('when a file is opened that contains mixed line endings', () => { it('displays "Mixed" as the line ending', () => { waitsFor(done => { atom.workspace.open('mixed-endings.md').then(() => { lineEndingTile.onDidChange(() => { - expect(lineEndingTile.element.textContent).toBe('Mixed') - done() - }) - }) - }) - }) - }) + expect(lineEndingTile.element.textContent).toBe('Mixed'); + done(); + }); + }); + }); + }); + }); describe('clicking the tile', () => { - let lineEndingModal, lineEndingSelector + let lineEndingModal, lineEndingSelector; beforeEach(() => { - jasmine.attachToDOM(atom.views.getView(atom.workspace)) + jasmine.attachToDOM(atom.views.getView(atom.workspace)); waitsFor(done => atom.workspace .open('unix-endings.md') .then(() => lineEndingTile.onDidChange(done)) - ) - }) + ); + }); describe('when the text editor has focus', () => { it('opens the line ending selector modal for the text editor', () => { - atom.workspace.getCenter().activate() - const item = atom.workspace.getActivePaneItem() - expect(item.getFileName && item.getFileName()).toBe('unix-endings.md') + atom.workspace.getCenter().activate(); + const item = atom.workspace.getActivePaneItem(); + expect(item.getFileName && item.getFileName()).toBe( + 'unix-endings.md' + ); - lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) - lineEndingModal = atom.workspace.getModalPanels()[0] - lineEndingSelector = lineEndingModal.getItem() + lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})); + lineEndingModal = atom.workspace.getModalPanels()[0]; + lineEndingSelector = lineEndingModal.getItem(); - expect(lineEndingModal.isVisible()).toBe(true) + expect(lineEndingModal.isVisible()).toBe(true); expect( lineEndingSelector.element.contains(document.activeElement) - ).toBe(true) - let listItems = lineEndingSelector.element.querySelectorAll('li') - expect(listItems[0].textContent).toBe('LF') - expect(listItems[1].textContent).toBe('CRLF') - }) - }) + ).toBe(true); + let listItems = lineEndingSelector.element.querySelectorAll('li'); + expect(listItems[0].textContent).toBe('LF'); + expect(listItems[1].textContent).toBe('CRLF'); + }); + }); describe('when the text editor does not have focus', () => { it('opens the line ending selector modal for the active text editor', () => { - atom.workspace.getLeftDock().activate() - const item = atom.workspace.getActivePaneItem() - expect(item instanceof TextEditor).toBe(false) + atom.workspace.getLeftDock().activate(); + const item = atom.workspace.getActivePaneItem(); + expect(item instanceof TextEditor).toBe(false); - lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) - lineEndingModal = atom.workspace.getModalPanels()[0] - lineEndingSelector = lineEndingModal.getItem() + lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})); + lineEndingModal = atom.workspace.getModalPanels()[0]; + lineEndingSelector = lineEndingModal.getItem(); - expect(lineEndingModal.isVisible()).toBe(true) + expect(lineEndingModal.isVisible()).toBe(true); expect( lineEndingSelector.element.contains(document.activeElement) - ).toBe(true) - let listItems = lineEndingSelector.element.querySelectorAll('li') - expect(listItems[0].textContent).toBe('LF') - expect(listItems[1].textContent).toBe('CRLF') - }) - }) + ).toBe(true); + let listItems = lineEndingSelector.element.querySelectorAll('li'); + expect(listItems[0].textContent).toBe('LF'); + expect(listItems[1].textContent).toBe('CRLF'); + }); + }); describe('when selecting a different line ending for the file', () => { it('changes the line endings in the buffer', () => { - lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) - lineEndingModal = atom.workspace.getModalPanels()[0] - lineEndingSelector = lineEndingModal.getItem() + lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})); + lineEndingModal = atom.workspace.getModalPanels()[0]; + lineEndingSelector = lineEndingModal.getItem(); const lineEndingChangedPromise = new Promise(resolve => { lineEndingTile.onDidChange(() => { - expect(lineEndingTile.element.textContent).toBe('CRLF') - const editor = atom.workspace.getActiveTextEditor() - expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nUnix\r\n') - expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n') - resolve() - }) - }) + expect(lineEndingTile.element.textContent).toBe('CRLF'); + const editor = atom.workspace.getActiveTextEditor(); + expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nUnix\r\n'); + expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n'); + resolve(); + }); + }); - lineEndingSelector.refs.queryEditor.setText('CR') - lineEndingSelector.confirmSelection() - expect(lineEndingModal.isVisible()).toBe(false) + lineEndingSelector.refs.queryEditor.setText('CR'); + lineEndingSelector.confirmSelection(); + expect(lineEndingModal.isVisible()).toBe(false); - waitsForPromise(() => lineEndingChangedPromise) - }) - }) + waitsForPromise(() => lineEndingChangedPromise); + }); + }); describe('when modal is exited', () => { it('leaves the tile selection as-is', () => { - lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) - lineEndingModal = atom.workspace.getModalPanels()[0] - lineEndingSelector = lineEndingModal.getItem() + lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})); + lineEndingModal = atom.workspace.getModalPanels()[0]; + lineEndingSelector = lineEndingModal.getItem(); - lineEndingSelector.cancelSelection() - expect(lineEndingTile.element.textContent).toBe('LF') - }) - }) - }) + lineEndingSelector.cancelSelection(); + expect(lineEndingTile.element.textContent).toBe('LF'); + }); + }); + }); describe('closing the last text editor', () => { it('displays no line ending in the status bar', () => { waitsForPromise(() => { return atom.workspace.open('unix-endings.md').then(() => { - atom.workspace.getActivePane().destroy() - expect(lineEndingTile.element.textContent).toBe('') - }) - }) - }) - }) + atom.workspace.getActivePane().destroy(); + expect(lineEndingTile.element.textContent).toBe(''); + }); + }); + }); + }); describe("when the buffer's line endings change", () => { - let editor + let editor; beforeEach(() => { waitsFor(done => { atom.workspace.open('unix-endings.md').then(e => { - editor = e - lineEndingTile.onDidChange(done) - }) - }) - }) + editor = e; + lineEndingTile.onDidChange(done); + }); + }); + }); it('updates the line ending text in the tile', () => { - let tileText = lineEndingTile.element.textContent - let tileUpdateCount = 0 + let tileText = lineEndingTile.element.textContent; + let tileUpdateCount = 0; Object.defineProperty(lineEndingTile.element, 'textContent', { - get () { - return tileText + get() { + return tileText; }, - set (text) { - tileUpdateCount++ - tileText = text + set(text) { + tileUpdateCount++; + tileText = text; } - }) + }); - expect(lineEndingTile.element.textContent).toBe('LF') + expect(lineEndingTile.element.textContent).toBe('LF'); expect(getTooltipText(lineEndingTile.element)).toBe( 'File uses LF (Unix) line endings' - ) + ); waitsFor(done => { - editor.setTextInBufferRange([[0, 0], [0, 0]], '... ') + editor.setTextInBufferRange([[0, 0], [0, 0]], '... '); editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', { normalizeLineEndings: false - }) - lineEndingTile.onDidChange(done) - }) + }); + lineEndingTile.onDidChange(done); + }); runs(() => { - expect(tileUpdateCount).toBe(1) - expect(lineEndingTile.element.textContent).toBe('Mixed') + expect(tileUpdateCount).toBe(1); + expect(lineEndingTile.element.textContent).toBe('Mixed'); expect(getTooltipText(lineEndingTile.element)).toBe( 'File uses mixed line endings' - ) - }) + ); + }); waitsFor(done => { atom.commands.dispatch( editor.getElement(), 'line-ending-selector:convert-to-CRLF' - ) - lineEndingTile.onDidChange(done) - }) + ); + lineEndingTile.onDidChange(done); + }); runs(() => { - expect(tileUpdateCount).toBe(2) - expect(lineEndingTile.element.textContent).toBe('CRLF') + expect(tileUpdateCount).toBe(2); + expect(lineEndingTile.element.textContent).toBe('CRLF'); expect(getTooltipText(lineEndingTile.element)).toBe( 'File uses CRLF (Windows) line endings' - ) - }) + ); + }); waitsFor(done => { atom.commands.dispatch( editor.getElement(), 'line-ending-selector:convert-to-LF' - ) - lineEndingTile.onDidChange(done) - }) + ); + lineEndingTile.onDidChange(done); + }); runs(() => { - expect(tileUpdateCount).toBe(3) - expect(lineEndingTile.element.textContent).toBe('LF') - }) + expect(tileUpdateCount).toBe(3); + expect(lineEndingTile.element.textContent).toBe('LF'); + }); runs(() => { - editor.setTextInBufferRange([[0, 0], [0, 0]], '\n') - }) + editor.setTextInBufferRange([[0, 0], [0, 0]], '\n'); + }); - waits(100) + waits(100); runs(() => { - expect(tileUpdateCount).toBe(3) - }) - }) - }) - }) -}) + expect(tileUpdateCount).toBe(3); + }); + }); + }); + }); +}); -function getTooltipText (element) { - const [tooltip] = atom.tooltips.findTooltips(element) - return tooltip.getTitle() +function getTooltipText(element) { + const [tooltip] = atom.tooltips.findTooltips(element); + return tooltip.getTitle(); } diff --git a/packages/link/lib/link.js b/packages/link/lib/link.js index 005c9e614..4590456ab 100644 --- a/packages/link/lib/link.js +++ b/packages/link/lib/link.js @@ -1,64 +1,64 @@ -const url = require('url') -const { shell } = require('electron') -const _ = require('underscore-plus') +const url = require('url'); +const { shell } = require('electron'); +const _ = require('underscore-plus'); -const LINK_SCOPE_REGEX = /markup\.underline\.link/ +const LINK_SCOPE_REGEX = /markup\.underline\.link/; module.exports = { - activate () { + activate() { this.commandDisposable = atom.commands.add( 'atom-text-editor', 'link:open', () => this.openLink() - ) + ); }, - deactivate () { - this.commandDisposable.dispose() + deactivate() { + this.commandDisposable.dispose(); }, - openLink () { - const editor = atom.workspace.getActiveTextEditor() - if (editor == null) return + openLink() { + const editor = atom.workspace.getActiveTextEditor(); + if (editor == null) return; - let link = this.linkUnderCursor(editor) - if (link == null) return + let link = this.linkUnderCursor(editor); + if (link == null) return; if (editor.getGrammar().scopeName === 'source.gfm') { - link = this.linkForName(editor, link) + link = this.linkForName(editor, link); } - const { protocol } = url.parse(link) + const { protocol } = url.parse(link); if (protocol === 'http:' || protocol === 'https:' || protocol === 'atom:') { - shell.openExternal(link) + shell.openExternal(link); } }, // Get the link under the cursor in the editor // // Returns a {String} link or undefined if no link found. - linkUnderCursor (editor) { - const cursorPosition = editor.getCursorBufferPosition() - const link = this.linkAtPosition(editor, cursorPosition) - if (link != null) return link + linkUnderCursor(editor) { + const cursorPosition = editor.getCursorBufferPosition(); + const link = this.linkAtPosition(editor, cursorPosition); + if (link != null) return link; // Look for a link to the left of the cursor if (cursorPosition.column > 0) { - return this.linkAtPosition(editor, cursorPosition.translate([0, -1])) + return this.linkAtPosition(editor, cursorPosition.translate([0, -1])); } }, // Get the link at the buffer position in the editor. // // Returns a {String} link or undefined if no link found. - linkAtPosition (editor, bufferPosition) { - const token = editor.tokenForBufferPosition(bufferPosition) + linkAtPosition(editor, bufferPosition) { + const token = editor.tokenForBufferPosition(bufferPosition); if ( token && token.value && token.scopes.some(scope => LINK_SCOPE_REGEX.test(scope)) ) { - return token.value + return token.value; } }, @@ -73,20 +73,20 @@ module.exports = { // ``` // // Returns a {String} link - linkForName (editor, linkName) { - let link = linkName + linkForName(editor, linkName) { + let link = linkName; const regex = new RegExp( `^\\s*\\[${_.escapeRegExp(linkName)}\\]\\s*:\\s*(.+)$`, 'g' - ) + ); editor.backwardsScanInBufferRange( regex, [[0, 0], [Infinity, Infinity]], ({ match, stop }) => { - link = match[1] - stop() + link = match[1]; + stop(); } - ) - return link + ); + return link; } -} +}; diff --git a/packages/link/spec/link-spec.js b/packages/link/spec/link-spec.js index e31d3f6ca..e8a1bb4ce 100644 --- a/packages/link/spec/link-spec.js +++ b/packages/link/spec/link-spec.js @@ -1,136 +1,136 @@ -const { shell } = require('electron') +const { shell } = require('electron'); describe('link package', () => { beforeEach(async () => { - await atom.packages.activatePackage('language-gfm') - await atom.packages.activatePackage('language-hyperlink') + await atom.packages.activatePackage('language-gfm'); + await atom.packages.activatePackage('language-hyperlink'); - const activationPromise = atom.packages.activatePackage('link') - atom.commands.dispatch(atom.views.getView(atom.workspace), 'link:open') - await activationPromise - }) + const activationPromise = atom.packages.activatePackage('link'); + atom.commands.dispatch(atom.views.getView(atom.workspace), 'link:open'); + await activationPromise; + }); describe('when the cursor is on a link', () => { it("opens the link using the 'open' command", async () => { - await atom.workspace.open('sample.md') + await atom.workspace.open('sample.md'); - const editor = atom.workspace.getActiveTextEditor() - editor.setText('// "http://github.com"') + const editor = atom.workspace.getActiveTextEditor(); + editor.setText('// "http://github.com"'); - spyOn(shell, 'openExternal') - atom.commands.dispatch(atom.views.getView(editor), 'link:open') - expect(shell.openExternal).not.toHaveBeenCalled() + spyOn(shell, 'openExternal'); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); + expect(shell.openExternal).not.toHaveBeenCalled(); - editor.setCursorBufferPosition([0, 4]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + editor.setCursorBufferPosition([0, 4]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() - expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com') + expect(shell.openExternal).toHaveBeenCalled(); + expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com'); - shell.openExternal.reset() - editor.setCursorBufferPosition([0, 8]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + shell.openExternal.reset(); + editor.setCursorBufferPosition([0, 8]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() - expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com') + expect(shell.openExternal).toHaveBeenCalled(); + expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com'); - shell.openExternal.reset() - editor.setCursorBufferPosition([0, 21]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + shell.openExternal.reset(); + editor.setCursorBufferPosition([0, 21]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() - expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com') - }) + expect(shell.openExternal).toHaveBeenCalled(); + expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com'); + }); // only works in Atom >= 1.33.0 // https://github.com/atom/link/pull/33#issuecomment-419643655 - const atomVersion = atom.getVersion().split('.') - console.error('atomVersion', atomVersion) + const atomVersion = atom.getVersion().split('.'); + console.error('atomVersion', atomVersion); if (+atomVersion[0] > 1 || +atomVersion[1] >= 33) { it("opens an 'atom:' link", async () => { - await atom.workspace.open('sample.md') + await atom.workspace.open('sample.md'); - const editor = atom.workspace.getActiveTextEditor() + const editor = atom.workspace.getActiveTextEditor(); editor.setText( '// "atom://core/open/file?filename=sample.js&line=1&column=2"' - ) + ); - spyOn(shell, 'openExternal') - atom.commands.dispatch(atom.views.getView(editor), 'link:open') - expect(shell.openExternal).not.toHaveBeenCalled() + spyOn(shell, 'openExternal'); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); + expect(shell.openExternal).not.toHaveBeenCalled(); - editor.setCursorBufferPosition([0, 4]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + editor.setCursorBufferPosition([0, 4]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() + expect(shell.openExternal).toHaveBeenCalled(); expect(shell.openExternal.argsForCall[0][0]).toBe( 'atom://core/open/file?filename=sample.js&line=1&column=2' - ) + ); - shell.openExternal.reset() - editor.setCursorBufferPosition([0, 8]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + shell.openExternal.reset(); + editor.setCursorBufferPosition([0, 8]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() + expect(shell.openExternal).toHaveBeenCalled(); expect(shell.openExternal.argsForCall[0][0]).toBe( 'atom://core/open/file?filename=sample.js&line=1&column=2' - ) + ); - shell.openExternal.reset() - editor.setCursorBufferPosition([0, 60]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + shell.openExternal.reset(); + editor.setCursorBufferPosition([0, 60]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() + expect(shell.openExternal).toHaveBeenCalled(); expect(shell.openExternal.argsForCall[0][0]).toBe( 'atom://core/open/file?filename=sample.js&line=1&column=2' - ) - }) + ); + }); } describe('when the cursor is on a [name][url-name] style markdown link', () => it('opens the named url', async () => { - await atom.workspace.open('README.md') + await atom.workspace.open('README.md'); - const editor = atom.workspace.getActiveTextEditor() + const editor = atom.workspace.getActiveTextEditor(); editor.setText(`\ you should [click][here] you should not [click][her] [here]: http://github.com\ -`) +`); - spyOn(shell, 'openExternal') - editor.setCursorBufferPosition([0, 0]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') - expect(shell.openExternal).not.toHaveBeenCalled() + spyOn(shell, 'openExternal'); + editor.setCursorBufferPosition([0, 0]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); + expect(shell.openExternal).not.toHaveBeenCalled(); - editor.setCursorBufferPosition([0, 20]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + editor.setCursorBufferPosition([0, 20]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).toHaveBeenCalled() - expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com') + expect(shell.openExternal).toHaveBeenCalled(); + expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com'); - shell.openExternal.reset() - editor.setCursorBufferPosition([1, 24]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + shell.openExternal.reset(); + editor.setCursorBufferPosition([1, 24]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).not.toHaveBeenCalled() - })) + expect(shell.openExternal).not.toHaveBeenCalled(); + })); it('does not open non http/https/atom links', async () => { - await atom.workspace.open('sample.md') + await atom.workspace.open('sample.md'); - const editor = atom.workspace.getActiveTextEditor() - editor.setText('// ftp://github.com\n') + const editor = atom.workspace.getActiveTextEditor(); + editor.setText('// ftp://github.com\n'); - spyOn(shell, 'openExternal') - atom.commands.dispatch(atom.views.getView(editor), 'link:open') - expect(shell.openExternal).not.toHaveBeenCalled() + spyOn(shell, 'openExternal'); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); + expect(shell.openExternal).not.toHaveBeenCalled(); - editor.setCursorBufferPosition([0, 5]) - atom.commands.dispatch(atom.views.getView(editor), 'link:open') + editor.setCursorBufferPosition([0, 5]); + atom.commands.dispatch(atom.views.getView(editor), 'link:open'); - expect(shell.openExternal).not.toHaveBeenCalled() - }) - }) -}) + expect(shell.openExternal).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/one-dark-ui/lib/main.js b/packages/one-dark-ui/lib/main.js index 180c7c9f5..664010616 100644 --- a/packages/one-dark-ui/lib/main.js +++ b/packages/one-dark-ui/lib/main.js @@ -1,88 +1,88 @@ -const root = document.documentElement -const themeName = 'one-dark-ui' +const root = document.documentElement; +const themeName = 'one-dark-ui'; module.exports = { - activate (state) { - atom.config.observe(`${themeName}.fontSize`, setFontSize) - atom.config.observe(`${themeName}.tabSizing`, setTabSizing) - atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton) - atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons) - atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders) + activate(state) { + atom.config.observe(`${themeName}.fontSize`, setFontSize); + atom.config.observe(`${themeName}.tabSizing`, setTabSizing); + atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton); + atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons); + atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders); // DEPRECATED: This can be removed at some point (added in Atom 1.17/1.18ish) // It removes `layoutMode` if (atom.config.get(`${themeName}.layoutMode`)) { - atom.config.unset(`${themeName}.layoutMode`) + atom.config.unset(`${themeName}.layoutMode`); } }, - deactivate () { - unsetFontSize() - unsetTabSizing() - unsetTabCloseButton() - unsetHideDockButtons() - unsetStickyHeaders() + deactivate() { + unsetFontSize(); + unsetTabSizing(); + unsetTabCloseButton(); + unsetHideDockButtons(); + unsetStickyHeaders(); } -} +}; // Font Size ----------------------- -function setFontSize (currentFontSize) { - root.style.fontSize = `${currentFontSize}px` +function setFontSize(currentFontSize) { + root.style.fontSize = `${currentFontSize}px`; } -function unsetFontSize () { - root.style.fontSize = '' +function unsetFontSize() { + root.style.fontSize = ''; } // Tab Sizing ----------------------- -function setTabSizing (tabSizing) { - root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase()) +function setTabSizing(tabSizing) { + root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase()); } -function unsetTabSizing () { - root.removeAttribute(`theme-${themeName}-tabsizing`) +function unsetTabSizing() { + root.removeAttribute(`theme-${themeName}-tabsizing`); } // Tab Close Button ----------------------- -function setTabCloseButton (tabCloseButton) { +function setTabCloseButton(tabCloseButton) { if (tabCloseButton === 'Left') { - root.setAttribute(`theme-${themeName}-tab-close-button`, 'left') + root.setAttribute(`theme-${themeName}-tab-close-button`, 'left'); } else { - unsetTabCloseButton() + unsetTabCloseButton(); } } -function unsetTabCloseButton () { - root.removeAttribute(`theme-${themeName}-tab-close-button`) +function unsetTabCloseButton() { + root.removeAttribute(`theme-${themeName}-tab-close-button`); } // Dock Buttons ----------------------- -function setHideDockButtons (hideDockButtons) { +function setHideDockButtons(hideDockButtons) { if (hideDockButtons) { - root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden') + root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden'); } else { - unsetHideDockButtons() + unsetHideDockButtons(); } } -function unsetHideDockButtons () { - root.removeAttribute(`theme-${themeName}-dock-buttons`) +function unsetHideDockButtons() { + root.removeAttribute(`theme-${themeName}-dock-buttons`); } // Sticky Headers ----------------------- -function setStickyHeaders (stickyHeaders) { +function setStickyHeaders(stickyHeaders) { if (stickyHeaders) { - root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky') + root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky'); } else { - unsetStickyHeaders() + unsetStickyHeaders(); } } -function unsetStickyHeaders () { - root.removeAttribute(`theme-${themeName}-sticky-headers`) +function unsetStickyHeaders() { + root.removeAttribute(`theme-${themeName}-sticky-headers`); } diff --git a/packages/one-dark-ui/spec/theme-spec.js b/packages/one-dark-ui/spec/theme-spec.js index a43ea4f7c..4bd0ef85d 100644 --- a/packages/one-dark-ui/spec/theme-spec.js +++ b/packages/one-dark-ui/spec/theme-spec.js @@ -1,58 +1,58 @@ -const themeName = 'one-dark-ui' +const themeName = 'one-dark-ui'; describe(`${themeName} theme`, () => { beforeEach(() => { - waitsForPromise(() => atom.packages.activatePackage(themeName)) - }) + waitsForPromise(() => atom.packages.activatePackage(themeName)); + }); it('allows the font size to be set via config', () => { - expect(document.documentElement.style.fontSize).toBe('12px') + expect(document.documentElement.style.fontSize).toBe('12px'); - atom.config.set(`${themeName}.fontSize`, '10') - expect(document.documentElement.style.fontSize).toBe('10px') - }) + atom.config.set(`${themeName}.fontSize`, '10'); + expect(document.documentElement.style.fontSize).toBe('10px'); + }); it('allows the tab sizing to be set via config', () => { - atom.config.set(`${themeName}.tabSizing`, 'Maximum') + atom.config.set(`${themeName}.tabSizing`, 'Maximum'); expect( document.documentElement.getAttribute(`theme-${themeName}-tabsizing`) - ).toBe('maximum') - }) + ).toBe('maximum'); + }); it('allows the tab sizing to be set via config', () => { - atom.config.set(`${themeName}.tabSizing`, 'Minimum') + atom.config.set(`${themeName}.tabSizing`, 'Minimum'); expect( document.documentElement.getAttribute(`theme-${themeName}-tabsizing`) - ).toBe('minimum') - }) + ).toBe('minimum'); + }); it('allows the tab close button to be shown on the left via config', () => { - atom.config.set(`${themeName}.tabCloseButton`, 'Left') + atom.config.set(`${themeName}.tabCloseButton`, 'Left'); expect( document.documentElement.getAttribute( `theme-${themeName}-tab-close-button` ) - ).toBe('left') - }) + ).toBe('left'); + }); it('allows the dock toggle buttons to be hidden via config', () => { - atom.config.set(`${themeName}.hideDockButtons`, true) + atom.config.set(`${themeName}.hideDockButtons`, true); expect( document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`) - ).toBe('hidden') - }) + ).toBe('hidden'); + }); it('allows the tree-view headers to be sticky via config', () => { - atom.config.set(`${themeName}.stickyHeaders`, true) + atom.config.set(`${themeName}.stickyHeaders`, true); expect( document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`) - ).toBe('sticky') - }) + ).toBe('sticky'); + }); it('allows the tree-view headers to not be sticky via config', () => { - atom.config.set(`${themeName}.stickyHeaders`, false) + atom.config.set(`${themeName}.stickyHeaders`, false); expect( document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`) - ).toBe(null) - }) -}) + ).toBe(null); + }); +}); diff --git a/packages/one-light-ui/lib/main.js b/packages/one-light-ui/lib/main.js index b9be214b9..f8a6a6d49 100644 --- a/packages/one-light-ui/lib/main.js +++ b/packages/one-light-ui/lib/main.js @@ -1,88 +1,88 @@ -const root = document.documentElement -const themeName = 'one-light-ui' +const root = document.documentElement; +const themeName = 'one-light-ui'; module.exports = { - activate (state) { - atom.config.observe(`${themeName}.fontSize`, setFontSize) - atom.config.observe(`${themeName}.tabSizing`, setTabSizing) - atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton) - atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons) - atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders) + activate(state) { + atom.config.observe(`${themeName}.fontSize`, setFontSize); + atom.config.observe(`${themeName}.tabSizing`, setTabSizing); + atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton); + atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons); + atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders); // DEPRECATED: This can be removed at some point (added in Atom 1.17/1.18ish) // It removes `layoutMode` if (atom.config.get(`${themeName}.layoutMode`)) { - atom.config.unset(`${themeName}.layoutMode`) + atom.config.unset(`${themeName}.layoutMode`); } }, - deactivate () { - unsetFontSize() - unsetTabSizing() - unsetTabCloseButton() - unsetHideDockButtons() - unsetStickyHeaders() + deactivate() { + unsetFontSize(); + unsetTabSizing(); + unsetTabCloseButton(); + unsetHideDockButtons(); + unsetStickyHeaders(); } -} +}; // Font Size ----------------------- -function setFontSize (currentFontSize) { - root.style.fontSize = `${currentFontSize}px` +function setFontSize(currentFontSize) { + root.style.fontSize = `${currentFontSize}px`; } -function unsetFontSize () { - root.style.fontSize = '' +function unsetFontSize() { + root.style.fontSize = ''; } // Tab Sizing ----------------------- -function setTabSizing (tabSizing) { - root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase()) +function setTabSizing(tabSizing) { + root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase()); } -function unsetTabSizing () { - root.removeAttribute(`theme-${themeName}-tabsizing`) +function unsetTabSizing() { + root.removeAttribute(`theme-${themeName}-tabsizing`); } // Tab Close Button ----------------------- -function setTabCloseButton (tabCloseButton) { +function setTabCloseButton(tabCloseButton) { if (tabCloseButton === 'Left') { - root.setAttribute(`theme-${themeName}-tab-close-button`, 'left') + root.setAttribute(`theme-${themeName}-tab-close-button`, 'left'); } else { - unsetTabCloseButton() + unsetTabCloseButton(); } } -function unsetTabCloseButton () { - root.removeAttribute(`theme-${themeName}-tab-close-button`) +function unsetTabCloseButton() { + root.removeAttribute(`theme-${themeName}-tab-close-button`); } // Dock Buttons ----------------------- -function setHideDockButtons (hideDockButtons) { +function setHideDockButtons(hideDockButtons) { if (hideDockButtons) { - root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden') + root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden'); } else { - unsetHideDockButtons() + unsetHideDockButtons(); } } -function unsetHideDockButtons () { - root.removeAttribute(`theme-${themeName}-dock-buttons`) +function unsetHideDockButtons() { + root.removeAttribute(`theme-${themeName}-dock-buttons`); } // Sticky Headers ----------------------- -function setStickyHeaders (stickyHeaders) { +function setStickyHeaders(stickyHeaders) { if (stickyHeaders) { - root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky') + root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky'); } else { - unsetStickyHeaders() + unsetStickyHeaders(); } } -function unsetStickyHeaders () { - root.removeAttribute(`theme-${themeName}-sticky-headers`) +function unsetStickyHeaders() { + root.removeAttribute(`theme-${themeName}-sticky-headers`); } diff --git a/packages/one-light-ui/spec/theme-spec.js b/packages/one-light-ui/spec/theme-spec.js index 862d2ef5e..c97b6f544 100644 --- a/packages/one-light-ui/spec/theme-spec.js +++ b/packages/one-light-ui/spec/theme-spec.js @@ -1,58 +1,58 @@ -const themeName = 'one-light-ui' +const themeName = 'one-light-ui'; describe(`${themeName} theme`, () => { beforeEach(() => { - waitsForPromise(() => atom.packages.activatePackage(themeName)) - }) + waitsForPromise(() => atom.packages.activatePackage(themeName)); + }); it('allows the font size to be set via config', () => { - expect(document.documentElement.style.fontSize).toBe('12px') + expect(document.documentElement.style.fontSize).toBe('12px'); - atom.config.set(`${themeName}.fontSize`, '10') - expect(document.documentElement.style.fontSize).toBe('10px') - }) + atom.config.set(`${themeName}.fontSize`, '10'); + expect(document.documentElement.style.fontSize).toBe('10px'); + }); it('allows the tab sizing to be set via config', () => { - atom.config.set(`${themeName}.tabSizing`, 'Maximum') + atom.config.set(`${themeName}.tabSizing`, 'Maximum'); expect( document.documentElement.getAttribute(`theme-${themeName}-tabsizing`) - ).toBe('maximum') - }) + ).toBe('maximum'); + }); it('allows the tab sizing to be set via config', () => { - atom.config.set(`${themeName}.tabSizing`, 'Minimum') + atom.config.set(`${themeName}.tabSizing`, 'Minimum'); expect( document.documentElement.getAttribute(`theme-${themeName}-tabsizing`) - ).toBe('minimum') - }) + ).toBe('minimum'); + }); it('allows the tab close button to be shown on the left via config', () => { - atom.config.set(`${themeName}.tabCloseButton`, 'Left') + atom.config.set(`${themeName}.tabCloseButton`, 'Left'); expect( document.documentElement.getAttribute( `theme-${themeName}-tab-close-button` ) - ).toBe('left') - }) + ).toBe('left'); + }); it('allows the dock toggle buttons to be hidden via config', () => { - atom.config.set(`${themeName}.hideDockButtons`, true) + atom.config.set(`${themeName}.hideDockButtons`, true); expect( document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`) - ).toBe('hidden') - }) + ).toBe('hidden'); + }); it('allows the tree-view headers to be sticky via config', () => { - atom.config.set(`${themeName}.stickyHeaders`, true) + atom.config.set(`${themeName}.stickyHeaders`, true); expect( document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`) - ).toBe('sticky') - }) + ).toBe('sticky'); + }); it('allows the tree-view headers to not be sticky via config', () => { - atom.config.set(`${themeName}.stickyHeaders`, false) + atom.config.set(`${themeName}.stickyHeaders`, false); expect( document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`) - ).toBe(null) - }) -}) + ).toBe(null); + }); +}); diff --git a/resources/win/atom.js b/resources/win/atom.js index 02f0af089..4c4a099e9 100644 --- a/resources/win/atom.js +++ b/resources/win/atom.js @@ -1,9 +1,9 @@ -var path = require('path'); +var path = require('path'); var spawn = require('child_process').spawn; var atomCommandPath = path.resolve(__dirname, '..', '..', process.argv[2]); var args = process.argv.slice(3); args.unshift('--executed-from', process.cwd()); -var options = {detached: true, stdio: 'ignore'}; +var options = { detached: true, stdio: 'ignore' }; spawn(atomCommandPath, args, options); process.exit(0); diff --git a/script/config.js b/script/config.js index 5c56e3f9e..20a36a3ca 100644 --- a/script/config.js +++ b/script/config.js @@ -1,22 +1,23 @@ // This module exports paths, names, and other metadata that is referenced // throughout the build. -'use strict' +'use strict'; -const fs = require('fs') -const path = require('path') -const spawnSync = require('./lib/spawn-sync') +const fs = require('fs'); +const path = require('path'); +const spawnSync = require('./lib/spawn-sync'); -const repositoryRootPath = path.resolve(__dirname, '..') -const apmRootPath = path.join(repositoryRootPath, 'apm') -const scriptRootPath = path.join(repositoryRootPath, 'script') -const buildOutputPath = path.join(repositoryRootPath, 'out') -const docsOutputPath = path.join(repositoryRootPath, 'docs', 'output') -const intermediateAppPath = path.join(buildOutputPath, 'app') -const symbolsPath = path.join(buildOutputPath, 'symbols') -const electronDownloadPath = path.join(repositoryRootPath, 'electron') -const homeDirPath = process.env.HOME || process.env.USERPROFILE -const atomHomeDirPath = process.env.ATOM_HOME || path.join(homeDirPath, '.atom') +const repositoryRootPath = path.resolve(__dirname, '..'); +const apmRootPath = path.join(repositoryRootPath, 'apm'); +const scriptRootPath = path.join(repositoryRootPath, 'script'); +const buildOutputPath = path.join(repositoryRootPath, 'out'); +const docsOutputPath = path.join(repositoryRootPath, 'docs', 'output'); +const intermediateAppPath = path.join(buildOutputPath, 'app'); +const symbolsPath = path.join(buildOutputPath, 'symbols'); +const electronDownloadPath = path.join(repositoryRootPath, 'electron'); +const homeDirPath = process.env.HOME || process.env.USERPROFILE; +const atomHomeDirPath = + process.env.ATOM_HOME || path.join(homeDirPath, '.atom'); const appMetadata = require(path.join(repositoryRootPath, 'package.json')) const apmMetadata = require(path.join(apmRootPath, 'package.json')) @@ -45,23 +46,24 @@ module.exports = { getApmBinPath, getNpmBinPath, snapshotAuxiliaryData: {} -} +}; -function getChannel (version) { - const match = version.match(/\d+\.\d+\.\d+(-([a-z]+)(\d+|-\w{4,})?)?$/) +function getChannel(version) { + const match = version.match(/\d+\.\d+\.\d+(-([a-z]+)(\d+|-\w{4,})?)?$/); if (!match) { - throw new Error(`Found incorrectly formatted Atom version ${version}`) + throw new Error(`Found incorrectly formatted Atom version ${version}`); } else if (match[2]) { - return match[2] + return match[2]; } - return 'stable' + return 'stable'; } -function getAppName (channel) { +function getAppName(channel) { return channel === 'stable' ? 'Atom' - : `Atom ${process.env.ATOM_CHANNEL_DISPLAY_NAME || channel.charAt(0).toUpperCase() + channel.slice(1)}` + : `Atom ${process.env.ATOM_CHANNEL_DISPLAY_NAME || + channel.charAt(0).toUpperCase() + channel.slice(1)}`; } function getExecutableName (channel, appName) { @@ -76,22 +78,38 @@ function getExecutableName (channel, appName) { function computeAppVersion (version) { if (version.match(/-dev$/)) { - const result = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {cwd: repositoryRootPath}) - const commitHash = result.stdout.toString().trim() - version += '-' + commitHash + const result = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { + cwd: repositoryRootPath + }); + const commitHash = result.stdout.toString().trim(); + version += '-' + commitHash; } - return version + return version; } -function getApmBinPath () { - const apmBinName = process.platform === 'win32' ? 'apm.cmd' : 'apm' - return path.join(apmRootPath, 'node_modules', 'atom-package-manager', 'bin', apmBinName) +function getApmBinPath() { + const apmBinName = process.platform === 'win32' ? 'apm.cmd' : 'apm'; + return path.join( + apmRootPath, + 'node_modules', + 'atom-package-manager', + 'bin', + apmBinName + ); } -function getNpmBinPath (external = false) { - if (process.env.NPM_BIN_PATH) return process.env.NPM_BIN_PATH +function getNpmBinPath(external = false) { + if (process.env.NPM_BIN_PATH) return process.env.NPM_BIN_PATH; - const npmBinName = process.platform === 'win32' ? 'npm.cmd' : 'npm' - const localNpmBinPath = path.resolve(repositoryRootPath, 'script', 'node_modules', '.bin', npmBinName) - return !external && fs.existsSync(localNpmBinPath) ? localNpmBinPath : npmBinName + const npmBinName = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + const localNpmBinPath = path.resolve( + repositoryRootPath, + 'script', + 'node_modules', + '.bin', + npmBinName + ); + return !external && fs.existsSync(localNpmBinPath) + ? localNpmBinPath + : npmBinName; } diff --git a/script/lib/backup-node-modules.js b/script/lib/backup-node-modules.js index 74a4fb6d3..759451a4a 100644 --- a/script/lib/backup-node-modules.js +++ b/script/lib/backup-node-modules.js @@ -1,41 +1,54 @@ -const fs = require('fs-extra') -const path = require('path') +const fs = require('fs-extra'); +const path = require('path'); -module.exports = function (packagePath) { - const nodeModulesPath = path.join(packagePath, 'node_modules') - const nodeModulesBackupPath = path.join(packagePath, 'node_modules.bak') +module.exports = function(packagePath) { + const nodeModulesPath = path.join(packagePath, 'node_modules'); + const nodeModulesBackupPath = path.join(packagePath, 'node_modules.bak'); if (fs.existsSync(nodeModulesBackupPath)) { - throw new Error('Cannot back up ' + nodeModulesPath + '; ' + nodeModulesBackupPath + ' already exists') + throw new Error( + 'Cannot back up ' + + nodeModulesPath + + '; ' + + nodeModulesBackupPath + + ' already exists' + ); } // some packages may have no node_modules after deduping, but we still want // to "back-up" and later restore that fact if (!fs.existsSync(nodeModulesPath)) { - const msg = 'Skipping backing up ' + nodeModulesPath + ' as it does not exist' - console.log(msg.gray) + const msg = + 'Skipping backing up ' + nodeModulesPath + ' as it does not exist'; + console.log(msg.gray); - const restore = function stubRestoreNodeModules () { + const restore = function stubRestoreNodeModules() { if (fs.existsSync(nodeModulesPath)) { - fs.removeSync(nodeModulesPath) + fs.removeSync(nodeModulesPath); } - } + }; - return {restore, nodeModulesPath, nodeModulesBackupPath} + return { restore, nodeModulesPath, nodeModulesBackupPath }; } - fs.copySync(nodeModulesPath, nodeModulesBackupPath) + fs.copySync(nodeModulesPath, nodeModulesBackupPath); - const restore = function restoreNodeModules () { + const restore = function restoreNodeModules() { if (!fs.existsSync(nodeModulesBackupPath)) { - throw new Error('Cannot restore ' + nodeModulesPath + '; ' + nodeModulesBackupPath + ' does not exist') + throw new Error( + 'Cannot restore ' + + nodeModulesPath + + '; ' + + nodeModulesBackupPath + + ' does not exist' + ); } if (fs.existsSync(nodeModulesPath)) { - fs.removeSync(nodeModulesPath) + fs.removeSync(nodeModulesPath); } - fs.renameSync(nodeModulesBackupPath, nodeModulesPath) - } + fs.renameSync(nodeModulesBackupPath, nodeModulesPath); + }; - return {restore, nodeModulesPath, nodeModulesBackupPath} -} + return { restore, nodeModulesPath, nodeModulesBackupPath }; +}; diff --git a/script/lib/check-chromedriver-version.js b/script/lib/check-chromedriver-version.js index f5cada045..d91b5a094 100644 --- a/script/lib/check-chromedriver-version.js +++ b/script/lib/check-chromedriver-version.js @@ -1,33 +1,45 @@ -'use strict' +'use strict'; -const buildMetadata = require('../package.json') -const CONFIG = require('../config') -const semver = require('semver') +const buildMetadata = require('../package.json'); +const CONFIG = require('../config'); +const semver = require('semver'); -module.exports = function () { +module.exports = function() { // Chromedriver should be specified as ^n.x where n matches the Electron major version - const chromedriverVer = buildMetadata.dependencies['electron-chromedriver'] - const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot'] + const chromedriverVer = buildMetadata.dependencies['electron-chromedriver']; + const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot']; // Always use caret on electron-chromedriver so that it can pick up the best minor/patch versions if (!chromedriverVer.startsWith('^')) { - throw new Error(`electron-chromedriver version in script/package.json should start with a caret to match latest patch version.`) + throw new Error( + `electron-chromedriver version in script/package.json should start with a caret to match latest patch version.` + ); } if (!mksnapshotVer.startsWith('^')) { - throw new Error(`electron-mksnapshot version in script/package.json should start with a caret to match latest patch version.`) + throw new Error( + `electron-mksnapshot version in script/package.json should start with a caret to match latest patch version.` + ); } - const electronVer = CONFIG.appMetadata.electronVersion + 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)}' ?`) + 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)}' ?`) + 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/clean-caches.js b/script/lib/clean-caches.js index 3861908bb..04e70b992 100644 --- a/script/lib/clean-caches.js +++ b/script/lib/clean-caches.js @@ -1,12 +1,12 @@ -'use strict' +'use strict'; -const fs = require('fs-extra') -const os = require('os') -const path = require('path') +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { +module.exports = function() { const cachePaths = [ path.join(CONFIG.repositoryRootPath, 'electron'), path.join(CONFIG.atomHomeDirPath, '.node-gyp'), @@ -19,10 +19,10 @@ module.exports = function () { path.join(CONFIG.atomHomeDirPath, 'electron'), path.join(os.tmpdir(), 'atom-build'), path.join(os.tmpdir(), 'atom-cached-atom-shells') - ] + ]; for (let path of cachePaths) { - console.log(`Cleaning ${path}`) - fs.removeSync(path) + console.log(`Cleaning ${path}`); + fs.removeSync(path); } -} +}; diff --git a/script/lib/clean-dependencies.js b/script/lib/clean-dependencies.js index 5b64e5e89..107822209 100644 --- a/script/lib/clean-dependencies.js +++ b/script/lib/clean-dependencies.js @@ -1,29 +1,42 @@ -const path = require('path') +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { +module.exports = function() { // We can't require fs-extra or glob if `script/bootstrap` has never been run, // because they are third party modules. This is okay because cleaning // dependencies only makes sense if dependencies have been installed at least // once. - const fs = require('fs-extra') - const glob = require('glob') + const fs = require('fs-extra'); + const glob = require('glob'); - const apmDependenciesPath = path.join(CONFIG.apmRootPath, 'node_modules') - console.log(`Cleaning ${apmDependenciesPath}`) - fs.removeSync(apmDependenciesPath) + const apmDependenciesPath = path.join(CONFIG.apmRootPath, 'node_modules'); + console.log(`Cleaning ${apmDependenciesPath}`); + fs.removeSync(apmDependenciesPath); - const atomDependenciesPath = path.join(CONFIG.repositoryRootPath, 'node_modules') - console.log(`Cleaning ${atomDependenciesPath}`) - fs.removeSync(atomDependenciesPath) + const atomDependenciesPath = path.join( + CONFIG.repositoryRootPath, + 'node_modules' + ); + console.log(`Cleaning ${atomDependenciesPath}`); + fs.removeSync(atomDependenciesPath); - const scriptDependenciesPath = path.join(CONFIG.scriptRootPath, 'node_modules') - console.log(`Cleaning ${scriptDependenciesPath}`) - fs.removeSync(scriptDependenciesPath) + const scriptDependenciesPath = path.join( + CONFIG.scriptRootPath, + 'node_modules' + ); + console.log(`Cleaning ${scriptDependenciesPath}`); + fs.removeSync(scriptDependenciesPath); - const bundledPackageDependenciesPaths = path.join(CONFIG.repositoryRootPath, 'packages', '**', 'node_modules') - for (const bundledPackageDependencyPath of glob.sync(bundledPackageDependenciesPaths)) { - fs.removeSync(bundledPackageDependencyPath) + const bundledPackageDependenciesPaths = path.join( + CONFIG.repositoryRootPath, + 'packages', + '**', + 'node_modules' + ); + for (const bundledPackageDependencyPath of glob.sync( + bundledPackageDependenciesPaths + )) { + fs.removeSync(bundledPackageDependencyPath); } -} +}; diff --git a/script/lib/clean-output-directory.js b/script/lib/clean-output-directory.js index 93925e504..4bbf1d10d 100644 --- a/script/lib/clean-output-directory.js +++ b/script/lib/clean-output-directory.js @@ -1,9 +1,9 @@ -const fs = require('fs-extra') -const CONFIG = require('../config') +const fs = require('fs-extra'); +const CONFIG = require('../config'); -module.exports = function () { +module.exports = function() { if (fs.existsSync(CONFIG.buildOutputPath)) { - console.log(`Cleaning ${CONFIG.buildOutputPath}`) - fs.removeSync(CONFIG.buildOutputPath) + console.log(`Cleaning ${CONFIG.buildOutputPath}`); + fs.removeSync(CONFIG.buildOutputPath); } -} +}; diff --git a/script/lib/code-sign-on-mac.js b/script/lib/code-sign-on-mac.js index 1d8243808..b1f246b94 100644 --- a/script/lib/code-sign-on-mac.js +++ b/script/lib/code-sign-on-mac.js @@ -1,89 +1,143 @@ -const downloadFileFromGithub = require('./download-file-from-github') -const fs = require('fs-extra') -const os = require('os') -const path = require('path') -const spawnSync = require('./spawn-sync') +const downloadFileFromGithub = require('./download-file-from-github'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); +const spawnSync = require('./spawn-sync'); -module.exports = function (packagedAppPath) { - if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) { - console.log('Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray) - return +module.exports = function(packagedAppPath) { + if ( + !process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL && + !process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH + ) { + console.log( + 'Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined' + .gray + ); + return; } - let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH + let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH; if (!certPath) { - certPath = path.join(os.tmpdir(), 'mac.p12') - downloadFileFromGithub(process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath) + certPath = path.join(os.tmpdir(), 'mac.p12'); + downloadFileFromGithub( + process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL, + certPath + ); } try { - console.log(`Ensuring keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN} exists`) + console.log( + `Ensuring keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN} exists` + ); try { - spawnSync('security', [ - 'show-keychain-info', - process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN - ], {stdio: 'inherit'}) + spawnSync( + 'security', + ['show-keychain-info', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN], + { stdio: 'inherit' } + ); } catch (err) { - console.log(`Creating keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`) + console.log( + `Creating keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}` + ); // The keychain doesn't exist, try to create it - spawnSync('security', [ - 'create-keychain', - '-p', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD, - process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN - ], {stdio: 'inherit'}) + spawnSync( + 'security', + [ + 'create-keychain', + '-p', + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD, + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN + ], + { stdio: 'inherit' } + ); // List the keychain to "activate" it. Somehow this seems // to be needed otherwise the signing operation fails - spawnSync('security', [ - 'list-keychains', - '-s', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN - ], {stdio: 'inherit'}) + spawnSync( + 'security', + ['list-keychains', '-s', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN], + { stdio: 'inherit' } + ); // Make sure it doesn't time out before we use it - spawnSync('security', [ - 'set-keychain-settings', - '-t', '3600', - '-u', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN - ], {stdio: 'inherit'}) + spawnSync( + 'security', + [ + 'set-keychain-settings', + '-t', + '3600', + '-u', + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN + ], + { stdio: 'inherit' } + ); } - console.log(`Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`) - const unlockArgs = ['unlock-keychain'] + console.log( + `Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}` + ); + const unlockArgs = ['unlock-keychain']; // For signing on local workstations, password could be entered interactively if (process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD) { - unlockArgs.push('-p', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD) + unlockArgs.push( + '-p', + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD + ); } - unlockArgs.push(process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN) - spawnSync('security', unlockArgs, {stdio: 'inherit'}) + unlockArgs.push(process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN); + spawnSync('security', unlockArgs, { stdio: 'inherit' }); - console.log(`Importing certificate at ${certPath} into ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN} keychain`) + console.log( + `Importing certificate at ${certPath} into ${ + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN + } keychain` + ); spawnSync('security', [ - 'import', certPath, - '-P', process.env.ATOM_MAC_CODE_SIGNING_CERT_PASSWORD, - '-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN, - '-T', '/usr/bin/codesign' - ]) + 'import', + certPath, + '-P', + process.env.ATOM_MAC_CODE_SIGNING_CERT_PASSWORD, + '-k', + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN, + '-T', + '/usr/bin/codesign' + ]); - console.log('Running incantation to suppress dialog when signing on macOS Sierra') + console.log( + 'Running incantation to suppress dialog when signing on macOS Sierra' + ); try { spawnSync('security', [ - 'set-key-partition-list', '-S', 'apple-tool:,apple:', '-s', - '-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD, + 'set-key-partition-list', + '-S', + 'apple-tool:,apple:', + '-s', + '-k', + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD, process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN - ]) + ]); } catch (e) { - console.log('Incantation failed... maybe this isn\'t Sierra?') + console.log("Incantation failed... maybe this isn't Sierra?"); } - console.log(`Code-signing application at ${packagedAppPath}`) - spawnSync('codesign', [ - '--deep', '--force', '--verbose', - '--keychain', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN, - '--sign', 'Developer ID Application: GitHub', packagedAppPath - ], {stdio: 'inherit'}) + console.log(`Code-signing application at ${packagedAppPath}`); + spawnSync( + 'codesign', + [ + '--deep', + '--force', + '--verbose', + '--keychain', + process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN, + '--sign', + 'Developer ID Application: GitHub', + packagedAppPath + ], + { stdio: 'inherit' } + ); } finally { if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) { - console.log(`Deleting certificate at ${certPath}`) - fs.removeSync(certPath) + console.log(`Deleting certificate at ${certPath}`); + fs.removeSync(certPath); } } -} +}; diff --git a/script/lib/code-sign-on-windows.js b/script/lib/code-sign-on-windows.js index a73ea90be..93bd3e65d 100644 --- a/script/lib/code-sign-on-windows.js +++ b/script/lib/code-sign-on-windows.js @@ -1,33 +1,49 @@ -const downloadFileFromGithub = require('./download-file-from-github') -const fs = require('fs-extra') -const os = require('os') -const path = require('path') -const {spawnSync} = require('child_process') +const downloadFileFromGithub = require('./download-file-from-github'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); +const { spawnSync } = require('child_process'); -module.exports = function (filesToSign) { - if (!process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) { - console.log('Skipping code signing because the ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray) - return +module.exports = function(filesToSign) { + if ( + !process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL && + !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH + ) { + console.log( + 'Skipping code signing because the ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined' + .gray + ); + return; } - let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH + let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH; if (!certPath) { - certPath = path.join(os.tmpdir(), 'win.p12') - downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath) + certPath = path.join(os.tmpdir(), 'win.p12'); + downloadFileFromGithub( + process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, + certPath + ); } try { for (const fileToSign of filesToSign) { - console.log(`Code-signing executable at ${fileToSign}`) - signFile(fileToSign) + console.log(`Code-signing executable at ${fileToSign}`); + signFile(fileToSign); } } finally { if (!process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) { - fs.removeSync(certPath) + fs.removeSync(certPath); } } - function signFile (fileToSign) { - const signCommand = path.resolve(__dirname, '..', 'node_modules', 'electron-winstaller', 'vendor', 'signtool.exe') + function signFile(fileToSign) { + const signCommand = path.resolve( + __dirname, + '..', + 'node_modules', + 'electron-winstaller', + 'vendor', + 'signtool.exe' + ); const args = [ 'sign', `/f ${certPath}`, // Signing cert file @@ -36,11 +52,20 @@ module.exports = function (filesToSign) { '/tr http://timestamp.digicert.com', // Time stamp server '/td sha256', // Times stamp algorithm `"${fileToSign}"` - ] - const result = spawnSync(signCommand, args, {stdio: 'inherit', shell: true}) + ]; + const result = spawnSync(signCommand, args, { + stdio: 'inherit', + shell: true + }); if (result.status !== 0) { // Ensure we do not dump the signing password into the logs if something goes wrong - throw new Error(`Command ${signCommand} ${args.map(a => a.replace(process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD, '******')).join(' ')} exited with code ${result.status}`) + throw new Error( + `Command ${signCommand} ${args + .map(a => + a.replace(process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD, '******') + ) + .join(' ')} exited with code ${result.status}` + ); } } -} +}; diff --git a/script/lib/compress-artifacts.js b/script/lib/compress-artifacts.js index 74f31313f..e687aa85b 100644 --- a/script/lib/compress-artifacts.js +++ b/script/lib/compress-artifacts.js @@ -1,56 +1,67 @@ -'use strict' +'use strict'; -const fs = require('fs-extra') -const path = require('path') -const spawnSync = require('./spawn-sync') -const { path7za } = require('7zip-bin') +const fs = require('fs-extra'); +const path = require('path'); +const spawnSync = require('./spawn-sync'); +const { path7za } = require('7zip-bin'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (packagedAppPath) { - const appArchivePath = path.join(CONFIG.buildOutputPath, getArchiveName()) - compress(packagedAppPath, appArchivePath) +module.exports = function(packagedAppPath) { + const appArchivePath = path.join(CONFIG.buildOutputPath, getArchiveName()); + compress(packagedAppPath, appArchivePath); if (process.platform === 'darwin') { - const symbolsArchivePath = path.join(CONFIG.buildOutputPath, 'atom-mac-symbols.zip') - compress(CONFIG.symbolsPath, symbolsArchivePath) + const symbolsArchivePath = path.join( + CONFIG.buildOutputPath, + 'atom-mac-symbols.zip' + ); + compress(CONFIG.symbolsPath, symbolsArchivePath); } -} +}; -function getArchiveName () { +function getArchiveName() { switch (process.platform) { - case 'darwin': return 'atom-mac.zip' - case 'win32': return `atom-${process.arch === 'x64' ? 'x64-' : ''}windows.zip` - default: return `atom-${getLinuxArchiveArch()}.tar.gz` + case 'darwin': + return 'atom-mac.zip'; + case 'win32': + return `atom-${process.arch === 'x64' ? 'x64-' : ''}windows.zip`; + default: + return `atom-${getLinuxArchiveArch()}.tar.gz`; } } -function getLinuxArchiveArch () { +function getLinuxArchiveArch() { switch (process.arch) { - case 'ia32': return 'i386' - case 'x64' : return 'amd64' - default: return process.arch + case 'ia32': + return 'i386'; + case 'x64': + return 'amd64'; + default: + return process.arch; } } -function compress (inputDirPath, outputArchivePath) { +function compress(inputDirPath, outputArchivePath) { if (fs.existsSync(outputArchivePath)) { - console.log(`Deleting "${outputArchivePath}"`) - fs.removeSync(outputArchivePath) + console.log(`Deleting "${outputArchivePath}"`); + fs.removeSync(outputArchivePath); } - console.log(`Compressing "${inputDirPath}" to "${outputArchivePath}"`) - let compressCommand, compressArguments + console.log(`Compressing "${inputDirPath}" to "${outputArchivePath}"`); + let compressCommand, compressArguments; if (process.platform === 'darwin') { - compressCommand = 'zip' - compressArguments = ['-r', '--symlinks'] + compressCommand = 'zip'; + compressArguments = ['-r', '--symlinks']; } else if (process.platform === 'win32') { - compressCommand = path7za - compressArguments = ['a', '-r'] + compressCommand = path7za; + compressArguments = ['a', '-r']; } else { - compressCommand = 'tar' - compressArguments = ['caf'] + compressCommand = 'tar'; + compressArguments = ['caf']; } - compressArguments.push(outputArchivePath, path.basename(inputDirPath)) - spawnSync(compressCommand, compressArguments, {cwd: path.dirname(inputDirPath)}) + compressArguments.push(outputArchivePath, path.basename(inputDirPath)); + spawnSync(compressCommand, compressArguments, { + cwd: path.dirname(inputDirPath) + }); } diff --git a/script/lib/copy-assets.js b/script/lib/copy-assets.js index b04916cd9..aa4bff674 100644 --- a/script/lib/copy-assets.js +++ b/script/lib/copy-assets.js @@ -1,16 +1,16 @@ // This module exports a function that copies all the static assets into the // appropriate location in the build output directory. -'use strict' +'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') +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}`) +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'), @@ -19,10 +19,16 @@ module.exports = function () { path.join(CONFIG.repositoryRootPath, 'static'), path.join(CONFIG.repositoryRootPath, 'src'), path.join(CONFIG.repositoryRootPath, 'vendor') - ] - srcPaths = srcPaths.concat(glob.sync(path.join(CONFIG.repositoryRootPath, 'spec', '*.*'), {ignore: path.join('**', '*-spec.*')})) + ]; + srcPaths = srcPaths.concat( + glob.sync(path.join(CONFIG.repositoryRootPath, 'spec', '*.*'), { + ignore: path.join('**', '*-spec.*') + }) + ); for (let srcPath of srcPaths) { - fs.copySync(srcPath, computeDestinationPath(srcPath), {filter: includePathInPackagedApp}) + fs.copySync(srcPath, computeDestinationPath(srcPath), { + filter: includePathInPackagedApp + }); } // Run a copy pass to dereference symlinked directories under node_modules. @@ -30,21 +36,37 @@ module.exports = function () { // copied to the output folder correctly. We dereference only the top-level // symlinks and not nested symlinks to avoid issues where symlinked binaries // are duplicated in Atom's installation packages (see atom/atom#18490). - const nodeModulesPath = path.join(CONFIG.repositoryRootPath, 'node_modules') - glob.sync(path.join(nodeModulesPath, '*')) - .map(p => fs.lstatSync(p).isSymbolicLink() ? path.resolve(nodeModulesPath, fs.readlinkSync(p)) : p) - .forEach(modulePath => { - const destPath = path.join(CONFIG.intermediateAppPath, 'node_modules', path.basename(modulePath)) - fs.copySync(modulePath, destPath, { filter: includePathInPackagedApp }) - }) + const nodeModulesPath = path.join(CONFIG.repositoryRootPath, 'node_modules'); + glob + .sync(path.join(nodeModulesPath, '*')) + .map(p => + fs.lstatSync(p).isSymbolicLink() + ? path.resolve(nodeModulesPath, fs.readlinkSync(p)) + : p + ) + .forEach(modulePath => { + const destPath = path.join( + CONFIG.intermediateAppPath, + 'node_modules', + path.basename(modulePath) + ); + fs.copySync(modulePath, destPath, { filter: includePathInPackagedApp }); + }); fs.copySync( - path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png', '1024.png'), + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'app-icons', + CONFIG.channel, + 'png', + '1024.png' + ), path.join(CONFIG.intermediateAppPath, 'resources', 'atom.png') - ) -} + ); +}; -function computeDestinationPath (srcPath) { - const relativePath = path.relative(CONFIG.repositoryRootPath, srcPath) - return path.join(CONFIG.intermediateAppPath, relativePath) +function computeDestinationPath(srcPath) { + const relativePath = path.relative(CONFIG.repositoryRootPath, srcPath); + return path.join(CONFIG.intermediateAppPath, relativePath); } diff --git a/script/lib/create-debian-package.js b/script/lib/create-debian-package.js index cf5aaecf8..4bc1a1dff 100644 --- a/script/lib/create-debian-package.js +++ b/script/lib/create-debian-package.js @@ -1,127 +1,224 @@ -'use strict' +'use strict'; -const fs = require('fs-extra') -const os = require('os') -const path = require('path') -const spawnSync = require('./spawn-sync') -const template = require('lodash.template') +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); +const spawnSync = require('./spawn-sync'); +const template = require('lodash.template'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (packagedAppPath) { - console.log(`Creating Debian package for "${packagedAppPath}"`) - const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}` - const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}` - const appDescription = CONFIG.appMetadata.description - const appVersion = CONFIG.appMetadata.version - let arch +module.exports = function(packagedAppPath) { + console.log(`Creating Debian package for "${packagedAppPath}"`); + const atomExecutableName = + CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`; + const apmExecutableName = + CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}`; + const appDescription = CONFIG.appMetadata.description; + const appVersion = CONFIG.appMetadata.version; + let arch; if (process.arch === 'ia32') { - arch = 'i386' + arch = 'i386'; } else if (process.arch === 'x64') { - arch = 'amd64' + arch = 'amd64'; } else if (process.arch === 'ppc') { - arch = 'powerpc' + arch = 'powerpc'; } else { - arch = process.arch + arch = process.arch; } - const outputDebianPackageFilePath = path.join(CONFIG.buildOutputPath, `atom-${arch}.deb`) - const debianPackageDirPath = path.join(os.tmpdir(), path.basename(packagedAppPath)) - const debianPackageConfigPath = path.join(debianPackageDirPath, 'DEBIAN') - const debianPackageInstallDirPath = path.join(debianPackageDirPath, 'usr') - const debianPackageBinDirPath = path.join(debianPackageInstallDirPath, 'bin') - const debianPackageShareDirPath = path.join(debianPackageInstallDirPath, 'share') - const debianPackageAtomDirPath = path.join(debianPackageShareDirPath, atomExecutableName) - const debianPackageApplicationsDirPath = path.join(debianPackageShareDirPath, 'applications') - const debianPackageIconsDirPath = path.join(debianPackageShareDirPath, 'pixmaps') - const debianPackageLintianOverridesDirPath = path.join(debianPackageShareDirPath, 'lintian', 'overrides') - const debianPackageDocsDirPath = path.join(debianPackageShareDirPath, 'doc', atomExecutableName) + const outputDebianPackageFilePath = path.join( + CONFIG.buildOutputPath, + `atom-${arch}.deb` + ); + const debianPackageDirPath = path.join( + os.tmpdir(), + path.basename(packagedAppPath) + ); + const debianPackageConfigPath = path.join(debianPackageDirPath, 'DEBIAN'); + const debianPackageInstallDirPath = path.join(debianPackageDirPath, 'usr'); + const debianPackageBinDirPath = path.join(debianPackageInstallDirPath, 'bin'); + const debianPackageShareDirPath = path.join( + debianPackageInstallDirPath, + 'share' + ); + const debianPackageAtomDirPath = path.join( + debianPackageShareDirPath, + atomExecutableName + ); + const debianPackageApplicationsDirPath = path.join( + debianPackageShareDirPath, + 'applications' + ); + const debianPackageIconsDirPath = path.join( + debianPackageShareDirPath, + 'pixmaps' + ); + const debianPackageLintianOverridesDirPath = path.join( + debianPackageShareDirPath, + 'lintian', + 'overrides' + ); + const debianPackageDocsDirPath = path.join( + debianPackageShareDirPath, + 'doc', + atomExecutableName + ); if (fs.existsSync(debianPackageDirPath)) { - console.log(`Deleting existing build dir for Debian package at "${debianPackageDirPath}"`) - fs.removeSync(debianPackageDirPath) + console.log( + `Deleting existing build dir for Debian package at "${debianPackageDirPath}"` + ); + fs.removeSync(debianPackageDirPath); } if (fs.existsSync(`${debianPackageDirPath}.deb`)) { - console.log(`Deleting existing Debian package at "${debianPackageDirPath}.deb"`) - fs.removeSync(`${debianPackageDirPath}.deb`) + console.log( + `Deleting existing Debian package at "${debianPackageDirPath}.deb"` + ); + fs.removeSync(`${debianPackageDirPath}.deb`); } if (fs.existsSync(debianPackageDirPath)) { - console.log(`Deleting existing Debian package at "${outputDebianPackageFilePath}"`) - fs.removeSync(debianPackageDirPath) + console.log( + `Deleting existing Debian package at "${outputDebianPackageFilePath}"` + ); + fs.removeSync(debianPackageDirPath); } - console.log(`Creating Debian package directory structure at "${debianPackageDirPath}"`) - fs.mkdirpSync(debianPackageDirPath) - fs.mkdirpSync(debianPackageConfigPath) - fs.mkdirpSync(debianPackageInstallDirPath) - fs.mkdirpSync(debianPackageShareDirPath) - fs.mkdirpSync(debianPackageApplicationsDirPath) - fs.mkdirpSync(debianPackageIconsDirPath) - fs.mkdirpSync(debianPackageLintianOverridesDirPath) - fs.mkdirpSync(debianPackageDocsDirPath) - fs.mkdirpSync(debianPackageBinDirPath) + console.log( + `Creating Debian package directory structure at "${debianPackageDirPath}"` + ); + fs.mkdirpSync(debianPackageDirPath); + fs.mkdirpSync(debianPackageConfigPath); + fs.mkdirpSync(debianPackageInstallDirPath); + fs.mkdirpSync(debianPackageShareDirPath); + fs.mkdirpSync(debianPackageApplicationsDirPath); + fs.mkdirpSync(debianPackageIconsDirPath); + fs.mkdirpSync(debianPackageLintianOverridesDirPath); + fs.mkdirpSync(debianPackageDocsDirPath); + fs.mkdirpSync(debianPackageBinDirPath); - console.log(`Copying "${packagedAppPath}" to "${debianPackageAtomDirPath}"`) - fs.copySync(packagedAppPath, debianPackageAtomDirPath) - fs.chmodSync(debianPackageAtomDirPath, '755') + console.log(`Copying "${packagedAppPath}" to "${debianPackageAtomDirPath}"`); + fs.copySync(packagedAppPath, debianPackageAtomDirPath); + fs.chmodSync(debianPackageAtomDirPath, '755'); - console.log(`Copying binaries into "${debianPackageBinDirPath}"`) - fs.copySync(path.join(CONFIG.repositoryRootPath, 'atom.sh'), path.join(debianPackageBinDirPath, atomExecutableName)) + console.log(`Copying binaries into "${debianPackageBinDirPath}"`); + fs.copySync( + path.join(CONFIG.repositoryRootPath, 'atom.sh'), + path.join(debianPackageBinDirPath, atomExecutableName) + ); fs.symlinkSync( - path.join('..', 'share', atomExecutableName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), + path.join( + '..', + 'share', + atomExecutableName, + 'resources', + 'app', + 'apm', + 'node_modules', + '.bin', + 'apm' + ), path.join(debianPackageBinDirPath, apmExecutableName) - ) + ); - console.log(`Writing control file into "${debianPackageConfigPath}"`) - const packageSizeInKilobytes = spawnSync('du', ['-sk', packagedAppPath]).stdout.toString().split(/\s+/)[0] - const controlFileTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'debian', 'control.in')) + console.log(`Writing control file into "${debianPackageConfigPath}"`); + const packageSizeInKilobytes = spawnSync('du', ['-sk', packagedAppPath]) + .stdout.toString() + .split(/\s+/)[0]; + const controlFileTemplate = fs.readFileSync( + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'linux', + 'debian', + 'control.in' + ) + ); const controlFileContents = template(controlFileTemplate)({ appFileName: atomExecutableName, version: appVersion, arch: arch, installedSize: packageSizeInKilobytes, description: appDescription - }) - fs.writeFileSync(path.join(debianPackageConfigPath, 'control'), controlFileContents) + }); + fs.writeFileSync( + path.join(debianPackageConfigPath, 'control'), + controlFileContents + ); - console.log(`Writing desktop entry file into "${debianPackageApplicationsDirPath}"`) - const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in')) + console.log( + `Writing desktop entry file into "${debianPackageApplicationsDirPath}"` + ); + const desktopEntryTemplate = fs.readFileSync( + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'linux', + 'atom.desktop.in' + ) + ); const desktopEntryContents = template(desktopEntryTemplate)({ appName: CONFIG.appName, appFileName: atomExecutableName, description: appDescription, installDir: '/usr', iconPath: atomExecutableName - }) - fs.writeFileSync(path.join(debianPackageApplicationsDirPath, `${atomExecutableName}.desktop`), desktopEntryContents) + }); + fs.writeFileSync( + path.join( + debianPackageApplicationsDirPath, + `${atomExecutableName}.desktop` + ), + desktopEntryContents + ); - console.log(`Copying icon into "${debianPackageIconsDirPath}"`) + console.log(`Copying icon into "${debianPackageIconsDirPath}"`); fs.copySync( - path.join(packagedAppPath, 'resources', 'app.asar.unpacked', 'resources', 'atom.png'), + path.join( + packagedAppPath, + 'resources', + 'app.asar.unpacked', + 'resources', + 'atom.png' + ), path.join(debianPackageIconsDirPath, `${atomExecutableName}.png`) - ) + ); - console.log(`Copying license into "${debianPackageDocsDirPath}"`) + console.log(`Copying license into "${debianPackageDocsDirPath}"`); fs.copySync( path.join(packagedAppPath, 'resources', 'LICENSE.md'), path.join(debianPackageDocsDirPath, 'copyright') - ) + ); - console.log(`Copying lintian overrides into "${debianPackageLintianOverridesDirPath}"`) + console.log( + `Copying lintian overrides into "${debianPackageLintianOverridesDirPath}"` + ); fs.copySync( - path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'debian', 'lintian-overrides'), + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'linux', + 'debian', + 'lintian-overrides' + ), path.join(debianPackageLintianOverridesDirPath, atomExecutableName) - ) + ); - console.log(`Copying polkit configuration into "${debianPackageShareDirPath}"`) + console.log( + `Copying polkit configuration into "${debianPackageShareDirPath}"` + ); fs.copySync( path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.policy'), path.join(debianPackageShareDirPath, 'polkit-1', 'actions', 'atom.policy') - ) + ); - console.log(`Generating .deb file from ${debianPackageDirPath}`) - spawnSync('fakeroot', ['dpkg-deb', '-b', debianPackageDirPath], {stdio: 'inherit'}) + console.log(`Generating .deb file from ${debianPackageDirPath}`); + spawnSync('fakeroot', ['dpkg-deb', '-b', debianPackageDirPath], { + stdio: 'inherit' + }); - console.log(`Copying generated package into "${outputDebianPackageFilePath}"`) - fs.copySync(`${debianPackageDirPath}.deb`, outputDebianPackageFilePath) -} + console.log( + `Copying generated package into "${outputDebianPackageFilePath}"` + ); + fs.copySync(`${debianPackageDirPath}.deb`, outputDebianPackageFilePath); +}; diff --git a/script/lib/create-rpm-package.js b/script/lib/create-rpm-package.js index 22068dcd3..52f33d32a 100644 --- a/script/lib/create-rpm-package.js +++ b/script/lib/create-rpm-package.js @@ -1,54 +1,79 @@ -'use strict' +'use strict'; -const assert = require('assert') -const fs = require('fs-extra') -const path = require('path') -const spawnSync = require('./spawn-sync') -const template = require('lodash.template') +const assert = require('assert'); +const fs = require('fs-extra'); +const path = require('path'); +const spawnSync = require('./spawn-sync'); +const template = require('lodash.template'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (packagedAppPath) { - console.log(`Creating rpm package for "${packagedAppPath}"`) - const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}` - const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}` - const appName = CONFIG.appName - const appDescription = CONFIG.appMetadata.description +module.exports = function(packagedAppPath) { + console.log(`Creating rpm package for "${packagedAppPath}"`); + const atomExecutableName = + CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`; + const apmExecutableName = + CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}`; + const appName = CONFIG.appName; + const appDescription = CONFIG.appMetadata.description; // RPM versions can't have dashes or tildes in them. // (Ref.: https://twiki.cern.ch/twiki/bin/view/Main/RPMAndDebVersioning) - const appVersion = CONFIG.appMetadata.version.replace(/-/g, '.') + const appVersion = CONFIG.appMetadata.version.replace(/-/g, '.'); - const rpmPackageDirPath = path.join(CONFIG.homeDirPath, 'rpmbuild') - const rpmPackageBuildDirPath = path.join(rpmPackageDirPath, 'BUILD') - const rpmPackageSourcesDirPath = path.join(rpmPackageDirPath, 'SOURCES') - const rpmPackageSpecsDirPath = path.join(rpmPackageDirPath, 'SPECS') - const rpmPackageRpmsDirPath = path.join(rpmPackageDirPath, 'RPMS') - const rpmPackageApplicationDirPath = path.join(rpmPackageBuildDirPath, appName) - const rpmPackageIconsDirPath = path.join(rpmPackageBuildDirPath, 'icons') + const rpmPackageDirPath = path.join(CONFIG.homeDirPath, 'rpmbuild'); + const rpmPackageBuildDirPath = path.join(rpmPackageDirPath, 'BUILD'); + const rpmPackageSourcesDirPath = path.join(rpmPackageDirPath, 'SOURCES'); + const rpmPackageSpecsDirPath = path.join(rpmPackageDirPath, 'SPECS'); + const rpmPackageRpmsDirPath = path.join(rpmPackageDirPath, 'RPMS'); + const rpmPackageApplicationDirPath = path.join( + rpmPackageBuildDirPath, + appName + ); + const rpmPackageIconsDirPath = path.join(rpmPackageBuildDirPath, 'icons'); if (fs.existsSync(rpmPackageDirPath)) { - console.log(`Deleting existing rpm build directory at "${rpmPackageDirPath}"`) - fs.removeSync(rpmPackageDirPath) + console.log( + `Deleting existing rpm build directory at "${rpmPackageDirPath}"` + ); + fs.removeSync(rpmPackageDirPath); } - console.log(`Creating rpm package directory structure at "${rpmPackageDirPath}"`) - fs.mkdirpSync(rpmPackageDirPath) - fs.mkdirpSync(rpmPackageBuildDirPath) - fs.mkdirpSync(rpmPackageSourcesDirPath) - fs.mkdirpSync(rpmPackageSpecsDirPath) + console.log( + `Creating rpm package directory structure at "${rpmPackageDirPath}"` + ); + fs.mkdirpSync(rpmPackageDirPath); + fs.mkdirpSync(rpmPackageBuildDirPath); + fs.mkdirpSync(rpmPackageSourcesDirPath); + fs.mkdirpSync(rpmPackageSpecsDirPath); - console.log(`Copying "${packagedAppPath}" to "${rpmPackageApplicationDirPath}"`) - fs.copySync(packagedAppPath, rpmPackageApplicationDirPath) + console.log( + `Copying "${packagedAppPath}" to "${rpmPackageApplicationDirPath}"` + ); + fs.copySync(packagedAppPath, rpmPackageApplicationDirPath); - console.log(`Copying icons into "${rpmPackageIconsDirPath}"`) + console.log(`Copying icons into "${rpmPackageIconsDirPath}"`); fs.copySync( - path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png'), + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'app-icons', + CONFIG.channel, + 'png' + ), rpmPackageIconsDirPath - ) + ); - console.log(`Writing rpm package spec file into "${rpmPackageSpecsDirPath}"`) - const rpmPackageSpecFilePath = path.join(rpmPackageSpecsDirPath, 'atom.spec') - const rpmPackageSpecsTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'redhat', 'atom.spec.in')) + console.log(`Writing rpm package spec file into "${rpmPackageSpecsDirPath}"`); + const rpmPackageSpecFilePath = path.join(rpmPackageSpecsDirPath, 'atom.spec'); + const rpmPackageSpecsTemplate = fs.readFileSync( + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'linux', + 'redhat', + 'atom.spec.in' + ) + ); const rpmPackageSpecsContents = template(rpmPackageSpecsTemplate)({ appName: appName, appFileName: atomExecutableName, @@ -56,41 +81,65 @@ module.exports = function (packagedAppPath) { description: appDescription, installDir: '/usr', version: appVersion - }) - fs.writeFileSync(rpmPackageSpecFilePath, rpmPackageSpecsContents) + }); + fs.writeFileSync(rpmPackageSpecFilePath, rpmPackageSpecsContents); - console.log(`Writing desktop entry file into "${rpmPackageBuildDirPath}"`) - const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in')) + console.log(`Writing desktop entry file into "${rpmPackageBuildDirPath}"`); + const desktopEntryTemplate = fs.readFileSync( + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'linux', + 'atom.desktop.in' + ) + ); const desktopEntryContents = template(desktopEntryTemplate)({ appName: appName, appFileName: atomExecutableName, description: appDescription, installDir: '/usr', iconPath: atomExecutableName - }) - fs.writeFileSync(path.join(rpmPackageBuildDirPath, `${atomExecutableName}.desktop`), desktopEntryContents) + }); + fs.writeFileSync( + path.join(rpmPackageBuildDirPath, `${atomExecutableName}.desktop`), + desktopEntryContents + ); - console.log(`Copying atom.sh into "${rpmPackageBuildDirPath}"`) + console.log(`Copying atom.sh into "${rpmPackageBuildDirPath}"`); fs.copySync( path.join(CONFIG.repositoryRootPath, 'atom.sh'), path.join(rpmPackageBuildDirPath, 'atom.sh') - ) + ); - console.log(`Copying atom.policy into "${rpmPackageBuildDirPath}"`) + console.log(`Copying atom.policy into "${rpmPackageBuildDirPath}"`); fs.copySync( path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.policy'), path.join(rpmPackageBuildDirPath, 'atom.policy') - ) + ); - console.log(`Generating .rpm package from "${rpmPackageDirPath}"`) - spawnSync('rpmbuild', ['-ba', '--clean', rpmPackageSpecFilePath]) + console.log(`Generating .rpm package from "${rpmPackageDirPath}"`); + spawnSync('rpmbuild', ['-ba', '--clean', rpmPackageSpecFilePath]); for (let generatedArch of fs.readdirSync(rpmPackageRpmsDirPath)) { - const generatedArchDirPath = path.join(rpmPackageRpmsDirPath, generatedArch) - const generatedPackageFileNames = fs.readdirSync(generatedArchDirPath) - assert(generatedPackageFileNames.length === 1, 'Generated more than one rpm package') - const generatedPackageFilePath = path.join(generatedArchDirPath, generatedPackageFileNames[0]) - const outputRpmPackageFilePath = path.join(CONFIG.buildOutputPath, `atom.${generatedArch}.rpm`) - console.log(`Copying "${generatedPackageFilePath}" into "${outputRpmPackageFilePath}"`) - fs.copySync(generatedPackageFilePath, outputRpmPackageFilePath) + const generatedArchDirPath = path.join( + rpmPackageRpmsDirPath, + generatedArch + ); + const generatedPackageFileNames = fs.readdirSync(generatedArchDirPath); + assert( + generatedPackageFileNames.length === 1, + 'Generated more than one rpm package' + ); + const generatedPackageFilePath = path.join( + generatedArchDirPath, + generatedPackageFileNames[0] + ); + const outputRpmPackageFilePath = path.join( + CONFIG.buildOutputPath, + `atom.${generatedArch}.rpm` + ); + console.log( + `Copying "${generatedPackageFilePath}" into "${outputRpmPackageFilePath}"` + ); + fs.copySync(generatedPackageFilePath, outputRpmPackageFilePath); } -} +}; diff --git a/script/lib/create-windows-installer.js b/script/lib/create-windows-installer.js index 6b4827424..1cdd7c0f4 100644 --- a/script/lib/create-windows-installer.js +++ b/script/lib/create-windows-installer.js @@ -1,40 +1,58 @@ -'use strict' +'use strict'; -const electronInstaller = require('electron-winstaller') -const fs = require('fs') -const glob = require('glob') -const path = require('path') +const electronInstaller = require('electron-winstaller'); +const fs = require('fs'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = (packagedAppPath) => { - const archSuffix = process.arch === 'ia32' ? '' : '-' + process.arch - const updateUrlPrefix = process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io' +module.exports = packagedAppPath => { + const archSuffix = process.arch === 'ia32' ? '' : '-' + process.arch; + const updateUrlPrefix = + process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io'; const options = { title: CONFIG.appName, appDirectory: packagedAppPath, authors: 'GitHub Inc.', - iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${CONFIG.channel}/atom.ico`, - loadingGif: path.join(CONFIG.repositoryRootPath, 'resources', 'win', 'loading.gif'), + iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${ + CONFIG.channel + }/atom.ico`, + loadingGif: path.join( + CONFIG.repositoryRootPath, + 'resources', + 'win', + 'loading.gif' + ), outputDirectory: CONFIG.buildOutputPath, noMsi: true, noDelta: CONFIG.channel === 'nightly', // Delta packages are broken for nightly versions past nightly9 due to Squirrel/NuGet limitations - remoteReleases: `${updateUrlPrefix}/api/updates${archSuffix}?version=${CONFIG.computedAppVersion}`, + remoteReleases: `${updateUrlPrefix}/api/updates${archSuffix}?version=${ + CONFIG.computedAppVersion + }`, setupExe: `AtomSetup${process.arch === 'x64' ? '-x64' : ''}.exe`, - setupIcon: path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom.ico') - } + setupIcon: path.join( + CONFIG.repositoryRootPath, + 'resources', + 'app-icons', + CONFIG.channel, + 'atom.ico' + ) + }; const cleanUp = () => { - const releasesPath = `${CONFIG.buildOutputPath}/RELEASES` + const releasesPath = `${CONFIG.buildOutputPath}/RELEASES`; if (process.arch === 'x64' && fs.existsSync(releasesPath)) { - fs.renameSync(releasesPath, `${releasesPath}-x64`) + fs.renameSync(releasesPath, `${releasesPath}-x64`); } let appName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}` for (let nupkgPath of glob.sync(`${CONFIG.buildOutputPath}/${appName}-*.nupkg`)) { if (!nupkgPath.includes(CONFIG.computedAppVersion)) { - console.log(`Deleting downloaded nupkg for previous version at ${nupkgPath} to prevent it from being stored as an artifact`) - fs.unlinkSync(nupkgPath) + console.log( + `Deleting downloaded nupkg for previous version at ${nupkgPath} to prevent it from being stored as an artifact` + ); + fs.unlinkSync(nupkgPath); } else { if (process.arch === 'x64') { // Use the original .nupkg filename to generate the `atom-x64` name by inserting `-x64` after `atom` @@ -44,13 +62,14 @@ module.exports = (packagedAppPath) => { } } - return `${CONFIG.buildOutputPath}/${options.setupExe}` - } + return `${CONFIG.buildOutputPath}/${options.setupExe}`; + }; - console.log(`Creating Windows Installer for ${packagedAppPath}`) - return electronInstaller.createWindowsInstaller(options) + console.log(`Creating Windows Installer for ${packagedAppPath}`); + return electronInstaller + .createWindowsInstaller(options) .then(cleanUp, error => { - cleanUp() - return Promise.reject(error) - }) -} + cleanUp(); + return Promise.reject(error); + }); +}; diff --git a/script/lib/delete-msbuild-from-path.js b/script/lib/delete-msbuild-from-path.js index 54fa1c082..034ba4542 100644 --- a/script/lib/delete-msbuild-from-path.js +++ b/script/lib/delete-msbuild-from-path.js @@ -1,19 +1,22 @@ -'use strict' +'use strict'; -const fs = require('fs') -const path = require('path') +const fs = require('fs'); +const path = require('path'); -module.exports = function () { - process.env['PATH'] = - process.env['PATH'] - .split(';') - .filter(function (p) { - if (fs.existsSync(path.join(p, 'msbuild.exe'))) { - console.log('Excluding "' + p + '" from PATH to avoid msbuild.exe mismatch that causes errors during module installation') - return false - } else { - return true - } - }) - .join(';') -} +module.exports = function() { + process.env['PATH'] = process.env['PATH'] + .split(';') + .filter(function(p) { + if (fs.existsSync(path.join(p, 'msbuild.exe'))) { + console.log( + 'Excluding "' + + p + + '" from PATH to avoid msbuild.exe mismatch that causes errors during module installation' + ); + return false; + } else { + return true; + } + }) + .join(';'); +}; diff --git a/script/lib/dependencies-fingerprint.js b/script/lib/dependencies-fingerprint.js index 52b5d170a..ad26c5ed2 100644 --- a/script/lib/dependencies-fingerprint.js +++ b/script/lib/dependencies-fingerprint.js @@ -1,28 +1,49 @@ -const crypto = require('crypto') -const fs = require('fs') -const path = require('path') +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); -const CONFIG = require('../config') -const FINGERPRINT_PATH = path.join(CONFIG.repositoryRootPath, 'node_modules', '.dependencies-fingerprint') +const CONFIG = require('../config'); +const FINGERPRINT_PATH = path.join( + CONFIG.repositoryRootPath, + 'node_modules', + '.dependencies-fingerprint' +); module.exports = { - write: function () { - const fingerprint = this.compute() - fs.writeFileSync(FINGERPRINT_PATH, fingerprint) - console.log('Wrote Dependencies Fingerprint:', FINGERPRINT_PATH, fingerprint) + write: function() { + const fingerprint = this.compute(); + fs.writeFileSync(FINGERPRINT_PATH, fingerprint); + console.log( + 'Wrote Dependencies Fingerprint:', + FINGERPRINT_PATH, + fingerprint + ); }, - read: function () { - return fs.existsSync(FINGERPRINT_PATH) ? fs.readFileSync(FINGERPRINT_PATH, 'utf8') : null + read: function() { + return fs.existsSync(FINGERPRINT_PATH) + ? fs.readFileSync(FINGERPRINT_PATH, 'utf8') + : null; }, - isOutdated: function () { - const fingerprint = this.read() - return fingerprint ? fingerprint !== this.compute() : false + isOutdated: function() { + const fingerprint = this.read(); + return fingerprint ? fingerprint !== this.compute() : false; }, - compute: function () { + compute: function() { // Include the electron minor version in the fingerprint since that changing requires a re-install - const electronVersion = CONFIG.appMetadata.electronVersion.replace(/\.\d+$/, '') - const apmVersion = CONFIG.apmMetadata.dependencies['atom-package-manager'] - const body = electronVersion + apmVersion + process.platform + process.version + process.arch - return crypto.createHash('sha1').update(body).digest('hex') + const electronVersion = CONFIG.appMetadata.electronVersion.replace( + /\.\d+$/, + '' + ); + const apmVersion = CONFIG.apmMetadata.dependencies['atom-package-manager']; + const body = + electronVersion + + apmVersion + + process.platform + + process.version + + process.arch; + return crypto + .createHash('sha1') + .update(body) + .digest('hex'); } -} +}; diff --git a/script/lib/download-file-from-github.js b/script/lib/download-file-from-github.js index 13e04e99e..650b3c480 100644 --- a/script/lib/download-file-from-github.js +++ b/script/lib/download-file-from-github.js @@ -1,19 +1,24 @@ -'use strict' +'use strict'; -const fs = require('fs-extra') -const path = require('path') -const syncRequest = require('sync-request') +const fs = require('fs-extra'); +const path = require('path'); +const syncRequest = require('sync-request'); -module.exports = function (downloadURL, destinationPath) { - console.log(`Downloading file from GitHub Repository to ${destinationPath}`) +module.exports = function(downloadURL, 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'} - }) + headers: { + Accept: 'application/vnd.github.v3.raw', + 'User-Agent': 'Atom Build' + } + }); if (response.statusCode === 200) { - fs.mkdirpSync(path.dirname(destinationPath)) - fs.writeFileSync(destinationPath, response.body) + fs.mkdirpSync(path.dirname(destinationPath)); + fs.writeFileSync(destinationPath, response.body); } else { - throw new Error('Error downloading file. HTTP Status ' + response.statusCode + '.') + throw new Error( + 'Error downloading file. HTTP Status ' + response.statusCode + '.' + ); } -} +}; diff --git a/script/lib/dump-symbols.js b/script/lib/dump-symbols.js index 9b7492ed7..f454d574c 100644 --- a/script/lib/dump-symbols.js +++ b/script/lib/dump-symbols.js @@ -1,44 +1,55 @@ -'use strict' +'use strict'; -const fs = require('fs-extra') -const glob = require('glob') -const path = require('path') +const fs = require('fs-extra'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') -module.exports = function () { +const CONFIG = require('../config'); +module.exports = function() { if (process.platform === 'win32') { - console.log('Skipping symbol dumping because minidump is not supported on Windows'.gray) - return Promise.resolve() + console.log( + 'Skipping symbol dumping because minidump is not supported on Windows' + .gray + ); + return Promise.resolve(); } else { - console.log(`Dumping symbols in ${CONFIG.symbolsPath}`) - const binaryPaths = glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', '**', '*.node')) - return Promise.all(binaryPaths.map(dumpSymbol)) + console.log(`Dumping symbols in ${CONFIG.symbolsPath}`); + const binaryPaths = glob.sync( + path.join(CONFIG.intermediateAppPath, 'node_modules', '**', '*.node') + ); + return Promise.all(binaryPaths.map(dumpSymbol)); } -} +}; -function dumpSymbol (binaryPath) { - const minidump = require('minidump') +function dumpSymbol(binaryPath) { + const minidump = require('minidump'); - return new Promise(function (resolve, reject) { - minidump.dumpSymbol(binaryPath, function (error, content) { + return new Promise(function(resolve, reject) { + minidump.dumpSymbol(binaryPath, function(error, content) { if (error) { - console.error(error) - throw new Error(error) + console.error(error); + throw new Error(error); } else { - const moduleLine = /MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n/.exec(content) + const moduleLine = /MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n/.exec( + content + ); if (moduleLine.length !== 3) { - const errorMessage = `Invalid output when dumping symbol for ${binaryPath}` - console.error(errorMessage) - throw new Error(errorMessage) + const errorMessage = `Invalid output when dumping symbol for ${binaryPath}`; + console.error(errorMessage); + throw new Error(errorMessage); } else { - const filename = moduleLine[2] - const symbolDirPath = path.join(CONFIG.symbolsPath, filename, moduleLine[1]) - const symbolFilePath = path.join(symbolDirPath, `${filename}.sym`) - fs.mkdirpSync(symbolDirPath) - fs.writeFileSync(symbolFilePath, content) - resolve() + const filename = moduleLine[2]; + const symbolDirPath = path.join( + CONFIG.symbolsPath, + filename, + moduleLine[1] + ); + const symbolFilePath = path.join(symbolDirPath, `${filename}.sym`); + fs.mkdirpSync(symbolDirPath); + fs.writeFileSync(symbolFilePath, content); + resolve(); } } - }) - }) + }); + }); } diff --git a/script/lib/expand-glob-paths.js b/script/lib/expand-glob-paths.js index 3d47a6dac..9cf351823 100644 --- a/script/lib/expand-glob-paths.js +++ b/script/lib/expand-glob-paths.js @@ -1,19 +1,21 @@ -'use strict' +'use strict'; -const glob = require('glob') +const glob = require('glob'); -module.exports = function (globPaths) { - return Promise.all(globPaths.map(g => expandGlobPath(g))).then(paths => paths.reduce((a, b) => a.concat(b), [])) -} +module.exports = function(globPaths) { + return Promise.all(globPaths.map(g => expandGlobPath(g))).then(paths => + paths.reduce((a, b) => a.concat(b), []) + ); +}; -function expandGlobPath (globPath) { +function expandGlobPath(globPath) { return new Promise((resolve, reject) => { glob(globPath, (error, paths) => { if (error) { - reject(error) + reject(error); } else { - resolve(paths) + resolve(paths); } - }) - }) + }); + }); } diff --git a/script/lib/generate-api-docs.js b/script/lib/generate-api-docs.js index 6e985edd2..ad39bea7d 100644 --- a/script/lib/generate-api-docs.js +++ b/script/lib/generate-api-docs.js @@ -1,53 +1,55 @@ -'use strict' +'use strict'; -const donna = require('donna') -const tello = require('tello') -const joanna = require('joanna') -const glob = require('glob') -const fs = require('fs-extra') -const path = require('path') +const donna = require('donna'); +const tello = require('tello'); +const joanna = require('joanna'); +const glob = require('glob'); +const fs = require('fs-extra'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { - const generatedJSONPath = path.join(CONFIG.docsOutputPath, 'atom-api.json') - console.log(`Generating API docs at ${generatedJSONPath}`) +module.exports = function() { + const generatedJSONPath = path.join(CONFIG.docsOutputPath, 'atom-api.json'); + console.log(`Generating API docs at ${generatedJSONPath}`); // Unfortunately, correct relative paths depend on a specific working // directory, but this script should be able to run from anywhere, so we // muck with the cwd temporarily. - const oldWorkingDirectoryPath = process.cwd() - process.chdir(CONFIG.repositoryRootPath) - const coffeeMetadata = donna.generateMetadata(['.'])[0] - const jsMetadata = joanna(glob.sync(`src/**/*.js`)) - process.chdir(oldWorkingDirectoryPath) + const oldWorkingDirectoryPath = process.cwd(); + process.chdir(CONFIG.repositoryRootPath); + const coffeeMetadata = donna.generateMetadata(['.'])[0]; + const jsMetadata = joanna(glob.sync(`src/**/*.js`)); + process.chdir(oldWorkingDirectoryPath); const metadata = { repository: coffeeMetadata.repository, version: coffeeMetadata.version, files: Object.assign(coffeeMetadata.files, jsMetadata.files) + }; + + const api = tello.digest([metadata]); + Object.assign(api.classes, getAPIDocsForDependencies()); + api.classes = sortObjectByKey(api.classes); + + fs.mkdirpSync(CONFIG.docsOutputPath); + fs.writeFileSync(generatedJSONPath, JSON.stringify(api, null, 2)); +}; + +function getAPIDocsForDependencies() { + const classes = {}; + for (let apiJSONPath of glob.sync( + `${CONFIG.repositoryRootPath}/node_modules/*/api.json` + )) { + Object.assign(classes, require(apiJSONPath).classes); } - - const api = tello.digest([metadata]) - Object.assign(api.classes, getAPIDocsForDependencies()) - api.classes = sortObjectByKey(api.classes) - - fs.mkdirpSync(CONFIG.docsOutputPath) - fs.writeFileSync(generatedJSONPath, JSON.stringify(api, null, 2)) + return classes; } -function getAPIDocsForDependencies () { - const classes = {} - for (let apiJSONPath of glob.sync(`${CONFIG.repositoryRootPath}/node_modules/*/api.json`)) { - Object.assign(classes, require(apiJSONPath).classes) - } - return classes -} - -function sortObjectByKey (object) { - const sortedObject = {} +function sortObjectByKey(object) { + const sortedObject = {}; for (let keyName of Object.keys(object).sort()) { - sortedObject[keyName] = object[keyName] + sortedObject[keyName] = object[keyName]; } - return sortedObject + return sortedObject; } diff --git a/script/lib/generate-metadata.js b/script/lib/generate-metadata.js index b0579cc7a..165dfd623 100644 --- a/script/lib/generate-metadata.js +++ b/script/lib/generate-metadata.js @@ -29,146 +29,255 @@ module.exports = function () { fs.writeFileSync(path.join(CONFIG.intermediateAppPath, 'package.json'), JSON.stringify(CONFIG.appMetadata)) } -function buildBundledPackagesMetadata () { - const packages = {} +module.exports = function() { + console.log( + `Generating metadata for ${path.join( + CONFIG.intermediateAppPath, + 'package.json' + )}` + ); + CONFIG.appMetadata._atomPackages = buildBundledPackagesMetadata(); + CONFIG.appMetadata._atomMenu = buildPlatformMenuMetadata(); + CONFIG.appMetadata._atomKeymaps = buildPlatformKeymapsMetadata(); + CONFIG.appMetadata._deprecatedPackages = deprecatedPackagesMetadata; + CONFIG.appMetadata.version = CONFIG.computedAppVersion; + checkDeprecatedPackagesMetadata(); + fs.writeFileSync( + path.join(CONFIG.intermediateAppPath, 'package.json'), + JSON.stringify(CONFIG.appMetadata) + ); +}; + +function buildBundledPackagesMetadata() { + const packages = {}; for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - 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, (msg) => { - if (!msg.match(/No README data$/)) { - 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\+/, '') + 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, + msg => { + if (!msg.match(/No README data$/)) { + 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\+/, + '' + ); } - delete packageMetadata['_from'] - delete packageMetadata['_id'] - delete packageMetadata['dist'] - delete packageMetadata['readme'] - delete packageMetadata['readmeFilename'] + delete packageMetadata['_from']; + delete packageMetadata['_id']; + delete packageMetadata['dist']; + delete packageMetadata['readme']; + delete packageMetadata['readmeFilename']; - const packageModuleCache = packageMetadata._atomModuleCache || {} - if (packageModuleCache.extensions && packageModuleCache.extensions['.json']) { - const index = packageModuleCache.extensions['.json'].indexOf('package.json') + const packageModuleCache = packageMetadata._atomModuleCache || {}; + if ( + packageModuleCache.extensions && + packageModuleCache.extensions['.json'] + ) { + const index = packageModuleCache.extensions['.json'].indexOf( + 'package.json' + ); if (index !== -1) { - packageModuleCache.extensions['.json'].splice(index, 1) + packageModuleCache.extensions['.json'].splice(index, 1); } } - const packageNewMetadata = {metadata: packageMetadata, keymaps: {}, menus: {}, grammarPaths: [], settings: {}} + const packageNewMetadata = { + metadata: packageMetadata, + keymaps: {}, + menus: {}, + grammarPaths: [], + settings: {} + }; - packageNewMetadata.rootDirPath = path.relative(CONFIG.intermediateAppPath, packagePath) + packageNewMetadata.rootDirPath = path.relative( + CONFIG.intermediateAppPath, + packagePath + ); if (packageMetadata.main) { - const mainPath = require.resolve(path.resolve(packagePath, packageMetadata.main)) - packageNewMetadata.main = path.relative(path.join(CONFIG.intermediateAppPath, 'static'), mainPath) + const mainPath = require.resolve( + path.resolve(packagePath, packageMetadata.main) + ); + packageNewMetadata.main = path.relative( + path.join(CONFIG.intermediateAppPath, 'static'), + mainPath + ); // Convert backward slashes to forward slashes in order to allow package // main modules to be required from the snapshot. This is because we use // forward slashes to cache the sources in the snapshot, so we need to use // them here as well. - packageNewMetadata.main = packageNewMetadata.main.replace(/\\/g, '/') + packageNewMetadata.main = packageNewMetadata.main.replace(/\\/g, '/'); } - const packageKeymapsPath = path.join(packagePath, 'keymaps') + const packageKeymapsPath = path.join(packagePath, 'keymaps'); if (fs.existsSync(packageKeymapsPath)) { for (let packageKeymapName of fs.readdirSync(packageKeymapsPath)) { - const packageKeymapPath = path.join(packageKeymapsPath, packageKeymapName) - if (packageKeymapPath.endsWith('.cson') || packageKeymapPath.endsWith('.json')) { - const relativePath = path.relative(CONFIG.intermediateAppPath, packageKeymapPath) - packageNewMetadata.keymaps[relativePath] = CSON.readFileSync(packageKeymapPath) + const packageKeymapPath = path.join( + packageKeymapsPath, + packageKeymapName + ); + if ( + packageKeymapPath.endsWith('.cson') || + packageKeymapPath.endsWith('.json') + ) { + const relativePath = path.relative( + CONFIG.intermediateAppPath, + packageKeymapPath + ); + packageNewMetadata.keymaps[relativePath] = CSON.readFileSync( + packageKeymapPath + ); } } } - const packageMenusPath = path.join(packagePath, 'menus') + const packageMenusPath = path.join(packagePath, 'menus'); if (fs.existsSync(packageMenusPath)) { for (let packageMenuName of fs.readdirSync(packageMenusPath)) { - const packageMenuPath = path.join(packageMenusPath, packageMenuName) - if (packageMenuPath.endsWith('.cson') || packageMenuPath.endsWith('.json')) { - const relativePath = path.relative(CONFIG.intermediateAppPath, packageMenuPath) - packageNewMetadata.menus[relativePath] = CSON.readFileSync(packageMenuPath) + const packageMenuPath = path.join(packageMenusPath, packageMenuName); + if ( + packageMenuPath.endsWith('.cson') || + packageMenuPath.endsWith('.json') + ) { + const relativePath = path.relative( + CONFIG.intermediateAppPath, + packageMenuPath + ); + packageNewMetadata.menus[relativePath] = CSON.readFileSync( + packageMenuPath + ); } } } - const packageGrammarsPath = path.join(packagePath, 'grammars') - for (let packageGrammarPath of fs.listSync(packageGrammarsPath, ['json', 'cson'])) { - const relativePath = path.relative(CONFIG.intermediateAppPath, packageGrammarPath) - packageNewMetadata.grammarPaths.push(relativePath) + const packageGrammarsPath = path.join(packagePath, 'grammars'); + for (let packageGrammarPath of fs.listSync(packageGrammarsPath, [ + 'json', + 'cson' + ])) { + const relativePath = path.relative( + CONFIG.intermediateAppPath, + packageGrammarPath + ); + packageNewMetadata.grammarPaths.push(relativePath); } - const packageSettingsPath = path.join(packagePath, 'settings') - for (let packageSettingPath of fs.listSync(packageSettingsPath, ['json', 'cson'])) { - const relativePath = path.relative(CONFIG.intermediateAppPath, packageSettingPath) - packageNewMetadata.settings[relativePath] = CSON.readFileSync(packageSettingPath) + const packageSettingsPath = path.join(packagePath, 'settings'); + for (let packageSettingPath of fs.listSync(packageSettingsPath, [ + 'json', + 'cson' + ])) { + const relativePath = path.relative( + CONFIG.intermediateAppPath, + packageSettingPath + ); + packageNewMetadata.settings[relativePath] = CSON.readFileSync( + packageSettingPath + ); } - const packageStyleSheetsPath = path.join(packagePath, 'styles') - let styleSheets = null + const packageStyleSheetsPath = path.join(packagePath, 'styles'); + let styleSheets = null; if (packageMetadata.mainStyleSheet) { - styleSheets = [fs.resolve(packagePath, packageMetadata.mainStyleSheet)] + styleSheets = [fs.resolve(packagePath, packageMetadata.mainStyleSheet)]; } else if (packageMetadata.styleSheets) { - styleSheets = packageMetadata.styleSheets.map((name) => ( + styleSheets = packageMetadata.styleSheets.map(name => fs.resolve(packageStyleSheetsPath, name, ['css', 'less', '']) - )) + ); } else { - const indexStylesheet = fs.resolve(packagePath, 'index', ['css', 'less']) + const indexStylesheet = fs.resolve(packagePath, 'index', ['css', 'less']); if (indexStylesheet) { - styleSheets = [indexStylesheet] + styleSheets = [indexStylesheet]; } else { - styleSheets = fs.listSync(packageStyleSheetsPath, ['css', 'less']) + styleSheets = fs.listSync(packageStyleSheetsPath, ['css', 'less']); } } - packageNewMetadata.styleSheetPaths = - styleSheets.map(styleSheetPath => path.relative(packagePath, styleSheetPath)) + packageNewMetadata.styleSheetPaths = styleSheets.map(styleSheetPath => + path.relative(packagePath, styleSheetPath) + ); - packages[packageMetadata.name] = packageNewMetadata + packages[packageMetadata.name] = packageNewMetadata; if (packageModuleCache.extensions) { for (let extension of Object.keys(packageModuleCache.extensions)) { - const paths = packageModuleCache.extensions[extension] + const paths = packageModuleCache.extensions[extension]; if (paths.length === 0) { - delete packageModuleCache.extensions[extension] + delete packageModuleCache.extensions[extension]; } } } } - return packages + return packages; } -function buildPlatformMenuMetadata () { - const menuPath = path.join(CONFIG.repositoryRootPath, 'menus', `${process.platform}.cson`) +function buildPlatformMenuMetadata() { + const menuPath = path.join( + CONFIG.repositoryRootPath, + 'menus', + `${process.platform}.cson` + ); if (fs.existsSync(menuPath)) { - return CSON.readFileSync(menuPath) + return CSON.readFileSync(menuPath); } else { - return null + return null; } } -function buildPlatformKeymapsMetadata () { - const invalidPlatforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32'].filter(p => p !== process.platform) - const keymapsPath = path.join(CONFIG.repositoryRootPath, 'keymaps') - const keymaps = {} +function buildPlatformKeymapsMetadata() { + const invalidPlatforms = [ + 'darwin', + 'freebsd', + 'linux', + 'sunos', + 'win32' + ].filter(p => p !== process.platform); + const keymapsPath = path.join(CONFIG.repositoryRootPath, 'keymaps'); + const keymaps = {}; for (let keymapName of fs.readdirSync(keymapsPath)) { - const keymapPath = path.join(keymapsPath, keymapName) + const keymapPath = path.join(keymapsPath, keymapName); if (keymapPath.endsWith('.cson') || keymapPath.endsWith('.json')) { - const keymapPlatform = path.basename(keymapPath, path.extname(keymapPath)) + const keymapPlatform = path.basename( + keymapPath, + path.extname(keymapPath) + ); if (invalidPlatforms.indexOf(keymapPlatform) === -1) { - keymaps[path.basename(keymapPath)] = CSON.readFileSync(keymapPath) + keymaps[path.basename(keymapPath)] = CSON.readFileSync(keymapPath); } } } - return keymaps + return keymaps; } -function checkDeprecatedPackagesMetadata () { +function checkDeprecatedPackagesMetadata() { for (let packageName of Object.keys(deprecatedPackagesMetadata)) { - const packageMetadata = deprecatedPackagesMetadata[packageName] - if (packageMetadata.version && !semver.validRange(packageMetadata.version)) { - throw new Error(`Invalid range: ${packageMetadata.version} (${packageName}).`) + const packageMetadata = deprecatedPackagesMetadata[packageName]; + if ( + packageMetadata.version && + !semver.validRange(packageMetadata.version) + ) { + throw new Error( + `Invalid range: ${packageMetadata.version} (${packageName}).` + ); } } } diff --git a/script/lib/generate-module-cache.js b/script/lib/generate-module-cache.js index 940dc24a1..93ee7bebb 100644 --- a/script/lib/generate-module-cache.js +++ b/script/lib/generate-module-cache.js @@ -1,18 +1,22 @@ -'use strict' +'use strict'; -const fs = require('fs') -const path = require('path') -const ModuleCache = require('../../src/module-cache') +const fs = require('fs'); +const path = require('path'); +const ModuleCache = require('../../src/module-cache'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { - console.log(`Generating module cache for ${CONFIG.intermediateAppPath}`) +module.exports = function() { + console.log(`Generating module cache for ${CONFIG.intermediateAppPath}`); for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - ModuleCache.create(path.join(CONFIG.intermediateAppPath, 'node_modules', packageName)) + ModuleCache.create( + path.join(CONFIG.intermediateAppPath, 'node_modules', packageName) + ); } - ModuleCache.create(CONFIG.intermediateAppPath) - const newMetadata = JSON.parse(fs.readFileSync(path.join(CONFIG.intermediateAppPath, 'package.json'))) + ModuleCache.create(CONFIG.intermediateAppPath); + const newMetadata = JSON.parse( + fs.readFileSync(path.join(CONFIG.intermediateAppPath, 'package.json')) + ); for (let folder of newMetadata._atomModuleCache.folders) { if (folder.paths.indexOf('') !== -1) { folder.paths = [ @@ -23,9 +27,12 @@ module.exports = function () { 'src/main-process', 'static', 'vendor' - ] + ]; } } - CONFIG.appMetadata = newMetadata - fs.writeFileSync(path.join(CONFIG.intermediateAppPath, 'package.json'), JSON.stringify(CONFIG.appMetadata)) -} + CONFIG.appMetadata = newMetadata; + fs.writeFileSync( + path.join(CONFIG.intermediateAppPath, 'package.json'), + JSON.stringify(CONFIG.appMetadata) + ); +}; diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index a54ad0de6..0888e51a4 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -1,96 +1,262 @@ -const childProcess = require('child_process') -const fs = require('fs') -const path = require('path') -const electronLink = require('electron-link') -const terser = require('terser') -const CONFIG = require('../config') +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const electronLink = require('electron-link'); +const terser = require('terser'); +const CONFIG = require('../config'); -module.exports = function (packagedAppPath) { - const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js') - const coreModules = new Set(['electron', 'atom', 'shell', 'WNdb', 'lapack', 'remote']) - const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') - let processedFiles = 0 +module.exports = function(packagedAppPath) { + const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js'); + const coreModules = new Set([ + 'electron', + 'atom', + 'shell', + 'WNdb', + 'lapack', + 'remote' + ]); + const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static'); + let processedFiles = 0; return electronLink({ baseDirPath, - mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'), + mainPath: path.resolve( + baseDirPath, + '..', + 'src', + 'initialize-application-window.js' + ), cachePath: path.join(CONFIG.atomHomeDirPath, 'snapshot-cache'), auxiliaryData: CONFIG.snapshotAuxiliaryData, - shouldExcludeModule: ({requiringModulePath, requiredModulePath}) => { + shouldExcludeModule: ({ requiringModulePath, requiredModulePath }) => { if (processedFiles > 0) { - process.stdout.write('\r') + process.stdout.write('\r'); } - process.stdout.write(`Generating snapshot script at "${snapshotScriptPath}" (${++processedFiles})`) + process.stdout.write( + `Generating snapshot script at "${snapshotScriptPath}" (${++processedFiles})` + ); - const requiringModuleRelativePath = path.relative(baseDirPath, requiringModulePath) - const requiredModuleRelativePath = path.relative(baseDirPath, requiredModulePath) + const requiringModuleRelativePath = path.relative( + baseDirPath, + requiringModulePath + ); + const requiredModuleRelativePath = path.relative( + baseDirPath, + requiredModulePath + ); return ( requiredModulePath.endsWith('.node') || coreModules.has(requiredModulePath) || - requiringModuleRelativePath.endsWith(path.join('node_modules/xregexp/xregexp-all.js')) || - (requiredModuleRelativePath.startsWith(path.join('..', 'src')) && requiredModuleRelativePath.endsWith('-element.js')) || - requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'dugite')) || - requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'yaml-front-matter')) || - requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'cheerio')) || - requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'marked')) || - requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'typescript-simple')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'fs-extra', 'lib', 'index.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'graceful-fs', 'graceful-fs.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'htmlparser2', 'lib', 'index.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'minimatch', 'minimatch.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'request', 'index.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'request', 'request.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'superstring', 'index.js')) || - requiredModuleRelativePath.endsWith(path.join('node_modules', 'temp', 'lib', 'temp.js')) || + requiringModuleRelativePath.endsWith( + path.join('node_modules/xregexp/xregexp-all.js') + ) || + (requiredModuleRelativePath.startsWith(path.join('..', 'src')) && + requiredModuleRelativePath.endsWith('-element.js')) || + requiredModuleRelativePath.startsWith( + path.join('..', 'node_modules', 'dugite') + ) || + requiredModuleRelativePath.startsWith( + path.join( + '..', + 'node_modules', + 'markdown-preview', + 'node_modules', + 'yaml-front-matter' + ) + ) || + requiredModuleRelativePath.startsWith( + path.join( + '..', + 'node_modules', + 'markdown-preview', + 'node_modules', + 'cheerio' + ) + ) || + requiredModuleRelativePath.startsWith( + path.join( + '..', + 'node_modules', + 'markdown-preview', + 'node_modules', + 'marked' + ) + ) || + requiredModuleRelativePath.startsWith( + path.join('..', 'node_modules', 'typescript-simple') + ) || + requiredModuleRelativePath.endsWith( + path.join( + 'node_modules', + 'coffee-script', + 'lib', + 'coffee-script', + 'register.js' + ) + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'fs-extra', 'lib', 'index.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'graceful-fs', 'graceful-fs.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'htmlparser2', 'lib', 'index.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'minimatch', 'minimatch.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'request', 'index.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'request', 'request.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'superstring', 'index.js') + ) || + requiredModuleRelativePath.endsWith( + path.join('node_modules', 'temp', 'lib', 'temp.js') + ) || requiredModuleRelativePath === path.join('..', 'exports', 'atom.js') || - requiredModuleRelativePath === path.join('..', 'src', 'electron-shims.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'babel-core', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'debug', 'node.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'git-utils', 'src', 'git.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'glob', 'glob.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'iconv-lite', 'lib', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'lodash.isequal', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'node-fetch', 'lib', 'fetch-error.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'resolve', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'tar', 'tar.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'ls-archive', 'node_modules', 'tar', 'tar.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'tree-sitter', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'yauzl', 'index.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', '@atom', 'fuzzy-native', 'lib', 'main.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', 'vscode-ripgrep', 'lib', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'src', 'electron-shims.js') || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'atom-keymap', + 'lib', + 'command-event.js' + ) || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'babel-core', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'debug', 'node.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'git-utils', 'src', 'git.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'glob', 'glob.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'iconv-lite', 'lib', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'less', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'less', + 'lib', + 'less-node', + 'index.js' + ) || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'lodash.isequal', 'index.js') || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'node-fetch', + 'lib', + 'fetch-error.js' + ) || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'resolve', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'settings-view', + 'node_modules', + 'glob', + 'glob.js' + ) || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'spellchecker', + 'lib', + 'spellchecker.js' + ) || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'spelling-manager', + 'node_modules', + 'natural', + 'lib', + 'natural', + 'index.js' + ) || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'tar', 'tar.js') || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'ls-archive', + 'node_modules', + 'tar', + 'tar.js' + ) || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'tree-sitter', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'yauzl', 'index.js') || + requiredModuleRelativePath === + path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js') || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + '@atom', + 'fuzzy-native', + 'lib', + 'main.js' + ) || + requiredModuleRelativePath === + path.join( + '..', + 'node_modules', + 'vscode-ripgrep', + 'lib', + 'index.js' + ) || // The startup-time script is used by both the renderer and the main process and having it in the // snapshot causes issues. requiredModuleRelativePath === path.join('..', 'src', 'startup-time.js') - ) + ); } - }).then(({snapshotScript}) => { - process.stdout.write('\n') + }).then(({ snapshotScript }) => { + process.stdout.write('\n'); - process.stdout.write('Minifying startup script') + process.stdout.write('Minifying startup script'); const minification = terser.minify(snapshotScript, { keep_fnames: true, keep_classnames: true, - compress: {keep_fargs: true, keep_infinity: true} - }) - if (minification.error) throw minification.error - process.stdout.write('\n') - fs.writeFileSync(snapshotScriptPath, minification.code) + compress: { keep_fargs: true, keep_infinity: true } + }); + if (minification.error) throw minification.error; + process.stdout.write('\n'); + fs.writeFileSync(snapshotScriptPath, minification.code); - console.log('Verifying if snapshot can be executed via `mksnapshot`') - const verifySnapshotScriptPath = path.join(CONFIG.repositoryRootPath, 'script', 'verify-snapshot-script') - let nodeBundledInElectronPath + console.log('Verifying if snapshot can be executed via `mksnapshot`'); + const verifySnapshotScriptPath = path.join( + CONFIG.repositoryRootPath, + 'script', + 'verify-snapshot-script' + ); + let nodeBundledInElectronPath; if (process.platform === 'darwin') { nodeBundledInElectronPath = path.join(packagedAppPath, 'Contents', 'MacOS', CONFIG.executableName) } else { @@ -99,39 +265,49 @@ module.exports = function (packagedAppPath) { childProcess.execFileSync( nodeBundledInElectronPath, [verifySnapshotScriptPath, snapshotScriptPath], - {env: Object.assign({}, process.env, {ELECTRON_RUN_AS_NODE: 1})} - ) + { env: Object.assign({}, process.env, { ELECTRON_RUN_AS_NODE: 1 }) } + ); - console.log('Generating startup blob with mksnapshot') - childProcess.spawnSync( - process.execPath, [ - path.join(CONFIG.repositoryRootPath, 'script', 'node_modules', 'electron-mksnapshot', 'mksnapshot.js'), - snapshotScriptPath, - '--output_dir', - CONFIG.buildOutputPath - ] - ) + console.log('Generating startup blob with mksnapshot'); + childProcess.spawnSync(process.execPath, [ + path.join( + CONFIG.repositoryRootPath, + 'script', + 'node_modules', + 'electron-mksnapshot', + 'mksnapshot.js' + ), + snapshotScriptPath, + '--output_dir', + CONFIG.buildOutputPath + ]); - let startupBlobDestinationPath + let startupBlobDestinationPath; if (process.platform === 'darwin') { - startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources` + startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources`; } else { - startupBlobDestinationPath = packagedAppPath + startupBlobDestinationPath = packagedAppPath; } - const snapshotBinaries = ['v8_context_snapshot.bin', 'snapshot_blob.bin'] + const snapshotBinaries = ['v8_context_snapshot.bin', 'snapshot_blob.bin']; for (let snapshotBinary of snapshotBinaries) { - const destinationPath = path.join(startupBlobDestinationPath, snapshotBinary) - console.log(`Moving generated startup blob into "${destinationPath}"`) + const destinationPath = path.join( + startupBlobDestinationPath, + snapshotBinary + ); + console.log(`Moving generated startup blob into "${destinationPath}"`); try { - fs.unlinkSync(destinationPath) + fs.unlinkSync(destinationPath); } catch (err) { // Doesn't matter if the file doesn't exist already if (!err.code || err.code !== 'ENOENT') { - throw err + throw err; } } - fs.renameSync(path.join(CONFIG.buildOutputPath, snapshotBinary), destinationPath) + fs.renameSync( + path.join(CONFIG.buildOutputPath, snapshotBinary), + destinationPath + ); } - }) -} + }); +}; diff --git a/script/lib/get-license-text.js b/script/lib/get-license-text.js index 5edc41550..1eeef095e 100644 --- a/script/lib/get-license-text.js +++ b/script/lib/get-license-text.js @@ -1,38 +1,46 @@ -'use strict' +'use strict'; -const fs = require('fs') -const path = require('path') -const legalEagle = require('legal-eagle') +const fs = require('fs'); +const path = require('path'); +const legalEagle = require('legal-eagle'); -const licenseOverrides = require('../license-overrides') -const CONFIG = require('../config') +const licenseOverrides = require('../license-overrides'); +const CONFIG = require('../config'); -module.exports = function () { +module.exports = function() { return new Promise((resolve, reject) => { - legalEagle({path: CONFIG.repositoryRootPath, overrides: licenseOverrides}, (err, packagesLicenses) => { - if (err) { - reject(err) - throw new Error(err) - } else { - let text = - fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'LICENSE.md'), 'utf8') + '\n\n' + - 'This application bundles the following third-party packages in accordance\n' + - 'with the following licenses:\n\n' - for (let packageName of Object.keys(packagesLicenses).sort()) { - const packageLicense = packagesLicenses[packageName] - text += '-------------------------------------------------------------------------\n\n' - text += `Package: ${packageName}\n` - text += `License: ${packageLicense.license}\n` - if (packageLicense.source) { - text += `License Source: ${packageLicense.source}\n` + legalEagle( + { path: CONFIG.repositoryRootPath, overrides: licenseOverrides }, + (err, packagesLicenses) => { + if (err) { + reject(err); + throw new Error(err); + } else { + let text = + fs.readFileSync( + path.join(CONFIG.repositoryRootPath, 'LICENSE.md'), + 'utf8' + ) + + '\n\n' + + 'This application bundles the following third-party packages in accordance\n' + + 'with the following licenses:\n\n'; + for (let packageName of Object.keys(packagesLicenses).sort()) { + const packageLicense = packagesLicenses[packageName]; + text += + '-------------------------------------------------------------------------\n\n'; + text += `Package: ${packageName}\n`; + text += `License: ${packageLicense.license}\n`; + if (packageLicense.source) { + text += `License Source: ${packageLicense.source}\n`; + } + if (packageLicense.sourceText) { + text += `Source Text:\n\n${packageLicense.sourceText}`; + } + text += '\n'; } - if (packageLicense.sourceText) { - text += `Source Text:\n\n${packageLicense.sourceText}` - } - text += '\n' + resolve(text); } - resolve(text) } - }) - }) -} + ); + }); +}; diff --git a/script/lib/handle-tilde.js b/script/lib/handle-tilde.js index e59df4530..fae49de81 100644 --- a/script/lib/handle-tilde.js +++ b/script/lib/handle-tilde.js @@ -1,24 +1,29 @@ -'use strict' +'use strict'; -const os = require('os') -const passwdUser = require('passwd-user') -const path = require('path') +const os = require('os'); +const passwdUser = require('passwd-user'); +const path = require('path'); -module.exports = function (aPath) { +module.exports = function(aPath) { if (!aPath.startsWith('~')) { - return aPath + return aPath; } - const sepIndex = aPath.indexOf(path.sep) - const user = (sepIndex < 0) ? aPath.substring(1) : aPath.substring(1, sepIndex) - const rest = (sepIndex < 0) ? '' : aPath.substring(sepIndex) - const home = (user === '') ? os.homedir() : (() => { - const passwd = passwdUser.sync(user) - if (passwd === undefined) { - throw new Error(`Failed to expand the tilde in ${aPath} - user "${user}" does not exist`) - } - return passwd.homedir - })() + const sepIndex = aPath.indexOf(path.sep); + const user = sepIndex < 0 ? aPath.substring(1) : aPath.substring(1, sepIndex); + const rest = sepIndex < 0 ? '' : aPath.substring(sepIndex); + const home = + user === '' + ? os.homedir() + : (() => { + const passwd = passwdUser.sync(user); + if (passwd === undefined) { + throw new Error( + `Failed to expand the tilde in ${aPath} - user "${user}" does not exist` + ); + } + return passwd.homedir; + })(); - return `${home}${rest}` -} + return `${home}${rest}`; +}; diff --git a/script/lib/include-path-in-packaged-app.js b/script/lib/include-path-in-packaged-app.js index 8d807d975..3bb80fd6c 100644 --- a/script/lib/include-path-in-packaged-app.js +++ b/script/lib/include-path-in-packaged-app.js @@ -1,11 +1,14 @@ -'use strict' +'use strict'; -const path = require('path') -const CONFIG = require('../config') +const path = require('path'); +const CONFIG = require('../config'); -module.exports = function (filePath) { - return !EXCLUDED_PATHS_REGEXP.test(filePath) || INCLUDED_PATHS_REGEXP.test(filePath) -} +module.exports = function(filePath) { + return ( + !EXCLUDED_PATHS_REGEXP.test(filePath) || + INCLUDED_PATHS_REGEXP.test(filePath) + ); +}; const EXCLUDE_REGEXPS_SOURCES = [ escapeRegExp('.DS_Store'), @@ -35,7 +38,9 @@ const EXCLUDE_REGEXPS_SOURCES = [ escapeRegExp(path.join('npm', 'node_modules', '.bin', 'starwars')), escapeRegExp(path.join('pegjs', 'examples')), escapeRegExp(path.join('get-parameter-names', 'node_modules', 'testla')), - escapeRegExp(path.join('get-parameter-names', 'node_modules', '.bin', 'testla')), + escapeRegExp( + path.join('get-parameter-names', 'node_modules', '.bin', 'testla') + ), escapeRegExp(path.join('jasmine-reporters', 'ext')), escapeRegExp(path.join('node_modules', 'nan')), escapeRegExp(path.join('node_modules', 'native-mate')), @@ -53,7 +58,9 @@ const EXCLUDE_REGEXPS_SOURCES = [ escapeRegExp(path.join('node_modules', 'loophole')), escapeRegExp(path.join('node_modules', 'pegjs')), escapeRegExp(path.join('node_modules', '.bin', 'pegjs')), - escapeRegExp(path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep) + '.*', + escapeRegExp( + path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep + ) + '.*', // node_modules of the fuzzy-native package are only required for building it. escapeRegExp(path.join('node_modules', 'fuzzy-native', 'node_modules')), @@ -66,34 +73,59 @@ const EXCLUDE_REGEXPS_SOURCES = [ escapeRegExp(path.sep) + '.+\\.target.mk$', escapeRegExp(path.sep) + 'linker\\.lock$', escapeRegExp(path.join('build', 'Release') + path.sep) + '.+\\.node\\.dSYM', - escapeRegExp(path.join('build', 'Release') + path.sep) + '.*\\.(pdb|lib|exp|map|ipdb|iobj)', + escapeRegExp(path.join('build', 'Release') + path.sep) + + '.*\\.(pdb|lib|exp|map|ipdb|iobj)', // Ignore node_module files we won't need at runtime - 'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + '_*te?sts?_*' + escapeRegExp(path.sep), - 'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + 'examples?' + escapeRegExp(path.sep), + 'node_modules' + + escapeRegExp(path.sep) + + '.*' + + escapeRegExp(path.sep) + + '_*te?sts?_*' + + escapeRegExp(path.sep), + 'node_modules' + + escapeRegExp(path.sep) + + '.*' + + escapeRegExp(path.sep) + + 'examples?' + + escapeRegExp(path.sep), 'node_modules' + escapeRegExp(path.sep) + '.*' + '\\.d\\.ts$', 'node_modules' + escapeRegExp(path.sep) + '.*' + '\\.js\\.map$', '.*' + escapeRegExp(path.sep) + 'test.*\\.html$' -] +]; // Ignore spec directories in all bundled packages for (let packageName in CONFIG.appMetadata.packageDependencies) { - EXCLUDE_REGEXPS_SOURCES.push('^' + escapeRegExp(path.join(CONFIG.repositoryRootPath, 'node_modules', packageName, 'spec'))) + EXCLUDE_REGEXPS_SOURCES.push( + '^' + + escapeRegExp( + path.join( + CONFIG.repositoryRootPath, + 'node_modules', + packageName, + 'spec' + ) + ) + ); } // Ignore Hunspell dictionaries only on macOS. if (process.platform === 'darwin') { - EXCLUDE_REGEXPS_SOURCES.push(escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell_dictionaries'))) + EXCLUDE_REGEXPS_SOURCES.push( + escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell_dictionaries')) + ); } const EXCLUDED_PATHS_REGEXP = new RegExp( EXCLUDE_REGEXPS_SOURCES.map(path => `(${path})`).join('|') -) +); const INCLUDED_PATHS_REGEXP = new RegExp( - escapeRegExp(path.join('node_modules', 'node-gyp', 'src', 'win_delay_load_hook.cc')) -) + escapeRegExp( + path.join('node_modules', 'node-gyp', 'src', 'win_delay_load_hook.cc') + ) +); -function escapeRegExp (string) { - return string.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&') +function escapeRegExp(string) { + return string.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } diff --git a/script/lib/install-apm.js b/script/lib/install-apm.js index 81c9e849d..a233f6119 100644 --- a/script/lib/install-apm.js +++ b/script/lib/install-apm.js @@ -1,15 +1,15 @@ -'use strict' +'use strict'; -const childProcess = require('child_process') +const childProcess = require('child_process'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (ci) { - console.log('Installing apm') +module.exports = function(ci) { + console.log('Installing apm'); // npm ci leaves apm with a bunch of unmet dependencies childProcess.execFileSync( CONFIG.getNpmBinPath(), ['--global-style', '--loglevel=error', 'install'], - {env: process.env, cwd: CONFIG.apmRootPath} - ) -} + { env: process.env, cwd: CONFIG.apmRootPath } + ); +}; diff --git a/script/lib/install-application.js b/script/lib/install-application.js index 8a29372cd..f6edded67 100644 --- a/script/lib/install-application.js +++ b/script/lib/install-application.js @@ -1,22 +1,26 @@ -'use strict' +'use strict'; -const fs = require('fs-extra') -const handleTilde = require('./handle-tilde') -const path = require('path') -const template = require('lodash.template') -const startCase = require('lodash.startcase') -const execSync = require('child_process').execSync +const fs = require('fs-extra'); +const handleTilde = require('./handle-tilde'); +const path = require('path'); +const template = require('lodash.template'); +const startCase = require('lodash.startcase'); +const execSync = require('child_process').execSync; -const CONFIG = require('../config') +const CONFIG = require('../config'); -function install (installationDirPath, packagedAppFileName, packagedAppPath) { +function install(installationDirPath, packagedAppFileName, packagedAppPath) { if (fs.existsSync(installationDirPath)) { - console.log(`Removing previously installed "${packagedAppFileName}" at "${installationDirPath}"`) - fs.removeSync(installationDirPath) + console.log( + `Removing previously installed "${packagedAppFileName}" at "${installationDirPath}"` + ); + fs.removeSync(installationDirPath); } - console.log(`Installing "${packagedAppFileName}" at "${installationDirPath}"`) - fs.copySync(packagedAppPath, installationDirPath) + console.log( + `Installing "${packagedAppFileName}" at "${installationDirPath}"` + ); + fs.copySync(packagedAppPath, installationDirPath); } /** @@ -26,132 +30,205 @@ function install (installationDirPath, packagedAppFileName, packagedAppPath) { * and the XDG Base Directory Specification: * https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables */ -function findBaseIconThemeDirPath () { - const defaultBaseIconThemeDir = '/usr/share/icons/hicolor' - const dataDirsString = process.env.XDG_DATA_DIRS +function findBaseIconThemeDirPath() { + const defaultBaseIconThemeDir = '/usr/share/icons/hicolor'; + const dataDirsString = process.env.XDG_DATA_DIRS; if (dataDirsString) { - const dataDirs = dataDirsString.split(path.delimiter) + const dataDirs = dataDirsString.split(path.delimiter); if (dataDirs.includes('/usr/share/') || dataDirs.includes('/usr/share')) { - return defaultBaseIconThemeDir + return defaultBaseIconThemeDir; } else { - return path.join(dataDirs[0], 'icons', 'hicolor') + return path.join(dataDirs[0], 'icons', 'hicolor'); } } else { - return defaultBaseIconThemeDir + return defaultBaseIconThemeDir; } } -module.exports = function (packagedAppPath, installDir) { - const packagedAppFileName = path.basename(packagedAppPath) +module.exports = function(packagedAppPath, installDir) { + const packagedAppFileName = path.basename(packagedAppPath); if (process.platform === 'darwin') { - const installPrefix = installDir !== '' ? handleTilde(installDir) : path.join(path.sep, 'Applications') - const installationDirPath = path.join(installPrefix, packagedAppFileName) - install(installationDirPath, packagedAppFileName, packagedAppPath) + const installPrefix = + installDir !== '' + ? handleTilde(installDir) + : path.join(path.sep, 'Applications'); + const installationDirPath = path.join(installPrefix, packagedAppFileName); + install(installationDirPath, packagedAppFileName, packagedAppPath); } else if (process.platform === 'win32') { - const installPrefix = installDir !== '' ? installDir : process.env.LOCALAPPDATA - const installationDirPath = path.join(installPrefix, packagedAppFileName, 'app-dev') + const installPrefix = + installDir !== '' ? installDir : process.env.LOCALAPPDATA; + const installationDirPath = path.join( + installPrefix, + packagedAppFileName, + 'app-dev' + ); try { - install(installationDirPath, packagedAppFileName, packagedAppPath) + install(installationDirPath, packagedAppFileName, packagedAppPath); } catch (e) { - console.log(`Administrator elevation required to install into "${installationDirPath}"`) - const fsAdmin = require('fs-admin') + console.log( + `Administrator elevation required to install into "${installationDirPath}"` + ); + const fsAdmin = require('fs-admin'); return new Promise((resolve, reject) => { - fsAdmin.recursiveCopy(packagedAppPath, installationDirPath, (error) => { - error ? reject(error) : resolve() - }) - }) + fsAdmin.recursiveCopy(packagedAppPath, installationDirPath, error => { + error ? reject(error) : resolve(); + }); + }); } } else { - const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : 'atom-' + CONFIG.channel - const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : 'apm-' + CONFIG.channel - const appName = CONFIG.channel === 'stable' ? 'Atom' : startCase('Atom ' + CONFIG.channel) - const appDescription = CONFIG.appMetadata.description - const prefixDirPath = installDir !== '' ? handleTilde(installDir) : path.join('/usr', 'local') - const shareDirPath = path.join(prefixDirPath, 'share') - const installationDirPath = path.join(shareDirPath, atomExecutableName) - const applicationsDirPath = path.join(shareDirPath, 'applications') + const atomExecutableName = + CONFIG.channel === 'stable' ? 'atom' : 'atom-' + CONFIG.channel; + const apmExecutableName = + CONFIG.channel === 'stable' ? 'apm' : 'apm-' + CONFIG.channel; + const appName = + CONFIG.channel === 'stable' + ? 'Atom' + : startCase('Atom ' + CONFIG.channel); + const appDescription = CONFIG.appMetadata.description; + const prefixDirPath = + installDir !== '' ? handleTilde(installDir) : path.join('/usr', 'local'); + const shareDirPath = path.join(prefixDirPath, 'share'); + const installationDirPath = path.join(shareDirPath, atomExecutableName); + const applicationsDirPath = path.join(shareDirPath, 'applications'); - const binDirPath = path.join(prefixDirPath, 'bin') + const binDirPath = path.join(prefixDirPath, 'bin'); - fs.mkdirpSync(applicationsDirPath) - fs.mkdirpSync(binDirPath) + fs.mkdirpSync(applicationsDirPath); + fs.mkdirpSync(binDirPath); - install(installationDirPath, packagedAppFileName, packagedAppPath) + install(installationDirPath, packagedAppFileName, packagedAppPath); - { // Install icons - const baseIconThemeDirPath = findBaseIconThemeDirPath() - const fullIconName = atomExecutableName + '.png' + { + // Install icons + const baseIconThemeDirPath = findBaseIconThemeDirPath(); + const fullIconName = atomExecutableName + '.png'; - let existingIconsFound = false + let existingIconsFound = false; fs.readdirSync(baseIconThemeDirPath).forEach(size => { - const iconPath = path.join(baseIconThemeDirPath, size, 'apps', fullIconName) + const iconPath = path.join( + baseIconThemeDirPath, + size, + 'apps', + fullIconName + ); if (fs.existsSync(iconPath)) { if (!existingIconsFound) { - console.log(`Removing existing icons from "${baseIconThemeDirPath}"`) + console.log( + `Removing existing icons from "${baseIconThemeDirPath}"` + ); } - existingIconsFound = true - fs.removeSync(iconPath) + existingIconsFound = true; + fs.removeSync(iconPath); } - }) + }); - console.log(`Installing icons at "${baseIconThemeDirPath}"`) - const appIconsPath = path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png') + console.log(`Installing icons at "${baseIconThemeDirPath}"`); + const appIconsPath = path.join( + CONFIG.repositoryRootPath, + 'resources', + 'app-icons', + CONFIG.channel, + 'png' + ); fs.readdirSync(appIconsPath).forEach(imageName => { if (/\.png$/.test(imageName)) { - const size = path.basename(imageName, '.png') - const iconPath = path.join(appIconsPath, imageName) - fs.copySync(iconPath, path.join(baseIconThemeDirPath, `${size}x${size}`, 'apps', fullIconName)) + const size = path.basename(imageName, '.png'); + const iconPath = path.join(appIconsPath, imageName); + fs.copySync( + iconPath, + path.join( + baseIconThemeDirPath, + `${size}x${size}`, + 'apps', + fullIconName + ) + ); } - }) + }); - console.log(`Updating icon cache for "${baseIconThemeDirPath}"`) + console.log(`Updating icon cache for "${baseIconThemeDirPath}"`); try { - execSync(`gtk-update-icon-cache ${baseIconThemeDirPath} --force`) + execSync(`gtk-update-icon-cache ${baseIconThemeDirPath} --force`); } catch (e) {} } - { // Install xdg desktop file - const desktopEntryPath = path.join(applicationsDirPath, `${atomExecutableName}.desktop`) + { + // Install xdg desktop file + const desktopEntryPath = path.join( + applicationsDirPath, + `${atomExecutableName}.desktop` + ); if (fs.existsSync(desktopEntryPath)) { - console.log(`Removing existing desktop entry file at "${desktopEntryPath}"`) - fs.removeSync(desktopEntryPath) + console.log( + `Removing existing desktop entry file at "${desktopEntryPath}"` + ); + fs.removeSync(desktopEntryPath); } - console.log(`Writing desktop entry file at "${desktopEntryPath}"`) - const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in')) + console.log(`Writing desktop entry file at "${desktopEntryPath}"`); + const desktopEntryTemplate = fs.readFileSync( + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'linux', + 'atom.desktop.in' + ) + ); const desktopEntryContents = template(desktopEntryTemplate)({ appName, appFileName: atomExecutableName, description: appDescription, installDir: prefixDirPath, iconPath: atomExecutableName - }) - fs.writeFileSync(desktopEntryPath, desktopEntryContents) + }); + fs.writeFileSync(desktopEntryPath, desktopEntryContents); } - { // Add atom executable to the PATH - const atomBinDestinationPath = path.join(binDirPath, atomExecutableName) + { + // Add atom executable to the PATH + const atomBinDestinationPath = path.join(binDirPath, atomExecutableName); if (fs.existsSync(atomBinDestinationPath)) { - console.log(`Removing existing executable at "${atomBinDestinationPath}"`) - fs.removeSync(atomBinDestinationPath) + console.log( + `Removing existing executable at "${atomBinDestinationPath}"` + ); + fs.removeSync(atomBinDestinationPath); } - console.log(`Copying atom.sh to "${atomBinDestinationPath}"`) - fs.copySync(path.join(CONFIG.repositoryRootPath, 'atom.sh'), atomBinDestinationPath) + console.log(`Copying atom.sh to "${atomBinDestinationPath}"`); + fs.copySync( + path.join(CONFIG.repositoryRootPath, 'atom.sh'), + atomBinDestinationPath + ); } - { // Link apm executable to the PATH - const apmBinDestinationPath = path.join(binDirPath, apmExecutableName) + { + // Link apm executable to the PATH + const apmBinDestinationPath = path.join(binDirPath, apmExecutableName); try { - fs.lstatSync(apmBinDestinationPath) - console.log(`Removing existing executable at "${apmBinDestinationPath}"`) - fs.removeSync(apmBinDestinationPath) - } catch (e) { } - console.log(`Symlinking apm to "${apmBinDestinationPath}"`) - fs.symlinkSync(path.join('..', 'share', atomExecutableName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), apmBinDestinationPath) + fs.lstatSync(apmBinDestinationPath); + console.log( + `Removing existing executable at "${apmBinDestinationPath}"` + ); + fs.removeSync(apmBinDestinationPath); + } catch (e) {} + console.log(`Symlinking apm to "${apmBinDestinationPath}"`); + fs.symlinkSync( + path.join( + '..', + 'share', + atomExecutableName, + 'resources', + 'app', + 'apm', + 'node_modules', + '.bin', + 'apm' + ), + apmBinDestinationPath + ); } - console.log(`Changing permissions to 755 for "${installationDirPath}"`) - fs.chmodSync(installationDirPath, '755') + console.log(`Changing permissions to 755 for "${installationDirPath}"`); + fs.chmodSync(installationDirPath, '755'); } - return Promise.resolve() -} + return Promise.resolve(); +}; diff --git a/script/lib/install-script-dependencies.js b/script/lib/install-script-dependencies.js index 76f6e94c6..75406b612 100644 --- a/script/lib/install-script-dependencies.js +++ b/script/lib/install-script-dependencies.js @@ -1,14 +1,14 @@ -'use strict' +'use strict'; -const childProcess = require('child_process') +const childProcess = require('child_process'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (ci) { - console.log('Installing script dependencies') +module.exports = function(ci) { + console.log('Installing script dependencies'); childProcess.execFileSync( CONFIG.getNpmBinPath(ci), ['--loglevel=error', ci ? 'ci' : 'install'], - {env: process.env, cwd: CONFIG.scriptRootPath} - ) -} + { env: process.env, cwd: CONFIG.scriptRootPath } + ); +}; diff --git a/script/lib/kill-running-atom-instances.js b/script/lib/kill-running-atom-instances.js index 616ca6060..a4c4850cc 100644 --- a/script/lib/kill-running-atom-instances.js +++ b/script/lib/kill-running-atom-instances.js @@ -1,12 +1,14 @@ -const childProcess = require('child_process') +const childProcess = require('child_process'); -const CONFIG = require('../config.js') +const CONFIG = require('../config.js'); -module.exports = function () { +module.exports = function() { if (process.platform === 'win32') { // Use START as a way to ignore error if Atom.exe isnt running - childProcess.execSync(`START taskkill /F /IM ${CONFIG.appMetadata.productName}.exe`) + childProcess.execSync( + `START taskkill /F /IM ${CONFIG.appMetadata.productName}.exe` + ); } else { - childProcess.execSync(`pkill -9 ${CONFIG.appMetadata.productName} || true`) + childProcess.execSync(`pkill -9 ${CONFIG.appMetadata.productName} || true`); } -} +}; diff --git a/script/lib/lint-coffee-script-paths.js b/script/lib/lint-coffee-script-paths.js index 4acf8ec56..fe53d31a0 100644 --- a/script/lib/lint-coffee-script-paths.js +++ b/script/lib/lint-coffee-script-paths.js @@ -1,27 +1,41 @@ -'use strict' +'use strict'; -const coffeelint = require('coffeelint') -const expandGlobPaths = require('./expand-glob-paths') -const path = require('path') -const readFiles = require('./read-files') +const coffeelint = require('coffeelint'); +const expandGlobPaths = require('./expand-glob-paths'); +const path = require('path'); +const readFiles = require('./read-files'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { +module.exports = function() { const globPathsToLint = [ path.join(CONFIG.repositoryRootPath, 'dot-atom/**/*.coffee'), path.join(CONFIG.repositoryRootPath, 'src/**/*.coffee'), path.join(CONFIG.repositoryRootPath, 'spec/*.coffee') - ] - return expandGlobPaths(globPathsToLint).then(readFiles).then((files) => { - const errors = [] - const lintConfiguration = require(path.join(CONFIG.repositoryRootPath, 'coffeelint.json')) - for (let file of files) { - const lintErrors = coffeelint.lint(file.content, lintConfiguration, false) - for (let error of lintErrors) { - errors.push({path: file.path, lineNumber: error.lineNumber, message: error.message, rule: error.rule}) + ]; + return expandGlobPaths(globPathsToLint) + .then(readFiles) + .then(files => { + const errors = []; + const lintConfiguration = require(path.join( + CONFIG.repositoryRootPath, + 'coffeelint.json' + )); + for (let file of files) { + const lintErrors = coffeelint.lint( + file.content, + lintConfiguration, + false + ); + for (let error of lintErrors) { + errors.push({ + path: file.path, + lineNumber: error.lineNumber, + message: error.message, + rule: error.rule + }); + } } - } - return errors - }) -} + return errors; + }); +}; diff --git a/script/lib/lint-java-script-paths.js b/script/lib/lint-java-script-paths.js index 96b5b33e0..dd64a6a21 100644 --- a/script/lib/lint-java-script-paths.js +++ b/script/lib/lint-java-script-paths.js @@ -1,46 +1,46 @@ -'use strict' +'use strict'; -const path = require('path') -const {spawn} = require('child_process') -const process = require('process') +const path = require('path'); +const { spawn } = require('child_process'); +const process = require('process'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = async function () { +module.exports = async function() { return new Promise((resolve, reject) => { - const eslintArgs = ['--cache', '--format', 'json'] + const eslintArgs = ['--cache', '--format', 'json']; if (process.argv.includes('--fix')) { - eslintArgs.push('--fix') + eslintArgs.push('--fix'); } - const eslintBinary = process.platform === 'win32' ? 'eslint.cmd' : 'eslint' + const eslintBinary = process.platform === 'win32' ? 'eslint.cmd' : 'eslint'; const eslint = spawn( path.join('script', 'node_modules', '.bin', eslintBinary), [...eslintArgs, '.'], { cwd: CONFIG.repositoryRootPath } - ) + ); - let output = '' - let errorOutput = '' + let output = ''; + let errorOutput = ''; eslint.stdout.on('data', data => { - output += data.toString() - }) + output += data.toString(); + }); eslint.stderr.on('data', data => { - errorOutput += data.toString() - }) + errorOutput += data.toString(); + }); - eslint.on('error', error => reject(error)) + eslint.on('error', error => reject(error)); eslint.on('close', exitCode => { - const errors = [] - let files + const errors = []; + let files; try { - files = JSON.parse(output) + files = JSON.parse(output); } catch (_) { - reject(errorOutput) - return + reject(errorOutput); + return; } for (const file of files) { @@ -50,11 +50,11 @@ module.exports = async function () { message: error.message, lineNumber: error.line, rule: error.ruleId - }) + }); } } - resolve(errors) - }) - }) -} + resolve(errors); + }); + }); +}; diff --git a/script/lib/lint-less-paths.js b/script/lib/lint-less-paths.js index c46d847c3..ba96c0f1b 100644 --- a/script/lib/lint-less-paths.js +++ b/script/lib/lint-less-paths.js @@ -1,25 +1,25 @@ -'use strict' +'use strict'; -const stylelint = require('stylelint') -const path = require('path') +const stylelint = require('stylelint'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { +module.exports = function() { return stylelint .lint({ files: path.join(CONFIG.repositoryRootPath, 'static/**/*.less'), configBasedir: __dirname, configFile: path.resolve(__dirname, '..', '..', 'stylelint.config.js') }) - .then(({results}) => { - const errors = [] + .then(({ results }) => { + const errors = []; for (const result of results) { for (const deprecation of result.deprecations) { - console.log('stylelint encountered deprecation:', deprecation.text) + console.log('stylelint encountered deprecation:', deprecation.text); if (deprecation.reference != null) { - console.log('more information at', deprecation.reference) + console.log('more information at', deprecation.reference); } } @@ -27,7 +27,7 @@ module.exports = function () { console.warn( 'stylelint encountered invalid option:', invalidOptionWarning.text - ) + ); } if (result.errored) { @@ -38,7 +38,7 @@ module.exports = function () { lineNumber: warning.line, message: warning.text, rule: warning.rule - }) + }); } else { console.warn( 'stylelint encountered non-critical warning in file', @@ -48,16 +48,16 @@ module.exports = function () { 'for rule', warning.rule + ':', warning.text - ) + ); } } } } - return errors + return errors; }) .catch(err => { - console.error('There was a problem linting LESS:') - throw err - }) -} + console.error('There was a problem linting LESS:'); + throw err; + }); +}; diff --git a/script/lib/package-application.js b/script/lib/package-application.js index 0178f69d0..7ddb5754d 100644 --- a/script/lib/package-application.js +++ b/script/lib/package-application.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; const assert = require('assert') const childProcess = require('child_process') @@ -11,26 +11,41 @@ const path = require('path') const spawnSync = require('./spawn-sync') const template = require('lodash.template') -const CONFIG = require('../config') -const HOST_ARCH = hostArch() +const CONFIG = require('../config'); +const HOST_ARCH = hostArch(); -module.exports = function () { - const appName = getAppName() - console.log(`Running electron-packager on ${CONFIG.intermediateAppPath} with app name "${appName}"`) +module.exports = function() { + const appName = getAppName(); + console.log( + `Running electron-packager on ${ + CONFIG.intermediateAppPath + } with app name "${appName}"` + ); return runPackager({ appBundleId: 'com.github.atom', - appCopyright: `Copyright © 2014-${(new Date()).getFullYear()} GitHub, Inc. All rights reserved.`, + appCopyright: `Copyright © 2014-${new Date().getFullYear()} GitHub, Inc. All rights reserved.`, appVersion: CONFIG.appMetadata.version, arch: process.platform === 'darwin' ? 'x64' : HOST_ARCH, // OS X is 64-bit only - asar: {unpack: buildAsarUnpackGlobExpression()}, + asar: { unpack: buildAsarUnpackGlobExpression() }, buildVersion: CONFIG.appMetadata.version, derefSymlinks: false, - download: {cache: CONFIG.electronDownloadPath}, + download: { cache: CONFIG.electronDownloadPath }, dir: CONFIG.intermediateAppPath, electronVersion: CONFIG.appMetadata.electronVersion, - extendInfo: path.join(CONFIG.repositoryRootPath, 'resources', 'mac', 'atom-Info.plist'), + extendInfo: path.join( + CONFIG.repositoryRootPath, + 'resources', + 'mac', + 'atom-Info.plist' + ), helperBundleId: 'com.github.atom.helper', - icon: path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom'), + icon: path.join( + CONFIG.repositoryRootPath, + 'resources', + 'app-icons', + CONFIG.channel, + 'atom' + ), name: appName, out: CONFIG.buildOutputPath, overwrite: true, @@ -42,42 +57,79 @@ module.exports = function () { FileDescription: 'Atom', ProductName: CONFIG.appName } - }).then((packagedAppPath) => { - let bundledResourcesPath + }).then(packagedAppPath => { + let bundledResourcesPath; if (process.platform === 'darwin') { - bundledResourcesPath = path.join(packagedAppPath, 'Contents', 'Resources') - setAtomHelperVersion(packagedAppPath) + bundledResourcesPath = path.join( + packagedAppPath, + 'Contents', + 'Resources' + ); + setAtomHelperVersion(packagedAppPath); } else if (process.platform === 'linux') { - bundledResourcesPath = path.join(packagedAppPath, 'resources') - chmodNodeFiles(packagedAppPath) + bundledResourcesPath = path.join(packagedAppPath, 'resources'); + chmodNodeFiles(packagedAppPath); } else { - bundledResourcesPath = path.join(packagedAppPath, 'resources') + bundledResourcesPath = path.join(packagedAppPath, 'resources'); } - return copyNonASARResources(packagedAppPath, bundledResourcesPath).then(() => { - console.log(`Application bundle created at ${packagedAppPath}`) - return packagedAppPath - }) - }) -} + return copyNonASARResources(packagedAppPath, bundledResourcesPath).then( + () => { + console.log(`Application bundle created at ${packagedAppPath}`); + return packagedAppPath; + } + ); + }); +}; -function copyNonASARResources (packagedAppPath, bundledResourcesPath) { - console.log(`Copying non-ASAR resources to ${bundledResourcesPath}`) +function copyNonASARResources(packagedAppPath, bundledResourcesPath) { + console.log(`Copying non-ASAR resources to ${bundledResourcesPath}`); fs.copySync( - path.join(CONFIG.repositoryRootPath, 'apm', 'node_modules', 'atom-package-manager'), + path.join( + CONFIG.repositoryRootPath, + 'apm', + 'node_modules', + 'atom-package-manager' + ), path.join(bundledResourcesPath, 'app', 'apm'), - {filter: includePathInPackagedApp} - ) + { filter: includePathInPackagedApp } + ); if (process.platform !== 'win32') { // Existing symlinks on user systems point to an outdated path, so just symlink it to the real location of the apm binary. // TODO: Change command installer to point to appropriate path and remove this fallback after a few releases. - fs.symlinkSync(path.join('..', '..', 'bin', 'apm'), path.join(bundledResourcesPath, 'app', 'apm', 'node_modules', '.bin', 'apm')) - fs.copySync(path.join(CONFIG.repositoryRootPath, 'atom.sh'), path.join(bundledResourcesPath, 'app', 'atom.sh')) + fs.symlinkSync( + path.join('..', '..', 'bin', 'apm'), + path.join( + bundledResourcesPath, + 'app', + 'apm', + 'node_modules', + '.bin', + 'apm' + ) + ); + fs.copySync( + path.join(CONFIG.repositoryRootPath, 'atom.sh'), + path.join(bundledResourcesPath, 'app', 'atom.sh') + ); } if (process.platform === 'darwin') { - fs.copySync(path.join(CONFIG.repositoryRootPath, 'resources', 'mac', 'file.icns'), path.join(bundledResourcesPath, 'file.icns')) + fs.copySync( + path.join(CONFIG.repositoryRootPath, 'resources', 'mac', 'file.icns'), + path.join(bundledResourcesPath, 'file.icns') + ); } else if (process.platform === 'linux') { - fs.copySync(path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png', '1024.png'), path.join(packagedAppPath, 'atom.png')) + fs.copySync( + path.join( + CONFIG.repositoryRootPath, + 'resources', + 'app-icons', + CONFIG.channel, + 'png', + '1024.png' + ), + path.join(packagedAppPath, 'atom.png') + ); } else if (process.platform === 'win32') { [ 'atom.sh', 'atom.js', 'apm.cmd', 'apm.sh', 'file.ico', 'folder.ico' ] .forEach(file => fs.copySync(path.join('resources', 'win', file), path.join(bundledResourcesPath, 'cli', file))) @@ -86,26 +138,44 @@ function copyNonASARResources (packagedAppPath, bundledResourcesPath) { generateAtomCmdForChannel(bundledResourcesPath) } - console.log(`Writing LICENSE.md to ${bundledResourcesPath}`) - return getLicenseText().then((licenseText) => { - fs.writeFileSync(path.join(bundledResourcesPath, 'LICENSE.md'), licenseText) - }) + console.log(`Writing LICENSE.md to ${bundledResourcesPath}`); + return getLicenseText().then(licenseText => { + fs.writeFileSync( + path.join(bundledResourcesPath, 'LICENSE.md'), + licenseText + ); + }); } -function setAtomHelperVersion (packagedAppPath) { - const frameworksPath = path.join(packagedAppPath, 'Contents', 'Frameworks') - const helperPListPath = path.join(frameworksPath, 'Atom Helper.app', 'Contents', 'Info.plist') - console.log(`Setting Atom Helper Version for ${helperPListPath}`) - spawnSync('/usr/libexec/PlistBuddy', ['-c', `Add CFBundleVersion string ${CONFIG.appMetadata.version}`, helperPListPath]) - spawnSync('/usr/libexec/PlistBuddy', ['-c', `Add CFBundleShortVersionString string ${CONFIG.appMetadata.version}`, helperPListPath]) +function setAtomHelperVersion(packagedAppPath) { + const frameworksPath = path.join(packagedAppPath, 'Contents', 'Frameworks'); + const helperPListPath = path.join( + frameworksPath, + 'Atom Helper.app', + 'Contents', + 'Info.plist' + ); + console.log(`Setting Atom Helper Version for ${helperPListPath}`); + spawnSync('/usr/libexec/PlistBuddy', [ + '-c', + `Add CFBundleVersion string ${CONFIG.appMetadata.version}`, + helperPListPath + ]); + spawnSync('/usr/libexec/PlistBuddy', [ + '-c', + `Add CFBundleShortVersionString string ${CONFIG.appMetadata.version}`, + helperPListPath + ]); } -function chmodNodeFiles (packagedAppPath) { - console.log(`Changing permissions for node files in ${packagedAppPath}`) - childProcess.execSync(`find "${packagedAppPath}" -type f -name *.node -exec chmod a-x {} \\;`) +function chmodNodeFiles(packagedAppPath) { + console.log(`Changing permissions for node files in ${packagedAppPath}`); + childProcess.execSync( + `find "${packagedAppPath}" -type f -name *.node -exec chmod a-x {} \\;` + ); } -function buildAsarUnpackGlobExpression () { +function buildAsarUnpackGlobExpression() { const unpack = [ '*.node', 'ctags-config', @@ -117,58 +187,68 @@ function buildAsarUnpackGlobExpression () { path.join('**', 'node_modules', 'github', 'bin', '**'), path.join('**', 'node_modules', 'vscode-ripgrep', 'bin', '**'), path.join('**', 'resources', 'atom.png') - ] + ]; - return `{${unpack.join(',')}}` + return `{${unpack.join(',')}}`; } -function getAppName () { +function getAppName() { if (process.platform === 'darwin') { return CONFIG.appName } else if (process.platform === 'win32') { return CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}` } else { - return 'atom' + return 'atom'; } } -async function runPackager (options) { - const packageOutputDirPaths = await electronPackager(options) +async function runPackager(options) { + const packageOutputDirPaths = await electronPackager(options); - assert(packageOutputDirPaths.length === 1, 'Generated more than one electron application!') + assert( + packageOutputDirPaths.length === 1, + 'Generated more than one electron application!' + ); - return renamePackagedAppDir(packageOutputDirPaths[0]) + return renamePackagedAppDir(packageOutputDirPaths[0]); } -function renamePackagedAppDir (packageOutputDirPath) { - let packagedAppPath +function renamePackagedAppDir(packageOutputDirPath) { + let packagedAppPath; if (process.platform === 'darwin') { - const appBundleName = getAppName() + '.app' - packagedAppPath = path.join(CONFIG.buildOutputPath, appBundleName) - if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath) - fs.renameSync(path.join(packageOutputDirPath, appBundleName), packagedAppPath) + const appBundleName = getAppName() + '.app'; + packagedAppPath = path.join(CONFIG.buildOutputPath, appBundleName); + if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath); + fs.renameSync( + path.join(packageOutputDirPath, appBundleName), + packagedAppPath + ); } else if (process.platform === 'linux') { - const appName = CONFIG.channel !== 'stable' ? `atom-${CONFIG.channel}` : 'atom' - let architecture + const appName = + CONFIG.channel !== 'stable' ? `atom-${CONFIG.channel}` : 'atom'; + let architecture; if (HOST_ARCH === 'ia32') { - architecture = 'i386' + architecture = 'i386'; } else if (HOST_ARCH === 'x64') { - architecture = 'amd64' + architecture = 'amd64'; } else { - architecture = HOST_ARCH + architecture = HOST_ARCH; } - packagedAppPath = path.join(CONFIG.buildOutputPath, `${appName}-${CONFIG.appMetadata.version}-${architecture}`) - if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath) - fs.renameSync(packageOutputDirPath, packagedAppPath) + packagedAppPath = path.join( + CONFIG.buildOutputPath, + `${appName}-${CONFIG.appMetadata.version}-${architecture}` + ); + if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath); + fs.renameSync(packageOutputDirPath, packagedAppPath); } else { - packagedAppPath = path.join(CONFIG.buildOutputPath, CONFIG.appName) + packagedAppPath = path.join(CONFIG.buildOutputPath, CONFIG.appName); if (process.platform === 'win32' && HOST_ARCH !== 'ia32') { - packagedAppPath += ` ${process.arch}` + packagedAppPath += ` ${process.arch}`; } - if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath) - fs.renameSync(packageOutputDirPath, packagedAppPath) + if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath); + fs.renameSync(packageOutputDirPath, packagedAppPath); } - return packagedAppPath + return packagedAppPath; } function generateAtomCmdForChannel (bundledResourcesPath) { diff --git a/script/lib/prebuild-less-cache.js b/script/lib/prebuild-less-cache.js index e12595332..c7dc2696a 100644 --- a/script/lib/prebuild-less-cache.js +++ b/script/lib/prebuild-less-cache.js @@ -1,46 +1,64 @@ -'use strict' +'use strict'; -const fs = require('fs') -const klawSync = require('klaw-sync') -const glob = require('glob') -const path = require('path') -const LessCache = require('less-cache') +const fs = require('fs'); +const klawSync = require('klaw-sync'); +const glob = require('glob'); +const path = require('path'); +const LessCache = require('less-cache'); -const CONFIG = require('../config') -const LESS_CACHE_VERSION = require('less-cache/package.json').version -const FALLBACK_VARIABLE_IMPORTS = '@import "variables/ui-variables";\n@import "variables/syntax-variables";\n' +const CONFIG = require('../config'); +const LESS_CACHE_VERSION = require('less-cache/package.json').version; +const FALLBACK_VARIABLE_IMPORTS = + '@import "variables/ui-variables";\n@import "variables/syntax-variables";\n'; -module.exports = function () { - const cacheDirPath = path.join(CONFIG.intermediateAppPath, 'less-compile-cache') - console.log(`Generating pre-built less cache in ${cacheDirPath}`) +module.exports = function() { + const cacheDirPath = path.join( + CONFIG.intermediateAppPath, + 'less-compile-cache' + ); + console.log(`Generating pre-built less cache in ${cacheDirPath}`); // Group bundled packages into UI themes, syntax themes, and non-theme packages - const uiThemes = [] - const syntaxThemes = [] - const nonThemePackages = [] + const uiThemes = []; + const syntaxThemes = []; + const nonThemePackages = []; for (let packageName in CONFIG.appMetadata.packageDependencies) { - const packageMetadata = require(path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, 'package.json')) + const packageMetadata = require(path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + 'package.json' + )); if (packageMetadata.theme === 'ui') { - uiThemes.push(packageName) + uiThemes.push(packageName); } else if (packageMetadata.theme === 'syntax') { - syntaxThemes.push(packageName) + syntaxThemes.push(packageName); } else { - nonThemePackages.push(packageName) + nonThemePackages.push(packageName); } } - CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath = {} - function saveIntoSnapshotAuxiliaryData (absoluteFilePath, content) { - const relativeFilePath = path.relative(CONFIG.intermediateAppPath, absoluteFilePath) - if (!CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath.hasOwnProperty(relativeFilePath)) { - CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath[relativeFilePath] = { + CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath = {}; + function saveIntoSnapshotAuxiliaryData(absoluteFilePath, content) { + const relativeFilePath = path.relative( + CONFIG.intermediateAppPath, + absoluteFilePath + ); + if ( + !CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath.hasOwnProperty( + relativeFilePath + ) + ) { + CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath[ + relativeFilePath + ] = { content: content, digest: LessCache.digestForContent(content) - } + }; } } - CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath = {} + CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath = {}; // Warm cache for every combination of the default UI and syntax themes, // because themes assign variables which may be used in any style sheet. for (let uiTheme of uiThemes) { @@ -48,72 +66,153 @@ module.exports = function () { // Build a LessCache instance with import paths based on the current theme combination const lessCache = new LessCache({ cacheDir: cacheDirPath, - fallbackDir: path.join(CONFIG.atomHomeDirPath, 'compile-cache', 'prebuild-less', LESS_CACHE_VERSION), + fallbackDir: path.join( + CONFIG.atomHomeDirPath, + 'compile-cache', + 'prebuild-less', + LESS_CACHE_VERSION + ), syncCaches: true, resourcePath: CONFIG.intermediateAppPath, importPaths: [ - path.join(CONFIG.intermediateAppPath, 'node_modules', syntaxTheme, 'styles'), - path.join(CONFIG.intermediateAppPath, 'node_modules', uiTheme, 'styles'), + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + syntaxTheme, + 'styles' + ), + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + uiTheme, + 'styles' + ), path.join(CONFIG.intermediateAppPath, 'static', 'variables'), path.join(CONFIG.intermediateAppPath, 'static') ] - }) + }); // Store file paths located at the import paths so that we can avoid scanning them at runtime. for (const absoluteImportPath of lessCache.getImportPaths()) { - const relativeImportPath = path.relative(CONFIG.intermediateAppPath, absoluteImportPath) - if (!CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath.hasOwnProperty(relativeImportPath)) { - CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[relativeImportPath] = [] - for (const importedFile of klawSync(absoluteImportPath, {nodir: true})) { - CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[relativeImportPath].push( + const relativeImportPath = path.relative( + CONFIG.intermediateAppPath, + absoluteImportPath + ); + if ( + !CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath.hasOwnProperty( + relativeImportPath + ) + ) { + CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[ + relativeImportPath + ] = []; + for (const importedFile of klawSync(absoluteImportPath, { + nodir: true + })) { + CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[ + relativeImportPath + ].push( path.relative(CONFIG.intermediateAppPath, importedFile.path) - ) + ); } } } // Cache all styles in static; don't append variable imports - for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'static', '**', '*.less'))) { - cacheCompiledCSS(lessCache, lessFilePath, false) + for (let lessFilePath of glob.sync( + path.join(CONFIG.intermediateAppPath, 'static', '**', '*.less') + )) { + cacheCompiledCSS(lessCache, lessFilePath, false); } // Cache styles for all bundled non-theme packages for (let nonThemePackage of nonThemePackages) { - for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', nonThemePackage, '**', '*.less'))) { - cacheCompiledCSS(lessCache, lessFilePath, true) + for (let lessFilePath of glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + nonThemePackage, + '**', + '*.less' + ) + )) { + cacheCompiledCSS(lessCache, lessFilePath, true); } } // Cache styles for this UI theme - const uiThemeMainPath = path.join(CONFIG.intermediateAppPath, 'node_modules', uiTheme, 'index.less') - cacheCompiledCSS(lessCache, uiThemeMainPath, true) - for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', uiTheme, '**', '*.less'))) { + const uiThemeMainPath = path.join( + CONFIG.intermediateAppPath, + 'node_modules', + uiTheme, + 'index.less' + ); + cacheCompiledCSS(lessCache, uiThemeMainPath, true); + for (let lessFilePath of glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + uiTheme, + '**', + '*.less' + ) + )) { if (lessFilePath !== uiThemeMainPath) { - saveIntoSnapshotAuxiliaryData(lessFilePath, fs.readFileSync(lessFilePath, 'utf8')) + saveIntoSnapshotAuxiliaryData( + lessFilePath, + fs.readFileSync(lessFilePath, 'utf8') + ); } } // Cache styles for this syntax theme - const syntaxThemeMainPath = path.join(CONFIG.intermediateAppPath, 'node_modules', syntaxTheme, 'index.less') - cacheCompiledCSS(lessCache, syntaxThemeMainPath, true) - for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', syntaxTheme, '**', '*.less'))) { + const syntaxThemeMainPath = path.join( + CONFIG.intermediateAppPath, + 'node_modules', + syntaxTheme, + 'index.less' + ); + cacheCompiledCSS(lessCache, syntaxThemeMainPath, true); + for (let lessFilePath of glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + syntaxTheme, + '**', + '*.less' + ) + )) { if (lessFilePath !== syntaxThemeMainPath) { - saveIntoSnapshotAuxiliaryData(lessFilePath, fs.readFileSync(lessFilePath, 'utf8')) + saveIntoSnapshotAuxiliaryData( + lessFilePath, + fs.readFileSync(lessFilePath, 'utf8') + ); } } } } - for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', 'atom-ui', '**', '*.less'))) { - saveIntoSnapshotAuxiliaryData(lessFilePath, fs.readFileSync(lessFilePath, 'utf8')) + for (let lessFilePath of glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + 'atom-ui', + '**', + '*.less' + ) + )) { + saveIntoSnapshotAuxiliaryData( + lessFilePath, + fs.readFileSync(lessFilePath, 'utf8') + ); } - function cacheCompiledCSS (lessCache, lessFilePath, importFallbackVariables) { - let lessSource = fs.readFileSync(lessFilePath, 'utf8') + function cacheCompiledCSS(lessCache, lessFilePath, importFallbackVariables) { + let lessSource = fs.readFileSync(lessFilePath, 'utf8'); if (importFallbackVariables) { - lessSource = FALLBACK_VARIABLE_IMPORTS + lessSource + lessSource = FALLBACK_VARIABLE_IMPORTS + lessSource; } - lessCache.cssForFile(lessFilePath, lessSource) - saveIntoSnapshotAuxiliaryData(lessFilePath, lessSource) + lessCache.cssForFile(lessFilePath, lessSource); + saveIntoSnapshotAuxiliaryData(lessFilePath, lessSource); } -} +}; diff --git a/script/lib/read-files.js b/script/lib/read-files.js index c360073aa..0fbc2e657 100644 --- a/script/lib/read-files.js +++ b/script/lib/read-files.js @@ -1,19 +1,19 @@ -'use strict' +'use strict'; -const fs = require('fs') +const fs = require('fs'); -module.exports = function (paths) { - return Promise.all(paths.map(readFile)) -} +module.exports = function(paths) { + return Promise.all(paths.map(readFile)); +}; -function readFile (path) { +function readFile(path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf8', (error, content) => { if (error) { - reject(error) + reject(error); } else { - resolve({path, content}) + resolve({ path, content }); } - }) - }) + }); + }); } diff --git a/script/lib/run-apm-install.js b/script/lib/run-apm-install.js index 4a1511c0c..b5ce08691 100644 --- a/script/lib/run-apm-install.js +++ b/script/lib/run-apm-install.js @@ -1,19 +1,19 @@ -'use strict' +'use strict'; -const childProcess = require('child_process') +const childProcess = require('child_process'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (packagePath, ci, stdioOptions) { - const installEnv = Object.assign({}, process.env) +module.exports = function(packagePath, ci, stdioOptions) { + const installEnv = Object.assign({}, process.env); // Set resource path so that apm can load metadata related to Atom. - installEnv.ATOM_RESOURCE_PATH = CONFIG.repositoryRootPath + installEnv.ATOM_RESOURCE_PATH = CONFIG.repositoryRootPath; // Set our target (Electron) version so that node-pre-gyp can download the // proper binaries. - installEnv.npm_config_target = CONFIG.appMetadata.electronVersion - childProcess.execFileSync( - CONFIG.getApmBinPath(), - [ci ? 'ci' : 'install'], - {env: installEnv, cwd: packagePath, stdio: stdioOptions || 'inherit'} - ) -} + installEnv.npm_config_target = CONFIG.appMetadata.electronVersion; + childProcess.execFileSync(CONFIG.getApmBinPath(), [ci ? 'ci' : 'install'], { + env: installEnv, + cwd: packagePath, + stdio: stdioOptions || 'inherit' + }); +}; diff --git a/script/lib/spawn-sync.js b/script/lib/spawn-sync.js index a9477e8ea..6765e2698 100644 --- a/script/lib/spawn-sync.js +++ b/script/lib/spawn-sync.js @@ -4,17 +4,19 @@ // `execSync` does, but we want to use `spawnSync` because it provides automatic // escaping for the supplied arguments. -const childProcess = require('child_process') +const childProcess = require('child_process'); -module.exports = function () { - const result = childProcess.spawnSync.apply(childProcess, arguments) +module.exports = function() { + const result = childProcess.spawnSync.apply(childProcess, arguments); if (result.error) { - throw result.error + throw result.error; } else if (result.status !== 0) { - if (result.stdout) console.error(result.stdout.toString()) - if (result.stderr) console.error(result.stderr.toString()) - throw new Error(`Command ${result.args.join(' ')} exited with code "${result.status}"`) + if (result.stdout) console.error(result.stdout.toString()); + if (result.stderr) console.error(result.stderr.toString()); + throw new Error( + `Command ${result.args.join(' ')} exited with code "${result.status}"` + ); } else { - return result + return result; } -} +}; diff --git a/script/lib/test-sign-on-mac.js b/script/lib/test-sign-on-mac.js index eb64b8c0d..60a985d33 100644 --- a/script/lib/test-sign-on-mac.js +++ b/script/lib/test-sign-on-mac.js @@ -1,23 +1,36 @@ -const spawnSync = require('./spawn-sync') +const spawnSync = require('./spawn-sync'); -module.exports = function (packagedAppPath) { - const result = - spawnSync('security', [ - 'find-certificate', '-c', 'Mac Developer' - ]) +module.exports = function(packagedAppPath) { + const result = spawnSync('security', [ + 'find-certificate', + '-c', + 'Mac Developer' + ]); - const certMatch = (result.stdout || '').toString().match(/"(Mac Developer.*\))"/) + const certMatch = (result.stdout || '') + .toString() + .match(/"(Mac Developer.*\))"/); if (!certMatch || !certMatch[1]) { - console.error('A "Mac Developer" certificate must be configured to perform test signing') + console.error( + 'A "Mac Developer" certificate must be configured to perform test signing' + ); } else { // This code-signs the application with a local certificate which won't be // useful anywhere else but the current machine // See this issue for more details: https://github.com/electron/electron/issues/7476#issuecomment-356084754 - console.log(`Found development certificate '${certMatch[1]}'`) - console.log(`Test-signing application at ${packagedAppPath}`) - spawnSync('codesign', [ - '--deep', '--force', '--verbose', - '--sign', certMatch[1], packagedAppPath - ], {stdio: 'inherit'}) + console.log(`Found development certificate '${certMatch[1]}'`); + console.log(`Test-signing application at ${packagedAppPath}`); + spawnSync( + 'codesign', + [ + '--deep', + '--force', + '--verbose', + '--sign', + certMatch[1], + packagedAppPath + ], + { stdio: 'inherit' } + ); } -} +}; diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index befd2477b..50fad2aa2 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -1,30 +1,51 @@ -'use strict' +'use strict'; -const CompileCache = require('../../src/compile-cache') -const fs = require('fs') -const glob = require('glob') -const path = require('path') +const CompileCache = require('../../src/compile-cache'); +const fs = require('fs'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { - console.log(`Transpiling Babel paths in ${CONFIG.intermediateAppPath}`) +module.exports = function() { + console.log(`Transpiling Babel paths in ${CONFIG.intermediateAppPath}`); for (let path of getPathsToTranspile()) { - transpileBabelPath(path) + transpileBabelPath(path); } -} +}; -function getPathsToTranspile () { - let paths = [] +function getPathsToTranspile() { + let paths = []; for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - paths = paths.concat(glob.sync( - path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.js'), - {ignore: path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, 'spec', '**', '*.js'), nodir: true} - )) + paths = paths.concat( + glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + '**', + '*.js' + ), + { + ignore: path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + 'spec', + '**', + '*.js' + ), + nodir: true + } + ) + ); } - return paths + return paths; } -function transpileBabelPath (path) { - fs.writeFileSync(path, CompileCache.addPathToCache(path, CONFIG.atomHomeDirPath)) +function transpileBabelPath(path) { + fs.writeFileSync( + path, + CompileCache.addPathToCache(path, CONFIG.atomHomeDirPath) + ); } diff --git a/script/lib/transpile-coffee-script-paths.js b/script/lib/transpile-coffee-script-paths.js index abf558f58..f88fe04a5 100644 --- a/script/lib/transpile-coffee-script-paths.js +++ b/script/lib/transpile-coffee-script-paths.js @@ -1,34 +1,65 @@ -'use strict' +'use strict'; -const CompileCache = require('../../src/compile-cache') -const fs = require('fs') -const glob = require('glob') -const path = require('path') +const CompileCache = require('../../src/compile-cache'); +const fs = require('fs'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { - console.log(`Transpiling CoffeeScript paths in ${CONFIG.intermediateAppPath}`) +module.exports = function() { + console.log( + `Transpiling CoffeeScript paths in ${CONFIG.intermediateAppPath}` + ); for (let path of getPathsToTranspile()) { - transpileCoffeeScriptPath(path) + transpileCoffeeScriptPath(path); } -} +}; -function getPathsToTranspile () { - let paths = [] - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.coffee'), {nodir: true})) - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'spec', '*.coffee'), {nodir: true})) +function getPathsToTranspile() { + let paths = []; + paths = paths.concat( + glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.coffee'), { + nodir: true + }) + ); + paths = paths.concat( + glob.sync(path.join(CONFIG.intermediateAppPath, 'spec', '*.coffee'), { + nodir: true + }) + ); for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - paths = paths.concat(glob.sync( - path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.coffee'), - {ignore: path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, 'spec', '**', '*.coffee'), nodir: true} - )) + paths = paths.concat( + glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + '**', + '*.coffee' + ), + { + ignore: path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + 'spec', + '**', + '*.coffee' + ), + nodir: true + } + ) + ); } - return paths + return paths; } -function transpileCoffeeScriptPath (coffeePath) { - const jsPath = coffeePath.replace(/coffee$/g, 'js') - fs.writeFileSync(jsPath, CompileCache.addPathToCache(coffeePath, CONFIG.atomHomeDirPath)) - fs.unlinkSync(coffeePath) +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/lib/transpile-cson-paths.js b/script/lib/transpile-cson-paths.js index 58f0fde6b..a4d8d95e9 100644 --- a/script/lib/transpile-cson-paths.js +++ b/script/lib/transpile-cson-paths.js @@ -1,32 +1,55 @@ -'use strict' +'use strict'; -const CompileCache = require('../../src/compile-cache') -const fs = require('fs') -const glob = require('glob') -const path = require('path') +const CompileCache = require('../../src/compile-cache'); +const fs = require('fs'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { - console.log(`Transpiling CSON paths in ${CONFIG.intermediateAppPath}`) +module.exports = function() { + console.log(`Transpiling CSON paths in ${CONFIG.intermediateAppPath}`); for (let path of getPathsToTranspile()) { - transpileCsonPath(path) + transpileCsonPath(path); } -} +}; -function getPathsToTranspile () { - let paths = [] +function getPathsToTranspile() { + let paths = []; for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - paths = paths.concat(glob.sync( - path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.cson'), - {ignore: path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, 'spec', '**', '*.cson'), nodir: true} - )) + paths = paths.concat( + glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + '**', + '*.cson' + ), + { + ignore: path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + 'spec', + '**', + '*.cson' + ), + nodir: true + } + ) + ); } - return paths + return paths; } -function transpileCsonPath (csonPath) { - const jsonPath = csonPath.replace(/cson$/g, 'json') - fs.writeFileSync(jsonPath, JSON.stringify(CompileCache.addPathToCache(csonPath, CONFIG.atomHomeDirPath))) - fs.unlinkSync(csonPath) +function transpileCsonPath(csonPath) { + const jsonPath = csonPath.replace(/cson$/g, 'json'); + fs.writeFileSync( + jsonPath, + JSON.stringify( + CompileCache.addPathToCache(csonPath, CONFIG.atomHomeDirPath) + ) + ); + fs.unlinkSync(csonPath); } diff --git a/script/lib/transpile-packages-with-custom-transpiler-paths.js b/script/lib/transpile-packages-with-custom-transpiler-paths.js index 7aaf1c319..d176bcdf7 100644 --- a/script/lib/transpile-packages-with-custom-transpiler-paths.js +++ b/script/lib/transpile-packages-with-custom-transpiler-paths.js @@ -1,56 +1,88 @@ -'use strict' +'use strict'; -const CompileCache = require('../../src/compile-cache') -const fs = require('fs-extra') -const glob = require('glob') -const path = require('path') +const CompileCache = require('../../src/compile-cache'); +const fs = require('fs-extra'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') -const backupNodeModules = require('./backup-node-modules') -const runApmInstall = require('./run-apm-install') +const CONFIG = require('../config'); +const backupNodeModules = require('./backup-node-modules'); +const runApmInstall = require('./run-apm-install'); -require('colors') +require('colors'); -module.exports = function () { - console.log(`Transpiling packages with custom transpiler configurations in ${CONFIG.intermediateAppPath}`) +module.exports = function() { + console.log( + `Transpiling packages with custom transpiler configurations in ${ + CONFIG.intermediateAppPath + }` + ); for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - const rootPackagePath = path.join(CONFIG.repositoryRootPath, 'node_modules', packageName) - const intermediatePackagePath = path.join(CONFIG.intermediateAppPath, 'node_modules', packageName) + const rootPackagePath = path.join( + CONFIG.repositoryRootPath, + 'node_modules', + packageName + ); + const intermediatePackagePath = path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName + ); - const metadataPath = path.join(intermediatePackagePath, 'package.json') - const metadata = require(metadataPath) + const metadataPath = path.join(intermediatePackagePath, 'package.json'); + const metadata = require(metadataPath); if (metadata.atomTranspilers) { - console.log(' transpiling for package '.cyan + packageName.cyan) - const rootPackageBackup = backupNodeModules(rootPackagePath) - const intermediatePackageBackup = backupNodeModules(intermediatePackagePath) + console.log(' transpiling for package '.cyan + packageName.cyan); + const rootPackageBackup = backupNodeModules(rootPackagePath); + const intermediatePackageBackup = backupNodeModules( + intermediatePackagePath + ); // Run `apm install` in the *root* package's path, so we get devDeps w/o apm's weird caching // Then copy this folder into the intermediate package's path so we can run the transpilation in-line. - runApmInstall(rootPackagePath) + runApmInstall(rootPackagePath); if (fs.existsSync(intermediatePackageBackup.nodeModulesPath)) { - fs.removeSync(intermediatePackageBackup.nodeModulesPath) + fs.removeSync(intermediatePackageBackup.nodeModulesPath); } - fs.copySync(rootPackageBackup.nodeModulesPath, intermediatePackageBackup.nodeModulesPath) + fs.copySync( + rootPackageBackup.nodeModulesPath, + intermediatePackageBackup.nodeModulesPath + ); - CompileCache.addTranspilerConfigForPath(intermediatePackagePath, metadata.name, metadata, metadata.atomTranspilers) + CompileCache.addTranspilerConfigForPath( + intermediatePackagePath, + metadata.name, + metadata, + metadata.atomTranspilers + ); for (let config of metadata.atomTranspilers) { - const pathsToCompile = glob.sync(path.join(intermediatePackagePath, config.glob), {nodir: true}) - pathsToCompile.forEach(transpilePath) + const pathsToCompile = glob.sync( + path.join(intermediatePackagePath, config.glob), + { nodir: true } + ); + pathsToCompile.forEach(transpilePath); } // Now that we've transpiled everything in-place, we no longer want Atom to try to transpile // the same files when they're being required. - delete metadata.atomTranspilers - fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, ' '), 'utf8') + delete metadata.atomTranspilers; + fs.writeFileSync( + metadataPath, + JSON.stringify(metadata, null, ' '), + 'utf8' + ); - CompileCache.removeTranspilerConfigForPath(intermediatePackagePath) - rootPackageBackup.restore() - intermediatePackageBackup.restore() + CompileCache.removeTranspilerConfigForPath(intermediatePackagePath); + rootPackageBackup.restore(); + intermediatePackageBackup.restore(); } } -} +}; -function transpilePath (path) { - fs.writeFileSync(path, CompileCache.addPathToCache(path, CONFIG.atomHomeDirPath)) +function transpilePath(path) { + fs.writeFileSync( + path, + CompileCache.addPathToCache(path, CONFIG.atomHomeDirPath) + ); } diff --git a/script/lib/transpile-peg-js-paths.js b/script/lib/transpile-peg-js-paths.js index 8d70748a1..5d500e806 100644 --- a/script/lib/transpile-peg-js-paths.js +++ b/script/lib/transpile-peg-js-paths.js @@ -1,31 +1,43 @@ -'use strict' +'use strict'; -const peg = require('pegjs') -const fs = require('fs') -const glob = require('glob') -const path = require('path') +const peg = require('pegjs'); +const fs = require('fs'); +const glob = require('glob'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function () { - console.log(`Transpiling PEG.js paths in ${CONFIG.intermediateAppPath}`) +module.exports = function() { + console.log(`Transpiling PEG.js paths in ${CONFIG.intermediateAppPath}`); for (let path of getPathsToTranspile()) { - transpilePegJsPath(path) + transpilePegJsPath(path); } -} +}; -function getPathsToTranspile () { - let paths = [] +function getPathsToTranspile() { + let paths = []; for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { - paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', packageName, '**', '*.pegjs'), {nodir: true})) + paths = paths.concat( + glob.sync( + path.join( + CONFIG.intermediateAppPath, + 'node_modules', + packageName, + '**', + '*.pegjs' + ), + { nodir: true } + ) + ); } - return paths + return paths; } -function transpilePegJsPath (pegJsPath) { - const inputCode = fs.readFileSync(pegJsPath, 'utf8') - const jsPath = pegJsPath.replace(/pegjs$/g, 'js') - const outputCode = 'module.exports = ' + peg.buildParser(inputCode, {output: 'source'}) - fs.writeFileSync(jsPath, outputCode) - fs.unlinkSync(pegJsPath) +function transpilePegJsPath(pegJsPath) { + const inputCode = fs.readFileSync(pegJsPath, 'utf8'); + const jsPath = pegJsPath.replace(/pegjs$/g, 'js'); + const outputCode = + 'module.exports = ' + peg.buildParser(inputCode, { output: 'source' }); + fs.writeFileSync(jsPath, outputCode); + fs.unlinkSync(pegJsPath); } diff --git a/script/lib/verify-machine-requirements.js b/script/lib/verify-machine-requirements.js index 17276d83b..51ca5a6cd 100644 --- a/script/lib/verify-machine-requirements.js +++ b/script/lib/verify-machine-requirements.js @@ -1,71 +1,85 @@ -'use strict' +'use strict'; -const childProcess = require('child_process') -const fs = require('fs') -const path = require('path') +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -module.exports = function (ci) { - verifyNode() - verifyNpm(ci) +module.exports = function(ci) { + verifyNode(); + verifyNpm(ci); if (process.platform === 'win32') { - verifyPython() + verifyPython(); } -} +}; -function verifyNode () { - const fullVersion = process.versions.node - const majorVersion = fullVersion.split('.')[0] +function verifyNode() { + const fullVersion = process.versions.node; + const majorVersion = fullVersion.split('.')[0]; if (majorVersion >= 6) { - console.log(`Node:\tv${fullVersion}`) + console.log(`Node:\tv${fullVersion}`); } else if (majorVersion >= 4) { - console.log(`Node:\tv${fullVersion}`) - console.warn('\tWarning: Building on Node below version 6 is deprecated. Please use Node 6.x+ to build Atom.') + console.log(`Node:\tv${fullVersion}`); + console.warn( + '\tWarning: Building on Node below version 6 is deprecated. Please use Node 6.x+ to build Atom.' + ); } else { - throw new Error(`node v4+ is required to build Atom. node v${fullVersion} is installed.`) + throw new Error( + `node v4+ is required to build Atom. node v${fullVersion} is installed.` + ); } } -function verifyNpm (ci) { - const stdout = childProcess.execFileSync(CONFIG.getNpmBinPath(ci), ['--version'], {env: process.env}) - const fullVersion = stdout.toString().trim() - const majorVersion = fullVersion.split('.')[0] - const oldestMajorVersionSupported = ci ? 6 : 3 +function verifyNpm(ci) { + const stdout = childProcess.execFileSync( + CONFIG.getNpmBinPath(ci), + ['--version'], + { env: process.env } + ); + const fullVersion = stdout.toString().trim(); + const majorVersion = fullVersion.split('.')[0]; + const oldestMajorVersionSupported = ci ? 6 : 3; if (majorVersion >= oldestMajorVersionSupported) { - console.log(`Npm:\tv${fullVersion}`) + console.log(`Npm:\tv${fullVersion}`); } else { - throw new Error(`npm v${oldestMajorVersionSupported}+ is required to build Atom. npm v${fullVersion} was detected.`) + throw new Error( + `npm v${oldestMajorVersionSupported}+ is required to build Atom. npm v${fullVersion} was detected.` + ); } } -function verifyPython () { - const systemDrive = process.env.SystemDrive || 'C:\\' - let pythonExecutable +function verifyPython() { + const systemDrive = process.env.SystemDrive || 'C:\\'; + let pythonExecutable; if (process.env.PYTHON) { - pythonExecutable = process.env.PYTHON + pythonExecutable = process.env.PYTHON; } else { - const pythonBinPath = path.join(systemDrive, 'Python27', 'python.exe') + const pythonBinPath = path.join(systemDrive, 'Python27', 'python.exe'); if (fs.existsSync(pythonBinPath)) { - pythonExecutable = pythonBinPath + pythonExecutable = pythonBinPath; } else { - pythonExecutable = 'python' + pythonExecutable = 'python'; } } - let stdout = childProcess.execFileSync(pythonExecutable, ['-c', 'import platform\nprint(platform.python_version())'], {env: process.env}) - if (stdout.indexOf('+') !== -1) stdout = stdout.replace(/\+/g, '') - if (stdout.indexOf('rc') !== -1) stdout = stdout.replace(/rc(.*)$/ig, '') - const fullVersion = stdout.toString().trim() - const versionComponents = fullVersion.split('.') - const majorVersion = Number(versionComponents[0]) - const minorVersion = Number(versionComponents[1]) + let stdout = childProcess.execFileSync( + pythonExecutable, + ['-c', 'import platform\nprint(platform.python_version())'], + { env: process.env } + ); + if (stdout.indexOf('+') !== -1) stdout = stdout.replace(/\+/g, ''); + if (stdout.indexOf('rc') !== -1) stdout = stdout.replace(/rc(.*)$/gi, ''); + const fullVersion = stdout.toString().trim(); + const versionComponents = fullVersion.split('.'); + const majorVersion = Number(versionComponents[0]); + const minorVersion = Number(versionComponents[1]); if (majorVersion === 2 && minorVersion === 7) { - console.log(`Python:\tv${fullVersion}`) + console.log(`Python:\tv${fullVersion}`); } else { throw new Error( `Python 2.7 is required to build Atom. ${pythonExecutable} returns version ${fullVersion}.\n` + - `Set the PYTHON env var to '/path/to/Python27/python.exe' if your python is installed in a non-default location.` - ) + `Set the PYTHON env var to '/path/to/Python27/python.exe' if your python is installed in a non-default location.` + ); } } diff --git a/script/license-overrides.js b/script/license-overrides.js index 09f2b9071..3385f98a3 100644 --- a/script/license-overrides.js +++ b/script/license-overrides.js @@ -3,14 +3,16 @@ module.exports = { repository: 'https://github.com/mikeal/aws-sign', license: 'MIT', source: 'index.js', - sourceText: '/*!\n * knox - auth\n * Copyright(c) 2010 LearnBoost \n * MIT Licensed\n */\n' + sourceText: + '/*!\n * knox - auth\n * Copyright(c) 2010 LearnBoost \n * MIT Licensed\n */\n' }, 'bufferjs@2.0.0': { repository: 'https://github.com/coolaj86/node-bufferjs', license: 'MIT', source: 'LICENSE.MIT', - sourceText: 'Copyright (c) 2010 AJ ONeal (and Contributors)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'Software\'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \'AS IS\', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.' + sourceText: + "Copyright (c) 2010 AJ ONeal (and Contributors)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." }, 'buffers@0.1.1': { @@ -35,50 +37,59 @@ module.exports = { 'promzard@0.2.0': { license: 'ISC', source: 'LICENSE in the repository', - sourceText: 'The ISC License\n\nCopyright (c) Isaac Z. Schlueter\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \'AS IS\' AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.' + sourceText: + "The ISC License\n\nCopyright (c) Isaac Z. Schlueter\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE." }, 'jschardet@1.1.1': { license: 'LGPL', source: 'README.md in the repository', - sourceText: 'JsChardet\n=========\n\nPort of python\'s chardet (http://chardet.feedparser.org/).\n\nLicense\n-------\n\nLGPL' + sourceText: + "JsChardet\n=========\n\nPort of python's chardet (http://chardet.feedparser.org/).\n\nLicense\n-------\n\nLGPL" }, 'core-js@0.4.10': { license: 'MIT', - source: 'http://rock.mit-license.org linked in source files and bower.json says MIT' + source: + 'http://rock.mit-license.org linked in source files and bower.json says MIT' }, 'log-driver@1.2.4': { license: 'ISC', source: 'LICENSE file in the repository', - sourceText: 'Copyright (c) 2014, Gregg Caines, gregg@caines.ca\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \'AS IS\' AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.' + sourceText: + "Copyright (c) 2014, Gregg Caines, gregg@caines.ca\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE." }, 'shelljs@0.3.0': { license: 'BSD', source: 'LICENSE file in repository - 3-clause BSD (aka BSD-new)', - sourceText: 'Copyright (c) 2012, Artur Adib \nAll rights reserved.\n\nYou may use this project under the terms of the New BSD license as follows:\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n * Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n * Neither the name of Artur Adib nor the\n names of the contributors may be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \'AS IS\'\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.' + sourceText: + "Copyright (c) 2012, Artur Adib \nAll rights reserved.\n\nYou may use this project under the terms of the New BSD license as follows:\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n * Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n * Neither the name of Artur Adib nor the\n names of the contributors may be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." }, 'json-schema@0.2.2': { repository: 'https://github.com/kriszyp/json-schema', license: 'BSD', - source: 'README links to https://github.com/dojo/dojo/blob/8b6a5e4c42f9cf777dd39eaae8b188e0ebb59a4c/LICENSE', - sourceText: 'Dojo is available under *either* the terms of the modified BSD license *or* the\nAcademic Free License version 2.1. As a recipient of Dojo, you may choose which\nlicense to receive this code under (except as noted in per-module LICENSE\nfiles). Some modules may not be the copyright of the Dojo Foundation. These\nmodules contain explicit declarations of copyright in both the LICENSE files in\nthe directories in which they reside and in the code itself. No external\ncontributions are allowed under licenses which are fundamentally incompatible\nwith the AFL or BSD licenses that Dojo is distributed under.\n\nThe text of the AFL and BSD licenses is reproduced below.\n\n-------------------------------------------------------------------------------\nThe \'New\' BSD License:\n**********************\n\nCopyright (c) 2005-2015, The Dojo Foundation\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n * Neither the name of the Dojo Foundation nor the names of its contributors\n may be used to endorse or promote products derived from this software\n without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \'AS IS\' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-------------------------------------------------------------------------------\nThe Academic Free License, v. 2.1:\n**********************************\n\nThis Academic Free License (the \'License\') applies to any original work of\nauthorship (the \'Original Work\') whose owner (the \'Licensor\') has placed the\nfollowing notice immediately following the copyright notice for the Original\nWork:\n\nLicensed under the Academic Free License version 2.1\n\n1) Grant of Copyright License. Licensor hereby grants You a world-wide,\nroyalty-free, non-exclusive, perpetual, sublicenseable license to do the\nfollowing:\n\na) to reproduce the Original Work in copies;\n\nb) to prepare derivative works (\'Derivative Works\') based upon the Original\nWork;\n\nc) to distribute copies of the Original Work and Derivative Works to the\npublic;\n\nd) to perform the Original Work publicly; and\n\ne) to display the Original Work publicly.\n\n2) Grant of Patent License. Licensor hereby grants You a world-wide,\nroyalty-free, non-exclusive, perpetual, sublicenseable license, under patent\nclaims owned or controlled by the Licensor that are embodied in the Original\nWork as furnished by the Licensor, to make, use, sell and offer for sale the\nOriginal Work and Derivative Works.\n\n3) Grant of Source Code License. The term \'Source Code\' means the preferred\nform of the Original Work for making modifications to it and all available\ndocumentation describing how to modify the Original Work. Licensor hereby\nagrees to provide a machine-readable copy of the Source Code of the Original\nWork along with each copy of the Original Work that Licensor distributes.\nLicensor reserves the right to satisfy this obligation by placing a\nmachine-readable copy of the Source Code in an information repository\nreasonably calculated to permit inexpensive and convenient access by You for as\nlong as Licensor continues to distribute the Original Work, and by publishing\nthe address of that information repository in a notice immediately following\nthe copyright notice that applies to the Original Work.\n\n4) Exclusions From License Grant. Neither the names of Licensor, nor the names\nof any contributors to the Original Work, nor any of their trademarks or\nservice marks, may be used to endorse or promote products derived from this\nOriginal Work without express prior written permission of the Licensor. Nothing\nin this License shall be deemed to grant any rights to trademarks, copyrights,\npatents, trade secrets or any other intellectual property of Licensor except as\nexpressly stated herein. No patent license is granted to make, use, sell or\noffer to sell embodiments of any patent claims other than the licensed claims\ndefined in Section 2. No right is granted to the trademarks of Licensor even if\nsuch marks are included in the Original Work. Nothing in this License shall be\ninterpreted to prohibit Licensor from licensing under different terms from this\nLicense any Original Work that Licensor otherwise would have a right to\nlicense.\n\n5) This section intentionally omitted.\n\n6) Attribution Rights. You must retain, in the Source Code of any Derivative\nWorks that You create, all copyright, patent or trademark notices from the\nSource Code of the Original Work, as well as any notices of licensing and any\ndescriptive text identified therein as an \'Attribution Notice.\' You must cause\nthe Source Code for any Derivative Works that You create to carry a prominent\nAttribution Notice reasonably calculated to inform recipients that You have\nmodified the Original Work.\n\n7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that\nthe copyright in and to the Original Work and the patent rights granted herein\nby Licensor are owned by the Licensor or are sublicensed to You under the terms\nof this License with the permission of the contributor(s) of those copyrights\nand patent rights. Except as expressly stated in the immediately proceeding\nsentence, the Original Work is provided under this License on an \'AS IS\' BASIS\nand WITHOUT WARRANTY, either express or implied, including, without limitation,\nthe warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU.\nThis DISCLAIMER OF WARRANTY constitutes an essential part of this License. No\nlicense to Original Work is granted hereunder except under this disclaimer.\n\n8) Limitation of Liability. Under no circumstances and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise, shall the\nLicensor be liable to any person for any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License\nor the use of the Original Work including, without limitation, damages for loss\nof goodwill, work stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses. This limitation of liability shall not\napply to liability for death or personal injury resulting from Licensor\'s\nnegligence to the extent applicable law prohibits such limitation. Some\njurisdictions do not allow the exclusion or limitation of incidental or\nconsequential damages, so this exclusion and limitation may not apply to You.\n\n9) Acceptance and Termination. If You distribute copies of the Original Work or\na Derivative Work, You must make a reasonable effort under the circumstances to\nobtain the express assent of recipients to the terms of this License. Nothing\nelse but this License (or another written agreement between Licensor and You)\ngrants You permission to create Derivative Works based upon the Original Work\nor to exercise any of the rights granted in Section 1 herein, and any attempt\nto do so except under the terms of this License (or another written agreement\nbetween Licensor and You) is expressly prohibited by U.S. copyright law, the\nequivalent laws of other countries, and by international treaty. Therefore, by\nexercising any of the rights granted to You in Section 1 herein, You indicate\nYour acceptance of this License and all of its terms and conditions.\n\n10) Termination for Patent Action. This License shall terminate automatically\nand You may no longer exercise any of the rights granted to You by this License\nas of the date You commence an action, including a cross-claim or counterclaim,\nagainst Licensor or any licensee alleging that the Original Work infringes a\npatent. This termination provision shall not apply for an action alleging\npatent infringement by combinations of the Original Work with other software or\nhardware.\n\n11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this\nLicense may be brought only in the courts of a jurisdiction wherein the\nLicensor resides or in which Licensor conducts its primary business, and under\nthe laws of that jurisdiction excluding its conflict-of-law provisions. The\napplication of the United Nations Convention on Contracts for the International\nSale of Goods is expressly excluded. Any use of the Original Work outside the\nscope of this License or after its termination shall be subject to the\nrequirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et\nseq., the equivalent laws of other countries, and international treaty. This\nsection shall survive the termination of this License.\n\n12) Attorneys Fees. In any action to enforce the terms of this License or\nseeking damages relating thereto, the prevailing party shall be entitled to\nrecover its costs and expenses, including, without limitation, reasonable\nattorneys\' fees and costs incurred in connection with such action, including\nany appeal of such action. This section shall survive the termination of this\nLicense.\n\n13) Miscellaneous. This License represents the complete agreement concerning\nthe subject matter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent necessary to\nmake it enforceable.\n\n14) Definition of \'You\' in This License. \'You\' throughout this License, whether\nin upper or lower case, means an individual or a legal entity exercising rights\nunder, and complying with all of the terms of, this License. For legal\nentities, \'You\' includes any entity that controls, is controlled by, or is\nunder common control with you. For purposes of this definition, \'control\' means\n(i) the power, direct or indirect, to cause the direction or management of such\nentity, whether by contract or otherwise, or (ii) ownership of fifty percent\n(50%) or more of the outstanding shares, or (iii) beneficial ownership of such\nentity.\n\n15) Right to Use. You may use the Original Work in all ways not otherwise\nrestricted or conditioned by this License or by law, and Licensor promises not\nto interfere with or be responsible for such uses by You.\n\nThis license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved.\nPermission is hereby granted to copy and distribute this license without\nmodification. This license may not be modified without the express written\npermission of its copyright owner.' + source: + 'README links to https://github.com/dojo/dojo/blob/8b6a5e4c42f9cf777dd39eaae8b188e0ebb59a4c/LICENSE', + sourceText: + "Dojo is available under *either* the terms of the modified BSD license *or* the\nAcademic Free License version 2.1. As a recipient of Dojo, you may choose which\nlicense to receive this code under (except as noted in per-module LICENSE\nfiles). Some modules may not be the copyright of the Dojo Foundation. These\nmodules contain explicit declarations of copyright in both the LICENSE files in\nthe directories in which they reside and in the code itself. No external\ncontributions are allowed under licenses which are fundamentally incompatible\nwith the AFL or BSD licenses that Dojo is distributed under.\n\nThe text of the AFL and BSD licenses is reproduced below.\n\n-------------------------------------------------------------------------------\nThe 'New' BSD License:\n**********************\n\nCopyright (c) 2005-2015, The Dojo Foundation\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n * Neither the name of the Dojo Foundation nor the names of its contributors\n may be used to endorse or promote products derived from this software\n without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-------------------------------------------------------------------------------\nThe Academic Free License, v. 2.1:\n**********************************\n\nThis Academic Free License (the 'License') applies to any original work of\nauthorship (the 'Original Work') whose owner (the 'Licensor') has placed the\nfollowing notice immediately following the copyright notice for the Original\nWork:\n\nLicensed under the Academic Free License version 2.1\n\n1) Grant of Copyright License. Licensor hereby grants You a world-wide,\nroyalty-free, non-exclusive, perpetual, sublicenseable license to do the\nfollowing:\n\na) to reproduce the Original Work in copies;\n\nb) to prepare derivative works ('Derivative Works') based upon the Original\nWork;\n\nc) to distribute copies of the Original Work and Derivative Works to the\npublic;\n\nd) to perform the Original Work publicly; and\n\ne) to display the Original Work publicly.\n\n2) Grant of Patent License. Licensor hereby grants You a world-wide,\nroyalty-free, non-exclusive, perpetual, sublicenseable license, under patent\nclaims owned or controlled by the Licensor that are embodied in the Original\nWork as furnished by the Licensor, to make, use, sell and offer for sale the\nOriginal Work and Derivative Works.\n\n3) Grant of Source Code License. The term 'Source Code' means the preferred\nform of the Original Work for making modifications to it and all available\ndocumentation describing how to modify the Original Work. Licensor hereby\nagrees to provide a machine-readable copy of the Source Code of the Original\nWork along with each copy of the Original Work that Licensor distributes.\nLicensor reserves the right to satisfy this obligation by placing a\nmachine-readable copy of the Source Code in an information repository\nreasonably calculated to permit inexpensive and convenient access by You for as\nlong as Licensor continues to distribute the Original Work, and by publishing\nthe address of that information repository in a notice immediately following\nthe copyright notice that applies to the Original Work.\n\n4) Exclusions From License Grant. Neither the names of Licensor, nor the names\nof any contributors to the Original Work, nor any of their trademarks or\nservice marks, may be used to endorse or promote products derived from this\nOriginal Work without express prior written permission of the Licensor. Nothing\nin this License shall be deemed to grant any rights to trademarks, copyrights,\npatents, trade secrets or any other intellectual property of Licensor except as\nexpressly stated herein. No patent license is granted to make, use, sell or\noffer to sell embodiments of any patent claims other than the licensed claims\ndefined in Section 2. No right is granted to the trademarks of Licensor even if\nsuch marks are included in the Original Work. Nothing in this License shall be\ninterpreted to prohibit Licensor from licensing under different terms from this\nLicense any Original Work that Licensor otherwise would have a right to\nlicense.\n\n5) This section intentionally omitted.\n\n6) Attribution Rights. You must retain, in the Source Code of any Derivative\nWorks that You create, all copyright, patent or trademark notices from the\nSource Code of the Original Work, as well as any notices of licensing and any\ndescriptive text identified therein as an 'Attribution Notice.' You must cause\nthe Source Code for any Derivative Works that You create to carry a prominent\nAttribution Notice reasonably calculated to inform recipients that You have\nmodified the Original Work.\n\n7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that\nthe copyright in and to the Original Work and the patent rights granted herein\nby Licensor are owned by the Licensor or are sublicensed to You under the terms\nof this License with the permission of the contributor(s) of those copyrights\nand patent rights. Except as expressly stated in the immediately proceeding\nsentence, the Original Work is provided under this License on an 'AS IS' BASIS\nand WITHOUT WARRANTY, either express or implied, including, without limitation,\nthe warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU.\nThis DISCLAIMER OF WARRANTY constitutes an essential part of this License. No\nlicense to Original Work is granted hereunder except under this disclaimer.\n\n8) Limitation of Liability. Under no circumstances and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise, shall the\nLicensor be liable to any person for any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License\nor the use of the Original Work including, without limitation, damages for loss\nof goodwill, work stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses. This limitation of liability shall not\napply to liability for death or personal injury resulting from Licensor's\nnegligence to the extent applicable law prohibits such limitation. Some\njurisdictions do not allow the exclusion or limitation of incidental or\nconsequential damages, so this exclusion and limitation may not apply to You.\n\n9) Acceptance and Termination. If You distribute copies of the Original Work or\na Derivative Work, You must make a reasonable effort under the circumstances to\nobtain the express assent of recipients to the terms of this License. Nothing\nelse but this License (or another written agreement between Licensor and You)\ngrants You permission to create Derivative Works based upon the Original Work\nor to exercise any of the rights granted in Section 1 herein, and any attempt\nto do so except under the terms of this License (or another written agreement\nbetween Licensor and You) is expressly prohibited by U.S. copyright law, the\nequivalent laws of other countries, and by international treaty. Therefore, by\nexercising any of the rights granted to You in Section 1 herein, You indicate\nYour acceptance of this License and all of its terms and conditions.\n\n10) Termination for Patent Action. This License shall terminate automatically\nand You may no longer exercise any of the rights granted to You by this License\nas of the date You commence an action, including a cross-claim or counterclaim,\nagainst Licensor or any licensee alleging that the Original Work infringes a\npatent. This termination provision shall not apply for an action alleging\npatent infringement by combinations of the Original Work with other software or\nhardware.\n\n11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this\nLicense may be brought only in the courts of a jurisdiction wherein the\nLicensor resides or in which Licensor conducts its primary business, and under\nthe laws of that jurisdiction excluding its conflict-of-law provisions. The\napplication of the United Nations Convention on Contracts for the International\nSale of Goods is expressly excluded. Any use of the Original Work outside the\nscope of this License or after its termination shall be subject to the\nrequirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et\nseq., the equivalent laws of other countries, and international treaty. This\nsection shall survive the termination of this License.\n\n12) Attorneys Fees. In any action to enforce the terms of this License or\nseeking damages relating thereto, the prevailing party shall be entitled to\nrecover its costs and expenses, including, without limitation, reasonable\nattorneys' fees and costs incurred in connection with such action, including\nany appeal of such action. This section shall survive the termination of this\nLicense.\n\n13) Miscellaneous. This License represents the complete agreement concerning\nthe subject matter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent necessary to\nmake it enforceable.\n\n14) Definition of 'You' in This License. 'You' throughout this License, whether\nin upper or lower case, means an individual or a legal entity exercising rights\nunder, and complying with all of the terms of, this License. For legal\nentities, 'You' includes any entity that controls, is controlled by, or is\nunder common control with you. For purposes of this definition, 'control' means\n(i) the power, direct or indirect, to cause the direction or management of such\nentity, whether by contract or otherwise, or (ii) ownership of fifty percent\n(50%) or more of the outstanding shares, or (iii) beneficial ownership of such\nentity.\n\n15) Right to Use. You may use the Original Work in all ways not otherwise\nrestricted or conditioned by this License or by law, and Licensor promises not\nto interfere with or be responsible for such uses by You.\n\nThis license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved.\nPermission is hereby granted to copy and distribute this license without\nmodification. This license may not be modified without the express written\npermission of its copyright owner." }, 'inherit@2.2.2': { license: 'MIT', repository: 'https://github.com/dfilatov/inherit', source: 'LICENSE.md', - sourceText: 'Copyright (c) 2012 Dmitry Filatov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'Software\'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \'AS IS\', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.' + sourceText: + "Copyright (c) 2012 Dmitry Filatov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." }, 'tweetnacl@0.14.3': { license: 'Public Domain', repository: 'https://github.com/dchest/tweetnacl', source: 'COPYING.txt', - sourceText: 'Public Domain\n\nThe person who associated a work with this deed has dedicated the work to the\npublic domain by waiving all of his or her rights to the work worldwide under\ncopyright law, including all related and neighboring rights, to the extent\nallowed by law.\n\nYou can copy, modify, distribute and perform the work, even for commercial\npurposes, all without asking permission.' + sourceText: + 'Public Domain\n\nThe person who associated a work with this deed has dedicated the work to the\npublic domain by waiving all of his or her rights to the work worldwide under\ncopyright law, including all related and neighboring rights, to the extent\nallowed by law.\n\nYou can copy, modify, distribute and perform the work, even for commercial\npurposes, all without asking permission.' } -} +}; diff --git a/script/update-server/run-server.js b/script/update-server/run-server.js index 6d6bba459..e38c84e36 100644 --- a/script/update-server/run-server.js +++ b/script/update-server/run-server.js @@ -1,98 +1,134 @@ -require('colors') +require('colors'); -const fs = require('fs') -const path = require('path') -const express = require('express') +const fs = require('fs'); +const path = require('path'); +const express = require('express'); -const app = express() -const port = process.env.PORT || 3456 +const app = express(); +const port = process.env.PORT || 3456; // Load the metadata for the local build of Atom -const buildPath = path.resolve(__dirname, '..', '..', 'out') -const packageJsonPath = path.join(buildPath, 'app', 'package.json') +const buildPath = path.resolve(__dirname, '..', '..', 'out'); +const packageJsonPath = path.join(buildPath, 'app', 'package.json'); if (!fs.existsSync(buildPath) || !fs.existsSync(packageJsonPath)) { - console.log(`This script requires a full Atom build with release packages for the current platform in the following path:\n ${buildPath}\n`) + console.log( + `This script requires a full Atom build with release packages for the current platform in the following path:\n ${buildPath}\n` + ); if (process.platform === 'darwin') { - console.log(`Run this command before trying again:\n script/build --compress-artifacts --test-sign\n\n`) + console.log( + `Run this command before trying again:\n script/build --compress-artifacts --test-sign\n\n` + ); } else if (process.platform === 'win32') { - console.log(`Run this command before trying again:\n script/build --create-windows-installer\n\n`) + console.log( + `Run this command before trying again:\n script/build --create-windows-installer\n\n` + ); } - process.exit(1) + process.exit(1); } -const appMetadata = require(packageJsonPath) -const versionMatch = appMetadata.version.match(/-(beta|nightly)\d+$/) -const releaseChannel = versionMatch ? versionMatch[1] : 'stable' +const appMetadata = require(packageJsonPath); +const versionMatch = appMetadata.version.match(/-(beta|nightly)\d+$/); +const releaseChannel = versionMatch ? versionMatch[1] : 'stable'; -console.log(`Serving ${appMetadata.productName} release assets (channel = ${releaseChannel})\n`.green) +console.log( + `Serving ${ + appMetadata.productName + } release assets (channel = ${releaseChannel})\n`.green +); -function getMacZip (req, res) { - console.log(`Received request for atom-mac.zip, sending it`) - res.sendFile(path.join(buildPath, 'atom-mac.zip')) +function getMacZip(req, res) { + console.log(`Received request for atom-mac.zip, sending it`); + res.sendFile(path.join(buildPath, 'atom-mac.zip')); } -function getMacUpdates (req, res) { +function getMacUpdates(req, res) { if (req.query.version !== appMetadata.version) { const updateInfo = { name: appMetadata.version, pub_date: new Date().toISOString(), url: `http://localhost:${port}/mac/atom-mac.zip`, notes: '

          No Details

          ' - } + }; - console.log(`Received request for macOS updates (version = ${req.query.version}), sending\n`, updateInfo) - res.json(updateInfo) + console.log( + `Received request for macOS updates (version = ${ + req.query.version + }), sending\n`, + updateInfo + ); + res.json(updateInfo); } else { - console.log(`Received request for macOS updates, sending 204 as Atom is up to date (version = ${req.query.version})`) - res.sendStatus(204) + console.log( + `Received request for macOS updates, sending 204 as Atom is up to date (version = ${ + req.query.version + })` + ); + res.sendStatus(204); } } -function getReleasesFile (fileName) { - return function (req, res) { - console.log(`Received request for ${fileName}, version: ${req.query.version}`) +function getReleasesFile(fileName) { + return function(req, res) { + console.log( + `Received request for ${fileName}, version: ${req.query.version}` + ); if (req.query.version) { - const versionMatch = (req.query.version || '').match(/-(beta|nightly)\d+$/) - const versionChannel = (versionMatch && versionMatch[1]) || 'stable' + const versionMatch = (req.query.version || '').match( + /-(beta|nightly)\d+$/ + ); + const versionChannel = (versionMatch && versionMatch[1]) || 'stable'; if (releaseChannel !== versionChannel) { - console.log(`Atom requested an update for version ${req.query.version} but the current release channel is ${releaseChannel}`) - res.sendStatus(404) - return + console.log( + `Atom requested an update for version ${ + req.query.version + } but the current release channel is ${releaseChannel}` + ); + res.sendStatus(404); + return; } } - res.sendFile(path.join(buildPath, fileName)) - } + res.sendFile(path.join(buildPath, fileName)); + }; } -function getNupkgFile (is64bit) { - return function (req, res) { - let nupkgFile = req.params.nupkg +function getNupkgFile(is64bit) { + return function(req, res) { + let nupkgFile = req.params.nupkg; if (is64bit) { - const nupkgMatch = nupkgFile.match(/atom-(.+)-(delta|full)\.nupkg/) + const nupkgMatch = nupkgFile.match(/atom-(.+)-(delta|full)\.nupkg/); if (nupkgMatch) { - nupkgFile = `atom-x64-${nupkgMatch[1]}-${nupkgMatch[2]}.nupkg` + nupkgFile = `atom-x64-${nupkgMatch[1]}-${nupkgMatch[2]}.nupkg`; } } - console.log(`Received request for ${req.params.nupkg}, sending ${nupkgFile}`) - res.sendFile(path.join(buildPath, nupkgFile)) - } + console.log( + `Received request for ${req.params.nupkg}, sending ${nupkgFile}` + ); + res.sendFile(path.join(buildPath, nupkgFile)); + }; } if (process.platform === 'darwin') { - app.get('/mac/atom-mac.zip', getMacZip) - app.get('/api/updates', getMacUpdates) + app.get('/mac/atom-mac.zip', getMacZip); + app.get('/api/updates', getMacUpdates); } else if (process.platform === 'win32') { - app.get('/api/updates/RELEASES', getReleasesFile('RELEASES')) - app.get('/api/updates/:nupkg', getNupkgFile()) - app.get('/api/updates-x64/RELEASES', getReleasesFile('RELEASES-x64')) - app.get('/api/updates-x64/:nupkg', getNupkgFile(true)) + app.get('/api/updates/RELEASES', getReleasesFile('RELEASES')); + app.get('/api/updates/:nupkg', getNupkgFile()); + app.get('/api/updates-x64/RELEASES', getReleasesFile('RELEASES-x64')); + app.get('/api/updates-x64/:nupkg', getNupkgFile(true)); } else { - console.log(`The current platform '${process.platform}' doesn't support Squirrel updates, exiting.`.red) - process.exit(1) + console.log( + `The current platform '${ + process.platform + }' doesn't support Squirrel updates, exiting.`.red + ); + process.exit(1); } app.listen(port, () => { - console.log(`Run Atom with ATOM_UPDATE_URL_PREFIX="http://localhost:${port}" set to test updates!\n`.yellow) -}) + console.log( + `Run Atom with ATOM_UPDATE_URL_PREFIX="http://localhost:${port}" set to test updates!\n` + .yellow + ); +}); diff --git a/script/vsts/get-release-version.js b/script/vsts/get-release-version.js index 61d284d96..1db97aa0e 100644 --- a/script/vsts/get-release-version.js +++ b/script/vsts/get-release-version.js @@ -1,60 +1,78 @@ -const path = require('path') -const request = require('request-promise-native') +const path = require('path'); +const request = require('request-promise-native'); -const repositoryRootPath = path.resolve(__dirname, '..', '..') -const appMetadata = require(path.join(repositoryRootPath, 'package.json')) +const repositoryRootPath = path.resolve(__dirname, '..', '..'); +const appMetadata = require(path.join(repositoryRootPath, 'package.json')); -const yargs = require('yargs') +const yargs = require('yargs'); const argv = yargs .usage('Usage: $0 [options]') .help('help') .describe('nightly', 'Indicates that a nightly version should be produced') - .wrap(yargs.terminalWidth()) - .argv + .wrap(yargs.terminalWidth()).argv; -async function getReleaseVersion () { - let releaseVersion = process.env.ATOM_RELEASE_VERSION || appMetadata.version +async function getReleaseVersion() { + let releaseVersion = process.env.ATOM_RELEASE_VERSION || appMetadata.version; if (argv.nightly) { const releases = await request({ url: 'https://api.github.com/repos/atom/atom-nightly-releases/releases', - headers: {'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'Atom Release Build'}, + headers: { + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'Atom Release Build' + }, json: true - }) + }); - let releaseNumber = 0 - const baseVersion = appMetadata.version.split('-')[0] + let releaseNumber = 0; + const baseVersion = appMetadata.version.split('-')[0]; if (releases && releases.length > 0) { - const latestRelease = releases.find(r => !r.draft) - const versionMatch = latestRelease.tag_name.match(/^v?(\d+\.\d+\.\d+)-nightly(\d+)$/) + const latestRelease = releases.find(r => !r.draft); + const versionMatch = latestRelease.tag_name.match( + /^v?(\d+\.\d+\.\d+)-nightly(\d+)$/ + ); if (versionMatch && versionMatch[1] === baseVersion) { - releaseNumber = parseInt(versionMatch[2]) + 1 + releaseNumber = parseInt(versionMatch[2]) + 1; } } - releaseVersion = `${baseVersion}-nightly${releaseNumber}` + releaseVersion = `${baseVersion}-nightly${releaseNumber}`; } // Set our ReleaseVersion build variable and update VSTS' build number to // include the version. Writing these strings to stdout causes VSTS to set // the associated variables. - console.log(`##vso[task.setvariable variable=ReleaseVersion;isOutput=true]${releaseVersion}`) + console.log( + `##vso[task.setvariable variable=ReleaseVersion;isOutput=true]${releaseVersion}` + ); if (!process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER) { // Only set the build number on non-PR builds as it causes build errors when // non-admins send PRs to the repo - console.log(`##vso[build.updatebuildnumber]${releaseVersion}+${process.env.BUILD_BUILDID}`) + console.log( + `##vso[build.updatebuildnumber]${releaseVersion}+${ + process.env.BUILD_BUILDID + }` + ); } // Write out some variables that indicate whether artifacts should be uploaded - const buildBranch = process.env.BUILD_SOURCEBRANCHNAME - const isReleaseBranch = process.env.IS_RELEASE_BRANCH || argv.nightly || buildBranch.match(/\d\.\d+-releases/) !== null + const buildBranch = process.env.BUILD_SOURCEBRANCHNAME; + const isReleaseBranch = + process.env.IS_RELEASE_BRANCH || + argv.nightly || + buildBranch.match(/\d\.\d+-releases/) !== null; const isSignedZipBranch = !isReleaseBranch && (process.env.IS_SIGNED_ZIP_BRANCH || - buildBranch.startsWith('electron-') || - buildBranch === 'master' && !process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER) - console.log(`##vso[task.setvariable variable=IsReleaseBranch;isOutput=true]${isReleaseBranch}`) - console.log(`##vso[task.setvariable variable=IsSignedZipBranch;isOutput=true]${isSignedZipBranch}`) + buildBranch.startsWith('electron-') || + (buildBranch === 'master' && + !process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER)); + console.log( + `##vso[task.setvariable variable=IsReleaseBranch;isOutput=true]${isReleaseBranch}` + ); + console.log( + `##vso[task.setvariable variable=IsSignedZipBranch;isOutput=true]${isSignedZipBranch}` + ); } -getReleaseVersion() +getReleaseVersion(); diff --git a/script/vsts/lib/release-notes.js b/script/vsts/lib/release-notes.js index c7c9e7d41..c76c23317 100644 --- a/script/vsts/lib/release-notes.js +++ b/script/vsts/lib/release-notes.js @@ -1,49 +1,59 @@ -const semver = require('semver') -const octokit = require('@octokit/rest')() -const changelog = require('pr-changelog') -const childProcess = require('child_process') +const semver = require('semver'); +const octokit = require('@octokit/rest')(); +const changelog = require('pr-changelog'); +const childProcess = require('child_process'); -module.exports.getRelease = async function (releaseVersion, githubToken) { +module.exports.getRelease = async function(releaseVersion, githubToken) { if (githubToken) { octokit.authenticate({ type: 'oauth', token: githubToken - }) + }); } - const releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'}) - const release = releases.data.find(r => semver.eq(r.name, releaseVersion)) + const releases = await octokit.repos.getReleases({ + owner: 'atom', + repo: 'atom' + }); + const release = releases.data.find(r => semver.eq(r.name, releaseVersion)); return { exists: release !== undefined, isDraft: release && release.draft, releaseNotes: release ? release.body : undefined - } -} + }; +}; -module.exports.generateForVersion = async function (releaseVersion, githubToken, oldReleaseNotes) { - let oldVersion = null - let oldVersionName = null - const parsedVersion = semver.parse(releaseVersion) - const newVersionBranch = getBranchForVersion(parsedVersion) +module.exports.generateForVersion = async function( + releaseVersion, + githubToken, + oldReleaseNotes +) { + let oldVersion = null; + let oldVersionName = null; + const parsedVersion = semver.parse(releaseVersion); + const newVersionBranch = getBranchForVersion(parsedVersion); if (githubToken) { - changelog.setGithubAccessToken(githubToken) + changelog.setGithubAccessToken(githubToken); octokit.authenticate({ type: 'oauth', token: githubToken - }) + }); } if (parsedVersion.prerelease && parsedVersion.prerelease[0] === 'beta0') { // For beta0 releases, stable hasn't been released yet so compare against // the stable version's release branch - oldVersion = `${parsedVersion.major}.${parsedVersion.minor - 1}-releases` - oldVersionName = `v${parsedVersion.major}.${parsedVersion.minor - 1}.0` + oldVersion = `${parsedVersion.major}.${parsedVersion.minor - 1}-releases`; + oldVersionName = `v${parsedVersion.major}.${parsedVersion.minor - 1}.0`; } else { - let releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'}) - oldVersion = 'v' + getPreviousRelease(releaseVersion, releases.data).name - oldVersionName = oldVersion + let releases = await octokit.repos.getReleases({ + owner: 'atom', + repo: 'atom' + }); + oldVersion = 'v' + getPreviousRelease(releaseVersion, releases.data).name; + oldVersionName = oldVersion; } const allChangesText = await changelog.getChangelog({ @@ -52,21 +62,27 @@ module.exports.generateForVersion = async function (releaseVersion, githubToken, fromTag: oldVersion, toTag: newVersionBranch, dependencyKey: 'packageDependencies', - changelogFormatter: function ({pullRequests, owner, repo, fromTag, toTag}) { - let prString = changelog.pullRequestsToString(pullRequests) - let title = repo + changelogFormatter: function({ + pullRequests, + owner, + repo, + fromTag, + toTag + }) { + let prString = changelog.pullRequestsToString(pullRequests); + let title = repo; if (repo === 'atom') { - title = 'Atom Core' - fromTag = oldVersionName - toTag = releaseVersion + title = 'Atom Core'; + fromTag = oldVersionName; + toTag = releaseVersion; } - return `### [${title}](https://github.com/${owner}/${repo})\n\n${fromTag}...${toTag}\n\n${prString}` + return `### [${title}](https://github.com/${owner}/${repo})\n\n${fromTag}...${toTag}\n\n${prString}`; } - }) + }); const writtenReleaseNotes = extractWrittenReleaseNotes(oldReleaseNotes) || - '**TODO**: Pull relevant changes here!' + '**TODO**: Pull relevant changes here!'; return `## Notable Changes\n ${writtenReleaseNotes}\n @@ -74,62 +90,78 @@ ${writtenReleaseNotes}\n All Changes\n ${allChangesText} -` -} +`; +}; -module.exports.generateForNightly = async function (releaseVersion, githubToken) { - const releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom-nightly-releases'}) - const previousRelease = getPreviousRelease(releaseVersion, releases.data) - const oldReleaseNotes = previousRelease ? previousRelease.body : undefined +module.exports.generateForNightly = async function( + releaseVersion, + githubToken +) { + const releases = await octokit.repos.getReleases({ + owner: 'atom', + repo: 'atom-nightly-releases' + }); + const previousRelease = getPreviousRelease(releaseVersion, releases.data); + const oldReleaseNotes = previousRelease ? previousRelease.body : undefined; - const latestCommitResult = childProcess.spawnSync('git', ['rev-parse', '--short', 'HEAD']) + const latestCommitResult = childProcess.spawnSync('git', [ + 'rev-parse', + '--short', + 'HEAD' + ]); if (latestCommitResult && oldReleaseNotes) { - const latestCommit = latestCommitResult.stdout.toString().trim() - const extractMatch = oldReleaseNotes.match(/atom\/atom\/compare\/([0-9a-f]{5,40})\.\.\.([0-9a-f]{5,40})/) + const latestCommit = latestCommitResult.stdout.toString().trim(); + const extractMatch = oldReleaseNotes.match( + /atom\/atom\/compare\/([0-9a-f]{5,40})\.\.\.([0-9a-f]{5,40})/ + ); if (extractMatch) { - return `### Click [here](https://github.com/atom/atom/compare/${extractMatch[2]}...${latestCommit}) to see the changes included with this release! :atom: :night_with_stars:` + return `### Click [here](https://github.com/atom/atom/compare/${ + extractMatch[2] + }...${latestCommit}) to see the changes included with this release! :atom: :night_with_stars:`; } } - return undefined -} + return undefined; +}; -function extractWrittenReleaseNotes (oldReleaseNotes) { +function extractWrittenReleaseNotes(oldReleaseNotes) { if (oldReleaseNotes) { - const extractMatch = oldReleaseNotes.match(/^## Notable Changes\r\n([\s\S]*)
          /) + const extractMatch = oldReleaseNotes.match( + /^## Notable Changes\r\n([\s\S]*)
          / + ); if (extractMatch && extractMatch[1]) { - return extractMatch[1].trim() + return extractMatch[1].trim(); } } - return undefined + return undefined; } -function getPreviousRelease (version, allReleases) { - const versionIsStable = semver.prerelease(version) === null +function getPreviousRelease(version, allReleases) { + const versionIsStable = semver.prerelease(version) === null; // Make sure versions are sorted before using them - allReleases.sort((v1, v2) => semver.rcompare(v1.name, v2.name)) + allReleases.sort((v1, v2) => semver.rcompare(v1.name, v2.name)); for (let release of allReleases) { if (versionIsStable && semver.prerelease(release.name)) { - continue + continue; } if (semver.lt(release.name, version)) { - return release + return release; } } - return null + return null; } -function getBranchForVersion (version) { - let parsedVersion = version +function getBranchForVersion(version) { + let parsedVersion = version; if (!(version instanceof semver.SemVer)) { - parsedVersion = semver.parse(version) + parsedVersion = semver.parse(version); } - return `${parsedVersion.major}.${parsedVersion.minor}-releases` + return `${parsedVersion.major}.${parsedVersion.minor}-releases`; } diff --git a/script/vsts/lib/upload-linux-packages.js b/script/vsts/lib/upload-linux-packages.js index cf56f0e68..98036a645 100644 --- a/script/vsts/lib/upload-linux-packages.js +++ b/script/vsts/lib/upload-linux-packages.js @@ -1,23 +1,23 @@ -const fs = require('fs') -const path = require('path') -const request = require('request-promise-native') +const fs = require('fs'); +const path = require('path'); +const request = require('request-promise-native'); -module.exports = async function (packageRepoName, apiToken, version, artifacts) { +module.exports = async function(packageRepoName, apiToken, version, artifacts) { for (let artifact of artifacts) { - let fileExt = path.extname(artifact) + let fileExt = path.extname(artifact); switch (fileExt) { case '.deb': - await uploadDebPackage(version, artifact) - break + await uploadDebPackage(version, artifact); + break; case '.rpm': - await uploadRpmPackage(version, artifact) - break + await uploadRpmPackage(version, artifact); + break; default: - continue + continue; } } - async function uploadDebPackage (version, filePath) { + async function uploadDebPackage(version, filePath) { // NOTE: Not sure if distro IDs update over time, might need // to query the following endpoint dynamically to find the right IDs: // @@ -31,10 +31,10 @@ module.exports = async function (packageRepoName, apiToken, version, artifacts) distroId: 35 /* Any .deb distribution */, distroName: 'any', distroVersion: 'any' - }) + }); } - async function uploadRpmPackage (version, filePath) { + async function uploadRpmPackage(version, filePath) { await uploadPackage({ version, filePath, @@ -44,70 +44,91 @@ module.exports = async function (packageRepoName, apiToken, version, artifacts) distroId: 140 /* Enterprise Linux 7 */, distroName: 'el', distroVersion: '7' - }) + }); } - async function uploadPackage (packageDetails) { + async function uploadPackage(packageDetails) { // Infer the package suffix from the version if (/-beta\d+/.test(packageDetails.version)) { - packageDetails.releaseSuffix = '-beta' + packageDetails.releaseSuffix = '-beta'; } else if (/-nightly\d+/.test(packageDetails.version)) { - packageDetails.releaseSuffix = '-nightly' + packageDetails.releaseSuffix = '-nightly'; } - await removePackageIfExists(packageDetails) - await uploadToPackageCloud(packageDetails) + await removePackageIfExists(packageDetails); + await uploadToPackageCloud(packageDetails); } - function uploadToPackageCloud (packageDetails) { + function uploadToPackageCloud(packageDetails) { return new Promise(async (resolve, reject) => { - console.log(`Uploading ${packageDetails.fileName} to https://packagecloud.io/AtomEditor/${packageRepoName}`) + console.log( + `Uploading ${ + packageDetails.fileName + } to https://packagecloud.io/AtomEditor/${packageRepoName}` + ); var uploadOptions = { url: `https://${apiToken}:@packagecloud.io/api/v1/repos/AtomEditor/${packageRepoName}/packages.json`, formData: { 'package[distro_version_id]': packageDetails.distroId, 'package[package_file]': fs.createReadStream(packageDetails.filePath) } - } + }; request.post(uploadOptions, (error, uploadResponse, body) => { if (error || uploadResponse.statusCode !== 201) { - console.log(`Error while uploading '${packageDetails.fileName}' v${packageDetails.version}: ${uploadResponse}`) - reject(uploadResponse) + console.log( + `Error while uploading '${packageDetails.fileName}' v${ + packageDetails.version + }: ${uploadResponse}` + ); + reject(uploadResponse); } else { - console.log(`Successfully uploaded ${packageDetails.fileName}!`) - resolve(uploadResponse) + console.log(`Successfully uploaded ${packageDetails.fileName}!`); + resolve(uploadResponse); } - }) - }) + }); + }); } - async function removePackageIfExists ({ version, type, arch, fileName, distroName, distroVersion, releaseSuffix }) { + async function removePackageIfExists({ + version, + type, + arch, + fileName, + distroName, + distroVersion, + releaseSuffix + }) { // RPM URI paths have an extra '/0.1' thrown in let versionJsonPath = - type === 'rpm' - ? `${version.replace('-', '.')}/0.1` - : version + type === 'rpm' ? `${version.replace('-', '.')}/0.1` : version; try { - const existingPackageDetails = - await request({ - uri: `https://${apiToken}:@packagecloud.io/api/v1/repos/AtomEditor/${packageRepoName}/package/${type}/${distroName}/${distroVersion}/atom${releaseSuffix || ''}/${arch}/${versionJsonPath}.json`, - method: 'get', - json: true - }) + const existingPackageDetails = await request({ + uri: `https://${apiToken}:@packagecloud.io/api/v1/repos/AtomEditor/${packageRepoName}/package/${type}/${distroName}/${distroVersion}/atom${releaseSuffix || + ''}/${arch}/${versionJsonPath}.json`, + method: 'get', + json: true + }); if (existingPackageDetails && existingPackageDetails.destroy_url) { - console.log(`Deleting pre-existing package ${fileName} in ${packageRepoName}`) + console.log( + `Deleting pre-existing package ${fileName} in ${packageRepoName}` + ); await request({ - uri: `https://${apiToken}:@packagecloud.io/${existingPackageDetails.destroy_url}`, + uri: `https://${apiToken}:@packagecloud.io/${ + existingPackageDetails.destroy_url + }`, method: 'delete' - }) + }); } } catch (err) { if (err.statusCode !== 404) { - console.log(`Error while checking for existing '${fileName}' v${version}:\n\n`, err) + console.log( + `Error while checking for existing '${fileName}' v${version}:\n\n`, + err + ); } } } -} +}; diff --git a/script/vsts/lib/upload-to-s3.js b/script/vsts/lib/upload-to-s3.js index fde60210c..36c69e9de 100644 --- a/script/vsts/lib/upload-to-s3.js +++ b/script/vsts/lib/upload-to-s3.js @@ -1,57 +1,70 @@ -'use strict' +'use strict'; -const fs = require('fs') -const path = require('path') -const aws = require('aws-sdk') +const fs = require('fs'); +const path = require('path'); +const aws = require('aws-sdk'); -module.exports = function (s3Key, s3Secret, s3Bucket, directory, assets, acl = 'public-read') { +module.exports = function( + s3Key, + s3Secret, + s3Bucket, + directory, + assets, + acl = 'public-read' +) { const s3 = new aws.S3({ accessKeyId: s3Key, secretAccessKey: s3Secret, params: { Bucket: s3Bucket } - }) + }); - function listExistingAssetsForDirectory (directory) { - return s3.listObjectsV2({ Prefix: directory }).promise().then((res) => { - return res.Contents.map((obj) => { return { Key: obj.Key } }) - }) + function listExistingAssetsForDirectory(directory) { + return s3 + .listObjectsV2({ Prefix: directory }) + .promise() + .then(res => { + return res.Contents.map(obj => { + return { Key: obj.Key }; + }); + }); } - function deleteExistingAssets (existingAssets) { + function deleteExistingAssets(existingAssets) { if (existingAssets.length > 0) { - return s3.deleteObjects({ Delete: { Objects: existingAssets } }).promise() + return s3 + .deleteObjects({ Delete: { Objects: existingAssets } }) + .promise(); } else { - return Promise.resolve(true) + return Promise.resolve(true); } } - function uploadAssets (assets, directory) { - return assets.reduce( - function (promise, asset) { - return promise.then(() => uploadAsset(directory, asset)) - }, Promise.resolve()) + function uploadAssets(assets, directory) { + return assets.reduce(function(promise, asset) { + return promise.then(() => uploadAsset(directory, asset)); + }, Promise.resolve()); } - function uploadAsset (directory, assetPath) { + function uploadAsset(directory, assetPath) { return new Promise((resolve, reject) => { - console.info(`Uploading ${assetPath}`) + console.info(`Uploading ${assetPath}`); const params = { Key: `${directory}${path.basename(assetPath)}`, ACL: acl, Body: fs.createReadStream(assetPath) - } + }; s3.upload(params, error => { if (error) { - reject(error) + reject(error); } else { - resolve() + resolve(); } - }) - }) + }); + }); } return listExistingAssetsForDirectory(directory) .then(deleteExistingAssets) - .then(() => uploadAssets(assets, directory)) -} + .then(() => uploadAssets(assets, directory)); +}; diff --git a/script/vsts/upload-artifacts.js b/script/vsts/upload-artifacts.js index c3b14167c..20175e30c 100644 --- a/script/vsts/upload-artifacts.js +++ b/script/vsts/upload-artifacts.js @@ -1,143 +1,172 @@ -'use strict' +'use strict'; -const fs = require('fs') -const os = require('os') -const path = require('path') -const glob = require('glob') -const spawnSync = require('../lib/spawn-sync') -const publishRelease = require('publish-release') -const releaseNotes = require('./lib/release-notes') -const uploadToS3 = require('./lib/upload-to-s3') -const uploadLinuxPackages = require('./lib/upload-linux-packages') +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const glob = require('glob'); +const spawnSync = require('../lib/spawn-sync'); +const publishRelease = require('publish-release'); +const releaseNotes = require('./lib/release-notes'); +const uploadToS3 = require('./lib/upload-to-s3'); +const uploadLinuxPackages = require('./lib/upload-linux-packages'); -const CONFIG = require('../config') +const CONFIG = require('../config'); -const yargs = require('yargs') +const yargs = require('yargs'); const argv = yargs .usage('Usage: $0 [options]') .help('help') - .describe('assets-path', 'Path to the folder where all release assets are stored') - .describe('s3-path', 'Indicates the S3 path in which the assets should be uploaded') - .describe('create-github-release', 'Creates a GitHub release for this build, draft if release branch or public if Nightly') - .describe('linux-repo-name', 'If specified, uploads Linux packages to the given repo name on packagecloud') - .wrap(yargs.terminalWidth()) - .argv + .describe( + 'assets-path', + 'Path to the folder where all release assets are stored' + ) + .describe( + 's3-path', + 'Indicates the S3 path in which the assets should be uploaded' + ) + .describe( + 'create-github-release', + 'Creates a GitHub release for this build, draft if release branch or public if Nightly' + ) + .describe( + 'linux-repo-name', + 'If specified, uploads Linux packages to the given repo name on packagecloud' + ) + .wrap(yargs.terminalWidth()).argv; -const releaseVersion = CONFIG.computedAppVersion -const isNightlyRelease = CONFIG.channel === 'nightly' -const assetsPath = argv.assetsPath || CONFIG.buildOutputPath -const assetsPattern = '/**/*(*.exe|*.zip|*.nupkg|*.tar.gz|*.rpm|*.deb|RELEASES*|atom-api.json)' -const assets = glob.sync(assetsPattern, { root: assetsPath, nodir: true }) -const bucketPath = argv.s3Path || `releases/v${releaseVersion}/` +const releaseVersion = CONFIG.computedAppVersion; +const isNightlyRelease = CONFIG.channel === 'nightly'; +const assetsPath = argv.assetsPath || CONFIG.buildOutputPath; +const assetsPattern = + '/**/*(*.exe|*.zip|*.nupkg|*.tar.gz|*.rpm|*.deb|RELEASES*|atom-api.json)'; +const assets = glob.sync(assetsPattern, { root: assetsPath, nodir: true }); +const bucketPath = argv.s3Path || `releases/v${releaseVersion}/`; if (!assets || assets.length === 0) { - console.error(`No assets found under specified path: ${assetsPath}`) - process.exit(1) + console.error(`No assets found under specified path: ${assetsPath}`); + process.exit(1); } -async function uploadArtifacts () { - let releaseForVersion = - await releaseNotes.getRelease( - releaseVersion, - process.env.GITHUB_TOKEN) +async function uploadArtifacts() { + let releaseForVersion = await releaseNotes.getRelease( + releaseVersion, + process.env.GITHUB_TOKEN + ); if (releaseForVersion.exists && !releaseForVersion.isDraft) { - console.log(`Published release already exists for ${releaseVersion}, skipping upload.`) - return + console.log( + `Published release already exists for ${releaseVersion}, skipping upload.` + ); + return; } - console.log(`Uploading ${assets.length} release assets for ${releaseVersion} to S3 under '${bucketPath}'`) + console.log( + `Uploading ${ + assets.length + } release assets for ${releaseVersion} to S3 under '${bucketPath}'` + ); await uploadToS3( process.env.ATOM_RELEASES_S3_KEY, process.env.ATOM_RELEASES_S3_SECRET, process.env.ATOM_RELEASES_S3_BUCKET, bucketPath, - assets) + assets + ); if (argv.linuxRepoName) { await uploadLinuxPackages( argv.linuxRepoName, process.env.PACKAGE_CLOUD_API_KEY, releaseVersion, - assets) + assets + ); } else { - console.log('\nNo Linux package repo name specified, skipping Linux package upload.') + console.log( + '\nNo Linux package repo name specified, skipping Linux package upload.' + ); } - const oldReleaseNotes = releaseForVersion.releaseNotes + const oldReleaseNotes = releaseForVersion.releaseNotes; if (oldReleaseNotes) { - const oldReleaseNotesPath = path.resolve(os.tmpdir(), 'OLD_RELEASE_NOTES.md') - console.log(`Saving existing ${releaseVersion} release notes to ${oldReleaseNotesPath}`) - fs.writeFileSync(oldReleaseNotesPath, oldReleaseNotes, 'utf8') + const oldReleaseNotesPath = path.resolve( + os.tmpdir(), + 'OLD_RELEASE_NOTES.md' + ); + console.log( + `Saving existing ${releaseVersion} release notes to ${oldReleaseNotesPath}` + ); + fs.writeFileSync(oldReleaseNotesPath, oldReleaseNotes, 'utf8'); // This line instructs VSTS to upload the file as an artifact - console.log(`##vso[artifact.upload containerfolder=OldReleaseNotes;artifactname=OldReleaseNotes;]${oldReleaseNotesPath}`) + console.log( + `##vso[artifact.upload containerfolder=OldReleaseNotes;artifactname=OldReleaseNotes;]${oldReleaseNotesPath}` + ); } if (argv.createGithubRelease) { - console.log(`\nGenerating new release notes for ${releaseVersion}`) - let newReleaseNotes = '' + console.log(`\nGenerating new release notes for ${releaseVersion}`); + let newReleaseNotes = ''; if (isNightlyRelease) { - newReleaseNotes = - await releaseNotes.generateForNightly( - releaseVersion, - process.env.GITHUB_TOKEN, - oldReleaseNotes) + newReleaseNotes = await releaseNotes.generateForNightly( + releaseVersion, + process.env.GITHUB_TOKEN, + oldReleaseNotes + ); } else { - newReleaseNotes = - await releaseNotes.generateForVersion( - releaseVersion, - process.env.GITHUB_TOKEN, - oldReleaseNotes) + newReleaseNotes = await releaseNotes.generateForVersion( + releaseVersion, + process.env.GITHUB_TOKEN, + oldReleaseNotes + ); } - console.log(`New release notes:\n\n${newReleaseNotes}`) + console.log(`New release notes:\n\n${newReleaseNotes}`); - const releaseSha = - !isNightlyRelease - ? spawnSync('git', ['rev-parse', 'HEAD']).stdout.toString().trimEnd() - : 'master' // Nightly tags are created in atom/atom-nightly-releases so the SHA is irrelevant + const releaseSha = !isNightlyRelease + ? spawnSync('git', ['rev-parse', 'HEAD']) + .stdout.toString() + .trimEnd() + : 'master'; // Nightly tags are created in atom/atom-nightly-releases so the SHA is irrelevant - console.log(`Creating GitHub release v${releaseVersion}`) - const release = - await publishReleaseAsync({ - token: process.env.GITHUB_TOKEN, - owner: 'atom', - repo: !isNightlyRelease ? 'atom' : 'atom-nightly-releases', - name: CONFIG.computedAppVersion, - notes: newReleaseNotes, - target_commitish: releaseSha, - tag: `v${CONFIG.computedAppVersion}`, - draft: !isNightlyRelease, - prerelease: CONFIG.channel !== 'stable', - editRelease: true, - reuseRelease: true, - skipIfPublished: true, - assets - }) + console.log(`Creating GitHub release v${releaseVersion}`); + const release = await publishReleaseAsync({ + token: process.env.GITHUB_TOKEN, + owner: 'atom', + repo: !isNightlyRelease ? 'atom' : 'atom-nightly-releases', + name: CONFIG.computedAppVersion, + notes: newReleaseNotes, + target_commitish: releaseSha, + tag: `v${CONFIG.computedAppVersion}`, + draft: !isNightlyRelease, + prerelease: CONFIG.channel !== 'stable', + editRelease: true, + reuseRelease: true, + skipIfPublished: true, + assets + }); - console.log('Release published successfully: ', release.html_url) + console.log('Release published successfully: ', release.html_url); } else { - console.log('Skipping GitHub release creation') + console.log('Skipping GitHub release creation'); } } -async function publishReleaseAsync (options) { +async function publishReleaseAsync(options) { return new Promise((resolve, reject) => { publishRelease(options, (err, release) => { if (err) { - reject(err) + reject(err); } else { - resolve(release) + resolve(release); } - }) - }) + }); + }); } // Wrap the call the async function and catch errors from its promise because // Node.js doesn't yet allow use of await at the script scope uploadArtifacts().catch(err => { - console.error('An error occurred while uploading the release:\n\n', err) - process.exit(1) -}) + console.error('An error occurred while uploading the release:\n\n', err); + process.exit(1); +}); diff --git a/script/vsts/upload-crash-reports.js b/script/vsts/upload-crash-reports.js index 0861afa1c..268fbf588 100644 --- a/script/vsts/upload-crash-reports.js +++ b/script/vsts/upload-crash-reports.js @@ -1,24 +1,33 @@ -'use strict' +'use strict'; -const glob = require('glob') -const uploadToS3 = require('./lib/upload-to-s3') +const glob = require('glob'); +const uploadToS3 = require('./lib/upload-to-s3'); -const yargs = require('yargs') +const yargs = require('yargs'); const argv = yargs .usage('Usage: $0 [options]') .help('help') - .describe('crash-report-path', 'The local path of a directory containing crash reports to upload') - .describe('s3-path', 'Indicates the S3 path in which the crash reports should be uploaded') - .wrap(yargs.terminalWidth()) - .argv + .describe( + 'crash-report-path', + 'The local path of a directory containing crash reports to upload' + ) + .describe( + 's3-path', + 'Indicates the S3 path in which the crash reports should be uploaded' + ) + .wrap(yargs.terminalWidth()).argv; -async function uploadCrashReports () { - const crashesPath = argv.crashReportPath - const crashes = glob.sync('/*.dmp', { root: crashesPath }) - const bucketPath = argv.s3Path +async function uploadCrashReports() { + const crashesPath = argv.crashReportPath; + const crashes = glob.sync('/*.dmp', { root: crashesPath }); + const bucketPath = argv.s3Path; if (crashes && crashes.length > 0) { - console.log(`Uploading ${crashes.length} private crash reports to S3 under '${bucketPath}'`) + console.log( + `Uploading ${ + crashes.length + } private crash reports to S3 under '${bucketPath}'` + ); await uploadToS3( process.env.ATOM_RELEASES_S3_KEY, @@ -27,13 +36,13 @@ async function uploadCrashReports () { bucketPath, crashes, 'private' - ) + ); } } // Wrap the call the async function and catch errors from its promise because // Node.js doesn't yet allow use of await at the script scope uploadCrashReports().catch(err => { - console.error('An error occurred while uploading crash reports:\n\n', err) - process.exit(1) -}) + console.error('An error occurred while uploading crash reports:\n\n', err); + process.exit(1); +}); diff --git a/script/vsts/windows-run.js b/script/vsts/windows-run.js index 926f0e607..2ac8760a3 100644 --- a/script/vsts/windows-run.js +++ b/script/vsts/windows-run.js @@ -1,51 +1,54 @@ // NOTE: This script is only used as part of the Windows build on VSTS, // see script/vsts/platforms/windows.yml for its usage -const fs = require('fs') -const path = require('path') -const download = require('download') -const childProcess = require('child_process') +const fs = require('fs'); +const path = require('path'); +const download = require('download'); +const childProcess = require('child_process'); -const nodeVersion = '8.9.3' -const nodeFileName = `node-v${nodeVersion}-win-x86` -const extractedNodePath = `c:\\tmp\\${nodeFileName}` +const nodeVersion = '8.9.3'; +const nodeFileName = `node-v${nodeVersion}-win-x86`; +const extractedNodePath = `c:\\tmp\\${nodeFileName}`; -async function downloadX86Node () { +async function downloadX86Node() { if (!fs.existsSync(extractedNodePath)) { - await download(`https://nodejs.org/download/release/v${nodeVersion}/${nodeFileName}.zip`, 'c:\\tmp', { extract: true }) + await download( + `https://nodejs.org/download/release/v${nodeVersion}/${nodeFileName}.zip`, + 'c:\\tmp', + { extract: true } + ); } } -async function runScriptForBuildArch () { +async function runScriptForBuildArch() { if (process.argv.length <= 2) { - console.error('A target script must be specified') - process.exit(1) + console.error('A target script must be specified'); + process.exit(1); } - let exitCode = 0 + let exitCode = 0; if (process.env.BUILD_ARCH === 'x86') { - await downloadX86Node() + await downloadX86Node(); // Write out a launcher script that will launch the requested script // using the 32-bit cmd.exe and 32-bit Node.js - const runScript = `@echo off\r\nCALL ${extractedNodePath}\\nodevars.bat\r\nCALL ${path.resolve(process.argv[2])} ${process.argv.splice(3).join(' ')}` - const runScriptPath = 'c:\\tmp\\run.cmd' - fs.writeFileSync(runScriptPath, runScript) - exitCode = - childProcess.spawnSync( - 'C:\\Windows\\SysWOW64\\cmd.exe', - ['/C', runScriptPath], - { env: process.env, stdio: 'inherit' } - ).status + const runScript = `@echo off\r\nCALL ${extractedNodePath}\\nodevars.bat\r\nCALL ${path.resolve( + process.argv[2] + )} ${process.argv.splice(3).join(' ')}`; + const runScriptPath = 'c:\\tmp\\run.cmd'; + fs.writeFileSync(runScriptPath, runScript); + exitCode = childProcess.spawnSync( + 'C:\\Windows\\SysWOW64\\cmd.exe', + ['/C', runScriptPath], + { env: process.env, stdio: 'inherit' } + ).status; } else { - exitCode = - childProcess.spawnSync( - process.argv[2], - process.argv.splice(3), - { env: process.env, stdio: 'inherit' } - ).status + exitCode = childProcess.spawnSync(process.argv[2], process.argv.splice(3), { + env: process.env, + stdio: 'inherit' + }).status; } - process.exit(exitCode) + process.exit(exitCode); } -runScriptForBuildArch() +runScriptForBuildArch(); diff --git a/spec/application-delegate-spec.js b/spec/application-delegate-spec.js index 044e257b5..3ebd5149b 100644 --- a/spec/application-delegate-spec.js +++ b/spec/application-delegate-spec.js @@ -1,18 +1,18 @@ /** @babel */ -import ApplicationDelegate from '../src/application-delegate' +import ApplicationDelegate from '../src/application-delegate'; -describe('ApplicationDelegate', function () { - describe('set/getTemporaryWindowState', function () { - it('can serialize object trees containing redundant child object references', async function () { - const applicationDelegate = new ApplicationDelegate() - const childObject = { c: 1 } - const sentObject = { a: childObject, b: childObject } +describe('ApplicationDelegate', function() { + describe('set/getTemporaryWindowState', function() { + it('can serialize object trees containing redundant child object references', async function() { + const applicationDelegate = new ApplicationDelegate(); + const childObject = { c: 1 }; + const sentObject = { a: childObject, b: childObject }; - await applicationDelegate.setTemporaryWindowState(sentObject) - const receivedObject = await applicationDelegate.getTemporaryWindowState() + await applicationDelegate.setTemporaryWindowState(sentObject); + const receivedObject = await applicationDelegate.getTemporaryWindowState(); - expect(receivedObject).toEqual(sentObject) - }) - }) -}) + expect(receivedObject).toEqual(sentObject); + }); + }); +}); diff --git a/spec/async-spec-helpers.js b/spec/async-spec-helpers.js index 840a04b2a..033b4b98f 100644 --- a/spec/async-spec-helpers.js +++ b/spec/async-spec-helpers.js @@ -1,40 +1,40 @@ -async function conditionPromise ( +async function conditionPromise( condition, description = 'anonymous condition' ) { - const startTime = Date.now() + const startTime = Date.now(); while (true) { - await timeoutPromise(100) + await timeoutPromise(100); if (await condition()) { - return + return; } if (Date.now() - startTime > 5000) { - throw new Error('Timed out waiting on ' + description) + throw new Error('Timed out waiting on ' + description); } } } -function timeoutPromise (timeout) { +function timeoutPromise(timeout) { return new Promise(resolve => { - global.setTimeout(resolve, timeout) - }) + global.setTimeout(resolve, timeout); + }); } -function emitterEventPromise (emitter, event, timeout = 15000) { +function emitterEventPromise(emitter, event, timeout = 15000) { return new Promise((resolve, reject) => { const timeoutHandle = setTimeout(() => { - reject(new Error(`Timed out waiting for '${event}' event`)) - }, timeout) + reject(new Error(`Timed out waiting for '${event}' event`)); + }, timeout); emitter.once(event, () => { - clearTimeout(timeoutHandle) - resolve() - }) - }) + clearTimeout(timeoutHandle); + resolve(); + }); + }); } -exports.conditionPromise = conditionPromise -exports.emitterEventPromise = emitterEventPromise -exports.timeoutPromise = timeoutPromise +exports.conditionPromise = conditionPromise; +exports.emitterEventPromise = emitterEventPromise; +exports.timeoutPromise = timeoutPromise; diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index 5ca2a8761..18e21ca94 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -1,136 +1,136 @@ -const { conditionPromise } = require('./async-spec-helpers') -const fs = require('fs') -const path = require('path') -const temp = require('temp').track() -const AtomEnvironment = require('../src/atom-environment') +const { conditionPromise } = require('./async-spec-helpers'); +const fs = require('fs'); +const path = require('path'); +const temp = require('temp').track(); +const AtomEnvironment = require('../src/atom-environment'); describe('AtomEnvironment', () => { afterEach(() => { try { - temp.cleanupSync() + temp.cleanupSync(); } catch (error) {} - }) + }); describe('window sizing methods', () => { describe('::getPosition and ::setPosition', () => { - let originalPosition = null - beforeEach(() => (originalPosition = atom.getPosition())) + let originalPosition = null; + beforeEach(() => (originalPosition = atom.getPosition())); - afterEach(() => atom.setPosition(originalPosition.x, originalPosition.y)) + afterEach(() => atom.setPosition(originalPosition.x, originalPosition.y)); it('sets the position of the window, and can retrieve the position just set', () => { - atom.setPosition(22, 45) - expect(atom.getPosition()).toEqual({ x: 22, y: 45 }) - }) - }) + atom.setPosition(22, 45); + expect(atom.getPosition()).toEqual({ x: 22, y: 45 }); + }); + }); describe('::getSize and ::setSize', () => { - let originalSize = null - beforeEach(() => (originalSize = atom.getSize())) - afterEach(() => atom.setSize(originalSize.width, originalSize.height)) + let originalSize = null; + beforeEach(() => (originalSize = atom.getSize())); + afterEach(() => atom.setSize(originalSize.width, originalSize.height)); it('sets the size of the window, and can retrieve the size just set', async () => { - const newWidth = originalSize.width - 12 - const newHeight = originalSize.height - 23 - await atom.setSize(newWidth, newHeight) - expect(atom.getSize()).toEqual({ width: newWidth, height: newHeight }) - }) - }) - }) + const newWidth = originalSize.width - 12; + const newHeight = originalSize.height - 23; + await atom.setSize(newWidth, newHeight); + expect(atom.getSize()).toEqual({ width: newWidth, height: newHeight }); + }); + }); + }); describe('.isReleasedVersion()', () => { it('returns false if the version is a SHA and true otherwise', () => { - let version = '0.1.0' - spyOn(atom, 'getVersion').andCallFake(() => version) - expect(atom.isReleasedVersion()).toBe(true) - version = '36b5518' - expect(atom.isReleasedVersion()).toBe(false) - }) - }) + let version = '0.1.0'; + spyOn(atom, 'getVersion').andCallFake(() => version); + expect(atom.isReleasedVersion()).toBe(true); + version = '36b5518'; + expect(atom.isReleasedVersion()).toBe(false); + }); + }); describe('loading default config', () => { it('loads the default core config schema', () => { - expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe(true) - expect(atom.config.get('core.followSymlinks')).toBe(true) - expect(atom.config.get('editor.showInvisibles')).toBe(false) - }) - }) + expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe(true); + expect(atom.config.get('core.followSymlinks')).toBe(true); + expect(atom.config.get('editor.showInvisibles')).toBe(false); + }); + }); describe('window onerror handler', () => { - let devToolsPromise = null + let devToolsPromise = null; beforeEach(() => { - devToolsPromise = Promise.resolve() - spyOn(atom, 'openDevTools').andReturn(devToolsPromise) - spyOn(atom, 'executeJavaScriptInDevTools') - }) + devToolsPromise = Promise.resolve(); + spyOn(atom, 'openDevTools').andReturn(devToolsPromise); + spyOn(atom, 'executeJavaScriptInDevTools'); + }); it('will open the dev tools when an error is triggered', async () => { try { - a + 1 // eslint-disable-line no-undef, no-unused-expressions + a + 1; // eslint-disable-line no-undef, no-unused-expressions } catch (e) { - window.onerror(e.toString(), 'abc', 2, 3, e) + window.onerror(e.toString(), 'abc', 2, 3, e); } - await devToolsPromise - expect(atom.openDevTools).toHaveBeenCalled() - expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled() - }) + await devToolsPromise; + expect(atom.openDevTools).toHaveBeenCalled(); + expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled(); + }); describe('::onWillThrowError', () => { - let willThrowSpy = null + let willThrowSpy = null; beforeEach(() => { - willThrowSpy = jasmine.createSpy() - }) + willThrowSpy = jasmine.createSpy(); + }); it('is called when there is an error', () => { - let error = null - atom.onWillThrowError(willThrowSpy) + let error = null; + atom.onWillThrowError(willThrowSpy); try { - a + 1 // eslint-disable-line no-undef, no-unused-expressions + a + 1; // eslint-disable-line no-undef, no-unused-expressions } catch (e) { - error = e - window.onerror(e.toString(), 'abc', 2, 3, e) + error = e; + window.onerror(e.toString(), 'abc', 2, 3, e); } - delete willThrowSpy.mostRecentCall.args[0].preventDefault + delete willThrowSpy.mostRecentCall.args[0].preventDefault; expect(willThrowSpy).toHaveBeenCalledWith({ message: error.toString(), url: 'abc', line: 2, column: 3, originalError: error - }) - }) + }); + }); it('will not show the devtools when preventDefault() is called', () => { - willThrowSpy.andCallFake(errorObject => errorObject.preventDefault()) - atom.onWillThrowError(willThrowSpy) + willThrowSpy.andCallFake(errorObject => errorObject.preventDefault()); + atom.onWillThrowError(willThrowSpy); try { - a + 1 // eslint-disable-line no-undef, no-unused-expressions + a + 1; // eslint-disable-line no-undef, no-unused-expressions } catch (e) { - window.onerror(e.toString(), 'abc', 2, 3, e) + window.onerror(e.toString(), 'abc', 2, 3, e); } - expect(willThrowSpy).toHaveBeenCalled() - expect(atom.openDevTools).not.toHaveBeenCalled() - expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled() - }) - }) + expect(willThrowSpy).toHaveBeenCalled(); + expect(atom.openDevTools).not.toHaveBeenCalled(); + expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled(); + }); + }); describe('::onDidThrowError', () => { - let didThrowSpy = null - beforeEach(() => (didThrowSpy = jasmine.createSpy())) + let didThrowSpy = null; + beforeEach(() => (didThrowSpy = jasmine.createSpy())); it('is called when there is an error', () => { - let error = null - atom.onDidThrowError(didThrowSpy) + let error = null; + atom.onDidThrowError(didThrowSpy); try { - a + 1 // eslint-disable-line no-undef, no-unused-expressions + a + 1; // eslint-disable-line no-undef, no-unused-expressions } catch (e) { - error = e - window.onerror(e.toString(), 'abc', 2, 3, e) + error = e; + window.onerror(e.toString(), 'abc', 2, 3, e); } expect(didThrowSpy).toHaveBeenCalledWith({ message: error.toString(), @@ -138,180 +138,180 @@ describe('AtomEnvironment', () => { line: 2, column: 3, originalError: error - }) - }) - }) - }) + }); + }); + }); + }); describe('.assert(condition, message, callback)', () => { - let errors = null + let errors = null; beforeEach(() => { - errors = [] - spyOn(atom, 'isReleasedVersion').andReturn(true) - atom.onDidFailAssertion(error => errors.push(error)) - }) + errors = []; + spyOn(atom, 'isReleasedVersion').andReturn(true); + atom.onDidFailAssertion(error => errors.push(error)); + }); describe('if the condition is false', () => { it('notifies onDidFailAssertion handlers with an error object based on the call site of the assertion', () => { - const result = atom.assert(false, 'a == b') - expect(result).toBe(false) - expect(errors.length).toBe(1) - expect(errors[0].message).toBe('Assertion failed: a == b') - expect(errors[0].stack).toContain('atom-environment-spec') - }) + const result = atom.assert(false, 'a == b'); + expect(result).toBe(false); + expect(errors.length).toBe(1); + expect(errors[0].message).toBe('Assertion failed: a == b'); + expect(errors[0].stack).toContain('atom-environment-spec'); + }); describe('if passed a callback function', () => { it("calls the callback with the assertion failure's error object", () => { - let error = null - atom.assert(false, 'a == b', e => (error = e)) - expect(error).toBe(errors[0]) - }) - }) + let error = null; + atom.assert(false, 'a == b', e => (error = e)); + expect(error).toBe(errors[0]); + }); + }); describe('if passed metadata', () => { it("assigns the metadata on the assertion failure's error object", () => { - atom.assert(false, 'a == b', { foo: 'bar' }) - expect(errors[0].metadata).toEqual({ foo: 'bar' }) - }) - }) + 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) + atom.isReleasedVersion.andReturn(false); expect(() => atom.assert(false, 'testing')).toThrow( 'Assertion failed: testing' - ) - }) - }) - }) + ); + }); + }); + }); describe('if the condition is true', () => { it('does nothing', () => { - const result = atom.assert(true, 'a == b') - expect(result).toBe(true) - expect(errors).toEqual([]) - }) - }) - }) + const result = atom.assert(true, 'a == b'); + expect(result).toBe(true); + expect(errors).toEqual([]); + }); + }); + }); describe('saving and loading', () => { - beforeEach(() => (atom.enablePersistence = true)) + beforeEach(() => (atom.enablePersistence = true)); - afterEach(() => (atom.enablePersistence = false)) + afterEach(() => (atom.enablePersistence = false)); it('selects the state based on the current project paths', async () => { - jasmine.useRealClock() + jasmine.useRealClock(); - const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')] + const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')]; const loadSettings = Object.assign(atom.getLoadSettings(), { initialProjectRoots: [dir1], windowState: null - }) + }); - spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings) - spyOn(atom, 'serialize').andReturn({ stuff: 'cool' }) + spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings); + spyOn(atom, 'serialize').andReturn({ stuff: 'cool' }); - atom.project.setPaths([dir1, dir2]) + atom.project.setPaths([dir1, dir2]); // State persistence will fail if other Atom instances are running - expect(await atom.stateStore.connect()).toBe(true) + expect(await atom.stateStore.connect()).toBe(true); - await atom.saveState() - expect(await atom.loadState()).toBeFalsy() + await atom.saveState(); + expect(await atom.loadState()).toBeFalsy(); - loadSettings.initialProjectRoots = [dir2, dir1] - expect(await atom.loadState()).toEqual({ stuff: 'cool' }) - }) + loadSettings.initialProjectRoots = [dir2, dir1]; + expect(await atom.loadState()).toEqual({ stuff: 'cool' }); + }); it('saves state when the CPU is idle after a keydown or mousedown event', () => { const atomEnv = new AtomEnvironment({ applicationDelegate: global.atom.applicationDelegate - }) - const idleCallbacks = [] + }); + const idleCallbacks = []; atomEnv.initialize({ window: { - requestIdleCallback (callback) { - idleCallbacks.push(callback) + requestIdleCallback(callback) { + idleCallbacks.push(callback); }, - addEventListener () {}, - removeEventListener () {} + addEventListener() {}, + removeEventListener() {} }, document: document.implementation.createHTMLDocument() - }) + }); - spyOn(atomEnv, 'saveState') + spyOn(atomEnv, 'saveState'); - const keydown = new KeyboardEvent('keydown') - atomEnv.document.dispatchEvent(keydown) - advanceClock(atomEnv.saveStateDebounceInterval) - idleCallbacks.shift()() - expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false }) - expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true }) + const keydown = new KeyboardEvent('keydown'); + atomEnv.document.dispatchEvent(keydown); + advanceClock(atomEnv.saveStateDebounceInterval); + idleCallbacks.shift()(); + expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false }); + expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true }); - atomEnv.saveState.reset() - const mousedown = new MouseEvent('mousedown') - atomEnv.document.dispatchEvent(mousedown) - advanceClock(atomEnv.saveStateDebounceInterval) - idleCallbacks.shift()() - expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false }) - expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true }) + atomEnv.saveState.reset(); + const mousedown = new MouseEvent('mousedown'); + atomEnv.document.dispatchEvent(mousedown); + advanceClock(atomEnv.saveStateDebounceInterval); + idleCallbacks.shift()(); + expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false }); + expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true }); - atomEnv.destroy() - }) + atomEnv.destroy(); + }); it('ignores mousedown/keydown events happening after calling prepareToUnloadEditorWindow', async () => { const atomEnv = new AtomEnvironment({ applicationDelegate: global.atom.applicationDelegate - }) - const idleCallbacks = [] + }); + const idleCallbacks = []; atomEnv.initialize({ window: { - requestIdleCallback (callback) { - idleCallbacks.push(callback) + requestIdleCallback(callback) { + idleCallbacks.push(callback); }, - addEventListener () {}, - removeEventListener () {} + addEventListener() {}, + removeEventListener() {} }, document: document.implementation.createHTMLDocument() - }) + }); - spyOn(atomEnv, 'saveState') + spyOn(atomEnv, 'saveState'); - let mousedown = new MouseEvent('mousedown') - atomEnv.document.dispatchEvent(mousedown) - expect(atomEnv.saveState).not.toHaveBeenCalled() - await atomEnv.prepareToUnloadEditorWindow() - expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: true }) + let mousedown = new MouseEvent('mousedown'); + atomEnv.document.dispatchEvent(mousedown); + expect(atomEnv.saveState).not.toHaveBeenCalled(); + await atomEnv.prepareToUnloadEditorWindow(); + expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: true }); - advanceClock(atomEnv.saveStateDebounceInterval) - idleCallbacks.shift()() - expect(atomEnv.saveState.calls.length).toBe(1) + advanceClock(atomEnv.saveStateDebounceInterval); + idleCallbacks.shift()(); + expect(atomEnv.saveState.calls.length).toBe(1); - mousedown = new MouseEvent('mousedown') - atomEnv.document.dispatchEvent(mousedown) - advanceClock(atomEnv.saveStateDebounceInterval) - idleCallbacks.shift()() - expect(atomEnv.saveState.calls.length).toBe(1) + mousedown = new MouseEvent('mousedown'); + atomEnv.document.dispatchEvent(mousedown); + advanceClock(atomEnv.saveStateDebounceInterval); + idleCallbacks.shift()(); + expect(atomEnv.saveState.calls.length).toBe(1); - atomEnv.destroy() - }) + atomEnv.destroy(); + }); it('serializes the project state with all the options supplied in saveState', async () => { - spyOn(atom.project, 'serialize').andReturn({ foo: 42 }) + spyOn(atom.project, 'serialize').andReturn({ foo: 42 }); - await atom.saveState({ anyOption: 'any option' }) - expect(atom.project.serialize.calls.length).toBe(1) + await atom.saveState({ anyOption: 'any option' }); + expect(atom.project.serialize.calls.length).toBe(1); expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({ anyOption: 'any option' - }) - }) + }); + }); it('serializes the text editor registry', async () => { - await atom.packages.activatePackage('language-text') - const editor = await atom.workspace.open('sample.js') - expect(atom.grammars.assignLanguageMode(editor, 'text.plain')).toBe(true) + await atom.packages.activatePackage('language-text'); + const editor = await atom.workspace.open('sample.js'); + expect(atom.grammars.assignLanguageMode(editor, 'text.plain')).toBe(true); const atom2 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate, @@ -320,580 +320,596 @@ describe('AtomEnvironment', () => { body: document.createElement('div'), head: document.createElement('div') }) - }) - atom2.initialize({ document, window }) + }); + atom2.initialize({ document, window }); - await atom2.deserialize(atom.serialize()) - await atom2.packages.activatePackage('language-text') - const editor2 = atom2.workspace.getActiveTextEditor() + await atom2.deserialize(atom.serialize()); + await atom2.packages.activatePackage('language-text'); + const editor2 = atom2.workspace.getActiveTextEditor(); expect( editor2 .getBuffer() .getLanguageMode() .getLanguageId() - ).toBe('text.plain') - atom2.destroy() - }) + ).toBe('text.plain'); + atom2.destroy(); + }); describe('deserialization failures', () => { it('propagates unrecognized project state restoration failures', async () => { - let err + let err; spyOn(atom.project, 'deserialize').andCallFake(() => { - err = new Error('deserialization failure') - return Promise.reject(err) - }) - spyOn(atom.notifications, 'addError') + err = new Error('deserialization failure'); + return Promise.reject(err); + }); + spyOn(atom.notifications, 'addError'); - await atom.deserialize({ project: 'should work' }) + await atom.deserialize({ project: 'should work' }); expect(atom.notifications.addError).toHaveBeenCalledWith( 'Unable to deserialize project', { description: 'deserialization failure', stack: err.stack } - ) - }) + ); + }); it('disregards missing project folder errors', async () => { spyOn(atom.project, 'deserialize').andCallFake(() => { - const err = new Error('deserialization failure') - err.missingProjectPaths = ['nah'] - return Promise.reject(err) - }) - spyOn(atom.notifications, 'addError') + const err = new Error('deserialization failure'); + err.missingProjectPaths = ['nah']; + return Promise.reject(err); + }); + spyOn(atom.notifications, 'addError'); - await atom.deserialize({ project: 'should work' }) - expect(atom.notifications.addError).not.toHaveBeenCalled() - }) - }) - }) + await atom.deserialize({ project: 'should work' }); + expect(atom.notifications.addError).not.toHaveBeenCalled(); + }); + }); + }); describe('openInitialEmptyEditorIfNecessary', () => { describe('when there are no paths set', () => { - beforeEach(() => spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: false })) + beforeEach(() => + spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: false }) + ); it('opens an empty buffer', () => { - spyOn(atom.workspace, 'open') - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).toHaveBeenCalledWith(null, {pending: true}) - }) + spyOn(atom.workspace, 'open'); + atom.openInitialEmptyEditorIfNecessary(); + expect(atom.workspace.open).toHaveBeenCalledWith(null, { + pending: true + }); + }); it('does not open an empty buffer when a buffer is already open', async () => { - await atom.workspace.open() - spyOn(atom.workspace, 'open') - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).not.toHaveBeenCalled() - }) + await atom.workspace.open(); + spyOn(atom.workspace, 'open'); + atom.openInitialEmptyEditorIfNecessary(); + expect(atom.workspace.open).not.toHaveBeenCalled(); + }); it('does not open an empty buffer when core.openEmptyEditorOnStart is false', async () => { - atom.config.set('core.openEmptyEditorOnStart', false) - spyOn(atom.workspace, 'open') - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).not.toHaveBeenCalled() - }) - }) + atom.config.set('core.openEmptyEditorOnStart', false); + spyOn(atom.workspace, 'open'); + atom.openInitialEmptyEditorIfNecessary(); + expect(atom.workspace.open).not.toHaveBeenCalled(); + }); + }); describe('when the project has a path', () => { beforeEach(() => { - spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: true }) - spyOn(atom.workspace, 'open') - }) + spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: true }); + spyOn(atom.workspace, 'open'); + }); it('does not open an empty buffer', () => { - atom.openInitialEmptyEditorIfNecessary() - expect(atom.workspace.open).not.toHaveBeenCalled() - }) - }) - }) + atom.openInitialEmptyEditorIfNecessary(); + expect(atom.workspace.open).not.toHaveBeenCalled(); + }); + }); + }); describe('adding a project folder', () => { it('does nothing if the user dismisses the file picker', () => { - const projectRoots = atom.project.getPaths() - spyOn(atom, 'pickFolder').andCallFake(callback => callback(null)) - atom.addProjectFolder() - expect(atom.project.getPaths()).toEqual(projectRoots) - }) + const projectRoots = atom.project.getPaths(); + spyOn(atom, 'pickFolder').andCallFake(callback => callback(null)); + atom.addProjectFolder(); + expect(atom.project.getPaths()).toEqual(projectRoots); + }); describe('when there is no saved state for the added folders', () => { beforeEach(() => { - spyOn(atom, 'loadState').andReturn(Promise.resolve(null)) - spyOn(atom, 'attemptRestoreProjectStateForPaths') - }) + spyOn(atom, 'loadState').andReturn(Promise.resolve(null)); + spyOn(atom, 'attemptRestoreProjectStateForPaths'); + }); it('adds the selected folder to the project', async () => { - atom.project.setPaths([]) - const tempDirectory = temp.mkdirSync('a-new-directory') + atom.project.setPaths([]); + const tempDirectory = temp.mkdirSync('a-new-directory'); spyOn(atom, 'pickFolder').andCallFake(callback => callback([tempDirectory]) - ) - await atom.addProjectFolder() - expect(atom.project.getPaths()).toEqual([tempDirectory]) - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - }) - }) + ); + await atom.addProjectFolder(); + expect(atom.project.getPaths()).toEqual([tempDirectory]); + expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled(); + }); + }); describe('when there is saved state for the relevant directories', () => { - const state = Symbol('savedState') + const state = Symbol('savedState'); beforeEach(() => { - spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')) + spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')); spyOn(atom, 'loadState').andCallFake(async key => key === __dirname ? state : null - ) - spyOn(atom, 'attemptRestoreProjectStateForPaths') - spyOn(atom, 'pickFolder').andCallFake(callback => callback([__dirname])) - atom.project.setPaths([]) - }) + ); + spyOn(atom, 'attemptRestoreProjectStateForPaths'); + spyOn(atom, 'pickFolder').andCallFake(callback => + callback([__dirname]) + ); + atom.project.setPaths([]); + }); describe('when there are no project folders', () => { it('attempts to restore the project state', async () => { - await atom.addProjectFolder() + await atom.addProjectFolder(); expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith( state, [__dirname] - ) - expect(atom.project.getPaths()).toEqual([]) - }) - }) + ); + expect(atom.project.getPaths()).toEqual([]); + }); + }); describe('when there are already project folders', () => { - const openedPath = path.join(__dirname, 'fixtures') + const openedPath = path.join(__dirname, 'fixtures'); - beforeEach(() => atom.project.setPaths([openedPath])) + beforeEach(() => atom.project.setPaths([openedPath])); it('does not attempt to restore the project state, instead adding the project paths', async () => { - await atom.addProjectFolder() - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([openedPath, __dirname]) - }) - }) - }) - }) + await atom.addProjectFolder(); + expect( + atom.attemptRestoreProjectStateForPaths + ).not.toHaveBeenCalled(); + expect(atom.project.getPaths()).toEqual([openedPath, __dirname]); + }); + }); + }); + }); describe('attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)', () => { describe('when the window is clean (empty or has only unnamed, unmodified buffers)', () => { beforeEach(async () => { // Unnamed, unmodified buffer doesn't count toward "clean"-ness - await atom.workspace.open() - }) + await atom.workspace.open(); + }); it('automatically restores the saved state into the current environment', async () => { - const projectPath = temp.mkdirSync() - const filePath1 = path.join(projectPath, 'file-1') - const filePath2 = path.join(projectPath, 'file-2') - const filePath3 = path.join(projectPath, 'file-3') - fs.writeFileSync(filePath1, 'abc') - fs.writeFileSync(filePath2, 'def') - fs.writeFileSync(filePath3, 'ghi') + const projectPath = temp.mkdirSync(); + const filePath1 = path.join(projectPath, 'file-1'); + const filePath2 = path.join(projectPath, 'file-2'); + const filePath3 = path.join(projectPath, 'file-3'); + fs.writeFileSync(filePath1, 'abc'); + fs.writeFileSync(filePath2, 'def'); + fs.writeFileSync(filePath3, 'ghi'); const env1 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate - }) - env1.project.setPaths([projectPath]) - await env1.workspace.open(filePath1) - await env1.workspace.open(filePath2) - await env1.workspace.open(filePath3) - const env1State = env1.serialize() - env1.destroy() + }); + env1.project.setPaths([projectPath]); + await env1.workspace.open(filePath1); + await env1.workspace.open(filePath2); + await env1.workspace.open(filePath3); + const env1State = env1.serialize(); + env1.destroy(); const env2 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate - }) + }); await env2.attemptRestoreProjectStateForPaths( env1State, [projectPath], [filePath2] - ) - const restoredURIs = env2.workspace.getPaneItems().map(p => p.getURI()) - expect(restoredURIs).toEqual([filePath1, filePath2, filePath3]) - env2.destroy() - }) + ); + const restoredURIs = env2.workspace.getPaneItems().map(p => p.getURI()); + expect(restoredURIs).toEqual([filePath1, filePath2, filePath3]); + env2.destroy(); + }); describe('when a dock has a non-text editor', () => { it("doesn't prompt the user to restore state", () => { - const dock = atom.workspace.getLeftDock() + const dock = atom.workspace.getLeftDock(); dock.getActivePane().addItem({ - getTitle () { - return 'title' + getTitle() { + return 'title'; }, element: document.createElement('div') - }) - const state = {} - spyOn(atom, 'confirm') + }); + const state = {}; + spyOn(atom, 'confirm'); atom.attemptRestoreProjectStateForPaths( state, [__dirname], [__filename] - ) - expect(atom.confirm).not.toHaveBeenCalled() - }) - }) - }) + ); + expect(atom.confirm).not.toHaveBeenCalled(); + }); + }); + }); describe('when the window is dirty', () => { - let editor + let editor; beforeEach(async () => { - editor = await atom.workspace.open() - editor.setText('new editor') - }) + editor = await atom.workspace.open(); + editor.setText('new editor'); + }); describe('when a dock has a modified editor', () => { it('prompts the user to restore the state', () => { - const dock = atom.workspace.getLeftDock() - dock.getActivePane().addItem(editor) - spyOn(atom, 'confirm').andReturn(1) - spyOn(atom.project, 'addPath') - spyOn(atom.workspace, 'open') - const state = Symbol('state') + const dock = atom.workspace.getLeftDock(); + dock.getActivePane().addItem(editor); + spyOn(atom, 'confirm').andReturn(1); + spyOn(atom.project, 'addPath'); + spyOn(atom.workspace, 'open'); + const state = Symbol('state'); atom.attemptRestoreProjectStateForPaths( state, [__dirname], [__filename] - ) - expect(atom.confirm).toHaveBeenCalled() - }) - }) + ); + expect(atom.confirm).toHaveBeenCalled(); + }); + }); it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', async () => { - jasmine.useRealClock() - spyOn(atom, 'confirm').andCallFake((options, callback) => callback(1)) - spyOn(atom.project, 'addPath') - spyOn(atom.workspace, 'open') - const state = Symbol('state') + jasmine.useRealClock(); + spyOn(atom, 'confirm').andCallFake((options, callback) => callback(1)); + spyOn(atom.project, 'addPath'); + spyOn(atom.workspace, 'open'); + const state = Symbol('state'); atom.attemptRestoreProjectStateForPaths( state, [__dirname], [__filename] - ) - expect(atom.confirm).toHaveBeenCalled() - await conditionPromise(() => atom.project.addPath.callCount === 1) + ); + expect(atom.confirm).toHaveBeenCalled(); + await conditionPromise(() => atom.project.addPath.callCount === 1); - expect(atom.project.addPath).toHaveBeenCalledWith(__dirname) - expect(atom.workspace.open.callCount).toBe(1) - expect(atom.workspace.open).toHaveBeenCalledWith(__filename) - }) + expect(atom.project.addPath).toHaveBeenCalledWith(__dirname); + expect(atom.workspace.open.callCount).toBe(1); + expect(atom.workspace.open).toHaveBeenCalledWith(__filename); + }); it('prompts the user to restore the state in a new window, opening a new window', async () => { - jasmine.useRealClock() - spyOn(atom, 'confirm').andCallFake((options, callback) => callback(0)) - spyOn(atom, 'open') - const state = Symbol('state') + jasmine.useRealClock(); + spyOn(atom, 'confirm').andCallFake((options, callback) => callback(0)); + spyOn(atom, 'open'); + const state = Symbol('state'); atom.attemptRestoreProjectStateForPaths( state, [__dirname], [__filename] - ) - expect(atom.confirm).toHaveBeenCalled() - await conditionPromise(() => atom.open.callCount === 1) + ); + expect(atom.confirm).toHaveBeenCalled(); + await conditionPromise(() => atom.open.callCount === 1); expect(atom.open).toHaveBeenCalledWith({ pathsToOpen: [__dirname, __filename], newWindow: true, devMode: atom.inDevMode(), safeMode: atom.inSafeMode() - }) - }) - }) - }) + }); + }); + }); + }); describe('::unloadEditorWindow()', () => { it('saves the BlobStore so it can be loaded after reload', () => { - const configDirPath = temp.mkdirSync('atom-spec-environment') - const fakeBlobStore = jasmine.createSpyObj('blob store', ['save']) + const configDirPath = temp.mkdirSync('atom-spec-environment'); + const fakeBlobStore = jasmine.createSpyObj('blob store', ['save']); const atomEnvironment = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate, enablePersistence: true - }) + }); atomEnvironment.initialize({ configDirPath, blobStore: fakeBlobStore, window, document - }) + }); - atomEnvironment.unloadEditorWindow() + atomEnvironment.unloadEditorWindow(); - expect(fakeBlobStore.save).toHaveBeenCalled() + expect(fakeBlobStore.save).toHaveBeenCalled(); - atomEnvironment.destroy() - }) - }) + atomEnvironment.destroy(); + }); + }); describe('::destroy()', () => { it('does not throw exceptions when unsubscribing from ipc events (regression)', async () => { const fakeDocument = { - addEventListener () {}, - removeEventListener () {}, + addEventListener() {}, + removeEventListener() {}, head: document.createElement('head'), body: document.createElement('body') - } + }; const atomEnvironment = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate - }) - atomEnvironment.initialize({ window, document: fakeDocument }) + }); + atomEnvironment.initialize({ window, document: fakeDocument }); spyOn(atomEnvironment.packages, 'loadPackages').andReturn( Promise.resolve() - ) - spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve()) - spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve()) - await atomEnvironment.startEditorWindow() - atomEnvironment.unloadEditorWindow() - atomEnvironment.destroy() - }) - }) + ); + spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve()); + spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve()); + await atomEnvironment.startEditorWindow(); + atomEnvironment.unloadEditorWindow(); + atomEnvironment.destroy(); + }); + }); describe('::whenShellEnvironmentLoaded()', () => { - let atomEnvironment, envLoaded, spy + let atomEnvironment, envLoaded, spy; beforeEach(() => { - let resolvePromise = null + let resolvePromise = null; const promise = new Promise(resolve => { - resolvePromise = resolve - }) + resolvePromise = resolve; + }); envLoaded = () => { - resolvePromise() - return promise - } + resolvePromise(); + return promise; + }; atomEnvironment = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate, - updateProcessEnv () { - return promise + updateProcessEnv() { + return promise; } - }) - atomEnvironment.initialize({ window, document }) - spy = jasmine.createSpy() - }) + }); + atomEnvironment.initialize({ window, document }); + spy = jasmine.createSpy(); + }); - afterEach(() => atomEnvironment.destroy()) + afterEach(() => atomEnvironment.destroy()); it('is triggered once the shell environment is loaded', async () => { - atomEnvironment.whenShellEnvironmentLoaded(spy) - atomEnvironment.updateProcessEnvAndTriggerHooks() - await envLoaded() - expect(spy).toHaveBeenCalled() - }) + atomEnvironment.whenShellEnvironmentLoaded(spy); + atomEnvironment.updateProcessEnvAndTriggerHooks(); + await envLoaded(); + expect(spy).toHaveBeenCalled(); + }); it('triggers the callback immediately if the shell environment is already loaded', async () => { - atomEnvironment.updateProcessEnvAndTriggerHooks() - await envLoaded() - atomEnvironment.whenShellEnvironmentLoaded(spy) - expect(spy).toHaveBeenCalled() - }) - }) + atomEnvironment.updateProcessEnvAndTriggerHooks(); + await envLoaded(); + atomEnvironment.whenShellEnvironmentLoaded(spy); + expect(spy).toHaveBeenCalled(); + }); + }); describe('::openLocations(locations)', () => { beforeEach(() => { - atom.project.setPaths([]) - }) + atom.project.setPaths([]); + }); describe('when there is no saved state', () => { beforeEach(() => { - spyOn(atom, 'loadState').andReturn(Promise.resolve(null)) - }) + spyOn(atom, 'loadState').andReturn(Promise.resolve(null)); + }); describe('when the opened path exists', () => { it('opens a file', async () => { - const pathToOpen = __filename - await atom.openLocations([{ pathToOpen, exists: true, isFile: true }]) - expect(atom.project.getPaths()).toEqual([]) - }) + const pathToOpen = __filename; + await atom.openLocations([ + { pathToOpen, exists: true, isFile: true } + ]); + expect(atom.project.getPaths()).toEqual([]); + }); it('opens a directory as a project folder', async () => { - const pathToOpen = __dirname - await atom.openLocations([{ pathToOpen, exists: true, isDirectory: true }]) + const pathToOpen = __dirname; + await atom.openLocations([ + { pathToOpen, exists: true, isDirectory: true } + ]); expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual( [] - ) - expect(atom.project.getPaths()).toEqual([pathToOpen]) - }) - }) + ); + expect(atom.project.getPaths()).toEqual([pathToOpen]); + }); + }); describe('when the opened path does not exist', () => { it('opens it as a new file', async () => { const pathToOpen = path.join( __dirname, 'this-path-does-not-exist.txt' - ) - await atom.openLocations([{ pathToOpen, exists: false }]) + ); + await atom.openLocations([{ pathToOpen, exists: false }]); expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual( [pathToOpen] - ) - expect(atom.project.getPaths()).toEqual([]) - }) + ); + expect(atom.project.getPaths()).toEqual([]); + }); it('may be required to be an existing directory', async () => { - spyOn(atom.notifications, 'addWarning') + spyOn(atom.notifications, 'addWarning'); - const nonExistent = path.join(__dirname, 'no') - const existingFile = __filename - const existingDir = path.join(__dirname, 'fixtures') + const nonExistent = path.join(__dirname, 'no'); + const existingFile = __filename; + const existingDir = path.join(__dirname, 'fixtures'); await atom.openLocations([ { pathToOpen: nonExistent, isDirectory: true }, { pathToOpen: existingFile, isDirectory: true }, { pathToOpen: existingDir, isDirectory: true } - ]) + ]); - expect(atom.workspace.getTextEditors()).toEqual([]) - expect(atom.project.getPaths()).toEqual([existingDir]) + expect(atom.workspace.getTextEditors()).toEqual([]); + expect(atom.project.getPaths()).toEqual([existingDir]); expect(atom.notifications.addWarning).toHaveBeenCalledWith( 'Unable to open project folders', { description: `The directories \`${nonExistent}\` and \`${existingFile}\` do not exist.` } - ) - }) - }) + ); + }); + }); describe('when the opened path is handled by a registered directory provider', () => { - let serviceDisposable + let serviceDisposable; beforeEach(() => { serviceDisposable = atom.packages.serviceHub.provide( 'atom.directory-provider', '0.1.0', { - directoryForURISync (uri) { + directoryForURISync(uri) { if (uri.startsWith('remote://')) { return { - getPath () { - return uri + getPath() { + return uri; } - } + }; } else { - return null + return null; } } } - ) + ); - waitsFor(() => atom.project.directoryProviders.length > 0) - }) + waitsFor(() => atom.project.directoryProviders.length > 0); + }); afterEach(() => { - serviceDisposable.dispose() - }) + serviceDisposable.dispose(); + }); it("adds it to the project's paths as is", async () => { - const pathToOpen = 'remote://server:7644/some/dir/path' - spyOn(atom.project, 'addPath') - await atom.openLocations([{ pathToOpen }]) - expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen) - }) - }) - }) + const pathToOpen = 'remote://server:7644/some/dir/path'; + spyOn(atom.project, 'addPath'); + await atom.openLocations([{ pathToOpen }]); + expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen); + }); + }); + }); describe('when there is saved state for the relevant directories', () => { - const state = Symbol('savedState') + const state = Symbol('savedState'); beforeEach(() => { - spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')) - spyOn(atom, 'loadState').andCallFake(function (key) { + spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')); + spyOn(atom, 'loadState').andCallFake(function(key) { if (key === __dirname) { - return Promise.resolve(state) + return Promise.resolve(state); } else { - return Promise.resolve(null) + return Promise.resolve(null); } - }) - spyOn(atom, 'attemptRestoreProjectStateForPaths') - }) + }); + spyOn(atom, 'attemptRestoreProjectStateForPaths'); + }); describe('when there are no project folders', () => { it('attempts to restore the project state', async () => { - const pathToOpen = __dirname - await atom.openLocations([{ pathToOpen, isDirectory: true }]) + const pathToOpen = __dirname; + await atom.openLocations([{ pathToOpen, isDirectory: true }]); expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith( state, [pathToOpen], [] - ) - expect(atom.project.getPaths()).toEqual([]) - }) + ); + expect(atom.project.getPaths()).toEqual([]); + }); it('includes missing mandatory project folders in computation of initial state key', async () => { - const existingDir = path.join(__dirname, 'fixtures') - const missingDir = path.join(__dirname, 'no') + const existingDir = path.join(__dirname, 'fixtures'); + const missingDir = path.join(__dirname, 'no'); - atom.loadState.andCallFake(function (key) { + atom.loadState.andCallFake(function(key) { if (key === `${existingDir}:${missingDir}`) { - return Promise.resolve(state) + return Promise.resolve(state); } else { - return Promise.resolve(null) + return Promise.resolve(null); } - }) + }); await atom.openLocations([ { pathToOpen: existingDir }, { pathToOpen: missingDir, isDirectory: true } - ]) + ]); expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith( state, [existingDir], [] - ) - expect(atom.project.getPaths(), [existingDir]) - }) + ); + expect(atom.project.getPaths(), [existingDir]); + }); it('opens the specified files', async () => { await atom.openLocations([ { pathToOpen: __dirname, isDirectory: true }, { pathToOpen: __filename } - ]) + ]); expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith( state, [__dirname], [__filename] - ) - expect(atom.project.getPaths()).toEqual([]) - }) - }) + ); + expect(atom.project.getPaths()).toEqual([]); + }); + }); describe('when there are already project folders', () => { - beforeEach(() => atom.project.setPaths([__dirname])) + beforeEach(() => atom.project.setPaths([__dirname])); it('does not attempt to restore the project state, instead adding the project paths', async () => { - const pathToOpen = path.join(__dirname, 'fixtures') - await atom.openLocations([{ pathToOpen, exists: true, isDirectory: true }]) - expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled() - expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) - }) + const pathToOpen = path.join(__dirname, 'fixtures'); + await atom.openLocations([ + { pathToOpen, exists: true, isDirectory: true } + ]); + expect( + atom.attemptRestoreProjectStateForPaths + ).not.toHaveBeenCalled(); + expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]); + }); it('opens the specified files', async () => { - const pathToOpen = path.join(__dirname, 'fixtures') - const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt') + const pathToOpen = path.join(__dirname, 'fixtures'); + const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt'); await atom.openLocations([ { pathToOpen, exists: true, isDirectory: true }, { pathToOpen: fileToOpen, exists: true, isFile: true } - ]) + ]); expect( atom.attemptRestoreProjectStateForPaths - ).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]) - expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) - }) - }) - }) - }) + ).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]); + expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]); + }); + }); + }); + }); describe('::getReleaseChannel()', () => { - let version + let version; beforeEach(() => { - spyOn(atom, 'getVersion').andCallFake(() => version) - }) + spyOn(atom, 'getVersion').andCallFake(() => version); + }); it('returns the correct channel based on the version number', () => { - version = '1.5.6' - expect(atom.getReleaseChannel()).toBe('stable') + version = '1.5.6'; + expect(atom.getReleaseChannel()).toBe('stable'); - version = '1.5.0-beta10' - expect(atom.getReleaseChannel()).toBe('beta') + version = '1.5.0-beta10'; + expect(atom.getReleaseChannel()).toBe('beta'); - version = '1.7.0-dev-5340c91' - expect(atom.getReleaseChannel()).toBe('dev') - }) - }) -}) + version = '1.7.0-dev-5340c91'; + expect(atom.getReleaseChannel()).toBe('dev'); + }); + }); +}); diff --git a/spec/atom-paths-spec.js b/spec/atom-paths-spec.js index cc4bb769b..de95dc706 100644 --- a/spec/atom-paths-spec.js +++ b/spec/atom-paths-spec.js @@ -1,127 +1,127 @@ /** @babel */ -import { app } from 'remote' -import atomPaths from '../src/atom-paths' -import fs from 'fs-plus' -import path from 'path' -const temp = require('temp').track() +import { app } from 'remote'; +import atomPaths from '../src/atom-paths'; +import fs from 'fs-plus'; +import path from 'path'; +const temp = require('temp').track(); describe('AtomPaths', () => { const portableAtomHomePath = path.join( atomPaths.getAppDirectory(), '..', '.atom' - ) + ); afterEach(() => { - atomPaths.setAtomHome(app.getPath('home')) - }) + atomPaths.setAtomHome(app.getPath('home')); + }); describe('SetAtomHomePath', () => { describe('when a portable .atom folder exists', () => { beforeEach(() => { - delete process.env.ATOM_HOME + delete process.env.ATOM_HOME; if (!fs.existsSync(portableAtomHomePath)) { - fs.mkdirSync(portableAtomHomePath) + fs.mkdirSync(portableAtomHomePath); } - }) + }); afterEach(() => { - delete process.env.ATOM_HOME - fs.removeSync(portableAtomHomePath) - }) + delete process.env.ATOM_HOME; + fs.removeSync(portableAtomHomePath); + }); it('sets ATOM_HOME to the portable .atom folder if it has permission', () => { - atomPaths.setAtomHome(app.getPath('home')) - expect(process.env.ATOM_HOME).toEqual(portableAtomHomePath) - }) + atomPaths.setAtomHome(app.getPath('home')); + expect(process.env.ATOM_HOME).toEqual(portableAtomHomePath); + }); it('uses ATOM_HOME if no write access to portable .atom folder', () => { - if (process.platform === 'win32') return + if (process.platform === 'win32') return; - const readOnlyPath = temp.mkdirSync('atom-path-spec-no-write-access') - process.env.ATOM_HOME = readOnlyPath - fs.chmodSync(portableAtomHomePath, 444) - atomPaths.setAtomHome(app.getPath('home')) - expect(process.env.ATOM_HOME).toEqual(readOnlyPath) - }) - }) + const readOnlyPath = temp.mkdirSync('atom-path-spec-no-write-access'); + process.env.ATOM_HOME = readOnlyPath; + fs.chmodSync(portableAtomHomePath, 444); + atomPaths.setAtomHome(app.getPath('home')); + expect(process.env.ATOM_HOME).toEqual(readOnlyPath); + }); + }); describe('when a portable folder does not exist', () => { beforeEach(() => { - delete process.env.ATOM_HOME - fs.removeSync(portableAtomHomePath) - }) + delete process.env.ATOM_HOME; + fs.removeSync(portableAtomHomePath); + }); afterEach(() => { - delete process.env.ATOM_HOME - }) + delete process.env.ATOM_HOME; + }); it('leaves ATOM_HOME unmodified if it was already set', () => { - const temporaryHome = temp.mkdirSync('atom-spec-setatomhomepath') - process.env.ATOM_HOME = temporaryHome - atomPaths.setAtomHome(app.getPath('home')) - expect(process.env.ATOM_HOME).toEqual(temporaryHome) - }) + const temporaryHome = temp.mkdirSync('atom-spec-setatomhomepath'); + process.env.ATOM_HOME = temporaryHome; + atomPaths.setAtomHome(app.getPath('home')); + expect(process.env.ATOM_HOME).toEqual(temporaryHome); + }); it('sets ATOM_HOME to a default location if not yet set', () => { - const expectedPath = path.join(app.getPath('home'), '.atom') - atomPaths.setAtomHome(app.getPath('home')) - expect(process.env.ATOM_HOME).toEqual(expectedPath) - }) - }) - }) + const expectedPath = path.join(app.getPath('home'), '.atom'); + atomPaths.setAtomHome(app.getPath('home')); + expect(process.env.ATOM_HOME).toEqual(expectedPath); + }); + }); + }); describe('setUserData', () => { - let tempAtomConfigPath = null - let tempAtomHomePath = null - let electronUserDataPath = null - let defaultElectronUserDataPath = null + let tempAtomConfigPath = null; + let tempAtomHomePath = null; + let electronUserDataPath = null; + let defaultElectronUserDataPath = null; beforeEach(() => { - defaultElectronUserDataPath = app.getPath('userData') - delete process.env.ATOM_HOME - tempAtomHomePath = temp.mkdirSync('atom-paths-specs-userdata-home') - tempAtomConfigPath = path.join(tempAtomHomePath, '.atom') - fs.mkdirSync(tempAtomConfigPath) - electronUserDataPath = path.join(tempAtomConfigPath, 'electronUserData') - atomPaths.setAtomHome(tempAtomHomePath) - }) + defaultElectronUserDataPath = app.getPath('userData'); + delete process.env.ATOM_HOME; + tempAtomHomePath = temp.mkdirSync('atom-paths-specs-userdata-home'); + tempAtomConfigPath = path.join(tempAtomHomePath, '.atom'); + fs.mkdirSync(tempAtomConfigPath); + electronUserDataPath = path.join(tempAtomConfigPath, 'electronUserData'); + atomPaths.setAtomHome(tempAtomHomePath); + }); afterEach(() => { - delete process.env.ATOM_HOME - fs.removeSync(electronUserDataPath) + delete process.env.ATOM_HOME; + fs.removeSync(electronUserDataPath); try { - temp.cleanupSync() + temp.cleanupSync(); } catch (e) { // Ignore } - app.setPath('userData', defaultElectronUserDataPath) - }) + app.setPath('userData', defaultElectronUserDataPath); + }); describe('when an electronUserData folder exists', () => { it('sets userData path to the folder if it has permission', () => { - fs.mkdirSync(electronUserDataPath) - atomPaths.setUserData(app) - expect(app.getPath('userData')).toEqual(electronUserDataPath) - }) + fs.mkdirSync(electronUserDataPath); + atomPaths.setUserData(app); + expect(app.getPath('userData')).toEqual(electronUserDataPath); + }); it('leaves userData unchanged if no write access to electronUserData folder', () => { - if (process.platform === 'win32') return + if (process.platform === 'win32') return; - fs.mkdirSync(electronUserDataPath) - fs.chmodSync(electronUserDataPath, 444) - atomPaths.setUserData(app) - fs.chmodSync(electronUserDataPath, 666) - expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath) - }) - }) + fs.mkdirSync(electronUserDataPath); + fs.chmodSync(electronUserDataPath, 444); + atomPaths.setUserData(app); + fs.chmodSync(electronUserDataPath, 666); + expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath); + }); + }); describe('when an electronUserDataPath folder does not exist', () => { it('leaves userData app path unchanged', () => { - atomPaths.setUserData(app) - expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath) - }) - }) - }) -}) + atomPaths.setUserData(app); + expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath); + }); + }); + }); +}); diff --git a/spec/auto-update-manager-spec.js b/spec/auto-update-manager-spec.js index 563085934..31dde418c 100644 --- a/spec/auto-update-manager-spec.js +++ b/spec/auto-update-manager-spec.js @@ -1,128 +1,128 @@ -const AutoUpdateManager = require('../src/auto-update-manager') -const { remote } = require('electron') -const electronAutoUpdater = remote.require('electron').autoUpdater +const AutoUpdateManager = require('../src/auto-update-manager'); +const { remote } = require('electron'); +const electronAutoUpdater = remote.require('electron').autoUpdater; describe('AutoUpdateManager (renderer)', () => { - if (process.platform !== 'darwin') return // Tests are tied to electron autoUpdater, we use something else on Linux and Win32 + if (process.platform !== 'darwin') return; // Tests are tied to electron autoUpdater, we use something else on Linux and Win32 - let autoUpdateManager + let autoUpdateManager; beforeEach(() => { autoUpdateManager = new AutoUpdateManager({ applicationDelegate: atom.applicationDelegate - }) - autoUpdateManager.initialize() - }) + }); + autoUpdateManager.initialize(); + }); afterEach(() => { - autoUpdateManager.destroy() - }) + autoUpdateManager.destroy(); + }); describe('::onDidBeginCheckingForUpdate', () => { it('subscribes to "did-begin-checking-for-update" event', () => { - const spy = jasmine.createSpy('spy') - autoUpdateManager.onDidBeginCheckingForUpdate(spy) - electronAutoUpdater.emit('checking-for-update') + const spy = jasmine.createSpy('spy'); + autoUpdateManager.onDidBeginCheckingForUpdate(spy); + electronAutoUpdater.emit('checking-for-update'); waitsFor(() => { - return spy.callCount === 1 - }) - }) - }) + return spy.callCount === 1; + }); + }); + }); describe('::onDidBeginDownloadingUpdate', () => { it('subscribes to "did-begin-downloading-update" event', () => { - const spy = jasmine.createSpy('spy') - autoUpdateManager.onDidBeginDownloadingUpdate(spy) - electronAutoUpdater.emit('update-available') + const spy = jasmine.createSpy('spy'); + autoUpdateManager.onDidBeginDownloadingUpdate(spy); + electronAutoUpdater.emit('update-available'); waitsFor(() => { - return spy.callCount === 1 - }) - }) - }) + return spy.callCount === 1; + }); + }); + }); describe('::onDidCompleteDownloadingUpdate', () => { it('subscribes to "did-complete-downloading-update" event', () => { - const spy = jasmine.createSpy('spy') - autoUpdateManager.onDidCompleteDownloadingUpdate(spy) - electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3') + const spy = jasmine.createSpy('spy'); + autoUpdateManager.onDidCompleteDownloadingUpdate(spy); + electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3'); waitsFor(() => { - return spy.callCount === 1 - }) + return spy.callCount === 1; + }); runs(() => { - expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3') - }) - }) - }) + expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3'); + }); + }); + }); describe('::onUpdateNotAvailable', () => { it('subscribes to "update-not-available" event', () => { - const spy = jasmine.createSpy('spy') - autoUpdateManager.onUpdateNotAvailable(spy) - electronAutoUpdater.emit('update-not-available') + const spy = jasmine.createSpy('spy'); + autoUpdateManager.onUpdateNotAvailable(spy); + electronAutoUpdater.emit('update-not-available'); waitsFor(() => { - return spy.callCount === 1 - }) - }) - }) + return spy.callCount === 1; + }); + }); + }); describe('::onUpdateError', () => { it('subscribes to "update-error" event', () => { - const spy = jasmine.createSpy('spy') - autoUpdateManager.onUpdateError(spy) - electronAutoUpdater.emit('error', {}, 'an error message') - waitsFor(() => spy.callCount === 1) + const spy = jasmine.createSpy('spy'); + autoUpdateManager.onUpdateError(spy); + electronAutoUpdater.emit('error', {}, 'an error message'); + waitsFor(() => spy.callCount === 1); runs(() => expect(autoUpdateManager.getErrorMessage()).toBe('an error message') - ) - }) - }) + ); + }); + }); describe('::platformSupportsUpdates', () => { - let state, releaseChannel + let state, releaseChannel; it('returns true on macOS and Windows when in stable', () => { - spyOn(autoUpdateManager, 'getState').andCallFake(() => state) - spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel) + spyOn(autoUpdateManager, 'getState').andCallFake(() => state); + spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel); - state = 'idle' - releaseChannel = 'stable' - expect(autoUpdateManager.platformSupportsUpdates()).toBe(true) + state = 'idle'; + releaseChannel = 'stable'; + expect(autoUpdateManager.platformSupportsUpdates()).toBe(true); - state = 'idle' - releaseChannel = 'dev' - expect(autoUpdateManager.platformSupportsUpdates()).toBe(false) + state = 'idle'; + releaseChannel = 'dev'; + expect(autoUpdateManager.platformSupportsUpdates()).toBe(false); - state = 'unsupported' - releaseChannel = 'stable' - expect(autoUpdateManager.platformSupportsUpdates()).toBe(false) + state = 'unsupported'; + releaseChannel = 'stable'; + expect(autoUpdateManager.platformSupportsUpdates()).toBe(false); - state = 'unsupported' - releaseChannel = 'dev' - expect(autoUpdateManager.platformSupportsUpdates()).toBe(false) - }) - }) + state = 'unsupported'; + releaseChannel = 'dev'; + expect(autoUpdateManager.platformSupportsUpdates()).toBe(false); + }); + }); describe('::destroy', () => { it('unsubscribes from all events', () => { - const spy = jasmine.createSpy('spy') - const doneIndicator = jasmine.createSpy('spy') - atom.applicationDelegate.onUpdateNotAvailable(doneIndicator) - autoUpdateManager.onDidBeginCheckingForUpdate(spy) - autoUpdateManager.onDidBeginDownloadingUpdate(spy) - autoUpdateManager.onDidCompleteDownloadingUpdate(spy) - autoUpdateManager.onUpdateNotAvailable(spy) - autoUpdateManager.destroy() - electronAutoUpdater.emit('checking-for-update') - electronAutoUpdater.emit('update-available') - electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3') - electronAutoUpdater.emit('update-not-available') + const spy = jasmine.createSpy('spy'); + const doneIndicator = jasmine.createSpy('spy'); + atom.applicationDelegate.onUpdateNotAvailable(doneIndicator); + autoUpdateManager.onDidBeginCheckingForUpdate(spy); + autoUpdateManager.onDidBeginDownloadingUpdate(spy); + autoUpdateManager.onDidCompleteDownloadingUpdate(spy); + autoUpdateManager.onUpdateNotAvailable(spy); + autoUpdateManager.destroy(); + electronAutoUpdater.emit('checking-for-update'); + electronAutoUpdater.emit('update-available'); + electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3'); + electronAutoUpdater.emit('update-not-available'); waitsFor(() => { - return doneIndicator.callCount === 1 - }) + return doneIndicator.callCount === 1; + }); runs(() => { - expect(spy.callCount).toBe(0) - }) - }) - }) -}) + expect(spy.callCount).toBe(0); + }); + }); + }); +}); diff --git a/spec/command-installer-spec.js b/spec/command-installer-spec.js index 1cf6af5c2..06a000148 100644 --- a/spec/command-installer-spec.js +++ b/spec/command-installer-spec.js @@ -1,16 +1,16 @@ -const path = require('path') -const fs = require('fs-plus') -const temp = require('temp').track() -const CommandInstaller = require('../src/command-installer') +const path = require('path'); +const fs = require('fs-plus'); +const temp = require('temp').track(); +const CommandInstaller = require('../src/command-installer'); describe('CommandInstaller on #darwin', () => { - let installer, resourcesPath, installationPath, atomBinPath, apmBinPath + let installer, resourcesPath, installationPath, atomBinPath, apmBinPath; beforeEach(() => { - installationPath = temp.mkdirSync('atom-bin') + installationPath = temp.mkdirSync('atom-bin'); - resourcesPath = temp.mkdirSync('atom-app') - atomBinPath = path.join(resourcesPath, 'app', 'atom.sh') + resourcesPath = temp.mkdirSync('atom-app'); + atomBinPath = path.join(resourcesPath, 'app', 'atom.sh'); apmBinPath = path.join( resourcesPath, 'app', @@ -18,200 +18,204 @@ describe('CommandInstaller on #darwin', () => { 'node_modules', '.bin', 'apm' - ) - fs.writeFileSync(atomBinPath, '') - fs.writeFileSync(apmBinPath, '') - fs.chmodSync(atomBinPath, '755') - fs.chmodSync(apmBinPath, '755') + ); + fs.writeFileSync(atomBinPath, ''); + fs.writeFileSync(apmBinPath, ''); + fs.chmodSync(atomBinPath, '755'); + fs.chmodSync(apmBinPath, '755'); spyOn(CommandInstaller.prototype, 'getResourcesDirectory').andReturn( resourcesPath - ) + ); spyOn(CommandInstaller.prototype, 'getInstallDirectory').andReturn( installationPath - ) - }) + ); + }); afterEach(() => { try { - temp.cleanupSync() + temp.cleanupSync(); } catch (error) {} - }) + }); it('shows an error dialog when installing commands interactively fails', () => { - const appDelegate = jasmine.createSpyObj('appDelegate', ['confirm']) - installer = new CommandInstaller(appDelegate) - installer.initialize('2.0.2') + const appDelegate = jasmine.createSpyObj('appDelegate', ['confirm']); + installer = new CommandInstaller(appDelegate); + installer.initialize('2.0.2'); spyOn(installer, 'installAtomCommand').andCallFake((__, callback) => callback(new Error('an error')) - ) + ); - installer.installShellCommandsInteractively() + installer.installShellCommandsInteractively(); expect(appDelegate.confirm.mostRecentCall.args[0]).toEqual({ message: 'Failed to install shell commands', detail: 'an error' - }) + }); - appDelegate.confirm.reset() - installer.installAtomCommand.andCallFake((__, callback) => callback()) + appDelegate.confirm.reset(); + installer.installAtomCommand.andCallFake((__, callback) => callback()); spyOn(installer, 'installApmCommand').andCallFake((__, callback) => callback(new Error('another error')) - ) + ); - installer.installShellCommandsInteractively() + installer.installShellCommandsInteractively(); expect(appDelegate.confirm.mostRecentCall.args[0]).toEqual({ message: 'Failed to install shell commands', detail: 'another error' - }) - }) + }); + }); it('shows a success dialog when installing commands interactively succeeds', () => { - const appDelegate = jasmine.createSpyObj('appDelegate', ['confirm']) - installer = new CommandInstaller(appDelegate) - installer.initialize('2.0.2') + const appDelegate = jasmine.createSpyObj('appDelegate', ['confirm']); + installer = new CommandInstaller(appDelegate); + installer.initialize('2.0.2'); spyOn(installer, 'installAtomCommand').andCallFake((__, callback) => callback(undefined, 'atom') - ) + ); spyOn(installer, 'installApmCommand').andCallFake((__, callback) => callback(undefined, 'apm') - ) + ); - installer.installShellCommandsInteractively() + installer.installShellCommandsInteractively(); expect(appDelegate.confirm.mostRecentCall.args[0]).toEqual({ message: 'Commands installed.', detail: 'The shell commands `atom` and `apm` are installed.' - }) - }) + }); + }); describe('when using a stable version of atom', () => { beforeEach(() => { - installer = new CommandInstaller() - installer.initialize('2.0.2') - }) + installer = new CommandInstaller(); + installer.initialize('2.0.2'); + }); it("symlinks the atom command as 'atom'", () => { - const installedAtomPath = path.join(installationPath, 'atom') - expect(fs.isFileSync(installedAtomPath)).toBeFalsy() + const installedAtomPath = path.join(installationPath, 'atom'); + expect(fs.isFileSync(installedAtomPath)).toBeFalsy(); waitsFor(done => { installer.installAtomCommand(false, error => { - expect(error).toBeNull() + expect(error).toBeNull(); expect(fs.realpathSync(installedAtomPath)).toBe( fs.realpathSync(atomBinPath) - ) - expect(fs.isExecutableSync(installedAtomPath)).toBe(true) + ); + expect(fs.isExecutableSync(installedAtomPath)).toBe(true); expect(fs.isFileSync(path.join(installationPath, 'atom-beta'))).toBe( false - ) - done() - }) - }) - }) + ); + done(); + }); + }); + }); it("symlinks the apm command as 'apm'", () => { - const installedApmPath = path.join(installationPath, 'apm') - expect(fs.isFileSync(installedApmPath)).toBeFalsy() + const installedApmPath = path.join(installationPath, 'apm'); + expect(fs.isFileSync(installedApmPath)).toBeFalsy(); waitsFor(done => { installer.installApmCommand(false, error => { - expect(error).toBeNull() + expect(error).toBeNull(); expect(fs.realpathSync(installedApmPath)).toBe( fs.realpathSync(apmBinPath) - ) - expect(fs.isExecutableSync(installedApmPath)).toBeTruthy() + ); + expect(fs.isExecutableSync(installedApmPath)).toBeTruthy(); expect(fs.isFileSync(path.join(installationPath, 'apm-beta'))).toBe( false - ) - done() - }) - }) - }) - }) + ); + done(); + }); + }); + }); + }); describe('when using a beta version of atom', () => { beforeEach(() => { - installer = new CommandInstaller() - installer.initialize('2.2.0-beta.0') - }) + installer = new CommandInstaller(); + installer.initialize('2.2.0-beta.0'); + }); it("symlinks the atom command as 'atom-beta'", () => { - const installedAtomPath = path.join(installationPath, 'atom-beta') - expect(fs.isFileSync(installedAtomPath)).toBeFalsy() + const installedAtomPath = path.join(installationPath, 'atom-beta'); + expect(fs.isFileSync(installedAtomPath)).toBeFalsy(); waitsFor(done => { installer.installAtomCommand(false, error => { - expect(error).toBeNull() + expect(error).toBeNull(); expect(fs.realpathSync(installedAtomPath)).toBe( fs.realpathSync(atomBinPath) - ) - expect(fs.isExecutableSync(installedAtomPath)).toBe(true) - expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe(false) - done() - }) - }) - }) + ); + expect(fs.isExecutableSync(installedAtomPath)).toBe(true); + expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe( + false + ); + done(); + }); + }); + }); it("symlinks the apm command as 'apm-beta'", () => { - const installedApmPath = path.join(installationPath, 'apm-beta') - expect(fs.isFileSync(installedApmPath)).toBeFalsy() + const installedApmPath = path.join(installationPath, 'apm-beta'); + expect(fs.isFileSync(installedApmPath)).toBeFalsy(); waitsFor(done => { installer.installApmCommand(false, error => { - expect(error).toBeNull() + expect(error).toBeNull(); expect(fs.realpathSync(installedApmPath)).toBe( fs.realpathSync(apmBinPath) - ) - expect(fs.isExecutableSync(installedApmPath)).toBeTruthy() - expect(fs.isFileSync(path.join(installationPath, 'apm'))).toBe(false) - done() - }) - }) - }) - }) + ); + expect(fs.isExecutableSync(installedApmPath)).toBeTruthy(); + expect(fs.isFileSync(path.join(installationPath, 'apm'))).toBe(false); + done(); + }); + }); + }); + }); describe('when using a nightly version of atom', () => { beforeEach(() => { - installer = new CommandInstaller() - installer.initialize('2.2.0-nightly0') - }) + installer = new CommandInstaller(); + installer.initialize('2.2.0-nightly0'); + }); it("symlinks the atom command as 'atom-nightly'", () => { - const installedAtomPath = path.join(installationPath, 'atom-nightly') - expect(fs.isFileSync(installedAtomPath)).toBeFalsy() + const installedAtomPath = path.join(installationPath, 'atom-nightly'); + expect(fs.isFileSync(installedAtomPath)).toBeFalsy(); waitsFor(done => { installer.installAtomCommand(false, error => { - expect(error).toBeNull() + expect(error).toBeNull(); expect(fs.realpathSync(installedAtomPath)).toBe( fs.realpathSync(atomBinPath) - ) - expect(fs.isExecutableSync(installedAtomPath)).toBe(true) - expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe(false) - done() - }) - }) - }) + ); + expect(fs.isExecutableSync(installedAtomPath)).toBe(true); + expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe( + false + ); + done(); + }); + }); + }); it("symlinks the apm command as 'apm-nightly'", () => { - const installedApmPath = path.join(installationPath, 'apm-nightly') - expect(fs.isFileSync(installedApmPath)).toBeFalsy() + const installedApmPath = path.join(installationPath, 'apm-nightly'); + expect(fs.isFileSync(installedApmPath)).toBeFalsy(); waitsFor(done => { installer.installApmCommand(false, error => { - expect(error).toBeNull() + expect(error).toBeNull(); expect(fs.realpathSync(installedApmPath)).toBe( fs.realpathSync(apmBinPath) - ) - expect(fs.isExecutableSync(installedApmPath)).toBeTruthy() + ); + expect(fs.isExecutableSync(installedApmPath)).toBeTruthy(); expect(fs.isFileSync(path.join(installationPath, 'nightly'))).toBe( false - ) - done() - }) - }) - }) - }) -}) + ); + done(); + }); + }); + }); + }); +}); diff --git a/spec/command-registry-spec.js b/spec/command-registry-spec.js index 86a5f7101..1f170ef98 100644 --- a/spec/command-registry-spec.js +++ b/spec/command-registry-spec.js @@ -1,281 +1,283 @@ -const CommandRegistry = require('../src/command-registry') -const _ = require('underscore-plus') +const CommandRegistry = require('../src/command-registry'); +const _ = require('underscore-plus'); describe('CommandRegistry', () => { - let registry, parent, child, grandchild + let registry, parent, child, grandchild; beforeEach(() => { - parent = document.createElement('div') - child = document.createElement('div') - grandchild = document.createElement('div') - parent.classList.add('parent') - child.classList.add('child') - grandchild.classList.add('grandchild') - child.appendChild(grandchild) - parent.appendChild(child) - document.querySelector('#jasmine-content').appendChild(parent) + parent = document.createElement('div'); + child = document.createElement('div'); + grandchild = document.createElement('div'); + parent.classList.add('parent'); + child.classList.add('child'); + grandchild.classList.add('grandchild'); + child.appendChild(grandchild); + parent.appendChild(child); + document.querySelector('#jasmine-content').appendChild(parent); - registry = new CommandRegistry() - registry.attach(parent) - }) + registry = new CommandRegistry(); + registry.attach(parent); + }); - afterEach(() => registry.destroy()) + afterEach(() => registry.destroy()); describe('when a command event is dispatched on an element', () => { it('invokes callbacks with selectors matching the target', () => { - let called = false - registry.add('.grandchild', 'command', function (event) { - expect(this).toBe(grandchild) - expect(event.type).toBe('command') - expect(event.eventPhase).toBe(Event.BUBBLING_PHASE) - expect(event.target).toBe(grandchild) - expect(event.currentTarget).toBe(grandchild) - called = true - }) + let called = false; + registry.add('.grandchild', 'command', function(event) { + expect(this).toBe(grandchild); + expect(event.type).toBe('command'); + expect(event.eventPhase).toBe(Event.BUBBLING_PHASE); + expect(event.target).toBe(grandchild); + expect(event.currentTarget).toBe(grandchild); + called = true; + }); - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(called).toBe(true) - }) + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(called).toBe(true); + }); it('invokes callbacks with selectors matching ancestors of the target', () => { - const calls = [] + const calls = []; - registry.add('.child', 'command', function (event) { - expect(this).toBe(child) - expect(event.target).toBe(grandchild) - expect(event.currentTarget).toBe(child) - calls.push('child') - }) + registry.add('.child', 'command', function(event) { + expect(this).toBe(child); + expect(event.target).toBe(grandchild); + expect(event.currentTarget).toBe(child); + calls.push('child'); + }); - registry.add('.parent', 'command', function (event) { - expect(this).toBe(parent) - expect(event.target).toBe(grandchild) - expect(event.currentTarget).toBe(parent) - calls.push('parent') - }) + registry.add('.parent', 'command', function(event) { + expect(this).toBe(parent); + expect(event.target).toBe(grandchild); + expect(event.currentTarget).toBe(parent); + calls.push('parent'); + }); - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(calls).toEqual(['child', 'parent']) - }) + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(calls).toEqual(['child', 'parent']); + }); it('invokes inline listeners prior to listeners applied via selectors', () => { - const calls = [] - registry.add('.grandchild', 'command', () => calls.push('grandchild')) - registry.add(child, 'command', () => calls.push('child-inline')) - registry.add('.child', 'command', () => calls.push('child')) - registry.add('.parent', 'command', () => calls.push('parent')) + const calls = []; + registry.add('.grandchild', 'command', () => calls.push('grandchild')); + registry.add(child, 'command', () => calls.push('child-inline')); + registry.add('.child', 'command', () => calls.push('child')); + registry.add('.parent', 'command', () => calls.push('parent')); - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(calls).toEqual(['grandchild', 'child-inline', 'child', 'parent']) - }) + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(calls).toEqual(['grandchild', 'child-inline', 'child', 'parent']); + }); it('orders multiple matching listeners for an element by selector specificity', () => { - child.classList.add('foo', 'bar') - const calls = [] + child.classList.add('foo', 'bar'); + const calls = []; - registry.add('.foo.bar', 'command', () => calls.push('.foo.bar')) - registry.add('.foo', 'command', () => calls.push('.foo')) - registry.add('.bar', 'command', () => calls.push('.bar')) // specificity ties favor commands added later, like CSS + registry.add('.foo.bar', 'command', () => calls.push('.foo.bar')); + registry.add('.foo', 'command', () => calls.push('.foo')); + registry.add('.bar', 'command', () => calls.push('.bar')); // specificity ties favor commands added later, like CSS - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(calls).toEqual(['.foo.bar', '.bar', '.foo']) - }) + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(calls).toEqual(['.foo.bar', '.bar', '.foo']); + }); it('orders inline listeners by reverse registration order', () => { - const calls = [] - registry.add(child, 'command', () => calls.push('child1')) - registry.add(child, 'command', () => calls.push('child2')) - child.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(calls).toEqual(['child2', 'child1']) - }) + const calls = []; + registry.add(child, 'command', () => calls.push('child1')); + registry.add(child, 'command', () => calls.push('child2')); + child.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(calls).toEqual(['child2', 'child1']); + }); it('stops bubbling through ancestors when .stopPropagation() is called on the event', () => { - const calls = [] + const calls = []; - registry.add('.parent', 'command', () => calls.push('parent')) - registry.add('.child', 'command', () => calls.push('child-2')) + registry.add('.parent', 'command', () => calls.push('parent')); + registry.add('.child', 'command', () => calls.push('child-2')); registry.add('.child', 'command', event => { - calls.push('child-1') - event.stopPropagation() - }) + calls.push('child-1'); + event.stopPropagation(); + }); - const dispatchedEvent = new CustomEvent('command', { bubbles: true }) - spyOn(dispatchedEvent, 'stopPropagation') - grandchild.dispatchEvent(dispatchedEvent) - expect(calls).toEqual(['child-1', 'child-2']) - expect(dispatchedEvent.stopPropagation).toHaveBeenCalled() - }) + const dispatchedEvent = new CustomEvent('command', { bubbles: true }); + spyOn(dispatchedEvent, 'stopPropagation'); + grandchild.dispatchEvent(dispatchedEvent); + expect(calls).toEqual(['child-1', 'child-2']); + expect(dispatchedEvent.stopPropagation).toHaveBeenCalled(); + }); it('stops invoking callbacks when .stopImmediatePropagation() is called on the event', () => { - const calls = [] + const calls = []; - registry.add('.parent', 'command', () => calls.push('parent')) - registry.add('.child', 'command', () => calls.push('child-2')) + registry.add('.parent', 'command', () => calls.push('parent')); + registry.add('.child', 'command', () => calls.push('child-2')); registry.add('.child', 'command', event => { - calls.push('child-1') - event.stopImmediatePropagation() - }) + calls.push('child-1'); + event.stopImmediatePropagation(); + }); - const dispatchedEvent = new CustomEvent('command', { bubbles: true }) - spyOn(dispatchedEvent, 'stopImmediatePropagation') - grandchild.dispatchEvent(dispatchedEvent) - expect(calls).toEqual(['child-1']) - expect(dispatchedEvent.stopImmediatePropagation).toHaveBeenCalled() - }) + const dispatchedEvent = new CustomEvent('command', { bubbles: true }); + spyOn(dispatchedEvent, 'stopImmediatePropagation'); + grandchild.dispatchEvent(dispatchedEvent); + expect(calls).toEqual(['child-1']); + expect(dispatchedEvent.stopImmediatePropagation).toHaveBeenCalled(); + }); it('forwards .preventDefault() calls from the synthetic event to the original', () => { - registry.add('.child', 'command', event => event.preventDefault()) + registry.add('.child', 'command', event => event.preventDefault()); - const dispatchedEvent = new CustomEvent('command', { bubbles: true }) - spyOn(dispatchedEvent, 'preventDefault') - grandchild.dispatchEvent(dispatchedEvent) - expect(dispatchedEvent.preventDefault).toHaveBeenCalled() - }) + const dispatchedEvent = new CustomEvent('command', { bubbles: true }); + spyOn(dispatchedEvent, 'preventDefault'); + grandchild.dispatchEvent(dispatchedEvent); + expect(dispatchedEvent.preventDefault).toHaveBeenCalled(); + }); it('forwards .abortKeyBinding() calls from the synthetic event to the original', () => { - registry.add('.child', 'command', event => event.abortKeyBinding()) + registry.add('.child', 'command', event => event.abortKeyBinding()); - const dispatchedEvent = new CustomEvent('command', { bubbles: true }) - dispatchedEvent.abortKeyBinding = jasmine.createSpy('abortKeyBinding') - grandchild.dispatchEvent(dispatchedEvent) - expect(dispatchedEvent.abortKeyBinding).toHaveBeenCalled() - }) + const dispatchedEvent = new CustomEvent('command', { bubbles: true }); + dispatchedEvent.abortKeyBinding = jasmine.createSpy('abortKeyBinding'); + grandchild.dispatchEvent(dispatchedEvent); + expect(dispatchedEvent.abortKeyBinding).toHaveBeenCalled(); + }); it('copies non-standard properties from the original event to the synthetic event', () => { - let syntheticEvent = null - registry.add('.child', 'command', event => (syntheticEvent = event)) + let syntheticEvent = null; + registry.add('.child', 'command', event => (syntheticEvent = event)); - const dispatchedEvent = new CustomEvent('command', { bubbles: true }) - dispatchedEvent.nonStandardProperty = 'testing' - grandchild.dispatchEvent(dispatchedEvent) - expect(syntheticEvent.nonStandardProperty).toBe('testing') - }) + const dispatchedEvent = new CustomEvent('command', { bubbles: true }); + dispatchedEvent.nonStandardProperty = 'testing'; + grandchild.dispatchEvent(dispatchedEvent); + expect(syntheticEvent.nonStandardProperty).toBe('testing'); + }); it('allows listeners to be removed via a disposable returned by ::add', () => { - let calls = [] + let calls = []; const disposable1 = registry.add('.parent', 'command', () => calls.push('parent') - ) + ); const disposable2 = registry.add('.child', 'command', () => calls.push('child') - ) + ); - disposable1.dispose() - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(calls).toEqual(['child']) + disposable1.dispose(); + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(calls).toEqual(['child']); - calls = [] - disposable2.dispose() - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) - expect(calls).toEqual([]) - }) + calls = []; + disposable2.dispose(); + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); + expect(calls).toEqual([]); + }); it('allows multiple commands to be registered under one selector when called with an object', () => { - let calls = [] + let calls = []; const disposable = registry.add('.child', { - 'command-1' () { - calls.push('command-1') + 'command-1'() { + calls.push('command-1'); }, - 'command-2' () { - calls.push('command-2') + 'command-2'() { + calls.push('command-2'); } - }) + }); - grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })) - grandchild.dispatchEvent(new CustomEvent('command-2', { bubbles: true })) + grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })); + grandchild.dispatchEvent(new CustomEvent('command-2', { bubbles: true })); - expect(calls).toEqual(['command-1', 'command-2']) + expect(calls).toEqual(['command-1', 'command-2']); - calls = [] - disposable.dispose() - grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })) - grandchild.dispatchEvent(new CustomEvent('command-2', { bubbles: true })) - expect(calls).toEqual([]) - }) + calls = []; + disposable.dispose(); + grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })); + grandchild.dispatchEvent(new CustomEvent('command-2', { bubbles: true })); + expect(calls).toEqual([]); + }); it('invokes callbacks registered with ::onWillDispatch and ::onDidDispatch', () => { - const sequence = [] + const sequence = []; - registry.onDidDispatch(event => sequence.push(['onDidDispatch', event])) + registry.onDidDispatch(event => sequence.push(['onDidDispatch', event])); registry.add('.grandchild', 'command', event => sequence.push(['listener', event]) - ) + ); - registry.onWillDispatch(event => sequence.push(['onWillDispatch', event])) + registry.onWillDispatch(event => + sequence.push(['onWillDispatch', event]) + ); - grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })) + grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true })); - expect(sequence[0][0]).toBe('onWillDispatch') - expect(sequence[1][0]).toBe('listener') - expect(sequence[2][0]).toBe('onDidDispatch') + expect(sequence[0][0]).toBe('onWillDispatch'); + expect(sequence[1][0]).toBe('listener'); + expect(sequence[2][0]).toBe('onDidDispatch'); expect( sequence[0][1] === sequence[1][1] && sequence[1][1] === sequence[2][1] - ).toBe(true) - expect(sequence[0][1].constructor).toBe(CustomEvent) - expect(sequence[0][1].target).toBe(grandchild) - }) - }) + ).toBe(true); + expect(sequence[0][1].constructor).toBe(CustomEvent); + expect(sequence[0][1].target).toBe(grandchild); + }); + }); describe('::add(selector, commandName, callback)', () => { it('throws an error when called with an invalid selector', () => { - const badSelector = '<>' - let addError = null + const badSelector = '<>'; + let addError = null; try { - registry.add(badSelector, 'foo:bar', () => {}) + registry.add(badSelector, 'foo:bar', () => {}); } catch (error) { - addError = error + addError = error; } - expect(addError.message).toContain(badSelector) - }) + expect(addError.message).toContain(badSelector); + }); it('throws an error when called with a null callback and selector target', () => { - const badCallback = null + const badCallback = null; expect(() => { - registry.add('.selector', 'foo:bar', badCallback) - }).toThrow(new Error('Cannot register a command with a null listener.')) - }) + registry.add('.selector', 'foo:bar', badCallback); + }).toThrow(new Error('Cannot register a command with a null listener.')); + }); it('throws an error when called with a null callback and object target', () => { - const badCallback = null + const badCallback = null; expect(() => { - registry.add(document.body, 'foo:bar', badCallback) - }).toThrow(new Error('Cannot register a command with a null listener.')) - }) + registry.add(document.body, 'foo:bar', badCallback); + }).toThrow(new Error('Cannot register a command with a null listener.')); + }); it('throws an error when called with an object listener without a didDispatch method', () => { const badListener = { title: 'a listener without a didDispatch callback', description: 'this should throw an error' - } + }; expect(() => { - registry.add(document.body, 'foo:bar', badListener) + registry.add(document.body, 'foo:bar', badListener); }).toThrow( new Error( 'Listener must be a callback function or an object with a didDispatch method.' ) - ) - }) - }) + ); + }); + }); describe('::findCommands({target})', () => { it('returns command descriptors that can be invoked on the target or its ancestors', () => { - registry.add('.parent', 'namespace:command-1', () => {}) - registry.add('.child', 'namespace:command-2', () => {}) - registry.add('.grandchild', 'namespace:command-3', () => {}) - registry.add('.grandchild.no-match', 'namespace:command-4', () => {}) + registry.add('.parent', 'namespace:command-1', () => {}); + registry.add('.child', 'namespace:command-2', () => {}); + registry.add('.grandchild', 'namespace:command-3', () => {}); + registry.add('.grandchild.no-match', 'namespace:command-4', () => {}); - registry.add(grandchild, 'namespace:inline-command-1', () => {}) - registry.add(child, 'namespace:inline-command-2', () => {}) + registry.add(grandchild, 'namespace:inline-command-1', () => {}); + registry.add(child, 'namespace:inline-command-2', () => {}); - const commands = registry.findCommands({ target: grandchild }) - const nonJqueryCommands = _.reject(commands, cmd => cmd.jQuery) + const commands = registry.findCommands({ target: grandchild }); + const nonJqueryCommands = _.reject(commands, cmd => cmd.jQuery); expect(nonJqueryCommands).toEqual([ { name: 'namespace:inline-command-1', @@ -288,19 +290,19 @@ describe('CommandRegistry', () => { }, { name: 'namespace:command-2', displayName: 'Namespace: Command 2' }, { name: 'namespace:command-1', displayName: 'Namespace: Command 1' } - ]) - }) + ]); + }); it('returns command descriptors with arbitrary metadata if set in a listener object', () => { - registry.add('.grandchild', 'namespace:command-1', () => {}) + registry.add('.grandchild', 'namespace:command-1', () => {}); registry.add('.grandchild', 'namespace:command-2', { displayName: 'Custom Command 2', metadata: { some: 'other', object: 'data' }, - didDispatch () {} - }) + didDispatch() {} + }); registry.add('.grandchild', 'namespace:command-3', { name: 'some:other:incorrect:commandname', displayName: 'Custom Command 3', @@ -308,10 +310,10 @@ describe('CommandRegistry', () => { some: 'other', object: 'data' }, - didDispatch () {} - }) + didDispatch() {} + }); - const commands = registry.findCommands({ target: grandchild }) + const commands = registry.findCommands({ target: grandchild }); expect(commands).toEqual([ { displayName: 'Namespace: Command 1', @@ -333,19 +335,19 @@ describe('CommandRegistry', () => { }, name: 'namespace:command-3' } - ]) - }) + ]); + }); it('returns command descriptors with arbitrary metadata if set on a listener function', () => { - function listener () {} - listener.displayName = 'Custom Command 2' + function listener() {} + listener.displayName = 'Custom Command 2'; listener.metadata = { some: 'other', object: 'data' - } + }; - registry.add('.grandchild', 'namespace:command-2', listener) - const commands = registry.findCommands({ target: grandchild }) + registry.add('.grandchild', 'namespace:command-2', listener); + const commands = registry.findCommands({ target: grandchild }); expect(commands).toEqual([ { displayName: 'Custom Command 2', @@ -355,86 +357,86 @@ describe('CommandRegistry', () => { }, name: 'namespace:command-2' } - ]) - }) - }) + ]); + }); + }); describe('::dispatch(target, commandName)', () => { it('simulates invocation of the given command ', () => { - let called = false - registry.add('.grandchild', 'command', function (event) { - expect(this).toBe(grandchild) - expect(event.type).toBe('command') - expect(event.eventPhase).toBe(Event.BUBBLING_PHASE) - expect(event.target).toBe(grandchild) - expect(event.currentTarget).toBe(grandchild) - called = true - }) + let called = false; + registry.add('.grandchild', 'command', function(event) { + expect(this).toBe(grandchild); + expect(event.type).toBe('command'); + expect(event.eventPhase).toBe(Event.BUBBLING_PHASE); + expect(event.target).toBe(grandchild); + expect(event.currentTarget).toBe(grandchild); + called = true; + }); - registry.dispatch(grandchild, 'command') - expect(called).toBe(true) - }) + registry.dispatch(grandchild, 'command'); + expect(called).toBe(true); + }); it('returns a promise if any listeners matched the command', () => { - registry.add('.grandchild', 'command', () => {}) + registry.add('.grandchild', 'command', () => {}); expect(registry.dispatch(grandchild, 'command').constructor.name).toBe( 'Promise' - ) - expect(registry.dispatch(grandchild, 'bogus')).toBe(null) - expect(registry.dispatch(parent, 'command')).toBe(null) - }) + ); + expect(registry.dispatch(grandchild, 'bogus')).toBe(null); + expect(registry.dispatch(parent, 'command')).toBe(null); + }); it('returns a promise that resolves when the listeners resolve', async () => { - jasmine.useRealClock() - registry.add('.grandchild', 'command', () => 1) - registry.add('.grandchild', 'command', () => Promise.resolve(2)) + jasmine.useRealClock(); + registry.add('.grandchild', 'command', () => 1); + registry.add('.grandchild', 'command', () => Promise.resolve(2)); registry.add( '.grandchild', 'command', () => new Promise(resolve => { setTimeout(() => { - resolve(3) - }, 1) + resolve(3); + }, 1); }) - ) + ); - const values = await registry.dispatch(grandchild, 'command') - expect(values).toEqual([3, 2, 1]) - }) + const values = await registry.dispatch(grandchild, 'command'); + expect(values).toEqual([3, 2, 1]); + }); it('returns a promise that rejects when a listener is rejected', async () => { - jasmine.useRealClock() - registry.add('.grandchild', 'command', () => 1) - registry.add('.grandchild', 'command', () => Promise.resolve(2)) + jasmine.useRealClock(); + registry.add('.grandchild', 'command', () => 1); + registry.add('.grandchild', 'command', () => Promise.resolve(2)); registry.add( '.grandchild', 'command', () => new Promise((resolve, reject) => { setTimeout(() => { - reject(3) // eslint-disable-line prefer-promise-reject-errors - }, 1) + reject(3); // eslint-disable-line prefer-promise-reject-errors + }, 1); }) - ) + ); - let value + let value; try { - value = await registry.dispatch(grandchild, 'command') + value = await registry.dispatch(grandchild, 'command'); } catch (err) { - value = err + value = err; } - expect(value).toBe(3) - }) - }) + expect(value).toBe(3); + }); + }); describe('::getSnapshot and ::restoreSnapshot', () => it('removes all command handlers except for those in the snapshot', () => { - registry.add('.parent', 'namespace:command-1', () => {}) - registry.add('.child', 'namespace:command-2', () => {}) - const snapshot = registry.getSnapshot() - registry.add('.grandchild', 'namespace:command-3', () => {}) + registry.add('.parent', 'namespace:command-1', () => {}); + registry.add('.child', 'namespace:command-2', () => {}); + const snapshot = registry.getSnapshot(); + registry.add('.grandchild', 'namespace:command-3', () => {}); expect(registry.findCommands({ target: grandchild }).slice(0, 3)).toEqual( [ @@ -442,41 +444,41 @@ describe('CommandRegistry', () => { { name: 'namespace:command-2', displayName: 'Namespace: Command 2' }, { name: 'namespace:command-1', displayName: 'Namespace: Command 1' } ] - ) + ); - registry.restoreSnapshot(snapshot) + registry.restoreSnapshot(snapshot); expect(registry.findCommands({ target: grandchild }).slice(0, 2)).toEqual( [ { name: 'namespace:command-2', displayName: 'Namespace: Command 2' }, { name: 'namespace:command-1', displayName: 'Namespace: Command 1' } ] - ) + ); - registry.add('.grandchild', 'namespace:command-3', () => {}) - registry.restoreSnapshot(snapshot) + registry.add('.grandchild', 'namespace:command-3', () => {}); + registry.restoreSnapshot(snapshot); expect(registry.findCommands({ target: grandchild }).slice(0, 2)).toEqual( [ { name: 'namespace:command-2', displayName: 'Namespace: Command 2' }, { name: 'namespace:command-1', displayName: 'Namespace: Command 1' } ] - ) - })) + ); + })); describe('::attach(rootNode)', () => it('adds event listeners for any previously-added commands', () => { - const registry2 = new CommandRegistry() + const registry2 = new CommandRegistry(); - const commandSpy = jasmine.createSpy('command-callback') - registry2.add('.grandchild', 'command-1', commandSpy) + const commandSpy = jasmine.createSpy('command-callback'); + registry2.add('.grandchild', 'command-1', commandSpy); - grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })) - expect(commandSpy).not.toHaveBeenCalled() + grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })); + expect(commandSpy).not.toHaveBeenCalled(); - registry2.attach(parent) + registry2.attach(parent); - grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })) - expect(commandSpy).toHaveBeenCalled() - })) -}) + grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true })); + expect(commandSpy).toHaveBeenCalled(); + })); +}); diff --git a/spec/config-file-spec.js b/spec/config-file-spec.js index d5ae98170..bc229ed38 100644 --- a/spec/config-file-spec.js +++ b/spec/config-file-spec.js @@ -1,45 +1,45 @@ -const fs = require('fs-plus') -const path = require('path') -const temp = require('temp').track() -const dedent = require('dedent') -const ConfigFile = require('../src/config-file') +const fs = require('fs-plus'); +const path = require('path'); +const temp = require('temp').track(); +const dedent = require('dedent'); +const ConfigFile = require('../src/config-file'); describe('ConfigFile', () => { - let filePath, configFile, subscription + let filePath, configFile, subscription; beforeEach(async () => { - jasmine.useRealClock() - const tempDir = fs.realpathSync(temp.mkdirSync()) - filePath = path.join(tempDir, 'the-config.cson') - }) + jasmine.useRealClock(); + const tempDir = fs.realpathSync(temp.mkdirSync()); + filePath = path.join(tempDir, 'the-config.cson'); + }); afterEach(() => { - subscription.dispose() - }) + subscription.dispose(); + }); describe('when the file does not exist', () => { it('returns an empty object from .get()', async () => { - configFile = new ConfigFile(filePath) - subscription = await configFile.watch() - expect(configFile.get()).toEqual({}) - }) - }) + configFile = new ConfigFile(filePath); + subscription = await configFile.watch(); + expect(configFile.get()).toEqual({}); + }); + }); describe('when the file is empty', () => { it('returns an empty object from .get()', async () => { - writeFileSync(filePath, '') - configFile = new ConfigFile(filePath) - subscription = await configFile.watch() - expect(configFile.get()).toEqual({}) - }) - }) + writeFileSync(filePath, ''); + configFile = new ConfigFile(filePath); + subscription = await configFile.watch(); + expect(configFile.get()).toEqual({}); + }); + }); describe('when the file is updated with valid CSON', () => { it('notifies onDidChange observers with the data', async () => { - configFile = new ConfigFile(filePath) - subscription = await configFile.watch() + configFile = new ConfigFile(filePath); + subscription = await configFile.watch(); - const event = new Promise(resolve => configFile.onDidChange(resolve)) + const event = new Promise(resolve => configFile.onDidChange(resolve)); writeFileSync( filePath, @@ -50,26 +50,26 @@ describe('ConfigFile', () => { 'javascript': foo: 'baz' ` - ) + ); expect(await event).toEqual({ '*': { foo: 'bar' }, javascript: { foo: 'baz' } - }) + }); expect(configFile.get()).toEqual({ '*': { foo: 'bar' }, javascript: { foo: 'baz' } - }) - }) - }) + }); + }); + }); describe('when the file is updated with invalid CSON', () => { it('notifies onDidError observers', async () => { - configFile = new ConfigFile(filePath) - subscription = await configFile.watch() + configFile = new ConfigFile(filePath); + subscription = await configFile.watch(); - const message = new Promise(resolve => configFile.onDidError(resolve)) + const message = new Promise(resolve => configFile.onDidError(resolve)); writeFileSync( filePath, @@ -77,11 +77,11 @@ describe('ConfigFile', () => { um what? `, 2 - ) + ); - expect(await message).toContain('Failed to load `the-config.cson`') + expect(await message).toContain('Failed to load `the-config.cson`'); - const event = new Promise(resolve => configFile.onDidChange(resolve)) + const event = new Promise(resolve => configFile.onDidChange(resolve)); writeFileSync( filePath, @@ -93,39 +93,39 @@ describe('ConfigFile', () => { foo: 'baz' `, 4 - ) + ); expect(await event).toEqual({ '*': { foo: 'bar' }, javascript: { foo: 'baz' } - }) - }) - }) + }); + }); + }); describe('ConfigFile.at()', () => { - let path0, path1 + let path0, path1; beforeEach(() => { - path0 = filePath - path1 = path.join(fs.realpathSync(temp.mkdirSync()), 'the-config.cson') + path0 = filePath; + path1 = path.join(fs.realpathSync(temp.mkdirSync()), 'the-config.cson'); - configFile = ConfigFile.at(path0) - }) + configFile = ConfigFile.at(path0); + }); it('returns an existing ConfigFile', () => { - const cf = ConfigFile.at(path0) - expect(cf).toEqual(configFile) - }) + const cf = ConfigFile.at(path0); + expect(cf).toEqual(configFile); + }); it('creates a new ConfigFile for unrecognized paths', () => { - const cf = ConfigFile.at(path1) - expect(cf).not.toEqual(configFile) - }) - }) -}) + const cf = ConfigFile.at(path1); + expect(cf).not.toEqual(configFile); + }); + }); +}); -function writeFileSync (filePath, content, seconds = 2) { - const utime = Date.now() / 1000 + seconds - fs.writeFileSync(filePath, content) - fs.utimesSync(filePath, utime, utime) +function writeFileSync(filePath, content, seconds = 2) { + const utime = Date.now() / 1000 + seconds; + fs.writeFileSync(filePath, content); + fs.utimesSync(filePath, utime, utime); } diff --git a/spec/config-spec.js b/spec/config-spec.js index 3d4ed7d57..997f973a2 100644 --- a/spec/config-spec.js +++ b/spec/config-spec.js @@ -1,259 +1,259 @@ describe('Config', () => { - let savedSettings + let savedSettings; beforeEach(() => { - spyOn(console, 'warn') - atom.config.settingsLoaded = true + spyOn(console, 'warn'); + atom.config.settingsLoaded = true; - savedSettings = [] - atom.config.saveCallback = function (settings) { - savedSettings.push(settings) - } - }) + savedSettings = []; + atom.config.saveCallback = function(settings) { + savedSettings.push(settings); + }; + }); describe('.get(keyPath, {scope, sources, excludeSources})', () => { it("allows a key path's value to be read", () => { - expect(atom.config.set('foo.bar.baz', 42)).toBe(true) - expect(atom.config.get('foo.bar.baz')).toBe(42) - expect(atom.config.get('foo.quux')).toBeUndefined() - }) + expect(atom.config.set('foo.bar.baz', 42)).toBe(true); + expect(atom.config.get('foo.bar.baz')).toBe(42); + expect(atom.config.get('foo.quux')).toBeUndefined(); + }); it("returns a deep clone of the key path's value", () => { - atom.config.set('value', { array: [1, { b: 2 }, 3] }) - const retrievedValue = atom.config.get('value') - retrievedValue.array[0] = 4 - retrievedValue.array[1].b = 2.1 - expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] }) - }) + atom.config.set('value', { array: [1, { b: 2 }, 3] }); + const retrievedValue = atom.config.get('value'); + retrievedValue.array[0] = 4; + retrievedValue.array[1].b = 2.1; + expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] }); + }); it('merges defaults into the returned value if both the assigned value and the default value are objects', () => { - atom.config.setDefaults('foo.bar', { baz: 1, ok: 2 }) - atom.config.set('foo.bar', { baz: 3 }) - expect(atom.config.get('foo.bar')).toEqual({ baz: 3, ok: 2 }) + atom.config.setDefaults('foo.bar', { baz: 1, ok: 2 }); + atom.config.set('foo.bar', { baz: 3 }); + expect(atom.config.get('foo.bar')).toEqual({ baz: 3, ok: 2 }); - atom.config.setDefaults('other', { baz: 1 }) - atom.config.set('other', 7) - expect(atom.config.get('other')).toBe(7) + atom.config.setDefaults('other', { baz: 1 }); + atom.config.set('other', 7); + expect(atom.config.get('other')).toBe(7); - atom.config.set('bar.baz', { a: 3 }) - atom.config.setDefaults('bar', { baz: 7 }) - expect(atom.config.get('bar.baz')).toEqual({ a: 3 }) - }) + atom.config.set('bar.baz', { a: 3 }); + atom.config.setDefaults('bar', { baz: 7 }); + expect(atom.config.get('bar.baz')).toEqual({ a: 3 }); + }); describe("when a 'sources' option is specified", () => it('only retrieves values from the specified sources', () => { - atom.config.set('x.y', 1, { scopeSelector: '.foo', source: 'a' }) - atom.config.set('x.y', 2, { scopeSelector: '.foo', source: 'b' }) - atom.config.set('x.y', 3, { scopeSelector: '.foo', source: 'c' }) - atom.config.setSchema('x.y', { type: 'integer', default: 4 }) + atom.config.set('x.y', 1, { scopeSelector: '.foo', source: 'a' }); + atom.config.set('x.y', 2, { scopeSelector: '.foo', source: 'b' }); + atom.config.set('x.y', 3, { scopeSelector: '.foo', source: 'c' }); + atom.config.setSchema('x.y', { type: 'integer', default: 4 }); expect( atom.config.get('x.y', { sources: ['a'], scope: ['.foo'] }) - ).toBe(1) + ).toBe(1); expect( atom.config.get('x.y', { sources: ['b'], scope: ['.foo'] }) - ).toBe(2) + ).toBe(2); expect( atom.config.get('x.y', { sources: ['c'], scope: ['.foo'] }) - ).toBe(3) + ).toBe(3); // Schema defaults never match a specific source. We could potentially add a special "schema" source. expect( atom.config.get('x.y', { sources: ['x'], scope: ['.foo'] }) - ).toBeUndefined() + ).toBeUndefined(); expect( atom.config.get(null, { sources: ['a'], scope: ['.foo'] }).x.y - ).toBe(1) - })) + ).toBe(1); + })); describe("when an 'excludeSources' option is specified", () => it('only retrieves values from the specified sources', () => { - atom.config.set('x.y', 0) - atom.config.set('x.y', 1, { scopeSelector: '.foo', source: 'a' }) - atom.config.set('x.y', 2, { scopeSelector: '.foo', source: 'b' }) - atom.config.set('x.y', 3, { scopeSelector: '.foo', source: 'c' }) - atom.config.setSchema('x.y', { type: 'integer', default: 4 }) + atom.config.set('x.y', 0); + atom.config.set('x.y', 1, { scopeSelector: '.foo', source: 'a' }); + atom.config.set('x.y', 2, { scopeSelector: '.foo', source: 'b' }); + atom.config.set('x.y', 3, { scopeSelector: '.foo', source: 'c' }); + atom.config.setSchema('x.y', { type: 'integer', default: 4 }); expect( atom.config.get('x.y', { excludeSources: ['a'], scope: ['.foo'] }) - ).toBe(3) + ).toBe(3); expect( atom.config.get('x.y', { excludeSources: ['c'], scope: ['.foo'] }) - ).toBe(2) + ).toBe(2); expect( atom.config.get('x.y', { excludeSources: ['b', 'c'], scope: ['.foo'] }) - ).toBe(1) + ).toBe(1); expect( atom.config.get('x.y', { excludeSources: ['b', 'c', 'a'], scope: ['.foo'] }) - ).toBe(0) + ).toBe(0); expect( atom.config.get('x.y', { excludeSources: ['b', 'c', 'a', atom.config.getUserConfigPath()], scope: ['.foo'] }) - ).toBe(4) + ).toBe(4); expect( atom.config.get('x.y', { excludeSources: [atom.config.getUserConfigPath()] }) - ).toBe(4) - })) + ).toBe(4); + })); describe("when a 'scope' option is given", () => { it('returns the property with the most specific scope selector', () => { atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.double.coffee' - }) + }); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source .string.quoted.double' - }) - atom.config.set('foo.bar.baz', 11, { scopeSelector: '.source' }) + }); + atom.config.set('foo.bar.baz', 11, { scopeSelector: '.source' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.double.coffee'] }) - ).toBe(42) + ).toBe(42); expect( atom.config.get('foo.bar.baz', { scope: ['.source.js', '.string.quoted.double.js'] }) - ).toBe(22) + ).toBe(22); expect( atom.config.get('foo.bar.baz', { scope: ['.source.js', '.variable.assignment.js'] }) - ).toBe(11) + ).toBe(11); expect( atom.config.get('foo.bar.baz', { scope: ['.text'] }) - ).toBeUndefined() - }) + ).toBeUndefined(); + }); it('favors the most recently added properties in the event of a specificity tie', () => { atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.single' - }) + }); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source.coffee .string.quoted.double' - }) + }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.single'] }) - ).toBe(42) + ).toBe(42); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.single.double'] }) - ).toBe(22) - }) + ).toBe(22); + }); describe('when there are global defaults', () => it('falls back to the global when there is no scoped property specified', () => { - atom.config.setDefaults('foo', { hasDefault: 'ok' }) + atom.config.setDefaults('foo', { hasDefault: 'ok' }); expect( atom.config.get('foo.hasDefault', { scope: ['.source.coffee', '.string.quoted.single'] }) - ).toBe('ok') - })) + ).toBe('ok'); + })); describe('when package settings are added after user settings', () => it("returns the user's setting because the user's setting has higher priority", () => { atom.config.set('foo.bar.baz', 100, { scopeSelector: '.source.coffee' - }) + }); atom.config.set('foo.bar.baz', 1, { scopeSelector: '.source.coffee', source: 'some-package' - }) + }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(100) - })) - }) - }) + ).toBe(100); + })); + }); + }); describe('.getAll(keyPath, {scope, sources, excludeSources})', () => { it('reads all of the values for a given key-path', () => { - expect(atom.config.set('foo', 41)).toBe(true) - expect(atom.config.set('foo', 43, { scopeSelector: '.a .b' })).toBe(true) - expect(atom.config.set('foo', 42, { scopeSelector: '.a' })).toBe(true) + expect(atom.config.set('foo', 41)).toBe(true); + expect(atom.config.set('foo', 43, { scopeSelector: '.a .b' })).toBe(true); + expect(atom.config.set('foo', 42, { scopeSelector: '.a' })).toBe(true); expect(atom.config.set('foo', 44, { scopeSelector: '.a .b.c' })).toBe( true - ) + ); - expect(atom.config.set('foo', -44, { scopeSelector: '.d' })).toBe(true) + expect(atom.config.set('foo', -44, { scopeSelector: '.d' })).toBe(true); expect(atom.config.getAll('foo', { scope: ['.a', '.b.c'] })).toEqual([ { scopeSelector: '.a .b.c', value: 44 }, { scopeSelector: '.a .b', value: 43 }, { scopeSelector: '.a', value: 42 }, { scopeSelector: '*', value: 41 } - ]) - }) + ]); + }); it("includes the schema's default value", () => { - atom.config.setSchema('foo', { type: 'number', default: 40 }) - expect(atom.config.set('foo', 43, { scopeSelector: '.a .b' })).toBe(true) + atom.config.setSchema('foo', { type: 'number', default: 40 }); + expect(atom.config.set('foo', 43, { scopeSelector: '.a .b' })).toBe(true); expect(atom.config.getAll('foo', { scope: ['.a', '.b.c'] })).toEqual([ { scopeSelector: '.a .b', value: 43 }, { scopeSelector: '*', value: 40 } - ]) - }) - }) + ]); + }); + }); describe('.set(keyPath, value, {source, scopeSelector})', () => { it("allows a key path's value to be written", () => { - expect(atom.config.set('foo.bar.baz', 42)).toBe(true) - expect(atom.config.get('foo.bar.baz')).toBe(42) - }) + expect(atom.config.set('foo.bar.baz', 42)).toBe(true); + expect(atom.config.get('foo.bar.baz')).toBe(42); + }); it("saves the user's config to disk after it stops changing", () => { - atom.config.set('foo.bar.baz', 42) - expect(savedSettings.length).toBe(0) - atom.config.set('foo.bar.baz', 43) - expect(savedSettings.length).toBe(0) - atom.config.set('foo.bar.baz', 44) - advanceClock(10) - expect(savedSettings.length).toBe(1) - }) + atom.config.set('foo.bar.baz', 42); + expect(savedSettings.length).toBe(0); + atom.config.set('foo.bar.baz', 43); + expect(savedSettings.length).toBe(0); + atom.config.set('foo.bar.baz', 44); + advanceClock(10); + expect(savedSettings.length).toBe(1); + }); it("does not save when a non-default 'source' is given", () => { atom.config.set('foo.bar.baz', 42, { source: 'some-other-source', scopeSelector: '.a' - }) - advanceClock(500) - expect(savedSettings.length).toBe(0) - }) + }); + advanceClock(500); + expect(savedSettings.length).toBe(0); + }); it("does not allow a 'source' option without a 'scopeSelector'", () => { expect(() => atom.config.set('foo', 1, { source: ['.source.ruby'] }) - ).toThrow() - }) + ).toThrow(); + }); describe('when the key-path is null', () => it('sets the root object', () => { - expect(atom.config.set(null, { editor: { tabLength: 6 } })).toBe(true) - expect(atom.config.get('editor.tabLength')).toBe(6) + expect(atom.config.set(null, { editor: { tabLength: 6 } })).toBe(true); + expect(atom.config.get('editor.tabLength')).toBe(6); expect( atom.config.set(null, { editor: { tabLength: 8, scopeSelector: ['.source.js'] } }) - ).toBe(true) + ).toBe(true); expect( atom.config.get('editor.tabLength', { scope: ['.source.js'] }) - ).toBe(8) - })) + ).toBe(8); + })); describe('when the value equals the default value', () => it("does not store the value in the user's config", () => { @@ -285,61 +285,61 @@ describe('Config', () => { default: undefined } } - }) - expect(atom.config.settings.foo).toBeUndefined() + }); + expect(atom.config.settings.foo).toBeUndefined(); - atom.config.set('foo.same', 1) - atom.config.set('foo.changes', 2) - atom.config.set('foo.sameArray', [1, 2, 3]) - atom.config.set('foo.null', undefined) - atom.config.set('foo.undefined', null) - atom.config.set('foo.sameObject', { b: 2, a: 1 }) + atom.config.set('foo.same', 1); + atom.config.set('foo.changes', 2); + atom.config.set('foo.sameArray', [1, 2, 3]); + atom.config.set('foo.null', undefined); + atom.config.set('foo.undefined', null); + atom.config.set('foo.sameObject', { b: 2, a: 1 }); - const userConfigPath = atom.config.getUserConfigPath() + const userConfigPath = atom.config.getUserConfigPath(); expect( atom.config.get('foo.same', { sources: [userConfigPath] }) - ).toBeUndefined() + ).toBeUndefined(); - expect(atom.config.get('foo.changes')).toBe(2) + expect(atom.config.get('foo.changes')).toBe(2); expect( atom.config.get('foo.changes', { sources: [userConfigPath] }) - ).toBe(2) + ).toBe(2); - atom.config.set('foo.changes', 1) + atom.config.set('foo.changes', 1); expect( atom.config.get('foo.changes', { sources: [userConfigPath] }) - ).toBeUndefined() - })) + ).toBeUndefined(); + })); describe("when a 'scopeSelector' is given", () => it('sets the value and overrides the others', () => { atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.double.coffee' - }) + }); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source .string.quoted.double' - }) - atom.config.set('foo.bar.baz', 11, { scopeSelector: '.source' }) + }); + atom.config.set('foo.bar.baz', 11, { scopeSelector: '.source' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.double.coffee'] }) - ).toBe(42) + ).toBe(42); expect( atom.config.set('foo.bar.baz', 100, { scopeSelector: '.source.coffee .string.quoted.double.coffee' }) - ).toBe(true) + ).toBe(true); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.double.coffee'] }) - ).toBe(100) - })) - }) + ).toBe(100); + })); + }); describe('.unset(keyPath, {source, scopeSelector})', () => { beforeEach(() => @@ -365,30 +365,30 @@ describe('Config', () => { } } }) - ) + ); it('sets the value of the key path to its default', () => { - atom.config.setDefaults('a', { b: 3 }) - atom.config.set('a.b', 4) - expect(atom.config.get('a.b')).toBe(4) - atom.config.unset('a.b') - expect(atom.config.get('a.b')).toBe(3) + atom.config.setDefaults('a', { b: 3 }); + atom.config.set('a.b', 4); + expect(atom.config.get('a.b')).toBe(4); + atom.config.unset('a.b'); + expect(atom.config.get('a.b')).toBe(3); - atom.config.set('a.c', 5) - expect(atom.config.get('a.c')).toBe(5) - atom.config.unset('a.c') - expect(atom.config.get('a.c')).toBeUndefined() - }) + atom.config.set('a.c', 5); + expect(atom.config.get('a.c')).toBe(5); + atom.config.unset('a.c'); + expect(atom.config.get('a.c')).toBeUndefined(); + }); it('calls ::save()', () => { - atom.config.setDefaults('a', { b: 3 }) - atom.config.set('a.b', 4) - savedSettings.length = 0 + atom.config.setDefaults('a', { b: 3 }); + atom.config.set('a.b', 4); + savedSettings.length = 0; - atom.config.unset('a.c') - advanceClock(500) - expect(savedSettings.length).toBe(1) - }) + atom.config.unset('a.c'); + advanceClock(500); + expect(savedSettings.length).toBe(1); + }); describe("when no 'scopeSelector' is given", () => { describe("when a 'source' but no key-path is given", () => @@ -396,683 +396,694 @@ describe('Config', () => { atom.config.set('foo.bar.baz', 1, { scopeSelector: '.a', source: 'source-a' - }) + }); atom.config.set('foo.bar.quux', 2, { scopeSelector: '.b', source: 'source-a' - }) + }); expect(atom.config.get('foo.bar', { scope: ['.a.b'] })).toEqual({ baz: 1, quux: 2 - }) + }); - atom.config.unset(null, { source: 'source-a' }) + atom.config.unset(null, { source: 'source-a' }); expect(atom.config.get('foo.bar', { scope: ['.a'] })).toEqual({ baz: 0, ok: 0 - }) - })) + }); + })); describe("when a 'source' and a key-path is given", () => it('removes all scoped settings with the given source and key-path', () => { - atom.config.set('foo.bar.baz', 1) + atom.config.set('foo.bar.baz', 1); atom.config.set('foo.bar.baz', 2, { scopeSelector: '.a', source: 'source-a' - }) + }); atom.config.set('foo.bar.baz', 3, { scopeSelector: '.a.b', source: 'source-b' - }) - expect(atom.config.get('foo.bar.baz', { scope: ['.a.b'] })).toEqual(3) + }); + expect(atom.config.get('foo.bar.baz', { scope: ['.a.b'] })).toEqual( + 3 + ); - atom.config.unset('foo.bar.baz', { source: 'source-b' }) - expect(atom.config.get('foo.bar.baz', { scope: ['.a.b'] })).toEqual(2) - expect(atom.config.get('foo.bar.baz')).toEqual(1) - })) + atom.config.unset('foo.bar.baz', { source: 'source-b' }); + expect(atom.config.get('foo.bar.baz', { scope: ['.a.b'] })).toEqual( + 2 + ); + expect(atom.config.get('foo.bar.baz')).toEqual(1); + })); describe("when no 'source' is given", () => it('removes all scoped and unscoped properties for that key-path', () => { - atom.config.setDefaults('foo.bar', { baz: 100 }) + atom.config.setDefaults('foo.bar', { baz: 100 }); - atom.config.set('foo.bar', { baz: 1, ok: 2 }, { scopeSelector: '.a' }) + atom.config.set( + 'foo.bar', + { baz: 1, ok: 2 }, + { scopeSelector: '.a' } + ); atom.config.set( 'foo.bar', { baz: 11, ok: 12 }, { scopeSelector: '.b' } - ) - atom.config.set('foo.bar', { baz: 21, ok: 22 }) + ); + atom.config.set('foo.bar', { baz: 21, ok: 22 }); - atom.config.unset('foo.bar.baz') + atom.config.unset('foo.bar.baz'); - expect(atom.config.get('foo.bar.baz', { scope: ['.a'] })).toBe(100) - expect(atom.config.get('foo.bar.baz', { scope: ['.b'] })).toBe(100) - expect(atom.config.get('foo.bar.baz')).toBe(100) + expect(atom.config.get('foo.bar.baz', { scope: ['.a'] })).toBe(100); + expect(atom.config.get('foo.bar.baz', { scope: ['.b'] })).toBe(100); + expect(atom.config.get('foo.bar.baz')).toBe(100); - expect(atom.config.get('foo.bar.ok', { scope: ['.a'] })).toBe(2) - expect(atom.config.get('foo.bar.ok', { scope: ['.b'] })).toBe(12) - expect(atom.config.get('foo.bar.ok')).toBe(22) - })) - }) + expect(atom.config.get('foo.bar.ok', { scope: ['.a'] })).toBe(2); + expect(atom.config.get('foo.bar.ok', { scope: ['.b'] })).toBe(12); + expect(atom.config.get('foo.bar.ok')).toBe(22); + })); + }); describe("when a 'scopeSelector' is given", () => { it('restores the global default when no scoped default set', () => { - atom.config.setDefaults('foo', { bar: { baz: 10 } }) - atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }) + atom.config.setDefaults('foo', { bar: { baz: 10 } }); + atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(55) + ).toBe(55); - atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }) + atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(10) - }) + ).toBe(10); + }); it('restores the scoped default when a scoped default is set', () => { - atom.config.setDefaults('foo', { bar: { baz: 10 } }) + atom.config.setDefaults('foo', { bar: { baz: 10 } }); atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee', source: 'some-source' - }) - atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }) - atom.config.set('foo.bar.ok', 100, { scopeSelector: '.source.coffee' }) + }); + atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }); + atom.config.set('foo.bar.ok', 100, { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(55) + ).toBe(55); - atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }) + atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(42) + ).toBe(42); expect( atom.config.get('foo.bar.ok', { scope: ['.source.coffee'] }) - ).toBe(100) - }) + ).toBe(100); + }); it('calls ::save()', () => { - atom.config.setDefaults('foo', { bar: { baz: 10 } }) - atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }) - savedSettings.length = 0 + atom.config.setDefaults('foo', { bar: { baz: 10 } }); + atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }); + savedSettings.length = 0; - atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }) - advanceClock(150) - expect(savedSettings.length).toBe(1) - }) + atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }); + advanceClock(150); + expect(savedSettings.length).toBe(1); + }); it('allows removing settings for a specific source and scope selector', () => { atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee', source: 'source-a' - }) + }); atom.config.set('foo.bar.baz', 65, { scopeSelector: '.source.coffee', source: 'source-b' - }) + }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(65) + ).toBe(65); atom.config.unset('foo.bar.baz', { source: 'source-b', scopeSelector: '.source.coffee' - }) + }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string'] }) - ).toBe(55) - }) + ).toBe(55); + }); it('allows removing all settings for a specific source', () => { atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee', source: 'source-a' - }) + }); atom.config.set('foo.bar.baz', 65, { scopeSelector: '.source.coffee', source: 'source-b' - }) + }); atom.config.set('foo.bar.ok', 65, { scopeSelector: '.source.coffee', source: 'source-b' - }) + }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(65) + ).toBe(65); atom.config.unset(null, { source: 'source-b', scopeSelector: '.source.coffee' - }) + }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee', '.string'] }) - ).toBe(55) + ).toBe(55); expect( atom.config.get('foo.bar.ok', { scope: ['.source.coffee', '.string'] }) - ).toBe(0) - }) + ).toBe(0); + }); it('does not call ::save or add a scoped property when no value has been set', () => { // see https://github.com/atom/atom/issues/4175 - atom.config.setDefaults('foo', { bar: { baz: 10 } }) - atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }) + atom.config.setDefaults('foo', { bar: { baz: 10 } }); + atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(10) + ).toBe(10); - expect(savedSettings.length).toBe(0) + expect(savedSettings.length).toBe(0); const scopedProperties = atom.config.scopedSettingsStore.propertiesForSource( 'user-config' - ) - expect(scopedProperties['.coffee.source']).toBeUndefined() - }) + ); + expect(scopedProperties['.coffee.source']).toBeUndefined(); + }); it('removes the scoped value when it was the only set value on the object', () => { - atom.config.setDefaults('foo', { bar: { baz: 10 } }) - atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }) - atom.config.set('foo.bar.ok', 20, { scopeSelector: '.source.coffee' }) + atom.config.setDefaults('foo', { bar: { baz: 10 } }); + atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' }); + atom.config.set('foo.bar.ok', 20, { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(55) + ).toBe(55); - advanceClock(150) - savedSettings.length = 0 + advanceClock(150); + savedSettings.length = 0; - atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }) + atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' }); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(10) + ).toBe(10); expect( atom.config.get('foo.bar.ok', { scope: ['.source.coffee'] }) - ).toBe(20) + ).toBe(20); - advanceClock(150) + advanceClock(150); expect(savedSettings[0]['.coffee.source']).toEqual({ foo: { bar: { ok: 20 } } - }) + }); - atom.config.unset('foo.bar.ok', { scopeSelector: '.source.coffee' }) + atom.config.unset('foo.bar.ok', { scopeSelector: '.source.coffee' }); - advanceClock(150) - expect(savedSettings.length).toBe(2) - expect(savedSettings[1]['.coffee.source']).toBeUndefined() - }) + advanceClock(150); + expect(savedSettings.length).toBe(2); + expect(savedSettings[1]['.coffee.source']).toBeUndefined(); + }); it('does not call ::save when the value is already at the default', () => { - atom.config.setDefaults('foo', { bar: { baz: 10 } }) - atom.config.set('foo.bar.baz', 55) - advanceClock(150) - savedSettings.length = 0 + atom.config.setDefaults('foo', { bar: { baz: 10 } }); + atom.config.set('foo.bar.baz', 55); + advanceClock(150); + savedSettings.length = 0; - atom.config.unset('foo.bar.ok', { scopeSelector: '.source.coffee' }) - advanceClock(150) - expect(savedSettings.length).toBe(0) + atom.config.unset('foo.bar.ok', { scopeSelector: '.source.coffee' }); + advanceClock(150); + expect(savedSettings.length).toBe(0); expect( atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] }) - ).toBe(55) - }) - }) - }) + ).toBe(55); + }); + }); + }); describe('.onDidChange(keyPath, {scope})', () => { - let observeHandler = [] + let observeHandler = []; describe('when a keyPath is specified', () => { beforeEach(() => { - observeHandler = jasmine.createSpy('observeHandler') - atom.config.set('foo.bar.baz', 'value 1') - atom.config.onDidChange('foo.bar.baz', observeHandler) - }) + observeHandler = jasmine.createSpy('observeHandler'); + atom.config.set('foo.bar.baz', 'value 1'); + atom.config.onDidChange('foo.bar.baz', observeHandler); + }); it('does not fire the given callback with the current value at the keypath', () => - expect(observeHandler).not.toHaveBeenCalled()) + expect(observeHandler).not.toHaveBeenCalled()); it('fires the callback every time the observed value changes', () => { - atom.config.set('foo.bar.baz', 'value 2') + atom.config.set('foo.bar.baz', 'value 2'); expect(observeHandler).toHaveBeenCalledWith({ newValue: 'value 2', oldValue: 'value 1' - }) - observeHandler.reset() + }); + observeHandler.reset(); observeHandler.andCallFake(() => { - throw new Error('oops') - }) - expect(() => atom.config.set('foo.bar.baz', 'value 1')).toThrow('oops') + throw new Error('oops'); + }); + expect(() => atom.config.set('foo.bar.baz', 'value 1')).toThrow('oops'); expect(observeHandler).toHaveBeenCalledWith({ newValue: 'value 1', oldValue: 'value 2' - }) - observeHandler.reset() + }); + observeHandler.reset(); // Regression: exception in earlier handler shouldn't put observer // into a bad state. - atom.config.set('something.else', 'new value') - expect(observeHandler).not.toHaveBeenCalled() - }) - }) + atom.config.set('something.else', 'new value'); + expect(observeHandler).not.toHaveBeenCalled(); + }); + }); describe('when a keyPath is not specified', () => { beforeEach(() => { - observeHandler = jasmine.createSpy('observeHandler') - atom.config.set('foo.bar.baz', 'value 1') - atom.config.onDidChange(observeHandler) - }) + observeHandler = jasmine.createSpy('observeHandler'); + atom.config.set('foo.bar.baz', 'value 1'); + atom.config.onDidChange(observeHandler); + }); it('does not fire the given callback initially', () => - expect(observeHandler).not.toHaveBeenCalled()) + expect(observeHandler).not.toHaveBeenCalled()); it('fires the callback every time any value changes', () => { - observeHandler.reset() // clear the initial call - atom.config.set('foo.bar.baz', 'value 2') - expect(observeHandler).toHaveBeenCalled() + observeHandler.reset(); // clear the initial call + atom.config.set('foo.bar.baz', 'value 2'); + expect(observeHandler).toHaveBeenCalled(); expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe( 'value 2' - ) + ); expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe( 'value 1' - ) + ); - observeHandler.reset() - atom.config.set('foo.bar.baz', 'value 1') - expect(observeHandler).toHaveBeenCalled() + observeHandler.reset(); + atom.config.set('foo.bar.baz', 'value 1'); + expect(observeHandler).toHaveBeenCalled(); expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe( 'value 1' - ) + ); expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe( 'value 2' - ) + ); - observeHandler.reset() - atom.config.set('foo.bar.int', 1) - expect(observeHandler).toHaveBeenCalled() + observeHandler.reset(); + atom.config.set('foo.bar.int', 1); + expect(observeHandler).toHaveBeenCalled(); expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.int).toBe( 1 - ) + ); expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.int).toBe( undefined - ) - }) - }) + ); + }); + }); describe("when a 'scope' is given", () => it('calls the supplied callback when the value at the descriptor/keypath changes', () => { - const changeSpy = jasmine.createSpy('onDidChange callback') + const changeSpy = jasmine.createSpy('onDidChange callback'); atom.config.onDidChange( 'foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.double.coffee'] }, changeSpy - ) + ); - atom.config.set('foo.bar.baz', 12) + atom.config.set('foo.bar.baz', 12); expect(changeSpy).toHaveBeenCalledWith({ oldValue: undefined, newValue: 12 - }) - changeSpy.reset() + }); + changeSpy.reset(); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source .string.quoted.double', source: 'a' - }) - expect(changeSpy).toHaveBeenCalledWith({ oldValue: 12, newValue: 22 }) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith({ oldValue: 12, newValue: 22 }); + changeSpy.reset(); atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' - }) - expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 42 }) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 42 }); + changeSpy.reset(); atom.config.unset(null, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' - }) - expect(changeSpy).toHaveBeenCalledWith({ oldValue: 42, newValue: 22 }) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith({ oldValue: 42, newValue: 22 }); + changeSpy.reset(); atom.config.unset(null, { scopeSelector: '.source .string.quoted.double', source: 'a' - }) - expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 12 }) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 12 }); + changeSpy.reset(); - atom.config.set('foo.bar.baz', undefined) + atom.config.set('foo.bar.baz', undefined); expect(changeSpy).toHaveBeenCalledWith({ oldValue: 12, newValue: undefined - }) - changeSpy.reset() - })) - }) + }); + changeSpy.reset(); + })); + }); describe('.observe(keyPath, {scope})', () => { - let [observeHandler, observeSubscription] = [] + let [observeHandler, observeSubscription] = []; beforeEach(() => { - observeHandler = jasmine.createSpy('observeHandler') - atom.config.set('foo.bar.baz', 'value 1') - observeSubscription = atom.config.observe('foo.bar.baz', observeHandler) - }) + observeHandler = jasmine.createSpy('observeHandler'); + atom.config.set('foo.bar.baz', 'value 1'); + observeSubscription = atom.config.observe('foo.bar.baz', observeHandler); + }); it('fires the given callback with the current value at the keypath', () => - expect(observeHandler).toHaveBeenCalledWith('value 1')) + expect(observeHandler).toHaveBeenCalledWith('value 1')); it('fires the callback every time the observed value changes', () => { - observeHandler.reset() // clear the initial call - atom.config.set('foo.bar.baz', 'value 2') - expect(observeHandler).toHaveBeenCalledWith('value 2') + observeHandler.reset(); // clear the initial call + atom.config.set('foo.bar.baz', 'value 2'); + expect(observeHandler).toHaveBeenCalledWith('value 2'); - observeHandler.reset() - atom.config.set('foo.bar.baz', 'value 1') - expect(observeHandler).toHaveBeenCalledWith('value 1') - advanceClock(100) // complete pending save that was requested in ::set + observeHandler.reset(); + atom.config.set('foo.bar.baz', 'value 1'); + expect(observeHandler).toHaveBeenCalledWith('value 1'); + advanceClock(100); // complete pending save that was requested in ::set - observeHandler.reset() - atom.config.resetUserSettings({ foo: {} }) - expect(observeHandler).toHaveBeenCalledWith(undefined) - }) + observeHandler.reset(); + atom.config.resetUserSettings({ foo: {} }); + expect(observeHandler).toHaveBeenCalledWith(undefined); + }); it('fires the callback when the observed value is deleted', () => { - observeHandler.reset() // clear the initial call - atom.config.set('foo.bar.baz', undefined) - expect(observeHandler).toHaveBeenCalledWith(undefined) - }) + observeHandler.reset(); // clear the initial call + atom.config.set('foo.bar.baz', undefined); + expect(observeHandler).toHaveBeenCalledWith(undefined); + }); it('fires the callback when the full key path goes into and out of existence', () => { - observeHandler.reset() // clear the initial call - atom.config.set('foo.bar', undefined) - expect(observeHandler).toHaveBeenCalledWith(undefined) + observeHandler.reset(); // clear the initial call + atom.config.set('foo.bar', undefined); + expect(observeHandler).toHaveBeenCalledWith(undefined); - observeHandler.reset() - atom.config.set('foo.bar.baz', "i'm back") - expect(observeHandler).toHaveBeenCalledWith("i'm back") - }) + observeHandler.reset(); + atom.config.set('foo.bar.baz', "i'm back"); + expect(observeHandler).toHaveBeenCalledWith("i'm back"); + }); it('does not fire the callback once the subscription is disposed', () => { - observeHandler.reset() // clear the initial call - observeSubscription.dispose() - atom.config.set('foo.bar.baz', 'value 2') - expect(observeHandler).not.toHaveBeenCalled() - }) + observeHandler.reset(); // clear the initial call + observeSubscription.dispose(); + atom.config.set('foo.bar.baz', 'value 2'); + expect(observeHandler).not.toHaveBeenCalled(); + }); it('does not fire the callback for a similarly named keyPath', () => { - const bazCatHandler = jasmine.createSpy('bazCatHandler') - observeSubscription = atom.config.observe('foo.bar.bazCat', bazCatHandler) + const bazCatHandler = jasmine.createSpy('bazCatHandler'); + observeSubscription = atom.config.observe( + 'foo.bar.bazCat', + bazCatHandler + ); - bazCatHandler.reset() - atom.config.set('foo.bar.baz', 'value 10') - expect(bazCatHandler).not.toHaveBeenCalled() - }) + bazCatHandler.reset(); + atom.config.set('foo.bar.baz', 'value 10'); + expect(bazCatHandler).not.toHaveBeenCalled(); + }); describe("when a 'scope' is given", () => { - let otherHandler = null + let otherHandler = null; beforeEach(() => { - observeSubscription.dispose() - otherHandler = jasmine.createSpy('otherHandler') - }) + observeSubscription.dispose(); + otherHandler = jasmine.createSpy('otherHandler'); + }); it('allows settings to be observed in a specific scope', () => { atom.config.observe( 'foo.bar.baz', { scope: ['.some.scope'] }, observeHandler - ) + ); atom.config.observe( 'foo.bar.baz', { scope: ['.another.scope'] }, otherHandler - ) + ); - atom.config.set('foo.bar.baz', 'value 2', { scopeSelector: '.some' }) - expect(observeHandler).toHaveBeenCalledWith('value 2') - expect(otherHandler).not.toHaveBeenCalledWith('value 2') - }) + atom.config.set('foo.bar.baz', 'value 2', { scopeSelector: '.some' }); + expect(observeHandler).toHaveBeenCalledWith('value 2'); + expect(otherHandler).not.toHaveBeenCalledWith('value 2'); + }); it('calls the callback when properties with more specific selectors are removed', () => { - const changeSpy = jasmine.createSpy() + const changeSpy = jasmine.createSpy(); atom.config.observe( 'foo.bar.baz', { scope: ['.source.coffee', '.string.quoted.double.coffee'] }, changeSpy - ) - expect(changeSpy).toHaveBeenCalledWith('value 1') - changeSpy.reset() + ); + expect(changeSpy).toHaveBeenCalledWith('value 1'); + changeSpy.reset(); - atom.config.set('foo.bar.baz', 12) - expect(changeSpy).toHaveBeenCalledWith(12) - changeSpy.reset() + atom.config.set('foo.bar.baz', 12); + expect(changeSpy).toHaveBeenCalledWith(12); + changeSpy.reset(); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source .string.quoted.double', source: 'a' - }) - expect(changeSpy).toHaveBeenCalledWith(22) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith(22); + changeSpy.reset(); atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' - }) - expect(changeSpy).toHaveBeenCalledWith(42) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith(42); + changeSpy.reset(); atom.config.unset(null, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' - }) - expect(changeSpy).toHaveBeenCalledWith(22) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith(22); + changeSpy.reset(); atom.config.unset(null, { scopeSelector: '.source .string.quoted.double', source: 'a' - }) - expect(changeSpy).toHaveBeenCalledWith(12) - changeSpy.reset() + }); + expect(changeSpy).toHaveBeenCalledWith(12); + changeSpy.reset(); - atom.config.set('foo.bar.baz', undefined) - expect(changeSpy).toHaveBeenCalledWith(undefined) - changeSpy.reset() - }) - }) - }) + atom.config.set('foo.bar.baz', undefined); + expect(changeSpy).toHaveBeenCalledWith(undefined); + changeSpy.reset(); + }); + }); + }); describe('.transact(callback)', () => { - let changeSpy = null + let changeSpy = null; beforeEach(() => { - changeSpy = jasmine.createSpy('onDidChange callback') - atom.config.onDidChange('foo.bar.baz', changeSpy) - }) + changeSpy = jasmine.createSpy('onDidChange callback'); + atom.config.onDidChange('foo.bar.baz', changeSpy); + }); it('allows only one change event for the duration of the given callback', () => { atom.config.transact(() => { - atom.config.set('foo.bar.baz', 1) - atom.config.set('foo.bar.baz', 2) - atom.config.set('foo.bar.baz', 3) - }) + atom.config.set('foo.bar.baz', 1); + atom.config.set('foo.bar.baz', 2); + atom.config.set('foo.bar.baz', 3); + }); - expect(changeSpy.callCount).toBe(1) + expect(changeSpy.callCount).toBe(1); expect(changeSpy.argsForCall[0][0]).toEqual({ newValue: 3, oldValue: undefined - }) - }) + }); + }); it('does not emit an event if no changes occur while paused', () => { - atom.config.transact(() => {}) - expect(changeSpy).not.toHaveBeenCalled() - }) - }) + atom.config.transact(() => {}); + expect(changeSpy).not.toHaveBeenCalled(); + }); + }); describe('.transactAsync(callback)', () => { - let changeSpy = null + let changeSpy = null; beforeEach(() => { - changeSpy = jasmine.createSpy('onDidChange callback') - atom.config.onDidChange('foo.bar.baz', changeSpy) - }) + changeSpy = jasmine.createSpy('onDidChange callback'); + atom.config.onDidChange('foo.bar.baz', changeSpy); + }); it('allows only one change event for the duration of the given promise if it gets resolved', () => { - let promiseResult = null + let promiseResult = null; const transactionPromise = atom.config.transactAsync(() => { - atom.config.set('foo.bar.baz', 1) - atom.config.set('foo.bar.baz', 2) - atom.config.set('foo.bar.baz', 3) - return Promise.resolve('a result') - }) + atom.config.set('foo.bar.baz', 1); + atom.config.set('foo.bar.baz', 2); + atom.config.set('foo.bar.baz', 3); + return Promise.resolve('a result'); + }); waitsForPromise(() => transactionPromise.then(result => { - promiseResult = result + promiseResult = result; }) - ) + ); runs(() => { - expect(promiseResult).toBe('a result') - expect(changeSpy.callCount).toBe(1) + expect(promiseResult).toBe('a result'); + expect(changeSpy.callCount).toBe(1); expect(changeSpy.argsForCall[0][0]).toEqual({ newValue: 3, oldValue: undefined - }) - }) - }) + }); + }); + }); it('allows only one change event for the duration of the given promise if it gets rejected', () => { - let promiseError = null + let promiseError = null; const transactionPromise = atom.config.transactAsync(() => { - atom.config.set('foo.bar.baz', 1) - atom.config.set('foo.bar.baz', 2) - atom.config.set('foo.bar.baz', 3) - return Promise.reject(new Error('an error')) - }) + atom.config.set('foo.bar.baz', 1); + atom.config.set('foo.bar.baz', 2); + atom.config.set('foo.bar.baz', 3); + return Promise.reject(new Error('an error')); + }); waitsForPromise(() => transactionPromise.catch(error => { - promiseError = error + promiseError = error; }) - ) + ); runs(() => { - expect(promiseError.message).toBe('an error') - expect(changeSpy.callCount).toBe(1) + expect(promiseError.message).toBe('an error'); + expect(changeSpy.callCount).toBe(1); expect(changeSpy.argsForCall[0][0]).toEqual({ newValue: 3, oldValue: undefined - }) - }) - }) + }); + }); + }); it('allows only one change event even when the given callback throws', () => { - const error = new Error('Oops!') - let promiseError = null + const error = new Error('Oops!'); + let promiseError = null; const transactionPromise = atom.config.transactAsync(() => { - atom.config.set('foo.bar.baz', 1) - atom.config.set('foo.bar.baz', 2) - atom.config.set('foo.bar.baz', 3) - throw error - }) + atom.config.set('foo.bar.baz', 1); + atom.config.set('foo.bar.baz', 2); + atom.config.set('foo.bar.baz', 3); + throw error; + }); waitsForPromise(() => transactionPromise.catch(e => { - promiseError = e + promiseError = e; }) - ) + ); runs(() => { - expect(promiseError).toBe(error) - expect(changeSpy.callCount).toBe(1) + expect(promiseError).toBe(error); + expect(changeSpy.callCount).toBe(1); expect(changeSpy.argsForCall[0][0]).toEqual({ newValue: 3, oldValue: undefined - }) - }) - }) - }) + }); + }); + }); + }); describe('.getSources()', () => { it("returns an array of all of the config's source names", () => { - expect(atom.config.getSources()).toEqual([]) + expect(atom.config.getSources()).toEqual([]); - atom.config.set('a.b', 1, { scopeSelector: '.x1', source: 'source-1' }) - atom.config.set('a.c', 1, { scopeSelector: '.x1', source: 'source-1' }) - atom.config.set('a.b', 2, { scopeSelector: '.x2', source: 'source-2' }) - atom.config.set('a.b', 1, { scopeSelector: '.x3', source: 'source-3' }) + atom.config.set('a.b', 1, { scopeSelector: '.x1', source: 'source-1' }); + atom.config.set('a.c', 1, { scopeSelector: '.x1', source: 'source-1' }); + atom.config.set('a.b', 2, { scopeSelector: '.x2', source: 'source-2' }); + atom.config.set('a.b', 1, { scopeSelector: '.x3', source: 'source-3' }); expect(atom.config.getSources()).toEqual([ 'source-1', 'source-2', 'source-3' - ]) - }) - }) + ]); + }); + }); describe('.save()', () => { it('calls the save callback with any non-default properties', () => { - atom.config.set('a.b.c', 1) - atom.config.set('a.b.d', 2) - atom.config.set('x.y.z', 3) - atom.config.setDefaults('a.b', { e: 4, f: 5 }) + atom.config.set('a.b.c', 1); + atom.config.set('a.b.d', 2); + atom.config.set('x.y.z', 3); + atom.config.setDefaults('a.b', { e: 4, f: 5 }); - atom.config.save() - expect(savedSettings).toEqual([{ '*': atom.config.settings }]) - }) + atom.config.save(); + expect(savedSettings).toEqual([{ '*': atom.config.settings }]); + }); it('serializes properties in alphabetical order', () => { - atom.config.set('foo', 1) - atom.config.set('bar', 2) - atom.config.set('baz.foo', 3) - atom.config.set('baz.bar', 4) + atom.config.set('foo', 1); + atom.config.set('bar', 2); + atom.config.set('baz.foo', 3); + atom.config.set('baz.bar', 4); - savedSettings.length = 0 - atom.config.save() + savedSettings.length = 0; + atom.config.save(); - const writtenConfig = savedSettings[0] - expect(writtenConfig).toEqual({ '*': atom.config.settings }) + const writtenConfig = savedSettings[0]; + expect(writtenConfig).toEqual({ '*': atom.config.settings }); - let expectedKeys = ['bar', 'baz', 'foo'] - let foundKeys = [] + let expectedKeys = ['bar', 'baz', 'foo']; + let foundKeys = []; for (const key in writtenConfig['*']) { if (expectedKeys.includes(key)) { - foundKeys.push(key) + foundKeys.push(key); } } - expect(foundKeys).toEqual(expectedKeys) - expectedKeys = ['bar', 'foo'] - foundKeys = [] + expect(foundKeys).toEqual(expectedKeys); + expectedKeys = ['bar', 'foo']; + foundKeys = []; for (const key in writtenConfig['*']['baz']) { if (expectedKeys.includes(key)) { - foundKeys.push(key) + foundKeys.push(key); } } - expect(foundKeys).toEqual(expectedKeys) - }) + expect(foundKeys).toEqual(expectedKeys); + }); describe('when scoped settings are defined', () => { it('serializes any explicitly set config settings', () => { - atom.config.set('foo.bar', 'ruby', { scopeSelector: '.source.ruby' }) - atom.config.set('foo.omg', 'wow', { scopeSelector: '.source.ruby' }) + atom.config.set('foo.bar', 'ruby', { scopeSelector: '.source.ruby' }); + atom.config.set('foo.omg', 'wow', { scopeSelector: '.source.ruby' }); atom.config.set('foo.bar', 'coffee', { scopeSelector: '.source.coffee' - }) + }); - savedSettings.length = 0 - atom.config.save() + savedSettings.length = 0; + atom.config.save(); - const writtenConfig = savedSettings[0] + const writtenConfig = savedSettings[0]; expect(writtenConfig).toEqualJson({ '*': atom.config.settings, '.ruby.source': { @@ -1086,10 +1097,10 @@ describe('Config', () => { bar: 'coffee' } } - }) - }) - }) - }) + }); + }); + }); + }); describe('.resetUserSettings()', () => { beforeEach(() => { @@ -1105,8 +1116,8 @@ describe('Config', () => { default: 12 } } - }) - }) + }); + }); describe('when the config file contains scoped settings', () => { it('updates the config data based on the file contents', () => { @@ -1122,13 +1133,13 @@ describe('Config', () => { bar: 'more-specific' } } - }) - expect(atom.config.get('foo.bar')).toBe('baz') + }); + expect(atom.config.get('foo.bar')).toBe('baz'); expect(atom.config.get('foo.bar', { scope: ['.source.ruby'] })).toBe( 'more-specific' - ) - }) - }) + ); + }); + }); describe('when the config file does not conform to the schema', () => { it('validates and does not load the incorrect values', () => { @@ -1145,27 +1156,29 @@ describe('Config', () => { int: 'nope' } } - }) - expect(atom.config.get('foo.int')).toBe(12) - expect(atom.config.get('foo.bar')).toBe('omg') - expect(atom.config.get('foo.int', { scope: ['.source.ruby'] })).toBe(12) + }); + expect(atom.config.get('foo.int')).toBe(12); + expect(atom.config.get('foo.bar')).toBe('omg'); + expect(atom.config.get('foo.int', { scope: ['.source.ruby'] })).toBe( + 12 + ); expect(atom.config.get('foo.bar', { scope: ['.source.ruby'] })).toBe( 'scoped' - ) - }) - }) + ); + }); + }); it('updates the config data based on the file contents', () => { - atom.config.resetUserSettings({ foo: { bar: 'baz' } }) - expect(atom.config.get('foo.bar')).toBe('baz') - }) + atom.config.resetUserSettings({ foo: { bar: 'baz' } }); + expect(atom.config.get('foo.bar')).toBe('baz'); + }); it('notifies observers for updated keypaths on load', () => { - const observeHandler = jasmine.createSpy('observeHandler') - atom.config.observe('foo.bar', observeHandler) - atom.config.resetUserSettings({ foo: { bar: 'baz' } }) - expect(observeHandler).toHaveBeenCalledWith('baz') - }) + const observeHandler = jasmine.createSpy('observeHandler'); + atom.config.observe('foo.bar', observeHandler); + atom.config.resetUserSettings({ foo: { bar: 'baz' } }); + expect(observeHandler).toHaveBeenCalledWith('baz'); + }); describe('when the config file contains values that do not adhere to the schema', () => { it('updates the only the settings that have values matching the schema', () => { @@ -1174,30 +1187,30 @@ describe('Config', () => { bar: 'baz', int: 'bad value' } - }) - expect(atom.config.get('foo.bar')).toBe('baz') - expect(atom.config.get('foo.int')).toBe(12) - expect(console.warn).toHaveBeenCalled() - expect(console.warn.mostRecentCall.args[0]).toContain('foo.int') - }) - }) + }); + expect(atom.config.get('foo.bar')).toBe('baz'); + expect(atom.config.get('foo.int')).toBe(12); + expect(console.warn).toHaveBeenCalled(); + expect(console.warn.mostRecentCall.args[0]).toContain('foo.int'); + }); + }); it('does not fire a change event for paths that did not change', () => { atom.config.resetUserSettings({ foo: { bar: 'baz', int: 3 } - }) + }); - const noChangeSpy = jasmine.createSpy('unchanged') - atom.config.onDidChange('foo.bar', noChangeSpy) + const noChangeSpy = jasmine.createSpy('unchanged'); + atom.config.onDidChange('foo.bar', noChangeSpy); atom.config.resetUserSettings({ foo: { bar: 'baz', int: 4 } - }) + }); - expect(noChangeSpy).not.toHaveBeenCalled() - expect(atom.config.get('foo.bar')).toBe('baz') - expect(atom.config.get('foo.int')).toBe(4) - }) + expect(noChangeSpy).not.toHaveBeenCalled(); + expect(atom.config.get('foo.bar')).toBe('baz'); + expect(atom.config.get('foo.int')).toBe(4); + }); it('does not fire a change event for paths whose non-primitive values did not change', () => { atom.config.setSchema('foo.bar', { @@ -1205,40 +1218,40 @@ describe('Config', () => { items: { type: 'string' } - }) + }); atom.config.resetUserSettings({ foo: { bar: ['baz', 'quux'], int: 2 } - }) + }); - const noChangeSpy = jasmine.createSpy('unchanged') - atom.config.onDidChange('foo.bar', noChangeSpy) + const noChangeSpy = jasmine.createSpy('unchanged'); + atom.config.onDidChange('foo.bar', noChangeSpy); atom.config.resetUserSettings({ foo: { bar: ['baz', 'quux'], int: 2 } - }) + }); - expect(noChangeSpy).not.toHaveBeenCalled() - expect(atom.config.get('foo.bar')).toEqual(['baz', 'quux']) - }) + expect(noChangeSpy).not.toHaveBeenCalled(); + expect(atom.config.get('foo.bar')).toEqual(['baz', 'quux']); + }); describe('when a setting with a default is removed', () => { it('resets the setting back to the default', () => { atom.config.resetUserSettings({ foo: { bar: ['baz', 'quux'], int: 2 } - }) + }); - const events = [] - atom.config.onDidChange('foo.int', event => events.push(event)) + const events = []; + atom.config.onDidChange('foo.int', event => events.push(event)); atom.config.resetUserSettings({ foo: { bar: ['baz', 'quux'] } - }) + }); - expect(events.length).toBe(1) - expect(events[0]).toEqual({ oldValue: 2, newValue: 12 }) - }) - }) + expect(events.length).toBe(1); + expect(events[0]).toEqual({ oldValue: 2, newValue: 12 }); + }); + }); it('keeps all the global scope settings after overriding one', () => { atom.config.resetUserSettings({ @@ -1248,91 +1261,91 @@ describe('Config', () => { int: 99 } } - }) + }); - atom.config.set('foo.int', 50, { scopeSelector: '*' }) + atom.config.set('foo.int', 50, { scopeSelector: '*' }); - advanceClock(100) + advanceClock(100); expect(savedSettings[0]['*'].foo).toEqual({ bar: 'baz', int: 50 - }) - expect(atom.config.get('foo.int', { scope: ['*'] })).toEqual(50) - expect(atom.config.get('foo.bar', { scope: ['*'] })).toEqual('baz') - expect(atom.config.get('foo.int')).toEqual(50) - }) - }) + }); + expect(atom.config.get('foo.int', { scope: ['*'] })).toEqual(50); + expect(atom.config.get('foo.bar', { scope: ['*'] })).toEqual('baz'); + expect(atom.config.get('foo.int')).toEqual(50); + }); + }); describe('.pushAtKeyPath(keyPath, value)', () => { it('pushes the given value to the array at the key path and updates observers', () => { - atom.config.set('foo.bar.baz', ['a']) - const observeHandler = jasmine.createSpy('observeHandler') - atom.config.observe('foo.bar.baz', observeHandler) - observeHandler.reset() + atom.config.set('foo.bar.baz', ['a']); + const observeHandler = jasmine.createSpy('observeHandler'); + atom.config.observe('foo.bar.baz', observeHandler); + observeHandler.reset(); - expect(atom.config.pushAtKeyPath('foo.bar.baz', 'b')).toBe(2) - expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']) + expect(atom.config.pushAtKeyPath('foo.bar.baz', 'b')).toBe(2); + expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']); expect(observeHandler).toHaveBeenCalledWith( atom.config.get('foo.bar.baz') - ) - }) - }) + ); + }); + }); describe('.unshiftAtKeyPath(keyPath, value)', () => { it('unshifts the given value to the array at the key path and updates observers', () => { - atom.config.set('foo.bar.baz', ['b']) - const observeHandler = jasmine.createSpy('observeHandler') - atom.config.observe('foo.bar.baz', observeHandler) - observeHandler.reset() + atom.config.set('foo.bar.baz', ['b']); + const observeHandler = jasmine.createSpy('observeHandler'); + atom.config.observe('foo.bar.baz', observeHandler); + observeHandler.reset(); - expect(atom.config.unshiftAtKeyPath('foo.bar.baz', 'a')).toBe(2) - expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']) + expect(atom.config.unshiftAtKeyPath('foo.bar.baz', 'a')).toBe(2); + expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']); expect(observeHandler).toHaveBeenCalledWith( atom.config.get('foo.bar.baz') - ) - }) - }) + ); + }); + }); describe('.removeAtKeyPath(keyPath, value)', () => { it('removes the given value from the array at the key path and updates observers', () => { - atom.config.set('foo.bar.baz', ['a', 'b', 'c']) - const observeHandler = jasmine.createSpy('observeHandler') - atom.config.observe('foo.bar.baz', observeHandler) - observeHandler.reset() + atom.config.set('foo.bar.baz', ['a', 'b', 'c']); + const observeHandler = jasmine.createSpy('observeHandler'); + atom.config.observe('foo.bar.baz', observeHandler); + observeHandler.reset(); expect(atom.config.removeAtKeyPath('foo.bar.baz', 'b')).toEqual([ 'a', 'c' - ]) - expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'c']) + ]); + expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'c']); expect(observeHandler).toHaveBeenCalledWith( atom.config.get('foo.bar.baz') - ) - }) - }) + ); + }); + }); describe('.setDefaults(keyPath, defaults)', () => { it('assigns any previously-unassigned keys to the object at the key path', () => { - atom.config.set('foo.bar.baz', { a: 1 }) - atom.config.setDefaults('foo.bar.baz', { a: 2, b: 3, c: 4 }) - expect(atom.config.get('foo.bar.baz.a')).toBe(1) - expect(atom.config.get('foo.bar.baz.b')).toBe(3) - expect(atom.config.get('foo.bar.baz.c')).toBe(4) + atom.config.set('foo.bar.baz', { a: 1 }); + atom.config.setDefaults('foo.bar.baz', { a: 2, b: 3, c: 4 }); + expect(atom.config.get('foo.bar.baz.a')).toBe(1); + expect(atom.config.get('foo.bar.baz.b')).toBe(3); + expect(atom.config.get('foo.bar.baz.c')).toBe(4); - atom.config.setDefaults('foo.quux', { x: 0, y: 1 }) - expect(atom.config.get('foo.quux.x')).toBe(0) - expect(atom.config.get('foo.quux.y')).toBe(1) - }) + atom.config.setDefaults('foo.quux', { x: 0, y: 1 }); + expect(atom.config.get('foo.quux.x')).toBe(0); + expect(atom.config.get('foo.quux.y')).toBe(1); + }); it('emits an updated event', () => { - const updatedCallback = jasmine.createSpy('updated') - atom.config.onDidChange('foo.bar.baz.a', updatedCallback) - expect(updatedCallback.callCount).toBe(0) - atom.config.setDefaults('foo.bar.baz', { a: 2 }) - expect(updatedCallback.callCount).toBe(1) - }) - }) + const updatedCallback = jasmine.createSpy('updated'); + atom.config.onDidChange('foo.bar.baz.a', updatedCallback); + expect(updatedCallback.callCount).toBe(0); + atom.config.setDefaults('foo.bar.baz', { a: 2 }); + expect(updatedCallback.callCount).toBe(1); + }); + }); describe('.setSchema(keyPath, schema)', () => { it('creates a properly nested schema', () => { @@ -1344,9 +1357,9 @@ describe('Config', () => { default: 12 } } - } + }; - atom.config.setSchema('foo.bar', schema) + atom.config.setSchema('foo.bar', schema); expect(atom.config.getSchema('foo')).toEqual({ type: 'object', @@ -1361,8 +1374,8 @@ describe('Config', () => { } } } - }) - }) + }); + }); it('sets defaults specified by the schema', () => { const schema = { @@ -1391,16 +1404,16 @@ describe('Config', () => { } } } - } + }; - atom.config.setSchema('foo.bar', schema) - expect(atom.config.get('foo.bar.anInt')).toBe(12) + atom.config.setSchema('foo.bar', schema); + expect(atom.config.get('foo.bar.anInt')).toBe(12); expect(atom.config.get('foo.bar.anObject')).toEqual({ nestedInt: 24, nestedObject: { superNestedInt: 36 } - }) + }); expect(atom.config.get('foo')).toEqual({ bar: { @@ -1412,8 +1425,8 @@ describe('Config', () => { } } } - }) - atom.config.set('foo.bar.anObject.nestedObject.superNestedInt', 37) + }); + atom.config.set('foo.bar.anObject.nestedObject.superNestedInt', 37); expect(atom.config.get('foo')).toEqual({ bar: { anInt: 12, @@ -1424,22 +1437,22 @@ describe('Config', () => { } } } - }) - }) + }); + }); it('can set a non-object schema', () => { const schema = { type: 'integer', default: 12 - } + }; - atom.config.setSchema('foo.bar.anInt', schema) - expect(atom.config.get('foo.bar.anInt')).toBe(12) + atom.config.setSchema('foo.bar.anInt', schema); + expect(atom.config.get('foo.bar.anInt')).toBe(12); expect(atom.config.getSchema('foo.bar.anInt')).toEqual({ type: 'integer', default: 12 - }) - }) + }); + }); it('allows the schema to be retrieved via ::getSchema', () => { const schema = { @@ -1450,9 +1463,9 @@ describe('Config', () => { default: 12 } } - } + }; - atom.config.setSchema('foo.bar', schema) + atom.config.setSchema('foo.bar', schema); expect(atom.config.getSchema('foo.bar')).toEqual({ type: 'object', @@ -1462,16 +1475,16 @@ describe('Config', () => { default: 12 } } - }) + }); expect(atom.config.getSchema('foo.bar.anInt')).toEqual({ type: 'integer', default: 12 - }) + }); - expect(atom.config.getSchema('foo.baz')).toEqual({ type: 'any' }) - expect(atom.config.getSchema('foo.bar.anInt.baz')).toBe(null) - }) + expect(atom.config.getSchema('foo.baz')).toEqual({ type: 'any' }); + expect(atom.config.getSchema('foo.bar.anInt.baz')).toBe(null); + }); it('respects the schema for scoped settings', () => { const schema = { @@ -1482,20 +1495,20 @@ describe('Config', () => { default: 'omg' } } - } - atom.config.setSchema('foo.bar.str', schema) + }; + atom.config.setSchema('foo.bar.str', schema); - expect(atom.config.get('foo.bar.str')).toBe('ok') + expect(atom.config.get('foo.bar.str')).toBe('ok'); expect(atom.config.get('foo.bar.str', { scope: ['.source.js'] })).toBe( 'omg' - ) + ); expect( atom.config.get('foo.bar.str', { scope: ['.source.coffee'] }) - ).toBe('ok') - }) + ).toBe('ok'); + }); describe('when a schema is added after config values have been set', () => { - let schema = null + let schema = null; beforeEach(() => { schema = { type: 'object', @@ -1509,132 +1522,132 @@ describe('Config', () => { default: 'def' } } - } - }) + }; + }); it('respects the new schema when values are set', () => { - expect(atom.config.set('foo.bar.str', 'global')).toBe(true) + expect(atom.config.set('foo.bar.str', 'global')).toBe(true); expect( atom.config.set('foo.bar.str', 'scoped', { scopeSelector: '.source.js' }) - ).toBe(true) - expect(atom.config.get('foo.bar.str')).toBe('global') + ).toBe(true); + expect(atom.config.get('foo.bar.str')).toBe('global'); expect(atom.config.get('foo.bar.str', { scope: ['.source.js'] })).toBe( 'scoped' - ) + ); - expect(atom.config.set('foo.bar.noschema', 'nsGlobal')).toBe(true) + expect(atom.config.set('foo.bar.noschema', 'nsGlobal')).toBe(true); expect( atom.config.set('foo.bar.noschema', 'nsScoped', { scopeSelector: '.source.js' }) - ).toBe(true) - expect(atom.config.get('foo.bar.noschema')).toBe('nsGlobal') + ).toBe(true); + expect(atom.config.get('foo.bar.noschema')).toBe('nsGlobal'); expect( atom.config.get('foo.bar.noschema', { scope: ['.source.js'] }) - ).toBe('nsScoped') + ).toBe('nsScoped'); - expect(atom.config.set('foo.bar.int', 'nope')).toBe(true) + expect(atom.config.set('foo.bar.int', 'nope')).toBe(true); expect( atom.config.set('foo.bar.int', 'notanint', { scopeSelector: '.source.js' }) - ).toBe(true) + ).toBe(true); expect( atom.config.set('foo.bar.int', 23, { scopeSelector: '.source.coffee' }) - ).toBe(true) - expect(atom.config.get('foo.bar.int')).toBe('nope') + ).toBe(true); + expect(atom.config.get('foo.bar.int')).toBe('nope'); expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe( 'notanint' - ) + ); expect( atom.config.get('foo.bar.int', { scope: ['.source.coffee'] }) - ).toBe(23) + ).toBe(23); - atom.config.setSchema('foo.bar', schema) + atom.config.setSchema('foo.bar', schema); - expect(atom.config.get('foo.bar.str')).toBe('global') + expect(atom.config.get('foo.bar.str')).toBe('global'); expect(atom.config.get('foo.bar.str', { scope: ['.source.js'] })).toBe( 'scoped' - ) - expect(atom.config.get('foo.bar.noschema')).toBe('nsGlobal') + ); + expect(atom.config.get('foo.bar.noschema')).toBe('nsGlobal'); expect( atom.config.get('foo.bar.noschema', { scope: ['.source.js'] }) - ).toBe('nsScoped') + ).toBe('nsScoped'); - expect(atom.config.get('foo.bar.int')).toBe(2) + expect(atom.config.get('foo.bar.int')).toBe(2); expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe( 2 - ) + ); expect( atom.config.get('foo.bar.int', { scope: ['.source.coffee'] }) - ).toBe(23) - }) + ).toBe(23); + }); it('sets all values that adhere to the schema', () => { - expect(atom.config.set('foo.bar.int', 10)).toBe(true) + expect(atom.config.set('foo.bar.int', 10)).toBe(true); expect( atom.config.set('foo.bar.int', 15, { scopeSelector: '.source.js' }) - ).toBe(true) + ).toBe(true); expect( atom.config.set('foo.bar.int', 23, { scopeSelector: '.source.coffee' }) - ).toBe(true) - expect(atom.config.get('foo.bar.int')).toBe(10) + ).toBe(true); + expect(atom.config.get('foo.bar.int')).toBe(10); expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe( 15 - ) + ); expect( atom.config.get('foo.bar.int', { scope: ['.source.coffee'] }) - ).toBe(23) + ).toBe(23); - atom.config.setSchema('foo.bar', schema) + atom.config.setSchema('foo.bar', schema); - expect(atom.config.get('foo.bar.int')).toBe(10) + expect(atom.config.get('foo.bar.int')).toBe(10); expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe( 15 - ) + ); expect( atom.config.get('foo.bar.int', { scope: ['.source.coffee'] }) - ).toBe(23) - }) - }) + ).toBe(23); + }); + }); describe('when the value has an "integer" type', () => { beforeEach(() => { const schema = { type: 'integer', default: 12 - } - atom.config.setSchema('foo.bar.anInt', schema) - }) + }; + atom.config.setSchema('foo.bar.anInt', schema); + }); it('coerces a string to an int', () => { - atom.config.set('foo.bar.anInt', '123') - expect(atom.config.get('foo.bar.anInt')).toBe(123) - }) + atom.config.set('foo.bar.anInt', '123'); + expect(atom.config.get('foo.bar.anInt')).toBe(123); + }); it('does not allow infinity', () => { - atom.config.set('foo.bar.anInt', Infinity) - expect(atom.config.get('foo.bar.anInt')).toBe(12) - }) + atom.config.set('foo.bar.anInt', Infinity); + expect(atom.config.get('foo.bar.anInt')).toBe(12); + }); it('coerces a float to an int', () => { - atom.config.set('foo.bar.anInt', 12.3) - expect(atom.config.get('foo.bar.anInt')).toBe(12) - }) + atom.config.set('foo.bar.anInt', 12.3); + expect(atom.config.get('foo.bar.anInt')).toBe(12); + }); it('will not set non-integers', () => { - atom.config.set('foo.bar.anInt', null) - expect(atom.config.get('foo.bar.anInt')).toBe(12) + atom.config.set('foo.bar.anInt', null); + expect(atom.config.get('foo.bar.anInt')).toBe(12); - atom.config.set('foo.bar.anInt', 'nope') - expect(atom.config.get('foo.bar.anInt')).toBe(12) - }) + atom.config.set('foo.bar.anInt', 'nope'); + expect(atom.config.get('foo.bar.anInt')).toBe(12); + }); describe('when the minimum and maximum keys are used', () => { beforeEach(() => { @@ -1643,80 +1656,80 @@ describe('Config', () => { minimum: 10, maximum: 20, default: 12 - } - atom.config.setSchema('foo.bar.anInt', schema) - }) + }; + atom.config.setSchema('foo.bar.anInt', schema); + }); it('keeps the specified value within the specified range', () => { - atom.config.set('foo.bar.anInt', '123') - expect(atom.config.get('foo.bar.anInt')).toBe(20) + atom.config.set('foo.bar.anInt', '123'); + expect(atom.config.get('foo.bar.anInt')).toBe(20); - atom.config.set('foo.bar.anInt', '1') - expect(atom.config.get('foo.bar.anInt')).toBe(10) - }) - }) - }) + atom.config.set('foo.bar.anInt', '1'); + expect(atom.config.get('foo.bar.anInt')).toBe(10); + }); + }); + }); describe('when the value has an "integer" and "string" type', () => { beforeEach(() => { const schema = { type: ['integer', 'string'], default: 12 - } - atom.config.setSchema('foo.bar.anInt', schema) - }) + }; + atom.config.setSchema('foo.bar.anInt', schema); + }); it('can coerce an int, and fallback to a string', () => { - atom.config.set('foo.bar.anInt', '123') - expect(atom.config.get('foo.bar.anInt')).toBe(123) + atom.config.set('foo.bar.anInt', '123'); + expect(atom.config.get('foo.bar.anInt')).toBe(123); - atom.config.set('foo.bar.anInt', 'cats') - expect(atom.config.get('foo.bar.anInt')).toBe('cats') - }) - }) + atom.config.set('foo.bar.anInt', 'cats'); + expect(atom.config.get('foo.bar.anInt')).toBe('cats'); + }); + }); describe('when the value has an "string" and "boolean" type', () => { beforeEach(() => { const schema = { type: ['string', 'boolean'], default: 'def' - } - atom.config.setSchema('foo.bar', schema) - }) + }; + atom.config.setSchema('foo.bar', schema); + }); it('can set a string, a boolean, and revert back to the default', () => { - atom.config.set('foo.bar', 'ok') - expect(atom.config.get('foo.bar')).toBe('ok') + atom.config.set('foo.bar', 'ok'); + expect(atom.config.get('foo.bar')).toBe('ok'); - atom.config.set('foo.bar', false) - expect(atom.config.get('foo.bar')).toBe(false) + atom.config.set('foo.bar', false); + expect(atom.config.get('foo.bar')).toBe(false); - atom.config.set('foo.bar', undefined) - expect(atom.config.get('foo.bar')).toBe('def') - }) - }) + atom.config.set('foo.bar', undefined); + expect(atom.config.get('foo.bar')).toBe('def'); + }); + }); describe('when the value has a "number" type', () => { beforeEach(() => { const schema = { type: 'number', default: 12.1 - } - atom.config.setSchema('foo.bar.aFloat', schema) - }) + }; + atom.config.setSchema('foo.bar.aFloat', schema); + }); it('coerces a string to a float', () => { - atom.config.set('foo.bar.aFloat', '12.23') - expect(atom.config.get('foo.bar.aFloat')).toBe(12.23) - }) + atom.config.set('foo.bar.aFloat', '12.23'); + expect(atom.config.get('foo.bar.aFloat')).toBe(12.23); + }); it('will not set non-numbers', () => { - atom.config.set('foo.bar.aFloat', null) - expect(atom.config.get('foo.bar.aFloat')).toBe(12.1) + atom.config.set('foo.bar.aFloat', null); + expect(atom.config.get('foo.bar.aFloat')).toBe(12.1); - atom.config.set('foo.bar.aFloat', 'nope') - expect(atom.config.get('foo.bar.aFloat')).toBe(12.1) - }) + atom.config.set('foo.bar.aFloat', 'nope'); + expect(atom.config.get('foo.bar.aFloat')).toBe(12.1); + }); describe('when the minimum and maximum keys are used', () => { beforeEach(() => { @@ -1725,92 +1738,94 @@ describe('Config', () => { minimum: 11.2, maximum: 25.4, default: 12.1 - } - atom.config.setSchema('foo.bar.aFloat', schema) - }) + }; + atom.config.setSchema('foo.bar.aFloat', schema); + }); it('keeps the specified value within the specified range', () => { - atom.config.set('foo.bar.aFloat', '123.2') - expect(atom.config.get('foo.bar.aFloat')).toBe(25.4) + atom.config.set('foo.bar.aFloat', '123.2'); + expect(atom.config.get('foo.bar.aFloat')).toBe(25.4); - atom.config.set('foo.bar.aFloat', '1.0') - expect(atom.config.get('foo.bar.aFloat')).toBe(11.2) - }) - }) - }) + atom.config.set('foo.bar.aFloat', '1.0'); + expect(atom.config.get('foo.bar.aFloat')).toBe(11.2); + }); + }); + }); describe('when the value has a "boolean" type', () => { beforeEach(() => { const schema = { type: 'boolean', default: true - } - atom.config.setSchema('foo.bar.aBool', schema) - }) + }; + atom.config.setSchema('foo.bar.aBool', schema); + }); it('coerces various types to a boolean', () => { - atom.config.set('foo.bar.aBool', 'true') - expect(atom.config.get('foo.bar.aBool')).toBe(true) - atom.config.set('foo.bar.aBool', 'false') - expect(atom.config.get('foo.bar.aBool')).toBe(false) - atom.config.set('foo.bar.aBool', 'TRUE') - expect(atom.config.get('foo.bar.aBool')).toBe(true) - atom.config.set('foo.bar.aBool', 'FALSE') - expect(atom.config.get('foo.bar.aBool')).toBe(false) - atom.config.set('foo.bar.aBool', 1) - expect(atom.config.get('foo.bar.aBool')).toBe(false) - atom.config.set('foo.bar.aBool', 0) - expect(atom.config.get('foo.bar.aBool')).toBe(false) - atom.config.set('foo.bar.aBool', {}) - expect(atom.config.get('foo.bar.aBool')).toBe(false) - atom.config.set('foo.bar.aBool', null) - expect(atom.config.get('foo.bar.aBool')).toBe(false) - }) + atom.config.set('foo.bar.aBool', 'true'); + expect(atom.config.get('foo.bar.aBool')).toBe(true); + atom.config.set('foo.bar.aBool', 'false'); + expect(atom.config.get('foo.bar.aBool')).toBe(false); + atom.config.set('foo.bar.aBool', 'TRUE'); + expect(atom.config.get('foo.bar.aBool')).toBe(true); + atom.config.set('foo.bar.aBool', 'FALSE'); + expect(atom.config.get('foo.bar.aBool')).toBe(false); + atom.config.set('foo.bar.aBool', 1); + expect(atom.config.get('foo.bar.aBool')).toBe(false); + atom.config.set('foo.bar.aBool', 0); + expect(atom.config.get('foo.bar.aBool')).toBe(false); + atom.config.set('foo.bar.aBool', {}); + expect(atom.config.get('foo.bar.aBool')).toBe(false); + atom.config.set('foo.bar.aBool', null); + expect(atom.config.get('foo.bar.aBool')).toBe(false); + }); it('reverts back to the default value when undefined is passed to set', () => { - atom.config.set('foo.bar.aBool', 'false') - expect(atom.config.get('foo.bar.aBool')).toBe(false) + atom.config.set('foo.bar.aBool', 'false'); + expect(atom.config.get('foo.bar.aBool')).toBe(false); - atom.config.set('foo.bar.aBool', undefined) - expect(atom.config.get('foo.bar.aBool')).toBe(true) - }) - }) + atom.config.set('foo.bar.aBool', undefined); + expect(atom.config.get('foo.bar.aBool')).toBe(true); + }); + }); describe('when the value has an "string" type', () => { beforeEach(() => { const schema = { type: 'string', default: 'ok' - } - atom.config.setSchema('foo.bar.aString', schema) - }) + }; + atom.config.setSchema('foo.bar.aString', schema); + }); it('allows strings', () => { - atom.config.set('foo.bar.aString', 'yep') - expect(atom.config.get('foo.bar.aString')).toBe('yep') - }) + atom.config.set('foo.bar.aString', 'yep'); + expect(atom.config.get('foo.bar.aString')).toBe('yep'); + }); it('will only set strings', () => { - expect(atom.config.set('foo.bar.aString', 123)).toBe(false) - expect(atom.config.get('foo.bar.aString')).toBe('ok') + expect(atom.config.set('foo.bar.aString', 123)).toBe(false); + expect(atom.config.get('foo.bar.aString')).toBe('ok'); - expect(atom.config.set('foo.bar.aString', true)).toBe(false) - expect(atom.config.get('foo.bar.aString')).toBe('ok') + expect(atom.config.set('foo.bar.aString', true)).toBe(false); + expect(atom.config.get('foo.bar.aString')).toBe('ok'); - expect(atom.config.set('foo.bar.aString', null)).toBe(false) - expect(atom.config.get('foo.bar.aString')).toBe('ok') + expect(atom.config.set('foo.bar.aString', null)).toBe(false); + expect(atom.config.get('foo.bar.aString')).toBe('ok'); - expect(atom.config.set('foo.bar.aString', [])).toBe(false) - expect(atom.config.get('foo.bar.aString')).toBe('ok') + expect(atom.config.set('foo.bar.aString', [])).toBe(false); + expect(atom.config.get('foo.bar.aString')).toBe('ok'); - expect(atom.config.set('foo.bar.aString', { nope: 'nope' })).toBe(false) - expect(atom.config.get('foo.bar.aString')).toBe('ok') - }) + expect(atom.config.set('foo.bar.aString', { nope: 'nope' })).toBe( + false + ); + expect(atom.config.get('foo.bar.aString')).toBe('ok'); + }); it('does not allow setting children of that key-path', () => { - expect(atom.config.set('foo.bar.aString.something', 123)).toBe(false) - expect(atom.config.get('foo.bar.aString')).toBe('ok') - }) + expect(atom.config.set('foo.bar.aString.something', 123)).toBe(false); + expect(atom.config.get('foo.bar.aString')).toBe('ok'); + }); describe('when the schema has a "maximumLength" key', () => it('trims the string to be no longer than the specified maximum', () => { @@ -1818,12 +1833,12 @@ describe('Config', () => { type: 'string', default: 'ok', maximumLength: 3 - } - atom.config.setSchema('foo.bar.aString', schema) - atom.config.set('foo.bar.aString', 'abcdefg') - expect(atom.config.get('foo.bar.aString')).toBe('abc') - })) - }) + }; + atom.config.setSchema('foo.bar.aString', schema); + atom.config.set('foo.bar.aString', 'abcdefg'); + expect(atom.config.get('foo.bar.aString')).toBe('abc'); + })); + }); describe('when the value has an "object" type', () => { beforeEach(() => { @@ -1844,9 +1859,9 @@ describe('Config', () => { } } } - } - atom.config.setSchema('foo.bar', schema) - }) + }; + atom.config.setSchema('foo.bar', schema); + }); it('converts and validates all the children', () => { atom.config.set('foo.bar', { @@ -1854,14 +1869,14 @@ describe('Config', () => { nestedObject: { nestedBool: 'true' } - }) + }); expect(atom.config.get('foo.bar')).toEqual({ anInt: 23, nestedObject: { nestedBool: true } - }) - }) + }); + }); it('will set only the values that adhere to the schema', () => { expect( @@ -1871,10 +1886,12 @@ describe('Config', () => { nestedBool: true } }) - ).toBe(true) - expect(atom.config.get('foo.bar.anInt')).toEqual(12) - expect(atom.config.get('foo.bar.nestedObject.nestedBool')).toEqual(true) - }) + ).toBe(true); + expect(atom.config.get('foo.bar.anInt')).toEqual(12); + expect(atom.config.get('foo.bar.nestedObject.nestedBool')).toEqual( + true + ); + }); describe('when the value has additionalProperties set to false', () => it('does not allow other properties to be set on the object', () => { @@ -1887,19 +1904,19 @@ describe('Config', () => { } }, additionalProperties: false - }) + }); expect( atom.config.set('foo.bar', { anInt: 5, somethingElse: 'ok' }) - ).toBe(true) - expect(atom.config.get('foo.bar.anInt')).toBe(5) - expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined() + ).toBe(true); + expect(atom.config.get('foo.bar.anInt')).toBe(5); + expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined(); expect(atom.config.set('foo.bar.somethingElse', { anInt: 5 })).toBe( false - ) - expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined() - })) + ); + expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined(); + })); describe('when the value has an additionalProperties schema', () => it('validates properties of the object against that schema', () => { @@ -1914,24 +1931,24 @@ describe('Config', () => { additionalProperties: { type: 'string' } - }) + }); expect( atom.config.set('foo.bar', { anInt: 5, somethingElse: 'ok' }) - ).toBe(true) - expect(atom.config.get('foo.bar.anInt')).toBe(5) - expect(atom.config.get('foo.bar.somethingElse')).toBe('ok') + ).toBe(true); + expect(atom.config.get('foo.bar.anInt')).toBe(5); + expect(atom.config.get('foo.bar.somethingElse')).toBe('ok'); - expect(atom.config.set('foo.bar.somethingElse', 7)).toBe(false) - expect(atom.config.get('foo.bar.somethingElse')).toBe('ok') + expect(atom.config.set('foo.bar.somethingElse', 7)).toBe(false); + expect(atom.config.get('foo.bar.somethingElse')).toBe('ok'); expect( atom.config.set('foo.bar', { anInt: 6, somethingElse: 7 }) - ).toBe(true) - expect(atom.config.get('foo.bar.anInt')).toBe(6) - expect(atom.config.get('foo.bar.somethingElse')).toBe(undefined) - })) - }) + ).toBe(true); + expect(atom.config.get('foo.bar.anInt')).toBe(6); + expect(atom.config.get('foo.bar.somethingElse')).toBe(undefined); + })); + }); describe('when the value has an "array" type', () => { beforeEach(() => { @@ -1941,205 +1958,205 @@ describe('Config', () => { items: { type: 'integer' } - } - atom.config.setSchema('foo.bar', schema) - }) + }; + atom.config.setSchema('foo.bar', schema); + }); it('converts an array of strings to an array of ints', () => { - atom.config.set('foo.bar', ['2', '3', '4']) - expect(atom.config.get('foo.bar')).toEqual([2, 3, 4]) - }) + atom.config.set('foo.bar', ['2', '3', '4']); + expect(atom.config.get('foo.bar')).toEqual([2, 3, 4]); + }); it('does not allow setting children of that key-path', () => { - expect(atom.config.set('foo.bar.child', 123)).toBe(false) - expect(atom.config.set('foo.bar.child.grandchild', 123)).toBe(false) - expect(atom.config.get('foo.bar')).toEqual([1, 2, 3]) - }) - }) + expect(atom.config.set('foo.bar.child', 123)).toBe(false); + expect(atom.config.set('foo.bar.child.grandchild', 123)).toBe(false); + expect(atom.config.get('foo.bar')).toEqual([1, 2, 3]); + }); + }); describe('when the value has a "color" type', () => { beforeEach(() => { const schema = { type: 'color', default: 'white' - } - atom.config.setSchema('foo.bar.aColor', schema) - }) + }; + atom.config.setSchema('foo.bar.aColor', schema); + }); it('returns a Color object', () => { - let color = atom.config.get('foo.bar.aColor') - expect(color.toHexString()).toBe('#ffffff') - expect(color.toRGBAString()).toBe('rgba(255, 255, 255, 1)') + let color = atom.config.get('foo.bar.aColor'); + expect(color.toHexString()).toBe('#ffffff'); + expect(color.toRGBAString()).toBe('rgba(255, 255, 255, 1)'); - color.red = 0 - color.green = 0 - color.blue = 0 - color.alpha = 0 - atom.config.set('foo.bar.aColor', color) + color.red = 0; + color.green = 0; + color.blue = 0; + color.alpha = 0; + atom.config.set('foo.bar.aColor', color); - color = atom.config.get('foo.bar.aColor') - expect(color.toHexString()).toBe('#000000') - expect(color.toRGBAString()).toBe('rgba(0, 0, 0, 0)') + color = atom.config.get('foo.bar.aColor'); + expect(color.toHexString()).toBe('#000000'); + expect(color.toRGBAString()).toBe('rgba(0, 0, 0, 0)'); - color.red = 300 - color.green = -200 - color.blue = -1 - color.alpha = 'not see through' - atom.config.set('foo.bar.aColor', color) + color.red = 300; + color.green = -200; + color.blue = -1; + color.alpha = 'not see through'; + atom.config.set('foo.bar.aColor', color); - color = atom.config.get('foo.bar.aColor') - expect(color.toHexString()).toBe('#ff0000') - expect(color.toRGBAString()).toBe('rgba(255, 0, 0, 1)') + color = atom.config.get('foo.bar.aColor'); + expect(color.toHexString()).toBe('#ff0000'); + expect(color.toRGBAString()).toBe('rgba(255, 0, 0, 1)'); - color.red = 11 - color.green = 11 - color.blue = 124 - color.alpha = 1 - atom.config.set('foo.bar.aColor', color) + color.red = 11; + color.green = 11; + color.blue = 124; + color.alpha = 1; + atom.config.set('foo.bar.aColor', color); - color = atom.config.get('foo.bar.aColor') - expect(color.toHexString()).toBe('#0b0b7c') - expect(color.toRGBAString()).toBe('rgba(11, 11, 124, 1)') - }) + color = atom.config.get('foo.bar.aColor'); + expect(color.toHexString()).toBe('#0b0b7c'); + expect(color.toRGBAString()).toBe('rgba(11, 11, 124, 1)'); + }); it('coerces various types to a color object', () => { - atom.config.set('foo.bar.aColor', 'red') + atom.config.set('foo.bar.aColor', 'red'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 0, blue: 0, alpha: 1 - }) - atom.config.set('foo.bar.aColor', '#020') + }); + atom.config.set('foo.bar.aColor', '#020'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 0, green: 34, blue: 0, alpha: 1 - }) - atom.config.set('foo.bar.aColor', '#abcdef') + }); + atom.config.set('foo.bar.aColor', '#abcdef'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 171, green: 205, blue: 239, alpha: 1 - }) - atom.config.set('foo.bar.aColor', 'rgb(1,2,3)') + }); + atom.config.set('foo.bar.aColor', 'rgb(1,2,3)'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 1, green: 2, blue: 3, alpha: 1 - }) - atom.config.set('foo.bar.aColor', 'rgba(4,5,6,.7)') + }); + atom.config.set('foo.bar.aColor', 'rgba(4,5,6,.7)'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 4, green: 5, blue: 6, alpha: 0.7 - }) - atom.config.set('foo.bar.aColor', 'hsl(120,100%,50%)') + }); + atom.config.set('foo.bar.aColor', 'hsl(120,100%,50%)'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 0, green: 255, blue: 0, alpha: 1 - }) - atom.config.set('foo.bar.aColor', 'hsla(120,100%,50%,0.3)') + }); + atom.config.set('foo.bar.aColor', 'hsla(120,100%,50%,0.3)'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 0, green: 255, blue: 0, alpha: 0.3 - }) + }); atom.config.set('foo.bar.aColor', { red: 100, green: 255, blue: 2, alpha: 0.5 - }) + }); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 100, green: 255, blue: 2, alpha: 0.5 - }) - atom.config.set('foo.bar.aColor', { red: 255 }) + }); + atom.config.set('foo.bar.aColor', { red: 255 }); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 0, blue: 0, alpha: 1 - }) - atom.config.set('foo.bar.aColor', { red: 1000 }) + }); + atom.config.set('foo.bar.aColor', { red: 1000 }); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 0, blue: 0, alpha: 1 - }) - atom.config.set('foo.bar.aColor', { red: 'dark' }) + }); + atom.config.set('foo.bar.aColor', { red: 'dark' }); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 0, green: 0, blue: 0, alpha: 1 - }) - }) + }); + }); it('reverts back to the default value when undefined is passed to set', () => { - atom.config.set('foo.bar.aColor', undefined) + atom.config.set('foo.bar.aColor', undefined); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 255, blue: 255, alpha: 1 - }) - }) + }); + }); it('will not set non-colors', () => { - atom.config.set('foo.bar.aColor', null) + atom.config.set('foo.bar.aColor', null); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 255, blue: 255, alpha: 1 - }) + }); - atom.config.set('foo.bar.aColor', 'nope') + atom.config.set('foo.bar.aColor', 'nope'); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 255, blue: 255, alpha: 1 - }) + }); - atom.config.set('foo.bar.aColor', 30) + atom.config.set('foo.bar.aColor', 30); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 255, blue: 255, alpha: 1 - }) + }); - atom.config.set('foo.bar.aColor', false) + atom.config.set('foo.bar.aColor', false); expect(atom.config.get('foo.bar.aColor')).toEqual({ red: 255, green: 255, blue: 255, alpha: 1 - }) - }) + }); + }); it('returns a clone of the Color when returned in a parent object', () => { - const color1 = atom.config.get('foo.bar').aColor - const color2 = atom.config.get('foo.bar').aColor - expect(color1.toRGBAString()).toBe('rgba(255, 255, 255, 1)') - expect(color2.toRGBAString()).toBe('rgba(255, 255, 255, 1)') - expect(color1).not.toBe(color2) - expect(color1).toEqual(color2) - }) - }) + const color1 = atom.config.get('foo.bar').aColor; + const color2 = atom.config.get('foo.bar').aColor; + expect(color1.toRGBAString()).toBe('rgba(255, 255, 255, 1)'); + expect(color2.toRGBAString()).toBe('rgba(255, 255, 255, 1)'); + expect(color1).not.toBe(color2); + expect(color1).toEqual(color2); + }); + }); describe('when the `enum` key is used', () => { beforeEach(() => { @@ -2174,63 +2191,63 @@ describe('Config', () => { ] } } - } + }; - atom.config.setSchema('foo.bar', schema) - }) + atom.config.setSchema('foo.bar', schema); + }); it('will only set a string when the string is in the enum values', () => { - expect(atom.config.set('foo.bar.str', 'nope')).toBe(false) - expect(atom.config.get('foo.bar.str')).toBe('ok') + expect(atom.config.set('foo.bar.str', 'nope')).toBe(false); + expect(atom.config.get('foo.bar.str')).toBe('ok'); - expect(atom.config.set('foo.bar.str', 'one')).toBe(true) - expect(atom.config.get('foo.bar.str')).toBe('one') - }) + expect(atom.config.set('foo.bar.str', 'one')).toBe(true); + expect(atom.config.get('foo.bar.str')).toBe('one'); + }); it('will only set an integer when the integer is in the enum values', () => { - expect(atom.config.set('foo.bar.int', '400')).toBe(false) - expect(atom.config.get('foo.bar.int')).toBe(2) + expect(atom.config.set('foo.bar.int', '400')).toBe(false); + expect(atom.config.get('foo.bar.int')).toBe(2); - expect(atom.config.set('foo.bar.int', '3')).toBe(true) - expect(atom.config.get('foo.bar.int')).toBe(3) - }) + expect(atom.config.set('foo.bar.int', '3')).toBe(true); + expect(atom.config.get('foo.bar.int')).toBe(3); + }); it('will only set an array when the array values are in the enum values', () => { - expect(atom.config.set('foo.bar.arr', ['one', 'five'])).toBe(true) - expect(atom.config.get('foo.bar.arr')).toEqual(['one']) + expect(atom.config.set('foo.bar.arr', ['one', 'five'])).toBe(true); + expect(atom.config.get('foo.bar.arr')).toEqual(['one']); - expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe(true) - expect(atom.config.get('foo.bar.arr')).toEqual(['two', 'three']) - }) + expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe(true); + expect(atom.config.get('foo.bar.arr')).toEqual(['two', 'three']); + }); it('will honor the enum when specified as an array', () => { - expect(atom.config.set('foo.bar.str_options', 'one')).toBe(true) - expect(atom.config.get('foo.bar.str_options')).toEqual('one') + expect(atom.config.set('foo.bar.str_options', 'one')).toBe(true); + expect(atom.config.get('foo.bar.str_options')).toEqual('one'); - expect(atom.config.set('foo.bar.str_options', 'two')).toBe(true) - expect(atom.config.get('foo.bar.str_options')).toEqual('two') + expect(atom.config.set('foo.bar.str_options', 'two')).toBe(true); + expect(atom.config.get('foo.bar.str_options')).toEqual('two'); - expect(atom.config.set('foo.bar.str_options', 'One')).toBe(false) - expect(atom.config.get('foo.bar.str_options')).toEqual('two') - }) - }) - }) + expect(atom.config.set('foo.bar.str_options', 'One')).toBe(false); + expect(atom.config.get('foo.bar.str_options')).toEqual('two'); + }); + }); + }); describe('when .set/.unset is called prior to .resetUserSettings', () => { beforeEach(() => { - atom.config.settingsLoaded = false - }) + atom.config.settingsLoaded = false; + }); it('ensures that early set and unset calls are replayed after the config is loaded from disk', () => { - atom.config.unset('foo.bar') - atom.config.set('foo.qux', 'boo') + atom.config.unset('foo.bar'); + atom.config.set('foo.qux', 'boo'); - expect(atom.config.get('foo.bar')).toBeUndefined() - expect(atom.config.get('foo.qux')).toBe('boo') - expect(atom.config.get('do.ray')).toBeUndefined() + expect(atom.config.get('foo.bar')).toBeUndefined(); + expect(atom.config.get('foo.qux')).toBe('boo'); + expect(atom.config.get('do.ray')).toBeUndefined(); - advanceClock(100) - expect(savedSettings.length).toBe(0) + advanceClock(100); + expect(savedSettings.length).toBe(0); atom.config.resetUserSettings({ '*': { @@ -2241,121 +2258,121 @@ describe('Config', () => { ray: 'me' } } - }) + }); - advanceClock(100) - expect(savedSettings.length).toBe(1) - expect(atom.config.get('foo.bar')).toBeUndefined() - expect(atom.config.get('foo.qux')).toBe('boo') - expect(atom.config.get('do.ray')).toBe('me') - }) - }) + advanceClock(100); + expect(savedSettings.length).toBe(1); + expect(atom.config.get('foo.bar')).toBeUndefined(); + expect(atom.config.get('foo.qux')).toBe('boo'); + expect(atom.config.get('do.ray')).toBe('me'); + }); + }); describe('project specific settings', () => { describe('config.resetProjectSettings', () => { it('gracefully handles invalid config objects', () => { - atom.config.resetProjectSettings({}) - expect(atom.config.get('foo.bar')).toBeUndefined() - }) - }) + atom.config.resetProjectSettings({}); + expect(atom.config.get('foo.bar')).toBeUndefined(); + }); + }); describe('config.get', () => { - const dummyPath = '/Users/dummy/path.json' + const dummyPath = '/Users/dummy/path.json'; describe('project settings', () => { it('returns a deep clone of the property value', () => { atom.config.resetProjectSettings( { '*': { value: { array: [1, { b: 2 }, 3] } } }, dummyPath - ) - const retrievedValue = atom.config.get('value') - retrievedValue.array[0] = 4 - retrievedValue.array[1].b = 2.1 - expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] }) - }) + ); + const retrievedValue = atom.config.get('value'); + retrievedValue.array[0] = 4; + retrievedValue.array[1].b = 2.1; + expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] }); + }); it('properly gets project settings', () => { - atom.config.resetProjectSettings({ '*': { foo: 'wei' } }, dummyPath) - expect(atom.config.get('foo')).toBe('wei') + atom.config.resetProjectSettings({ '*': { foo: 'wei' } }, dummyPath); + expect(atom.config.get('foo')).toBe('wei'); atom.config.resetProjectSettings( { '*': { foo: { bar: 'baz' } } }, dummyPath - ) - expect(atom.config.get('foo.bar')).toBe('baz') - }) + ); + expect(atom.config.get('foo.bar')).toBe('baz'); + }); it('gets project settings with higher priority than regular settings', () => { - atom.config.set('foo', 'bar') - atom.config.resetProjectSettings({ '*': { foo: 'baz' } }, dummyPath) - expect(atom.config.get('foo')).toBe('baz') - }) + atom.config.set('foo', 'bar'); + atom.config.resetProjectSettings({ '*': { foo: 'baz' } }, dummyPath); + expect(atom.config.get('foo')).toBe('baz'); + }); it('correctly gets nested and scoped properties for project settings', () => { - expect(atom.config.set('foo.bar.str', 'global')).toBe(true) + expect(atom.config.set('foo.bar.str', 'global')).toBe(true); expect( atom.config.set('foo.bar.str', 'scoped', { scopeSelector: '.source.js' }) - ).toBe(true) - expect(atom.config.get('foo.bar.str')).toBe('global') + ).toBe(true); + expect(atom.config.get('foo.bar.str')).toBe('global'); expect( atom.config.get('foo.bar.str', { scope: ['.source.js'] }) - ).toBe('scoped') - }) + ).toBe('scoped'); + }); it('returns a deep clone of the property value', () => { - atom.config.set('value', { array: [1, { b: 2 }, 3] }) - const retrievedValue = atom.config.get('value') - retrievedValue.array[0] = 4 - retrievedValue.array[1].b = 2.1 - expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] }) - }) + atom.config.set('value', { array: [1, { b: 2 }, 3] }); + const retrievedValue = atom.config.get('value'); + retrievedValue.array[0] = 4; + retrievedValue.array[1].b = 2.1; + expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] }); + }); it('gets scoped values correctly', () => { - atom.config.set('foo', 'bam', { scope: ['second'] }) + atom.config.set('foo', 'bam', { scope: ['second'] }); expect(atom.config.get('foo', { scopeSelector: 'second' })).toBe( 'bam' - ) + ); atom.config.resetProjectSettings( { '*': { foo: 'baz' }, second: { foo: 'bar' } }, dummyPath - ) + ); expect(atom.config.get('foo', { scopeSelector: 'second' })).toBe( 'baz' - ) - atom.config.clearProjectSettings() + ); + atom.config.clearProjectSettings(); expect(atom.config.get('foo', { scopeSelector: 'second' })).toBe( 'bam' - ) - }) + ); + }); it('clears project settings correctly', () => { - atom.config.set('foo', 'bar') - expect(atom.config.get('foo')).toBe('bar') + atom.config.set('foo', 'bar'); + expect(atom.config.get('foo')).toBe('bar'); atom.config.resetProjectSettings( { '*': { foo: 'baz' }, second: { foo: 'bar' } }, dummyPath - ) - expect(atom.config.get('foo')).toBe('baz') - expect(atom.config.getSources().length).toBe(1) - atom.config.clearProjectSettings() - expect(atom.config.get('foo')).toBe('bar') - expect(atom.config.getSources().length).toBe(0) - }) - }) - }) + ); + expect(atom.config.get('foo')).toBe('baz'); + expect(atom.config.getSources().length).toBe(1); + atom.config.clearProjectSettings(); + expect(atom.config.get('foo')).toBe('bar'); + expect(atom.config.getSources().length).toBe(0); + }); + }); + }); describe('config.getAll', () => { - const dummyPath = '/Users/dummy/path.json' + const dummyPath = '/Users/dummy/path.json'; it('gets settings in the same way .get would return them', () => { - atom.config.resetProjectSettings({ '*': { a: 'b' } }, dummyPath) - atom.config.set('a', 'f') + atom.config.resetProjectSettings({ '*': { a: 'b' } }, dummyPath); + atom.config.set('a', 'f'); expect(atom.config.getAll('a')).toEqual([ { scopeSelector: '*', value: 'b' } - ]) - }) - }) - }) -}) + ]); + }); + }); + }); +}); diff --git a/spec/default-directory-searcher-spec.js b/spec/default-directory-searcher-spec.js index 1fdaf0c52..952668246 100644 --- a/spec/default-directory-searcher-spec.js +++ b/spec/default-directory-searcher-spec.js @@ -1,42 +1,42 @@ -const DefaultDirectorySearcher = require('../src/default-directory-searcher') -const Task = require('../src/task') -const path = require('path') +const DefaultDirectorySearcher = require('../src/default-directory-searcher'); +const Task = require('../src/task'); +const path = require('path'); -describe('DefaultDirectorySearcher', function () { - let searcher - let dirPath +describe('DefaultDirectorySearcher', function() { + let searcher; + let dirPath; - beforeEach(function () { - dirPath = path.resolve(__dirname, 'fixtures', 'dir') - searcher = new DefaultDirectorySearcher() - }) + beforeEach(function() { + dirPath = path.resolve(__dirname, 'fixtures', 'dir'); + searcher = new DefaultDirectorySearcher(); + }); - it('terminates the task after running a search', async function () { + it('terminates the task after running a search', async function() { const options = { ignoreCase: false, includeHidden: false, excludeVcsIgnores: true, inclusions: [], globalExclusions: ['a-dir'], - didMatch () {}, - didError () {}, - didSearchPaths () {} - } + didMatch() {}, + didError() {}, + didSearchPaths() {} + }; - spyOn(Task.prototype, 'terminate').andCallThrough() + spyOn(Task.prototype, 'terminate').andCallThrough(); await searcher.search( [ { - getPath () { - return dirPath + getPath() { + return dirPath; } } ], /abcdefg/, options - ) + ); - expect(Task.prototype.terminate).toHaveBeenCalled() - }) -}) + expect(Task.prototype.terminate).toHaveBeenCalled(); + }); +}); diff --git a/spec/dock-spec.js b/spec/dock-spec.js index c5000628f..6afc780b2 100644 --- a/spec/dock-spec.js +++ b/spec/dock-spec.js @@ -1,428 +1,428 @@ /** @babel */ -import etch from 'etch' +import etch from 'etch'; -const Grim = require('grim') +const Grim = require('grim'); -const getNextUpdatePromise = () => etch.getScheduler().nextUpdatePromise +const getNextUpdatePromise = () => etch.getScheduler().nextUpdatePromise; describe('Dock', () => { describe('when a dock is activated', () => { it('opens the dock and activates its active pane', () => { - jasmine.attachToDOM(atom.workspace.getElement()) - const dock = atom.workspace.getLeftDock() - const didChangeVisibleSpy = jasmine.createSpy() - dock.onDidChangeVisible(didChangeVisibleSpy) + jasmine.attachToDOM(atom.workspace.getElement()); + const dock = atom.workspace.getLeftDock(); + const didChangeVisibleSpy = jasmine.createSpy(); + dock.onDidChangeVisible(didChangeVisibleSpy); - expect(dock.isVisible()).toBe(false) + expect(dock.isVisible()).toBe(false); expect(document.activeElement).toBe( atom.workspace .getCenter() .getActivePane() .getElement() - ) - dock.activate() - expect(dock.isVisible()).toBe(true) - expect(document.activeElement).toBe(dock.getActivePane().getElement()) - expect(didChangeVisibleSpy).toHaveBeenCalledWith(true) - }) - }) + ); + dock.activate(); + expect(dock.isVisible()).toBe(true); + expect(document.activeElement).toBe(dock.getActivePane().getElement()); + expect(didChangeVisibleSpy).toHaveBeenCalledWith(true); + }); + }); describe('when a dock is hidden', () => { it('transfers focus back to the active center pane if the dock had focus', () => { - jasmine.attachToDOM(atom.workspace.getElement()) - const dock = atom.workspace.getLeftDock() - const didChangeVisibleSpy = jasmine.createSpy() - dock.onDidChangeVisible(didChangeVisibleSpy) + jasmine.attachToDOM(atom.workspace.getElement()); + const dock = atom.workspace.getLeftDock(); + const didChangeVisibleSpy = jasmine.createSpy(); + dock.onDidChangeVisible(didChangeVisibleSpy); - dock.activate() - expect(document.activeElement).toBe(dock.getActivePane().getElement()) - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true) + dock.activate(); + expect(document.activeElement).toBe(dock.getActivePane().getElement()); + expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true); - dock.hide() + dock.hide(); expect(document.activeElement).toBe( atom.workspace .getCenter() .getActivePane() .getElement() - ) - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false) + ); + expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false); - dock.activate() - expect(document.activeElement).toBe(dock.getActivePane().getElement()) - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true) + dock.activate(); + expect(document.activeElement).toBe(dock.getActivePane().getElement()); + expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true); - dock.toggle() + dock.toggle(); expect(document.activeElement).toBe( atom.workspace .getCenter() .getActivePane() .getElement() - ) - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false) + ); + expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false); // Don't change focus if the dock was not focused in the first place - const modalElement = document.createElement('div') - modalElement.setAttribute('tabindex', -1) - atom.workspace.addModalPanel({ item: modalElement }) - modalElement.focus() - expect(document.activeElement).toBe(modalElement) + const modalElement = document.createElement('div'); + modalElement.setAttribute('tabindex', -1); + atom.workspace.addModalPanel({ item: modalElement }); + modalElement.focus(); + expect(document.activeElement).toBe(modalElement); - dock.show() - expect(document.activeElement).toBe(modalElement) - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true) + dock.show(); + expect(document.activeElement).toBe(modalElement); + expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true); - dock.hide() - expect(document.activeElement).toBe(modalElement) - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false) - }) - }) + dock.hide(); + expect(document.activeElement).toBe(modalElement); + expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false); + }); + }); describe('when a pane in a dock is activated', () => { it('opens the dock', async () => { const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; } - } + }; - await atom.workspace.open(item, { activatePane: false }) - expect(atom.workspace.getLeftDock().isVisible()).toBe(false) + await atom.workspace.open(item, { activatePane: false }); + expect(atom.workspace.getLeftDock().isVisible()).toBe(false); atom.workspace .getLeftDock() .getPanes()[0] - .activate() - expect(atom.workspace.getLeftDock().isVisible()).toBe(true) - }) - }) + .activate(); + expect(atom.workspace.getLeftDock().isVisible()).toBe(true); + }); + }); describe('activating the next pane', () => { describe('when the dock has more than one pane', () => { it('activates the next pane', () => { - const dock = atom.workspace.getLeftDock() - const pane1 = dock.getPanes()[0] - const pane2 = pane1.splitRight() - const pane3 = pane2.splitRight() - pane2.activate() - expect(pane1.isActive()).toBe(false) - expect(pane2.isActive()).toBe(true) - expect(pane3.isActive()).toBe(false) + const dock = atom.workspace.getLeftDock(); + const pane1 = dock.getPanes()[0]; + const pane2 = pane1.splitRight(); + const pane3 = pane2.splitRight(); + pane2.activate(); + expect(pane1.isActive()).toBe(false); + expect(pane2.isActive()).toBe(true); + expect(pane3.isActive()).toBe(false); - dock.activateNextPane() - expect(pane1.isActive()).toBe(false) - expect(pane2.isActive()).toBe(false) - expect(pane3.isActive()).toBe(true) - }) - }) + dock.activateNextPane(); + expect(pane1.isActive()).toBe(false); + expect(pane2.isActive()).toBe(false); + expect(pane3.isActive()).toBe(true); + }); + }); describe('when the dock has only one pane', () => { it('leaves the current pane active', () => { - const dock = atom.workspace.getLeftDock() + const dock = atom.workspace.getLeftDock(); - expect(dock.getPanes().length).toBe(1) - const pane = dock.getPanes()[0] - expect(pane.isActive()).toBe(true) - dock.activateNextPane() - expect(pane.isActive()).toBe(true) - }) - }) - }) + expect(dock.getPanes().length).toBe(1); + const pane = dock.getPanes()[0]; + expect(pane.isActive()).toBe(true); + dock.activateNextPane(); + expect(pane.isActive()).toBe(true); + }); + }); + }); describe('activating the previous pane', () => { describe('when the dock has more than one pane', () => { it('activates the previous pane', () => { - const dock = atom.workspace.getLeftDock() - const pane1 = dock.getPanes()[0] - const pane2 = pane1.splitRight() - const pane3 = pane2.splitRight() - pane2.activate() - expect(pane1.isActive()).toBe(false) - expect(pane2.isActive()).toBe(true) - expect(pane3.isActive()).toBe(false) + const dock = atom.workspace.getLeftDock(); + const pane1 = dock.getPanes()[0]; + const pane2 = pane1.splitRight(); + const pane3 = pane2.splitRight(); + pane2.activate(); + expect(pane1.isActive()).toBe(false); + expect(pane2.isActive()).toBe(true); + expect(pane3.isActive()).toBe(false); - dock.activatePreviousPane() - expect(pane1.isActive()).toBe(true) - expect(pane2.isActive()).toBe(false) - expect(pane3.isActive()).toBe(false) - }) - }) + dock.activatePreviousPane(); + expect(pane1.isActive()).toBe(true); + expect(pane2.isActive()).toBe(false); + expect(pane3.isActive()).toBe(false); + }); + }); describe('when the dock has only one pane', () => { it('leaves the current pane active', () => { - const dock = atom.workspace.getLeftDock() + const dock = atom.workspace.getLeftDock(); - expect(dock.getPanes().length).toBe(1) - const pane = dock.getPanes()[0] - expect(pane.isActive()).toBe(true) - dock.activatePreviousPane() - expect(pane.isActive()).toBe(true) - }) - }) - }) + expect(dock.getPanes().length).toBe(1); + const pane = dock.getPanes()[0]; + expect(pane.isActive()).toBe(true); + dock.activatePreviousPane(); + expect(pane.isActive()).toBe(true); + }); + }); + }); describe('when the dock resize handle is double-clicked', () => { describe('when the dock is open', () => { it("resizes a vertically-oriented dock to the current item's preferred width", async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getPreferredWidth () { - return 142 + getPreferredWidth() { + return 142; }, - getPreferredHeight () { - return 122 + getPreferredHeight() { + return 122; } - } + }; - await atom.workspace.open(item) - const dock = atom.workspace.getLeftDock() - const dockElement = dock.getElement() + await atom.workspace.open(item); + const dock = atom.workspace.getLeftDock(); + const dockElement = dock.getElement(); - dock.setState({ size: 300 }) - await getNextUpdatePromise() - expect(dockElement.offsetWidth).toBe(300) + dock.setState({ size: 300 }); + await getNextUpdatePromise(); + expect(dockElement.offsetWidth).toBe(300); dockElement .querySelector('.atom-dock-resize-handle') - .dispatchEvent(new MouseEvent('mousedown', { detail: 2 })) - await getNextUpdatePromise() + .dispatchEvent(new MouseEvent('mousedown', { detail: 2 })); + await getNextUpdatePromise(); - expect(dockElement.offsetWidth).toBe(item.getPreferredWidth()) - }) + expect(dockElement.offsetWidth).toBe(item.getPreferredWidth()); + }); it("resizes a horizontally-oriented dock to the current item's preferred width", async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'bottom' + getDefaultLocation() { + return 'bottom'; }, - getPreferredWidth () { - return 122 + getPreferredWidth() { + return 122; }, - getPreferredHeight () { - return 142 + getPreferredHeight() { + return 142; } - } + }; - await atom.workspace.open(item) - const dock = atom.workspace.getBottomDock() - const dockElement = dock.getElement() + await atom.workspace.open(item); + const dock = atom.workspace.getBottomDock(); + const dockElement = dock.getElement(); - dock.setState({ size: 300 }) - await getNextUpdatePromise() - expect(dockElement.offsetHeight).toBe(300) + dock.setState({ size: 300 }); + await getNextUpdatePromise(); + expect(dockElement.offsetHeight).toBe(300); dockElement .querySelector('.atom-dock-resize-handle') - .dispatchEvent(new MouseEvent('mousedown', { detail: 2 })) - await getNextUpdatePromise() + .dispatchEvent(new MouseEvent('mousedown', { detail: 2 })); + await getNextUpdatePromise(); - expect(dockElement.offsetHeight).toBe(item.getPreferredHeight()) - }) - }) + expect(dockElement.offsetHeight).toBe(item.getPreferredHeight()); + }); + }); describe('when the dock is closed', () => { it('does nothing', async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'bottom' + getDefaultLocation() { + return 'bottom'; }, - getPreferredWidth () { - return 122 + getPreferredWidth() { + return 122; }, - getPreferredHeight () { - return 142 + getPreferredHeight() { + return 142; } - } + }; - await atom.workspace.open(item, { activatePane: false }) + await atom.workspace.open(item, { activatePane: false }); - const dockElement = atom.workspace.getBottomDock().getElement() + const dockElement = atom.workspace.getBottomDock().getElement(); dockElement .querySelector('.atom-dock-resize-handle') - .dispatchEvent(new MouseEvent('mousedown', { detail: 2 })) - expect(dockElement.offsetHeight).toBe(0) + .dispatchEvent(new MouseEvent('mousedown', { detail: 2 })); + expect(dockElement.offsetHeight).toBe(0); expect(dockElement.querySelector('.atom-dock-inner').offsetHeight).toBe( 0 - ) + ); // The content should be masked away. expect(dockElement.querySelector('.atom-dock-mask').offsetHeight).toBe( 0 - ) - }) - }) - }) + ); + }); + }); + }); describe('when you add an item to an empty dock', () => { describe('when the item has a preferred size', () => { it('is takes the preferred size of the item', async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const createItem = preferredWidth => ({ element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getPreferredWidth () { - return preferredWidth + getPreferredWidth() { + return preferredWidth; } - }) + }); - const dock = atom.workspace.getLeftDock() - const dockElement = dock.getElement() - expect(dock.getPaneItems()).toHaveLength(0) + const dock = atom.workspace.getLeftDock(); + const dockElement = dock.getElement(); + expect(dock.getPaneItems()).toHaveLength(0); - const item1 = createItem(111) - await atom.workspace.open(item1) + const item1 = createItem(111); + await atom.workspace.open(item1); // It should update the width every time we go from 0 -> 1 items, not just the first. - expect(dock.isVisible()).toBe(true) - expect(dockElement.offsetWidth).toBe(111) - dock.destroyActivePane() - expect(dock.getPaneItems()).toHaveLength(0) - expect(dock.isVisible()).toBe(false) - const item2 = createItem(222) - await atom.workspace.open(item2) - expect(dock.isVisible()).toBe(true) - expect(dockElement.offsetWidth).toBe(222) + expect(dock.isVisible()).toBe(true); + expect(dockElement.offsetWidth).toBe(111); + dock.destroyActivePane(); + expect(dock.getPaneItems()).toHaveLength(0); + expect(dock.isVisible()).toBe(false); + const item2 = createItem(222); + await atom.workspace.open(item2); + expect(dock.isVisible()).toBe(true); + expect(dockElement.offsetWidth).toBe(222); // Adding a second shouldn't change the size. - const item3 = createItem(333) - await atom.workspace.open(item3) - expect(dockElement.offsetWidth).toBe(222) - }) - }) + const item3 = createItem(333); + await atom.workspace.open(item3); + expect(dockElement.offsetWidth).toBe(222); + }); + }); describe('when the item has no preferred size', () => { it('is still has an explicit size', async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; } - } - const dock = atom.workspace.getLeftDock() - expect(dock.getPaneItems()).toHaveLength(0) + }; + const dock = atom.workspace.getLeftDock(); + expect(dock.getPaneItems()).toHaveLength(0); - expect(dock.state.size).toBe(null) - await atom.workspace.open(item) - expect(dock.state.size).not.toBe(null) - }) - }) - }) + expect(dock.state.size).toBe(null); + await atom.workspace.open(item); + expect(dock.state.size).not.toBe(null); + }); + }); + }); describe('a deserialized dock', () => { it('restores the serialized size', async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getPreferredWidth () { - return 122 + getPreferredWidth() { + return 122; }, serialize: () => ({ deserializer: 'DockTestItem' }) - } + }; atom.deserializers.add({ name: 'DockTestItem', deserialize: () => item - }) - const dock = atom.workspace.getLeftDock() - const dockElement = dock.getElement() + }); + const dock = atom.workspace.getLeftDock(); + const dockElement = dock.getElement(); - await atom.workspace.open(item) - dock.setState({ size: 150 }) - expect(dockElement.offsetWidth).toBe(150) - const serialized = dock.serialize() - dock.setState({ size: 122 }) - expect(dockElement.offsetWidth).toBe(122) - dock.destroyActivePane() - dock.deserialize(serialized, atom.deserializers) - expect(dockElement.offsetWidth).toBe(150) - }) + await atom.workspace.open(item); + dock.setState({ size: 150 }); + expect(dockElement.offsetWidth).toBe(150); + const serialized = dock.serialize(); + dock.setState({ size: 122 }); + expect(dockElement.offsetWidth).toBe(122); + dock.destroyActivePane(); + dock.deserialize(serialized, atom.deserializers); + expect(dockElement.offsetWidth).toBe(150); + }); it("isn't visible if it has no items", async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); const item = { element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getPreferredWidth () { - return 122 + getPreferredWidth() { + return 122; } - } - const dock = atom.workspace.getLeftDock() + }; + const dock = atom.workspace.getLeftDock(); - await atom.workspace.open(item) - expect(dock.isVisible()).toBe(true) - const serialized = dock.serialize() - dock.deserialize(serialized, atom.deserializers) - expect(dock.getPaneItems()).toHaveLength(0) - expect(dock.isVisible()).toBe(false) - }) - }) + await atom.workspace.open(item); + expect(dock.isVisible()).toBe(true); + const serialized = dock.serialize(); + dock.deserialize(serialized, atom.deserializers); + expect(dock.getPaneItems()).toHaveLength(0); + expect(dock.isVisible()).toBe(false); + }); + }); describe('drag handling', () => { it('expands docks to match the preferred size of the dragged item', async () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); - const element = document.createElement('div') - element.setAttribute('is', 'tabs-tab') + const element = document.createElement('div'); + element.setAttribute('is', 'tabs-tab'); element.item = { element, - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getPreferredWidth () { - return 144 + getPreferredWidth() { + return 144; } - } + }; - const dragEvent = new DragEvent('dragstart') - Object.defineProperty(dragEvent, 'target', { value: element }) + const dragEvent = new DragEvent('dragstart'); + Object.defineProperty(dragEvent, 'target', { value: element }); - atom.workspace.getElement().handleDragStart(dragEvent) - await getNextUpdatePromise() + atom.workspace.getElement().handleDragStart(dragEvent); + await getNextUpdatePromise(); expect(atom.workspace.getLeftDock().refs.wrapperElement.offsetWidth).toBe( 144 - ) - }) + ); + }); it('does nothing when text nodes are dragged', () => { - jasmine.attachToDOM(atom.workspace.getElement()) + jasmine.attachToDOM(atom.workspace.getElement()); - const textNode = document.createTextNode('hello') + const textNode = document.createTextNode('hello'); - const dragEvent = new DragEvent('dragstart') - Object.defineProperty(dragEvent, 'target', { value: textNode }) + const dragEvent = new DragEvent('dragstart'); + Object.defineProperty(dragEvent, 'target', { value: textNode }); expect(() => atom.workspace.getElement().handleDragStart(dragEvent) - ).not.toThrow() - }) - }) + ).not.toThrow(); + }); + }); describe('::getActiveTextEditor()', () => { it('is deprecated', () => { - spyOn(Grim, 'deprecate') + spyOn(Grim, 'deprecate'); - atom.workspace.getLeftDock().getActiveTextEditor() - expect(Grim.deprecate.callCount).toBe(1) - }) - }) -}) + atom.workspace.getLeftDock().getActiveTextEditor(); + expect(Grim.deprecate.callCount).toBe(1); + }); + }); +}); diff --git a/spec/git-repository-provider-spec.js b/spec/git-repository-provider-spec.js index 5a4c4ba07..a1ddad739 100644 --- a/spec/git-repository-provider-spec.js +++ b/spec/git-repository-provider-spec.js @@ -1,199 +1,218 @@ -const path = require('path') -const fs = require('fs-plus') -const temp = require('temp').track() -const { Directory } = require('pathwatcher') -const GitRepository = require('../src/git-repository') -const GitRepositoryProvider = require('../src/git-repository-provider') +const path = require('path'); +const fs = require('fs-plus'); +const temp = require('temp').track(); +const { Directory } = require('pathwatcher'); +const GitRepository = require('../src/git-repository'); +const GitRepositoryProvider = require('../src/git-repository-provider'); describe('GitRepositoryProvider', () => { - let provider + let provider; beforeEach(() => { provider = new GitRepositoryProvider( atom.project, atom.config, atom.confirm - ) - }) + ); + }); afterEach(() => { if (provider) { Object.keys(provider.pathToRepository).forEach(key => { - provider.pathToRepository[key].destroy() - }) + provider.pathToRepository[key].destroy(); + }); } - }) + }); describe('.repositoryForDirectory(directory)', () => { describe('when specified a Directory with a Git repository', () => { it('resolves with a GitRepository', async () => { const directory = new Directory( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - const result = await provider.repositoryForDirectory(directory) - expect(result).toBeInstanceOf(GitRepository) - expect(provider.pathToRepository[result.getPath()]).toBeTruthy() - expect(result.getType()).toBe('git') + ); + const result = await provider.repositoryForDirectory(directory); + expect(result).toBeInstanceOf(GitRepository); + expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); + expect(result.getType()).toBe('git'); // Refresh should be started - await new Promise(resolve => result.onDidChangeStatuses(resolve)) - }) + await new Promise(resolve => result.onDidChangeStatuses(resolve)); + }); it('resolves with the same GitRepository for different Directory objects in the same repo', async () => { const firstRepo = await provider.repositoryForDirectory( new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git')) - ) + ); const secondRepo = await provider.repositoryForDirectory( new Directory( path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects') ) - ) + ); - expect(firstRepo).toBeInstanceOf(GitRepository) - expect(firstRepo).toBe(secondRepo) - }) - }) + expect(firstRepo).toBeInstanceOf(GitRepository); + expect(firstRepo).toBe(secondRepo); + }); + }); describe('when specified a Directory without a Git repository', () => { it('resolves with null', async () => { - const directory = new Directory(temp.mkdirSync('dir')) - const repo = await provider.repositoryForDirectory(directory) - expect(repo).toBe(null) - }) - }) + const directory = new Directory(temp.mkdirSync('dir')); + const repo = await provider.repositoryForDirectory(directory); + expect(repo).toBe(null); + }); + }); describe('when specified a Directory with an invalid Git repository', () => { it('resolves with null', async () => { - const dirPath = temp.mkdirSync('dir') - fs.writeFileSync(path.join(dirPath, '.git', 'objects'), '') - fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), '') - fs.writeFileSync(path.join(dirPath, '.git', 'refs'), '') + const dirPath = temp.mkdirSync('dir'); + fs.writeFileSync(path.join(dirPath, '.git', 'objects'), ''); + fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), ''); + fs.writeFileSync(path.join(dirPath, '.git', 'refs'), ''); - const directory = new Directory(dirPath) - const repo = await provider.repositoryForDirectory(directory) - expect(repo).toBe(null) - }) - }) + const directory = new Directory(dirPath); + const repo = await provider.repositoryForDirectory(directory); + expect(repo).toBe(null); + }); + }); describe('when specified a Directory with a valid gitfile-linked repository', () => { it('returns a Promise that resolves to a GitRepository', async () => { - const gitDirPath = path.join(__dirname, 'fixtures', 'git', 'master.git') - const workDirPath = temp.mkdirSync('git-workdir') + const gitDirPath = path.join( + __dirname, + 'fixtures', + 'git', + 'master.git' + ); + const workDirPath = temp.mkdirSync('git-workdir'); fs.writeFileSync( path.join(workDirPath, '.git'), `gitdir: ${gitDirPath}\n` - ) + ); - const directory = new Directory(workDirPath) - const result = await provider.repositoryForDirectory(directory) - expect(result).toBeInstanceOf(GitRepository) - expect(provider.pathToRepository[result.getPath()]).toBeTruthy() - expect(result.getType()).toBe('git') - }) - }) + const directory = new Directory(workDirPath); + const result = await provider.repositoryForDirectory(directory); + expect(result).toBeInstanceOf(GitRepository); + expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); + expect(result.getType()).toBe('git'); + }); + }); describe('when specified a Directory without exists()', () => { - let directory + let directory; beforeEach(() => { // An implementation of Directory that does not implement existsSync(). - const subdirectory = {} + const subdirectory = {}; directory = { - getSubdirectory () {}, - isRoot () { - return true + getSubdirectory() {}, + isRoot() { + return true; } - } - spyOn(directory, 'getSubdirectory').andReturn(subdirectory) - }) + }; + spyOn(directory, 'getSubdirectory').andReturn(subdirectory); + }); it('returns a Promise that resolves to null', async () => { - const repo = await provider.repositoryForDirectory(directory) - expect(repo).toBe(null) - expect(directory.getSubdirectory).toHaveBeenCalledWith('.git') - }) - }) - }) + const repo = await provider.repositoryForDirectory(directory); + expect(repo).toBe(null); + expect(directory.getSubdirectory).toHaveBeenCalledWith('.git'); + }); + }); + }); describe('.repositoryForDirectorySync(directory)', () => { describe('when specified a Directory with a Git repository', () => { it('resolves with a GitRepository', async () => { - const directory = new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git')) - const result = provider.repositoryForDirectorySync(directory) - expect(result).toBeInstanceOf(GitRepository) - expect(provider.pathToRepository[result.getPath()]).toBeTruthy() - expect(result.getType()).toBe('git') + const directory = new Directory( + path.join(__dirname, 'fixtures', 'git', 'master.git') + ); + const result = provider.repositoryForDirectorySync(directory); + expect(result).toBeInstanceOf(GitRepository); + expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); + expect(result.getType()).toBe('git'); // Refresh should be started - await new Promise(resolve => result.onDidChangeStatuses(resolve)) - }) + await new Promise(resolve => result.onDidChangeStatuses(resolve)); + }); it('resolves with the same GitRepository for different Directory objects in the same repo', () => { const firstRepo = provider.repositoryForDirectorySync( new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git')) - ) + ); const secondRepo = provider.repositoryForDirectorySync( - new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects')) - ) + new Directory( + path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects') + ) + ); - expect(firstRepo).toBeInstanceOf(GitRepository) - expect(firstRepo).toBe(secondRepo) - }) - }) + expect(firstRepo).toBeInstanceOf(GitRepository); + expect(firstRepo).toBe(secondRepo); + }); + }); describe('when specified a Directory without a Git repository', () => { it('resolves with null', () => { - const directory = new Directory(temp.mkdirSync('dir')) - const repo = provider.repositoryForDirectorySync(directory) - expect(repo).toBe(null) - }) - }) + const directory = new Directory(temp.mkdirSync('dir')); + const repo = provider.repositoryForDirectorySync(directory); + expect(repo).toBe(null); + }); + }); describe('when specified a Directory with an invalid Git repository', () => { it('resolves with null', () => { - const dirPath = temp.mkdirSync('dir') - fs.writeFileSync(path.join(dirPath, '.git', 'objects'), '') - fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), '') - fs.writeFileSync(path.join(dirPath, '.git', 'refs'), '') + const dirPath = temp.mkdirSync('dir'); + fs.writeFileSync(path.join(dirPath, '.git', 'objects'), ''); + fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), ''); + fs.writeFileSync(path.join(dirPath, '.git', 'refs'), ''); - const directory = new Directory(dirPath) - const repo = provider.repositoryForDirectorySync(directory) - expect(repo).toBe(null) - }) - }) + const directory = new Directory(dirPath); + const repo = provider.repositoryForDirectorySync(directory); + expect(repo).toBe(null); + }); + }); describe('when specified a Directory with a valid gitfile-linked repository', () => { it('returns a Promise that resolves to a GitRepository', () => { - const gitDirPath = path.join(__dirname, 'fixtures', 'git', 'master.git') - const workDirPath = temp.mkdirSync('git-workdir') - fs.writeFileSync(path.join(workDirPath, '.git'), `gitdir: ${gitDirPath}\n`) + const gitDirPath = path.join( + __dirname, + 'fixtures', + 'git', + 'master.git' + ); + const workDirPath = temp.mkdirSync('git-workdir'); + fs.writeFileSync( + path.join(workDirPath, '.git'), + `gitdir: ${gitDirPath}\n` + ); - const directory = new Directory(workDirPath) - const result = provider.repositoryForDirectorySync(directory) - expect(result).toBeInstanceOf(GitRepository) - expect(provider.pathToRepository[result.getPath()]).toBeTruthy() - expect(result.getType()).toBe('git') - }) - }) + const directory = new Directory(workDirPath); + const result = provider.repositoryForDirectorySync(directory); + expect(result).toBeInstanceOf(GitRepository); + expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); + expect(result.getType()).toBe('git'); + }); + }); describe('when specified a Directory without existsSync()', () => { - let directory + let directory; beforeEach(() => { // An implementation of Directory that does not implement existsSync(). - const subdirectory = {} + const subdirectory = {}; directory = { - getSubdirectory () {}, - isRoot () { return true } - } - spyOn(directory, 'getSubdirectory').andReturn(subdirectory) - }) + getSubdirectory() {}, + isRoot() { + return true; + } + }; + spyOn(directory, 'getSubdirectory').andReturn(subdirectory); + }); it('returns null', () => { - const repo = provider.repositoryForDirectorySync(directory) - expect(repo).toBe(null) - expect(directory.getSubdirectory).toHaveBeenCalledWith('.git') - }) - }) - }) -}) + const repo = provider.repositoryForDirectorySync(directory); + expect(repo).toBe(null); + expect(directory.getSubdirectory).toHaveBeenCalledWith('.git'); + }); + }); + }); +}); diff --git a/spec/git-repository-spec.js b/spec/git-repository-spec.js index ed5699843..1a3401c9f 100644 --- a/spec/git-repository-spec.js +++ b/spec/git-repository-spec.js @@ -1,410 +1,410 @@ -const path = require('path') -const fs = require('fs-plus') -const temp = require('temp').track() -const GitRepository = require('../src/git-repository') -const Project = require('../src/project') +const path = require('path'); +const fs = require('fs-plus'); +const temp = require('temp').track(); +const GitRepository = require('../src/git-repository'); +const Project = require('../src/project'); describe('GitRepository', () => { - let repo + let repo; beforeEach(() => { - const gitPath = path.join(temp.dir, '.git') - if (fs.isDirectorySync(gitPath)) fs.removeSync(gitPath) - }) + const gitPath = path.join(temp.dir, '.git'); + if (fs.isDirectorySync(gitPath)) fs.removeSync(gitPath); + }); afterEach(() => { - if (repo && !repo.isDestroyed()) repo.destroy() - }) + if (repo && !repo.isDestroyed()) repo.destroy(); + }); describe('@open(path)', () => { it('returns null when no repository is found', () => { - expect(GitRepository.open(path.join(temp.dir, 'nogit.txt'))).toBeNull() - }) - }) + expect(GitRepository.open(path.join(temp.dir, 'nogit.txt'))).toBeNull(); + }); + }); describe('new GitRepository(path)', () => { it('throws an exception when no repository is found', () => { expect( () => new GitRepository(path.join(temp.dir, 'nogit.txt')) - ).toThrow() - }) - }) + ).toThrow(); + }); + }); describe('.getPath()', () => { 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') - ) - }) + ); + }); it('returns the repository path for a repository path', () => { repo = new GitRepository( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) + ); expect(repo.getPath()).toBe( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - }) - }) + ); + }); + }); describe('.isPathIgnored(path)', () => { it('returns true for an ignored path', () => { repo = new GitRepository( path.join(__dirname, 'fixtures', 'git', 'ignore.git') - ) - expect(repo.isPathIgnored('a.txt')).toBeTruthy() - }) + ); + expect(repo.isPathIgnored('a.txt')).toBeTruthy(); + }); it('returns false for a non-ignored path', () => { repo = new GitRepository( path.join(__dirname, 'fixtures', 'git', 'ignore.git') - ) - expect(repo.isPathIgnored('b.txt')).toBeFalsy() - }) - }) + ); + expect(repo.isPathIgnored('b.txt')).toBeFalsy(); + }); + }); describe('.isPathModified(path)', () => { - let filePath, newPath + let filePath, newPath; beforeEach(() => { - const workingDirPath = copyRepository() - repo = new GitRepository(workingDirPath) - filePath = path.join(workingDirPath, 'a.txt') - newPath = path.join(workingDirPath, 'new-path.txt') - }) + const workingDirPath = copyRepository(); + repo = new GitRepository(workingDirPath); + filePath = path.join(workingDirPath, 'a.txt'); + newPath = path.join(workingDirPath, 'new-path.txt'); + }); describe('when the path is unstaged', () => { it('returns false if the path has not been modified', () => { - expect(repo.isPathModified(filePath)).toBeFalsy() - }) + expect(repo.isPathModified(filePath)).toBeFalsy(); + }); it('returns true if the path is modified', () => { - fs.writeFileSync(filePath, 'change') - expect(repo.isPathModified(filePath)).toBeTruthy() - }) + fs.writeFileSync(filePath, 'change'); + expect(repo.isPathModified(filePath)).toBeTruthy(); + }); it('returns true if the path is deleted', () => { - fs.removeSync(filePath) - expect(repo.isPathModified(filePath)).toBeTruthy() - }) + fs.removeSync(filePath); + expect(repo.isPathModified(filePath)).toBeTruthy(); + }); it('returns false if the path is new', () => { - expect(repo.isPathModified(newPath)).toBeFalsy() - }) - }) - }) + expect(repo.isPathModified(newPath)).toBeFalsy(); + }); + }); + }); describe('.isPathNew(path)', () => { - let filePath, newPath + let filePath, newPath; beforeEach(() => { - const workingDirPath = copyRepository() - repo = new GitRepository(workingDirPath) - filePath = path.join(workingDirPath, 'a.txt') - newPath = path.join(workingDirPath, 'new-path.txt') - fs.writeFileSync(newPath, "i'm new here") - }) + const workingDirPath = copyRepository(); + repo = new GitRepository(workingDirPath); + filePath = path.join(workingDirPath, 'a.txt'); + newPath = path.join(workingDirPath, 'new-path.txt'); + fs.writeFileSync(newPath, "i'm new here"); + }); describe('when the path is unstaged', () => { it('returns true if the path is new', () => { - expect(repo.isPathNew(newPath)).toBeTruthy() - }) + expect(repo.isPathNew(newPath)).toBeTruthy(); + }); it("returns false if the path isn't new", () => { - expect(repo.isPathNew(filePath)).toBeFalsy() - }) - }) - }) + expect(repo.isPathNew(filePath)).toBeFalsy(); + }); + }); + }); describe('.checkoutHead(path)', () => { - let filePath + let filePath; beforeEach(() => { - const workingDirPath = copyRepository() - repo = new GitRepository(workingDirPath) - filePath = path.join(workingDirPath, 'a.txt') - }) + const workingDirPath = copyRepository(); + repo = new GitRepository(workingDirPath); + filePath = path.join(workingDirPath, 'a.txt'); + }); it('no longer reports a path as modified after checkout', () => { - expect(repo.isPathModified(filePath)).toBeFalsy() - fs.writeFileSync(filePath, 'ch ch changes') - expect(repo.isPathModified(filePath)).toBeTruthy() - expect(repo.checkoutHead(filePath)).toBeTruthy() - expect(repo.isPathModified(filePath)).toBeFalsy() - }) + expect(repo.isPathModified(filePath)).toBeFalsy(); + fs.writeFileSync(filePath, 'ch ch changes'); + expect(repo.isPathModified(filePath)).toBeTruthy(); + expect(repo.checkoutHead(filePath)).toBeTruthy(); + expect(repo.isPathModified(filePath)).toBeFalsy(); + }); it('restores the contents of the path to the original text', () => { - fs.writeFileSync(filePath, 'ch ch changes') - expect(repo.checkoutHead(filePath)).toBeTruthy() - expect(fs.readFileSync(filePath, 'utf8')).toBe('') - }) + fs.writeFileSync(filePath, 'ch ch changes'); + expect(repo.checkoutHead(filePath)).toBeTruthy(); + expect(fs.readFileSync(filePath, 'utf8')).toBe(''); + }); it('fires a status-changed event if the checkout completes successfully', () => { - fs.writeFileSync(filePath, 'ch ch changes') - repo.getPathStatus(filePath) - const statusHandler = jasmine.createSpy('statusHandler') - repo.onDidChangeStatus(statusHandler) - repo.checkoutHead(filePath) - expect(statusHandler.callCount).toBe(1) + fs.writeFileSync(filePath, 'ch ch changes'); + repo.getPathStatus(filePath); + const statusHandler = jasmine.createSpy('statusHandler'); + repo.onDidChangeStatus(statusHandler); + repo.checkoutHead(filePath); + expect(statusHandler.callCount).toBe(1); expect(statusHandler.argsForCall[0][0]).toEqual({ path: filePath, pathStatus: 0 - }) + }); - repo.checkoutHead(filePath) - expect(statusHandler.callCount).toBe(1) - }) - }) + repo.checkoutHead(filePath); + expect(statusHandler.callCount).toBe(1); + }); + }); describe('.checkoutHeadForEditor(editor)', () => { - let filePath, editor + let filePath, editor; beforeEach(async () => { - spyOn(atom, 'confirm') + spyOn(atom, 'confirm'); - const workingDirPath = copyRepository() + const workingDirPath = copyRepository(); repo = new GitRepository(workingDirPath, { project: atom.project, config: atom.config, confirm: atom.confirm - }) - filePath = path.join(workingDirPath, 'a.txt') - fs.writeFileSync(filePath, 'ch ch changes') + }); + filePath = path.join(workingDirPath, 'a.txt'); + fs.writeFileSync(filePath, 'ch ch changes'); - editor = await atom.workspace.open(filePath) - }) + editor = await atom.workspace.open(filePath); + }); it('displays a confirmation dialog by default', () => { // Permissions issues with this test on Windows - if (process.platform === 'win32') return + if (process.platform === 'win32') return; - atom.confirm.andCallFake(({ buttons }) => buttons.OK()) - atom.config.set('editor.confirmCheckoutHeadRevision', true) + atom.confirm.andCallFake(({ buttons }) => buttons.OK()); + atom.config.set('editor.confirmCheckoutHeadRevision', true); - repo.checkoutHeadForEditor(editor) + repo.checkoutHeadForEditor(editor); - expect(fs.readFileSync(filePath, 'utf8')).toBe('') - }) + expect(fs.readFileSync(filePath, 'utf8')).toBe(''); + }); it('does not display a dialog when confirmation is disabled', () => { // Flakey EPERM opening a.txt on Win32 - if (process.platform === 'win32') return - atom.config.set('editor.confirmCheckoutHeadRevision', false) + if (process.platform === 'win32') return; + atom.config.set('editor.confirmCheckoutHeadRevision', false); - repo.checkoutHeadForEditor(editor) + repo.checkoutHeadForEditor(editor); - expect(fs.readFileSync(filePath, 'utf8')).toBe('') - expect(atom.confirm).not.toHaveBeenCalled() - }) - }) + expect(fs.readFileSync(filePath, 'utf8')).toBe(''); + expect(atom.confirm).not.toHaveBeenCalled(); + }); + }); describe('.destroy()', () => { it('throws an exception when any method is called after it is called', () => { repo = new GitRepository( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - repo.destroy() - expect(() => repo.getShortHead()).toThrow() - }) - }) + ); + repo.destroy(); + expect(() => repo.getShortHead()).toThrow(); + }); + }); describe('.getPathStatus(path)', () => { - let filePath + let filePath; beforeEach(() => { - const workingDirectory = copyRepository() - repo = new GitRepository(workingDirectory) - filePath = path.join(workingDirectory, 'file.txt') - }) + const workingDirectory = copyRepository(); + repo = new GitRepository(workingDirectory); + filePath = path.join(workingDirectory, 'file.txt'); + }); it('trigger a status-changed event when the new status differs from the last cached one', () => { - const statusHandler = jasmine.createSpy('statusHandler') - repo.onDidChangeStatus(statusHandler) - fs.writeFileSync(filePath, '') - let status = repo.getPathStatus(filePath) - expect(statusHandler.callCount).toBe(1) + const statusHandler = jasmine.createSpy('statusHandler'); + repo.onDidChangeStatus(statusHandler); + fs.writeFileSync(filePath, ''); + let status = repo.getPathStatus(filePath); + expect(statusHandler.callCount).toBe(1); expect(statusHandler.argsForCall[0][0]).toEqual({ path: filePath, pathStatus: status - }) + }); - fs.writeFileSync(filePath, 'abc') - status = repo.getPathStatus(filePath) - expect(statusHandler.callCount).toBe(1) - }) - }) + fs.writeFileSync(filePath, 'abc'); + status = repo.getPathStatus(filePath); + expect(statusHandler.callCount).toBe(1); + }); + }); describe('.getDirectoryStatus(path)', () => { - let directoryPath, filePath + let directoryPath, filePath; beforeEach(() => { - const workingDirectory = copyRepository() - repo = new GitRepository(workingDirectory) - directoryPath = path.join(workingDirectory, 'dir') - filePath = path.join(directoryPath, 'b.txt') - }) + const workingDirectory = copyRepository(); + repo = new GitRepository(workingDirectory); + directoryPath = path.join(workingDirectory, 'dir'); + filePath = path.join(directoryPath, 'b.txt'); + }); it('gets the status based on the files inside the directory', () => { expect( repo.isStatusModified(repo.getDirectoryStatus(directoryPath)) - ).toBe(false) - fs.writeFileSync(filePath, 'abc') - repo.getPathStatus(filePath) + ).toBe(false); + fs.writeFileSync(filePath, 'abc'); + repo.getPathStatus(filePath); expect( repo.isStatusModified(repo.getDirectoryStatus(directoryPath)) - ).toBe(true) - }) - }) + ).toBe(true); + }); + }); describe('.refreshStatus()', () => { - let newPath, modifiedPath, cleanPath, workingDirectory + let newPath, modifiedPath, cleanPath, workingDirectory; beforeEach(() => { - workingDirectory = copyRepository() + workingDirectory = copyRepository(); repo = new GitRepository(workingDirectory, { project: atom.project, config: atom.config - }) - modifiedPath = path.join(workingDirectory, 'file.txt') - newPath = path.join(workingDirectory, 'untracked.txt') - cleanPath = path.join(workingDirectory, 'other.txt') - fs.writeFileSync(cleanPath, 'Full of text') - fs.writeFileSync(newPath, '') - newPath = fs.absolute(newPath) - }) + }); + modifiedPath = path.join(workingDirectory, 'file.txt'); + newPath = path.join(workingDirectory, 'untracked.txt'); + cleanPath = path.join(workingDirectory, 'other.txt'); + fs.writeFileSync(cleanPath, 'Full of text'); + fs.writeFileSync(newPath, ''); + newPath = fs.absolute(newPath); + }); it('returns status information for all new and modified files', async () => { - const statusHandler = jasmine.createSpy('statusHandler') - repo.onDidChangeStatuses(statusHandler) - fs.writeFileSync(modifiedPath, 'making this path modified') + const statusHandler = jasmine.createSpy('statusHandler'); + repo.onDidChangeStatuses(statusHandler); + fs.writeFileSync(modifiedPath, 'making this path modified'); - await repo.refreshStatus() - expect(statusHandler.callCount).toBe(1) - expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined() - expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy() + await repo.refreshStatus(); + expect(statusHandler.callCount).toBe(1); + expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined(); + expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy(); expect( repo.isStatusModified(repo.getCachedPathStatus(modifiedPath)) - ).toBeTruthy() - }) + ).toBeTruthy(); + }); it('caches the proper statuses when a subdir is open', async () => { - const subDir = path.join(workingDirectory, 'dir') - fs.mkdirSync(subDir) - const filePath = path.join(subDir, 'b.txt') - fs.writeFileSync(filePath, '') - atom.project.setPaths([subDir]) - await atom.workspace.open('b.txt') - repo = atom.project.getRepositories()[0] + const subDir = path.join(workingDirectory, 'dir'); + fs.mkdirSync(subDir); + const filePath = path.join(subDir, 'b.txt'); + fs.writeFileSync(filePath, ''); + atom.project.setPaths([subDir]); + await atom.workspace.open('b.txt'); + repo = atom.project.getRepositories()[0]; - await repo.refreshStatus() - const status = repo.getCachedPathStatus(filePath) - expect(repo.isStatusModified(status)).toBe(false) - expect(repo.isStatusNew(status)).toBe(false) - }) + await repo.refreshStatus(); + const status = repo.getCachedPathStatus(filePath); + expect(repo.isStatusModified(status)).toBe(false); + expect(repo.isStatusNew(status)).toBe(false); + }); it('works correctly when the project has multiple folders (regression)', async () => { - atom.project.addPath(workingDirectory) - atom.project.addPath(path.join(__dirname, 'fixtures', 'dir')) + atom.project.addPath(workingDirectory); + atom.project.addPath(path.join(__dirname, 'fixtures', 'dir')); - await repo.refreshStatus() - expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined() - expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy() + await repo.refreshStatus(); + expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined(); + expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy(); expect( repo.isStatusModified(repo.getCachedPathStatus(modifiedPath)) - ).toBeTruthy() - }) + ).toBeTruthy(); + }); it('caches statuses that were looked up synchronously', async () => { - const originalContent = 'undefined' - fs.writeFileSync(modifiedPath, 'making this path modified') - repo.getPathStatus('file.txt') + const originalContent = 'undefined'; + fs.writeFileSync(modifiedPath, 'making this path modified'); + repo.getPathStatus('file.txt'); - fs.writeFileSync(modifiedPath, originalContent) - await repo.refreshStatus() + fs.writeFileSync(modifiedPath, originalContent); + await repo.refreshStatus(); expect( repo.isStatusModified(repo.getCachedPathStatus(modifiedPath)) - ).toBeFalsy() - }) - }) + ).toBeFalsy(); + }); + }); describe('buffer events', () => { - let editor + let editor; beforeEach(async () => { - atom.project.setPaths([copyRepository()]) + atom.project.setPaths([copyRepository()]); const refreshPromise = new Promise(resolve => atom.project.getRepositories()[0].onDidChangeStatuses(resolve) - ) - editor = await atom.workspace.open('other.txt') - await refreshPromise - }) + ); + editor = await atom.workspace.open('other.txt'); + await refreshPromise; + }); it('emits a status-changed event when a buffer is saved', async () => { - editor.insertNewline() + editor.insertNewline(); - const statusHandler = jasmine.createSpy('statusHandler') - atom.project.getRepositories()[0].onDidChangeStatus(statusHandler) + const statusHandler = jasmine.createSpy('statusHandler'); + atom.project.getRepositories()[0].onDidChangeStatus(statusHandler); - await editor.save() - expect(statusHandler.callCount).toBe(1) + await editor.save(); + expect(statusHandler.callCount).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: editor.getPath(), pathStatus: 256 - }) - }) + }); + }); it('emits a status-changed event when a buffer is reloaded', async () => { - fs.writeFileSync(editor.getPath(), 'changed') + fs.writeFileSync(editor.getPath(), 'changed'); - const statusHandler = jasmine.createSpy('statusHandler') - atom.project.getRepositories()[0].onDidChangeStatus(statusHandler) + const statusHandler = jasmine.createSpy('statusHandler'); + atom.project.getRepositories()[0].onDidChangeStatus(statusHandler); - await editor.getBuffer().reload() - expect(statusHandler.callCount).toBe(1) + await editor.getBuffer().reload(); + expect(statusHandler.callCount).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: editor.getPath(), pathStatus: 256 - }) + }); - await editor.getBuffer().reload() - expect(statusHandler.callCount).toBe(1) - }) + await editor.getBuffer().reload(); + expect(statusHandler.callCount).toBe(1); + }); it("emits a status-changed event when a buffer's path changes", () => { - fs.writeFileSync(editor.getPath(), 'changed') + fs.writeFileSync(editor.getPath(), 'changed'); - const statusHandler = jasmine.createSpy('statusHandler') - atom.project.getRepositories()[0].onDidChangeStatus(statusHandler) - editor.getBuffer().emitter.emit('did-change-path') - expect(statusHandler.callCount).toBe(1) + const statusHandler = jasmine.createSpy('statusHandler'); + atom.project.getRepositories()[0].onDidChangeStatus(statusHandler); + editor.getBuffer().emitter.emit('did-change-path'); + expect(statusHandler.callCount).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: editor.getPath(), pathStatus: 256 - }) - editor.getBuffer().emitter.emit('did-change-path') - expect(statusHandler.callCount).toBe(1) - }) + }); + editor.getBuffer().emitter.emit('did-change-path'); + expect(statusHandler.callCount).toBe(1); + }); it('stops listening to the buffer when the repository is destroyed (regression)', () => { - atom.project.getRepositories()[0].destroy() - expect(() => editor.save()).not.toThrow() - }) - }) + atom.project.getRepositories()[0].destroy(); + expect(() => editor.save()).not.toThrow(); + }); + }); describe('when a project is deserialized', () => { - let buffer, project2, statusHandler + let buffer, project2, statusHandler; afterEach(() => { - if (project2) project2.destroy() - }) + if (project2) project2.destroy(); + }); it('subscribes to all the serialized buffers in the project', async () => { - atom.project.setPaths([copyRepository()]) + atom.project.setPaths([copyRepository()]); - await atom.workspace.open('file.txt') + await atom.workspace.open('file.txt'); project2 = new Project({ notificationManager: atom.notifications, @@ -412,34 +412,36 @@ describe('GitRepository', () => { confirm: atom.confirm, grammarRegistry: atom.grammars, applicationDelegate: atom.applicationDelegate - }) - await project2.deserialize(atom.project.serialize({ isUnloading: false })) + }); + await project2.deserialize( + atom.project.serialize({ isUnloading: false }) + ); - buffer = project2.getBuffers()[0] - buffer.append('changes') + buffer = project2.getBuffers()[0]; + buffer.append('changes'); - statusHandler = jasmine.createSpy('statusHandler') - project2.getRepositories()[0].onDidChangeStatus(statusHandler) - await buffer.save() + statusHandler = jasmine.createSpy('statusHandler'); + project2.getRepositories()[0].onDidChangeStatus(statusHandler); + await buffer.save(); - expect(statusHandler.callCount).toBe(1) + expect(statusHandler.callCount).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: buffer.getPath(), pathStatus: 256 - }) - }) - }) -}) + }); + }); + }); +}); -function copyRepository () { - const workingDirPath = temp.mkdirSync('atom-spec-git') +function copyRepository() { + const workingDirPath = temp.mkdirSync('atom-spec-git'); fs.copySync( path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath - ) + ); fs.renameSync( path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git') - ) - return workingDirPath + ); + return workingDirPath; } diff --git a/spec/grammar-registry-spec.js b/spec/grammar-registry-spec.js index 6b1902241..da17bdc2c 100644 --- a/spec/grammar-registry-spec.js +++ b/spec/grammar-registry-spec.js @@ -1,419 +1,425 @@ -const dedent = require('dedent') -const path = require('path') -const fs = require('fs-plus') -const temp = require('temp').track() -const TextBuffer = require('text-buffer') -const GrammarRegistry = require('../src/grammar-registry') -const TreeSitterGrammar = require('../src/tree-sitter-grammar') -const FirstMate = require('first-mate') -const { OnigRegExp } = require('oniguruma') +const dedent = require('dedent'); +const path = require('path'); +const fs = require('fs-plus'); +const temp = require('temp').track(); +const TextBuffer = require('text-buffer'); +const GrammarRegistry = require('../src/grammar-registry'); +const TreeSitterGrammar = require('../src/tree-sitter-grammar'); +const FirstMate = require('first-mate'); +const { OnigRegExp } = require('oniguruma'); describe('GrammarRegistry', () => { - let grammarRegistry + let grammarRegistry; beforeEach(() => { - grammarRegistry = new GrammarRegistry({ config: atom.config }) - expect(subscriptionCount(grammarRegistry)).toBe(1) - }) + grammarRegistry = new GrammarRegistry({ config: atom.config }); + expect(subscriptionCount(grammarRegistry)).toBe(1); + }); describe('.assignLanguageMode(buffer, languageId)', () => { it('assigns to the buffer a language mode with the given language id', async () => { grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-css/grammars/css.cson') - ) + ); - const buffer = new TextBuffer() - expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe(true) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js') - expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe('source.js') + const buffer = new TextBuffer(); + expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe( + true + ); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js'); + expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe('source.js'); // Returns true if we found the grammar, even if it didn't change - expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe(true) + expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe( + true + ); // Language names are not case-sensitive expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe( true - ) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css') + ); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); // Returns false if no language is found - expect(grammarRegistry.assignLanguageMode(buffer, 'blub')).toBe(false) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css') - }) + expect(grammarRegistry.assignLanguageMode(buffer, 'blub')).toBe(false); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); + }); describe('when no languageId is passed', () => { it('makes the buffer use the null grammar', () => { grammarRegistry.loadGrammarSync( require.resolve('language-css/grammars/css.cson') - ) + ); - const buffer = new TextBuffer() + const buffer = new TextBuffer(); expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe( true - ) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css') + ); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); - expect(grammarRegistry.assignLanguageMode(buffer, null)).toBe(true) + expect(grammarRegistry.assignLanguageMode(buffer, null)).toBe(true); expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' - ) - expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe(null) - }) - }) - }) + ); + expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe(null); + }); + }); + }); describe('.grammarForId(languageId)', () => { it('returns a text-mate grammar when `core.useTreeSitterParsers` is false', () => { atom.config.set('core.useTreeSitterParsers', false, { scopeSelector: '.source.js' - }) + }); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) + ); - const grammar = grammarRegistry.grammarForId('source.js') - expect(grammar instanceof FirstMate.Grammar).toBe(true) - expect(grammar.scopeName).toBe('source.js') + const grammar = grammarRegistry.grammarForId('source.js'); + expect(grammar instanceof FirstMate.Grammar).toBe(true); + expect(grammar.scopeName).toBe('source.js'); - grammarRegistry.removeGrammar(grammar) - expect(grammarRegistry.grammarForId('javascript')).toBe(undefined) - }) + grammarRegistry.removeGrammar(grammar); + expect(grammarRegistry.grammarForId('javascript')).toBe(undefined); + }); it('returns a tree-sitter grammar when `core.useTreeSitterParsers` is true', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) + ); - const grammar = grammarRegistry.grammarForId('source.js') - expect(grammar instanceof TreeSitterGrammar).toBe(true) - expect(grammar.scopeName).toBe('source.js') + const grammar = grammarRegistry.grammarForId('source.js'); + expect(grammar instanceof TreeSitterGrammar).toBe(true); + expect(grammar.scopeName).toBe('source.js'); - grammarRegistry.removeGrammar(grammar) + grammarRegistry.removeGrammar(grammar); expect( grammarRegistry.grammarForId('source.js') instanceof FirstMate.Grammar - ).toBe(true) - }) - }) + ).toBe(true); + }); + }); describe('.autoAssignLanguageMode(buffer)', () => { it('assigns to the buffer a language mode based on the best available grammar', () => { grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-css/grammars/css.cson') - ) + ); - const buffer = new TextBuffer() - buffer.setPath('foo.js') + const buffer = new TextBuffer(); + buffer.setPath('foo.js'); expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe( true - ) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css') + ); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); - grammarRegistry.autoAssignLanguageMode(buffer) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js') - }) - }) + grammarRegistry.autoAssignLanguageMode(buffer); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js'); + }); + }); describe('.maintainLanguageMode(buffer)', () => { it('assigns a grammar to the buffer based on its path', async () => { - const buffer = new TextBuffer() + const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/c.cson') - ) + ); - buffer.setPath('test.js') - grammarRegistry.maintainLanguageMode(buffer) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js') + buffer.setPath('test.js'); + grammarRegistry.maintainLanguageMode(buffer); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js'); - buffer.setPath('test.c') - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.c') - }) + buffer.setPath('test.c'); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.c'); + }); it("updates the buffer's grammar when a more appropriate text-mate grammar is added for its path", async () => { - atom.config.set('core.useTreeSitterParsers', false) + atom.config.set('core.useTreeSitterParsers', false); - const buffer = new TextBuffer() - expect(buffer.getLanguageMode().getLanguageId()).toBe(null) + const buffer = new TextBuffer(); + expect(buffer.getLanguageMode().getLanguageId()).toBe(null); - buffer.setPath('test.js') - grammarRegistry.maintainLanguageMode(buffer) + buffer.setPath('test.js'); + grammarRegistry.maintainLanguageMode(buffer); const textMateGrammar = grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) - expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar) + ); + expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) - expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar) - }) + ); + expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar); + }); it("updates the buffer's grammar when a more appropriate tree-sitter grammar is added for its path", async () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); - const buffer = new TextBuffer() - expect(buffer.getLanguageMode().getLanguageId()).toBe(null) + const buffer = new TextBuffer(); + expect(buffer.getLanguageMode().getLanguageId()).toBe(null); - buffer.setPath('test.js') - grammarRegistry.maintainLanguageMode(buffer) + buffer.setPath('test.js'); + grammarRegistry.maintainLanguageMode(buffer); const treeSitterGrammar = grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) - expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar) + ); + expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) - expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar) - }) + ); + expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar); + }); it('can be overridden by calling .assignLanguageMode', () => { - const buffer = new TextBuffer() + const buffer = new TextBuffer(); - buffer.setPath('test.js') - grammarRegistry.maintainLanguageMode(buffer) + buffer.setPath('test.js'); + grammarRegistry.maintainLanguageMode(buffer); grammarRegistry.loadGrammarSync( require.resolve('language-css/grammars/css.cson') - ) + ); expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe( true - ) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css') + ); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css') - }) + ); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); + }); it('returns a disposable that can be used to stop the registry from updating the buffer', async () => { - const buffer = new TextBuffer() + const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); - const previousSubscriptionCount = buffer.emitter.getTotalListenerCount() - const disposable = grammarRegistry.maintainLanguageMode(buffer) + const previousSubscriptionCount = buffer.emitter.getTotalListenerCount(); + const disposable = grammarRegistry.maintainLanguageMode(buffer); expect(buffer.emitter.getTotalListenerCount()).toBeGreaterThan( previousSubscriptionCount - ) - expect(retainedBufferCount(grammarRegistry)).toBe(1) + ); + expect(retainedBufferCount(grammarRegistry)).toBe(1); - buffer.setPath('test.js') - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js') + buffer.setPath('test.js'); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js'); - buffer.setPath('test.txt') + buffer.setPath('test.txt'); expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' - ) + ); - disposable.dispose() + disposable.dispose(); expect(buffer.emitter.getTotalListenerCount()).toBe( previousSubscriptionCount - ) - expect(retainedBufferCount(grammarRegistry)).toBe(0) + ); + expect(retainedBufferCount(grammarRegistry)).toBe(0); - buffer.setPath('test.js') + buffer.setPath('test.js'); expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' - ) - expect(retainedBufferCount(grammarRegistry)).toBe(0) - }) + ); + expect(retainedBufferCount(grammarRegistry)).toBe(0); + }); it("doesn't do anything when called a second time with the same buffer", async () => { - const buffer = new TextBuffer() + const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) - const disposable1 = grammarRegistry.maintainLanguageMode(buffer) - const disposable2 = grammarRegistry.maintainLanguageMode(buffer) + ); + const disposable1 = grammarRegistry.maintainLanguageMode(buffer); + const disposable2 = grammarRegistry.maintainLanguageMode(buffer); - buffer.setPath('test.js') - expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js') + buffer.setPath('test.js'); + expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js'); - disposable2.dispose() - buffer.setPath('test.txt') + disposable2.dispose(); + buffer.setPath('test.txt'); expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' - ) + ); - disposable1.dispose() - buffer.setPath('test.js') + disposable1.dispose(); + buffer.setPath('test.js'); expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' - ) - }) + ); + }); it('does not retain the buffer after the buffer is destroyed', () => { - const buffer = new TextBuffer() + const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); - const disposable = grammarRegistry.maintainLanguageMode(buffer) - expect(retainedBufferCount(grammarRegistry)).toBe(1) - expect(subscriptionCount(grammarRegistry)).toBe(3) + const disposable = grammarRegistry.maintainLanguageMode(buffer); + expect(retainedBufferCount(grammarRegistry)).toBe(1); + expect(subscriptionCount(grammarRegistry)).toBe(3); - buffer.destroy() - expect(retainedBufferCount(grammarRegistry)).toBe(0) - expect(subscriptionCount(grammarRegistry)).toBe(1) - expect(buffer.emitter.getTotalListenerCount()).toBe(0) + buffer.destroy(); + expect(retainedBufferCount(grammarRegistry)).toBe(0); + expect(subscriptionCount(grammarRegistry)).toBe(1); + expect(buffer.emitter.getTotalListenerCount()).toBe(0); - disposable.dispose() - expect(retainedBufferCount(grammarRegistry)).toBe(0) - expect(subscriptionCount(grammarRegistry)).toBe(1) - }) + disposable.dispose(); + expect(retainedBufferCount(grammarRegistry)).toBe(0); + expect(subscriptionCount(grammarRegistry)).toBe(1); + }); it('does not retain the buffer when the grammar registry is destroyed', () => { - const buffer = new TextBuffer() + const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); - grammarRegistry.maintainLanguageMode(buffer) - expect(retainedBufferCount(grammarRegistry)).toBe(1) - expect(subscriptionCount(grammarRegistry)).toBe(3) + grammarRegistry.maintainLanguageMode(buffer); + expect(retainedBufferCount(grammarRegistry)).toBe(1); + expect(subscriptionCount(grammarRegistry)).toBe(3); - grammarRegistry.clear() + grammarRegistry.clear(); - expect(retainedBufferCount(grammarRegistry)).toBe(0) - expect(subscriptionCount(grammarRegistry)).toBe(1) - expect(buffer.emitter.getTotalListenerCount()).toBe(0) - }) - }) + expect(retainedBufferCount(grammarRegistry)).toBe(0); + expect(subscriptionCount(grammarRegistry)).toBe(1); + expect(buffer.emitter.getTotalListenerCount()).toBe(0); + }); + }); describe('.selectGrammar(filePath)', () => { it('always returns a grammar', () => { - const registry = new GrammarRegistry({ config: atom.config }) - expect(registry.selectGrammar().scopeName).toBe('text.plain.null-grammar') - }) + const registry = new GrammarRegistry({ config: atom.config }); + expect(registry.selectGrammar().scopeName).toBe( + 'text.plain.null-grammar' + ); + }); it('selects the text.plain grammar over the null grammar', async () => { - await atom.packages.activatePackage('language-text') + await atom.packages.activatePackage('language-text'); expect(atom.grammars.selectGrammar('test.txt').scopeName).toBe( 'text.plain' - ) - }) + ); + }); it('selects a grammar based on the file path case insensitively', async () => { - await atom.packages.activatePackage('language-coffee-script') + await atom.packages.activatePackage('language-coffee-script'); expect(atom.grammars.selectGrammar('/tmp/source.coffee').scopeName).toBe( 'source.coffee' - ) + ); expect(atom.grammars.selectGrammar('/tmp/source.COFFEE').scopeName).toBe( 'source.coffee' - ) - }) + ); + }); describe('on Windows', () => { - let originalPlatform + let originalPlatform; beforeEach(() => { - originalPlatform = process.platform - Object.defineProperty(process, 'platform', { value: 'win32' }) - }) + originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { value: 'win32' }); + }); afterEach(() => { - Object.defineProperty(process, 'platform', { value: originalPlatform }) - }) + Object.defineProperty(process, 'platform', { value: originalPlatform }); + }); it('normalizes back slashes to forward slashes when matching the fileTypes', async () => { - await atom.packages.activatePackage('language-git') + await atom.packages.activatePackage('language-git'); expect( atom.grammars.selectGrammar('something\\.git\\config').scopeName - ).toBe('source.git-config') - }) - }) + ).toBe('source.git-config'); + }); + }); it("can use the filePath to load the correct grammar based on the grammar's filetype", async () => { - await atom.packages.activatePackage('language-git') - await atom.packages.activatePackage('language-javascript') - await atom.packages.activatePackage('language-ruby') + await atom.packages.activatePackage('language-git'); + await atom.packages.activatePackage('language-javascript'); + await atom.packages.activatePackage('language-ruby'); - expect(atom.grammars.selectGrammar('file.js').name).toBe('JavaScript') // based on extension (.js) + expect(atom.grammars.selectGrammar('file.js').name).toBe('JavaScript'); // based on extension (.js) expect( atom.grammars.selectGrammar(path.join(temp.dir, '.git', 'config')).name - ).toBe('Git Config') // based on end of the path (.git/config) - expect(atom.grammars.selectGrammar('Rakefile').name).toBe('Ruby') // based on the file's basename (Rakefile) - expect(atom.grammars.selectGrammar('curb').name).toBe('Null Grammar') + ).toBe('Git Config'); // based on end of the path (.git/config) + expect(atom.grammars.selectGrammar('Rakefile').name).toBe('Ruby'); // based on the file's basename (Rakefile) + expect(atom.grammars.selectGrammar('curb').name).toBe('Null Grammar'); expect(atom.grammars.selectGrammar('/hu.git/config').name).toBe( 'Null Grammar' - ) - }) + ); + }); it("uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", async () => { - await atom.packages.activatePackage('language-javascript') - await atom.packages.activatePackage('language-ruby') + await atom.packages.activatePackage('language-javascript'); + await atom.packages.activatePackage('language-ruby'); - const filePath = require.resolve('./fixtures/shebang') - expect(atom.grammars.selectGrammar(filePath).name).toBe('Ruby') - }) + const filePath = require.resolve('./fixtures/shebang'); + expect(atom.grammars.selectGrammar(filePath).name).toBe('Ruby'); + }); it('uses the number of newlines in the first line regex to determine the number of lines to test against', async () => { - await atom.packages.activatePackage('language-property-list') - await atom.packages.activatePackage('language-coffee-script') + await atom.packages.activatePackage('language-property-list'); + await atom.packages.activatePackage('language-coffee-script'); - let fileContent = 'first-line\n' + let fileContent = 'first-line\n'; expect( atom.grammars.selectGrammar('dummy.coffee', fileContent).name - ).toBe('CoffeeScript') + ).toBe('CoffeeScript'); - fileContent = '' + fileContent = ''; expect( atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name - ).toBe('Null Grammar') + ).toBe('Null Grammar'); fileContent += - '\n' + '\n'; expect( atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name - ).toBe('Property List (XML)') - }) + ).toBe('Property List (XML)'); + }); it("doesn't read the file when the file contents are specified", async () => { - await atom.packages.activatePackage('language-ruby') + await atom.packages.activatePackage('language-ruby'); - const filePath = require.resolve('./fixtures/shebang') - const filePathContents = fs.readFileSync(filePath, 'utf8') - spyOn(fs, 'read').andCallThrough() + const filePath = require.resolve('./fixtures/shebang'); + const filePathContents = fs.readFileSync(filePath, 'utf8'); + spyOn(fs, 'read').andCallThrough(); expect(atom.grammars.selectGrammar(filePath, filePathContents).name).toBe( 'Ruby' - ) - expect(fs.read).not.toHaveBeenCalled() - }) + ); + expect(fs.read).not.toHaveBeenCalled(); + }); describe('when multiple grammars have matching fileTypes', () => { it('selects the grammar with the longest fileType match', () => { - const grammarPath1 = temp.path({ suffix: '.json' }) + const grammarPath1 = temp.path({ suffix: '.json' }); fs.writeFileSync( grammarPath1, JSON.stringify({ @@ -421,12 +427,12 @@ describe('GrammarRegistry', () => { scopeName: 'source1', fileTypes: ['test'] }) - ) - const grammar1 = atom.grammars.loadGrammarSync(grammarPath1) - expect(atom.grammars.selectGrammar('more.test', '')).toBe(grammar1) - fs.removeSync(grammarPath1) + ); + const grammar1 = atom.grammars.loadGrammarSync(grammarPath1); + expect(atom.grammars.selectGrammar('more.test', '')).toBe(grammar1); + fs.removeSync(grammarPath1); - const grammarPath2 = temp.path({ suffix: '.json' }) + const grammarPath2 = temp.path({ suffix: '.json' }); fs.writeFileSync( grammarPath2, JSON.stringify({ @@ -434,151 +440,153 @@ describe('GrammarRegistry', () => { scopeName: 'source2', fileTypes: ['test', 'more.test'] }) - ) - const grammar2 = atom.grammars.loadGrammarSync(grammarPath2) - expect(atom.grammars.selectGrammar('more.test', '')).toBe(grammar2) - return fs.removeSync(grammarPath2) - }) - }) + ); + const grammar2 = atom.grammars.loadGrammarSync(grammarPath2); + expect(atom.grammars.selectGrammar('more.test', '')).toBe(grammar2); + return fs.removeSync(grammarPath2); + }); + }); it('favors non-bundled packages when breaking scoring ties', async () => { - await atom.packages.activatePackage('language-ruby') + await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'packages', 'package-with-rb-filetype') - ) + ); - atom.grammars.grammarForScopeName('source.ruby').bundledPackage = true - atom.grammars.grammarForScopeName('test.rb').bundledPackage = false + atom.grammars.grammarForScopeName('source.ruby').bundledPackage = true; + atom.grammars.grammarForScopeName('test.rb').bundledPackage = false; expect( atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env ruby').scopeName - ).toBe('source.ruby') + ).toBe('source.ruby'); expect( atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env testruby') .scopeName - ).toBe('test.rb') - expect(atom.grammars.selectGrammar('test.rb').scopeName).toBe('test.rb') - }) + ).toBe('test.rb'); + expect(atom.grammars.selectGrammar('test.rb').scopeName).toBe('test.rb'); + }); describe('when there is no file path', () => { it('does not throw an exception (regression)', () => { expect(() => atom.grammars.selectGrammar(null, '#!/usr/bin/ruby') - ).not.toThrow() - expect(() => atom.grammars.selectGrammar(null, '')).not.toThrow() - expect(() => atom.grammars.selectGrammar(null, null)).not.toThrow() - }) - }) + ).not.toThrow(); + expect(() => atom.grammars.selectGrammar(null, '')).not.toThrow(); + expect(() => atom.grammars.selectGrammar(null, null)).not.toThrow(); + }); + }); describe('when the user has custom grammar file types', () => { it('considers the custom file types as well as those defined in the grammar', async () => { - await atom.packages.activatePackage('language-ruby') - atom.config.set('core.customFileTypes', { 'source.ruby': ['Cheffile'] }) + await atom.packages.activatePackage('language-ruby'); + atom.config.set('core.customFileTypes', { + 'source.ruby': ['Cheffile'] + }); expect( atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"') .scopeName - ).toBe('source.ruby') - }) + ).toBe('source.ruby'); + }); it('favors user-defined file types over built-in ones of equal length', async () => { - await atom.packages.activatePackage('language-ruby') - await atom.packages.activatePackage('language-coffee-script') + await atom.packages.activatePackage('language-ruby'); + await atom.packages.activatePackage('language-coffee-script'); atom.config.set('core.customFileTypes', { 'source.coffee': ['Rakefile'], 'source.ruby': ['Cakefile'] - }) + }); expect(atom.grammars.selectGrammar('Rakefile', '').scopeName).toBe( 'source.coffee' - ) + ); expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe( 'source.ruby' - ) - }) + ); + }); it('favors user-defined file types over grammars with matching first-line-regexps', async () => { - await atom.packages.activatePackage('language-ruby') - await atom.packages.activatePackage('language-javascript') + await atom.packages.activatePackage('language-ruby'); + await atom.packages.activatePackage('language-javascript'); atom.config.set('core.customFileTypes', { 'source.ruby': ['bootstrap'] - }) + }); expect( atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node') .scopeName - ).toBe('source.ruby') - }) - }) + ).toBe('source.ruby'); + }); + }); it('favors a grammar with a matching file type over one with m matching first line pattern', async () => { - await atom.packages.activatePackage('language-ruby') - await atom.packages.activatePackage('language-javascript') + await atom.packages.activatePackage('language-ruby'); + await atom.packages.activatePackage('language-javascript'); expect( atom.grammars.selectGrammar('foo.rb', '#!/usr/bin/env node').scopeName - ).toBe('source.ruby') - }) + ).toBe('source.ruby'); + }); describe('tree-sitter vs text-mate', () => { it('favors a text-mate grammar over a tree-sitter grammar when `core.useTreeSitterParsers` is false', () => { atom.config.set('core.useTreeSitterParsers', false, { scopeSelector: '.source.js' - }) + }); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) + ); - const grammar = grammarRegistry.selectGrammar('test.js') - expect(grammar.scopeName).toBe('source.js') - expect(grammar instanceof FirstMate.Grammar).toBe(true) - }) + const grammar = grammarRegistry.selectGrammar('test.js'); + expect(grammar.scopeName).toBe('source.js'); + expect(grammar instanceof FirstMate.Grammar).toBe(true); + }); it('favors a tree-sitter grammar over a text-mate grammar when `core.useTreeSitterParsers` is true', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) + ); - const grammar = grammarRegistry.selectGrammar('test.js') - expect(grammar instanceof TreeSitterGrammar).toBe(true) - }) + const grammar = grammarRegistry.selectGrammar('test.js'); + expect(grammar instanceof TreeSitterGrammar).toBe(true); + }); it('only favors a tree-sitter grammar if it actually matches in some way (regression)', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) + ); - const grammar = grammarRegistry.selectGrammar('test', '') - expect(grammar.name).toBe('Null Grammar') - }) - }) + const grammar = grammarRegistry.selectGrammar('test', ''); + expect(grammar.name).toBe('Null Grammar'); + }); + }); describe('tree-sitter grammars with content regexes', () => { it('recognizes C++ header files', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/tree-sitter-c.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/tree-sitter-cpp.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-coffee-script/grammars/coffeescript.cson') - ) + ); let grammar = grammarRegistry.selectGrammar( 'test.h', @@ -589,8 +597,8 @@ describe('GrammarRegistry', () => { void verb(); } Noun; ` - ) - expect(grammar.name).toBe('C') + ); + expect(grammar.name).toBe('C'); grammar = grammarRegistry.selectGrammar( 'test.h', @@ -602,8 +610,8 @@ describe('GrammarRegistry', () => { void verb(); }; ` - ) - expect(grammar.name).toBe('C++') + ); + expect(grammar.name).toBe('C++'); // The word `class` only indicates C++ in `.h` files, not in all files. grammar = grammarRegistry.selectGrammar( @@ -613,36 +621,36 @@ describe('GrammarRegistry', () => { class Noun verb: -> true ` - ) - expect(grammar.name).toBe('CoffeeScript') - }) + ); + expect(grammar.name).toBe('CoffeeScript'); + }); it('recognizes C++ files that do not match the content regex (regression)', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/tree-sitter-c.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/c++.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/tree-sitter-cpp.cson') - ) + ); let grammar = grammarRegistry.selectGrammar( 'test.cc', dedent` int a(); ` - ) - expect(grammar.name).toBe('C++') - }) + ); + expect(grammar.name).toBe('C++'); + }); it('does not apply content regexes from grammars without filetype or first line matches', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/tree-sitter-cpp.cson') - ) + ); let grammar = grammarRegistry.selectGrammar( '', @@ -651,19 +659,19 @@ describe('GrammarRegistry', () => { # this is ruby, not C++ end ` - ) + ); - expect(grammar.name).toBe('Null Grammar') - }) + expect(grammar.name).toBe('Null Grammar'); + }); it('recognizes shell scripts with shebang lines', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve('language-shellscript/grammars/shell-unix-bash.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-shellscript/grammars/tree-sitter-bash.cson') - ) + ); let grammar = grammarRegistry.selectGrammar( 'test.h', @@ -672,9 +680,9 @@ describe('GrammarRegistry', () => { echo "hi" ` - ) - expect(grammar.name).toBe('Shell Script') - expect(grammar instanceof TreeSitterGrammar).toBeTruthy() + ); + expect(grammar.name).toBe('Shell Script'); + expect(grammar instanceof TreeSitterGrammar).toBeTruthy(); grammar = grammarRegistry.selectGrammar( 'test.h', @@ -683,11 +691,11 @@ describe('GrammarRegistry', () => { echo "hi" ` - ) - expect(grammar.name).toBe('Shell Script') - expect(grammar instanceof TreeSitterGrammar).toBeTruthy() + ); + expect(grammar.name).toBe('Shell Script'); + expect(grammar instanceof TreeSitterGrammar).toBeTruthy(); - atom.config.set('core.useTreeSitterParsers', false) + atom.config.set('core.useTreeSitterParsers', false); grammar = grammarRegistry.selectGrammar( 'test.h', dedent` @@ -695,21 +703,21 @@ describe('GrammarRegistry', () => { echo "hi" ` - ) - expect(grammar.name).toBe('Shell Script') - expect(grammar instanceof TreeSitterGrammar).toBeFalsy() - }) + ); + expect(grammar.name).toBe('Shell Script'); + expect(grammar instanceof TreeSitterGrammar).toBeFalsy(); + }); it('recognizes JavaScript files that use Flow', () => { - atom.config.set('core.useTreeSitterParsers', true) + atom.config.set('core.useTreeSitterParsers', true); grammarRegistry.loadGrammarSync( require.resolve( 'language-javascript/grammars/tree-sitter-javascript.cson' ) - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-typescript/grammars/tree-sitter-flow.cson') - ) + ); let grammar = grammarRegistry.selectGrammar( 'test.js', @@ -719,141 +727,144 @@ describe('GrammarRegistry', () => { module.exports = function () { return 1 + 1 } ` - ) - expect(grammar.name).toBe('Flow JavaScript') + ); + expect(grammar.name).toBe('Flow JavaScript'); grammar = grammarRegistry.selectGrammar( 'test.js', dedent` module.exports = function () { return 1 + 1 } ` - ) - expect(grammar.name).toBe('JavaScript') - }) - }) + ); + expect(grammar.name).toBe('JavaScript'); + }); + }); describe('text-mate grammars with content regexes', () => { it('favors grammars that match the content regex', () => { const grammar1 = { name: 'foo', fileTypes: ['foo'] - } - grammarRegistry.addGrammar(grammar1) + }; + grammarRegistry.addGrammar(grammar1); const grammar2 = { name: 'foo++', contentRegex: new OnigRegExp('.*bar'), fileTypes: ['foo'] - } - grammarRegistry.addGrammar(grammar2) + }; + grammarRegistry.addGrammar(grammar2); const grammar = grammarRegistry.selectGrammar( 'test.foo', dedent` ${'\n'.repeat(50)}bar${'\n'.repeat(50)} - `) + ` + ); - expect(grammar).toBe(grammar2) - }) - }) - }) + expect(grammar).toBe(grammar2); + }); + }); + }); describe('.removeGrammar(grammar)', () => { it("removes the grammar, so it won't be returned by selectGrammar", async () => { - await atom.packages.activatePackage('language-css') - const grammar = atom.grammars.selectGrammar('foo.css') - atom.grammars.removeGrammar(grammar) - expect(atom.grammars.selectGrammar('foo.css').name).not.toBe(grammar.name) - }) - }) + await atom.packages.activatePackage('language-css'); + const grammar = atom.grammars.selectGrammar('foo.css'); + atom.grammars.removeGrammar(grammar); + expect(atom.grammars.selectGrammar('foo.css').name).not.toBe( + grammar.name + ); + }); + }); describe('.addInjectionPoint(languageId, {type, language, content})', () => { const injectionPoint = { type: 'some_node_type', - language () { - return 'some_language_name' + language() { + return 'some_language_name'; }, - content (node) { - return node + content(node) { + return node; } - } + }; beforeEach(() => { - atom.config.set('core.useTreeSitterParsers', true) - }) + atom.config.set('core.useTreeSitterParsers', true); + }); it('adds an injection point to the grammar with the given id', async () => { - await atom.packages.activatePackage('language-javascript') - atom.grammars.addInjectionPoint('javascript', injectionPoint) - const grammar = atom.grammars.grammarForId('javascript') - expect(grammar.injectionPoints).toContain(injectionPoint) - }) + await atom.packages.activatePackage('language-javascript'); + atom.grammars.addInjectionPoint('javascript', injectionPoint); + const grammar = atom.grammars.grammarForId('javascript'); + expect(grammar.injectionPoints).toContain(injectionPoint); + }); describe('when called before a grammar with the given id is loaded', () => { it('adds the injection point once the grammar is loaded', async () => { - atom.grammars.addInjectionPoint('javascript', injectionPoint) - await atom.packages.activatePackage('language-javascript') - const grammar = atom.grammars.grammarForId('javascript') - expect(grammar.injectionPoints).toContain(injectionPoint) - }) - }) - }) + atom.grammars.addInjectionPoint('javascript', injectionPoint); + await atom.packages.activatePackage('language-javascript'); + const grammar = atom.grammars.grammarForId('javascript'); + expect(grammar.injectionPoints).toContain(injectionPoint); + }); + }); + }); describe('serialization', () => { it("persists editors' grammar overrides", async () => { - const buffer1 = new TextBuffer() - const buffer2 = new TextBuffer() + const buffer1 = new TextBuffer(); + const buffer2 = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-c/grammars/c.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-html/grammars/html.cson') - ) + ); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) + ); - grammarRegistry.maintainLanguageMode(buffer1) - grammarRegistry.maintainLanguageMode(buffer2) - grammarRegistry.assignLanguageMode(buffer1, 'source.c') - grammarRegistry.assignLanguageMode(buffer2, 'source.js') + grammarRegistry.maintainLanguageMode(buffer1); + grammarRegistry.maintainLanguageMode(buffer2); + grammarRegistry.assignLanguageMode(buffer1, 'source.c'); + grammarRegistry.assignLanguageMode(buffer2, 'source.js'); - const buffer1Copy = await TextBuffer.deserialize(buffer1.serialize()) - const buffer2Copy = await TextBuffer.deserialize(buffer2.serialize()) + const buffer1Copy = await TextBuffer.deserialize(buffer1.serialize()); + const buffer2Copy = await TextBuffer.deserialize(buffer2.serialize()); - const grammarRegistryCopy = new GrammarRegistry({ config: atom.config }) + const grammarRegistryCopy = new GrammarRegistry({ config: atom.config }); grammarRegistryCopy.deserialize( JSON.parse(JSON.stringify(grammarRegistry.serialize())) - ) + ); grammarRegistryCopy.loadGrammarSync( require.resolve('language-c/grammars/c.cson') - ) + ); grammarRegistryCopy.loadGrammarSync( require.resolve('language-html/grammars/html.cson') - ) + ); - expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe(null) - expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe(null) + expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe(null); + expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe(null); - grammarRegistryCopy.maintainLanguageMode(buffer1Copy) - grammarRegistryCopy.maintainLanguageMode(buffer2Copy) - expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c') - expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe(null) + grammarRegistryCopy.maintainLanguageMode(buffer1Copy); + grammarRegistryCopy.maintainLanguageMode(buffer2Copy); + expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c'); + expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe(null); grammarRegistryCopy.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') - ) - expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c') - expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe('source.js') - }) - }) -}) + ); + expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c'); + expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe('source.js'); + }); + }); +}); -function retainedBufferCount (grammarRegistry) { - return grammarRegistry.grammarScoresByBuffer.size +function retainedBufferCount(grammarRegistry) { + return grammarRegistry.grammarScoresByBuffer.size; } -function subscriptionCount (grammarRegistry) { - return grammarRegistry.subscriptions.disposables.size +function subscriptionCount(grammarRegistry) { + return grammarRegistry.subscriptions.disposables.size; } diff --git a/spec/gutter-container-spec.js b/spec/gutter-container-spec.js index 50451ecfb..8320c6d83 100644 --- a/spec/gutter-container-spec.js +++ b/spec/gutter-container-spec.js @@ -1,83 +1,88 @@ -const Gutter = require('../src/gutter') -const GutterContainer = require('../src/gutter-container') +const Gutter = require('../src/gutter'); +const GutterContainer = require('../src/gutter-container'); describe('GutterContainer', () => { - let gutterContainer = null + let gutterContainer = null; const fakeTextEditor = { - scheduleComponentUpdate () {} - } + scheduleComponentUpdate() {} + }; beforeEach(() => { - gutterContainer = new GutterContainer(fakeTextEditor) - }) + gutterContainer = new GutterContainer(fakeTextEditor); + }); describe('when initialized', () => it('it has no gutters', () => { - expect(gutterContainer.getGutters().length).toBe(0) - })) + expect(gutterContainer.getGutters().length).toBe(0); + })); describe('::addGutter', () => { it('creates a new gutter', () => { const newGutter = gutterContainer.addGutter({ 'test-gutter': 'test-gutter', priority: 1 - }) - expect(gutterContainer.getGutters()).toEqual([newGutter]) - expect(newGutter.priority).toBe(1) - }) + }); + expect(gutterContainer.getGutters()).toEqual([newGutter]); + expect(newGutter.priority).toBe(1); + }); it('throws an error if the provided gutter name is already in use', () => { - const name = 'test-gutter' - gutterContainer.addGutter({ name }) - expect(gutterContainer.addGutter.bind(null, { name })).toThrow() - }) + const name = 'test-gutter'; + gutterContainer.addGutter({ name }); + expect(gutterContainer.addGutter.bind(null, { name })).toThrow(); + }); it('keeps added gutters sorted by ascending priority', () => { - const gutter1 = gutterContainer.addGutter({ name: 'first', priority: 1 }) - const gutter3 = gutterContainer.addGutter({ name: 'third', priority: 3 }) - const gutter2 = gutterContainer.addGutter({ name: 'second', priority: 2 }) - expect(gutterContainer.getGutters()).toEqual([gutter1, gutter2, gutter3]) - }) - }) + const gutter1 = gutterContainer.addGutter({ name: 'first', priority: 1 }); + const gutter3 = gutterContainer.addGutter({ name: 'third', priority: 3 }); + const gutter2 = gutterContainer.addGutter({ + name: 'second', + priority: 2 + }); + expect(gutterContainer.getGutters()).toEqual([gutter1, gutter2, gutter3]); + }); + }); describe('::removeGutter', () => { - let removedGutters + let removedGutters; - beforeEach(function () { - gutterContainer = new GutterContainer(fakeTextEditor) - removedGutters = [] + beforeEach(function() { + gutterContainer = new GutterContainer(fakeTextEditor); + removedGutters = []; gutterContainer.onDidRemoveGutter(gutterName => removedGutters.push(gutterName) - ) - }) + ); + }); it('removes the gutter if it is contained by this GutterContainer', () => { - const gutter = gutterContainer.addGutter({ 'test-gutter': 'test-gutter' }) - expect(gutterContainer.getGutters()).toEqual([gutter]) - gutterContainer.removeGutter(gutter) - expect(gutterContainer.getGutters().length).toBe(0) - expect(removedGutters).toEqual([gutter.name]) - }) + const gutter = gutterContainer.addGutter({ + 'test-gutter': 'test-gutter' + }); + expect(gutterContainer.getGutters()).toEqual([gutter]); + gutterContainer.removeGutter(gutter); + expect(gutterContainer.getGutters().length).toBe(0); + expect(removedGutters).toEqual([gutter.name]); + }); it('throws an error if the gutter is not within this GutterContainer', () => { - const fakeOtherTextEditor = {} - const otherGutterContainer = new GutterContainer(fakeOtherTextEditor) - const gutter = new Gutter('gutter-name', otherGutterContainer) - expect(gutterContainer.removeGutter.bind(null, gutter)).toThrow() - }) - }) + const fakeOtherTextEditor = {}; + const otherGutterContainer = new GutterContainer(fakeOtherTextEditor); + const gutter = new Gutter('gutter-name', otherGutterContainer); + expect(gutterContainer.removeGutter.bind(null, gutter)).toThrow(); + }); + }); describe('::destroy', () => it('clears its array of gutters and destroys custom gutters', () => { const newGutter = gutterContainer.addGutter({ 'test-gutter': 'test-gutter', priority: 1 - }) - const newGutterSpy = jasmine.createSpy() - newGutter.onDidDestroy(newGutterSpy) + }); + const newGutterSpy = jasmine.createSpy(); + newGutter.onDidDestroy(newGutterSpy); - gutterContainer.destroy() - expect(newGutterSpy).toHaveBeenCalled() - expect(gutterContainer.getGutters()).toEqual([]) - })) -}) + gutterContainer.destroy(); + expect(newGutterSpy).toHaveBeenCalled(); + expect(gutterContainer.getGutters()).toEqual([]); + })); +}); diff --git a/spec/gutter-spec.js b/spec/gutter-spec.js index c5051ea22..441122b04 100644 --- a/spec/gutter-spec.js +++ b/spec/gutter-spec.js @@ -1,82 +1,82 @@ -const Gutter = require('../src/gutter') +const Gutter = require('../src/gutter'); describe('Gutter', () => { const fakeGutterContainer = { - scheduleComponentUpdate () {} - } - const name = 'name' + scheduleComponentUpdate() {} + }; + const name = 'name'; describe('::hide', () => it('hides the gutter if it is visible.', () => { const options = { name, visible: true - } - const gutter = new Gutter(fakeGutterContainer, options) - const events = [] - gutter.onDidChangeVisible(gutter => events.push(gutter.isVisible())) + }; + const gutter = new Gutter(fakeGutterContainer, options); + const events = []; + gutter.onDidChangeVisible(gutter => events.push(gutter.isVisible())); - expect(gutter.isVisible()).toBe(true) - gutter.hide() - expect(gutter.isVisible()).toBe(false) - expect(events).toEqual([false]) - gutter.hide() - expect(gutter.isVisible()).toBe(false) + expect(gutter.isVisible()).toBe(true); + gutter.hide(); + expect(gutter.isVisible()).toBe(false); + expect(events).toEqual([false]); + gutter.hide(); + expect(gutter.isVisible()).toBe(false); // An event should only be emitted when the visibility changes. - expect(events.length).toBe(1) - })) + expect(events.length).toBe(1); + })); describe('::show', () => it('shows the gutter if it is hidden.', () => { const options = { name, visible: false - } - const gutter = new Gutter(fakeGutterContainer, options) - const events = [] - gutter.onDidChangeVisible(gutter => events.push(gutter.isVisible())) + }; + const gutter = new Gutter(fakeGutterContainer, options); + const events = []; + gutter.onDidChangeVisible(gutter => events.push(gutter.isVisible())); - expect(gutter.isVisible()).toBe(false) - gutter.show() - expect(gutter.isVisible()).toBe(true) - expect(events).toEqual([true]) - gutter.show() - expect(gutter.isVisible()).toBe(true) + expect(gutter.isVisible()).toBe(false); + gutter.show(); + expect(gutter.isVisible()).toBe(true); + expect(events).toEqual([true]); + gutter.show(); + expect(gutter.isVisible()).toBe(true); // An event should only be emitted when the visibility changes. - expect(events.length).toBe(1) - })) + expect(events.length).toBe(1); + })); describe('::destroy', () => { - let mockGutterContainer, mockGutterContainerRemovedGutters + let mockGutterContainer, mockGutterContainerRemovedGutters; beforeEach(() => { - mockGutterContainerRemovedGutters = [] + mockGutterContainerRemovedGutters = []; mockGutterContainer = { - removeGutter (destroyedGutter) { - mockGutterContainerRemovedGutters.push(destroyedGutter) + removeGutter(destroyedGutter) { + mockGutterContainerRemovedGutters.push(destroyedGutter); } - } - }) + }; + }); it('removes the gutter from its container.', () => { - const gutter = new Gutter(mockGutterContainer, { name }) - gutter.destroy() - expect(mockGutterContainerRemovedGutters).toEqual([gutter]) - }) + const gutter = new Gutter(mockGutterContainer, { name }); + gutter.destroy(); + expect(mockGutterContainerRemovedGutters).toEqual([gutter]); + }); it('calls all callbacks registered on ::onDidDestroy.', () => { - const gutter = new Gutter(mockGutterContainer, { name }) - let didDestroy = false + const gutter = new Gutter(mockGutterContainer, { name }); + let didDestroy = false; gutter.onDidDestroy(() => { - didDestroy = true - }) - gutter.destroy() - expect(didDestroy).toBe(true) - }) + didDestroy = true; + }); + gutter.destroy(); + expect(didDestroy).toBe(true); + }); it('does not allow destroying the line-number gutter', () => { - const gutter = new Gutter(mockGutterContainer, { name: 'line-number' }) - expect(gutter.destroy).toThrow() - }) - }) -}) + const gutter = new Gutter(mockGutterContainer, { name: 'line-number' }); + expect(gutter.destroy).toThrow(); + }); + }); +}); diff --git a/spec/helpers/random.js b/spec/helpers/random.js index 3a2ff9159..a8bfe4925 100644 --- a/spec/helpers/random.js +++ b/spec/helpers/random.js @@ -1,42 +1,42 @@ -const WORDS = require('./words') -const { Point, Range } = require('text-buffer') +const WORDS = require('./words'); +const { Point, Range } = require('text-buffer'); -exports.getRandomBufferRange = function getRandomBufferRange (random, buffer) { - const endRow = random(buffer.getLineCount()) - const startRow = random.intBetween(0, endRow) - const startColumn = random(buffer.lineForRow(startRow).length + 1) - const endColumn = random(buffer.lineForRow(endRow).length + 1) - return Range(Point(startRow, startColumn), Point(endRow, endColumn)) -} +exports.getRandomBufferRange = function getRandomBufferRange(random, buffer) { + const endRow = random(buffer.getLineCount()); + const startRow = random.intBetween(0, endRow); + const startColumn = random(buffer.lineForRow(startRow).length + 1); + const endColumn = random(buffer.lineForRow(endRow).length + 1); + return Range(Point(startRow, startColumn), Point(endRow, endColumn)); +}; -exports.buildRandomLines = function buildRandomLines (random, maxLines) { - const lines = [] +exports.buildRandomLines = function buildRandomLines(random, maxLines) { + const lines = []; for (let i = 0; i < random(maxLines); i++) { - lines.push(buildRandomLine(random)) + lines.push(buildRandomLine(random)); } - return lines.join('\n') -} + return lines.join('\n'); +}; -function buildRandomLine (random) { - const line = [] +function buildRandomLine(random) { + const line = []; for (let i = 0; i < random(5); i++) { - const n = random(10) + const n = random(10); if (n < 2) { - line.push('\t') + line.push('\t'); } else if (n < 4) { - line.push(' ') + line.push(' '); } else { if (line.length > 0 && !/\s/.test(line[line.length - 1])) { - line.push(' ') + line.push(' '); } - line.push(WORDS[random(WORDS.length)]) + line.push(WORDS[random(WORDS.length)]); } } - return line.join('') + return line.join(''); } diff --git a/spec/helpers/words.js b/spec/helpers/words.js index f5ae07149..5606c2eb0 100644 --- a/spec/helpers/words.js +++ b/spec/helpers/words.js @@ -46888,4 +46888,4 @@ module.exports = [ 'zymometer', 'zymosis', 'zymotic' -] +]; diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index bdf670425..b3e425da3 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -1,16 +1,16 @@ -const { HistoryManager, HistoryProject } = require('../src/history-manager') -const StateStore = require('../src/state-store') +const { HistoryManager, HistoryProject } = require('../src/history-manager'); +const StateStore = require('../src/state-store'); describe('HistoryManager', () => { - let historyManager, commandRegistry, project, stateStore - let commandDisposable, projectDisposable + let historyManager, commandRegistry, project, stateStore; + let commandDisposable, projectDisposable; beforeEach(async () => { - commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']) - commandRegistry.add.andReturn(commandDisposable) + commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']); + commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']); + commandRegistry.add.andReturn(commandDisposable); - stateStore = new StateStore('history-manager-test', 1) + stateStore = new StateStore('history-manager-test', 1); await stateStore.save('history-manager', { projects: [ { @@ -19,37 +19,37 @@ describe('HistoryManager', () => { }, { paths: ['/test'], lastOpened: new Date(2016, 9, 17, 11, 12, 13) } ] - }) + }); - projectDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - project = jasmine.createSpyObj('Project', ['onDidChangePaths']) + projectDisposable = jasmine.createSpyObj('Disposable', ['dispose']); + project = jasmine.createSpyObj('Project', ['onDidChangePaths']); project.onDidChangePaths.andCallFake(f => { - project.didChangePathsListener = f - return projectDisposable - }) + project.didChangePathsListener = f; + return projectDisposable; + }); historyManager = new HistoryManager({ stateStore, project, commands: commandRegistry - }) - await historyManager.loadState() - }) + }); + await historyManager.loadState(); + }); afterEach(async () => { - await stateStore.clear() - }) + await stateStore.clear(); + }); describe('constructor', () => { it("registers the 'clear-project-history' command function", () => { - expect(commandRegistry.add).toHaveBeenCalled() - const cmdCall = commandRegistry.add.calls[0] - expect(cmdCall.args.length).toBe(3) - expect(cmdCall.args[0]).toBe('atom-workspace') + expect(commandRegistry.add).toHaveBeenCalled(); + const cmdCall = commandRegistry.add.calls[0]; + expect(cmdCall.args.length).toBe(3); + expect(cmdCall.args[0]).toBe('atom-workspace'); expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe( 'function' - ) - }) + ); + }); describe('getProjects', () => { it('returns an array of HistoryProjects', () => { @@ -59,168 +59,168 @@ describe('HistoryManager', () => { new Date(2016, 9, 17, 17, 16, 23) ), new HistoryProject(['/test'], new Date(2016, 9, 17, 11, 12, 13)) - ]) - }) + ]); + }); it('returns an array of HistoryProjects that is not mutable state', () => { - const firstProjects = historyManager.getProjects() - firstProjects.pop() - firstProjects[0].path = 'modified' + const firstProjects = historyManager.getProjects(); + firstProjects.pop(); + firstProjects[0].path = 'modified'; - const secondProjects = historyManager.getProjects() - expect(secondProjects.length).toBe(2) - expect(secondProjects[0].path).not.toBe('modified') - }) - }) + const secondProjects = historyManager.getProjects(); + expect(secondProjects.length).toBe(2); + expect(secondProjects[0].path).not.toBe('modified'); + }); + }); describe('clearProjects', () => { it('clears the list of projects', async () => { - expect(historyManager.getProjects().length).not.toBe(0) - await historyManager.clearProjects() - expect(historyManager.getProjects().length).toBe(0) - }) + expect(historyManager.getProjects().length).not.toBe(0); + await historyManager.clearProjects(); + expect(historyManager.getProjects().length).toBe(0); + }); it('saves the state', async () => { - await historyManager.clearProjects() + await historyManager.clearProjects(); const historyManager2 = new HistoryManager({ stateStore, project, commands: commandRegistry - }) - await historyManager2.loadState() - expect(historyManager.getProjects().length).toBe(0) - }) + }); + await historyManager2.loadState(); + expect(historyManager.getProjects().length).toBe(0); + }); 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() - }) - }) + const didChangeSpy = jasmine.createSpy(); + historyManager.onDidChangeProjects(didChangeSpy); + await historyManager.clearProjects(); + expect(historyManager.getProjects().length).toBe(0); + expect(didChangeSpy).toHaveBeenCalled(); + }); + }); it('listens to project.onDidChangePaths adding a new project', () => { - const start = new Date() - project.didChangePathsListener(['/a/new', '/path/or/two']) - const projects = historyManager.getProjects() - expect(projects.length).toBe(3) - expect(projects[0].paths).toEqual(['/a/new', '/path/or/two']) - expect(projects[0].lastOpened).not.toBeLessThan(start) - }) + const start = new Date(); + project.didChangePathsListener(['/a/new', '/path/or/two']); + const projects = historyManager.getProjects(); + expect(projects.length).toBe(3); + expect(projects[0].paths).toEqual(['/a/new', '/path/or/two']); + expect(projects[0].lastOpened).not.toBeLessThan(start); + }); it('listens to project.onDidChangePaths updating an existing project', () => { - const start = new Date() - project.didChangePathsListener(['/test']) - const projects = historyManager.getProjects() - expect(projects.length).toBe(2) - expect(projects[0].paths).toEqual(['/test']) - expect(projects[0].lastOpened).not.toBeLessThan(start) - }) - }) + const start = new Date(); + project.didChangePathsListener(['/test']); + const projects = historyManager.getProjects(); + expect(projects.length).toBe(2); + expect(projects[0].paths).toEqual(['/test']); + expect(projects[0].lastOpened).not.toBeLessThan(start); + }); + }); describe('loadState', () => { it('defaults to an empty array if no state', async () => { - await stateStore.clear() - await historyManager.loadState() - expect(historyManager.getProjects()).toEqual([]) - }) + await stateStore.clear(); + await historyManager.loadState(); + expect(historyManager.getProjects()).toEqual([]); + }); it('defaults to an empty array if no projects', async () => { - await stateStore.save('history-manager', {}) - await historyManager.loadState() - expect(historyManager.getProjects()).toEqual([]) - }) - }) + await stateStore.save('history-manager', {}); + await historyManager.loadState(); + expect(historyManager.getProjects()).toEqual([]); + }); + }); describe('addProject', () => { it('adds a new project to the end', async () => { - const date = new Date(2010, 10, 9, 8, 7, 6) - 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) - }) + const date = new Date(2010, 10, 9, 8, 7, 6); + 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', async () => { - const date = 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) - }) + const date = 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', async () => { - const date = new 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) - }) + const date = new 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', async () => { - const didChangeSpy = jasmine.createSpy() - const beforeCount = historyManager.getProjects().length - historyManager.onDidChangeProjects(didChangeSpy) - await historyManager.addProject(['/test-new'], new Date()) - expect(didChangeSpy).toHaveBeenCalled() - expect(historyManager.getProjects().length).toBe(beforeCount + 1) - }) + const didChangeSpy = jasmine.createSpy(); + const beforeCount = historyManager.getProjects().length; + historyManager.onDidChangeProjects(didChangeSpy); + 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', async () => { - const didChangeSpy = jasmine.createSpy() - const beforeCount = historyManager.getProjects().length - historyManager.onDidChangeProjects(didChangeSpy) - await historyManager.addProject(['/test'], new Date()) - expect(didChangeSpy).toHaveBeenCalled() - expect(historyManager.getProjects().length).toBe(beforeCount) - }) - }) + const didChangeSpy = jasmine.createSpy(); + const beforeCount = historyManager.getProjects().length; + historyManager.onDidChangeProjects(didChangeSpy); + await historyManager.addProject(['/test'], new Date()); + expect(didChangeSpy).toHaveBeenCalled(); + expect(historyManager.getProjects().length).toBe(beforeCount); + }); + }); describe('getProject', () => { it('returns a project that matches the paths', () => { - const project = historyManager.getProject(['/1', 'c:\\2']) - expect(project).not.toBeNull() - expect(project.paths).toEqual(['/1', 'c:\\2']) - }) + const project = historyManager.getProject(['/1', 'c:\\2']); + expect(project).not.toBeNull(); + expect(project.paths).toEqual(['/1', 'c:\\2']); + }); it("returns null when it can't find the project", () => { - const project = historyManager.getProject(['/1']) - expect(project).toBeNull() - }) - }) + const project = historyManager.getProject(['/1']); + expect(project).toBeNull(); + }); + }); describe('saveState', () => { - let savedHistory + let savedHistory; beforeEach(() => { // historyManager.saveState is spied on globally to prevent specs from // modifying the shared project history. Since these tests depend on // saveState, we unspy it but in turn spy on the state store instead // so that no data is actually stored to it. - jasmine.unspy(historyManager, 'saveState') + jasmine.unspy(historyManager, 'saveState'); spyOn(historyManager.stateStore, 'save').andCallFake((name, history) => { - savedHistory = history - return Promise.resolve() - }) - }) + savedHistory = history; + return Promise.resolve(); + }); + }); it('saves the state', async () => { - await historyManager.addProject(['/save/state']) - await historyManager.saveState() + await historyManager.addProject(['/save/state']); + await historyManager.saveState(); const historyManager2 = new HistoryManager({ stateStore, project, commands: commandRegistry - }) + }); spyOn(historyManager2.stateStore, 'load').andCallFake(name => Promise.resolve(savedHistory) - ) - await historyManager2.loadState() - expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state']) - }) - }) -}) + ); + await historyManager2.loadState(); + expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state']); + }); + }); +}); diff --git a/spec/jasmine-junit-reporter.js b/spec/jasmine-junit-reporter.js index 14a4969b7..26c323165 100644 --- a/spec/jasmine-junit-reporter.js +++ b/spec/jasmine-junit-reporter.js @@ -1,21 +1,21 @@ -require('jasmine-reporters') +require('jasmine-reporters'); class JasmineJUnitReporter extends jasmine.JUnitXmlReporter { - fullDescription (spec) { - let fullDescription = spec.description - let currentSuite = spec.suite + fullDescription(spec) { + let fullDescription = spec.description; + let currentSuite = spec.suite; while (currentSuite) { - fullDescription = currentSuite.description + ' ' + fullDescription - currentSuite = currentSuite.parentSuite + fullDescription = currentSuite.description + ' ' + fullDescription; + currentSuite = currentSuite.parentSuite; } - return fullDescription + return fullDescription; } - reportSpecResults (spec) { - spec.description = this.fullDescription(spec) - return super.reportSpecResults(spec) + reportSpecResults(spec) { + spec.description = this.fullDescription(spec); + return super.reportSpecResults(spec); } } -module.exports = { JasmineJUnitReporter } +module.exports = { JasmineJUnitReporter }; diff --git a/spec/jasmine-list-reporter.js b/spec/jasmine-list-reporter.js index ab1688dfd..0928a13f1 100644 --- a/spec/jasmine-list-reporter.js +++ b/spec/jasmine-list-reporter.js @@ -1,35 +1,35 @@ -const { TerminalReporter } = require('jasmine-tagged') +const { TerminalReporter } = require('jasmine-tagged'); class JasmineListReporter extends TerminalReporter { - fullDescription (spec) { - let fullDescription = 'it ' + spec.description - let currentSuite = spec.suite + fullDescription(spec) { + let fullDescription = 'it ' + spec.description; + let currentSuite = spec.suite; while (currentSuite) { - fullDescription = currentSuite.description + ' > ' + fullDescription - currentSuite = currentSuite.parentSuite + fullDescription = currentSuite.description + ' > ' + fullDescription; + currentSuite = currentSuite.parentSuite; } - return fullDescription + return fullDescription; } - reportSpecStarting (spec) { - this.print_(this.fullDescription(spec) + ' ') + reportSpecStarting(spec) { + this.print_(this.fullDescription(spec) + ' '); } - reportSpecResults (spec) { - const result = spec.results() + reportSpecResults(spec) { + const result = spec.results(); if (result.skipped) { - return + return; } - let msg = '' + let msg = ''; if (result.passed()) { - msg = this.stringWithColor_('[pass]', this.color_.pass()) + msg = this.stringWithColor_('[pass]', this.color_.pass()); } else { - msg = this.stringWithColor_('[FAIL]', this.color_.fail()) - this.addFailureToFailures_(spec) + msg = this.stringWithColor_('[FAIL]', this.color_.fail()); + this.addFailureToFailures_(spec); } - this.printLine_(msg) + this.printLine_(msg); } } -module.exports = { JasmineListReporter } +module.exports = { JasmineListReporter }; diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 50674d7e2..be5dbd1c1 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -1,15 +1,18 @@ /* globals assert */ -const path = require('path') -const {EventEmitter} = require('events') -const temp = require('temp').track() -const fs = require('fs-plus') -const electron = require('electron') -const {sandbox} = require('sinon') +const path = require('path'); +const { EventEmitter } = require('events'); +const temp = require('temp').track(); +const fs = require('fs-plus'); +const electron = require('electron'); +const { sandbox } = require('sinon'); -const AtomApplication = require('../../src/main-process/atom-application') -const parseCommandLine = require('../../src/main-process/parse-command-line') -const {emitterEventPromise, conditionPromise} = require('../async-spec-helpers') +const AtomApplication = require('../../src/main-process/atom-application'); +const parseCommandLine = require('../../src/main-process/parse-command-line'); +const { + emitterEventPromise, + conditionPromise +} = require('../async-spec-helpers'); // These tests use a utility class called LaunchScenario, defined below, to manipulate AtomApplication instances that // (1) are stubbed to only simulate AtomWindow creation and (2) allow you to use a shorthand notation to assert the @@ -41,64 +44,64 @@ const {emitterEventPromise, conditionPromise} = require('../async-spec-helpers') // two project roots, `./b` and `./c`, and one open editor on `./b/2.md`. The windows are listed in their expected // creation order. -describe('AtomApplication', function () { - let scenario, sinon +describe('AtomApplication', function() { + let scenario, sinon; - beforeEach(async function () { - sinon = sandbox.create() - scenario = await LaunchScenario.create(sinon) - }) + beforeEach(async function() { + sinon = sandbox.create(); + scenario = await LaunchScenario.create(sinon); + }); - afterEach(async function () { - await scenario.destroy() - sinon.restore() - }) + afterEach(async function() { + await scenario.destroy(); + sinon.restore(); + }); - describe('command-line interface behavior', function () { - describe('with no open windows', function () { + describe('command-line interface behavior', function() { + describe('with no open windows', function() { // This is also the case when a user selects the application from the OS shell - it('opens an empty window', async function () { - await scenario.launch(parseCommandLine([])) - await scenario.assert('[_ _]') - }) + it('opens an empty window', async function() { + await scenario.launch(parseCommandLine([])); + await scenario.assert('[_ _]'); + }); // This is also the case when a user clicks on a file in their file manager - it('opens a file', async function () { - await scenario.open(parseCommandLine(['a/1.md'])) - await scenario.assert('[_ 1.md]') - }) + it('opens a file', async function() { + await scenario.open(parseCommandLine(['a/1.md'])); + await scenario.assert('[_ 1.md]'); + }); // This is also the case when a user clicks on a folder in their file manager // (or, on macOS, drags the folder to Atom in their doc) - it('opens a directory', async function () { - await scenario.open(parseCommandLine(['a'])) - await scenario.assert('[a _]') - }) + it('opens a directory', async function() { + await scenario.open(parseCommandLine(['a'])); + await scenario.assert('[a _]'); + }); - it('opens a file with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a/1.md'])) - await scenario.assert('[_ 1.md]') - }) + it('opens a file with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a/1.md'])); + await scenario.assert('[_ 1.md]'); + }); - it('opens a directory with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a'])) - await scenario.assert('[a _]') - }) + it('opens a directory with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a'])); + await scenario.assert('[a _]'); + }); - it('opens a file with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])) - await scenario.assert('[_ 1.md]') - }) + it('opens a file with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])); + await scenario.assert('[_ 1.md]'); + }); - it('opens a directory with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a'])) - await scenario.assert('[a _]') - }) + it('opens a directory with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a'])); + await scenario.assert('[a _]'); + }); - describe('with previous window state', function () { - let app + describe('with previous window state', function() { + let app; - beforeEach(function () { + beforeEach(function() { app = scenario.addApplication({ applicationJson: { version: '1', @@ -107,698 +110,805 @@ describe('AtomApplication', function () { { projectRoots: [scenario.convertRootPath('c')] } ] } - }) - }) + }); + }); - describe('with core.restorePreviousWindowsOnStart set to "no"', function () { - beforeEach(function () { - app.config.set('core.restorePreviousWindowsOnStart', 'no') - }) + describe('with core.restorePreviousWindowsOnStart set to "no"', function() { + beforeEach(function() { + app.config.set('core.restorePreviousWindowsOnStart', 'no'); + }); - it("doesn't restore windows when launched with no arguments", async function () { - await scenario.launch({app}) - await scenario.assert('[_ _]') - }) + it("doesn't restore windows when launched with no arguments", async function() { + await scenario.launch({ app }); + await scenario.assert('[_ _]'); + }); - it("doesn't restore windows when launched with paths to open", async function () { - await scenario.launch({app, pathsToOpen: ['a/1.md']}) - await scenario.assert('[_ 1.md]') - }) + it("doesn't restore windows when launched with paths to open", async function() { + await scenario.launch({ app, pathsToOpen: ['a/1.md'] }); + await scenario.assert('[_ 1.md]'); + }); - it("doesn't restore windows when --new-window is provided", async function () { - await scenario.launch({app, newWindow: true}) - await scenario.assert('[_ _]') - }) - }) + it("doesn't restore windows when --new-window is provided", async function() { + await scenario.launch({ app, newWindow: true }); + await scenario.assert('[_ _]'); + }); + }); - describe('with core.restorePreviousWindowsOnStart set to "yes"', function () { - beforeEach(function () { - app.config.set('core.restorePreviousWindowsOnStart', 'yes') - }) + describe('with core.restorePreviousWindowsOnStart set to "yes"', function() { + beforeEach(function() { + app.config.set('core.restorePreviousWindowsOnStart', 'yes'); + }); - it('restores windows when launched with no arguments', async function () { - await scenario.launch({app}) - await scenario.assert('[b _] [c _]') - }) + it('restores windows when launched with no arguments', async function() { + await scenario.launch({ app }); + await scenario.assert('[b _] [c _]'); + }); - it("doesn't restore windows when launched with paths to open", async function () { - await scenario.launch({app, pathsToOpen: ['a/1.md']}) - await scenario.assert('[_ 1.md]') - }) + it("doesn't restore windows when launched with paths to open", async function() { + await scenario.launch({ app, pathsToOpen: ['a/1.md'] }); + await scenario.assert('[_ 1.md]'); + }); - it("doesn't restore windows when --new-window is provided", async function () { - await scenario.launch({app, newWindow: true}) - await scenario.assert('[_ _]') - }) - }) + it("doesn't restore windows when --new-window is provided", async function() { + await scenario.launch({ app, newWindow: true }); + await scenario.assert('[_ _]'); + }); + }); - describe('with core.restorePreviousWindowsOnStart set to "always"', function () { - beforeEach(function () { - app.config.set('core.restorePreviousWindowsOnStart', 'always') - }) + describe('with core.restorePreviousWindowsOnStart set to "always"', function() { + beforeEach(function() { + app.config.set('core.restorePreviousWindowsOnStart', 'always'); + }); - it('restores windows when launched with no arguments', async function () { - await scenario.launch({app}) - await scenario.assert('[b _] [c _]') - }) + it('restores windows when launched with no arguments', async function() { + await scenario.launch({ app }); + await scenario.assert('[b _] [c _]'); + }); - it('restores windows when launched with a project path to open', async function () { - await scenario.launch({app, pathsToOpen: ['a']}) - await scenario.assert('[b _] [c _] [a _]') - }) + it('restores windows when launched with a project path to open', async function() { + await scenario.launch({ app, pathsToOpen: ['a'] }); + await scenario.assert('[b _] [c _] [a _]'); + }); - it('restores windows when launched with a file path to open', async function () { - await scenario.launch({app, pathsToOpen: ['a/1.md']}) - await scenario.assert('[b _] [c 1.md]') - }) + it('restores windows when launched with a file path to open', async function() { + await scenario.launch({ app, pathsToOpen: ['a/1.md'] }); + await scenario.assert('[b _] [c 1.md]'); + }); - it('collapses new paths into restored windows when appropriate', async function () { - await scenario.launch({app, pathsToOpen: ['b/2.md']}) - await scenario.assert('[b 2.md] [c _]') - }) + it('collapses new paths into restored windows when appropriate', async function() { + await scenario.launch({ app, pathsToOpen: ['b/2.md'] }); + await scenario.assert('[b 2.md] [c _]'); + }); - it("doesn't restore windows when --new-window is provided", async function () { - await scenario.launch({app, newWindow: true}) - await scenario.assert('[_ _]') - }) + it("doesn't restore windows when --new-window is provided", async function() { + await scenario.launch({ app, newWindow: true }); + await scenario.assert('[_ _]'); + }); - it("doesn't restore windows on open, just launch", async function () { - await scenario.launch({app, pathsToOpen: ['a'], newWindow: true}) - await scenario.open(parseCommandLine(['b'])) - await scenario.assert('[a _] [b _]') - }) - }) - }) + it("doesn't restore windows on open, just launch", async function() { + await scenario.launch({ app, pathsToOpen: ['a'], newWindow: true }); + await scenario.open(parseCommandLine(['b'])); + await scenario.assert('[a _] [b _]'); + }); + }); + }); - describe('with unversioned application state', function () { - it('reads "initialPaths" as project roots', async function () { + describe('with unversioned application state', function() { + it('reads "initialPaths" as project roots', async function() { const app = scenario.addApplication({ applicationJson: [ - {initialPaths: [scenario.convertRootPath('a')]}, - {initialPaths: [scenario.convertRootPath('b'), scenario.convertRootPath('c')]} + { initialPaths: [scenario.convertRootPath('a')] }, + { + initialPaths: [ + scenario.convertRootPath('b'), + scenario.convertRootPath('c') + ] + } ] - }) - app.config.set('core.restorePreviousWindowsOnStart', 'always') + }); + app.config.set('core.restorePreviousWindowsOnStart', 'always'); - await scenario.launch({app}) - await scenario.assert('[a _] [b,c _]') - }) + await scenario.launch({ app }); + await scenario.assert('[a _] [b,c _]'); + }); - it('filters file paths from project root lists', async function () { + it('filters file paths from project root lists', async function() { const app = scenario.addApplication({ applicationJson: [ - {initialPaths: [scenario.convertRootPath('b'), scenario.convertEditorPath('a/1.md')]} + { + initialPaths: [ + scenario.convertRootPath('b'), + scenario.convertEditorPath('a/1.md') + ] + } ] - }) - app.config.set('core.restorePreviousWindowsOnStart', 'always') + }); + app.config.set('core.restorePreviousWindowsOnStart', 'always'); - await scenario.launch({app}) - await scenario.assert('[b _]') - }) - }) - }) + await scenario.launch({ app }); + await scenario.assert('[b _]'); + }); + }); + }); - describe('with one empty window', function () { - beforeEach(async function () { - await scenario.preconditions('[_ _]') - }) + describe('with one empty window', function() { + beforeEach(async function() { + await scenario.preconditions('[_ _]'); + }); // This is also the case when a user selects the application from the OS shell - it('opens a new, empty window', async function () { - await scenario.open(parseCommandLine([])) - await scenario.assert('[_ _] [_ _]') - }) + it('opens a new, empty window', async function() { + await scenario.open(parseCommandLine([])); + await scenario.assert('[_ _] [_ _]'); + }); // This is also the case when a user clicks on a file in their file manager - it('opens a file', async function () { - await scenario.open(parseCommandLine(['a/1.md'])) - await scenario.assert('[_ 1.md]') - }) + it('opens a file', async function() { + await scenario.open(parseCommandLine(['a/1.md'])); + await scenario.assert('[_ 1.md]'); + }); // This is also the case when a user clicks on a folder in their file manager - it('opens a directory', async function () { - await scenario.open(parseCommandLine(['a'])) - await scenario.assert('[a _]') - }) + it('opens a directory', async function() { + await scenario.open(parseCommandLine(['a'])); + await scenario.assert('[a _]'); + }); - it('opens a file with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a/1.md'])) - await scenario.assert('[_ 1.md]') - }) + it('opens a file with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a/1.md'])); + await scenario.assert('[_ 1.md]'); + }); - it('opens a directory with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a'])) - await scenario.assert('[a _]') - }) + it('opens a directory with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a'])); + await scenario.assert('[a _]'); + }); - it('opens a file with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])) - await scenario.assert('[_ _] [_ 1.md]') - }) + it('opens a file with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])); + await scenario.assert('[_ _] [_ 1.md]'); + }); - it('opens a directory with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a'])) - await scenario.assert('[_ _] [a _]') - }) - }) + it('opens a directory with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a'])); + await scenario.assert('[_ _] [a _]'); + }); + }); - describe('with one window that has a project root', function () { - beforeEach(async function () { - await scenario.preconditions('[a _]') - }) + describe('with one window that has a project root', function() { + beforeEach(async function() { + await scenario.preconditions('[a _]'); + }); // This is also the case when a user selects the application from the OS shell - it('opens a new, empty window', async function () { - await scenario.open(parseCommandLine([])) - await scenario.assert('[a _] [_ _]') - }) + it('opens a new, empty window', async function() { + await scenario.open(parseCommandLine([])); + await scenario.assert('[a _] [_ _]'); + }); // This is also the case when a user clicks on a file within the project root in their file manager - it('opens a file within the project root', async function () { - await scenario.open(parseCommandLine(['a/1.md'])) - await scenario.assert('[a 1.md]') - }) + it('opens a file within the project root', async function() { + await scenario.open(parseCommandLine(['a/1.md'])); + await scenario.assert('[a 1.md]'); + }); // This is also the case when a user clicks on a project root folder in their file manager - it('opens a directory that matches the project root', async function () { - await scenario.open(parseCommandLine(['a'])) - await scenario.assert('[a _]') - }) + it('opens a directory that matches the project root', async function() { + await scenario.open(parseCommandLine(['a'])); + await scenario.assert('[a _]'); + }); // This is also the case when a user clicks on a file outside the project root in their file manager - it('opens a file outside the project root', async function () { - await scenario.open(parseCommandLine(['b/2.md'])) - await scenario.assert('[a 2.md]') - }) + it('opens a file outside the project root', async function() { + await scenario.open(parseCommandLine(['b/2.md'])); + await scenario.assert('[a 2.md]'); + }); // This is also the case when a user clicks on a new folder in their file manager - it('opens a directory other than the project root', async function () { - await scenario.open(parseCommandLine(['b'])) - await scenario.assert('[a _] [b _]') - }) + it('opens a directory other than the project root', async function() { + await scenario.open(parseCommandLine(['b'])); + await scenario.assert('[a _] [b _]'); + }); - it('opens a file within the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a/1.md'])) - await scenario.assert('[a 1.md]') - }) + it('opens a file within the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a/1.md'])); + await scenario.assert('[a 1.md]'); + }); - it('opens a directory that matches the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a'])) - await scenario.assert('[a _]') - }) + it('opens a directory that matches the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a'])); + await scenario.assert('[a _]'); + }); - it('opens a file outside the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'b/2.md'])) - await scenario.assert('[a 2.md]') - }) + it('opens a file outside the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'b/2.md'])); + await scenario.assert('[a 2.md]'); + }); - it('opens a directory other than the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'b'])) - await scenario.assert('[a,b _]') - }) + it('opens a directory other than the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'b'])); + await scenario.assert('[a,b _]'); + }); - it('opens a file within the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])) - await scenario.assert('[a _] [_ 1.md]') - }) + it('opens a file within the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])); + await scenario.assert('[a _] [_ 1.md]'); + }); - it('opens a directory that matches the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a'])) - await scenario.assert('[a _] [a _]') - }) + it('opens a directory that matches the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a'])); + await scenario.assert('[a _] [a _]'); + }); - it('opens a file outside the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'b/2.md'])) - await scenario.assert('[a _] [_ 2.md]') - }) + it('opens a file outside the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'b/2.md'])); + await scenario.assert('[a _] [_ 2.md]'); + }); - it('opens a directory other than the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'b'])) - await scenario.assert('[a _] [b _]') - }) - }) + it('opens a directory other than the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'b'])); + await scenario.assert('[a _] [b _]'); + }); + }); - describe('with two windows, one with a project root and one empty', function () { - beforeEach(async function () { - await scenario.preconditions('[a _] [_ _]') - }) + describe('with two windows, one with a project root and one empty', function() { + beforeEach(async function() { + await scenario.preconditions('[a _] [_ _]'); + }); // This is also the case when a user selects the application from the OS shell - it('opens a new, empty window', async function () { - await scenario.open(parseCommandLine([])) - await scenario.assert('[a _] [_ _] [_ _]') - }) + it('opens a new, empty window', async function() { + await scenario.open(parseCommandLine([])); + await scenario.assert('[a _] [_ _] [_ _]'); + }); // This is also the case when a user clicks on a file within the project root in their file manager - it('opens a file within the project root', async function () { - await scenario.open(parseCommandLine(['a/1.md'])) - await scenario.assert('[a 1.md] [_ _]') - }) + it('opens a file within the project root', async function() { + await scenario.open(parseCommandLine(['a/1.md'])); + await scenario.assert('[a 1.md] [_ _]'); + }); // This is also the case when a user clicks on a project root folder in their file manager - it('opens a directory that matches the project root', async function () { - await scenario.open(parseCommandLine(['a'])) - await scenario.assert('[a _] [_ _]') - }) + it('opens a directory that matches the project root', async function() { + await scenario.open(parseCommandLine(['a'])); + await scenario.assert('[a _] [_ _]'); + }); // This is also the case when a user clicks on a file outside the project root in their file manager - it('opens a file outside the project root', async function () { - await scenario.open(parseCommandLine(['b/2.md'])) - await scenario.assert('[a _] [_ 2.md]') - }) + it('opens a file outside the project root', async function() { + await scenario.open(parseCommandLine(['b/2.md'])); + await scenario.assert('[a _] [_ 2.md]'); + }); // This is also the case when a user clicks on a new folder in their file manager - it('opens a directory other than the project root', async function () { - await scenario.open(parseCommandLine(['b'])) - await scenario.assert('[a _] [b _]') - }) + it('opens a directory other than the project root', async function() { + await scenario.open(parseCommandLine(['b'])); + await scenario.assert('[a _] [b _]'); + }); - it('opens a file within the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a/1.md'])) - await scenario.assert('[a 1.md] [_ _]') - }) + it('opens a file within the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a/1.md'])); + await scenario.assert('[a 1.md] [_ _]'); + }); - it('opens a directory that matches the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a'])) - await scenario.assert('[a _] [_ _]') - }) + it('opens a directory that matches the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a'])); + await scenario.assert('[a _] [_ _]'); + }); - it('opens a file outside the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'b/2.md'])) - await scenario.assert('[a _] [_ 2.md]') - }) + it('opens a file outside the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'b/2.md'])); + await scenario.assert('[a _] [_ 2.md]'); + }); - it('opens a directory other than the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'b'])) - await scenario.assert('[a _] [b _]') - }) + it('opens a directory other than the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'b'])); + await scenario.assert('[a _] [b _]'); + }); - it('opens a file within the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])) - await scenario.assert('[a _] [_ _] [_ 1.md]') - }) + it('opens a file within the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])); + await scenario.assert('[a _] [_ _] [_ 1.md]'); + }); - it('opens a directory that matches the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a'])) - await scenario.assert('[a _] [_ _] [a _]') - }) + it('opens a directory that matches the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a'])); + await scenario.assert('[a _] [_ _] [a _]'); + }); - it('opens a file outside the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'b/2.md'])) - await scenario.assert('[a _] [_ _] [_ 2.md]') - }) + it('opens a file outside the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'b/2.md'])); + await scenario.assert('[a _] [_ _] [_ 2.md]'); + }); - it('opens a directory other than the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'b'])) - await scenario.assert('[a _] [_ _] [b _]') - }) - }) + it('opens a directory other than the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'b'])); + await scenario.assert('[a _] [_ _] [b _]'); + }); + }); - describe('with two windows, one empty and one with a project root', function () { - beforeEach(async function () { - await scenario.preconditions('[_ _] [a _]') - }) + describe('with two windows, one empty and one with a project root', function() { + beforeEach(async function() { + await scenario.preconditions('[_ _] [a _]'); + }); // This is also the case when a user selects the application from the OS shell - it('opens a new, empty window', async function () { - await scenario.open(parseCommandLine([])) - await scenario.assert('[_ _] [a _] [_ _]') - }) + it('opens a new, empty window', async function() { + await scenario.open(parseCommandLine([])); + await scenario.assert('[_ _] [a _] [_ _]'); + }); // This is also the case when a user clicks on a file within the project root in their file manager - it('opens a file within the project root', async function () { - await scenario.open(parseCommandLine(['a/1.md'])) - await scenario.assert('[_ _] [a 1.md]') - }) + it('opens a file within the project root', async function() { + await scenario.open(parseCommandLine(['a/1.md'])); + await scenario.assert('[_ _] [a 1.md]'); + }); // This is also the case when a user clicks on a project root folder in their file manager - it('opens a directory that matches the project root', async function () { - await scenario.open(parseCommandLine(['a'])) - await scenario.assert('[_ _] [a _]') - }) + it('opens a directory that matches the project root', async function() { + await scenario.open(parseCommandLine(['a'])); + await scenario.assert('[_ _] [a _]'); + }); // This is also the case when a user clicks on a file outside the project root in their file manager - it('opens a file outside the project root', async function () { - await scenario.open(parseCommandLine(['b/2.md'])) - await scenario.assert('[_ 2.md] [a _]') - }) + it('opens a file outside the project root', async function() { + await scenario.open(parseCommandLine(['b/2.md'])); + await scenario.assert('[_ 2.md] [a _]'); + }); // This is also the case when a user clicks on a new folder in their file manager - it('opens a directory other than the project root', async function () { - await scenario.open(parseCommandLine(['b'])) - await scenario.assert('[b _] [a _]') - }) + it('opens a directory other than the project root', async function() { + await scenario.open(parseCommandLine(['b'])); + await scenario.assert('[b _] [a _]'); + }); - it('opens a file within the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a/1.md'])) - await scenario.assert('[_ _] [a 1.md]') - }) + it('opens a file within the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a/1.md'])); + await scenario.assert('[_ _] [a 1.md]'); + }); - it('opens a directory that matches the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'a'])) - await scenario.assert('[_ _] [a _]') - }) + it('opens a directory that matches the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'a'])); + await scenario.assert('[_ _] [a _]'); + }); - it('opens a file outside the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'b/2.md'])) - await scenario.assert('[_ _] [a 2.md]') - }) + it('opens a file outside the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'b/2.md'])); + await scenario.assert('[_ _] [a 2.md]'); + }); - it('opens a directory other than the project root with --add', async function () { - await scenario.open(parseCommandLine(['--add', 'b'])) - await scenario.assert('[_ _] [a,b _]') - }) + it('opens a directory other than the project root with --add', async function() { + await scenario.open(parseCommandLine(['--add', 'b'])); + await scenario.assert('[_ _] [a,b _]'); + }); - it('opens a file within the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])) - await scenario.assert('[_ _] [a _] [_ 1.md]') - }) + it('opens a file within the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a/1.md'])); + await scenario.assert('[_ _] [a _] [_ 1.md]'); + }); - it('opens a directory that matches the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'a'])) - await scenario.assert('[_ _] [a _] [a _]') - }) + it('opens a directory that matches the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'a'])); + await scenario.assert('[_ _] [a _] [a _]'); + }); - it('opens a file outside the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'b/2.md'])) - await scenario.assert('[_ _] [a _] [_ 2.md]') - }) + it('opens a file outside the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'b/2.md'])); + await scenario.assert('[_ _] [a _] [_ 2.md]'); + }); - it('opens a directory other than the project root with --new-window', async function () { - await scenario.open(parseCommandLine(['--new-window', 'b'])) - await scenario.assert('[_ _] [a _] [b _]') - }) - }) + it('opens a directory other than the project root with --new-window', async function() { + await scenario.open(parseCommandLine(['--new-window', 'b'])); + await scenario.assert('[_ _] [a _] [b _]'); + }); + }); - describe('--wait', function () { - it('kills the specified pid after a newly-opened window is closed', async function () { - const [w0] = await scenario.launch(parseCommandLine(['--new-window', '--wait', '--pid', '101'])) - const w1 = await scenario.open(parseCommandLine(['--new-window', '--wait', '--pid', '202'])) + describe('--wait', function() { + it('kills the specified pid after a newly-opened window is closed', async function() { + const [w0] = await scenario.launch( + parseCommandLine(['--new-window', '--wait', '--pid', '101']) + ); + const w1 = await scenario.open( + parseCommandLine(['--new-window', '--wait', '--pid', '202']) + ); - assert.lengthOf(scenario.killedPids, 0) + assert.lengthOf(scenario.killedPids, 0); - w0.browserWindow.emit('closed') - assert.deepEqual(scenario.killedPids, [101]) + w0.browserWindow.emit('closed'); + assert.deepEqual(scenario.killedPids, [101]); - w1.browserWindow.emit('closed') - assert.deepEqual(scenario.killedPids, [101, 202]) - }) + w1.browserWindow.emit('closed'); + assert.deepEqual(scenario.killedPids, [101, 202]); + }); - it('kills the specified pid after all newly-opened files in an existing window are closed', async function () { - const [w] = await scenario.launch(parseCommandLine(['--new-window', 'a'])) - await scenario.open(parseCommandLine(['--add', '--wait', '--pid', '303', 'a/1.md', 'b/2.md'])) - await scenario.assert('[a 1.md,2.md]') + it('kills the specified pid after all newly-opened files in an existing window are closed', async function() { + const [w] = await scenario.launch( + parseCommandLine(['--new-window', 'a']) + ); + await scenario.open( + parseCommandLine([ + '--add', + '--wait', + '--pid', + '303', + 'a/1.md', + 'b/2.md' + ]) + ); + await scenario.assert('[a 1.md,2.md]'); - assert.lengthOf(scenario.killedPids, 0) + assert.lengthOf(scenario.killedPids, 0); - scenario.getApplication(0).windowDidClosePathWithWaitSession(w, scenario.convertEditorPath('b/2.md')) - assert.lengthOf(scenario.killedPids, 0) - scenario.getApplication(0).windowDidClosePathWithWaitSession(w, scenario.convertEditorPath('a/1.md')) - assert.deepEqual(scenario.killedPids, [303]) - }) + scenario + .getApplication(0) + .windowDidClosePathWithWaitSession( + w, + scenario.convertEditorPath('b/2.md') + ); + assert.lengthOf(scenario.killedPids, 0); + scenario + .getApplication(0) + .windowDidClosePathWithWaitSession( + w, + scenario.convertEditorPath('a/1.md') + ); + assert.deepEqual(scenario.killedPids, [303]); + }); - it('kills the specified pid after a newly-opened directory in an existing window is closed', async function () { - const [w] = await scenario.launch(parseCommandLine(['--new-window', 'a'])) - await scenario.open(parseCommandLine(['--add', '--wait', '--pid', '404', 'b'])) - await scenario.assert('[a,b _]') + it('kills the specified pid after a newly-opened directory in an existing window is closed', async function() { + const [w] = await scenario.launch( + parseCommandLine(['--new-window', 'a']) + ); + await scenario.open( + parseCommandLine(['--add', '--wait', '--pid', '404', 'b']) + ); + await scenario.assert('[a,b _]'); - assert.lengthOf(scenario.killedPids, 0) + assert.lengthOf(scenario.killedPids, 0); - scenario.getApplication(0).windowDidClosePathWithWaitSession(w, scenario.convertRootPath('b')) - assert.deepEqual(scenario.killedPids, [404]) - }) - }) + scenario + .getApplication(0) + .windowDidClosePathWithWaitSession(w, scenario.convertRootPath('b')); + assert.deepEqual(scenario.killedPids, [404]); + }); + }); - describe('atom:// URLs', function () { - describe('with a package-name host', function () { - it("loads the package's urlMain in a new window", async function () { - await scenario.launch({}) + describe('atom:// URLs', function() { + describe('with a package-name host', function() { + it("loads the package's urlMain in a new window", async function() { + await scenario.launch({}); - const app = scenario.getApplication(0) + const app = scenario.getApplication(0); app.packages = { - getAvailablePackageMetadata: () => [{name: 'package-with-url-main', urlMain: 'some/url-main'}], - resolvePackagePath: () => path.resolve('dot-atom/package-with-url-main') - } + getAvailablePackageMetadata: () => [ + { name: 'package-with-url-main', urlMain: 'some/url-main' } + ], + resolvePackagePath: () => + path.resolve('dot-atom/package-with-url-main') + }; - const [w1, w2] = await scenario.open(parseCommandLine([ - 'atom://package-with-url-main/test1', - 'atom://package-with-url-main/test2' - ])) + const [w1, w2] = await scenario.open( + parseCommandLine([ + 'atom://package-with-url-main/test1', + 'atom://package-with-url-main/test2' + ]) + ); assert.strictEqual( w1.loadSettings.windowInitializationScript, path.resolve('dot-atom/package-with-url-main/some/url-main') - ) + ); assert.strictEqual( w1.loadSettings.urlToOpen, 'atom://package-with-url-main/test1' - ) + ); assert.strictEqual( w2.loadSettings.windowInitializationScript, path.resolve('dot-atom/package-with-url-main/some/url-main') - ) + ); assert.strictEqual( w2.loadSettings.urlToOpen, 'atom://package-with-url-main/test2' - ) - }) + ); + }); - it('sends a URI message to the most recently focused non-spec window', async function () { - const [w0] = await scenario.launch({}) - const w1 = await scenario.open(parseCommandLine(['--new-window'])) - const w2 = await scenario.open(parseCommandLine(['--new-window'])) - const w3 = await scenario.open(parseCommandLine(['--test', 'a/1.md'])) + it('sends a URI message to the most recently focused non-spec window', async function() { + const [w0] = await scenario.launch({}); + const w1 = await scenario.open(parseCommandLine(['--new-window'])); + const w2 = await scenario.open(parseCommandLine(['--new-window'])); + const w3 = await scenario.open( + parseCommandLine(['--test', 'a/1.md']) + ); - const app = scenario.getApplication(0) + const app = scenario.getApplication(0); app.packages = { getAvailablePackageMetadata: () => [] - } + }; - const [uw] = await scenario.open(parseCommandLine(['atom://package-without-url-main/test'])) - assert.strictEqual(uw, w2) + const [uw] = await scenario.open( + parseCommandLine(['atom://package-without-url-main/test']) + ); + assert.strictEqual(uw, w2); - assert.isTrue(w2.sendURIMessage.calledWith('atom://package-without-url-main/test')) - assert.strictEqual(w2.focus.callCount, 2) + assert.isTrue( + w2.sendURIMessage.calledWith('atom://package-without-url-main/test') + ); + assert.strictEqual(w2.focus.callCount, 2); for (const other of [w0, w1, w3]) { - assert.isFalse(other.sendURIMessage.called) + assert.isFalse(other.sendURIMessage.called); } - }) + }); - it('creates a new window and sends a URI message to it once it loads', async function () { - const [w0] = await scenario.launch(parseCommandLine(['--test', 'a/1.md'])) + it('creates a new window and sends a URI message to it once it loads', async function() { + const [w0] = await scenario.launch( + parseCommandLine(['--test', 'a/1.md']) + ); - const app = scenario.getApplication(0) + const app = scenario.getApplication(0); app.packages = { getAvailablePackageMetadata: () => [] - } + }; - const [uw] = await scenario.open(parseCommandLine(['atom://package-without-url-main/test'])) - assert.notStrictEqual(uw, w0) + const [uw] = await scenario.open( + parseCommandLine(['atom://package-without-url-main/test']) + ); + assert.notStrictEqual(uw, w0); assert.strictEqual( uw.loadSettings.windowInitializationScript, - path.resolve(__dirname, '../../src/initialize-application-window.js') - ) + path.resolve( + __dirname, + '../../src/initialize-application-window.js' + ) + ); - uw.emit('window:loaded') - assert.isTrue(uw.sendURIMessage.calledWith('atom://package-without-url-main/test')) - }) - }) + uw.emit('window:loaded'); + assert.isTrue( + uw.sendURIMessage.calledWith('atom://package-without-url-main/test') + ); + }); + }); - describe('with a "core" host', function () { - it('sends a URI message to the most recently focused non-spec window that owns the open locations', async function () { - const [w0] = await scenario.launch(parseCommandLine(['a'])) - const w1 = await scenario.open(parseCommandLine(['--new-window', 'a'])) - const w2 = await scenario.open(parseCommandLine(['--new-window', 'b'])) + describe('with a "core" host', function() { + it('sends a URI message to the most recently focused non-spec window that owns the open locations', async function() { + const [w0] = await scenario.launch(parseCommandLine(['a'])); + const w1 = await scenario.open( + parseCommandLine(['--new-window', 'a']) + ); + const w2 = await scenario.open( + parseCommandLine(['--new-window', 'b']) + ); - const uri = `atom://core/open/file?filename=${encodeURIComponent(scenario.convertEditorPath('a/1.md'))}` - const [uw] = await scenario.open(parseCommandLine([uri])) - assert.strictEqual(uw, w1) - assert.isTrue(w1.sendURIMessage.calledWith(uri)) + const uri = `atom://core/open/file?filename=${encodeURIComponent( + scenario.convertEditorPath('a/1.md') + )}`; + const [uw] = await scenario.open(parseCommandLine([uri])); + assert.strictEqual(uw, w1); + assert.isTrue(w1.sendURIMessage.calledWith(uri)); for (const other of [w0, w2]) { - assert.isFalse(other.sendURIMessage.called) + assert.isFalse(other.sendURIMessage.called); } - }) + }); - it('creates a new window and sends a URI message to it once it loads', async function () { - const [w0] = await scenario.launch(parseCommandLine(['--test', 'a/1.md'])) + it('creates a new window and sends a URI message to it once it loads', async function() { + const [w0] = await scenario.launch( + parseCommandLine(['--test', 'a/1.md']) + ); - const uri = `atom://core/open/file?filename=${encodeURIComponent(scenario.convertEditorPath('b/2.md'))}` - const [uw] = await scenario.open(parseCommandLine([uri])) - assert.notStrictEqual(uw, w0) + const uri = `atom://core/open/file?filename=${encodeURIComponent( + scenario.convertEditorPath('b/2.md') + )}`; + const [uw] = await scenario.open(parseCommandLine([uri])); + assert.notStrictEqual(uw, w0); - uw.emit('window:loaded') - assert.isTrue(uw.sendURIMessage.calledWith(uri)) - }) - }) - }) + uw.emit('window:loaded'); + assert.isTrue(uw.sendURIMessage.calledWith(uri)); + }); + }); + }); - it('opens a file to a specific line number', async function () { - await scenario.open(parseCommandLine(['a/1.md:10'])) - await scenario.assert('[_ 1.md]') + it('opens a file to a specific line number', async function() { + await scenario.open(parseCommandLine(['a/1.md:10'])); + await scenario.assert('[_ 1.md]'); - const w = scenario.getWindow(0) - assert.lengthOf(w._locations, 1) - assert.strictEqual(w._locations[0].initialLine, 9) - assert.isNull(w._locations[0].initialColumn) - }) + const w = scenario.getWindow(0); + assert.lengthOf(w._locations, 1); + assert.strictEqual(w._locations[0].initialLine, 9); + assert.isNull(w._locations[0].initialColumn); + }); - it('opens a file to a specific line number and column', async function () { - await scenario.open(parseCommandLine('b/2.md:12:5')) - await scenario.assert('[_ 2.md]') + it('opens a file to a specific line number and column', async function() { + await scenario.open(parseCommandLine('b/2.md:12:5')); + await scenario.assert('[_ 2.md]'); - const w = scenario.getWindow(0) - assert.lengthOf(w._locations, 1) - assert.strictEqual(w._locations[0].initialLine, 11) - assert.strictEqual(w._locations[0].initialColumn, 4) - }) + const w = scenario.getWindow(0); + assert.lengthOf(w._locations, 1); + assert.strictEqual(w._locations[0].initialLine, 11); + assert.strictEqual(w._locations[0].initialColumn, 4); + }); - it('opens a directory with a non-file protocol', async function () { - await scenario.open(parseCommandLine(['remote://server:3437/some/directory/path'])) + it('opens a directory with a non-file protocol', async function() { + await scenario.open( + parseCommandLine(['remote://server:3437/some/directory/path']) + ); - const w = scenario.getWindow(0) - assert.lengthOf(w._locations, 1) - assert.strictEqual(w._locations[0].pathToOpen, 'remote://server:3437/some/directory/path') - assert.isFalse(w._locations[0].exists) - assert.isFalse(w._locations[0].isDirectory) - assert.isFalse(w._locations[0].isFile) - }) + const w = scenario.getWindow(0); + assert.lengthOf(w._locations, 1); + assert.strictEqual( + w._locations[0].pathToOpen, + 'remote://server:3437/some/directory/path' + ); + assert.isFalse(w._locations[0].exists); + assert.isFalse(w._locations[0].isDirectory); + assert.isFalse(w._locations[0].isFile); + }); - it('truncates trailing whitespace and colons', async function () { - await scenario.open(parseCommandLine('b/2.md:: ')) - await scenario.assert('[_ 2.md]') + it('truncates trailing whitespace and colons', async function() { + await scenario.open(parseCommandLine('b/2.md:: ')); + await scenario.assert('[_ 2.md]'); - const w = scenario.getWindow(0) - assert.lengthOf(w._locations, 1) - assert.isNull(w._locations[0].initialLine) - assert.isNull(w._locations[0].initialColumn) - }) + const w = scenario.getWindow(0); + assert.lengthOf(w._locations, 1); + assert.isNull(w._locations[0].initialLine); + assert.isNull(w._locations[0].initialColumn); + }); - it('disregards test and benchmark windows', async function () { - await scenario.launch(parseCommandLine(['--test', 'b'])) - await scenario.open(parseCommandLine(['--new-window'])) - await scenario.open(parseCommandLine(['--test', 'c'])) - await scenario.open(parseCommandLine(['--benchmark', 'b'])) + it('disregards test and benchmark windows', async function() { + await scenario.launch(parseCommandLine(['--test', 'b'])); + await scenario.open(parseCommandLine(['--new-window'])); + await scenario.open(parseCommandLine(['--test', 'c'])); + await scenario.open(parseCommandLine(['--benchmark', 'b'])); - await scenario.open(parseCommandLine(['a/1.md'])) + await scenario.open(parseCommandLine(['a/1.md'])); // Test and benchmark StubWindows are visible as empty editor windows here - await scenario.assert('[_ _] [_ 1.md] [_ _] [_ _]') - }) - }) + await scenario.assert('[_ _] [_ 1.md] [_ _] [_ _]'); + }); + }); if (process.platform === 'darwin' || process.platform === 'win32') { - it('positions new windows at an offset from the previous window', async function () { - const [w0] = await scenario.launch(parseCommandLine(['a'])) - w0.setSize(400, 400) - const d0 = w0.getDimensions() + it('positions new windows at an offset from the previous window', async function() { + const [w0] = await scenario.launch(parseCommandLine(['a'])); + w0.setSize(400, 400); + const d0 = w0.getDimensions(); - const w1 = await scenario.open(parseCommandLine(['b'])) - const d1 = w1.getDimensions() + const w1 = await scenario.open(parseCommandLine(['b'])); + const d1 = w1.getDimensions(); - assert.isAbove(d1.x, d0.x) - assert.isAbove(d1.y, d0.y) - }) + assert.isAbove(d1.x, d0.x); + assert.isAbove(d1.y, d0.y); + }); } if (process.platform === 'darwin') { - describe('with no windows open', function () { - let app + describe('with no windows open', function() { + let app; - beforeEach(async function () { - const [w] = await scenario.launch(parseCommandLine([])) + beforeEach(async function() { + const [w] = await scenario.launch(parseCommandLine([])); - app = scenario.getApplication(0) - app.removeWindow(w) - sinon.stub(app, 'promptForPathToOpen') - }) + app = scenario.getApplication(0); + app.removeWindow(w); + sinon.stub(app, 'promptForPathToOpen'); + }); - it('opens a new file', function () { - app.emit('application:open-file') - assert.isTrue(app.promptForPathToOpen.calledWith('file', {devMode: false, safeMode: false, window: null})) - }) + it('opens a new file', function() { + app.emit('application:open-file'); + assert.isTrue( + app.promptForPathToOpen.calledWith('file', { + devMode: false, + safeMode: false, + window: null + }) + ); + }); - it('opens a new directory', function () { - app.emit('application:open-folder') - assert.isTrue(app.promptForPathToOpen.calledWith('folder', {devMode: false, safeMode: false, window: null})) - }) + it('opens a new directory', function() { + app.emit('application:open-folder'); + assert.isTrue( + app.promptForPathToOpen.calledWith('folder', { + devMode: false, + safeMode: false, + window: null + }) + ); + }); - it('opens a new file or directory', function () { - app.emit('application:open') - assert.isTrue(app.promptForPathToOpen.calledWith('all', {devMode: false, safeMode: false, window: null})) - }) + it('opens a new file or directory', function() { + app.emit('application:open'); + assert.isTrue( + app.promptForPathToOpen.calledWith('all', { + devMode: false, + safeMode: false, + window: null + }) + ); + }); - it('reopens a project in a new window', async function () { - const paths = scenario.convertPaths(['a', 'b']) - app.emit('application:reopen-project', {paths}) + it('reopens a project in a new window', async function() { + const paths = scenario.convertPaths(['a', 'b']); + app.emit('application:reopen-project', { paths }); - await conditionPromise(() => app.getAllWindows().length > 0) + await conditionPromise(() => app.getAllWindows().length > 0); - assert.deepEqual(app.getAllWindows().map(w => Array.from(w._rootPaths)), [paths]) - }) - }) + assert.deepEqual( + app.getAllWindows().map(w => Array.from(w._rootPaths)), + [paths] + ); + }); + }); } - describe('existing application re-use', function () { - let createApplication + describe('existing application re-use', function() { + let createApplication; - const version = electron.app.getVersion() + const version = electron.app.getVersion(); - beforeEach(function () { + beforeEach(function() { createApplication = async options => { - options.version = version + options.version = version; - const app = scenario.addApplication(options) - await app.listenForArgumentsFromNewProcess(options) - await app.launch(options) - return app - } - }) + const app = scenario.addApplication(options); + await app.listenForArgumentsFromNewProcess(options); + await app.launch(options); + return app; + }; + }); - it('creates a new application when no socket is present', async function () { - const app0 = await AtomApplication.open({createApplication, version}) - await app0.deleteSocketSecretFile() + it('creates a new application when no socket is present', async function() { + const app0 = await AtomApplication.open({ createApplication, version }); + await app0.deleteSocketSecretFile(); - const app1 = await AtomApplication.open({createApplication, version}) - assert.isNotNull(app1) - assert.notStrictEqual(app0, app1) - }) + const app1 = await AtomApplication.open({ createApplication, version }); + assert.isNotNull(app1); + assert.notStrictEqual(app0, app1); + }); - it('creates a new application for spec windows', async function () { - const app0 = await AtomApplication.open({createApplication, version}) + it('creates a new application for spec windows', async function() { + const app0 = await AtomApplication.open({ createApplication, version }); - const app1 = await AtomApplication.open({createApplication, version, ...parseCommandLine(['--test', 'a'])}) - assert.isNotNull(app1) - assert.notStrictEqual(app0, app1) - }) + const app1 = await AtomApplication.open({ + createApplication, + version, + ...parseCommandLine(['--test', 'a']) + }); + assert.isNotNull(app1); + assert.notStrictEqual(app0, app1); + }); - it('sends a request to an existing application when a socket is present', async function () { - const app0 = await AtomApplication.open({createApplication, version}) - assert.lengthOf(app0.getAllWindows(), 1) + it('sends a request to an existing application when a socket is present', async function() { + const app0 = await AtomApplication.open({ createApplication, version }); + assert.lengthOf(app0.getAllWindows(), 1); - const app1 = await AtomApplication.open({createApplication, version, ...parseCommandLine(['--new-window'])}) - assert.isNull(app1) - assert.isTrue(electron.app.quit.called) + const app1 = await AtomApplication.open({ + createApplication, + version, + ...parseCommandLine(['--new-window']) + }); + assert.isNull(app1); + assert.isTrue(electron.app.quit.called); - await conditionPromise(() => app0.getAllWindows().length === 2) - await scenario.assert('[_ _] [_ _]') - }) - }) + await conditionPromise(() => app0.getAllWindows().length === 2); + await scenario.assert('[_ _] [_ _]'); + }); + }); - describe('IPC handling', function () { - let w0, w1, w2, app + describe('IPC handling', function() { + let w0, w1, w2, app; - beforeEach(async function () { - w0 = (await scenario.launch(parseCommandLine(['a'])))[0] - w1 = await scenario.open(parseCommandLine(['--new-window'])) - w2 = await scenario.open(parseCommandLine(['--new-window', 'b'])) + beforeEach(async function() { + w0 = (await scenario.launch(parseCommandLine(['a'])))[0]; + w1 = await scenario.open(parseCommandLine(['--new-window'])); + w2 = await scenario.open(parseCommandLine(['--new-window', 'b'])); - app = scenario.getApplication(0) - sinon.spy(app, 'openPaths') - sinon.stub(app, 'promptForPath', (_type, callback, defaultPath) => callback([defaultPath])) - }) + app = scenario.getApplication(0); + sinon.spy(app, 'openPaths'); + sinon.stub(app, 'promptForPath', (_type, callback, defaultPath) => + callback([defaultPath]) + ); + }); // This is the IPC message used to handle: // * application:reopen-project @@ -806,509 +916,630 @@ describe('AtomApplication', function () { // * drag and drop // * deprecated call links in deprecation-cop // * other direct callers of `atom.open()` - it('"open" opens a fixed path by the standard opening rules', async function () { - sinon.stub(app, 'atomWindowForEvent', () => w1) + it('"open" opens a fixed path by the standard opening rules', async function() { + sinon.stub(app, 'atomWindowForEvent', () => w1); - electron.ipcMain.emit('open', {}, {pathsToOpen: [scenario.convertEditorPath('a/1.md')]}) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a 1.md] [_ _] [b _]') + electron.ipcMain.emit( + 'open', + {}, + { pathsToOpen: [scenario.convertEditorPath('a/1.md')] } + ); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a 1.md] [_ _] [b _]'); - electron.ipcMain.emit('open', {}, {pathsToOpen: [scenario.convertRootPath('c')]}) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a 1.md] [c _] [b _]') + electron.ipcMain.emit( + 'open', + {}, + { pathsToOpen: [scenario.convertRootPath('c')] } + ); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a 1.md] [c _] [b _]'); - electron.ipcMain.emit('open', {}, {pathsToOpen: [scenario.convertRootPath('d')], here: true}) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a 1.md] [c,d _] [b _]') - }) + electron.ipcMain.emit( + 'open', + {}, + { pathsToOpen: [scenario.convertRootPath('d')], here: true } + ); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a 1.md] [c,d _] [b _]'); + }); - it('"open" without any option open the prompt for selecting a path', async function () { - sinon.stub(app, 'atomWindowForEvent', () => w1) + it('"open" without any option open the prompt for selecting a path', async function() { + sinon.stub(app, 'atomWindowForEvent', () => w1); - electron.ipcMain.emit('open', {}) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'all') - }) + electron.ipcMain.emit('open', {}); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'all'); + }); - it('"open-chosen-any" opens a file in the sending window', async function () { - sinon.stub(app, 'atomWindowForEvent', () => w2) + it('"open-chosen-any" opens a file in the sending window', async function() { + sinon.stub(app, 'atomWindowForEvent', () => w2); - electron.ipcMain.emit('open-chosen-any', {}, scenario.convertEditorPath('a/1.md')) - await conditionPromise(() => app.openPaths.called) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a _] [_ _] [b 1.md]') + electron.ipcMain.emit( + 'open-chosen-any', + {}, + scenario.convertEditorPath('a/1.md') + ); + await conditionPromise(() => app.openPaths.called); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a _] [_ _] [b 1.md]'); - assert.isTrue(app.promptForPath.called) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'all') - }) + assert.isTrue(app.promptForPath.called); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'all'); + }); - it('"open-chosen-any" opens a directory by the standard opening rules', async function () { - sinon.stub(app, 'atomWindowForEvent', () => w1) + it('"open-chosen-any" opens a directory by the standard opening rules', async function() { + sinon.stub(app, 'atomWindowForEvent', () => w1); // Open unrecognized directory in empty window - electron.ipcMain.emit('open-chosen-any', {}, scenario.convertRootPath('c')) - await conditionPromise(() => app.openPaths.callCount > 0) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a _] [c _] [b _]') + electron.ipcMain.emit( + 'open-chosen-any', + {}, + scenario.convertRootPath('c') + ); + await conditionPromise(() => app.openPaths.callCount > 0); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a _] [c _] [b _]'); - assert.strictEqual(app.promptForPath.callCount, 1) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'all') + assert.strictEqual(app.promptForPath.callCount, 1); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'all'); // Open unrecognized directory in new window - electron.ipcMain.emit('open-chosen-any', {}, scenario.convertRootPath('d')) - await conditionPromise(() => app.openPaths.callCount > 1) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a _] [c _] [b _] [d _]') + electron.ipcMain.emit( + 'open-chosen-any', + {}, + scenario.convertRootPath('d') + ); + await conditionPromise(() => app.openPaths.callCount > 1); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a _] [c _] [b _] [d _]'); - assert.strictEqual(app.promptForPath.callCount, 2) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'all') + assert.strictEqual(app.promptForPath.callCount, 2); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'all'); // Open recognized directory in existing window - electron.ipcMain.emit('open-chosen-any', {}, scenario.convertRootPath('a')) - await conditionPromise(() => app.openPaths.callCount > 2) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a _] [c _] [b _] [d _]') + electron.ipcMain.emit( + 'open-chosen-any', + {}, + scenario.convertRootPath('a') + ); + await conditionPromise(() => app.openPaths.callCount > 2); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a _] [c _] [b _] [d _]'); - assert.strictEqual(app.promptForPath.callCount, 3) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'all') - }) + assert.strictEqual(app.promptForPath.callCount, 3); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'all'); + }); - it('"open-chosen-file" opens a file chooser and opens the chosen file in the sending window', async function () { - sinon.stub(app, 'atomWindowForEvent', () => w0) + it('"open-chosen-file" opens a file chooser and opens the chosen file in the sending window', async function() { + sinon.stub(app, 'atomWindowForEvent', () => w0); - electron.ipcMain.emit('open-chosen-file', {}, scenario.convertEditorPath('b/2.md')) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a 2.md] [_ _] [b _]') + electron.ipcMain.emit( + 'open-chosen-file', + {}, + scenario.convertEditorPath('b/2.md') + ); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a 2.md] [_ _] [b _]'); - assert.isTrue(app.promptForPath.called) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'file') - }) + assert.isTrue(app.promptForPath.called); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'file'); + }); - it('"open-chosen-folder" opens a directory chooser and opens the chosen directory', async function () { - sinon.stub(app, 'atomWindowForEvent', () => w0) + it('"open-chosen-folder" opens a directory chooser and opens the chosen directory', async function() { + sinon.stub(app, 'atomWindowForEvent', () => w0); - electron.ipcMain.emit('open-chosen-folder', {}, scenario.convertRootPath('c')) - await app.openPaths.lastCall.returnValue - await scenario.assert('[a _] [c _] [b _]') + electron.ipcMain.emit( + 'open-chosen-folder', + {}, + scenario.convertRootPath('c') + ); + await app.openPaths.lastCall.returnValue; + await scenario.assert('[a _] [c _] [b _]'); - assert.isTrue(app.promptForPath.called) - assert.strictEqual(app.promptForPath.lastCall.args[0], 'folder') - }) - }) + assert.isTrue(app.promptForPath.called); + assert.strictEqual(app.promptForPath.lastCall.args[0], 'folder'); + }); + }); - describe('window state serialization', function () { - it('occurs immediately when adding a window', async function () { - await scenario.launch(parseCommandLine(['a'])) + describe('window state serialization', function() { + it('occurs immediately when adding a window', async function() { + await scenario.launch(parseCommandLine(['a'])); - const promise = emitterEventPromise(scenario.getApplication(0), 'application:did-save-state') - await scenario.open(parseCommandLine(['c', 'b'])) - await promise + const promise = emitterEventPromise( + scenario.getApplication(0), + 'application:did-save-state' + ); + await scenario.open(parseCommandLine(['c', 'b'])); + await promise; - assert.isTrue(scenario.getApplication(0).storageFolder.store.calledWith( - 'application.json', - { - version: '1', - windows: [ - {projectRoots: [scenario.convertRootPath('a')]}, - {projectRoots: [scenario.convertRootPath('b'), scenario.convertRootPath('c')]} - ] - } - )) - }) + assert.isTrue( + scenario + .getApplication(0) + .storageFolder.store.calledWith('application.json', { + version: '1', + windows: [ + { projectRoots: [scenario.convertRootPath('a')] }, + { + projectRoots: [ + scenario.convertRootPath('b'), + scenario.convertRootPath('c') + ] + } + ] + }) + ); + }); - it('occurs immediately when removing a window', async function () { - await scenario.launch(parseCommandLine(['a'])) - const w = await scenario.open(parseCommandLine(['b'])) + it('occurs immediately when removing a window', async function() { + await scenario.launch(parseCommandLine(['a'])); + const w = await scenario.open(parseCommandLine(['b'])); - const promise = emitterEventPromise(scenario.getApplication(0), 'application:did-save-state') - scenario.getApplication(0).removeWindow(w) - await promise + const promise = emitterEventPromise( + scenario.getApplication(0), + 'application:did-save-state' + ); + scenario.getApplication(0).removeWindow(w); + await promise; - assert.isTrue(scenario.getApplication(0).storageFolder.store.calledWith( - 'application.json', - { - version: '1', - windows: [ - {projectRoots: [scenario.convertRootPath('a')]} - ] - } - )) - }) + assert.isTrue( + scenario + .getApplication(0) + .storageFolder.store.calledWith('application.json', { + version: '1', + windows: [{ projectRoots: [scenario.convertRootPath('a')] }] + }) + ); + }); - it('occurs when the window is blurred', async function () { - const [w] = await scenario.launch(parseCommandLine(['a'])) - const promise = emitterEventPromise(scenario.getApplication(0), 'application:did-save-state') - w.browserWindow.emit('blur') - await promise - }) - }) + it('occurs when the window is blurred', async function() { + const [w] = await scenario.launch(parseCommandLine(['a'])); + const promise = emitterEventPromise( + scenario.getApplication(0), + 'application:did-save-state' + ); + w.browserWindow.emit('blur'); + await promise; + }); + }); - describe('when closing the last window', function () { + describe('when closing the last window', function() { if (process.platform === 'linux' || process.platform === 'win32') { - it('quits the application', async function () { - const [w] = await scenario.launch(parseCommandLine(['a'])) - scenario.getApplication(0).removeWindow(w) - assert.isTrue(electron.app.quit.called) - }) + it('quits the application', async function() { + const [w] = await scenario.launch(parseCommandLine(['a'])); + scenario.getApplication(0).removeWindow(w); + assert.isTrue(electron.app.quit.called); + }); } else if (process.platform === 'darwin') { - it('leaves the application open', async function () { - const [w] = await scenario.launch(parseCommandLine(['a'])) - scenario.getApplication(0).removeWindow(w) - assert.isFalse(electron.app.quit.called) - }) + it('leaves the application open', async function() { + const [w] = await scenario.launch(parseCommandLine(['a'])); + scenario.getApplication(0).removeWindow(w); + assert.isFalse(electron.app.quit.called); + }); } - }) + }); - describe('quitting', function () { - it('waits until all windows have saved their state before quitting', async function () { - const [w0] = await scenario.launch(parseCommandLine(['a'])) - const w1 = await scenario.open(parseCommandLine(['b'])) - assert.notStrictEqual(w0, w1) + describe('quitting', function() { + it('waits until all windows have saved their state before quitting', async function() { + const [w0] = await scenario.launch(parseCommandLine(['a'])); + const w1 = await scenario.open(parseCommandLine(['b'])); + assert.notStrictEqual(w0, w1); - sinon.spy(w0, 'close') - let resolveUnload0 - w0.prepareToUnload = () => new Promise(resolve => { resolveUnload0 = resolve }) + sinon.spy(w0, 'close'); + let resolveUnload0; + w0.prepareToUnload = () => + new Promise(resolve => { + resolveUnload0 = resolve; + }); - sinon.spy(w1, 'close') - let resolveUnload1 - w1.prepareToUnload = () => new Promise(resolve => { resolveUnload1 = resolve }) + sinon.spy(w1, 'close'); + let resolveUnload1; + w1.prepareToUnload = () => + new Promise(resolve => { + resolveUnload1 = resolve; + }); - const evt = {preventDefault: sinon.spy()} - electron.app.emit('before-quit', evt) - await new Promise(process.nextTick) - assert.isTrue(evt.preventDefault.called) - assert.isFalse(electron.app.quit.called) + const evt = { preventDefault: sinon.spy() }; + electron.app.emit('before-quit', evt); + await new Promise(process.nextTick); + assert.isTrue(evt.preventDefault.called); + assert.isFalse(electron.app.quit.called); - resolveUnload1(true) - await new Promise(process.nextTick) - assert.isFalse(electron.app.quit.called) + resolveUnload1(true); + await new Promise(process.nextTick); + assert.isFalse(electron.app.quit.called); - resolveUnload0(true) - await scenario.getApplication(0).lastBeforeQuitPromise - assert.isTrue(electron.app.quit.called) + resolveUnload0(true); + await scenario.getApplication(0).lastBeforeQuitPromise; + assert.isTrue(electron.app.quit.called); - assert.isTrue(w0.close.called) - assert.isTrue(w1.close.called) - }) + assert.isTrue(w0.close.called); + assert.isTrue(w1.close.called); + }); - it('prevents a quit if a user cancels when prompted to save', async function () { - const [w] = await scenario.launch(parseCommandLine(['a'])) - let resolveUnload - w.prepareToUnload = () => new Promise(resolve => { resolveUnload = resolve }) + it('prevents a quit if a user cancels when prompted to save', async function() { + const [w] = await scenario.launch(parseCommandLine(['a'])); + let resolveUnload; + w.prepareToUnload = () => + new Promise(resolve => { + resolveUnload = resolve; + }); - const evt = {preventDefault: sinon.spy()} - electron.app.emit('before-quit', evt) - await new Promise(process.nextTick) - assert.isTrue(evt.preventDefault.called) + const evt = { preventDefault: sinon.spy() }; + electron.app.emit('before-quit', evt); + await new Promise(process.nextTick); + assert.isTrue(evt.preventDefault.called); - resolveUnload(false) - await scenario.getApplication(0).lastBeforeQuitPromise + resolveUnload(false); + await scenario.getApplication(0).lastBeforeQuitPromise; - assert.isFalse(electron.app.quit.called) - }) + assert.isFalse(electron.app.quit.called); + }); - it('closes successfully unloaded windows', async function () { - const [w0] = await scenario.launch(parseCommandLine(['a'])) - const w1 = await scenario.open(parseCommandLine(['b'])) + it('closes successfully unloaded windows', async function() { + const [w0] = await scenario.launch(parseCommandLine(['a'])); + const w1 = await scenario.open(parseCommandLine(['b'])); - sinon.spy(w0, 'close') - let resolveUnload0 - w0.prepareToUnload = () => new Promise(resolve => { resolveUnload0 = resolve }) + sinon.spy(w0, 'close'); + let resolveUnload0; + w0.prepareToUnload = () => + new Promise(resolve => { + resolveUnload0 = resolve; + }); - sinon.spy(w1, 'close') - let resolveUnload1 - w1.prepareToUnload = () => new Promise(resolve => { resolveUnload1 = resolve }) + sinon.spy(w1, 'close'); + let resolveUnload1; + w1.prepareToUnload = () => + new Promise(resolve => { + resolveUnload1 = resolve; + }); - const evt = {preventDefault () {}} - electron.app.emit('before-quit', evt) + const evt = { preventDefault() {} }; + electron.app.emit('before-quit', evt); - resolveUnload0(false) - resolveUnload1(true) + resolveUnload0(false); + resolveUnload1(true); - await scenario.getApplication(0).lastBeforeQuitPromise + await scenario.getApplication(0).lastBeforeQuitPromise; - assert.isFalse(electron.app.quit.called) - assert.isFalse(w0.close.called) - assert.isTrue(w1.close.called) - }) - }) -}) + assert.isFalse(electron.app.quit.called); + assert.isFalse(w0.close.called); + assert.isTrue(w1.close.called); + }); + }); +}); class StubWindow extends EventEmitter { - constructor (sinon, loadSettings, options) { - super() + constructor(sinon, loadSettings, options) { + super(); - this.loadSettings = loadSettings + this.loadSettings = loadSettings; - this._dimensions = Object.assign({}, loadSettings.windowDimensions) || {x: 100, y: 100} - this._position = {x: 0, y: 0} - this._locations = [] - this._rootPaths = new Set() - this._editorPaths = new Set() + this._dimensions = Object.assign({}, loadSettings.windowDimensions) || { + x: 100, + y: 100 + }; + this._position = { x: 0, y: 0 }; + this._locations = []; + this._rootPaths = new Set(); + this._editorPaths = new Set(); - let resolveClosePromise - this.closedPromise = new Promise(resolve => { resolveClosePromise = resolve }) + let resolveClosePromise; + this.closedPromise = new Promise(resolve => { + resolveClosePromise = resolve; + }); - this.minimize = sinon.spy() - this.maximize = sinon.spy() - this.center = sinon.spy() - this.focus = sinon.spy() - this.show = sinon.spy() - this.hide = sinon.spy() - this.prepareToUnload = sinon.spy() - this.close = resolveClosePromise + this.minimize = sinon.spy(); + this.maximize = sinon.spy(); + this.center = sinon.spy(); + this.focus = sinon.spy(); + this.show = sinon.spy(); + this.hide = sinon.spy(); + this.prepareToUnload = sinon.spy(); + this.close = resolveClosePromise; - this.replaceEnvironment = sinon.spy() - this.disableZoom = sinon.spy() + this.replaceEnvironment = sinon.spy(); + this.disableZoom = sinon.spy(); - this.isFocused = sinon.stub().returns(options.isFocused !== undefined ? options.isFocused : false) - this.isMinimized = sinon.stub().returns(options.isMinimized !== undefined ? options.isMinimized : false) - this.isMaximized = sinon.stub().returns(options.isMaximized !== undefined ? options.isMaximized : false) + this.isFocused = sinon + .stub() + .returns(options.isFocused !== undefined ? options.isFocused : false); + this.isMinimized = sinon + .stub() + .returns(options.isMinimized !== undefined ? options.isMinimized : false); + this.isMaximized = sinon + .stub() + .returns(options.isMaximized !== undefined ? options.isMaximized : false); - this.sendURIMessage = sinon.spy() - this.didChangeUserSettings = sinon.spy() - this.didFailToReadUserSettings = sinon.spy() + this.sendURIMessage = sinon.spy(); + this.didChangeUserSettings = sinon.spy(); + this.didFailToReadUserSettings = sinon.spy(); - this.isSpec = loadSettings.isSpec !== undefined ? loadSettings.isSpec : false - this.devMode = loadSettings.devMode !== undefined ? loadSettings.devMode : false - this.safeMode = loadSettings.safeMode !== undefined ? loadSettings.safeMode : false + this.isSpec = + loadSettings.isSpec !== undefined ? loadSettings.isSpec : false; + this.devMode = + loadSettings.devMode !== undefined ? loadSettings.devMode : false; + this.safeMode = + loadSettings.safeMode !== undefined ? loadSettings.safeMode : false; - this.browserWindow = new EventEmitter() - this.browserWindow.webContents = new EventEmitter() + this.browserWindow = new EventEmitter(); + this.browserWindow.webContents = new EventEmitter(); - const locationsToOpen = this.loadSettings.locationsToOpen || [] - if (!(locationsToOpen.length === 1 && locationsToOpen[0].pathToOpen == null) && !this.isSpec) { - this.openLocations(locationsToOpen) + const locationsToOpen = this.loadSettings.locationsToOpen || []; + if ( + !( + locationsToOpen.length === 1 && locationsToOpen[0].pathToOpen == null + ) && + !this.isSpec + ) { + this.openLocations(locationsToOpen); } } - openPath (pathToOpen, initialLine, initialColumn) { - return this.openLocations([{pathToOpen, initialLine, initialColumn}]) + openPath(pathToOpen, initialLine, initialColumn) { + return this.openLocations([{ pathToOpen, initialLine, initialColumn }]); } - openLocations (locations) { - this._locations.push(...locations) + openLocations(locations) { + this._locations.push(...locations); for (const location of locations) { if (location.pathToOpen) { if (location.isDirectory) { - this._rootPaths.add(location.pathToOpen) + this._rootPaths.add(location.pathToOpen); } else if (location.isFile) { - this._editorPaths.add(location.pathToOpen) + this._editorPaths.add(location.pathToOpen); } } } - this.projectRoots = Array.from(this._rootPaths) - this.projectRoots.sort() + this.projectRoots = Array.from(this._rootPaths); + this.projectRoots.sort(); - this.emit('window:locations-opened') + this.emit('window:locations-opened'); } - setSize (x, y) { - this._dimensions = {x, y} + setSize(x, y) { + this._dimensions = { x, y }; } - setPosition (x, y) { - this._position = {x, y} + setPosition(x, y) { + this._position = { x, y }; } - isSpecWindow () { - return this.isSpec + isSpecWindow() { + return this.isSpec; } - hasProjectPaths () { - return this._rootPaths.size > 0 + hasProjectPaths() { + return this._rootPaths.size > 0; } - containsLocations (locations) { - return locations.every(location => this.containsLocation(location)) + containsLocations(locations) { + return locations.every(location => this.containsLocation(location)); } - containsLocation (location) { - if (!location.pathToOpen) return false + containsLocation(location) { + if (!location.pathToOpen) return false; return Array.from(this._rootPaths).some(projectPath => { - if (location.pathToOpen === projectPath) return true + if (location.pathToOpen === projectPath) return true; if (location.pathToOpen.startsWith(path.join(projectPath, path.sep))) { - if (!location.exists) return true - if (!location.isDirectory) return true + if (!location.exists) return true; + if (!location.isDirectory) return true; } - return false - }) + return false; + }); } - getDimensions () { - return Object.assign({}, this._dimensions) + getDimensions() { + return Object.assign({}, this._dimensions); } } class LaunchScenario { - static async create (sandbox) { - const scenario = new this(sandbox) - await scenario.init() - return scenario + static async create(sandbox) { + const scenario = new this(sandbox); + await scenario.init(); + return scenario; } - constructor (sandbox) { - this.sinon = sandbox + constructor(sandbox) { + this.sinon = sandbox; - this.applications = new Set() - this.windows = new Set() - this.root = null - this.atomHome = null - this.projectRootPool = new Map() - this.filePathPool = new Map() + this.applications = new Set(); + this.windows = new Set(); + this.root = null; + this.atomHome = null; + this.projectRootPool = new Map(); + this.filePathPool = new Map(); - this.killedPids = [] - this.originalAtomHome = null + this.killedPids = []; + this.originalAtomHome = null; } - async init () { + async init() { if (this.root !== null) { - return this.root + return this.root; } this.root = await new Promise((resolve, reject) => { temp.mkdir('launch-', (err, rootPath) => { - if (err) { reject(err) } else { resolve(rootPath) } - }) - }) + if (err) { + reject(err); + } else { + resolve(rootPath); + } + }); + }); - this.atomHome = path.join(this.root, '.atom') + this.atomHome = path.join(this.root, '.atom'); await new Promise((resolve, reject) => { fs.makeTree(this.atomHome, err => { - if (err) { reject(err) } else { resolve() } - }) - }) - this.originalAtomHome = process.env.ATOM_HOME - process.env.ATOM_HOME = this.atomHome + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + this.originalAtomHome = process.env.ATOM_HOME; + process.env.ATOM_HOME = this.atomHome; await Promise.all( - ['a', 'b', 'c', 'd'].map(dirPath => new Promise((resolve, reject) => { - const fullDirPath = path.join(this.root, dirPath) - fs.makeTree(fullDirPath, err => { - if (err) { - reject(err) - } else { - this.projectRootPool.set(dirPath, fullDirPath) - resolve() - } - }) - })) - ) + ['a', 'b', 'c', 'd'].map( + dirPath => + new Promise((resolve, reject) => { + const fullDirPath = path.join(this.root, dirPath); + fs.makeTree(fullDirPath, err => { + if (err) { + reject(err); + } else { + this.projectRootPool.set(dirPath, fullDirPath); + resolve(); + } + }); + }) + ) + ); await Promise.all( - ['a/1.md', 'b/2.md'].map(filePath => new Promise((resolve, reject) => { - const fullFilePath = path.join(this.root, filePath) - fs.writeFile(fullFilePath, `file: ${filePath}\n`, {encoding: 'utf8'}, err => { - if (err) { - reject(err) - } else { - this.filePathPool.set(filePath, fullFilePath) - this.filePathPool.set(path.basename(filePath), fullFilePath) - resolve() - } - }) - })) - ) + ['a/1.md', 'b/2.md'].map( + filePath => + new Promise((resolve, reject) => { + const fullFilePath = path.join(this.root, filePath); + fs.writeFile( + fullFilePath, + `file: ${filePath}\n`, + { encoding: 'utf8' }, + err => { + if (err) { + reject(err); + } else { + this.filePathPool.set(filePath, fullFilePath); + this.filePathPool.set(path.basename(filePath), fullFilePath); + resolve(); + } + } + ); + }) + ) + ); - this.sinon.stub(electron.app, 'quit') + this.sinon.stub(electron.app, 'quit'); } - async preconditions (source) { - const app = this.addApplication() - const windowPromises = [] + async preconditions(source) { + const app = this.addApplication(); + const windowPromises = []; for (const windowSpec of this.parseWindowSpecs(source)) { if (windowSpec.editors.length === 0) { - windowSpec.editors.push(null) + windowSpec.editors.push(null); } - windowPromises.push(((theApp, foldersToOpen, pathsToOpen) => { - return theApp.openPaths({ newWindow: true, foldersToOpen, pathsToOpen }) - })(app, windowSpec.roots, windowSpec.editors)) + windowPromises.push( + ((theApp, foldersToOpen, pathsToOpen) => { + return theApp.openPaths({ + newWindow: true, + foldersToOpen, + pathsToOpen + }); + })(app, windowSpec.roots, windowSpec.editors) + ); } - await Promise.all(windowPromises) + await Promise.all(windowPromises); } - launch (options) { - const app = options.app || this.addApplication() - delete options.app + launch(options) { + const app = options.app || this.addApplication(); + delete options.app; if (options.pathsToOpen) { - options.pathsToOpen = this.convertPaths(options.pathsToOpen) + options.pathsToOpen = this.convertPaths(options.pathsToOpen); } - return app.launch(options) + return app.launch(options); } - open (options) { + open(options) { if (this.applications.size === 0) { - return this.launch(options) + return this.launch(options); } - let app = options.app + let app = options.app; if (!app) { - const apps = Array.from(this.applications) - app = apps[apps.length - 1] + const apps = Array.from(this.applications); + app = apps[apps.length - 1]; } else { - delete options.app + delete options.app; } if (options.pathsToOpen) { - options.pathsToOpen = this.convertPaths(options.pathsToOpen) + options.pathsToOpen = this.convertPaths(options.pathsToOpen); } - options.preserveFocus = true + options.preserveFocus = true; - return app.openWithOptions(options) + return app.openWithOptions(options); } - async assert (source) { - const windowSpecs = this.parseWindowSpecs(source) - let specIndex = 0 + async assert(source) { + const windowSpecs = this.parseWindowSpecs(source); + let specIndex = 0; - const windowPromises = [] + const windowPromises = []; for (const window of this.windows) { - windowPromises.push((async (theWindow, theSpec) => { - const {_rootPaths: rootPaths, _editorPaths: editorPaths} = theWindow + windowPromises.push( + (async (theWindow, theSpec) => { + const { + _rootPaths: rootPaths, + _editorPaths: editorPaths + } = theWindow; - const comparison = { - ok: true, - extraWindow: false, - missingWindow: false, - extraRoots: [], - missingRoots: [], - extraEditors: [], - missingEditors: [], - roots: rootPaths, - editors: editorPaths - } + const comparison = { + ok: true, + extraWindow: false, + missingWindow: false, + extraRoots: [], + missingRoots: [], + extraEditors: [], + missingEditors: [], + roots: rootPaths, + editors: editorPaths + }; - if (!theSpec) { - comparison.ok = false - comparison.extraWindow = true - comparison.extraRoots = rootPaths - comparison.extraEditors = editorPaths - } else { - const [missingRoots, extraRoots] = this.compareSets(theSpec.roots, rootPaths) - const [missingEditors, extraEditors] = this.compareSets(theSpec.editors, editorPaths) + if (!theSpec) { + comparison.ok = false; + comparison.extraWindow = true; + comparison.extraRoots = rootPaths; + comparison.extraEditors = editorPaths; + } else { + const [missingRoots, extraRoots] = this.compareSets( + theSpec.roots, + rootPaths + ); + const [missingEditors, extraEditors] = this.compareSets( + theSpec.editors, + editorPaths + ); - comparison.ok = missingRoots.length === 0 && - extraRoots.length === 0 && - missingEditors.length === 0 && - extraEditors.length === 0 - comparison.extraRoots = extraRoots - comparison.missingRoots = missingRoots - comparison.extraEditors = extraEditors - comparison.missingEditors = missingEditors - } + comparison.ok = + missingRoots.length === 0 && + extraRoots.length === 0 && + missingEditors.length === 0 && + extraEditors.length === 0; + comparison.extraRoots = extraRoots; + comparison.missingRoots = missingRoots; + comparison.extraEditors = extraEditors; + comparison.missingEditors = missingEditors; + } - return comparison - })(window, windowSpecs[specIndex++])) + return comparison; + })(window, windowSpecs[specIndex++]) + ); } - const comparisons = await Promise.all(windowPromises) + const comparisons = await Promise.all(windowPromises); for (; specIndex < windowSpecs.length; specIndex++) { - const spec = windowSpecs[specIndex] + const spec = windowSpecs[specIndex]; comparisons.push({ ok: false, extraWindow: false, @@ -1319,170 +1550,195 @@ class LaunchScenario { missingEditors: spec.editors, roots: null, editors: null - }) + }); } - const shorthandParts = [] - const descriptionParts = [] + const shorthandParts = []; + const descriptionParts = []; for (const comparison of comparisons) { if (comparison.roots !== null && comparison.editors !== null) { - const shortRoots = Array.from(comparison.roots, r => path.basename(r)).join(',') - const shortPaths = Array.from(comparison.editors, e => path.basename(e)).join(',') - shorthandParts.push(`[${shortRoots} ${shortPaths}]`) + const shortRoots = Array.from(comparison.roots, r => + path.basename(r) + ).join(','); + const shortPaths = Array.from(comparison.editors, e => + path.basename(e) + ).join(','); + shorthandParts.push(`[${shortRoots} ${shortPaths}]`); } if (comparison.ok) { - continue + continue; } - let parts = [] + let parts = []; if (comparison.extraWindow) { - parts.push('extra window\n') + parts.push('extra window\n'); } else if (comparison.missingWindow) { - parts.push('missing window\n') + parts.push('missing window\n'); } else { - parts.push('incorrect window\n') + parts.push('incorrect window\n'); } - const shorten = fullPaths => fullPaths.map(fullPath => path.basename(fullPath)).join(', ') + const shorten = fullPaths => + fullPaths.map(fullPath => path.basename(fullPath)).join(', '); if (comparison.extraRoots.length > 0) { - parts.push(`* extra roots ${shorten(comparison.extraRoots)}\n`) + parts.push(`* extra roots ${shorten(comparison.extraRoots)}\n`); } if (comparison.missingRoots.length > 0) { - parts.push(`* missing roots ${shorten(comparison.missingRoots)}\n`) + parts.push(`* missing roots ${shorten(comparison.missingRoots)}\n`); } if (comparison.extraEditors.length > 0) { - parts.push(`* extra editors ${shorten(comparison.extraEditors)}\n`) + parts.push(`* extra editors ${shorten(comparison.extraEditors)}\n`); } if (comparison.missingEditors.length > 0) { - parts.push(`* missing editors ${shorten(comparison.missingEditors)}\n`) + parts.push(`* missing editors ${shorten(comparison.missingEditors)}\n`); } - descriptionParts.push(parts.join('')) + descriptionParts.push(parts.join('')); } if (descriptionParts.length !== 0) { - descriptionParts.unshift(shorthandParts.join(' ') + '\n') - descriptionParts.unshift('Launched windows did not match spec\n') + descriptionParts.unshift(shorthandParts.join(' ') + '\n'); + descriptionParts.unshift('Launched windows did not match spec\n'); } - assert.isTrue(descriptionParts.length === 0, descriptionParts.join('')) + assert.isTrue(descriptionParts.length === 0, descriptionParts.join('')); } - async destroy () { - await Promise.all( - Array.from(this.applications, app => app.destroy()) - ) + async destroy() { + await Promise.all(Array.from(this.applications, app => app.destroy())); if (this.originalAtomHome) { - process.env.ATOM_HOME = this.originalAtomHome + process.env.ATOM_HOME = this.originalAtomHome; } } - addApplication (options = {}) { + addApplication(options = {}) { const app = new AtomApplication({ resourcePath: path.resolve(__dirname, '../..'), atomHomeDirPath: this.atomHome, preserveFocus: true, - killProcess: pid => { this.killedPids.push(pid) }, + killProcess: pid => { + this.killedPids.push(pid); + }, ...options - }) + }); this.sinon.stub(app, 'createWindow', loadSettings => { - const newWindow = new StubWindow(this.sinon, loadSettings, options) - this.windows.add(newWindow) - return newWindow - }) - this.sinon.stub(app.storageFolder, 'load', () => Promise.resolve( - options.applicationJson || {version: '1', windows: []} - )) - this.sinon.stub(app.storageFolder, 'store', () => Promise.resolve()) - this.applications.add(app) - return app + const newWindow = new StubWindow(this.sinon, loadSettings, options); + this.windows.add(newWindow); + return newWindow; + }); + this.sinon.stub(app.storageFolder, 'load', () => + Promise.resolve(options.applicationJson || { version: '1', windows: [] }) + ); + this.sinon.stub(app.storageFolder, 'store', () => Promise.resolve()); + this.applications.add(app); + return app; } - getApplication (index) { - const app = Array.from(this.applications)[index] + getApplication(index) { + const app = Array.from(this.applications)[index]; if (!app) { - throw new Error(`Application ${index} does not exist`) + throw new Error(`Application ${index} does not exist`); } - return app + return app; } - getWindow (index) { - const window = Array.from(this.windows)[index] + getWindow(index) { + const window = Array.from(this.windows)[index]; if (!window) { - throw new Error(`Window ${index} does not exist`) + throw new Error(`Window ${index} does not exist`); } - return window + return window; } - compareSets (expected, actual) { - const expectedItems = new Set(expected) - const extra = [] - const missing = [] + compareSets(expected, actual) { + const expectedItems = new Set(expected); + const extra = []; + const missing = []; for (const actualItem of actual) { if (!expectedItems.delete(actualItem)) { // actualItem was present, but not expected - extra.push(actualItem) + extra.push(actualItem); } } for (const remainingItem of expectedItems) { // remainingItem was expected, but not present - missing.push(remainingItem) + missing.push(remainingItem); } - return [missing, extra] + return [missing, extra]; } - convertRootPath (shortRootPath) { - if (shortRootPath.startsWith('atom://') || shortRootPath.startsWith('remote://')) { return shortRootPath } + convertRootPath(shortRootPath) { + if ( + shortRootPath.startsWith('atom://') || + shortRootPath.startsWith('remote://') + ) { + return shortRootPath; + } - const fullRootPath = this.projectRootPool.get(shortRootPath) + const fullRootPath = this.projectRootPool.get(shortRootPath); if (!fullRootPath) { - throw new Error(`Unexpected short project root path: ${shortRootPath}`) + throw new Error(`Unexpected short project root path: ${shortRootPath}`); } - return fullRootPath + return fullRootPath; } - convertEditorPath (shortEditorPath) { - const [truncatedPath, ...suffix] = shortEditorPath.split(/(?=:)/) - const fullEditorPath = this.filePathPool.get(truncatedPath) + convertEditorPath(shortEditorPath) { + const [truncatedPath, ...suffix] = shortEditorPath.split(/(?=:)/); + const fullEditorPath = this.filePathPool.get(truncatedPath); if (!fullEditorPath) { - throw new Error(`Unexpected short editor path: ${shortEditorPath}`) + throw new Error(`Unexpected short editor path: ${shortEditorPath}`); } - return fullEditorPath + suffix.join('') + return fullEditorPath + suffix.join(''); } - convertPaths (paths) { + convertPaths(paths) { return paths.map(shortPath => { - if (shortPath.startsWith('atom://') || shortPath.startsWith('remote://')) { return shortPath } + if ( + shortPath.startsWith('atom://') || + shortPath.startsWith('remote://') + ) { + return shortPath; + } - const fullRoot = this.projectRootPool.get(shortPath) - if (fullRoot) { return fullRoot } + const fullRoot = this.projectRootPool.get(shortPath); + if (fullRoot) { + return fullRoot; + } - const [truncatedPath, ...suffix] = shortPath.split(/(?=:)/) - const fullEditor = this.filePathPool.get(truncatedPath) - if (fullEditor) { return fullEditor + suffix.join('') } + const [truncatedPath, ...suffix] = shortPath.split(/(?=:)/); + const fullEditor = this.filePathPool.get(truncatedPath); + if (fullEditor) { + return fullEditor + suffix.join(''); + } - throw new Error(`Unexpected short path: ${shortPath}`) - }) + throw new Error(`Unexpected short path: ${shortPath}`); + }); } - parseWindowSpecs (source) { - const specs = [] + parseWindowSpecs(source) { + const specs = []; - const rx = /\s*\[(?:_|(\S+)) (?:_|(\S+))\]/g - let match = rx.exec(source) + const rx = /\s*\[(?:_|(\S+)) (?:_|(\S+))\]/g; + let match = rx.exec(source); while (match) { - const roots = match[1] ? match[1].split(',').map(shortPath => this.convertRootPath(shortPath)) : [] - const editors = match[2] ? match[2].split(',').map(shortPath => this.convertEditorPath(shortPath)) : [] - specs.push({ roots, editors }) + const roots = match[1] + ? match[1].split(',').map(shortPath => this.convertRootPath(shortPath)) + : []; + const editors = match[2] + ? match[2] + .split(',') + .map(shortPath => this.convertEditorPath(shortPath)) + : []; + specs.push({ roots, editors }); - match = rx.exec(source) + match = rx.exec(source); } - return specs + return specs; } } diff --git a/spec/main-process/atom-window.test.js b/spec/main-process/atom-window.test.js index 72f79f9d4..2f38624b8 100644 --- a/spec/main-process/atom-window.test.js +++ b/spec/main-process/atom-window.test.js @@ -1,52 +1,57 @@ /* globals assert */ -const path = require('path') -const fs = require('fs-plus') -const url = require('url') -const {EventEmitter} = require('events') -const temp = require('temp').track() -const {sandbox} = require('sinon') -const dedent = require('dedent') +const path = require('path'); +const fs = require('fs-plus'); +const url = require('url'); +const { EventEmitter } = require('events'); +const temp = require('temp').track(); +const { sandbox } = require('sinon'); +const dedent = require('dedent'); -const AtomWindow = require('../../src/main-process/atom-window') -const {emitterEventPromise} = require('../async-spec-helpers') +const AtomWindow = require('../../src/main-process/atom-window'); +const { emitterEventPromise } = require('../async-spec-helpers'); -describe('AtomWindow', function () { - let sinon, app, service +describe('AtomWindow', function() { + let sinon, app, service; - beforeEach(function () { - sinon = sandbox.create() - app = new StubApplication(sinon) - service = new StubRecoveryService(sinon) - }) + beforeEach(function() { + sinon = sandbox.create(); + app = new StubApplication(sinon); + service = new StubRecoveryService(sinon); + }); - afterEach(function () { - sinon.restore() - }) + afterEach(function() { + sinon.restore(); + }); - describe('creating a real window', function () { - let resourcePath, windowInitializationScript, atomHome - let original + describe('creating a real window', function() { + let resourcePath, windowInitializationScript, atomHome; + let original; - this.timeout(10 * 1000) + this.timeout(10 * 1000); - beforeEach(async function () { + beforeEach(async function() { original = { ATOM_HOME: process.env.ATOM_HOME, - ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT - } + ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: + process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT + }; - resourcePath = path.resolve(__dirname, '../..') + resourcePath = path.resolve(__dirname, '../..'); windowInitializationScript = require.resolve( path.join(resourcePath, 'src/initialize-application-window') - ) + ); atomHome = await new Promise((resolve, reject) => { temp.mkdir('launch-', (err, rootPath) => { - if (err) { reject(err) } else { resolve(rootPath) } - }) - }) + if (err) { + reject(err); + } else { + resolve(rootPath); + } + }); + }); await new Promise((resolve, reject) => { const config = dedent` @@ -56,122 +61,141 @@ describe('AtomWindow', function () { telemetryConsent: "no" welcome: showOnStartup: false - ` + `; - fs.writeFile(path.join(atomHome, 'config.cson'), config, {encoding: 'utf8'}, err => { - if (err) { reject(err) } else { resolve() } - }) - }) + fs.writeFile( + path.join(atomHome, 'config.cson'), + config, + { encoding: 'utf8' }, + err => { + if (err) { + reject(err); + } else { + resolve(); + } + } + ); + }); await new Promise((resolve, reject) => { fs.symlink( path.join(original.ATOM_HOME, 'compile-cache'), path.join(atomHome, 'compile-cache'), 'junction', - err => { if (err) { reject(err) } else { resolve() } } - ) - }) + err => { + if (err) { + reject(err); + } else { + resolve(); + } + } + ); + }); - process.env.ATOM_HOME = atomHome - process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = 'true' - }) + process.env.ATOM_HOME = atomHome; + process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = 'true'; + }); - afterEach(async function () { - process.env.ATOM_HOME = original.ATOM_HOME - process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = original.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT - }) + afterEach(async function() { + process.env.ATOM_HOME = original.ATOM_HOME; + process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = + original.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT; + }); - it('creates a real, properly configured BrowserWindow', async function () { + it('creates a real, properly configured BrowserWindow', async function() { const w = new AtomWindow(app, service, { resourcePath, windowInitializationScript, headless: true, extra: 'extra-load-setting' - }) - const {browserWindow} = w + }); + const { browserWindow } = w; - assert.isFalse(browserWindow.isVisible()) - assert.strictEqual(browserWindow.getTitle(), 'Atom') + assert.isFalse(browserWindow.isVisible()); + assert.strictEqual(browserWindow.getTitle(), 'Atom'); - const settings = JSON.parse(browserWindow.loadSettingsJSON) - assert.strictEqual(settings.userSettings, 'stub-config') - assert.strictEqual(settings.extra, 'extra-load-setting') - assert.strictEqual(settings.resourcePath, resourcePath) - assert.strictEqual(settings.atomHome, atomHome) - assert.isFalse(settings.devMode) - assert.isFalse(settings.safeMode) - assert.isFalse(settings.clearWindowState) + const settings = JSON.parse(browserWindow.loadSettingsJSON); + assert.strictEqual(settings.userSettings, 'stub-config'); + assert.strictEqual(settings.extra, 'extra-load-setting'); + assert.strictEqual(settings.resourcePath, resourcePath); + assert.strictEqual(settings.atomHome, atomHome); + assert.isFalse(settings.devMode); + assert.isFalse(settings.safeMode); + assert.isFalse(settings.clearWindowState); - await emitterEventPromise(browserWindow, 'ready-to-show') + await emitterEventPromise(browserWindow, 'ready-to-show'); - assert.strictEqual(browserWindow.webContents.getURL(), url.format({ - protocol: 'file', - pathname: `${resourcePath.replace(/\\/g, '/')}/static/index.html`, - slashes: true - })) - }) - }) + assert.strictEqual( + browserWindow.webContents.getURL(), + url.format({ + protocol: 'file', + pathname: `${resourcePath.replace(/\\/g, '/')}/static/index.html`, + slashes: true + }) + ); + }); + }); - describe('launch behavior', function () { + describe('launch behavior', function() { if (process.platform === 'darwin') { - it('sets titleBarStyle to "hidden" for a custom title bar on non-spec windows', function () { - app.config['core.titleBar'] = 'custom' + it('sets titleBarStyle to "hidden" for a custom title bar on non-spec windows', function() { + app.config['core.titleBar'] = 'custom'; - const {browserWindow: w0} = new AtomWindow(app, service, { + const { browserWindow: w0 } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow - }) - assert.strictEqual(w0.options.titleBarStyle, 'hidden') + }); + assert.strictEqual(w0.options.titleBarStyle, 'hidden'); - const {browserWindow: w1} = new AtomWindow(app, service, { + const { browserWindow: w1 } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true - }) - assert.isUndefined(w1.options.titleBarStyle) - }) + }); + assert.isUndefined(w1.options.titleBarStyle); + }); - it('sets titleBarStyle to "hiddenInset" for a custom inset title bar on non-spec windows', function () { - app.config['core.titleBar'] = 'custom-inset' + it('sets titleBarStyle to "hiddenInset" for a custom inset title bar on non-spec windows', function() { + app.config['core.titleBar'] = 'custom-inset'; - const {browserWindow: w0} = new AtomWindow(app, service, { + const { browserWindow: w0 } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow - }) - assert.strictEqual(w0.options.titleBarStyle, 'hiddenInset') + }); + assert.strictEqual(w0.options.titleBarStyle, 'hiddenInset'); - const {browserWindow: w1} = new AtomWindow(app, service, { + const { browserWindow: w1 } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true - }) - assert.isUndefined(w1.options.titleBarStyle) - }) + }); + assert.isUndefined(w1.options.titleBarStyle); + }); - it('sets frame to "false" for a hidden title bar on non-spec windows', function () { - app.config['core.titleBar'] = 'hidden' + it('sets frame to "false" for a hidden title bar on non-spec windows', function() { + app.config['core.titleBar'] = 'hidden'; - const {browserWindow: w0} = new AtomWindow(app, service, { + const { browserWindow: w0 } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow - }) - assert.isFalse(w0.options.frame) + }); + assert.isFalse(w0.options.frame); - const {browserWindow: w1} = new AtomWindow(app, service, { + const { browserWindow: w1 } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true - }) - assert.isUndefined(w1.options.frame) - }) + }); + assert.isUndefined(w1.options.frame); + }); } else { - it('ignores title bar style settings', function () { + it('ignores title bar style settings', function() { for (const value of ['custom', 'custom-inset', 'hidden']) { - app.config['core.titleBar'] = value - const {browserWindow} = new AtomWindow(app, service, { + app.config['core.titleBar'] = value; + const { browserWindow } = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow - }) - assert.isUndefined(browserWindow.options.titleBarStyle) - assert.isUndefined(browserWindow.options.frame) + }); + assert.isUndefined(browserWindow.options.titleBarStyle); + assert.isUndefined(browserWindow.options.frame); } - }) + }); } - it('opens initial locations', async function () { + it('opens initial locations', async function() { const locationsToOpen = [ { pathToOpen: 'file.txt', @@ -187,238 +211,326 @@ describe('AtomWindow', function () { isDirectory: true, hasWaitSession: false } - ] + ]; - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) - assert.deepEqual(w.projectRoots, ['/directory']) + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); + assert.deepEqual(w.projectRoots, ['/directory']); - const loadPromise = emitterEventPromise(w, 'window:loaded') - w.browserWindow.emit('window:loaded') - await loadPromise + const loadPromise = emitterEventPromise(w, 'window:loaded'); + w.browserWindow.emit('window:loaded'); + await loadPromise; - assert.deepEqual(w.browserWindow.sent, [['message', 'open-locations', locationsToOpen]]) - }) + assert.deepEqual(w.browserWindow.sent, [ + ['message', 'open-locations', locationsToOpen] + ]); + }); - it('does not open an initial null location', async function () { + it('does not open an initial null location', async function() { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen: [{ pathToOpen: null }] - }) + }); - const loadPromise = emitterEventPromise(w, 'window:loaded') - w.browserWindow.emit('window:loaded') - await loadPromise + const loadPromise = emitterEventPromise(w, 'window:loaded'); + w.browserWindow.emit('window:loaded'); + await loadPromise; - assert.lengthOf(w.browserWindow.sent, 0) - }) + assert.lengthOf(w.browserWindow.sent, 0); + }); - it('does not open initial locations in spec mode', async function () { + it('does not open initial locations in spec mode', async function() { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen: [{ pathToOpen: 'file.txt' }], isSpec: true - }) + }); - const loadPromise = emitterEventPromise(w, 'window:loaded') - w.browserWindow.emit('window:loaded') - await loadPromise + const loadPromise = emitterEventPromise(w, 'window:loaded'); + w.browserWindow.emit('window:loaded'); + await loadPromise; - assert.lengthOf(w.browserWindow.sent, 0) - }) + assert.lengthOf(w.browserWindow.sent, 0); + }); - it('focuses the webView for specs', function () { + it('focuses the webView for specs', function() { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true - }) + }); - assert.isTrue(w.browserWindow.behavior.focusOnWebView) - }) - }) + assert.isTrue(w.browserWindow.behavior.focusOnWebView); + }); + }); - describe('project root tracking', function () { - it('knows when it has no roots', function () { - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) - assert.isFalse(w.hasProjectPaths()) - }) + describe('project root tracking', function() { + it('knows when it has no roots', function() { + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow + }); + assert.isFalse(w.hasProjectPaths()); + }); - it('is initialized from directories in the initial locationsToOpen', function () { + it('is initialized from directories in the initial locationsToOpen', function() { const locationsToOpen = [ { pathToOpen: 'file.txt', exists: true, isFile: true }, { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true }, { pathToOpen: 'new-file.txt' }, { pathToOpen: null } - ] + ]; - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); - assert.deepEqual(w.projectRoots, ['directory0', 'directory1']) - assert.isTrue(w.loadSettings.hasOpenFiles) - assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0', 'directory1']) - assert.isTrue(w.hasProjectPaths()) - }) + assert.deepEqual(w.projectRoots, ['directory0', 'directory1']); + assert.isTrue(w.loadSettings.hasOpenFiles); + assert.deepEqual(w.loadSettings.initialProjectRoots, [ + 'directory0', + 'directory1' + ]); + assert.isTrue(w.hasProjectPaths()); + }); - it('is updated synchronously by openLocations', async function () { + it('is updated synchronously by openLocations', async function() { const locationsToOpen = [ { pathToOpen: 'file.txt', isFile: true }, { pathToOpen: 'directory1', isDirectory: true }, { pathToOpen: 'directory0', isDirectory: true }, { pathToOpen: 'directory0', isDirectory: true }, { pathToOpen: 'new-file.txt' } - ] + ]; - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) - assert.deepEqual(w.projectRoots, []) + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow + }); + assert.deepEqual(w.projectRoots, []); - const promise = w.openLocations(locationsToOpen) - assert.deepEqual(w.projectRoots, ['directory0', 'directory1']) - w.resolveLoadedPromise() - await promise - }) + const promise = w.openLocations(locationsToOpen); + assert.deepEqual(w.projectRoots, ['directory0', 'directory1']); + w.resolveLoadedPromise(); + await promise; + }); - it('is updated by setProjectRoots', function () { + it('is updated by setProjectRoots', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true } - ] + ]; - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) - assert.deepEqual(w.projectRoots, ['directory0']) - assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0']) + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); + assert.deepEqual(w.projectRoots, ['directory0']); + assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0']); - w.setProjectRoots(['directory1', 'directory0', 'directory2']) - assert.deepEqual(w.projectRoots, ['directory0', 'directory1', 'directory2']) - assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0', 'directory1', 'directory2']) - }) + w.setProjectRoots(['directory1', 'directory0', 'directory2']); + assert.deepEqual(w.projectRoots, [ + 'directory0', + 'directory1', + 'directory2' + ]); + assert.deepEqual(w.loadSettings.initialProjectRoots, [ + 'directory0', + 'directory1', + 'directory2' + ]); + }); - it('never reports that it owns the empty path', function () { + it('never reports that it owns the empty path', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true }, { pathToOpen: null } - ] + ]; - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) - assert.isFalse(w.containsLocation({pathToOpen: null})) - }) + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); + assert.isFalse(w.containsLocation({ pathToOpen: null })); + }); - it('discovers an exact path match', function () { + it('discovers an exact path match', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } - ] - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) + ]; + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); - assert.isTrue(w.containsLocation({pathToOpen: 'directory0'})) - assert.isFalse(w.containsLocation({pathToOpen: 'directory2'})) - }) + assert.isTrue(w.containsLocation({ pathToOpen: 'directory0' })); + assert.isFalse(w.containsLocation({ pathToOpen: 'directory2' })); + }); - it('discovers the path of a file within any project root', function () { + it('discovers the path of a file within any project root', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } - ] - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) + ]; + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); - assert.isTrue(w.containsLocation({ - pathToOpen: path.join('directory0/file-0.txt'), exists: true, isFile: true - })) - assert.isTrue(w.containsLocation({ - pathToOpen: path.join('directory0/deep/file-0.txt'), exists: true, isFile: true - })) - assert.isFalse(w.containsLocation({ - pathToOpen: path.join('directory2/file-9.txt'), exists: true, isFile: true - })) - assert.isFalse(w.containsLocation({ - pathToOpen: path.join('directory2/deep/file-9.txt'), exists: true, isFile: true - })) - }) + assert.isTrue( + w.containsLocation({ + pathToOpen: path.join('directory0/file-0.txt'), + exists: true, + isFile: true + }) + ); + assert.isTrue( + w.containsLocation({ + pathToOpen: path.join('directory0/deep/file-0.txt'), + exists: true, + isFile: true + }) + ); + assert.isFalse( + w.containsLocation({ + pathToOpen: path.join('directory2/file-9.txt'), + exists: true, + isFile: true + }) + ); + assert.isFalse( + w.containsLocation({ + pathToOpen: path.join('directory2/deep/file-9.txt'), + exists: true, + isFile: true + }) + ); + }); - it('reports that it owns nonexistent paths within a project root', function () { + it('reports that it owns nonexistent paths within a project root', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } - ] - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) + ]; + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); - assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory0/file-1.txt'), exists: false })) - assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory1/subdir/file-0.txt'), exists: false })) - }) + assert.isTrue( + w.containsLocation({ + pathToOpen: path.join('directory0/file-1.txt'), + exists: false + }) + ); + assert.isTrue( + w.containsLocation({ + pathToOpen: path.join('directory1/subdir/file-0.txt'), + exists: false + }) + ); + }); - it('never reports that it owns directories within a project root', function () { + it('never reports that it owns directories within a project root', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } - ] - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) + ]; + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); - assert.isFalse(w.containsLocation({ - pathToOpen: path.join('directory0/subdir-0'), exists: true, isDirectory: true - })) - }) + assert.isFalse( + w.containsLocation({ + pathToOpen: path.join('directory0/subdir-0'), + exists: true, + isDirectory: true + }) + ); + }); - it('checks a full list of paths and reports if it owns all of them', function () { + it('checks a full list of paths and reports if it owns all of them', function() { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } - ] - const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) + ]; + const w = new AtomWindow(app, service, { + browserWindowConstructor: StubBrowserWindow, + locationsToOpen + }); - assert.isTrue(w.containsLocations([ - { pathToOpen: 'directory0' }, - { pathToOpen: path.join('directory1/file-0.txt'), exists: true, isFile: true } - ])) - assert.isFalse(w.containsLocations([ - { pathToOpen: 'directory2' }, - { pathToOpen: 'directory0' } - ])) - assert.isFalse(w.containsLocations([ - { pathToOpen: 'directory2' }, - { pathToOpen: 'directory1' } - ])) - }) - }) -}) + assert.isTrue( + w.containsLocations([ + { pathToOpen: 'directory0' }, + { + pathToOpen: path.join('directory1/file-0.txt'), + exists: true, + isFile: true + } + ]) + ); + assert.isFalse( + w.containsLocations([ + { pathToOpen: 'directory2' }, + { pathToOpen: 'directory0' } + ]) + ); + assert.isFalse( + w.containsLocations([ + { pathToOpen: 'directory2' }, + { pathToOpen: 'directory1' } + ]) + ); + }); + }); +}); class StubApplication { - constructor (sinon) { + constructor(sinon) { this.config = { 'core.titleBar': 'native', get: key => this.config[key] || null - } + }; this.configFile = { - get () { return 'stub-config' } - } + get() { + return 'stub-config'; + } + }; - this.removeWindow = sinon.spy() - this.saveCurrentWindowOptions = sinon.spy() + this.removeWindow = sinon.spy(); + this.saveCurrentWindowOptions = sinon.spy(); } } class StubRecoveryService { - constructor (sinon) { - this.didCloseWindow = sinon.spy() - this.didCrashWindow = sinon.spy() + constructor(sinon) { + this.didCloseWindow = sinon.spy(); + this.didCrashWindow = sinon.spy(); } } class StubBrowserWindow extends EventEmitter { - constructor (options) { - super() - this.options = options - this.sent = [] + constructor(options) { + super(); + this.options = options; + this.sent = []; this.behavior = { focusOnWebView: false - } + }; - this.webContents = new EventEmitter() - this.webContents.send = (...args) => { this.sent.push(args) } - this.webContents.setVisualZoomLevelLimits = () => {} + this.webContents = new EventEmitter(); + this.webContents.send = (...args) => { + this.sent.push(args); + }; + this.webContents.setVisualZoomLevelLimits = () => {}; } - loadURL () {} + loadURL() {} - focusOnWebView () { - this.behavior.focusOnWebView = true + focusOnWebView() { + this.behavior.focusOnWebView = true; } } diff --git a/spec/main-process/file-recovery-service.test.js b/spec/main-process/file-recovery-service.test.js index 400f8722c..ea27b3d4d 100644 --- a/spec/main-process/file-recovery-service.test.js +++ b/spec/main-process/file-recovery-service.test.js @@ -1,166 +1,166 @@ -const { dialog } = require('electron') -const FileRecoveryService = require('../../src/main-process/file-recovery-service') -const fs = require('fs-plus') -const fsreal = require('fs') -const EventEmitter = require('events').EventEmitter -const { assert } = require('chai') -const sinon = require('sinon') -const { escapeRegExp } = require('underscore-plus') -const temp = require('temp').track() +const { dialog } = require('electron'); +const FileRecoveryService = require('../../src/main-process/file-recovery-service'); +const fs = require('fs-plus'); +const fsreal = require('fs'); +const EventEmitter = require('events').EventEmitter; +const { assert } = require('chai'); +const sinon = require('sinon'); +const { escapeRegExp } = require('underscore-plus'); +const temp = require('temp').track(); -describe('FileRecoveryService', function () { - let recoveryService, recoveryDirectory, spies +describe('FileRecoveryService', function() { + let recoveryService, recoveryDirectory, spies; - this.timeout(10 * 1000) + this.timeout(10 * 1000); beforeEach(() => { - recoveryDirectory = temp.mkdirSync('atom-spec-file-recovery') - recoveryService = new FileRecoveryService(recoveryDirectory) - spies = sinon.sandbox.create() - }) + recoveryDirectory = temp.mkdirSync('atom-spec-file-recovery'); + recoveryService = new FileRecoveryService(recoveryDirectory); + spies = sinon.sandbox.create(); + }); afterEach(() => { - spies.restore() + spies.restore(); try { - temp.cleanupSync() + temp.cleanupSync(); } catch (e) { // Ignore } - }) + }); describe('when no crash happens during a save', () => { it('creates a recovery file and deletes it after saving', async () => { - const mockWindow = {} - const filePath = temp.path() + const mockWindow = {}; + const filePath = temp.path(); - fs.writeFileSync(filePath, 'some content') - await recoveryService.willSavePath(mockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + fs.writeFileSync(filePath, 'some content'); + await recoveryService.willSavePath(mockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1); - fs.writeFileSync(filePath, 'changed') - await recoveryService.didSavePath(mockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) - assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed') + fs.writeFileSync(filePath, 'changed'); + await recoveryService.didSavePath(mockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); + assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed'); - fs.removeSync(filePath) - }) + fs.removeSync(filePath); + }); it('creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it', async () => { - const mockWindow = {} - const anotherMockWindow = {} - const filePath = temp.path() + const mockWindow = {}; + const anotherMockWindow = {}; + const filePath = temp.path(); - fs.writeFileSync(filePath, 'some content') - await recoveryService.willSavePath(mockWindow, filePath) - await recoveryService.willSavePath(anotherMockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + fs.writeFileSync(filePath, 'some content'); + await recoveryService.willSavePath(mockWindow, filePath); + await recoveryService.willSavePath(anotherMockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1); - fs.writeFileSync(filePath, 'changed') - await recoveryService.didSavePath(mockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) - assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed') + fs.writeFileSync(filePath, 'changed'); + await recoveryService.didSavePath(mockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1); + assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed'); - await recoveryService.didSavePath(anotherMockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) - assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed') + await recoveryService.didSavePath(anotherMockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); + assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed'); - fs.removeSync(filePath) - }) - }) + fs.removeSync(filePath); + }); + }); describe('when a crash happens during a save', () => { it('restores the created recovery file and deletes it', async () => { - const mockWindow = {} - const filePath = temp.path() + const mockWindow = {}; + const filePath = temp.path(); - fs.writeFileSync(filePath, 'some content') - await recoveryService.willSavePath(mockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + fs.writeFileSync(filePath, 'some content'); + await recoveryService.willSavePath(mockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1); - fs.writeFileSync(filePath, 'changed') - await recoveryService.didCrashWindow(mockWindow) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) - assert.equal(fs.readFileSync(filePath, 'utf8'), 'some content') + fs.writeFileSync(filePath, 'changed'); + await recoveryService.didCrashWindow(mockWindow); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); + assert.equal(fs.readFileSync(filePath, 'utf8'), 'some content'); - fs.removeSync(filePath) - }) + fs.removeSync(filePath); + }); it('restores the created recovery file when many windows attempt to save the same file and one of them crashes', async () => { - const mockWindow = {} - const anotherMockWindow = {} - const filePath = temp.path() + const mockWindow = {}; + const anotherMockWindow = {}; + const filePath = temp.path(); - fs.writeFileSync(filePath, 'A') - await recoveryService.willSavePath(mockWindow, filePath) - fs.writeFileSync(filePath, 'B') - await recoveryService.willSavePath(anotherMockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + fs.writeFileSync(filePath, 'A'); + await recoveryService.willSavePath(mockWindow, filePath); + fs.writeFileSync(filePath, 'B'); + await recoveryService.willSavePath(anotherMockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1); - fs.writeFileSync(filePath, 'C') + fs.writeFileSync(filePath, 'C'); - await recoveryService.didCrashWindow(mockWindow) - assert.equal(fs.readFileSync(filePath, 'utf8'), 'A') - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + await recoveryService.didCrashWindow(mockWindow); + assert.equal(fs.readFileSync(filePath, 'utf8'), 'A'); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); - fs.writeFileSync(filePath, 'D') - await recoveryService.willSavePath(mockWindow, filePath) - fs.writeFileSync(filePath, 'E') - await recoveryService.willSavePath(anotherMockWindow, filePath) - assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + fs.writeFileSync(filePath, 'D'); + await recoveryService.willSavePath(mockWindow, filePath); + fs.writeFileSync(filePath, 'E'); + await recoveryService.willSavePath(anotherMockWindow, filePath); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1); - fs.writeFileSync(filePath, 'F') + fs.writeFileSync(filePath, 'F'); - await recoveryService.didCrashWindow(anotherMockWindow) - assert.equal(fs.readFileSync(filePath, 'utf8'), 'D') - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + await recoveryService.didCrashWindow(anotherMockWindow); + assert.equal(fs.readFileSync(filePath, 'utf8'), 'D'); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); - fs.removeSync(filePath) - }) + fs.removeSync(filePath); + }); it("emits a warning when a file can't be recovered", async () => { - const mockWindow = {} - const filePath = temp.path() - fs.writeFileSync(filePath, 'content') + const mockWindow = {}; + const filePath = temp.path(); + fs.writeFileSync(filePath, 'content'); - let logs = [] - spies.stub(console, 'log', message => logs.push(message)) - spies.stub(dialog, 'showMessageBox') + let logs = []; + spies.stub(console, 'log', message => logs.push(message)); + spies.stub(dialog, 'showMessageBox'); // Copy files to be recovered before mocking fs.createWriteStream - await recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath); // Stub out fs.createWriteStream so that we can return a fake error when // attempting to copy the recovered file to its original location - var fakeEmitter = new EventEmitter() - var onStub = spies.stub(fakeEmitter, 'on') + var fakeEmitter = new EventEmitter(); + var onStub = spies.stub(fakeEmitter, 'on'); onStub .withArgs('error') .yields(new Error('Nope')) - .returns(fakeEmitter) - onStub.withArgs('open').returns(fakeEmitter) + .returns(fakeEmitter); + onStub.withArgs('open').returns(fakeEmitter); spies .stub(fsreal, 'createWriteStream') .withArgs(filePath) - .returns(fakeEmitter) + .returns(fakeEmitter); - await recoveryService.didCrashWindow(mockWindow) - let recoveryFiles = fs.listTreeSync(recoveryDirectory) - assert.equal(recoveryFiles.length, 1) - assert.equal(logs.length, 1) - assert.match(logs[0], new RegExp(escapeRegExp(filePath))) - assert.match(logs[0], new RegExp(escapeRegExp(recoveryFiles[0]))) + await recoveryService.didCrashWindow(mockWindow); + let recoveryFiles = fs.listTreeSync(recoveryDirectory); + assert.equal(recoveryFiles.length, 1); + assert.equal(logs.length, 1); + assert.match(logs[0], new RegExp(escapeRegExp(filePath))); + assert.match(logs[0], new RegExp(escapeRegExp(recoveryFiles[0]))); - fs.removeSync(filePath) - }) - }) + fs.removeSync(filePath); + }); + }); it("doesn't create a recovery file when the file that's being saved doesn't exist yet", async () => { - const mockWindow = {} + const mockWindow = {}; - await recoveryService.willSavePath(mockWindow, 'a-file-that-doesnt-exist') - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + await recoveryService.willSavePath(mockWindow, 'a-file-that-doesnt-exist'); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); - await recoveryService.didSavePath(mockWindow, 'a-file-that-doesnt-exist') - assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) - }) -}) + await recoveryService.didSavePath(mockWindow, 'a-file-that-doesnt-exist'); + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0); + }); +}); diff --git a/spec/main-process/mocha-test-runner.js b/spec/main-process/mocha-test-runner.js index ef8ef69ef..2c3a28ded 100644 --- a/spec/main-process/mocha-test-runner.js +++ b/spec/main-process/mocha-test-runner.js @@ -1,13 +1,13 @@ -const Mocha = require('mocha') -const fs = require('fs-plus') -const { assert } = require('chai') +const Mocha = require('mocha'); +const fs = require('fs-plus'); +const { assert } = require('chai'); -module.exports = function (testPaths) { - global.assert = assert +module.exports = function(testPaths) { + global.assert = assert; let reporterOptions = { reporterEnabled: 'list' - } + }; if (process.env.TEST_JUNIT_XML_PATH) { reporterOptions = { @@ -15,31 +15,31 @@ module.exports = function (testPaths) { mochaJunitReporterReporterOptions: { mochaFile: process.env.TEST_JUNIT_XML_PATH } - } + }; } const mocha = new Mocha({ reporter: 'mocha-multi-reporters', reporterOptions - }) + }); for (let testPath of testPaths) { if (fs.isDirectorySync(testPath)) { for (let testFilePath of fs.listTreeSync(testPath)) { if (/\.test\.(coffee|js)$/.test(testFilePath)) { - mocha.addFile(testFilePath) + mocha.addFile(testFilePath); } } } else { - mocha.addFile(testPath) + mocha.addFile(testPath); } } mocha.run(failures => { if (failures === 0) { - process.exit(0) + process.exit(0); } else { - process.exit(1) + process.exit(1); } - }) -} + }); +}; diff --git a/spec/main-process/parse-command-line.test.js b/spec/main-process/parse-command-line.test.js index 6b5ac3ba5..e41feacf1 100644 --- a/spec/main-process/parse-command-line.test.js +++ b/spec/main-process/parse-command-line.test.js @@ -1,5 +1,5 @@ -const { assert } = require('chai') -const parseCommandLine = require('../../src/main-process/parse-command-line') +const { assert } = require('chai'); +const parseCommandLine = require('../../src/main-process/parse-command-line'); describe('parseCommandLine', () => { describe('when --uri-handler is not passed', () => { @@ -11,14 +11,17 @@ describe('parseCommandLine', () => { '/some/path', 'atom://test/url', 'atom://other/url' - ]) - assert.isTrue(args.devMode) - assert.isTrue(args.safeMode) - assert.isTrue(args.test) - assert.deepEqual(args.urlsToOpen, ['atom://test/url', 'atom://other/url']) - assert.deepEqual(args.pathsToOpen, ['/some/path']) - }) - }) + ]); + assert.isTrue(args.devMode); + assert.isTrue(args.safeMode); + assert.isTrue(args.test); + assert.deepEqual(args.urlsToOpen, [ + 'atom://test/url', + 'atom://other/url' + ]); + assert.deepEqual(args.pathsToOpen, ['/some/path']); + }); + }); describe('when --uri-handler is passed', () => { it('ignores other arguments and limits to one URL', () => { @@ -30,12 +33,12 @@ describe('parseCommandLine', () => { '/some/path', 'atom://test/url', 'atom://other/url' - ]) - assert.isUndefined(args.devMode) - assert.isUndefined(args.safeMode) - assert.isUndefined(args.test) - assert.deepEqual(args.urlsToOpen, ['atom://test/url']) - assert.deepEqual(args.pathsToOpen, []) - }) - }) -}) + ]); + assert.isUndefined(args.devMode); + assert.isUndefined(args.safeMode); + assert.isUndefined(args.test); + assert.deepEqual(args.urlsToOpen, ['atom://test/url']); + assert.deepEqual(args.pathsToOpen, []); + }); + }); +}); diff --git a/spec/menu-sort-helpers-spec.js b/spec/menu-sort-helpers-spec.js index 0e63824ff..2ac0b23d0 100644 --- a/spec/menu-sort-helpers-spec.js +++ b/spec/menu-sort-helpers-spec.js @@ -1,39 +1,39 @@ -const { sortMenuItems } = require('../src/menu-sort-helpers') +const { sortMenuItems } = require('../src/menu-sort-helpers'); describe('contextMenu', () => { describe('dedupes separators', () => { it('preserves existing submenus', () => { - const items = [{ submenu: [] }] - expect(sortMenuItems(items)).toEqual(items) - }) - }) + const items = [{ submenu: [] }]; + expect(sortMenuItems(items)).toEqual(items); + }); + }); describe('dedupes separators', () => { it('trims leading separators', () => { - const items = [{ type: 'separator' }, { command: 'core:one' }] - const expected = [{ command: 'core:one' }] - expect(sortMenuItems(items)).toEqual(expected) - }) + const items = [{ type: 'separator' }, { command: 'core:one' }]; + const expected = [{ command: 'core:one' }]; + expect(sortMenuItems(items)).toEqual(expected); + }); it('preserves separators at the begining of set two', () => { const items = [ { command: 'core:one' }, { type: 'separator' }, { command: 'core:two' } - ] + ]; const expected = [ { command: 'core:one' }, { type: 'separator' }, { command: 'core:two' } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); it('trims trailing separators', () => { - const items = [{ command: 'core:one' }, { type: 'separator' }] - const expected = [{ command: 'core:one' }] - expect(sortMenuItems(items)).toEqual(expected) - }) + const items = [{ command: 'core:one' }, { type: 'separator' }]; + const expected = [{ command: 'core:one' }]; + expect(sortMenuItems(items)).toEqual(expected); + }); it('removes duplicate separators across sets', () => { const items = [ @@ -41,15 +41,15 @@ describe('contextMenu', () => { { type: 'separator' }, { type: 'separator' }, { command: 'core:two' } - ] + ]; const expected = [ { command: 'core:one' }, { type: 'separator' }, { command: 'core:two' } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); + }); describe('can move an item to a different group by merging groups', () => { it('can move a group of one item', () => { @@ -60,15 +60,15 @@ describe('contextMenu', () => { { type: 'separator' }, { command: 'core:three', after: ['core:one'] }, { type: 'separator' } - ] + ]; const expected = [ { command: 'core:one' }, { command: 'core:three', after: ['core:one'] }, { type: 'separator' }, { command: 'core:two' } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); it("moves all items in the moving item's group", () => { const items = [ @@ -79,16 +79,16 @@ describe('contextMenu', () => { { command: 'core:three', after: ['core:one'] }, { command: 'core:four' }, { type: 'separator' } - ] + ]; const expected = [ { command: 'core:one' }, { command: 'core:three', after: ['core:one'] }, { command: 'core:four' }, { type: 'separator' }, { command: 'core:two' } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); it("ignores positions relative to commands that don't exist", () => { const items = [ @@ -99,30 +99,30 @@ describe('contextMenu', () => { { command: 'core:three', after: ['core:does-not-exist'] }, { command: 'core:four', after: ['core:one'] }, { type: 'separator' } - ] + ]; const expected = [ { command: 'core:one' }, { command: 'core:three', after: ['core:does-not-exist'] }, { command: 'core:four', after: ['core:one'] }, { type: 'separator' }, { command: 'core:two' } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); it('can handle recursive group merging', () => { const items = [ { command: 'core:one', after: ['core:three'] }, { command: 'core:two', before: ['core:one'] }, { command: 'core:three' } - ] + ]; const expected = [ { command: 'core:three' }, { command: 'core:two', before: ['core:one'] }, { command: 'core:one', after: ['core:three'] } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); it('can merge multiple groups when given a list of before/after commands', () => { const items = [ @@ -131,14 +131,14 @@ describe('contextMenu', () => { { command: 'core:two' }, { type: 'separator' }, { command: 'core:three', after: ['core:one', 'core:two'] } - ] + ]; const expected = [ { command: 'core:two' }, { command: 'core:one' }, { command: 'core:three', after: ['core:one', 'core:two'] } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); it('can merge multiple groups based on both before/after commands', () => { const items = [ @@ -147,39 +147,39 @@ describe('contextMenu', () => { { command: 'core:two' }, { type: 'separator' }, { command: 'core:three', after: ['core:one'], before: ['core:two'] } - ] + ]; const expected = [ { command: 'core:one' }, { command: 'core:three', after: ['core:one'], before: ['core:two'] }, { command: 'core:two' } - ] - expect(sortMenuItems(items)).toEqual(expected) - }) - }) + ]; + expect(sortMenuItems(items)).toEqual(expected); + }); + }); describe('sorts items within their ultimate group', () => { it('does a simple sort', () => { const items = [ { command: 'core:two', after: ['core:one'] }, { command: 'core:one' } - ] + ]; expect(sortMenuItems(items)).toEqual([ { command: 'core:one' }, { command: 'core:two', after: ['core:one'] } - ]) - }) + ]); + }); it('resolves cycles by ignoring things that conflict', () => { const items = [ { command: 'core:two', after: ['core:one'] }, { command: 'core:one', after: ['core:two'] } - ] + ]; expect(sortMenuItems(items)).toEqual([ { command: 'core:one', after: ['core:two'] }, { command: 'core:two', after: ['core:one'] } - ]) - }) - }) + ]); + }); + }); describe('sorts groups', () => { it('does a simple sort', () => { @@ -187,26 +187,26 @@ describe('contextMenu', () => { { command: 'core:two', afterGroupContaining: ['core:one'] }, { type: 'separator' }, { command: 'core:one' } - ] + ]; expect(sortMenuItems(items)).toEqual([ { command: 'core:one' }, { type: 'separator' }, { command: 'core:two', afterGroupContaining: ['core:one'] } - ]) - }) + ]); + }); it('resolves cycles by ignoring things that conflict', () => { const items = [ { command: 'core:two', afterGroupContaining: ['core:one'] }, { type: 'separator' }, { command: 'core:one', afterGroupContaining: ['core:two'] } - ] + ]; expect(sortMenuItems(items)).toEqual([ { command: 'core:one', afterGroupContaining: ['core:two'] }, { type: 'separator' }, { command: 'core:two', afterGroupContaining: ['core:one'] } - ]) - }) + ]); + }); it('ignores references to commands that do not exist', () => { const items = [ @@ -216,13 +216,13 @@ describe('contextMenu', () => { command: 'core:two', afterGroupContaining: ['core:does-not-exist'] } - ] + ]; expect(sortMenuItems(items)).toEqual([ { command: 'core:one' }, { type: 'separator' }, { command: 'core:two', afterGroupContaining: ['core:does-not-exist'] } - ]) - }) + ]); + }); it('only respects the first matching [before|after]GroupContaining rule in a given group', () => { const items = [ @@ -232,7 +232,7 @@ describe('contextMenu', () => { { command: 'core:four', afterGroupContaining: ['core:two'] }, { type: 'separator' }, { command: 'core:two' } - ] + ]; expect(sortMenuItems(items)).toEqual([ { command: 'core:three', beforeGroupContaining: ['core:one'] }, { command: 'core:four', afterGroupContaining: ['core:two'] }, @@ -240,7 +240,7 @@ describe('contextMenu', () => { { command: 'core:one' }, { type: 'separator' }, { command: 'core:two' } - ]) - }) - }) -}) + ]); + }); + }); +}); diff --git a/spec/native-watcher-registry-spec.js b/spec/native-watcher-registry-spec.js index d18ff0d8e..fa0e22fb4 100644 --- a/spec/native-watcher-registry-spec.js +++ b/spec/native-watcher-registry-spec.js @@ -1,213 +1,213 @@ /** @babel */ -import path from 'path' -import { Emitter } from 'event-kit' +import path from 'path'; +import { Emitter } from 'event-kit'; -import { NativeWatcherRegistry } from '../src/native-watcher-registry' +import { NativeWatcherRegistry } from '../src/native-watcher-registry'; -function findRootDirectory () { - let current = process.cwd() +function findRootDirectory() { + let current = process.cwd(); while (true) { - let next = path.resolve(current, '..') + let next = path.resolve(current, '..'); if (next === current) { - return next + return next; } else { - current = next + current = next; } } } -const ROOT = findRootDirectory() +const ROOT = findRootDirectory(); -function absolute (...parts) { - const candidate = path.join(...parts) - return path.isAbsolute(candidate) ? candidate : path.join(ROOT, candidate) +function absolute(...parts) { + const candidate = path.join(...parts); + return path.isAbsolute(candidate) ? candidate : path.join(ROOT, candidate); } -function parts (fullPath) { - return fullPath.split(path.sep).filter(part => part.length > 0) +function parts(fullPath) { + return fullPath.split(path.sep).filter(part => part.length > 0); } class MockWatcher { - constructor (normalizedPath) { - this.normalizedPath = normalizedPath - this.native = null + constructor(normalizedPath) { + this.normalizedPath = normalizedPath; + this.native = null; } - getNormalizedPathPromise () { - return Promise.resolve(this.normalizedPath) + getNormalizedPathPromise() { + return Promise.resolve(this.normalizedPath); } - attachToNative (native, nativePath) { + attachToNative(native, nativePath) { if (this.normalizedPath.startsWith(nativePath)) { if (this.native) { this.native.attached = this.native.attached.filter( each => each !== this - ) + ); } - this.native = native - this.native.attached.push(this) + this.native = native; + this.native.attached.push(this); } } } class MockNative { - constructor (name) { - this.name = name - this.attached = [] - this.disposed = false - this.stopped = false + constructor(name) { + this.name = name; + this.attached = []; + this.disposed = false; + this.stopped = false; - this.emitter = new Emitter() + this.emitter = new Emitter(); } - reattachTo (newNative, nativePath) { + reattachTo(newNative, nativePath) { for (const watcher of this.attached) { - watcher.attachToNative(newNative, nativePath) + watcher.attachToNative(newNative, nativePath); } } - onWillStop (callback) { - return this.emitter.on('will-stop', callback) + onWillStop(callback) { + return this.emitter.on('will-stop', callback); } - dispose () { - this.disposed = true + dispose() { + this.disposed = true; } - stop () { - this.stopped = true - this.emitter.emit('will-stop') + stop() { + this.stopped = true; + this.emitter.emit('will-stop'); } } -describe('NativeWatcherRegistry', function () { - let createNative, registry +describe('NativeWatcherRegistry', function() { + let createNative, registry; - beforeEach(function () { + beforeEach(function() { registry = new NativeWatcherRegistry(normalizedPath => createNative(normalizedPath) - ) - }) + ); + }); - it('attaches a Watcher to a newly created NativeWatcher for a new directory', async function () { - const watcher = new MockWatcher(absolute('some', 'path')) - const NATIVE = new MockNative('created') - createNative = () => NATIVE + it('attaches a Watcher to a newly created NativeWatcher for a new directory', async function() { + const watcher = new MockWatcher(absolute('some', 'path')); + const NATIVE = new MockNative('created'); + createNative = () => NATIVE; - await registry.attach(watcher) + await registry.attach(watcher); - expect(watcher.native).toBe(NATIVE) - }) + expect(watcher.native).toBe(NATIVE); + }); - it('reuses an existing NativeWatcher on the same directory', async function () { - this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD() + it('reuses an existing NativeWatcher on the same directory', async function() { + this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD(); - const EXISTING = new MockNative('existing') - const existingPath = absolute('existing', 'path') - let firstTime = true + const EXISTING = new MockNative('existing'); + const existingPath = absolute('existing', 'path'); + let firstTime = true; createNative = () => { if (firstTime) { - firstTime = false - return EXISTING + firstTime = false; + return EXISTING; } - return new MockNative('nope') - } - await registry.attach(new MockWatcher(existingPath)) + return new MockNative('nope'); + }; + await registry.attach(new MockWatcher(existingPath)); - const watcher = new MockWatcher(existingPath) - await registry.attach(watcher) + const watcher = new MockWatcher(existingPath); + await registry.attach(watcher); - expect(watcher.native).toBe(EXISTING) - }) + expect(watcher.native).toBe(EXISTING); + }); - it('attaches to an existing NativeWatcher on a parent directory', async function () { - const EXISTING = new MockNative('existing') - const parentDir = absolute('existing', 'path') - const subDir = path.join(parentDir, 'sub', 'directory') - let firstTime = true + it('attaches to an existing NativeWatcher on a parent directory', async function() { + const EXISTING = new MockNative('existing'); + const parentDir = absolute('existing', 'path'); + const subDir = path.join(parentDir, 'sub', 'directory'); + let firstTime = true; createNative = () => { if (firstTime) { - firstTime = false - return EXISTING + firstTime = false; + return EXISTING; } - return new MockNative('nope') - } - await registry.attach(new MockWatcher(parentDir)) + return new MockNative('nope'); + }; + await registry.attach(new MockWatcher(parentDir)); - const watcher = new MockWatcher(subDir) - await registry.attach(watcher) + const watcher = new MockWatcher(subDir); + await registry.attach(watcher); - expect(watcher.native).toBe(EXISTING) - }) + expect(watcher.native).toBe(EXISTING); + }); - it('adopts Watchers from NativeWatchers on child directories', async function () { - const parentDir = absolute('existing', 'path') - const childDir0 = path.join(parentDir, 'child', 'directory', 'zero') - const childDir1 = path.join(parentDir, 'child', 'directory', 'one') - const otherDir = absolute('another', 'path') + it('adopts Watchers from NativeWatchers on child directories', async function() { + const parentDir = absolute('existing', 'path'); + const childDir0 = path.join(parentDir, 'child', 'directory', 'zero'); + const childDir1 = path.join(parentDir, 'child', 'directory', 'one'); + const otherDir = absolute('another', 'path'); - const CHILD0 = new MockNative('existing0') - const CHILD1 = new MockNative('existing1') - const OTHER = new MockNative('existing2') - const PARENT = new MockNative('parent') + const CHILD0 = new MockNative('existing0'); + const CHILD1 = new MockNative('existing1'); + const OTHER = new MockNative('existing2'); + const PARENT = new MockNative('parent'); createNative = dir => { if (dir === childDir0) { - return CHILD0 + return CHILD0; } else if (dir === childDir1) { - return CHILD1 + return CHILD1; } else if (dir === otherDir) { - return OTHER + return OTHER; } else if (dir === parentDir) { - return PARENT + return PARENT; } else { - throw new Error(`Unexpected path: ${dir}`) + throw new Error(`Unexpected path: ${dir}`); } - } + }; - const watcher0 = new MockWatcher(childDir0) - await registry.attach(watcher0) + const watcher0 = new MockWatcher(childDir0); + await registry.attach(watcher0); - const watcher1 = new MockWatcher(childDir1) - await registry.attach(watcher1) + const watcher1 = new MockWatcher(childDir1); + await registry.attach(watcher1); - const watcher2 = new MockWatcher(otherDir) - await registry.attach(watcher2) + const watcher2 = new MockWatcher(otherDir); + await registry.attach(watcher2); - expect(watcher0.native).toBe(CHILD0) - expect(watcher1.native).toBe(CHILD1) - expect(watcher2.native).toBe(OTHER) + expect(watcher0.native).toBe(CHILD0); + expect(watcher1.native).toBe(CHILD1); + expect(watcher2.native).toBe(OTHER); // Consolidate all three watchers beneath the same native watcher on the parent directory - const watcher = new MockWatcher(parentDir) - await registry.attach(watcher) + const watcher = new MockWatcher(parentDir); + await registry.attach(watcher); - expect(watcher.native).toBe(PARENT) + expect(watcher.native).toBe(PARENT); - expect(watcher0.native).toBe(PARENT) - expect(CHILD0.stopped).toBe(true) - expect(CHILD0.disposed).toBe(true) + expect(watcher0.native).toBe(PARENT); + expect(CHILD0.stopped).toBe(true); + expect(CHILD0.disposed).toBe(true); - expect(watcher1.native).toBe(PARENT) - expect(CHILD1.stopped).toBe(true) - expect(CHILD1.disposed).toBe(true) + expect(watcher1.native).toBe(PARENT); + expect(CHILD1.stopped).toBe(true); + expect(CHILD1.disposed).toBe(true); - expect(watcher2.native).toBe(OTHER) - expect(OTHER.stopped).toBe(false) - expect(OTHER.disposed).toBe(false) - }) + expect(watcher2.native).toBe(OTHER); + expect(OTHER.stopped).toBe(false); + expect(OTHER.disposed).toBe(false); + }); - describe('removing NativeWatchers', function () { - it('happens when they stop', async function () { - const STOPPED = new MockNative('stopped') - const RUNNING = new MockNative('running') + describe('removing NativeWatchers', function() { + it('happens when they stop', async function() { + const STOPPED = new MockNative('stopped'); + const RUNNING = new MockNative('running'); - const stoppedPath = absolute('watcher', 'that', 'will', 'be', 'stopped') + const stoppedPath = absolute('watcher', 'that', 'will', 'be', 'stopped'); const stoppedPathParts = stoppedPath .split(path.sep) - .filter(part => part.length > 0) + .filter(part => part.length > 0); const runningPath = absolute( 'watcher', 'that', @@ -215,86 +215,86 @@ describe('NativeWatcherRegistry', function () { 'continue', 'to', 'exist' - ) + ); const runningPathParts = runningPath .split(path.sep) - .filter(part => part.length > 0) + .filter(part => part.length > 0); createNative = dir => { if (dir === stoppedPath) { - return STOPPED + return STOPPED; } else if (dir === runningPath) { - return RUNNING + return RUNNING; } else { - throw new Error(`Unexpected path: ${dir}`) + throw new Error(`Unexpected path: ${dir}`); } - } + }; - const stoppedWatcher = new MockWatcher(stoppedPath) - await registry.attach(stoppedWatcher) + const stoppedWatcher = new MockWatcher(stoppedPath); + await registry.attach(stoppedWatcher); - const runningWatcher = new MockWatcher(runningPath) - await registry.attach(runningWatcher) + const runningWatcher = new MockWatcher(runningPath); + await registry.attach(runningWatcher); - STOPPED.stop() + STOPPED.stop(); const runningNode = registry.tree.root.lookup(runningPathParts).when({ parent: node => node, missing: () => false, children: () => false - }) - expect(runningNode).toBeTruthy() - expect(runningNode.getNativeWatcher()).toBe(RUNNING) + }); + expect(runningNode).toBeTruthy(); + expect(runningNode.getNativeWatcher()).toBe(RUNNING); const stoppedNode = registry.tree.root.lookup(stoppedPathParts).when({ parent: () => false, missing: () => true, children: () => false - }) - expect(stoppedNode).toBe(true) - }) + }); + expect(stoppedNode).toBe(true); + }); - it('reassigns new child watchers when a parent watcher is stopped', async function () { - const CHILD0 = new MockNative('child0') - const CHILD1 = new MockNative('child1') - const PARENT = new MockNative('parent') + it('reassigns new child watchers when a parent watcher is stopped', async function() { + const CHILD0 = new MockNative('child0'); + const CHILD1 = new MockNative('child1'); + const PARENT = new MockNative('parent'); - const parentDir = absolute('parent') - const childDir0 = path.join(parentDir, 'child0') - const childDir1 = path.join(parentDir, 'child1') + const parentDir = absolute('parent'); + const childDir0 = path.join(parentDir, 'child0'); + const childDir1 = path.join(parentDir, 'child1'); createNative = dir => { if (dir === parentDir) { - return PARENT + return PARENT; } else if (dir === childDir0) { - return CHILD0 + return CHILD0; } else if (dir === childDir1) { - return CHILD1 + return CHILD1; } else { - throw new Error(`Unexpected directory ${dir}`) + throw new Error(`Unexpected directory ${dir}`); } - } + }; - const parentWatcher = new MockWatcher(parentDir) - const childWatcher0 = new MockWatcher(childDir0) - const childWatcher1 = new MockWatcher(childDir1) + const parentWatcher = new MockWatcher(parentDir); + const childWatcher0 = new MockWatcher(childDir0); + const childWatcher1 = new MockWatcher(childDir1); - await registry.attach(parentWatcher) + await registry.attach(parentWatcher); await Promise.all([ registry.attach(childWatcher0), registry.attach(childWatcher1) - ]) + ]); // All three watchers should share the parent watcher's native watcher. - expect(parentWatcher.native).toBe(PARENT) - expect(childWatcher0.native).toBe(PARENT) - expect(childWatcher1.native).toBe(PARENT) + expect(parentWatcher.native).toBe(PARENT); + expect(childWatcher0.native).toBe(PARENT); + expect(childWatcher1.native).toBe(PARENT); // Stopping the parent should detach and recreate the child watchers. - PARENT.stop() + PARENT.stop(); - expect(childWatcher0.native).toBe(CHILD0) - expect(childWatcher1.native).toBe(CHILD1) + expect(childWatcher0.native).toBe(CHILD0); + expect(childWatcher1.native).toBe(CHILD1); expect( registry.tree.root.lookup(parts(parentDir)).when({ @@ -302,7 +302,7 @@ describe('NativeWatcherRegistry', function () { missing: () => false, children: () => true }) - ).toBe(true) + ).toBe(true); expect( registry.tree.root.lookup(parts(childDir0)).when({ @@ -310,7 +310,7 @@ describe('NativeWatcherRegistry', function () { missing: () => false, children: () => false }) - ).toBe(true) + ).toBe(true); expect( registry.tree.root.lookup(parts(childDir1)).when({ @@ -318,48 +318,48 @@ describe('NativeWatcherRegistry', function () { missing: () => false, children: () => false }) - ).toBe(true) - }) + ).toBe(true); + }); - it('consolidates children when splitting a parent watcher', async function () { - const CHILD0 = new MockNative('child0') - const PARENT = new MockNative('parent') + it('consolidates children when splitting a parent watcher', async function() { + const CHILD0 = new MockNative('child0'); + const PARENT = new MockNative('parent'); - const parentDir = absolute('parent') - const childDir0 = path.join(parentDir, 'child0') - const childDir1 = path.join(parentDir, 'child0', 'child1') + const parentDir = absolute('parent'); + const childDir0 = path.join(parentDir, 'child0'); + const childDir1 = path.join(parentDir, 'child0', 'child1'); createNative = dir => { if (dir === parentDir) { - return PARENT + return PARENT; } else if (dir === childDir0) { - return CHILD0 + return CHILD0; } else { - throw new Error(`Unexpected directory ${dir}`) + throw new Error(`Unexpected directory ${dir}`); } - } + }; - const parentWatcher = new MockWatcher(parentDir) - const childWatcher0 = new MockWatcher(childDir0) - const childWatcher1 = new MockWatcher(childDir1) + const parentWatcher = new MockWatcher(parentDir); + const childWatcher0 = new MockWatcher(childDir0); + const childWatcher1 = new MockWatcher(childDir1); - await registry.attach(parentWatcher) + await registry.attach(parentWatcher); await Promise.all([ registry.attach(childWatcher0), registry.attach(childWatcher1) - ]) + ]); // All three watchers should share the parent watcher's native watcher. - expect(parentWatcher.native).toBe(PARENT) - expect(childWatcher0.native).toBe(PARENT) - expect(childWatcher1.native).toBe(PARENT) + expect(parentWatcher.native).toBe(PARENT); + expect(childWatcher0.native).toBe(PARENT); + expect(childWatcher1.native).toBe(PARENT); // Stopping the parent should detach and create the child watchers. Both child watchers should // share the same native watcher. - PARENT.stop() + PARENT.stop(); - expect(childWatcher0.native).toBe(CHILD0) - expect(childWatcher1.native).toBe(CHILD0) + expect(childWatcher0.native).toBe(CHILD0); + expect(childWatcher1.native).toBe(CHILD0); expect( registry.tree.root.lookup(parts(parentDir)).when({ @@ -367,7 +367,7 @@ describe('NativeWatcherRegistry', function () { missing: () => false, children: () => true }) - ).toBe(true) + ).toBe(true); expect( registry.tree.root.lookup(parts(childDir0)).when({ @@ -375,7 +375,7 @@ describe('NativeWatcherRegistry', function () { missing: () => false, children: () => false }) - ).toBe(true) + ).toBe(true); expect( registry.tree.root.lookup(parts(childDir1)).when({ @@ -383,7 +383,7 @@ describe('NativeWatcherRegistry', function () { missing: () => false, children: () => false }) - ).toBe(true) - }) - }) -}) + ).toBe(true); + }); + }); +}); diff --git a/spec/notification-manager-spec.js b/spec/notification-manager-spec.js index 3fd91d0ca..9d8f28d1c 100644 --- a/spec/notification-manager-spec.js +++ b/spec/notification-manager-spec.js @@ -1,91 +1,91 @@ -const NotificationManager = require('../src/notification-manager') +const NotificationManager = require('../src/notification-manager'); describe('NotificationManager', () => { - let manager + let manager; beforeEach(() => { - manager = new NotificationManager() - }) + manager = new NotificationManager(); + }); describe('the atom global', () => it('has a notifications instance', () => { - expect(atom.notifications instanceof NotificationManager).toBe(true) - })) + expect(atom.notifications instanceof NotificationManager).toBe(true); + })); describe('adding events', () => { - let addSpy + let addSpy; beforeEach(() => { - addSpy = jasmine.createSpy() - manager.onDidAddNotification(addSpy) - }) + addSpy = jasmine.createSpy(); + manager.onDidAddNotification(addSpy); + }); it('emits an event when a notification has been added', () => { - manager.add('error', 'Some error!', { icon: 'someIcon' }) - expect(addSpy).toHaveBeenCalled() + manager.add('error', 'Some error!', { icon: 'someIcon' }); + expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('error') - expect(notification.getMessage()).toBe('Some error!') - expect(notification.getIcon()).toBe('someIcon') - }) + const notification = addSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('error'); + expect(notification.getMessage()).toBe('Some error!'); + expect(notification.getIcon()).toBe('someIcon'); + }); it('emits a fatal error when ::addFatalError has been called', () => { - manager.addFatalError('Some error!', { icon: 'someIcon' }) - expect(addSpy).toHaveBeenCalled() - const notification = addSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('fatal') - }) + manager.addFatalError('Some error!', { icon: 'someIcon' }); + expect(addSpy).toHaveBeenCalled(); + const notification = addSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('fatal'); + }); it('emits an error when ::addError has been called', () => { - manager.addError('Some error!', { icon: 'someIcon' }) - expect(addSpy).toHaveBeenCalled() - const notification = addSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('error') - }) + manager.addError('Some error!', { icon: 'someIcon' }); + expect(addSpy).toHaveBeenCalled(); + const notification = addSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('error'); + }); it('emits a warning notification when ::addWarning has been called', () => { - manager.addWarning('Something!', { icon: 'someIcon' }) - expect(addSpy).toHaveBeenCalled() - const notification = addSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('warning') - }) + manager.addWarning('Something!', { icon: 'someIcon' }); + expect(addSpy).toHaveBeenCalled(); + const notification = addSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('warning'); + }); it('emits an info notification when ::addInfo has been called', () => { - manager.addInfo('Something!', { icon: 'someIcon' }) - expect(addSpy).toHaveBeenCalled() - const notification = addSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('info') - }) + manager.addInfo('Something!', { icon: 'someIcon' }); + expect(addSpy).toHaveBeenCalled(); + const notification = addSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('info'); + }); it('emits a success notification when ::addSuccess has been called', () => { - manager.addSuccess('Something!', { icon: 'someIcon' }) - expect(addSpy).toHaveBeenCalled() - const notification = addSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('success') - }) - }) + manager.addSuccess('Something!', { icon: 'someIcon' }); + expect(addSpy).toHaveBeenCalled(); + const notification = addSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('success'); + }); + }); describe('clearing notifications', () => { it('clears the notifications when ::clear has been called', () => { - manager.addSuccess('success') - expect(manager.getNotifications().length).toBe(1) - manager.clear() - expect(manager.getNotifications().length).toBe(0) - }) + manager.addSuccess('success'); + expect(manager.getNotifications().length).toBe(1); + manager.clear(); + expect(manager.getNotifications().length).toBe(0); + }); describe('adding events', () => { - let clearSpy + let clearSpy; beforeEach(() => { - clearSpy = jasmine.createSpy() - manager.onDidClearNotifications(clearSpy) - }) + clearSpy = jasmine.createSpy(); + manager.onDidClearNotifications(clearSpy); + }); it('emits an event when the notifications have been cleared', () => { - manager.clear() - expect(clearSpy).toHaveBeenCalled() - }) - }) - }) -}) + manager.clear(); + expect(clearSpy).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/notification-spec.js b/spec/notification-spec.js index 179f70950..89e215a6a 100644 --- a/spec/notification-spec.js +++ b/spec/notification-spec.js @@ -1,72 +1,72 @@ -const Notification = require('../src/notification') +const Notification = require('../src/notification'); describe('Notification', () => { it('throws an error when created with a non-string message', () => { - expect(() => new Notification('error', null)).toThrow() - expect(() => new Notification('error', 3)).toThrow() - expect(() => new Notification('error', {})).toThrow() - expect(() => new Notification('error', false)).toThrow() - expect(() => new Notification('error', [])).toThrow() - }) + expect(() => new Notification('error', null)).toThrow(); + expect(() => new Notification('error', 3)).toThrow(); + expect(() => new Notification('error', {})).toThrow(); + expect(() => new Notification('error', false)).toThrow(); + expect(() => new Notification('error', [])).toThrow(); + }); it('throws an error when created with non-object options', () => { - expect(() => new Notification('error', 'message', 'foo')).toThrow() - expect(() => new Notification('error', 'message', 3)).toThrow() - expect(() => new Notification('error', 'message', false)).toThrow() - expect(() => new Notification('error', 'message', [])).toThrow() - }) + expect(() => new Notification('error', 'message', 'foo')).toThrow(); + expect(() => new Notification('error', 'message', 3)).toThrow(); + expect(() => new Notification('error', 'message', false)).toThrow(); + expect(() => new Notification('error', 'message', [])).toThrow(); + }); describe('::getTimestamp()', () => it('returns a Date object', () => { - const notification = new Notification('error', 'message!') - expect(notification.getTimestamp() instanceof Date).toBe(true) - })) + const notification = new Notification('error', 'message!'); + expect(notification.getTimestamp() instanceof Date).toBe(true); + })); describe('::getIcon()', () => { it('returns a default when no icon specified', () => { - const notification = new Notification('error', 'message!') - expect(notification.getIcon()).toBe('flame') - }) + const notification = new Notification('error', 'message!'); + expect(notification.getIcon()).toBe('flame'); + }); it('returns the icon specified', () => { const notification = new Notification('error', 'message!', { icon: 'my-icon' - }) - expect(notification.getIcon()).toBe('my-icon') - }) - }) + }); + expect(notification.getIcon()).toBe('my-icon'); + }); + }); describe('dismissing notifications', () => { describe('when the notfication is dismissable', () => it('calls a callback when the notification is dismissed', () => { - const dismissedSpy = jasmine.createSpy() + const dismissedSpy = jasmine.createSpy(); const notification = new Notification('error', 'message', { dismissable: true - }) - notification.onDidDismiss(dismissedSpy) + }); + notification.onDidDismiss(dismissedSpy); - expect(notification.isDismissable()).toBe(true) - expect(notification.isDismissed()).toBe(false) + expect(notification.isDismissable()).toBe(true); + expect(notification.isDismissed()).toBe(false); - notification.dismiss() + notification.dismiss(); - expect(dismissedSpy).toHaveBeenCalled() - expect(notification.isDismissed()).toBe(true) - })) + expect(dismissedSpy).toHaveBeenCalled(); + expect(notification.isDismissed()).toBe(true); + })); describe('when the notfication is not dismissable', () => it('does nothing when ::dismiss() is called', () => { - const dismissedSpy = jasmine.createSpy() - const notification = new Notification('error', 'message') - notification.onDidDismiss(dismissedSpy) + const dismissedSpy = jasmine.createSpy(); + const notification = new Notification('error', 'message'); + notification.onDidDismiss(dismissedSpy); - expect(notification.isDismissable()).toBe(false) - expect(notification.isDismissed()).toBe(true) + expect(notification.isDismissable()).toBe(false); + expect(notification.isDismissed()).toBe(true); - notification.dismiss() + notification.dismiss(); - expect(dismissedSpy).not.toHaveBeenCalled() - expect(notification.isDismissed()).toBe(true) - })) - }) -}) + expect(dismissedSpy).not.toHaveBeenCalled(); + expect(notification.isDismissed()).toBe(true); + })); + }); +}); diff --git a/spec/package-manager-spec.js b/spec/package-manager-spec.js index 599fb0ff9..c1feaaf7c 100644 --- a/spec/package-manager-spec.js +++ b/spec/package-manager-spec.js @@ -1,391 +1,401 @@ -const path = require('path') -const url = require('url') -const Package = require('../src/package') -const PackageManager = require('../src/package-manager') -const temp = require('temp').track() -const fs = require('fs-plus') -const { Disposable } = require('atom') -const { buildKeydownEvent } = require('../src/keymap-extensions') -const { mockLocalStorage } = require('./spec-helper') -const ModuleCache = require('../src/module-cache') +const path = require('path'); +const url = require('url'); +const Package = require('../src/package'); +const PackageManager = require('../src/package-manager'); +const temp = require('temp').track(); +const fs = require('fs-plus'); +const { Disposable } = require('atom'); +const { buildKeydownEvent } = require('../src/keymap-extensions'); +const { mockLocalStorage } = require('./spec-helper'); +const ModuleCache = require('../src/module-cache'); describe('PackageManager', () => { - function createTestElement (className) { - const element = document.createElement('div') - element.className = className - return element + function createTestElement(className) { + const element = document.createElement('div'); + element.className = className; + return element; } beforeEach(() => { - spyOn(ModuleCache, 'add') - }) + spyOn(ModuleCache, 'add'); + }); describe('initialize', () => { it('adds regular package path', () => { - const packageManger = new PackageManager({}) - const configDirPath = path.join('~', 'someConfig') - packageManger.initialize({ configDirPath }) - expect(packageManger.packageDirPaths.length).toBe(1) + const packageManger = new PackageManager({}); + const configDirPath = path.join('~', 'someConfig'); + packageManger.initialize({ configDirPath }); + expect(packageManger.packageDirPaths.length).toBe(1); expect(packageManger.packageDirPaths[0]).toBe( path.join(configDirPath, 'packages') - ) - }) + ); + }); it('adds regular package path, dev package path, and Atom repo package path in dev mode and dev resource path is set', () => { - const packageManger = new PackageManager({}) - const configDirPath = path.join('~', 'someConfig') - const resourcePath = path.join('~', '/atom') - packageManger.initialize({ configDirPath, resourcePath, devMode: true }) - expect(packageManger.packageDirPaths.length).toBe(3) + const packageManger = new PackageManager({}); + const configDirPath = path.join('~', 'someConfig'); + const resourcePath = path.join('~', '/atom'); + packageManger.initialize({ configDirPath, resourcePath, devMode: true }); + expect(packageManger.packageDirPaths.length).toBe(3); expect(packageManger.packageDirPaths).toContain( path.join(configDirPath, 'packages') - ) + ); expect(packageManger.packageDirPaths).toContain( path.join(configDirPath, 'dev', 'packages') - ) + ); expect(packageManger.packageDirPaths).toContain( path.join(resourcePath, 'packages') - ) - }) - }) + ); + }); + }); describe('::getApmPath()', () => { it('returns the path to the apm command', () => { - let apmPath = path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm') + let apmPath = path.join( + process.resourcesPath, + 'app', + 'apm', + 'bin', + 'apm' + ); if (process.platform === 'win32') { - apmPath += '.cmd' + apmPath += '.cmd'; } - expect(atom.packages.getApmPath()).toBe(apmPath) - }) + expect(atom.packages.getApmPath()).toBe(apmPath); + }); describe('when the core.apmPath setting is set', () => { - beforeEach(() => atom.config.set('core.apmPath', '/path/to/apm')) + beforeEach(() => atom.config.set('core.apmPath', '/path/to/apm')); it('returns the value of the core.apmPath config setting', () => { - expect(atom.packages.getApmPath()).toBe('/path/to/apm') - }) - }) - }) + expect(atom.packages.getApmPath()).toBe('/path/to/apm'); + }); + }); + }); describe('::loadPackages()', () => { - beforeEach(() => spyOn(atom.packages, 'loadAvailablePackage')) + beforeEach(() => spyOn(atom.packages, 'loadAvailablePackage')); afterEach(async () => { - await atom.packages.deactivatePackages() - atom.packages.unloadPackages() - }) + await atom.packages.deactivatePackages(); + atom.packages.unloadPackages(); + }); it('sets hasLoadedInitialPackages', () => { - expect(atom.packages.hasLoadedInitialPackages()).toBe(false) - atom.packages.loadPackages() - expect(atom.packages.hasLoadedInitialPackages()).toBe(true) - }) - }) + expect(atom.packages.hasLoadedInitialPackages()).toBe(false); + atom.packages.loadPackages(); + expect(atom.packages.hasLoadedInitialPackages()).toBe(true); + }); + }); describe('::loadPackage(name)', () => { - beforeEach(() => atom.config.set('core.disabledPackages', [])) + beforeEach(() => atom.config.set('core.disabledPackages', [])); it('returns the package', () => { - const pack = atom.packages.loadPackage('package-with-index') - expect(pack instanceof Package).toBe(true) - expect(pack.metadata.name).toBe('package-with-index') - }) + const pack = atom.packages.loadPackage('package-with-index'); + expect(pack instanceof Package).toBe(true); + expect(pack.metadata.name).toBe('package-with-index'); + }); it('returns the package if it has an invalid keymap', () => { - spyOn(atom, 'inSpecMode').andReturn(false) - const pack = atom.packages.loadPackage('package-with-broken-keymap') - expect(pack instanceof Package).toBe(true) - expect(pack.metadata.name).toBe('package-with-broken-keymap') - }) + spyOn(atom, 'inSpecMode').andReturn(false); + const pack = atom.packages.loadPackage('package-with-broken-keymap'); + expect(pack instanceof Package).toBe(true); + expect(pack.metadata.name).toBe('package-with-broken-keymap'); + }); it('returns the package if it has an invalid stylesheet', () => { - spyOn(atom, 'inSpecMode').andReturn(false) - const pack = atom.packages.loadPackage('package-with-invalid-styles') - expect(pack instanceof Package).toBe(true) - expect(pack.metadata.name).toBe('package-with-invalid-styles') - expect(pack.stylesheets.length).toBe(0) + spyOn(atom, 'inSpecMode').andReturn(false); + const pack = atom.packages.loadPackage('package-with-invalid-styles'); + expect(pack instanceof Package).toBe(true); + expect(pack.metadata.name).toBe('package-with-invalid-styles'); + expect(pack.stylesheets.length).toBe(0); - const addErrorHandler = jasmine.createSpy() - atom.notifications.onDidAddNotification(addErrorHandler) - expect(() => pack.reloadStylesheets()).not.toThrow() - expect(addErrorHandler.callCount).toBe(2) + const addErrorHandler = jasmine.createSpy(); + atom.notifications.onDidAddNotification(addErrorHandler); + expect(() => pack.reloadStylesheets()).not.toThrow(); + expect(addErrorHandler.callCount).toBe(2); expect(addErrorHandler.argsForCall[1][0].message).toContain( 'Failed to reload the package-with-invalid-styles package stylesheets' - ) + ); expect(addErrorHandler.argsForCall[1][0].options.packageName).toEqual( 'package-with-invalid-styles' - ) - }) + ); + }); it('returns null if the package has an invalid package.json', () => { - spyOn(atom, 'inSpecMode').andReturn(false) - const addErrorHandler = jasmine.createSpy() - atom.notifications.onDidAddNotification(addErrorHandler) + spyOn(atom, 'inSpecMode').andReturn(false); + const addErrorHandler = jasmine.createSpy(); + atom.notifications.onDidAddNotification(addErrorHandler); expect( atom.packages.loadPackage('package-with-broken-package-json') - ).toBeNull() - expect(addErrorHandler.callCount).toBe(1) + ).toBeNull(); + expect(addErrorHandler.callCount).toBe(1); expect(addErrorHandler.argsForCall[0][0].message).toContain( 'Failed to load the package-with-broken-package-json package' - ) + ); expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( 'package-with-broken-package-json' - ) - }) + ); + }); it('returns null if the package name or path starts with a dot', () => { expect( atom.packages.loadPackage('/Users/user/.atom/packages/.git') - ).toBeNull() - }) + ).toBeNull(); + }); it('normalizes short repository urls in package.json', () => { let { metadata } = atom.packages.loadPackage( 'package-with-short-url-package-json' - ) - expect(metadata.repository.type).toBe('git') - expect(metadata.repository.url).toBe('https://github.com/example/repo') - ;({ metadata } = atom.packages.loadPackage( + ); + expect(metadata.repository.type).toBe('git'); + expect(metadata.repository.url).toBe('https://github.com/example/repo'); + ({ metadata } = atom.packages.loadPackage( 'package-with-invalid-url-package-json' - )) - expect(metadata.repository.type).toBe('git') - expect(metadata.repository.url).toBe('foo') - }) + )); + expect(metadata.repository.type).toBe('git'); + expect(metadata.repository.url).toBe('foo'); + }); it('trims git+ from the beginning and .git from the end of repository URLs, even if npm already normalized them ', () => { const { metadata } = atom.packages.loadPackage( 'package-with-prefixed-and-suffixed-repo-url' - ) - expect(metadata.repository.type).toBe('git') - expect(metadata.repository.url).toBe('https://github.com/example/repo') - }) + ); + expect(metadata.repository.type).toBe('git'); + expect(metadata.repository.url).toBe('https://github.com/example/repo'); + }); it('returns null if the package is not found in any package directory', () => { - spyOn(console, 'warn') + spyOn(console, 'warn'); expect( atom.packages.loadPackage('this-package-cannot-be-found') - ).toBeNull() - expect(console.warn.callCount).toBe(1) - expect(console.warn.argsForCall[0][0]).toContain('Could not resolve') - }) + ).toBeNull(); + expect(console.warn.callCount).toBe(1); + expect(console.warn.argsForCall[0][0]).toContain('Could not resolve'); + }); describe('when the package is deprecated', () => { it('returns null', () => { - spyOn(console, 'warn') + spyOn(console, 'warn'); expect( atom.packages.loadPackage( path.join(__dirname, 'fixtures', 'packages', 'wordcount') ) - ).toBeNull() + ).toBeNull(); expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.9')).toBe( true - ) + ); expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.0')).toBe( true - ) + ); expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.1')).toBe( false - ) + ); expect( atom.packages.getDeprecatedPackageMetadata('wordcount').version - ).toBe('<=2.2.0') - }) - }) + ).toBe('<=2.2.0'); + }); + }); it('invokes ::onDidLoadPackage listeners with the loaded package', () => { - let loadedPackage = null + let loadedPackage = null; atom.packages.onDidLoadPackage(pack => { - loadedPackage = pack - }) + loadedPackage = pack; + }); - atom.packages.loadPackage('package-with-main') + atom.packages.loadPackage('package-with-main'); - expect(loadedPackage.name).toBe('package-with-main') - }) + expect(loadedPackage.name).toBe('package-with-main'); + }); it("registers any deserializers specified in the package's package.json", () => { - atom.packages.loadPackage('package-with-deserializers') + atom.packages.loadPackage('package-with-deserializers'); - const state1 = { deserializer: 'Deserializer1', a: 'b' } + const state1 = { deserializer: 'Deserializer1', a: 'b' }; expect(atom.deserializers.deserialize(state1)).toEqual({ wasDeserializedBy: 'deserializeMethod1', state: state1 - }) + }); - const state2 = { deserializer: 'Deserializer2', c: 'd' } + const state2 = { deserializer: 'Deserializer2', c: 'd' }; expect(atom.deserializers.deserialize(state2)).toEqual({ wasDeserializedBy: 'deserializeMethod2', state: state2 - }) - }) + }); + }); it('early-activates any atom.directory-provider or atom.repository-provider services that the package provide', () => { - jasmine.useRealClock() + jasmine.useRealClock(); - const providers = [] + const providers = []; atom.packages.serviceHub.consume( 'atom.directory-provider', '^0.1.0', provider => providers.push(provider) - ) + ); - atom.packages.loadPackage('package-with-directory-provider') + atom.packages.loadPackage('package-with-directory-provider'); expect(providers.map(p => p.name)).toEqual([ 'directory provider from package-with-directory-provider' - ]) - }) + ]); + }); describe("when there are view providers specified in the package's package.json", () => { - const model1 = { worksWithViewProvider1: true } - const model2 = { worksWithViewProvider2: true } + const model1 = { worksWithViewProvider1: true }; + const model2 = { worksWithViewProvider2: true }; afterEach(async () => { - await atom.packages.deactivatePackage('package-with-view-providers') - atom.packages.unloadPackage('package-with-view-providers') - }) + await atom.packages.deactivatePackage('package-with-view-providers'); + atom.packages.unloadPackage('package-with-view-providers'); + }); it('does not load the view providers immediately', () => { - const pack = atom.packages.loadPackage('package-with-view-providers') - expect(pack.mainModule).toBeNull() + const pack = atom.packages.loadPackage('package-with-view-providers'); + expect(pack.mainModule).toBeNull(); - expect(() => atom.views.getView(model1)).toThrow() - expect(() => atom.views.getView(model2)).toThrow() - }) + expect(() => atom.views.getView(model1)).toThrow(); + expect(() => atom.views.getView(model2)).toThrow(); + }); it('registers the view providers when the package is activated', async () => { - atom.packages.loadPackage('package-with-view-providers') + atom.packages.loadPackage('package-with-view-providers'); - await atom.packages.activatePackage('package-with-view-providers') + await atom.packages.activatePackage('package-with-view-providers'); - const element1 = atom.views.getView(model1) - expect(element1 instanceof HTMLDivElement).toBe(true) - expect(element1.dataset.createdBy).toBe('view-provider-1') + const element1 = atom.views.getView(model1); + expect(element1 instanceof HTMLDivElement).toBe(true); + expect(element1.dataset.createdBy).toBe('view-provider-1'); - const element2 = atom.views.getView(model2) - expect(element2 instanceof HTMLDivElement).toBe(true) - expect(element2.dataset.createdBy).toBe('view-provider-2') - }) + const element2 = atom.views.getView(model2); + expect(element2 instanceof HTMLDivElement).toBe(true); + expect(element2.dataset.createdBy).toBe('view-provider-2'); + }); it("registers the view providers when any of the package's deserializers are used", () => { - atom.packages.loadPackage('package-with-view-providers') + atom.packages.loadPackage('package-with-view-providers'); - spyOn(atom.views, 'addViewProvider').andCallThrough() + spyOn(atom.views, 'addViewProvider').andCallThrough(); atom.deserializers.deserialize({ deserializer: 'DeserializerFromPackageWithViewProviders', a: 'b' - }) - expect(atom.views.addViewProvider.callCount).toBe(2) + }); + expect(atom.views.addViewProvider.callCount).toBe(2); atom.deserializers.deserialize({ deserializer: 'DeserializerFromPackageWithViewProviders', a: 'b' - }) - expect(atom.views.addViewProvider.callCount).toBe(2) + }); + expect(atom.views.addViewProvider.callCount).toBe(2); - const element1 = atom.views.getView(model1) - expect(element1 instanceof HTMLDivElement).toBe(true) - expect(element1.dataset.createdBy).toBe('view-provider-1') + const element1 = atom.views.getView(model1); + expect(element1 instanceof HTMLDivElement).toBe(true); + expect(element1.dataset.createdBy).toBe('view-provider-1'); - const element2 = atom.views.getView(model2) - expect(element2 instanceof HTMLDivElement).toBe(true) - expect(element2.dataset.createdBy).toBe('view-provider-2') - }) - }) + const element2 = atom.views.getView(model2); + expect(element2 instanceof HTMLDivElement).toBe(true); + expect(element2.dataset.createdBy).toBe('view-provider-2'); + }); + }); it("registers the config schema in the package's metadata, if present", () => { - let pack = atom.packages.loadPackage('package-with-json-config-schema') + let pack = atom.packages.loadPackage('package-with-json-config-schema'); expect(atom.config.getSchema('package-with-json-config-schema')).toEqual({ type: 'object', properties: { a: { type: 'number', default: 5 }, b: { type: 'string', default: 'five' } } - }) + }); - expect(pack.mainModule).toBeNull() + expect(pack.mainModule).toBeNull(); - atom.packages.unloadPackage('package-with-json-config-schema') - atom.config.clear() + atom.packages.unloadPackage('package-with-json-config-schema'); + atom.config.clear(); - pack = atom.packages.loadPackage('package-with-json-config-schema') + pack = atom.packages.loadPackage('package-with-json-config-schema'); expect(atom.config.getSchema('package-with-json-config-schema')).toEqual({ type: 'object', properties: { a: { type: 'number', default: 5 }, b: { type: 'string', default: 'five' } } - }) - }) + }); + }); describe('when a package does not have deserializers, view providers or a config schema in its package.json', () => { - beforeEach(() => mockLocalStorage()) + beforeEach(() => mockLocalStorage()); it("defers loading the package's main module if the package previously used no Atom APIs when its main module was required", () => { - const pack1 = atom.packages.loadPackage('package-with-main') - expect(pack1.mainModule).toBeDefined() + const pack1 = atom.packages.loadPackage('package-with-main'); + expect(pack1.mainModule).toBeDefined(); - atom.packages.unloadPackage('package-with-main') + atom.packages.unloadPackage('package-with-main'); - const pack2 = atom.packages.loadPackage('package-with-main') - expect(pack2.mainModule).toBeNull() - }) + const pack2 = atom.packages.loadPackage('package-with-main'); + expect(pack2.mainModule).toBeNull(); + }); it("does not defer loading the package's main module if the package previously used Atom APIs when its main module was required", () => { const pack1 = atom.packages.loadPackage( 'package-with-eval-time-api-calls' - ) - expect(pack1.mainModule).toBeDefined() + ); + expect(pack1.mainModule).toBeDefined(); - atom.packages.unloadPackage('package-with-eval-time-api-calls') + atom.packages.unloadPackage('package-with-eval-time-api-calls'); const pack2 = atom.packages.loadPackage( 'package-with-eval-time-api-calls' - ) - expect(pack2.mainModule).not.toBeNull() - }) - }) - }) + ); + expect(pack2.mainModule).not.toBeNull(); + }); + }); + }); describe('::loadAvailablePackage(availablePackage)', () => { describe('if the package was preloaded', () => { it('adds the package path to the module cache', () => { const availablePackage = atom.packages .getAvailablePackages() - .find(p => p.name === 'spell-check') - availablePackage.isBundled = true + .find(p => p.name === 'spell-check'); + availablePackage.isBundled = true; expect( atom.packages.preloadedPackages[availablePackage.name] - ).toBeUndefined() - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false) + ).toBeUndefined(); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe( + false + ); - const metadata = atom.packages.loadPackageMetadata(availablePackage) + const metadata = atom.packages.loadPackageMetadata(availablePackage); atom.packages.preloadPackage(availablePackage.name, { rootDirPath: path.relative( atom.packages.resourcePath, availablePackage.path ), metadata - }) - atom.packages.loadAvailablePackage(availablePackage) - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(true) + }); + atom.packages.loadAvailablePackage(availablePackage); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(true); expect(ModuleCache.add).toHaveBeenCalledWith( availablePackage.path, metadata - ) - }) + ); + }); it('deactivates it if it had been disabled', () => { const availablePackage = atom.packages .getAvailablePackages() - .find(p => p.name === 'spell-check') - availablePackage.isBundled = true + .find(p => p.name === 'spell-check'); + availablePackage.isBundled = true; expect( atom.packages.preloadedPackages[availablePackage.name] - ).toBeUndefined() - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false) + ).toBeUndefined(); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe( + false + ); - const metadata = atom.packages.loadPackageMetadata(availablePackage) + const metadata = atom.packages.loadPackageMetadata(availablePackage); const preloadedPackage = atom.packages.preloadPackage( availablePackage.name, { @@ -395,32 +405,36 @@ describe('PackageManager', () => { ), metadata } - ) - expect(preloadedPackage.keymapActivated).toBe(true) - expect(preloadedPackage.settingsActivated).toBe(true) - expect(preloadedPackage.menusActivated).toBe(true) + ); + expect(preloadedPackage.keymapActivated).toBe(true); + expect(preloadedPackage.settingsActivated).toBe(true); + expect(preloadedPackage.menusActivated).toBe(true); atom.packages.loadAvailablePackage( availablePackage, new Set([availablePackage.name]) - ) - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false) - expect(preloadedPackage.keymapActivated).toBe(false) - expect(preloadedPackage.settingsActivated).toBe(false) - expect(preloadedPackage.menusActivated).toBe(false) - }) + ); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe( + false + ); + expect(preloadedPackage.keymapActivated).toBe(false); + expect(preloadedPackage.settingsActivated).toBe(false); + expect(preloadedPackage.menusActivated).toBe(false); + }); it('deactivates it and reloads the new one if trying to load the same package outside of the bundle', () => { const availablePackage = atom.packages .getAvailablePackages() - .find(p => p.name === 'spell-check') - availablePackage.isBundled = true + .find(p => p.name === 'spell-check'); + availablePackage.isBundled = true; expect( atom.packages.preloadedPackages[availablePackage.name] - ).toBeUndefined() - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false) + ).toBeUndefined(); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe( + false + ); - const metadata = atom.packages.loadPackageMetadata(availablePackage) + const metadata = atom.packages.loadPackageMetadata(availablePackage); const preloadedPackage = atom.packages.preloadPackage( availablePackage.name, { @@ -430,53 +444,53 @@ describe('PackageManager', () => { ), metadata } - ) - expect(preloadedPackage.keymapActivated).toBe(true) - expect(preloadedPackage.settingsActivated).toBe(true) - expect(preloadedPackage.menusActivated).toBe(true) + ); + expect(preloadedPackage.keymapActivated).toBe(true); + expect(preloadedPackage.settingsActivated).toBe(true); + expect(preloadedPackage.menusActivated).toBe(true); - availablePackage.isBundled = false - atom.packages.loadAvailablePackage(availablePackage) - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(true) - expect(preloadedPackage.keymapActivated).toBe(false) - expect(preloadedPackage.settingsActivated).toBe(false) - expect(preloadedPackage.menusActivated).toBe(false) - }) - }) + availablePackage.isBundled = false; + atom.packages.loadAvailablePackage(availablePackage); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(true); + expect(preloadedPackage.keymapActivated).toBe(false); + expect(preloadedPackage.settingsActivated).toBe(false); + expect(preloadedPackage.menusActivated).toBe(false); + }); + }); describe('if the package was not preloaded', () => { it('adds the package path to the module cache', () => { const availablePackage = atom.packages .getAvailablePackages() - .find(p => p.name === 'spell-check') - availablePackage.isBundled = true - const metadata = atom.packages.loadPackageMetadata(availablePackage) - atom.packages.loadAvailablePackage(availablePackage) + .find(p => p.name === 'spell-check'); + availablePackage.isBundled = true; + const metadata = atom.packages.loadPackageMetadata(availablePackage); + atom.packages.loadAvailablePackage(availablePackage); expect(ModuleCache.add).toHaveBeenCalledWith( availablePackage.path, metadata - ) - }) - }) - }) + ); + }); + }); + }); describe('preloading', () => { it('requires the main module, loads the config schema and activates keymaps, menus and settings without reactivating them during package activation', () => { const availablePackage = atom.packages .getAvailablePackages() - .find(p => p.name === 'spell-check') - availablePackage.isBundled = true - const metadata = atom.packages.loadPackageMetadata(availablePackage) + .find(p => p.name === 'spell-check'); + availablePackage.isBundled = true; + const metadata = atom.packages.loadPackageMetadata(availablePackage); expect( atom.packages.preloadedPackages[availablePackage.name] - ).toBeUndefined() - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false) + ).toBeUndefined(); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false); - atom.packages.packagesCache = {} + atom.packages.packagesCache = {}; atom.packages.packagesCache[availablePackage.name] = { main: path.join(availablePackage.path, metadata.main), grammarPaths: [] - } + }; const preloadedPackage = atom.packages.preloadPackage( availablePackage.name, { @@ -486,51 +500,51 @@ describe('PackageManager', () => { ), metadata } - ) - expect(preloadedPackage.keymapActivated).toBe(true) - expect(preloadedPackage.settingsActivated).toBe(true) - expect(preloadedPackage.menusActivated).toBe(true) - expect(preloadedPackage.mainModule).toBeTruthy() - expect(preloadedPackage.configSchemaRegisteredOnLoad).toBeTruthy() + ); + expect(preloadedPackage.keymapActivated).toBe(true); + expect(preloadedPackage.settingsActivated).toBe(true); + expect(preloadedPackage.menusActivated).toBe(true); + expect(preloadedPackage.mainModule).toBeTruthy(); + expect(preloadedPackage.configSchemaRegisteredOnLoad).toBeTruthy(); - spyOn(atom.keymaps, 'add') - spyOn(atom.menu, 'add') - spyOn(atom.contextMenu, 'add') - spyOn(atom.config, 'setSchema') + spyOn(atom.keymaps, 'add'); + spyOn(atom.menu, 'add'); + spyOn(atom.contextMenu, 'add'); + spyOn(atom.config, 'setSchema'); - atom.packages.loadAvailablePackage(availablePackage) + atom.packages.loadAvailablePackage(availablePackage); expect(preloadedPackage.getMainModulePath()).toBe( path.join(availablePackage.path, metadata.main) - ) + ); - atom.packages.activatePackage(availablePackage.name) - expect(atom.keymaps.add).not.toHaveBeenCalled() - expect(atom.menu.add).not.toHaveBeenCalled() - expect(atom.contextMenu.add).not.toHaveBeenCalled() - expect(atom.config.setSchema).not.toHaveBeenCalled() - expect(preloadedPackage.keymapActivated).toBe(true) - expect(preloadedPackage.settingsActivated).toBe(true) - expect(preloadedPackage.menusActivated).toBe(true) - expect(preloadedPackage.mainModule).toBeTruthy() - expect(preloadedPackage.configSchemaRegisteredOnLoad).toBeTruthy() - }) + atom.packages.activatePackage(availablePackage.name); + expect(atom.keymaps.add).not.toHaveBeenCalled(); + expect(atom.menu.add).not.toHaveBeenCalled(); + expect(atom.contextMenu.add).not.toHaveBeenCalled(); + expect(atom.config.setSchema).not.toHaveBeenCalled(); + expect(preloadedPackage.keymapActivated).toBe(true); + expect(preloadedPackage.settingsActivated).toBe(true); + expect(preloadedPackage.menusActivated).toBe(true); + expect(preloadedPackage.mainModule).toBeTruthy(); + expect(preloadedPackage.configSchemaRegisteredOnLoad).toBeTruthy(); + }); it('deactivates disabled keymaps during package activation', () => { const availablePackage = atom.packages .getAvailablePackages() - .find(p => p.name === 'spell-check') - availablePackage.isBundled = true - const metadata = atom.packages.loadPackageMetadata(availablePackage) + .find(p => p.name === 'spell-check'); + availablePackage.isBundled = true; + const metadata = atom.packages.loadPackageMetadata(availablePackage); expect( atom.packages.preloadedPackages[availablePackage.name] - ).toBeUndefined() - expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false) + ).toBeUndefined(); + expect(atom.packages.isPackageLoaded(availablePackage.name)).toBe(false); - atom.packages.packagesCache = {} + atom.packages.packagesCache = {}; atom.packages.packagesCache[availablePackage.name] = { main: path.join(availablePackage.path, metadata.main), grammarPaths: [] - } + }; const preloadedPackage = atom.packages.preloadPackage( availablePackage.name, { @@ -540,1226 +554,1238 @@ describe('PackageManager', () => { ), metadata } - ) - expect(preloadedPackage.keymapActivated).toBe(true) - expect(preloadedPackage.settingsActivated).toBe(true) - expect(preloadedPackage.menusActivated).toBe(true) + ); + expect(preloadedPackage.keymapActivated).toBe(true); + expect(preloadedPackage.settingsActivated).toBe(true); + expect(preloadedPackage.menusActivated).toBe(true); - atom.packages.loadAvailablePackage(availablePackage) + atom.packages.loadAvailablePackage(availablePackage); atom.config.set('core.packagesWithKeymapsDisabled', [ availablePackage.name - ]) - atom.packages.activatePackage(availablePackage.name) + ]); + atom.packages.activatePackage(availablePackage.name); - expect(preloadedPackage.keymapActivated).toBe(false) - expect(preloadedPackage.settingsActivated).toBe(true) - expect(preloadedPackage.menusActivated).toBe(true) - }) - }) + expect(preloadedPackage.keymapActivated).toBe(false); + expect(preloadedPackage.settingsActivated).toBe(true); + expect(preloadedPackage.menusActivated).toBe(true); + }); + }); describe('::unloadPackage(name)', () => { describe('when the package is active', () => { it('throws an error', async () => { - const pack = await atom.packages.activatePackage('package-with-main') - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() + const pack = await atom.packages.activatePackage('package-with-main'); + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy(); + expect(atom.packages.isPackageActive(pack.name)).toBeTruthy(); - expect(() => atom.packages.unloadPackage(pack.name)).toThrow() - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() - }) - }) + expect(() => atom.packages.unloadPackage(pack.name)).toThrow(); + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy(); + expect(atom.packages.isPackageActive(pack.name)).toBeTruthy(); + }); + }); describe('when the package is not loaded', () => { it('throws an error', () => { - expect(atom.packages.isPackageLoaded('unloaded')).toBeFalsy() - expect(() => atom.packages.unloadPackage('unloaded')).toThrow() - expect(atom.packages.isPackageLoaded('unloaded')).toBeFalsy() - }) - }) + expect(atom.packages.isPackageLoaded('unloaded')).toBeFalsy(); + expect(() => atom.packages.unloadPackage('unloaded')).toThrow(); + expect(atom.packages.isPackageLoaded('unloaded')).toBeFalsy(); + }); + }); describe('when the package is loaded', () => { it('no longers reports it as being loaded', () => { - const pack = atom.packages.loadPackage('package-with-main') - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - atom.packages.unloadPackage(pack.name) - expect(atom.packages.isPackageLoaded(pack.name)).toBeFalsy() - }) - }) + const pack = atom.packages.loadPackage('package-with-main'); + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy(); + atom.packages.unloadPackage(pack.name); + expect(atom.packages.isPackageLoaded(pack.name)).toBeFalsy(); + }); + }); it('invokes ::onDidUnloadPackage listeners with the unloaded package', () => { - atom.packages.loadPackage('package-with-main') - let unloadedPackage + atom.packages.loadPackage('package-with-main'); + let unloadedPackage; atom.packages.onDidUnloadPackage(pack => { - unloadedPackage = pack - }) - atom.packages.unloadPackage('package-with-main') - expect(unloadedPackage.name).toBe('package-with-main') - }) - }) + unloadedPackage = pack; + }); + atom.packages.unloadPackage('package-with-main'); + expect(unloadedPackage.name).toBe('package-with-main'); + }); + }); describe('::activatePackage(id)', () => { describe('when called multiple times', () => { it('it only calls activate on the package once', async () => { - spyOn(Package.prototype, 'activateNow').andCallThrough() - await atom.packages.activatePackage('package-with-index') - await atom.packages.activatePackage('package-with-index') - await atom.packages.activatePackage('package-with-index') + spyOn(Package.prototype, 'activateNow').andCallThrough(); + await atom.packages.activatePackage('package-with-index'); + await atom.packages.activatePackage('package-with-index'); + await atom.packages.activatePackage('package-with-index'); - expect(Package.prototype.activateNow.callCount).toBe(1) - }) - }) + expect(Package.prototype.activateNow.callCount).toBe(1); + }); + }); describe('when the package has a main module', () => { describe('when the metadata specifies a main module path˜', () => { it('requires the module at the specified path', async () => { - const mainModule = require('./fixtures/packages/package-with-main/main-module') - spyOn(mainModule, 'activate') + const mainModule = require('./fixtures/packages/package-with-main/main-module'); + spyOn(mainModule, 'activate'); - const pack = await atom.packages.activatePackage('package-with-main') - expect(mainModule.activate).toHaveBeenCalled() - expect(pack.mainModule).toBe(mainModule) - }) - }) + const pack = await atom.packages.activatePackage('package-with-main'); + expect(mainModule.activate).toHaveBeenCalled(); + expect(pack.mainModule).toBe(mainModule); + }); + }); describe('when the metadata does not specify a main module', () => { it('requires index.coffee', async () => { - const indexModule = require('./fixtures/packages/package-with-index/index') - spyOn(indexModule, 'activate') + const indexModule = require('./fixtures/packages/package-with-index/index'); + spyOn(indexModule, 'activate'); - const pack = await atom.packages.activatePackage('package-with-index') - expect(indexModule.activate).toHaveBeenCalled() - expect(pack.mainModule).toBe(indexModule) - }) - }) + const pack = await atom.packages.activatePackage( + 'package-with-index' + ); + expect(indexModule.activate).toHaveBeenCalled(); + expect(pack.mainModule).toBe(indexModule); + }); + }); it('assigns config schema, including defaults when package contains a schema', async () => { expect( atom.config.get('package-with-config-schema.numbers.one') - ).toBeUndefined() + ).toBeUndefined(); - await atom.packages.activatePackage('package-with-config-schema') + await atom.packages.activatePackage('package-with-config-schema'); expect(atom.config.get('package-with-config-schema.numbers.one')).toBe( 1 - ) + ); expect(atom.config.get('package-with-config-schema.numbers.two')).toBe( 2 - ) + ); expect( atom.config.set('package-with-config-schema.numbers.one', 'nope') - ).toBe(false) + ).toBe(false); expect( atom.config.set('package-with-config-schema.numbers.one', '10') - ).toBe(true) + ).toBe(true); expect(atom.config.get('package-with-config-schema.numbers.one')).toBe( 10 - ) - }) + ); + }); describe('when the package metadata includes `activationCommands`', () => { - let mainModule, promise, workspaceCommandListener, registration + let mainModule, promise, workspaceCommandListener, registration; beforeEach(() => { - jasmine.attachToDOM(atom.workspace.getElement()) - mainModule = require('./fixtures/packages/package-with-activation-commands/index') - mainModule.activationCommandCallCount = 0 - spyOn(mainModule, 'activate').andCallThrough() - spyOn(Package.prototype, 'requireMainModule').andCallThrough() + jasmine.attachToDOM(atom.workspace.getElement()); + mainModule = require('./fixtures/packages/package-with-activation-commands/index'); + mainModule.activationCommandCallCount = 0; + spyOn(mainModule, 'activate').andCallThrough(); + spyOn(Package.prototype, 'requireMainModule').andCallThrough(); workspaceCommandListener = jasmine.createSpy( 'workspaceCommandListener' - ) + ); registration = atom.commands.add( '.workspace', 'activation-command', workspaceCommandListener - ) + ); promise = atom.packages.activatePackage( 'package-with-activation-commands' - ) - }) + ); + }); afterEach(() => { if (registration) { - registration.dispose() + registration.dispose(); } - mainModule = null - }) + mainModule = null; + }); it('defers requiring/activating the main module until an activation event bubbles to the root view', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0) + expect(Package.prototype.requireMainModule.callCount).toBe(0); atom.workspace .getElement() .dispatchEvent( new CustomEvent('activation-command', { bubbles: true }) - ) + ); - await promise - expect(Package.prototype.requireMainModule.callCount).toBe(1) - }) + await promise; + expect(Package.prototype.requireMainModule.callCount).toBe(1); + }); it('triggers the activation event on all handlers registered during activation', async () => { - await atom.workspace.open() + await atom.workspace.open(); const editorElement = atom.workspace .getActiveTextEditor() - .getElement() + .getElement(); const editorCommandListener = jasmine.createSpy( 'editorCommandListener' - ) + ); atom.commands.add( 'atom-text-editor', 'activation-command', editorCommandListener - ) + ); - atom.commands.dispatch(editorElement, 'activation-command') - expect(mainModule.activate.callCount).toBe(1) - expect(mainModule.activationCommandCallCount).toBe(1) - expect(editorCommandListener.callCount).toBe(1) - expect(workspaceCommandListener.callCount).toBe(1) + atom.commands.dispatch(editorElement, 'activation-command'); + expect(mainModule.activate.callCount).toBe(1); + expect(mainModule.activationCommandCallCount).toBe(1); + expect(editorCommandListener.callCount).toBe(1); + expect(workspaceCommandListener.callCount).toBe(1); - atom.commands.dispatch(editorElement, 'activation-command') - expect(mainModule.activationCommandCallCount).toBe(2) - expect(editorCommandListener.callCount).toBe(2) - expect(workspaceCommandListener.callCount).toBe(2) - expect(mainModule.activate.callCount).toBe(1) - }) + atom.commands.dispatch(editorElement, 'activation-command'); + expect(mainModule.activationCommandCallCount).toBe(2); + expect(editorCommandListener.callCount).toBe(2); + expect(workspaceCommandListener.callCount).toBe(2); + expect(mainModule.activate.callCount).toBe(1); + }); it('activates the package immediately when the events are empty', async () => { - mainModule = require('./fixtures/packages/package-with-empty-activation-commands/index') - spyOn(mainModule, 'activate').andCallThrough() + mainModule = require('./fixtures/packages/package-with-empty-activation-commands/index'); + spyOn(mainModule, 'activate').andCallThrough(); atom.packages.activatePackage( 'package-with-empty-activation-commands' - ) + ); - expect(mainModule.activate.callCount).toBe(1) - }) + expect(mainModule.activate.callCount).toBe(1); + }); it('adds a notification when the activation commands are invalid', () => { - spyOn(atom, 'inSpecMode').andReturn(false) - const addErrorHandler = jasmine.createSpy() - atom.notifications.onDidAddNotification(addErrorHandler) + spyOn(atom, 'inSpecMode').andReturn(false); + const addErrorHandler = jasmine.createSpy(); + atom.notifications.onDidAddNotification(addErrorHandler); expect(() => atom.packages.activatePackage( 'package-with-invalid-activation-commands' ) - ).not.toThrow() - expect(addErrorHandler.callCount).toBe(1) + ).not.toThrow(); + expect(addErrorHandler.callCount).toBe(1); expect(addErrorHandler.argsForCall[0][0].message).toContain( 'Failed to activate the package-with-invalid-activation-commands package' - ) + ); expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( 'package-with-invalid-activation-commands' - ) - }) + ); + }); it('adds a notification when the context menu is invalid', () => { - spyOn(atom, 'inSpecMode').andReturn(false) - const addErrorHandler = jasmine.createSpy() - atom.notifications.onDidAddNotification(addErrorHandler) + spyOn(atom, 'inSpecMode').andReturn(false); + const addErrorHandler = jasmine.createSpy(); + atom.notifications.onDidAddNotification(addErrorHandler); expect(() => atom.packages.activatePackage('package-with-invalid-context-menu') - ).not.toThrow() - expect(addErrorHandler.callCount).toBe(1) + ).not.toThrow(); + expect(addErrorHandler.callCount).toBe(1); expect(addErrorHandler.argsForCall[0][0].message).toContain( 'Failed to activate the package-with-invalid-context-menu package' - ) + ); expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( 'package-with-invalid-context-menu' - ) - }) + ); + }); it('adds a notification when the grammar is invalid', async () => { - let notificationEvent + let notificationEvent; await new Promise(resolve => { const subscription = atom.notifications.onDidAddNotification( event => { - notificationEvent = event - subscription.dispose() - resolve() + notificationEvent = event; + subscription.dispose(); + resolve(); } - ) + ); - atom.packages.activatePackage('package-with-invalid-grammar') - }) + atom.packages.activatePackage('package-with-invalid-grammar'); + }); expect(notificationEvent.message).toContain( 'Failed to load a package-with-invalid-grammar package grammar' - ) + ); expect(notificationEvent.options.packageName).toEqual( 'package-with-invalid-grammar' - ) - }) + ); + }); it('adds a notification when the settings are invalid', async () => { - let notificationEvent + let notificationEvent; await new Promise(resolve => { const subscription = atom.notifications.onDidAddNotification( event => { - notificationEvent = event - subscription.dispose() - resolve() + notificationEvent = event; + subscription.dispose(); + resolve(); } - ) + ); - atom.packages.activatePackage('package-with-invalid-settings') - }) + atom.packages.activatePackage('package-with-invalid-settings'); + }); expect(notificationEvent.message).toContain( 'Failed to load the package-with-invalid-settings package settings' - ) + ); expect(notificationEvent.options.packageName).toEqual( 'package-with-invalid-settings' - ) - }) - }) - }) + ); + }); + }); + }); describe('when the package metadata includes `activationHooks`', () => { - let mainModule, promise + let mainModule, promise; beforeEach(() => { - mainModule = require('./fixtures/packages/package-with-activation-hooks/index') - spyOn(mainModule, 'activate').andCallThrough() - spyOn(Package.prototype, 'requireMainModule').andCallThrough() - }) + mainModule = require('./fixtures/packages/package-with-activation-hooks/index'); + spyOn(mainModule, 'activate').andCallThrough(); + spyOn(Package.prototype, 'requireMainModule').andCallThrough(); + }); it('defers requiring/activating the main module until an triggering of an activation hook occurs', async () => { - promise = atom.packages.activatePackage('package-with-activation-hooks') - expect(Package.prototype.requireMainModule.callCount).toBe(0) - atom.packages.triggerActivationHook('language-fictitious:grammar-used') - atom.packages.triggerDeferredActivationHooks() + promise = atom.packages.activatePackage( + 'package-with-activation-hooks' + ); + expect(Package.prototype.requireMainModule.callCount).toBe(0); + atom.packages.triggerActivationHook('language-fictitious:grammar-used'); + atom.packages.triggerDeferredActivationHooks(); - await promise - expect(Package.prototype.requireMainModule.callCount).toBe(1) - }) + await promise; + expect(Package.prototype.requireMainModule.callCount).toBe(1); + }); it('does not double register activation hooks when deactivating and reactivating', async () => { - promise = atom.packages.activatePackage('package-with-activation-hooks') - expect(mainModule.activate.callCount).toBe(0) - atom.packages.triggerActivationHook('language-fictitious:grammar-used') - atom.packages.triggerDeferredActivationHooks() + promise = atom.packages.activatePackage( + 'package-with-activation-hooks' + ); + expect(mainModule.activate.callCount).toBe(0); + atom.packages.triggerActivationHook('language-fictitious:grammar-used'); + atom.packages.triggerDeferredActivationHooks(); - await promise - expect(mainModule.activate.callCount).toBe(1) + await promise; + expect(mainModule.activate.callCount).toBe(1); - await atom.packages.deactivatePackage('package-with-activation-hooks') + await atom.packages.deactivatePackage('package-with-activation-hooks'); - promise = atom.packages.activatePackage('package-with-activation-hooks') - atom.packages.triggerActivationHook('language-fictitious:grammar-used') - atom.packages.triggerDeferredActivationHooks() + promise = atom.packages.activatePackage( + 'package-with-activation-hooks' + ); + atom.packages.triggerActivationHook('language-fictitious:grammar-used'); + atom.packages.triggerDeferredActivationHooks(); - await promise - expect(mainModule.activate.callCount).toBe(2) - }) + await promise; + expect(mainModule.activate.callCount).toBe(2); + }); it('activates the package immediately when activationHooks is empty', async () => { - mainModule = require('./fixtures/packages/package-with-empty-activation-hooks/index') - spyOn(mainModule, 'activate').andCallThrough() + mainModule = require('./fixtures/packages/package-with-empty-activation-hooks/index'); + spyOn(mainModule, 'activate').andCallThrough(); - expect(Package.prototype.requireMainModule.callCount).toBe(0) + expect(Package.prototype.requireMainModule.callCount).toBe(0); await atom.packages.activatePackage( 'package-with-empty-activation-hooks' - ) - expect(mainModule.activate.callCount).toBe(1) - expect(Package.prototype.requireMainModule.callCount).toBe(1) - }) + ); + expect(mainModule.activate.callCount).toBe(1); + expect(Package.prototype.requireMainModule.callCount).toBe(1); + }); it('activates the package immediately if the activation hook had already been triggered', async () => { - atom.packages.triggerActivationHook('language-fictitious:grammar-used') - atom.packages.triggerDeferredActivationHooks() - expect(Package.prototype.requireMainModule.callCount).toBe(0) + atom.packages.triggerActivationHook('language-fictitious:grammar-used'); + atom.packages.triggerDeferredActivationHooks(); + expect(Package.prototype.requireMainModule.callCount).toBe(0); - await atom.packages.activatePackage('package-with-activation-hooks') - expect(Package.prototype.requireMainModule.callCount).toBe(1) - }) - }) + await atom.packages.activatePackage('package-with-activation-hooks'); + expect(Package.prototype.requireMainModule.callCount).toBe(1); + }); + }); describe('when the package has no main module', () => { it('does not throw an exception', () => { - spyOn(console, 'error') - spyOn(console, 'warn').andCallThrough() + spyOn(console, 'error'); + spyOn(console, 'warn').andCallThrough(); expect(() => atom.packages.activatePackage('package-without-module') - ).not.toThrow() - expect(console.error).not.toHaveBeenCalled() - expect(console.warn).not.toHaveBeenCalled() - }) - }) + ).not.toThrow(); + expect(console.error).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + }); + }); describe('when the package does not export an activate function', () => { it('activates the package and does not throw an exception or log a warning', async () => { - spyOn(console, 'warn') - await atom.packages.activatePackage('package-with-no-activate') - expect(console.warn).not.toHaveBeenCalled() - }) - }) + spyOn(console, 'warn'); + await atom.packages.activatePackage('package-with-no-activate'); + expect(console.warn).not.toHaveBeenCalled(); + }); + }); it("passes the activate method the package's previously serialized state if it exists", async () => { const pack = await atom.packages.activatePackage( 'package-with-serialization' - ) - expect(pack.mainModule.someNumber).not.toBe(77) - pack.mainModule.someNumber = 77 - atom.packages.serializePackage('package-with-serialization') - await atom.packages.deactivatePackage('package-with-serialization') + ); + expect(pack.mainModule.someNumber).not.toBe(77); + pack.mainModule.someNumber = 77; + atom.packages.serializePackage('package-with-serialization'); + await atom.packages.deactivatePackage('package-with-serialization'); - spyOn(pack.mainModule, 'activate').andCallThrough() - await atom.packages.activatePackage('package-with-serialization') - expect(pack.mainModule.activate).toHaveBeenCalledWith({ someNumber: 77 }) - }) + spyOn(pack.mainModule, 'activate').andCallThrough(); + await atom.packages.activatePackage('package-with-serialization'); + expect(pack.mainModule.activate).toHaveBeenCalledWith({ someNumber: 77 }); + }); it('invokes ::onDidActivatePackage listeners with the activated package', async () => { - let activatedPackage + let activatedPackage; atom.packages.onDidActivatePackage(pack => { - activatedPackage = pack - }) + activatedPackage = pack; + }); - await atom.packages.activatePackage('package-with-main') - expect(activatedPackage.name).toBe('package-with-main') - }) + await atom.packages.activatePackage('package-with-main'); + expect(activatedPackage.name).toBe('package-with-main'); + }); describe("when the package's main module throws an error on load", () => { it('adds a notification instead of throwing an exception', () => { - spyOn(atom, 'inSpecMode').andReturn(false) - atom.config.set('core.disabledPackages', []) - const addErrorHandler = jasmine.createSpy() - atom.notifications.onDidAddNotification(addErrorHandler) + spyOn(atom, 'inSpecMode').andReturn(false); + atom.config.set('core.disabledPackages', []); + const addErrorHandler = jasmine.createSpy(); + atom.notifications.onDidAddNotification(addErrorHandler); expect(() => atom.packages.activatePackage('package-that-throws-an-exception') - ).not.toThrow() - expect(addErrorHandler.callCount).toBe(1) + ).not.toThrow(); + expect(addErrorHandler.callCount).toBe(1); expect(addErrorHandler.argsForCall[0][0].message).toContain( 'Failed to load the package-that-throws-an-exception package' - ) + ); expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( 'package-that-throws-an-exception' - ) - }) + ); + }); it('re-throws the exception in test mode', () => { - atom.config.set('core.disabledPackages', []) + atom.config.set('core.disabledPackages', []); expect(() => atom.packages.activatePackage('package-that-throws-an-exception') - ).toThrow('This package throws an exception') - }) - }) + ).toThrow('This package throws an exception'); + }); + }); describe('when the package is not found', () => { it('rejects the promise', async () => { - spyOn(console, 'warn') - atom.config.set('core.disabledPackages', []) + spyOn(console, 'warn'); + atom.config.set('core.disabledPackages', []); try { - await atom.packages.activatePackage('this-doesnt-exist') - expect('Error to be thrown').toBe('') + await atom.packages.activatePackage('this-doesnt-exist'); + expect('Error to be thrown').toBe(''); } catch (error) { - expect(console.warn.callCount).toBe(1) + expect(console.warn.callCount).toBe(1); expect(error.message).toContain( "Failed to load package 'this-doesnt-exist'" - ) + ); } - }) - }) + }); + }); describe('keymap loading', () => { describe("when the metadata does not contain a 'keymaps' manifest", () => { it('loads all the .cson/.json files in the keymaps directory', async () => { - const element1 = createTestElement('test-1') - const element2 = createTestElement('test-2') - const element3 = createTestElement('test-3') + const element1 = createTestElement('test-1'); + const element2 = createTestElement('test-2'); + const element3 = createTestElement('test-3'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 }) - ).toHaveLength(0) + ).toHaveLength(0); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element2 }) - ).toHaveLength(0) + ).toHaveLength(0); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element3 }) - ).toHaveLength(0) + ).toHaveLength(0); - await atom.packages.activatePackage('package-with-keymaps') + await atom.packages.activatePackage('package-with-keymaps'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 })[0].command - ).toBe('test-1') + ).toBe('test-1'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element2 })[0].command - ).toBe('test-2') + ).toBe('test-2'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element3 }) - ).toHaveLength(0) - }) - }) + ).toHaveLength(0); + }); + }); describe("when the metadata contains a 'keymaps' manifest", () => { it('loads only the keymaps specified by the manifest, in the specified order', async () => { - const element1 = createTestElement('test-1') - const element3 = createTestElement('test-3') + const element1 = createTestElement('test-1'); + const element3 = createTestElement('test-3'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 }) - ).toHaveLength(0) + ).toHaveLength(0); - await atom.packages.activatePackage('package-with-keymaps-manifest') + await atom.packages.activatePackage('package-with-keymaps-manifest'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 })[0].command - ).toBe('keymap-1') + ).toBe('keymap-1'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-n', target: element1 })[0].command - ).toBe('keymap-2') + ).toBe('keymap-2'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-y', target: element3 }) - ).toHaveLength(0) - }) - }) + ).toHaveLength(0); + }); + }); describe('when the keymap file is empty', () => { it('does not throw an error on activation', async () => { - await atom.packages.activatePackage('package-with-empty-keymap') + await atom.packages.activatePackage('package-with-empty-keymap'); expect( atom.packages.isPackageActive('package-with-empty-keymap') - ).toBe(true) - }) - }) + ).toBe(true); + }); + }); describe("when the package's keymaps have been disabled", () => { it('does not add the keymaps', async () => { - const element1 = createTestElement('test-1') + const element1 = createTestElement('test-1'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 }) - ).toHaveLength(0) + ).toHaveLength(0); atom.config.set('core.packagesWithKeymapsDisabled', [ 'package-with-keymaps-manifest' - ]) - await atom.packages.activatePackage('package-with-keymaps-manifest') + ]); + await atom.packages.activatePackage('package-with-keymaps-manifest'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 }) - ).toHaveLength(0) - }) - }) + ).toHaveLength(0); + }); + }); describe('when setting core.packagesWithKeymapsDisabled', () => { it("ignores package names in the array that aren't loaded", () => { - atom.packages.observePackagesWithKeymapsDisabled() + atom.packages.observePackagesWithKeymapsDisabled(); expect(() => atom.config.set('core.packagesWithKeymapsDisabled', [ 'package-does-not-exist' ]) - ).not.toThrow() + ).not.toThrow(); expect(() => atom.config.set('core.packagesWithKeymapsDisabled', []) - ).not.toThrow() - }) - }) + ).not.toThrow(); + }); + }); describe("when the package's keymaps are disabled and re-enabled after it is activated", () => { it('removes and re-adds the keymaps', async () => { - const element1 = createTestElement('test-1') - atom.packages.observePackagesWithKeymapsDisabled() + const element1 = createTestElement('test-1'); + atom.packages.observePackagesWithKeymapsDisabled(); - await atom.packages.activatePackage('package-with-keymaps-manifest') + await atom.packages.activatePackage('package-with-keymaps-manifest'); atom.config.set('core.packagesWithKeymapsDisabled', [ 'package-with-keymaps-manifest' - ]) + ]); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 }) - ).toHaveLength(0) + ).toHaveLength(0); - atom.config.set('core.packagesWithKeymapsDisabled', []) + atom.config.set('core.packagesWithKeymapsDisabled', []); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: element1 })[0].command - ).toBe('keymap-1') - }) - }) + ).toBe('keymap-1'); + }); + }); describe('when the package is de-activated and re-activated', () => { - let element, events, userKeymapPath + let element, events, userKeymapPath; beforeEach(() => { - userKeymapPath = path.join(temp.mkdirSync(), 'user-keymaps.cson') - spyOn(atom.keymaps, 'getUserKeymapPath').andReturn(userKeymapPath) + userKeymapPath = path.join(temp.mkdirSync(), 'user-keymaps.cson'); + spyOn(atom.keymaps, 'getUserKeymapPath').andReturn(userKeymapPath); - element = createTestElement('test-1') - jasmine.attachToDOM(element) + element = createTestElement('test-1'); + jasmine.attachToDOM(element); - events = [] - element.addEventListener('user-command', e => events.push(e)) - element.addEventListener('test-1', e => events.push(e)) - }) + events = []; + element.addEventListener('user-command', e => events.push(e)); + element.addEventListener('test-1', e => events.push(e)); + }); afterEach(() => { - element.remove() + element.remove(); // Avoid leaking user keymap subscription - atom.keymaps.watchSubscriptions[userKeymapPath].dispose() - delete atom.keymaps.watchSubscriptions[userKeymapPath] + atom.keymaps.watchSubscriptions[userKeymapPath].dispose(); + delete atom.keymaps.watchSubscriptions[userKeymapPath]; - temp.cleanupSync() - }) + temp.cleanupSync(); + }); it("doesn't override user-defined keymaps", async () => { fs.writeFileSync( userKeymapPath, `".test-1": {"ctrl-z": "user-command"}` - ) - atom.keymaps.loadUserKeymap() + ); + atom.keymaps.loadUserKeymap(); - await atom.packages.activatePackage('package-with-keymaps') + await atom.packages.activatePackage('package-with-keymaps'); atom.keymaps.handleKeyboardEvent( buildKeydownEvent('z', { ctrl: true, target: element }) - ) - expect(events.length).toBe(1) - expect(events[0].type).toBe('user-command') + ); + expect(events.length).toBe(1); + expect(events[0].type).toBe('user-command'); - await atom.packages.deactivatePackage('package-with-keymaps') - await atom.packages.activatePackage('package-with-keymaps') + await atom.packages.deactivatePackage('package-with-keymaps'); + await atom.packages.activatePackage('package-with-keymaps'); atom.keymaps.handleKeyboardEvent( buildKeydownEvent('z', { ctrl: true, target: element }) - ) - expect(events.length).toBe(2) - expect(events[1].type).toBe('user-command') - }) - }) - }) + ); + expect(events.length).toBe(2); + expect(events[1].type).toBe('user-command'); + }); + }); + }); describe('menu loading', () => { beforeEach(() => { - atom.contextMenu.definitions = [] - atom.menu.template = [] - }) + atom.contextMenu.definitions = []; + atom.menu.template = []; + }); describe("when the metadata does not contain a 'menus' manifest", () => { it('loads all the .cson/.json files in the menus directory', async () => { - const element = createTestElement('test-1') - expect(atom.contextMenu.templateForElement(element)).toEqual([]) + const element = createTestElement('test-1'); + expect(atom.contextMenu.templateForElement(element)).toEqual([]); - await atom.packages.activatePackage('package-with-menus') - expect(atom.menu.template.length).toBe(2) - expect(atom.menu.template[0].label).toBe('Second to Last') - expect(atom.menu.template[1].label).toBe('Last') + await atom.packages.activatePackage('package-with-menus'); + expect(atom.menu.template.length).toBe(2); + expect(atom.menu.template[0].label).toBe('Second to Last'); + expect(atom.menu.template[1].label).toBe('Last'); expect(atom.contextMenu.templateForElement(element)[0].label).toBe( 'Menu item 1' - ) + ); expect(atom.contextMenu.templateForElement(element)[1].label).toBe( 'Menu item 2' - ) + ); expect(atom.contextMenu.templateForElement(element)[2].label).toBe( 'Menu item 3' - ) - }) - }) + ); + }); + }); describe("when the metadata contains a 'menus' manifest", () => { it('loads only the menus specified by the manifest, in the specified order', async () => { - const element = createTestElement('test-1') - expect(atom.contextMenu.templateForElement(element)).toEqual([]) + const element = createTestElement('test-1'); + expect(atom.contextMenu.templateForElement(element)).toEqual([]); - await atom.packages.activatePackage('package-with-menus-manifest') - expect(atom.menu.template[0].label).toBe('Second to Last') - expect(atom.menu.template[1].label).toBe('Last') + await atom.packages.activatePackage('package-with-menus-manifest'); + expect(atom.menu.template[0].label).toBe('Second to Last'); + expect(atom.menu.template[1].label).toBe('Last'); expect(atom.contextMenu.templateForElement(element)[0].label).toBe( 'Menu item 2' - ) + ); expect(atom.contextMenu.templateForElement(element)[1].label).toBe( 'Menu item 1' - ) + ); expect( atom.contextMenu.templateForElement(element)[2] - ).toBeUndefined() - }) - }) + ).toBeUndefined(); + }); + }); describe('when the menu file is empty', () => { it('does not throw an error on activation', async () => { - await atom.packages.activatePackage('package-with-empty-menu') + await atom.packages.activatePackage('package-with-empty-menu'); expect(atom.packages.isPackageActive('package-with-empty-menu')).toBe( true - ) - }) - }) - }) + ); + }); + }); + }); describe('stylesheet loading', () => { describe("when the metadata contains a 'styleSheets' manifest", () => { it('loads style sheets from the styles directory as specified by the manifest', async () => { const one = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/1.css' - ) + ); const two = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/2.less' - ) + ); const three = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/3.css' - ) + ); - expect(atom.themes.stylesheetElementForId(one)).toBeNull() - expect(atom.themes.stylesheetElementForId(two)).toBeNull() - expect(atom.themes.stylesheetElementForId(three)).toBeNull() + expect(atom.themes.stylesheetElementForId(one)).toBeNull(); + expect(atom.themes.stylesheetElementForId(two)).toBeNull(); + expect(atom.themes.stylesheetElementForId(three)).toBeNull(); await atom.packages.activatePackage( 'package-with-style-sheets-manifest' - ) - expect(atom.themes.stylesheetElementForId(one)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(two)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(three)).toBeNull() + ); + expect(atom.themes.stylesheetElementForId(one)).not.toBeNull(); + expect(atom.themes.stylesheetElementForId(two)).not.toBeNull(); + expect(atom.themes.stylesheetElementForId(three)).toBeNull(); expect( getComputedStyle(document.querySelector('#jasmine-content')) .fontSize - ).toBe('1px') - }) - }) + ).toBe('1px'); + }); + }); describe("when the metadata does not contain a 'styleSheets' manifest", () => { it('loads all style sheets from the styles directory', async () => { const one = require.resolve( './fixtures/packages/package-with-styles/styles/1.css' - ) + ); const two = require.resolve( './fixtures/packages/package-with-styles/styles/2.less' - ) + ); const three = require.resolve( './fixtures/packages/package-with-styles/styles/3.test-context.css' - ) + ); const four = require.resolve( './fixtures/packages/package-with-styles/styles/4.css' - ) + ); - expect(atom.themes.stylesheetElementForId(one)).toBeNull() - expect(atom.themes.stylesheetElementForId(two)).toBeNull() - expect(atom.themes.stylesheetElementForId(three)).toBeNull() - expect(atom.themes.stylesheetElementForId(four)).toBeNull() + expect(atom.themes.stylesheetElementForId(one)).toBeNull(); + expect(atom.themes.stylesheetElementForId(two)).toBeNull(); + expect(atom.themes.stylesheetElementForId(three)).toBeNull(); + expect(atom.themes.stylesheetElementForId(four)).toBeNull(); - await atom.packages.activatePackage('package-with-styles') - expect(atom.themes.stylesheetElementForId(one)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(two)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(three)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(four)).not.toBeNull() + await atom.packages.activatePackage('package-with-styles'); + expect(atom.themes.stylesheetElementForId(one)).not.toBeNull(); + expect(atom.themes.stylesheetElementForId(two)).not.toBeNull(); + expect(atom.themes.stylesheetElementForId(three)).not.toBeNull(); + expect(atom.themes.stylesheetElementForId(four)).not.toBeNull(); expect( getComputedStyle(document.querySelector('#jasmine-content')) .fontSize - ).toBe('3px') - }) - }) + ).toBe('3px'); + }); + }); it("assigns the stylesheet's context based on the filename", async () => { - await atom.packages.activatePackage('package-with-styles') + await atom.packages.activatePackage('package-with-styles'); - let count = 0 + let count = 0; for (let styleElement of atom.styles.getStyleElements()) { if (styleElement.sourcePath.match(/1.css/)) { - expect(styleElement.context).toBe(undefined) - count++ + expect(styleElement.context).toBe(undefined); + count++; } if (styleElement.sourcePath.match(/2.less/)) { - expect(styleElement.context).toBe(undefined) - count++ + expect(styleElement.context).toBe(undefined); + count++; } if (styleElement.sourcePath.match(/3.test-context.css/)) { - expect(styleElement.context).toBe('test-context') - count++ + expect(styleElement.context).toBe('test-context'); + count++; } if (styleElement.sourcePath.match(/4.css/)) { - expect(styleElement.context).toBe(undefined) - count++ + expect(styleElement.context).toBe(undefined); + count++; } } - expect(count).toBe(4) - }) - }) + expect(count).toBe(4); + }); + }); describe('grammar loading', () => { it("loads the package's grammars", async () => { - await atom.packages.activatePackage('package-with-grammars') - expect(atom.grammars.selectGrammar('a.alot').name).toBe('Alot') - expect(atom.grammars.selectGrammar('a.alittle').name).toBe('Alittle') - }) + await atom.packages.activatePackage('package-with-grammars'); + expect(atom.grammars.selectGrammar('a.alot').name).toBe('Alot'); + expect(atom.grammars.selectGrammar('a.alittle').name).toBe('Alittle'); + }); it('loads any tree-sitter grammars defined in the package', async () => { - atom.config.set('core.useTreeSitterParsers', true) - await atom.packages.activatePackage('package-with-tree-sitter-grammar') - const grammar = atom.grammars.selectGrammar('test.somelang') - expect(grammar.name).toBe('Some Language') - expect(grammar.languageModule.isFakeTreeSitterParser).toBe(true) - }) - }) + atom.config.set('core.useTreeSitterParsers', true); + await atom.packages.activatePackage('package-with-tree-sitter-grammar'); + const grammar = atom.grammars.selectGrammar('test.somelang'); + expect(grammar.name).toBe('Some Language'); + expect(grammar.languageModule.isFakeTreeSitterParser).toBe(true); + }); + }); describe('scoped-property loading', () => { it('loads the scoped properties', async () => { - await atom.packages.activatePackage('package-with-settings') + await atom.packages.activatePackage('package-with-settings'); expect( atom.config.get('editor.increaseIndentPattern', { scope: ['.source.omg'] }) - ).toBe('^a') - }) - }) + ).toBe('^a'); + }); + }); describe('URI handler registration', () => { it("registers the package's specified URI handler", async () => { - const uri = 'atom://package-with-uri-handler/some/url?with=args' - const mod = require('./fixtures/packages/package-with-uri-handler') - spyOn(mod, 'handleURI') - spyOn(atom.packages, 'hasLoadedInitialPackages').andReturn(true) + const uri = 'atom://package-with-uri-handler/some/url?with=args'; + const mod = require('./fixtures/packages/package-with-uri-handler'); + spyOn(mod, 'handleURI'); + spyOn(atom.packages, 'hasLoadedInitialPackages').andReturn(true); const activationPromise = atom.packages.activatePackage( 'package-with-uri-handler' - ) - atom.dispatchURIMessage(uri) - await activationPromise - expect(mod.handleURI).toHaveBeenCalledWith(url.parse(uri, true), uri) - }) - }) + ); + atom.dispatchURIMessage(uri); + await activationPromise; + expect(mod.handleURI).toHaveBeenCalledWith(url.parse(uri, true), uri); + }); + }); describe('service registration', () => { it("registers the package's provided and consumed services", async () => { - const consumerModule = require('./fixtures/packages/package-with-consumed-services') + const consumerModule = require('./fixtures/packages/package-with-consumed-services'); - let firstServiceV3Disposed = false - let firstServiceV4Disposed = false - let secondServiceDisposed = false + let firstServiceV3Disposed = false; + let firstServiceV4Disposed = false; + let secondServiceDisposed = false; spyOn(consumerModule, 'consumeFirstServiceV3').andReturn( new Disposable(() => { - firstServiceV3Disposed = true + firstServiceV3Disposed = true; }) - ) + ); spyOn(consumerModule, 'consumeFirstServiceV4').andReturn( new Disposable(() => { - firstServiceV4Disposed = true + firstServiceV4Disposed = true; }) - ) + ); spyOn(consumerModule, 'consumeSecondService').andReturn( new Disposable(() => { - secondServiceDisposed = true + secondServiceDisposed = true; }) - ) + ); - await atom.packages.activatePackage('package-with-consumed-services') - await atom.packages.activatePackage('package-with-provided-services') - expect(consumerModule.consumeFirstServiceV3.callCount).toBe(1) + await atom.packages.activatePackage('package-with-consumed-services'); + await atom.packages.activatePackage('package-with-provided-services'); + expect(consumerModule.consumeFirstServiceV3.callCount).toBe(1); expect(consumerModule.consumeFirstServiceV3).toHaveBeenCalledWith( 'first-service-v3' - ) + ); expect(consumerModule.consumeFirstServiceV4).toHaveBeenCalledWith( 'first-service-v4' - ) + ); expect(consumerModule.consumeSecondService).toHaveBeenCalledWith( 'second-service' - ) + ); - consumerModule.consumeFirstServiceV3.reset() - consumerModule.consumeFirstServiceV4.reset() - consumerModule.consumeSecondService.reset() + consumerModule.consumeFirstServiceV3.reset(); + consumerModule.consumeFirstServiceV4.reset(); + consumerModule.consumeSecondService.reset(); - await atom.packages.deactivatePackage('package-with-provided-services') - expect(firstServiceV3Disposed).toBe(true) - expect(firstServiceV4Disposed).toBe(true) - expect(secondServiceDisposed).toBe(true) + await atom.packages.deactivatePackage('package-with-provided-services'); + expect(firstServiceV3Disposed).toBe(true); + expect(firstServiceV4Disposed).toBe(true); + expect(secondServiceDisposed).toBe(true); - await atom.packages.deactivatePackage('package-with-consumed-services') - await atom.packages.activatePackage('package-with-provided-services') - expect(consumerModule.consumeFirstServiceV3).not.toHaveBeenCalled() - expect(consumerModule.consumeFirstServiceV4).not.toHaveBeenCalled() - expect(consumerModule.consumeSecondService).not.toHaveBeenCalled() - }) + await atom.packages.deactivatePackage('package-with-consumed-services'); + await atom.packages.activatePackage('package-with-provided-services'); + expect(consumerModule.consumeFirstServiceV3).not.toHaveBeenCalled(); + expect(consumerModule.consumeFirstServiceV4).not.toHaveBeenCalled(); + expect(consumerModule.consumeSecondService).not.toHaveBeenCalled(); + }); it('ignores provided and consumed services that do not exist', async () => { - const addErrorHandler = jasmine.createSpy() - atom.notifications.onDidAddNotification(addErrorHandler) + const addErrorHandler = jasmine.createSpy(); + atom.notifications.onDidAddNotification(addErrorHandler); await atom.packages.activatePackage( 'package-with-missing-consumed-services' - ) + ); await atom.packages.activatePackage( 'package-with-missing-provided-services' - ) + ); expect( atom.packages.isPackageActive( 'package-with-missing-consumed-services' ) - ).toBe(true) + ).toBe(true); expect( atom.packages.isPackageActive( 'package-with-missing-provided-services' ) - ).toBe(true) - expect(addErrorHandler.callCount).toBe(0) - }) - }) - }) + ).toBe(true); + expect(addErrorHandler.callCount).toBe(0); + }); + }); + }); describe('::serialize', () => { it('does not serialize packages that threw an error during activation', async () => { - spyOn(atom, 'inSpecMode').andReturn(false) - spyOn(console, 'warn') + spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(console, 'warn'); const badPack = await atom.packages.activatePackage( 'package-that-throws-on-activate' - ) - spyOn(badPack.mainModule, 'serialize').andCallThrough() + ); + spyOn(badPack.mainModule, 'serialize').andCallThrough(); - atom.packages.serialize() - expect(badPack.mainModule.serialize).not.toHaveBeenCalled() - }) + atom.packages.serialize(); + expect(badPack.mainModule.serialize).not.toHaveBeenCalled(); + }); it("absorbs exceptions that are thrown by the package module's serialize method", async () => { - spyOn(console, 'error') + spyOn(console, 'error'); - await atom.packages.activatePackage('package-with-serialize-error') - await atom.packages.activatePackage('package-with-serialization') - atom.packages.serialize() + await atom.packages.activatePackage('package-with-serialize-error'); + await atom.packages.activatePackage('package-with-serialization'); + atom.packages.serialize(); expect( atom.packages.packageStates['package-with-serialize-error'] - ).toBeUndefined() + ).toBeUndefined(); expect(atom.packages.packageStates['package-with-serialization']).toEqual( { someNumber: 1 } - ) - expect(console.error).toHaveBeenCalled() - }) - }) + ); + expect(console.error).toHaveBeenCalled(); + }); + }); describe('::deactivatePackages()', () => { it('deactivates all packages but does not serialize them', async () => { const pack1 = await atom.packages.activatePackage( 'package-with-deactivate' - ) + ); const pack2 = await atom.packages.activatePackage( 'package-with-serialization' - ) + ); - spyOn(pack1.mainModule, 'deactivate') - spyOn(pack2.mainModule, 'serialize') - await atom.packages.deactivatePackages() - expect(pack1.mainModule.deactivate).toHaveBeenCalled() - expect(pack2.mainModule.serialize).not.toHaveBeenCalled() - }) - }) + spyOn(pack1.mainModule, 'deactivate'); + spyOn(pack2.mainModule, 'serialize'); + await atom.packages.deactivatePackages(); + expect(pack1.mainModule.deactivate).toHaveBeenCalled(); + expect(pack2.mainModule.serialize).not.toHaveBeenCalled(); + }); + }); describe('::deactivatePackage(id)', () => { - afterEach(() => atom.packages.unloadPackages()) + afterEach(() => atom.packages.unloadPackages()); it("calls `deactivate` on the package's main module if activate was successful", async () => { - spyOn(atom, 'inSpecMode').andReturn(false) + spyOn(atom, 'inSpecMode').andReturn(false); const pack = await atom.packages.activatePackage( 'package-with-deactivate' - ) + ); expect( atom.packages.isPackageActive('package-with-deactivate') - ).toBeTruthy() - spyOn(pack.mainModule, 'deactivate').andCallThrough() + ).toBeTruthy(); + spyOn(pack.mainModule, 'deactivate').andCallThrough(); - await atom.packages.deactivatePackage('package-with-deactivate') - expect(pack.mainModule.deactivate).toHaveBeenCalled() - expect(atom.packages.isPackageActive('package-with-module')).toBeFalsy() + await atom.packages.deactivatePackage('package-with-deactivate'); + expect(pack.mainModule.deactivate).toHaveBeenCalled(); + expect(atom.packages.isPackageActive('package-with-module')).toBeFalsy(); - spyOn(console, 'warn') + spyOn(console, 'warn'); const badPack = await atom.packages.activatePackage( 'package-that-throws-on-activate' - ) + ); expect( atom.packages.isPackageActive('package-that-throws-on-activate') - ).toBeTruthy() - spyOn(badPack.mainModule, 'deactivate').andCallThrough() + ).toBeTruthy(); + spyOn(badPack.mainModule, 'deactivate').andCallThrough(); - await atom.packages.deactivatePackage('package-that-throws-on-activate') - expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() + await atom.packages.deactivatePackage('package-that-throws-on-activate'); + expect(badPack.mainModule.deactivate).not.toHaveBeenCalled(); expect( atom.packages.isPackageActive('package-that-throws-on-activate') - ).toBeFalsy() - }) + ).toBeFalsy(); + }); it("absorbs exceptions that are thrown by the package module's deactivate method", async () => { - spyOn(console, 'error') - await atom.packages.activatePackage('package-that-throws-on-deactivate') - await atom.packages.deactivatePackage('package-that-throws-on-deactivate') - expect(console.error).toHaveBeenCalled() - }) + spyOn(console, 'error'); + await atom.packages.activatePackage('package-that-throws-on-deactivate'); + await atom.packages.deactivatePackage( + 'package-that-throws-on-deactivate' + ); + expect(console.error).toHaveBeenCalled(); + }); it("removes the package's grammars", async () => { - await atom.packages.activatePackage('package-with-grammars') - await atom.packages.deactivatePackage('package-with-grammars') - expect(atom.grammars.selectGrammar('a.alot').name).toBe('Null Grammar') - expect(atom.grammars.selectGrammar('a.alittle').name).toBe('Null Grammar') - }) + await atom.packages.activatePackage('package-with-grammars'); + await atom.packages.deactivatePackage('package-with-grammars'); + expect(atom.grammars.selectGrammar('a.alot').name).toBe('Null Grammar'); + expect(atom.grammars.selectGrammar('a.alittle').name).toBe( + 'Null Grammar' + ); + }); it("removes the package's keymaps", async () => { - await atom.packages.activatePackage('package-with-keymaps') - await atom.packages.deactivatePackage('package-with-keymaps') + await atom.packages.activatePackage('package-with-keymaps'); + await atom.packages.deactivatePackage('package-with-keymaps'); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: createTestElement('test-1') }) - ).toHaveLength(0) + ).toHaveLength(0); expect( atom.keymaps.findKeyBindings({ keystrokes: 'ctrl-z', target: createTestElement('test-2') }) - ).toHaveLength(0) - }) + ).toHaveLength(0); + }); it("removes the package's stylesheets", async () => { - await atom.packages.activatePackage('package-with-styles') - await atom.packages.deactivatePackage('package-with-styles') + await atom.packages.activatePackage('package-with-styles'); + await atom.packages.deactivatePackage('package-with-styles'); const one = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/1.css' - ) + ); const two = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/2.less' - ) + ); const three = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/3.css' - ) - expect(atom.themes.stylesheetElementForId(one)).not.toExist() - expect(atom.themes.stylesheetElementForId(two)).not.toExist() - expect(atom.themes.stylesheetElementForId(three)).not.toExist() - }) + ); + expect(atom.themes.stylesheetElementForId(one)).not.toExist(); + expect(atom.themes.stylesheetElementForId(two)).not.toExist(); + expect(atom.themes.stylesheetElementForId(three)).not.toExist(); + }); it("removes the package's scoped-properties", async () => { - await atom.packages.activatePackage('package-with-settings') + await atom.packages.activatePackage('package-with-settings'); expect( atom.config.get('editor.increaseIndentPattern', { scope: ['.source.omg'] }) - ).toBe('^a') + ).toBe('^a'); - await atom.packages.deactivatePackage('package-with-settings') + await atom.packages.deactivatePackage('package-with-settings'); expect( atom.config.get('editor.increaseIndentPattern', { scope: ['.source.omg'] }) - ).toBeUndefined() - }) + ).toBeUndefined(); + }); it('invokes ::onDidDeactivatePackage listeners with the deactivated package', async () => { - await atom.packages.activatePackage('package-with-main') + await atom.packages.activatePackage('package-with-main'); - let deactivatedPackage + let deactivatedPackage; atom.packages.onDidDeactivatePackage(pack => { - deactivatedPackage = pack - }) + deactivatedPackage = pack; + }); - await atom.packages.deactivatePackage('package-with-main') - expect(deactivatedPackage.name).toBe('package-with-main') - }) - }) + await atom.packages.deactivatePackage('package-with-main'); + expect(deactivatedPackage.name).toBe('package-with-main'); + }); + }); describe('::activate()', () => { beforeEach(() => { - spyOn(atom, 'inSpecMode').andReturn(false) - jasmine.snapshotDeprecations() - spyOn(console, 'warn') - atom.packages.loadPackages() + spyOn(atom, 'inSpecMode').andReturn(false); + jasmine.snapshotDeprecations(); + spyOn(console, 'warn'); + atom.packages.loadPackages(); - const loadedPackages = atom.packages.getLoadedPackages() - expect(loadedPackages.length).toBeGreaterThan(0) - }) + const loadedPackages = atom.packages.getLoadedPackages(); + expect(loadedPackages.length).toBeGreaterThan(0); + }); afterEach(async () => { - await atom.packages.deactivatePackages() - atom.packages.unloadPackages() - jasmine.restoreDeprecationsSnapshot() - }) + await atom.packages.deactivatePackages(); + atom.packages.unloadPackages(); + jasmine.restoreDeprecationsSnapshot(); + }); it('sets hasActivatedInitialPackages', async () => { - spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null) - spyOn(atom.packages, 'activatePackages') - expect(atom.packages.hasActivatedInitialPackages()).toBe(false) + spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null); + spyOn(atom.packages, 'activatePackages'); + expect(atom.packages.hasActivatedInitialPackages()).toBe(false); - await atom.packages.activate() - expect(atom.packages.hasActivatedInitialPackages()).toBe(true) - }) + await atom.packages.activate(); + expect(atom.packages.hasActivatedInitialPackages()).toBe(true); + }); it('activates all the packages, and none of the themes', () => { - const packageActivator = spyOn(atom.packages, 'activatePackages') - const themeActivator = spyOn(atom.themes, 'activatePackages') + const packageActivator = spyOn(atom.packages, 'activatePackages'); + const themeActivator = spyOn(atom.themes, 'activatePackages'); - atom.packages.activate() + atom.packages.activate(); - expect(packageActivator).toHaveBeenCalled() - expect(themeActivator).toHaveBeenCalled() + expect(packageActivator).toHaveBeenCalled(); + expect(themeActivator).toHaveBeenCalled(); - const packages = packageActivator.mostRecentCall.args[0] + const packages = packageActivator.mostRecentCall.args[0]; for (let pack of packages) { - expect(['atom', 'textmate']).toContain(pack.getType()) + expect(['atom', 'textmate']).toContain(pack.getType()); } - const themes = themeActivator.mostRecentCall.args[0] - themes.map(theme => expect(['theme']).toContain(theme.getType())) - }) + const themes = themeActivator.mostRecentCall.args[0]; + themes.map(theme => expect(['theme']).toContain(theme.getType())); + }); it('calls callbacks registered with ::onDidActivateInitialPackages', async () => { - const package1 = atom.packages.loadPackage('package-with-main') - const package2 = atom.packages.loadPackage('package-with-index') + const package1 = atom.packages.loadPackage('package-with-main'); + const package2 = atom.packages.loadPackage('package-with-index'); const package3 = atom.packages.loadPackage( 'package-with-activation-commands' - ) + ); spyOn(atom.packages, 'getLoadedPackages').andReturn([ package1, package2, package3 - ]) - spyOn(atom.themes, 'activatePackages') + ]); + spyOn(atom.themes, 'activatePackages'); - atom.packages.activate() + atom.packages.activate(); await new Promise(resolve => atom.packages.onDidActivateInitialPackages(resolve) - ) + ); - jasmine.unspy(atom.packages, 'getLoadedPackages') - expect(atom.packages.getActivePackages().includes(package1)).toBe(true) - expect(atom.packages.getActivePackages().includes(package2)).toBe(true) - expect(atom.packages.getActivePackages().includes(package3)).toBe(false) - }) - }) + jasmine.unspy(atom.packages, 'getLoadedPackages'); + expect(atom.packages.getActivePackages().includes(package1)).toBe(true); + expect(atom.packages.getActivePackages().includes(package2)).toBe(true); + expect(atom.packages.getActivePackages().includes(package3)).toBe(false); + }); + }); describe('::enablePackage(id) and ::disablePackage(id)', () => { describe('with packages', () => { it('enables a disabled package', async () => { - const packageName = 'package-with-main' - atom.config.pushAtKeyPath('core.disabledPackages', packageName) - atom.packages.observeDisabledPackages() - expect(atom.config.get('core.disabledPackages')).toContain(packageName) + const packageName = 'package-with-main'; + atom.config.pushAtKeyPath('core.disabledPackages', packageName); + atom.packages.observeDisabledPackages(); + expect(atom.config.get('core.disabledPackages')).toContain(packageName); - const pack = atom.packages.enablePackage(packageName) + const pack = atom.packages.enablePackage(packageName); await new Promise(resolve => atom.packages.onDidActivatePackage(resolve) - ) + ); - expect(atom.packages.getLoadedPackages()).toContain(pack) - expect(atom.packages.getActivePackages()).toContain(pack) + expect(atom.packages.getLoadedPackages()).toContain(pack); + expect(atom.packages.getActivePackages()).toContain(pack); expect(atom.config.get('core.disabledPackages')).not.toContain( packageName - ) - }) + ); + }); it('disables an enabled package', async () => { - const packageName = 'package-with-main' - const pack = await atom.packages.activatePackage(packageName) + const packageName = 'package-with-main'; + const pack = await atom.packages.activatePackage(packageName); - atom.packages.observeDisabledPackages() + atom.packages.observeDisabledPackages(); expect(atom.config.get('core.disabledPackages')).not.toContain( packageName - ) + ); await new Promise(resolve => { - atom.packages.onDidDeactivatePackage(resolve) - atom.packages.disablePackage(packageName) - }) + atom.packages.onDidDeactivatePackage(resolve); + atom.packages.disablePackage(packageName); + }); - expect(atom.packages.getActivePackages()).not.toContain(pack) - expect(atom.config.get('core.disabledPackages')).toContain(packageName) - }) + expect(atom.packages.getActivePackages()).not.toContain(pack); + expect(atom.config.get('core.disabledPackages')).toContain(packageName); + }); it('returns null if the package cannot be loaded', () => { - spyOn(console, 'warn') - expect(atom.packages.enablePackage('this-doesnt-exist')).toBeNull() - expect(console.warn.callCount).toBe(1) - }) + spyOn(console, 'warn'); + expect(atom.packages.enablePackage('this-doesnt-exist')).toBeNull(); + expect(console.warn.callCount).toBe(1); + }); it('does not disable an already disabled package', () => { - const packageName = 'package-with-main' - atom.config.pushAtKeyPath('core.disabledPackages', packageName) - atom.packages.observeDisabledPackages() - expect(atom.config.get('core.disabledPackages')).toContain(packageName) + const packageName = 'package-with-main'; + atom.config.pushAtKeyPath('core.disabledPackages', packageName); + atom.packages.observeDisabledPackages(); + expect(atom.config.get('core.disabledPackages')).toContain(packageName); - atom.packages.disablePackage(packageName) + atom.packages.disablePackage(packageName); const packagesDisabled = atom.config .get('core.disabledPackages') - .filter(pack => pack === packageName) - expect(packagesDisabled.length).toEqual(1) - }) - }) + .filter(pack => pack === packageName); + expect(packagesDisabled.length).toEqual(1); + }); + }); describe('with themes', () => { - beforeEach(() => atom.themes.activateThemes()) - afterEach(() => atom.themes.deactivateThemes()) + beforeEach(() => atom.themes.activateThemes()); + afterEach(() => atom.themes.deactivateThemes()); it('enables and disables a theme', async () => { - const packageName = 'theme-with-package-file' - expect(atom.config.get('core.themes')).not.toContain(packageName) + const packageName = 'theme-with-package-file'; + expect(atom.config.get('core.themes')).not.toContain(packageName); expect(atom.config.get('core.disabledPackages')).not.toContain( packageName - ) + ); // enabling of theme - const pack = atom.packages.enablePackage(packageName) + const pack = atom.packages.enablePackage(packageName); await new Promise(resolve => atom.packages.onDidActivatePackage(resolve) - ) - expect(atom.packages.isPackageActive(packageName)).toBe(true) - expect(atom.config.get('core.themes')).toContain(packageName) + ); + expect(atom.packages.isPackageActive(packageName)).toBe(true); + expect(atom.config.get('core.themes')).toContain(packageName); expect(atom.config.get('core.disabledPackages')).not.toContain( packageName - ) + ); await new Promise(resolve => { - atom.themes.onDidChangeActiveThemes(resolve) - atom.packages.disablePackage(packageName) - }) + atom.themes.onDidChangeActiveThemes(resolve); + atom.packages.disablePackage(packageName); + }); - expect(atom.packages.getActivePackages()).not.toContain(pack) - expect(atom.config.get('core.themes')).not.toContain(packageName) - expect(atom.config.get('core.themes')).not.toContain(packageName) + expect(atom.packages.getActivePackages()).not.toContain(pack); + expect(atom.config.get('core.themes')).not.toContain(packageName); + expect(atom.config.get('core.themes')).not.toContain(packageName); expect(atom.config.get('core.disabledPackages')).not.toContain( packageName - ) - }) - }) - }) -}) + ); + }); + }); + }); +}); diff --git a/spec/package-transpilation-registry-spec.js b/spec/package-transpilation-registry-spec.js index df2599db1..54d5d90d9 100644 --- a/spec/package-transpilation-registry-spec.js +++ b/spec/package-transpilation-registry-spec.js @@ -1,211 +1,211 @@ /** @babel */ -import path from 'path' +import path from 'path'; -import PackageTranspilationRegistry from '../src/package-transpilation-registry' +import PackageTranspilationRegistry from '../src/package-transpilation-registry'; const originalCompiler = { getCachePath: (sourceCode, filePath) => { - return 'orig-cache-path' + return 'orig-cache-path'; }, compile: (sourceCode, filePath) => { - return sourceCode + '-original-compiler' + return sourceCode + '-original-compiler'; }, shouldCompile: (sourceCode, filePath) => { - return path.extname(filePath) === '.js' + return path.extname(filePath) === '.js'; } -} +}; describe('PackageTranspilationRegistry', () => { - let registry - let wrappedCompiler + let registry; + let wrappedCompiler; beforeEach(() => { - registry = new PackageTranspilationRegistry() - wrappedCompiler = registry.wrapTranspiler(originalCompiler) - }) + registry = new PackageTranspilationRegistry(); + wrappedCompiler = registry.wrapTranspiler(originalCompiler); + }); it('falls through to the original compiler by default', () => { - spyOn(originalCompiler, 'getCachePath') - spyOn(originalCompiler, 'compile') - spyOn(originalCompiler, 'shouldCompile') + spyOn(originalCompiler, 'getCachePath'); + spyOn(originalCompiler, 'compile'); + spyOn(originalCompiler, 'shouldCompile'); - wrappedCompiler.getCachePath('source', '/path/to/file.js') - wrappedCompiler.compile('source', '/path/to/filejs') - wrappedCompiler.shouldCompile('source', '/path/to/file.js') + wrappedCompiler.getCachePath('source', '/path/to/file.js'); + wrappedCompiler.compile('source', '/path/to/filejs'); + wrappedCompiler.shouldCompile('source', '/path/to/file.js'); - expect(originalCompiler.getCachePath).toHaveBeenCalled() - expect(originalCompiler.compile).toHaveBeenCalled() - expect(originalCompiler.shouldCompile).toHaveBeenCalled() - }) + expect(originalCompiler.getCachePath).toHaveBeenCalled(); + expect(originalCompiler.compile).toHaveBeenCalled(); + expect(originalCompiler.shouldCompile).toHaveBeenCalled(); + }); describe('when a file is contained in a path that has custom transpilation', () => { - const hitPath = path.join('/path/to/lib/file.js') - const hitPathCoffee = path.join('/path/to/file2.coffee') - const missPath = path.join('/path/other/file3.js') - const hitPathMissSubdir = path.join('/path/to/file4.js') - const hitPathMissExt = path.join('/path/to/file5.ts') - const nodeModulesFolder = path.join('/path/to/lib/node_modules/file6.js') - const hitNonStandardExt = path.join('/path/to/file7.omgwhatisthis') + const hitPath = path.join('/path/to/lib/file.js'); + const hitPathCoffee = path.join('/path/to/file2.coffee'); + const missPath = path.join('/path/other/file3.js'); + const hitPathMissSubdir = path.join('/path/to/file4.js'); + const hitPathMissExt = path.join('/path/to/file5.ts'); + const nodeModulesFolder = path.join('/path/to/lib/node_modules/file6.js'); + const hitNonStandardExt = path.join('/path/to/file7.omgwhatisthis'); const jsSpec = { glob: 'lib/**/*.js', transpiler: './transpiler-js', options: { type: 'js' } - } + }; const coffeeSpec = { glob: '*.coffee', transpiler: './transpiler-coffee', options: { type: 'coffee' } - } + }; const omgSpec = { glob: '*.omgwhatisthis', transpiler: './transpiler-omg', options: { type: 'omg' } - } + }; const expectedMeta = { name: 'my-package', path: path.join('/path/to'), meta: { some: 'metadata' } - } + }; const jsTranspiler = { transpile: (sourceCode, filePath, options) => { - return { code: sourceCode + '-transpiler-js' } + return { code: sourceCode + '-transpiler-js' }; }, getCacheKeyData: (sourceCode, filePath, options) => { - return 'js-transpiler-cache-data' + return 'js-transpiler-cache-data'; } - } + }; const coffeeTranspiler = { transpile: (sourceCode, filePath, options) => { - return { code: sourceCode + '-transpiler-coffee' } + return { code: sourceCode + '-transpiler-coffee' }; }, getCacheKeyData: (sourceCode, filePath, options) => { - return 'coffee-transpiler-cache-data' + return 'coffee-transpiler-cache-data'; } - } + }; const omgTranspiler = { transpile: (sourceCode, filePath, options) => { - return { code: sourceCode + '-transpiler-omg' } + return { code: sourceCode + '-transpiler-omg' }; }, getCacheKeyData: (sourceCode, filePath, options) => { - return 'omg-transpiler-cache-data' + return 'omg-transpiler-cache-data'; } - } + }; beforeEach(() => { - jsSpec._transpilerSource = 'js-transpiler-source' - coffeeSpec._transpilerSource = 'coffee-transpiler-source' - omgTranspiler._transpilerSource = 'omg-transpiler-source' + jsSpec._transpilerSource = 'js-transpiler-source'; + coffeeSpec._transpilerSource = 'coffee-transpiler-source'; + omgTranspiler._transpilerSource = 'omg-transpiler-source'; spyOn(registry, 'getTranspiler').andCallFake(spec => { - if (spec.transpiler === './transpiler-js') return jsTranspiler - if (spec.transpiler === './transpiler-coffee') return coffeeTranspiler - if (spec.transpiler === './transpiler-omg') return omgTranspiler - throw new Error('bad transpiler path ' + spec.transpiler) - }) + if (spec.transpiler === './transpiler-js') return jsTranspiler; + if (spec.transpiler === './transpiler-coffee') return coffeeTranspiler; + if (spec.transpiler === './transpiler-omg') return omgTranspiler; + throw new Error('bad transpiler path ' + spec.transpiler); + }); registry.addTranspilerConfigForPath( path.join('/path/to'), 'my-package', { some: 'metadata' }, [jsSpec, coffeeSpec, omgSpec] - ) - }) + ); + }); it('always returns true from shouldCompile for a file in that dir that match a glob', () => { - spyOn(originalCompiler, 'shouldCompile').andReturn(false) - expect(wrappedCompiler.shouldCompile('source', hitPath)).toBe(true) - expect(wrappedCompiler.shouldCompile('source', hitPathCoffee)).toBe(true) + spyOn(originalCompiler, 'shouldCompile').andReturn(false); + expect(wrappedCompiler.shouldCompile('source', hitPath)).toBe(true); + expect(wrappedCompiler.shouldCompile('source', hitPathCoffee)).toBe(true); expect(wrappedCompiler.shouldCompile('source', hitNonStandardExt)).toBe( true - ) + ); expect(wrappedCompiler.shouldCompile('source', hitPathMissExt)).toBe( false - ) + ); expect(wrappedCompiler.shouldCompile('source', hitPathMissSubdir)).toBe( false - ) - expect(wrappedCompiler.shouldCompile('source', missPath)).toBe(false) + ); + expect(wrappedCompiler.shouldCompile('source', missPath)).toBe(false); expect(wrappedCompiler.shouldCompile('source', nodeModulesFolder)).toBe( false - ) - }) + ); + }); it('calls getCacheKeyData on the transpiler to get additional cache key data', () => { - spyOn(registry, 'getTranspilerPath').andReturn('./transpiler-js') - spyOn(jsTranspiler, 'getCacheKeyData').andCallThrough() + spyOn(registry, 'getTranspilerPath').andReturn('./transpiler-js'); + spyOn(jsTranspiler, 'getCacheKeyData').andCallThrough(); - wrappedCompiler.getCachePath('source', missPath, jsSpec) + wrappedCompiler.getCachePath('source', missPath, jsSpec); expect(jsTranspiler.getCacheKeyData).not.toHaveBeenCalledWith( 'source', missPath, jsSpec.options, expectedMeta - ) - wrappedCompiler.getCachePath('source', hitPath, jsSpec) + ); + wrappedCompiler.getCachePath('source', hitPath, jsSpec); expect(jsTranspiler.getCacheKeyData).toHaveBeenCalledWith( 'source', hitPath, jsSpec.options, expectedMeta - ) - }) + ); + }); it('compiles files matching a glob with the associated transpiler, and the old one otherwise', () => { - spyOn(jsTranspiler, 'transpile').andCallThrough() - spyOn(coffeeTranspiler, 'transpile').andCallThrough() - spyOn(omgTranspiler, 'transpile').andCallThrough() + spyOn(jsTranspiler, 'transpile').andCallThrough(); + spyOn(coffeeTranspiler, 'transpile').andCallThrough(); + spyOn(omgTranspiler, 'transpile').andCallThrough(); expect(wrappedCompiler.compile('source', hitPath)).toEqual( 'source-transpiler-js' - ) + ); expect(jsTranspiler.transpile).toHaveBeenCalledWith( 'source', hitPath, jsSpec.options, expectedMeta - ) + ); expect(wrappedCompiler.compile('source', hitPathCoffee)).toEqual( 'source-transpiler-coffee' - ) + ); expect(coffeeTranspiler.transpile).toHaveBeenCalledWith( 'source', hitPathCoffee, coffeeSpec.options, expectedMeta - ) + ); expect(wrappedCompiler.compile('source', hitNonStandardExt)).toEqual( 'source-transpiler-omg' - ) + ); expect(omgTranspiler.transpile).toHaveBeenCalledWith( 'source', hitNonStandardExt, omgSpec.options, expectedMeta - ) + ); expect(wrappedCompiler.compile('source', missPath)).toEqual( 'source-original-compiler' - ) + ); expect(wrappedCompiler.compile('source', hitPathMissExt)).toEqual( 'source-original-compiler' - ) + ); expect(wrappedCompiler.compile('source', hitPathMissSubdir)).toEqual( 'source-original-compiler' - ) + ); expect(wrappedCompiler.compile('source', nodeModulesFolder)).toEqual( 'source-original-compiler' - ) - }) + ); + }); describe('when the packages root path contains node_modules', () => { beforeEach(() => { @@ -214,24 +214,24 @@ describe('PackageTranspilationRegistry', () => { 'my-other-package', { some: 'metadata' }, [jsSpec] - ) - }) + ); + }); it('returns appropriate values from shouldCompile', () => { - spyOn(originalCompiler, 'shouldCompile').andReturn(false) + spyOn(originalCompiler, 'shouldCompile').andReturn(false); expect( wrappedCompiler.shouldCompile( 'source', '/path/with/node_modules/in/root/lib/test.js' ) - ).toBe(true) + ).toBe(true); expect( wrappedCompiler.shouldCompile( 'source', '/path/with/node_modules/in/root/lib/node_modules/test.js' ) - ).toBe(false) - }) - }) - }) -}) + ).toBe(false); + }); + }); + }); +}); diff --git a/spec/pane-container-spec.js b/spec/pane-container-spec.js index cd6da9939..a21c3fd9c 100644 --- a/spec/pane-container-spec.js +++ b/spec/pane-container-spec.js @@ -1,413 +1,413 @@ -const PaneContainer = require('../src/pane-container') +const PaneContainer = require('../src/pane-container'); describe('PaneContainer', () => { - let confirm, params + let confirm, params; beforeEach(() => { confirm = spyOn(atom.applicationDelegate, 'confirm').andCallFake( (options, callback) => callback(0) - ) + ); params = { location: 'center', config: atom.config, deserializerManager: atom.deserializers, applicationDelegate: atom.applicationDelegate, viewRegistry: atom.views - } - }) + }; + }); describe('serialization', () => { - let containerA, pane1A, pane2A, pane3A + let containerA, pane1A, pane2A, pane3A; beforeEach(() => { // This is a dummy item to prevent panes from being empty on deserialization class Item { - static deserialize () { - return new this() + static deserialize() { + return new this(); } - serialize () { - return { deserializer: 'Item' } + serialize() { + return { deserializer: 'Item' }; } } - atom.deserializers.add(Item) + atom.deserializers.add(Item); - containerA = new PaneContainer(params) - pane1A = containerA.getActivePane() - pane1A.addItem(new Item()) - pane2A = pane1A.splitRight({ items: [new Item()] }) - pane3A = pane2A.splitDown({ items: [new Item()] }) - pane3A.focus() - }) + containerA = new PaneContainer(params); + pane1A = containerA.getActivePane(); + pane1A.addItem(new Item()); + pane2A = pane1A.splitRight({ items: [new Item()] }); + pane3A = pane2A.splitDown({ items: [new Item()] }); + pane3A.focus(); + }); it('preserves the focused pane across serialization', () => { - expect(pane3A.focused).toBe(true) + expect(pane3A.focused).toBe(true); - const containerB = new PaneContainer(params) - containerB.deserialize(containerA.serialize(), atom.deserializers) - const pane3B = containerB.getPanes()[2] - expect(pane3B.focused).toBe(true) - }) + const containerB = new PaneContainer(params); + containerB.deserialize(containerA.serialize(), atom.deserializers); + const pane3B = containerB.getPanes()[2]; + expect(pane3B.focused).toBe(true); + }); it('preserves the active pane across serialization, independent of focus', () => { - pane3A.activate() - expect(containerA.getActivePane()).toBe(pane3A) + pane3A.activate(); + expect(containerA.getActivePane()).toBe(pane3A); - const containerB = new PaneContainer(params) - containerB.deserialize(containerA.serialize(), atom.deserializers) - const pane3B = containerB.getPanes()[2] - expect(containerB.getActivePane()).toBe(pane3B) - }) + const containerB = new PaneContainer(params); + containerB.deserialize(containerA.serialize(), atom.deserializers); + const pane3B = containerB.getPanes()[2]; + expect(containerB.getActivePane()).toBe(pane3B); + }); it('makes the first pane active if no pane exists for the activePaneId', () => { - pane3A.activate() - const state = containerA.serialize() - state.activePaneId = -22 - const containerB = new PaneContainer(params) - containerB.deserialize(state, atom.deserializers) - expect(containerB.getActivePane()).toBe(containerB.getPanes()[0]) - }) + pane3A.activate(); + const state = containerA.serialize(); + state.activePaneId = -22; + const containerB = new PaneContainer(params); + containerB.deserialize(state, atom.deserializers); + expect(containerB.getActivePane()).toBe(containerB.getPanes()[0]); + }); describe('if there are empty panes after deserialization', () => { beforeEach(() => { - pane3A.getItems()[0].serialize = () => ({ deserializer: 'Bogus' }) - }) + pane3A.getItems()[0].serialize = () => ({ deserializer: 'Bogus' }); + }); describe("if the 'core.destroyEmptyPanes' config option is false (the default)", () => it('leaves the empty panes intact', () => { - const state = containerA.serialize() - const containerB = new PaneContainer(params) - containerB.deserialize(state, atom.deserializers) - const [leftPane, column] = containerB.getRoot().getChildren() - const [topPane, bottomPane] = column.getChildren() + const state = containerA.serialize(); + const containerB = new PaneContainer(params); + containerB.deserialize(state, atom.deserializers); + const [leftPane, column] = containerB.getRoot().getChildren(); + const [topPane, bottomPane] = column.getChildren(); - expect(leftPane.getItems().length).toBe(1) - expect(topPane.getItems().length).toBe(1) - expect(bottomPane.getItems().length).toBe(0) - })) + expect(leftPane.getItems().length).toBe(1); + expect(topPane.getItems().length).toBe(1); + expect(bottomPane.getItems().length).toBe(0); + })); describe("if the 'core.destroyEmptyPanes' config option is true", () => it('removes empty panes on deserialization', () => { - atom.config.set('core.destroyEmptyPanes', true) + atom.config.set('core.destroyEmptyPanes', true); - const state = containerA.serialize() - const containerB = new PaneContainer(params) - containerB.deserialize(state, atom.deserializers) - const [leftPane, rightPane] = containerB.getRoot().getChildren() + const state = containerA.serialize(); + const containerB = new PaneContainer(params); + containerB.deserialize(state, atom.deserializers); + const [leftPane, rightPane] = containerB.getRoot().getChildren(); - expect(leftPane.getItems().length).toBe(1) - expect(rightPane.getItems().length).toBe(1) - })) - }) - }) + expect(leftPane.getItems().length).toBe(1); + expect(rightPane.getItems().length).toBe(1); + })); + }); + }); it('does not allow the root pane to be destroyed', () => { - const container = new PaneContainer(params) - container.getRoot().destroy() - expect(container.getRoot()).toBeDefined() - expect(container.getRoot().isDestroyed()).toBe(false) - }) + const container = new PaneContainer(params); + container.getRoot().destroy(); + expect(container.getRoot()).toBeDefined(); + expect(container.getRoot().isDestroyed()).toBe(false); + }); describe('::getActivePane()', () => { - let container, pane1, pane2 + let container, pane1, pane2; beforeEach(() => { - container = new PaneContainer(params) - pane1 = container.getRoot() - }) + container = new PaneContainer(params); + pane1 = container.getRoot(); + }); it('returns the first pane if no pane has been made active', () => { - expect(container.getActivePane()).toBe(pane1) - expect(pane1.isActive()).toBe(true) - }) + expect(container.getActivePane()).toBe(pane1); + expect(pane1.isActive()).toBe(true); + }); it('returns the most pane on which ::activate() was most recently called', () => { - pane2 = pane1.splitRight() - pane2.activate() - expect(container.getActivePane()).toBe(pane2) - expect(pane1.isActive()).toBe(false) - expect(pane2.isActive()).toBe(true) - pane1.activate() - expect(container.getActivePane()).toBe(pane1) - expect(pane1.isActive()).toBe(true) - expect(pane2.isActive()).toBe(false) - }) + pane2 = pane1.splitRight(); + pane2.activate(); + expect(container.getActivePane()).toBe(pane2); + expect(pane1.isActive()).toBe(false); + expect(pane2.isActive()).toBe(true); + pane1.activate(); + expect(container.getActivePane()).toBe(pane1); + expect(pane1.isActive()).toBe(true); + expect(pane2.isActive()).toBe(false); + }); it('returns the next pane if the current active pane is destroyed', () => { - pane2 = pane1.splitRight() - pane2.activate() - pane2.destroy() - expect(container.getActivePane()).toBe(pane1) - expect(pane1.isActive()).toBe(true) - }) - }) + pane2 = pane1.splitRight(); + pane2.activate(); + pane2.destroy(); + expect(container.getActivePane()).toBe(pane1); + expect(pane1.isActive()).toBe(true); + }); + }); describe('::onDidChangeActivePane()', () => { - let container, pane1, pane2, observed + let container, pane1, pane2, observed; beforeEach(() => { - container = new PaneContainer(params) - container.getRoot().addItems([{}, {}]) - container.getRoot().splitRight({ items: [{}, {}] }) - ;[pane1, pane2] = container.getPanes() + container = new PaneContainer(params); + container.getRoot().addItems([{}, {}]); + container.getRoot().splitRight({ items: [{}, {}] }); + [pane1, pane2] = container.getPanes(); - observed = [] - container.onDidChangeActivePane(pane => observed.push(pane)) - }) + observed = []; + container.onDidChangeActivePane(pane => observed.push(pane)); + }); it('invokes observers when the active pane changes', () => { - pane1.activate() - pane2.activate() - expect(observed).toEqual([pane1, pane2]) - }) - }) + pane1.activate(); + pane2.activate(); + expect(observed).toEqual([pane1, pane2]); + }); + }); describe('::onDidChangeActivePaneItem()', () => { - let container, pane1, pane2, observed + let container, pane1, pane2, observed; beforeEach(() => { - container = new PaneContainer(params) - container.getRoot().addItems([{}, {}]) - container.getRoot().splitRight({ items: [{}, {}] }) - ;[pane1, pane2] = container.getPanes() + container = new PaneContainer(params); + container.getRoot().addItems([{}, {}]); + container.getRoot().splitRight({ items: [{}, {}] }); + [pane1, pane2] = container.getPanes(); - observed = [] - container.onDidChangeActivePaneItem(item => observed.push(item)) - }) + observed = []; + container.onDidChangeActivePaneItem(item => observed.push(item)); + }); it('invokes observers when the active item of the active pane changes', () => { - pane2.activateNextItem() - pane2.activateNextItem() - expect(observed).toEqual([pane2.itemAtIndex(1), pane2.itemAtIndex(0)]) - }) + pane2.activateNextItem(); + pane2.activateNextItem(); + expect(observed).toEqual([pane2.itemAtIndex(1), pane2.itemAtIndex(0)]); + }); it('invokes observers when the active pane changes', () => { - pane1.activate() - pane2.activate() - expect(observed).toEqual([pane1.itemAtIndex(0), pane2.itemAtIndex(0)]) - }) - }) + pane1.activate(); + pane2.activate(); + expect(observed).toEqual([pane1.itemAtIndex(0), pane2.itemAtIndex(0)]); + }); + }); describe('::onDidStopChangingActivePaneItem()', () => { - let container, pane1, pane2, observed + let container, pane1, pane2, observed; beforeEach(() => { - container = new PaneContainer(params) - container.getRoot().addItems([{}, {}]) - container.getRoot().splitRight({ items: [{}, {}] }) - ;[pane1, pane2] = container.getPanes() + container = new PaneContainer(params); + container.getRoot().addItems([{}, {}]); + container.getRoot().splitRight({ items: [{}, {}] }); + [pane1, pane2] = container.getPanes(); - observed = [] - container.onDidStopChangingActivePaneItem(item => observed.push(item)) - }) + observed = []; + container.onDidStopChangingActivePaneItem(item => observed.push(item)); + }); it('invokes observers once when the active item of the active pane changes', () => { - pane2.activateNextItem() - pane2.activateNextItem() - expect(observed).toEqual([]) - advanceClock(100) - expect(observed).toEqual([pane2.itemAtIndex(0)]) - }) + pane2.activateNextItem(); + pane2.activateNextItem(); + expect(observed).toEqual([]); + advanceClock(100); + expect(observed).toEqual([pane2.itemAtIndex(0)]); + }); it('invokes observers once when the active pane changes', () => { - pane1.activate() - pane2.activate() - expect(observed).toEqual([]) - advanceClock(100) - expect(observed).toEqual([pane2.itemAtIndex(0)]) - }) - }) + pane1.activate(); + pane2.activate(); + expect(observed).toEqual([]); + advanceClock(100); + expect(observed).toEqual([pane2.itemAtIndex(0)]); + }); + }); describe('::onDidActivatePane', () => { it('invokes observers when a pane is activated (even if it was already active)', () => { - const container = new PaneContainer(params) - container.getRoot().splitRight() - const [pane1, pane2] = container.getPanes() + const container = new PaneContainer(params); + container.getRoot().splitRight(); + const [pane1, pane2] = container.getPanes(); - const activatedPanes = [] - container.onDidActivatePane(pane => activatedPanes.push(pane)) + const activatedPanes = []; + container.onDidActivatePane(pane => activatedPanes.push(pane)); - pane1.activate() - pane1.activate() - pane2.activate() - pane2.activate() - expect(activatedPanes).toEqual([pane1, pane1, pane2, pane2]) - }) - }) + pane1.activate(); + pane1.activate(); + pane2.activate(); + pane2.activate(); + expect(activatedPanes).toEqual([pane1, pane1, pane2, pane2]); + }); + }); describe('::observePanes()', () => { it('invokes observers with all current and future panes', () => { - const container = new PaneContainer(params) - container.getRoot().splitRight() - const [pane1, pane2] = container.getPanes() + const container = new PaneContainer(params); + container.getRoot().splitRight(); + const [pane1, pane2] = container.getPanes(); - const observed = [] - container.observePanes(pane => observed.push(pane)) + const observed = []; + container.observePanes(pane => observed.push(pane)); - const pane3 = pane2.splitDown() - const pane4 = pane2.splitRight() + const pane3 = pane2.splitDown(); + const pane4 = pane2.splitRight(); - expect(observed).toEqual([pane1, pane2, pane3, pane4]) - }) - }) + expect(observed).toEqual([pane1, pane2, pane3, pane4]); + }); + }); describe('::observePaneItems()', () => it('invokes observers with all current and future pane items', () => { - const container = new PaneContainer(params) - container.getRoot().addItems([{}, {}]) - container.getRoot().splitRight({ items: [{}] }) - const pane2 = container.getPanes()[1] - const observed = [] - container.observePaneItems(pane => observed.push(pane)) + const container = new PaneContainer(params); + container.getRoot().addItems([{}, {}]); + container.getRoot().splitRight({ items: [{}] }); + const pane2 = container.getPanes()[1]; + const observed = []; + container.observePaneItems(pane => observed.push(pane)); - const pane3 = pane2.splitDown({ items: [{}] }) - pane3.addItems([{}, {}]) + const pane3 = pane2.splitDown({ items: [{}] }); + pane3.addItems([{}, {}]); - expect(observed).toEqual(container.getPaneItems()) - })) + expect(observed).toEqual(container.getPaneItems()); + })); describe('::confirmClose()', () => { - let container, pane1, pane2 + let container, pane1, pane2; beforeEach(() => { class TestItem { - shouldPromptToSave () { - return true + shouldPromptToSave() { + return true; } - getURI () { - return 'test' + getURI() { + return 'test'; } } - container = new PaneContainer(params) - container.getRoot().splitRight() - ;[pane1, pane2] = container.getPanes() - pane1.addItem(new TestItem()) - pane2.addItem(new TestItem()) - }) + container = new PaneContainer(params); + container.getRoot().splitRight(); + [pane1, pane2] = container.getPanes(); + pane1.addItem(new TestItem()); + pane2.addItem(new TestItem()); + }); it('returns true if the user saves all modified files when prompted', async () => { - confirm.andCallFake((options, callback) => callback(0)) - const saved = await container.confirmClose() - expect(confirm).toHaveBeenCalled() - expect(saved).toBeTruthy() - }) + confirm.andCallFake((options, callback) => callback(0)); + const saved = await container.confirmClose(); + expect(confirm).toHaveBeenCalled(); + expect(saved).toBeTruthy(); + }); it('returns false if the user cancels saving any modified file', async () => { - confirm.andCallFake((options, callback) => callback(1)) - const saved = await container.confirmClose() - expect(confirm).toHaveBeenCalled() - expect(saved).toBeFalsy() - }) - }) + confirm.andCallFake((options, callback) => callback(1)); + const saved = await container.confirmClose(); + expect(confirm).toHaveBeenCalled(); + expect(saved).toBeFalsy(); + }); + }); describe('::onDidAddPane(callback)', () => { it('invokes the given callback when panes are added', () => { - const container = new PaneContainer(params) - const events = [] + const container = new PaneContainer(params); + const events = []; container.onDidAddPane(event => { - expect(container.getPanes().includes(event.pane)).toBe(true) - events.push(event) - }) + expect(container.getPanes().includes(event.pane)).toBe(true); + events.push(event); + }); - const pane1 = container.getActivePane() - const pane2 = pane1.splitRight() - const pane3 = pane2.splitDown() + const pane1 = container.getActivePane(); + const pane2 = pane1.splitRight(); + const pane3 = pane2.splitDown(); - expect(events).toEqual([{ pane: pane2 }, { pane: pane3 }]) - }) - }) + expect(events).toEqual([{ pane: pane2 }, { pane: pane3 }]); + }); + }); describe('::onWillDestroyPane(callback)', () => { it('invokes the given callback before panes or their items are destroyed', () => { class TestItem { - constructor () { - this._isDestroyed = false + constructor() { + this._isDestroyed = false; } - destroy () { - this._isDestroyed = true + destroy() { + this._isDestroyed = true; } - isDestroyed () { - return this._isDestroyed + isDestroyed() { + return this._isDestroyed; } } - const container = new PaneContainer(params) - const events = [] + const container = new PaneContainer(params); + const events = []; container.onWillDestroyPane(event => { const itemsDestroyed = event.pane .getItems() - .map(item => item.isDestroyed()) - events.push([event, { itemsDestroyed }]) - }) + .map(item => item.isDestroyed()); + events.push([event, { itemsDestroyed }]); + }); - const pane1 = container.getActivePane() - const pane2 = pane1.splitRight() - pane2.addItem(new TestItem()) + const pane1 = container.getActivePane(); + const pane2 = pane1.splitRight(); + pane2.addItem(new TestItem()); - pane2.destroy() + pane2.destroy(); - expect(events).toEqual([[{ pane: pane2 }, { itemsDestroyed: [false] }]]) - }) - }) + expect(events).toEqual([[{ pane: pane2 }, { itemsDestroyed: [false] }]]); + }); + }); describe('::onDidDestroyPane(callback)', () => { it('invokes the given callback when panes are destroyed', () => { - const container = new PaneContainer(params) - const events = [] + const container = new PaneContainer(params); + const events = []; container.onDidDestroyPane(event => { - expect(container.getPanes().includes(event.pane)).toBe(false) - events.push(event) - }) + expect(container.getPanes().includes(event.pane)).toBe(false); + events.push(event); + }); - const pane1 = container.getActivePane() - const pane2 = pane1.splitRight() - const pane3 = pane2.splitDown() + const pane1 = container.getActivePane(); + const pane2 = pane1.splitRight(); + const pane3 = pane2.splitDown(); - pane2.destroy() - pane3.destroy() + pane2.destroy(); + pane3.destroy(); - expect(events).toEqual([{ pane: pane2 }, { pane: pane3 }]) - }) + expect(events).toEqual([{ pane: pane2 }, { pane: pane3 }]); + }); it('invokes the given callback when the container is destroyed', () => { - const container = new PaneContainer(params) - const events = [] + const container = new PaneContainer(params); + const events = []; container.onDidDestroyPane(event => { - expect(container.getPanes().includes(event.pane)).toBe(false) - events.push(event) - }) + expect(container.getPanes().includes(event.pane)).toBe(false); + events.push(event); + }); - const pane1 = container.getActivePane() - const pane2 = pane1.splitRight() - const pane3 = pane2.splitDown() + const pane1 = container.getActivePane(); + const pane2 = pane1.splitRight(); + const pane3 = pane2.splitDown(); - container.destroy() + container.destroy(); expect(events).toEqual([ { pane: pane1 }, { pane: pane2 }, { pane: pane3 } - ]) - }) - }) + ]); + }); + }); describe('::onWillDestroyPaneItem() and ::onDidDestroyPaneItem', () => { it('invokes the given callbacks when an item will be destroyed on any pane', async () => { - const container = new PaneContainer(params) - const pane1 = container.getRoot() - const item1 = {} - const item2 = {} - const item3 = {} + const container = new PaneContainer(params); + const pane1 = container.getRoot(); + const item1 = {}; + const item2 = {}; + const item3 = {}; - pane1.addItem(item1) - const events = [] - container.onWillDestroyPaneItem(event => events.push(['will', event])) - container.onDidDestroyPaneItem(event => events.push(['did', event])) - const pane2 = pane1.splitRight({ items: [item2, item3] }) + pane1.addItem(item1); + const events = []; + container.onWillDestroyPaneItem(event => events.push(['will', event])); + container.onDidDestroyPaneItem(event => events.push(['did', event])); + const pane2 = pane1.splitRight({ items: [item2, item3] }); - await pane1.destroyItem(item1) - await pane2.destroyItem(item3) - await pane2.destroyItem(item2) + await pane1.destroyItem(item1); + await pane2.destroyItem(item3); + await pane2.destroyItem(item2); expect(events).toEqual([ ['will', { item: item1, pane: pane1, index: 0 }], @@ -416,94 +416,94 @@ describe('PaneContainer', () => { ['did', { item: item3, pane: pane2, index: 1 }], ['will', { item: item2, pane: pane2, index: 0 }], ['did', { item: item2, pane: pane2, index: 0 }] - ]) - }) - }) + ]); + }); + }); describe('::saveAll()', () => it('saves all modified pane items', async () => { - const container = new PaneContainer(params) - const pane1 = container.getRoot() - pane1.splitRight() + const container = new PaneContainer(params); + const pane1 = container.getRoot(); + pane1.splitRight(); const item1 = { saved: false, - getURI () { - return '' + getURI() { + return ''; }, - isModified () { - return true + isModified() { + return true; }, - save () { - this.saved = true + save() { + this.saved = true; } - } + }; const item2 = { saved: false, - getURI () { - return '' + getURI() { + return ''; }, - isModified () { - return false + isModified() { + return false; }, - save () { - this.saved = true + save() { + this.saved = true; } - } + }; const item3 = { saved: false, - getURI () { - return '' + getURI() { + return ''; }, - isModified () { - return true + isModified() { + return true; }, - save () { - this.saved = true + save() { + this.saved = true; } - } + }; - pane1.addItem(item1) - pane1.addItem(item2) - pane1.addItem(item3) + pane1.addItem(item1); + pane1.addItem(item2); + pane1.addItem(item3); - container.saveAll() + container.saveAll(); - expect(item1.saved).toBe(true) - expect(item2.saved).toBe(false) - expect(item3.saved).toBe(true) - })) + expect(item1.saved).toBe(true); + expect(item2.saved).toBe(false); + expect(item3.saved).toBe(true); + })); describe('::moveActiveItemToPane(destPane) and ::copyActiveItemToPane(destPane)', () => { - let container, pane1, pane2, item1 + let container, pane1, pane2, item1; beforeEach(() => { class TestItem { - constructor (id) { - this.id = id + constructor(id) { + this.id = id; } - copy () { - return new TestItem(this.id) + copy() { + return new TestItem(this.id); } } - container = new PaneContainer(params) - pane1 = container.getRoot() - item1 = new TestItem('1') - pane2 = pane1.splitRight({ items: [item1] }) - }) + container = new PaneContainer(params); + pane1 = container.getRoot(); + item1 = new TestItem('1'); + pane2 = pane1.splitRight({ items: [item1] }); + }); describe('::::moveActiveItemToPane(destPane)', () => it('moves active item to given pane and focuses it', () => { - container.moveActiveItemToPane(pane1) - expect(pane1.getActiveItem()).toBe(item1) - })) + container.moveActiveItemToPane(pane1); + expect(pane1.getActiveItem()).toBe(item1); + })); describe('::::copyActiveItemToPane(destPane)', () => it('copies active item to given pane and focuses it', () => { - container.copyActiveItemToPane(pane1) - expect(container.paneForItem(item1)).toBe(pane2) - expect(pane1.getActiveItem().id).toBe(item1.id) - })) - }) -}) + container.copyActiveItemToPane(pane1); + expect(container.paneForItem(item1)).toBe(pane2); + expect(pane1.getActiveItem().id).toBe(item1.id); + })); + }); +}); diff --git a/spec/pane-spec.js b/spec/pane-spec.js index c9ca0fb4b..6fcb1fa1d 100644 --- a/spec/pane-spec.js +++ b/spec/pane-spec.js @@ -1,78 +1,78 @@ -const { extend } = require('underscore-plus') -const { Emitter } = require('event-kit') -const Grim = require('grim') -const Pane = require('../src/pane') -const PaneContainer = require('../src/pane-container') -const { conditionPromise, timeoutPromise } = require('./async-spec-helpers') +const { extend } = require('underscore-plus'); +const { Emitter } = require('event-kit'); +const Grim = require('grim'); +const Pane = require('../src/pane'); +const PaneContainer = require('../src/pane-container'); +const { conditionPromise, timeoutPromise } = require('./async-spec-helpers'); describe('Pane', () => { - let confirm, showSaveDialog, deserializerDisposable + let confirm, showSaveDialog, deserializerDisposable; class Item { - static deserialize ({ name, uri }) { - return new Item(name, uri) + static deserialize({ name, uri }) { + return new Item(name, uri); } - constructor (name, uri) { - this.name = name - this.uri = uri - this.emitter = new Emitter() - this.destroyed = false + constructor(name, uri) { + this.name = name; + this.uri = uri; + this.emitter = new Emitter(); + this.destroyed = false; } - getURI () { - return this.uri + getURI() { + return this.uri; } - getPath () { - return this.path + getPath() { + return this.path; } - isEqual (other) { - return this.name === (other && other.name) + isEqual(other) { + return this.name === (other && other.name); } - isPermanentDockItem () { - return false + isPermanentDockItem() { + return false; } - isDestroyed () { - return this.destroyed + isDestroyed() { + return this.destroyed; } - serialize () { - return { deserializer: 'Item', name: this.name, uri: this.uri } + serialize() { + return { deserializer: 'Item', name: this.name, uri: this.uri }; } - copy () { - return new Item(this.name, this.uri) + copy() { + return new Item(this.name, this.uri); } - destroy () { - this.destroyed = true - return this.emitter.emit('did-destroy') + destroy() { + this.destroyed = true; + return this.emitter.emit('did-destroy'); } - onDidDestroy (fn) { - return this.emitter.on('did-destroy', fn) + onDidDestroy(fn) { + return this.emitter.on('did-destroy', fn); } - onDidTerminatePendingState (callback) { - return this.emitter.on('terminate-pending-state', callback) + onDidTerminatePendingState(callback) { + return this.emitter.on('terminate-pending-state', callback); } - terminatePendingState () { - return this.emitter.emit('terminate-pending-state') + terminatePendingState() { + return this.emitter.emit('terminate-pending-state'); } } beforeEach(() => { - confirm = spyOn(atom.applicationDelegate, 'confirm') - showSaveDialog = spyOn(atom.applicationDelegate, 'showSaveDialog') - deserializerDisposable = atom.deserializers.add(Item) - }) + confirm = spyOn(atom.applicationDelegate, 'confirm'); + showSaveDialog = spyOn(atom.applicationDelegate, 'showSaveDialog'); + deserializerDisposable = atom.deserializers.add(Item); + }); afterEach(() => { - deserializerDisposable.dispose() - }) + deserializerDisposable.dispose(); + }); - function paneParams (params) { + function paneParams(params) { return extend( { applicationDelegate: atom.applicationDelegate, @@ -81,337 +81,337 @@ describe('Pane', () => { notificationManager: atom.notifications }, params - ) + ); } describe('construction', () => { it('sets the active item to the first item', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0)) - }) + ); + expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0)); + }); it('compacts the items array', () => { const pane = new Pane( paneParams({ items: [undefined, new Item('A'), null, new Item('B')] }) - ) - expect(pane.getItems().length).toBe(2) - expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0)) - }) - }) + ); + expect(pane.getItems().length).toBe(2); + expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0)); + }); + }); describe('::activate()', () => { - let container, pane1, pane2 + let container, pane1, pane2; beforeEach(() => { container = new PaneContainer({ location: 'center', config: atom.config, applicationDelegate: atom.applicationDelegate - }) - container.getActivePane().splitRight() - ;[pane1, pane2] = container.getPanes() - }) + }); + container.getActivePane().splitRight(); + [pane1, pane2] = container.getPanes(); + }); it('changes the active pane on the container', () => { - expect(container.getActivePane()).toBe(pane2) - pane1.activate() - expect(container.getActivePane()).toBe(pane1) - pane2.activate() - expect(container.getActivePane()).toBe(pane2) - }) + expect(container.getActivePane()).toBe(pane2); + pane1.activate(); + expect(container.getActivePane()).toBe(pane1); + pane2.activate(); + expect(container.getActivePane()).toBe(pane2); + }); it('invokes ::onDidChangeActivePane observers on the container', () => { - const observed = [] - container.onDidChangeActivePane(activePane => observed.push(activePane)) + const observed = []; + container.onDidChangeActivePane(activePane => observed.push(activePane)); - pane1.activate() - pane1.activate() - pane2.activate() - pane1.activate() - expect(observed).toEqual([pane1, pane2, pane1]) - }) + pane1.activate(); + pane1.activate(); + pane2.activate(); + pane1.activate(); + expect(observed).toEqual([pane1, pane2, pane1]); + }); it('invokes ::onDidChangeActive observers on the relevant panes', () => { - const observed = [] - pane1.onDidChangeActive(active => observed.push(active)) - pane1.activate() - pane2.activate() - expect(observed).toEqual([true, false]) - }) + const observed = []; + pane1.onDidChangeActive(active => observed.push(active)); + pane1.activate(); + pane2.activate(); + expect(observed).toEqual([true, false]); + }); it('invokes ::onDidActivate() observers', () => { - let eventCount = 0 - pane1.onDidActivate(() => eventCount++) - pane1.activate() - pane1.activate() - pane2.activate() - expect(eventCount).toBe(2) - }) - }) + let eventCount = 0; + pane1.onDidActivate(() => eventCount++); + pane1.activate(); + pane1.activate(); + pane2.activate(); + expect(eventCount).toBe(2); + }); + }); describe('::addItem(item, index)', () => { it('adds the item at the given index', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - const [item1, item2] = pane.getItems() - const item3 = new Item('C') - pane.addItem(item3, { index: 1 }) - expect(pane.getItems()).toEqual([item1, item3, item2]) - }) + ); + const [item1, item2] = pane.getItems(); + const item3 = new Item('C'); + pane.addItem(item3, { index: 1 }); + expect(pane.getItems()).toEqual([item1, item3, item2]); + }); it('adds the item after the active item if no index is provided', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, item2, item3] = pane.getItems() - pane.activateItem(item2) - const item4 = new Item('D') - pane.addItem(item4) - expect(pane.getItems()).toEqual([item1, item2, item4, item3]) - }) + ); + const [item1, item2, item3] = pane.getItems(); + pane.activateItem(item2); + const item4 = new Item('D'); + pane.addItem(item4); + expect(pane.getItems()).toEqual([item1, item2, item4, item3]); + }); it('sets the active item after adding the first item', () => { - const pane = new Pane(paneParams()) - const item = new Item('A') - pane.addItem(item) - expect(pane.getActiveItem()).toBe(item) - }) + const pane = new Pane(paneParams()); + const item = new Item('A'); + pane.addItem(item); + expect(pane.getActiveItem()).toBe(item); + }); it('invokes ::onDidAddItem() observers', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - const events = [] - pane.onDidAddItem(event => events.push(event)) + ); + const events = []; + pane.onDidAddItem(event => events.push(event)); - const item = new Item('C') - pane.addItem(item, { index: 1 }) - expect(events).toEqual([{ item, index: 1, moved: false }]) - }) + const item = new Item('C'); + pane.addItem(item, { index: 1 }); + expect(events).toEqual([{ item, index: 1, moved: false }]); + }); it('throws an exception if the item is already present on a pane', () => { - const item = new Item('A') + const item = new Item('A'); const container = new PaneContainer({ config: atom.config, applicationDelegate: atom.applicationDelegate - }) - const pane1 = container.getActivePane() - pane1.addItem(item) - const pane2 = pane1.splitRight() - expect(() => pane2.addItem(item)).toThrow() - }) + }); + const pane1 = container.getActivePane(); + pane1.addItem(item); + const pane2 = pane1.splitRight(); + expect(() => pane2.addItem(item)).toThrow(); + }); it("throws an exception if the item isn't an object", () => { - const pane = new Pane(paneParams({ items: [] })) - expect(() => pane.addItem(null)).toThrow() - expect(() => pane.addItem('foo')).toThrow() - expect(() => pane.addItem(1)).toThrow() - }) + const pane = new Pane(paneParams({ items: [] })); + expect(() => pane.addItem(null)).toThrow(); + expect(() => pane.addItem('foo')).toThrow(); + expect(() => pane.addItem(1)).toThrow(); + }); it('destroys any existing pending item', () => { - const pane = new Pane(paneParams({ items: [] })) - const itemA = new Item('A') - const itemB = new Item('B') - const itemC = new Item('C') - pane.addItem(itemA, { pending: false }) - pane.addItem(itemB, { pending: true }) - pane.addItem(itemC, { pending: false }) - expect(itemB.isDestroyed()).toBe(true) - }) + const pane = new Pane(paneParams({ items: [] })); + const itemA = new Item('A'); + const itemB = new Item('B'); + const itemC = new Item('C'); + pane.addItem(itemA, { pending: false }); + pane.addItem(itemB, { pending: true }); + pane.addItem(itemC, { pending: false }); + expect(itemB.isDestroyed()).toBe(true); + }); it('adds the new item before destroying any existing pending item', () => { - const eventOrder = [] + const eventOrder = []; - const pane = new Pane(paneParams({ items: [] })) - const itemA = new Item('A') - const itemB = new Item('B') - pane.addItem(itemA, { pending: true }) + const pane = new Pane(paneParams({ items: [] })); + const itemA = new Item('A'); + const itemB = new Item('B'); + pane.addItem(itemA, { pending: true }); - pane.onDidAddItem(function ({ item }) { - if (item === itemB) eventOrder.push('add') - }) + pane.onDidAddItem(function({ item }) { + if (item === itemB) eventOrder.push('add'); + }); - pane.onDidRemoveItem(function ({ item }) { - if (item === itemA) eventOrder.push('remove') - }) + pane.onDidRemoveItem(function({ item }) { + if (item === itemA) eventOrder.push('remove'); + }); - pane.addItem(itemB) + pane.addItem(itemB); - waitsFor(() => eventOrder.length === 2) + waitsFor(() => eventOrder.length === 2); - runs(() => expect(eventOrder).toEqual(['add', 'remove'])) - }) + runs(() => expect(eventOrder).toEqual(['add', 'remove'])); + }); it('subscribes to be notified when item terminates its pending state', () => { - const fakeDisposable = { dispose: () => {} } + const fakeDisposable = { dispose: () => {} }; const spy = jasmine .createSpy('onDidTerminatePendingState') - .andReturn(fakeDisposable) + .andReturn(fakeDisposable); - const pane = new Pane(paneParams({ items: [] })) + const pane = new Pane(paneParams({ items: [] })); const item = { getTitle: () => '', onDidTerminatePendingState: spy - } - pane.addItem(item) + }; + pane.addItem(item); - expect(spy).toHaveBeenCalled() - }) + expect(spy).toHaveBeenCalled(); + }); it('subscribes to be notified when item is destroyed', () => { - const fakeDisposable = { dispose: () => {} } - const spy = jasmine.createSpy('onDidDestroy').andReturn(fakeDisposable) + const fakeDisposable = { dispose: () => {} }; + const spy = jasmine.createSpy('onDidDestroy').andReturn(fakeDisposable); - const pane = new Pane(paneParams({ items: [] })) + const pane = new Pane(paneParams({ items: [] })); const item = { getTitle: () => '', onDidDestroy: spy - } - pane.addItem(item) + }; + pane.addItem(item); - expect(spy).toHaveBeenCalled() - }) + expect(spy).toHaveBeenCalled(); + }); describe('when using the old API of ::addItem(item, index)', () => { - beforeEach(() => spyOn(Grim, 'deprecate')) + beforeEach(() => spyOn(Grim, 'deprecate')); it('supports the older public API', () => { - const pane = new Pane(paneParams({ items: [] })) - const itemA = new Item('A') - const itemB = new Item('B') - const itemC = new Item('C') - pane.addItem(itemA, 0) - pane.addItem(itemB, 0) - pane.addItem(itemC, 0) - expect(pane.getItems()).toEqual([itemC, itemB, itemA]) - }) + const pane = new Pane(paneParams({ items: [] })); + const itemA = new Item('A'); + const itemB = new Item('B'); + const itemC = new Item('C'); + pane.addItem(itemA, 0); + pane.addItem(itemB, 0); + pane.addItem(itemC, 0); + expect(pane.getItems()).toEqual([itemC, itemB, itemA]); + }); it('shows a deprecation warning', () => { - const pane = new Pane(paneParams({ items: [] })) - pane.addItem(new Item(), 2) + const pane = new Pane(paneParams({ items: [] })); + pane.addItem(new Item(), 2); expect(Grim.deprecate).toHaveBeenCalledWith( 'Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})' - ) - }) - }) - }) + ); + }); + }); + }); describe('::activateItem(item)', () => { - let pane = null + let pane = null; beforeEach(() => { - pane = new Pane(paneParams({ items: [new Item('A'), new Item('B')] })) - }) + pane = new Pane(paneParams({ items: [new Item('A'), new Item('B')] })); + }); it('changes the active item to the current item', () => { - expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0)) - pane.activateItem(pane.itemAtIndex(1)) - expect(pane.getActiveItem()).toBe(pane.itemAtIndex(1)) - }) + expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0)); + pane.activateItem(pane.itemAtIndex(1)); + expect(pane.getActiveItem()).toBe(pane.itemAtIndex(1)); + }); it("adds the given item if it isn't present in ::items", () => { - const item = new Item('C') - pane.activateItem(item) - expect(pane.getItems().includes(item)).toBe(true) - expect(pane.getActiveItem()).toBe(item) - }) + const item = new Item('C'); + pane.activateItem(item); + expect(pane.getItems().includes(item)).toBe(true); + expect(pane.getActiveItem()).toBe(item); + }); it('invokes ::onDidChangeActiveItem() observers', () => { - const observed = [] - pane.onDidChangeActiveItem(item => observed.push(item)) - pane.activateItem(pane.itemAtIndex(1)) - expect(observed).toEqual([pane.itemAtIndex(1)]) - }) + const observed = []; + pane.onDidChangeActiveItem(item => observed.push(item)); + pane.activateItem(pane.itemAtIndex(1)); + expect(observed).toEqual([pane.itemAtIndex(1)]); + }); describe('when the item being activated is pending', () => { - let itemC = null - let itemD = null + let itemC = null; + let itemD = null; beforeEach(() => { - itemC = new Item('C') - itemD = new Item('D') - }) + itemC = new Item('C'); + itemD = new Item('D'); + }); it('replaces the active item if it is pending', () => { - pane.activateItem(itemC, { pending: true }) - expect(pane.getItems().map(item => item.name)).toEqual(['A', 'C', 'B']) - pane.activateItem(itemD, { pending: true }) - expect(pane.getItems().map(item => item.name)).toEqual(['A', 'D', 'B']) - }) + pane.activateItem(itemC, { pending: true }); + expect(pane.getItems().map(item => item.name)).toEqual(['A', 'C', 'B']); + pane.activateItem(itemD, { pending: true }); + expect(pane.getItems().map(item => item.name)).toEqual(['A', 'D', 'B']); + }); it('adds the item after the active item if it is not pending', () => { - pane.activateItem(itemC, { pending: true }) - pane.activateItemAtIndex(2) - pane.activateItem(itemD, { pending: true }) - expect(pane.getItems().map(item => item.name)).toEqual(['A', 'B', 'D']) - }) - }) - }) + pane.activateItem(itemC, { pending: true }); + pane.activateItemAtIndex(2); + pane.activateItem(itemD, { pending: true }); + expect(pane.getItems().map(item => item.name)).toEqual(['A', 'B', 'D']); + }); + }); + }); describe('::setPendingItem', () => { - let pane = null + let pane = null; beforeEach(() => { - pane = atom.workspace.getActivePane() - }) + pane = atom.workspace.getActivePane(); + }); it('changes the pending item', () => { - expect(pane.getPendingItem()).toBeNull() - pane.setPendingItem('fake item') - expect(pane.getPendingItem()).toEqual('fake item') - }) - }) + expect(pane.getPendingItem()).toBeNull(); + pane.setPendingItem('fake item'); + expect(pane.getPendingItem()).toEqual('fake item'); + }); + }); describe('::onItemDidTerminatePendingState callback', () => { - let pane = null - let callbackCalled = false + let pane = null; + let callbackCalled = false; beforeEach(() => { - pane = atom.workspace.getActivePane() - callbackCalled = false - }) + pane = atom.workspace.getActivePane(); + callbackCalled = false; + }); it('is called when the pending item changes', () => { - pane.setPendingItem('fake item one') - pane.onItemDidTerminatePendingState(function (item) { - callbackCalled = true - expect(item).toEqual('fake item one') - }) - pane.setPendingItem('fake item two') - expect(callbackCalled).toBeTruthy() - }) + pane.setPendingItem('fake item one'); + pane.onItemDidTerminatePendingState(function(item) { + callbackCalled = true; + expect(item).toEqual('fake item one'); + }); + pane.setPendingItem('fake item two'); + expect(callbackCalled).toBeTruthy(); + }); it('has access to the new pending item via ::getPendingItem', () => { - pane.setPendingItem('fake item one') - pane.onItemDidTerminatePendingState(function (item) { - callbackCalled = true - expect(pane.getPendingItem()).toEqual('fake item two') - }) - pane.setPendingItem('fake item two') - expect(callbackCalled).toBeTruthy() - }) + pane.setPendingItem('fake item one'); + pane.onItemDidTerminatePendingState(function(item) { + callbackCalled = true; + expect(pane.getPendingItem()).toEqual('fake item two'); + }); + pane.setPendingItem('fake item two'); + expect(callbackCalled).toBeTruthy(); + }); it("isn't called when a pending item is replaced with a new one", async () => { - pane = null - const pendingSpy = jasmine.createSpy('onItemDidTerminatePendingState') - const destroySpy = jasmine.createSpy('onWillDestroyItem') + pane = null; + const pendingSpy = jasmine.createSpy('onItemDidTerminatePendingState'); + const destroySpy = jasmine.createSpy('onWillDestroyItem'); await atom.workspace.open('sample.txt', { pending: true }).then(() => { - pane = atom.workspace.getActivePane() - }) + pane = atom.workspace.getActivePane(); + }); - pane.onItemDidTerminatePendingState(pendingSpy) - pane.onWillDestroyItem(destroySpy) + pane.onItemDidTerminatePendingState(pendingSpy); + pane.onWillDestroyItem(destroySpy); - await atom.workspace.open('sample.js', { pending: true }) + await atom.workspace.open('sample.js', { pending: true }); - expect(destroySpy).toHaveBeenCalled() - expect(pendingSpy).not.toHaveBeenCalled() - }) - }) + expect(destroySpy).toHaveBeenCalled(); + expect(pendingSpy).not.toHaveBeenCalled(); + }); + }); describe('::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()', () => { it('sets the active item to the next/previous item in the itemStack, looping around at either end', () => { @@ -425,579 +425,579 @@ describe('Pane', () => { new Item('E') ] }) - ) - const [item1, item2, item3, item4, item5] = pane.getItems() - pane.itemStack = [item3, item1, item2, item5, item4] + ); + const [item1, item2, item3, item4, item5] = pane.getItems(); + pane.itemStack = [item3, item1, item2, item5, item4]; - pane.activateItem(item4) - expect(pane.getActiveItem()).toBe(item4) - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item5) - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item2) - pane.activatePreviousRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item5) - pane.activatePreviousRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item4) - pane.activatePreviousRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item3) - pane.activatePreviousRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item1) - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item3) - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe(item4) - pane.activateNextRecentlyUsedItem() - pane.moveActiveItemToTopOfStack() - expect(pane.getActiveItem()).toBe(item5) - expect(pane.itemStack[4]).toBe(item5) - }) - }) + pane.activateItem(item4); + expect(pane.getActiveItem()).toBe(item4); + pane.activateNextRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item5); + pane.activateNextRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item2); + pane.activatePreviousRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item5); + pane.activatePreviousRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item4); + pane.activatePreviousRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item3); + pane.activatePreviousRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item1); + pane.activateNextRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item3); + pane.activateNextRecentlyUsedItem(); + expect(pane.getActiveItem()).toBe(item4); + pane.activateNextRecentlyUsedItem(); + pane.moveActiveItemToTopOfStack(); + expect(pane.getActiveItem()).toBe(item5); + expect(pane.itemStack[4]).toBe(item5); + }); + }); describe('::activateNextItem() and ::activatePreviousItem()', () => { it('sets the active item to the next/previous item, looping around at either end', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, item2, item3] = pane.getItems() + ); + const [item1, item2, item3] = pane.getItems(); - expect(pane.getActiveItem()).toBe(item1) - pane.activatePreviousItem() - expect(pane.getActiveItem()).toBe(item3) - pane.activatePreviousItem() - expect(pane.getActiveItem()).toBe(item2) - pane.activateNextItem() - expect(pane.getActiveItem()).toBe(item3) - pane.activateNextItem() - expect(pane.getActiveItem()).toBe(item1) - }) - }) + expect(pane.getActiveItem()).toBe(item1); + pane.activatePreviousItem(); + expect(pane.getActiveItem()).toBe(item3); + pane.activatePreviousItem(); + expect(pane.getActiveItem()).toBe(item2); + pane.activateNextItem(); + expect(pane.getActiveItem()).toBe(item3); + pane.activateNextItem(); + expect(pane.getActiveItem()).toBe(item1); + }); + }); describe('::activateLastItem()', () => { it('sets the active item to the last item', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, , item3] = pane.getItems() + ); + const [item1, , item3] = pane.getItems(); - expect(pane.getActiveItem()).toBe(item1) - pane.activateLastItem() - expect(pane.getActiveItem()).toBe(item3) - }) - }) + expect(pane.getActiveItem()).toBe(item1); + pane.activateLastItem(); + expect(pane.getActiveItem()).toBe(item3); + }); + }); describe('::moveItemRight() and ::moveItemLeft()', () => { it('moves the active item to the right and left, without looping around at either end', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, item2, item3] = pane.getItems() + ); + const [item1, item2, item3] = pane.getItems(); - pane.activateItemAtIndex(0) - expect(pane.getActiveItem()).toBe(item1) - pane.moveItemLeft() - expect(pane.getItems()).toEqual([item1, item2, item3]) - pane.moveItemRight() - expect(pane.getItems()).toEqual([item2, item1, item3]) - pane.moveItemLeft() - expect(pane.getItems()).toEqual([item1, item2, item3]) - pane.activateItemAtIndex(2) - expect(pane.getActiveItem()).toBe(item3) - pane.moveItemRight() - expect(pane.getItems()).toEqual([item1, item2, item3]) - }) - }) + pane.activateItemAtIndex(0); + expect(pane.getActiveItem()).toBe(item1); + pane.moveItemLeft(); + expect(pane.getItems()).toEqual([item1, item2, item3]); + pane.moveItemRight(); + expect(pane.getItems()).toEqual([item2, item1, item3]); + pane.moveItemLeft(); + expect(pane.getItems()).toEqual([item1, item2, item3]); + pane.activateItemAtIndex(2); + expect(pane.getActiveItem()).toBe(item3); + pane.moveItemRight(); + expect(pane.getItems()).toEqual([item1, item2, item3]); + }); + }); describe('::activateItemAtIndex(index)', () => { it('activates the item at the given index', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, item2, item3] = pane.getItems() - pane.activateItemAtIndex(2) - expect(pane.getActiveItem()).toBe(item3) - pane.activateItemAtIndex(1) - expect(pane.getActiveItem()).toBe(item2) - pane.activateItemAtIndex(0) - expect(pane.getActiveItem()).toBe(item1) + ); + const [item1, item2, item3] = pane.getItems(); + pane.activateItemAtIndex(2); + expect(pane.getActiveItem()).toBe(item3); + pane.activateItemAtIndex(1); + expect(pane.getActiveItem()).toBe(item2); + pane.activateItemAtIndex(0); + expect(pane.getActiveItem()).toBe(item1); // Doesn't fail with out-of-bounds indices - pane.activateItemAtIndex(100) - expect(pane.getActiveItem()).toBe(item1) - pane.activateItemAtIndex(-1) - expect(pane.getActiveItem()).toBe(item1) - }) - }) + pane.activateItemAtIndex(100); + expect(pane.getActiveItem()).toBe(item1); + pane.activateItemAtIndex(-1); + expect(pane.getActiveItem()).toBe(item1); + }); + }); describe('::destroyItem(item)', () => { - let pane, item1, item2, item3 + let pane, item1, item2, item3; beforeEach(() => { pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - ;[item1, item2, item3] = pane.getItems() - }) + ); + [item1, item2, item3] = pane.getItems(); + }); it('removes the item from the items list and destroys it', () => { - expect(pane.getActiveItem()).toBe(item1) - pane.destroyItem(item2) - expect(pane.getItems().includes(item2)).toBe(false) - expect(item2.isDestroyed()).toBe(true) - expect(pane.getActiveItem()).toBe(item1) + expect(pane.getActiveItem()).toBe(item1); + pane.destroyItem(item2); + expect(pane.getItems().includes(item2)).toBe(false); + expect(item2.isDestroyed()).toBe(true); + expect(pane.getActiveItem()).toBe(item1); - pane.destroyItem(item1) - expect(pane.getItems().includes(item1)).toBe(false) - expect(item1.isDestroyed()).toBe(true) - }) + pane.destroyItem(item1); + expect(pane.getItems().includes(item1)).toBe(false); + expect(item1.isDestroyed()).toBe(true); + }); it('removes the item from the itemStack', () => { - pane.itemStack = [item2, item3, item1] + pane.itemStack = [item2, item3, item1]; - pane.activateItem(item1) - expect(pane.getActiveItem()).toBe(item1) - pane.destroyItem(item3) - expect(pane.itemStack).toEqual([item2, item1]) - expect(pane.getActiveItem()).toBe(item1) + pane.activateItem(item1); + expect(pane.getActiveItem()).toBe(item1); + pane.destroyItem(item3); + expect(pane.itemStack).toEqual([item2, item1]); + expect(pane.getActiveItem()).toBe(item1); - pane.destroyItem(item1) - expect(pane.itemStack).toEqual([item2]) - expect(pane.getActiveItem()).toBe(item2) + pane.destroyItem(item1); + expect(pane.itemStack).toEqual([item2]); + expect(pane.getActiveItem()).toBe(item2); - pane.destroyItem(item2) - expect(pane.itemStack).toEqual([]) - expect(pane.getActiveItem()).toBeUndefined() - }) + pane.destroyItem(item2); + expect(pane.itemStack).toEqual([]); + expect(pane.getActiveItem()).toBeUndefined(); + }); it('invokes ::onWillDestroyItem() and PaneContainer::onWillDestroyPaneItem observers before destroying the item', async () => { - jasmine.useRealClock() - pane.container = new PaneContainer({ config: atom.config, confirm }) - const events = [] + jasmine.useRealClock(); + pane.container = new PaneContainer({ config: atom.config, confirm }); + const events = []; pane.onWillDestroyItem(async event => { - expect(item2.isDestroyed()).toBe(false) - await timeoutPromise(50) - expect(item2.isDestroyed()).toBe(false) - events.push(['will-destroy-item', event]) - }) + expect(item2.isDestroyed()).toBe(false); + await timeoutPromise(50); + expect(item2.isDestroyed()).toBe(false); + events.push(['will-destroy-item', event]); + }); pane.container.onWillDestroyPaneItem(async event => { - expect(item2.isDestroyed()).toBe(false) - await timeoutPromise(50) - expect(item2.isDestroyed()).toBe(false) - events.push(['will-destroy-pane-item', event]) - }) + expect(item2.isDestroyed()).toBe(false); + await timeoutPromise(50); + expect(item2.isDestroyed()).toBe(false); + events.push(['will-destroy-pane-item', event]); + }); - await pane.destroyItem(item2) - expect(item2.isDestroyed()).toBe(true) + await pane.destroyItem(item2); + expect(item2.isDestroyed()).toBe(true); expect(events).toEqual([ ['will-destroy-item', { item: item2, index: 1 }], ['will-destroy-pane-item', { item: item2, index: 1, pane }] - ]) - }) + ]); + }); it('invokes ::onWillRemoveItem() observers', () => { - const events = [] - pane.onWillRemoveItem(event => events.push(event)) - pane.destroyItem(item2) + const events = []; + pane.onWillRemoveItem(event => events.push(event)); + pane.destroyItem(item2); expect(events).toEqual([ { item: item2, index: 1, moved: false, destroyed: true } - ]) - }) + ]); + }); it('invokes ::onDidRemoveItem() observers', () => { - const events = [] - pane.onDidRemoveItem(event => events.push(event)) - pane.destroyItem(item2) + const events = []; + pane.onDidRemoveItem(event => events.push(event)); + pane.destroyItem(item2); expect(events).toEqual([ { item: item2, index: 1, moved: false, destroyed: true } - ]) - }) + ]); + }); describe('when the destroyed item is the active item and is the first item', () => { it('activates the next item', () => { - expect(pane.getActiveItem()).toBe(item1) - pane.destroyItem(item1) - expect(pane.getActiveItem()).toBe(item2) - }) - }) + expect(pane.getActiveItem()).toBe(item1); + pane.destroyItem(item1); + expect(pane.getActiveItem()).toBe(item2); + }); + }); describe('when the destroyed item is the active item and is not the first item', () => { - beforeEach(() => pane.activateItem(item2)) + beforeEach(() => pane.activateItem(item2)); it('activates the previous item', () => { - expect(pane.getActiveItem()).toBe(item2) - pane.destroyItem(item2) - expect(pane.getActiveItem()).toBe(item1) - }) - }) + expect(pane.getActiveItem()).toBe(item2); + pane.destroyItem(item2); + expect(pane.getActiveItem()).toBe(item1); + }); + }); describe('if the item is modified', () => { - let itemURI = null + let itemURI = null; beforeEach(() => { - item1.shouldPromptToSave = () => true - item1.save = jasmine.createSpy('save') - item1.saveAs = jasmine.createSpy('saveAs') - item1.getURI = () => itemURI - }) + item1.shouldPromptToSave = () => true; + item1.save = jasmine.createSpy('save'); + item1.saveAs = jasmine.createSpy('saveAs'); + item1.getURI = () => itemURI; + }); describe('if the [Save] option is selected', () => { describe('when the item has a uri', () => { it('saves the item before destroying it', async () => { - itemURI = 'test' - confirm.andCallFake((options, callback) => callback(0)) + itemURI = 'test'; + confirm.andCallFake((options, callback) => callback(0)); - const success = await pane.destroyItem(item1) - expect(item1.save).toHaveBeenCalled() - expect(pane.getItems().includes(item1)).toBe(false) - expect(item1.isDestroyed()).toBe(true) - expect(success).toBe(true) - }) - }) + const success = await pane.destroyItem(item1); + expect(item1.save).toHaveBeenCalled(); + expect(pane.getItems().includes(item1)).toBe(false); + expect(item1.isDestroyed()).toBe(true); + expect(success).toBe(true); + }); + }); describe('when the item has no uri', () => { it('presents a save-as dialog, then saves the item with the given uri before removing and destroying it', async () => { - jasmine.useRealClock() + jasmine.useRealClock(); - itemURI = null + itemURI = null; showSaveDialog.andCallFake((options, callback) => callback('/selected/path') - ) - confirm.andCallFake((options, callback) => callback(0)) + ); + confirm.andCallFake((options, callback) => callback(0)); - const success = await pane.destroyItem(item1) - expect(showSaveDialog.mostRecentCall.args[0]).toEqual({}) + const success = await pane.destroyItem(item1); + expect(showSaveDialog.mostRecentCall.args[0]).toEqual({}); - await conditionPromise(() => item1.saveAs.callCount === 1) - expect(item1.saveAs).toHaveBeenCalledWith('/selected/path') - expect(pane.getItems().includes(item1)).toBe(false) - expect(item1.isDestroyed()).toBe(true) - expect(success).toBe(true) - }) - }) - }) + await conditionPromise(() => item1.saveAs.callCount === 1); + expect(item1.saveAs).toHaveBeenCalledWith('/selected/path'); + expect(pane.getItems().includes(item1)).toBe(false); + expect(item1.isDestroyed()).toBe(true); + expect(success).toBe(true); + }); + }); + }); describe("if the [Don't Save] option is selected", () => { it('removes and destroys the item without saving it', async () => { - confirm.andCallFake((options, callback) => callback(2)) + confirm.andCallFake((options, callback) => callback(2)); - const success = await pane.destroyItem(item1) - expect(item1.save).not.toHaveBeenCalled() - expect(pane.getItems().includes(item1)).toBe(false) - expect(item1.isDestroyed()).toBe(true) - expect(success).toBe(true) - }) - }) + const success = await pane.destroyItem(item1); + expect(item1.save).not.toHaveBeenCalled(); + expect(pane.getItems().includes(item1)).toBe(false); + expect(item1.isDestroyed()).toBe(true); + expect(success).toBe(true); + }); + }); describe('if the [Cancel] option is selected', () => { it('does not save, remove, or destroy the item', async () => { - confirm.andCallFake((options, callback) => callback(1)) + confirm.andCallFake((options, callback) => callback(1)); - const success = await pane.destroyItem(item1) - expect(item1.save).not.toHaveBeenCalled() - expect(pane.getItems().includes(item1)).toBe(true) - expect(item1.isDestroyed()).toBe(false) - expect(success).toBe(false) - }) - }) + const success = await pane.destroyItem(item1); + expect(item1.save).not.toHaveBeenCalled(); + expect(pane.getItems().includes(item1)).toBe(true); + expect(item1.isDestroyed()).toBe(false); + expect(success).toBe(false); + }); + }); describe('when force=true', () => { it('destroys the item immediately', async () => { - const success = await pane.destroyItem(item1, true) - expect(item1.save).not.toHaveBeenCalled() - expect(pane.getItems().includes(item1)).toBe(false) - expect(item1.isDestroyed()).toBe(true) - expect(success).toBe(true) - }) - }) - }) + const success = await pane.destroyItem(item1, true); + expect(item1.save).not.toHaveBeenCalled(); + expect(pane.getItems().includes(item1)).toBe(false); + expect(item1.isDestroyed()).toBe(true); + expect(success).toBe(true); + }); + }); + }); describe('when the last item is destroyed', () => { describe("when the 'core.destroyEmptyPanes' config option is false (the default)", () => { it('does not destroy the pane, but leaves it in place with empty items', () => { - expect(atom.config.get('core.destroyEmptyPanes')).toBe(false) + expect(atom.config.get('core.destroyEmptyPanes')).toBe(false); for (let item of pane.getItems()) { - pane.destroyItem(item) + pane.destroyItem(item); } - expect(pane.isDestroyed()).toBe(false) - expect(pane.getActiveItem()).toBeUndefined() - expect(() => pane.saveActiveItem()).not.toThrow() - expect(() => pane.saveActiveItemAs()).not.toThrow() - }) - }) + expect(pane.isDestroyed()).toBe(false); + expect(pane.getActiveItem()).toBeUndefined(); + expect(() => pane.saveActiveItem()).not.toThrow(); + expect(() => pane.saveActiveItemAs()).not.toThrow(); + }); + }); describe("when the 'core.destroyEmptyPanes' config option is true", () => { it('destroys the pane', () => { - atom.config.set('core.destroyEmptyPanes', true) + atom.config.set('core.destroyEmptyPanes', true); for (let item of pane.getItems()) { - pane.destroyItem(item) + pane.destroyItem(item); } - expect(pane.isDestroyed()).toBe(true) - }) - }) - }) + expect(pane.isDestroyed()).toBe(true); + }); + }); + }); describe('when passed a permanent dock item', () => { it("doesn't destroy the item", async () => { - spyOn(item1, 'isPermanentDockItem').andReturn(true) - const success = await pane.destroyItem(item1) - expect(pane.getItems().includes(item1)).toBe(true) - expect(item1.isDestroyed()).toBe(false) - expect(success).toBe(false) - }) + spyOn(item1, 'isPermanentDockItem').andReturn(true); + const success = await pane.destroyItem(item1); + expect(pane.getItems().includes(item1)).toBe(true); + expect(item1.isDestroyed()).toBe(false); + expect(success).toBe(false); + }); it('destroy the item if force=true', async () => { - spyOn(item1, 'isPermanentDockItem').andReturn(true) - const success = await pane.destroyItem(item1, true) - expect(pane.getItems().includes(item1)).toBe(false) - expect(item1.isDestroyed()).toBe(true) - expect(success).toBe(true) - }) - }) - }) + spyOn(item1, 'isPermanentDockItem').andReturn(true); + const success = await pane.destroyItem(item1, true); + expect(pane.getItems().includes(item1)).toBe(false); + expect(item1.isDestroyed()).toBe(true); + expect(success).toBe(true); + }); + }); + }); describe('::destroyActiveItem()', () => { it('destroys the active item', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - const activeItem = pane.getActiveItem() - pane.destroyActiveItem() - expect(activeItem.isDestroyed()).toBe(true) - expect(pane.getItems().includes(activeItem)).toBe(false) - }) + ); + const activeItem = pane.getActiveItem(); + pane.destroyActiveItem(); + expect(activeItem.isDestroyed()).toBe(true); + expect(pane.getItems().includes(activeItem)).toBe(false); + }); it('does not throw an exception if there are no more items', () => { - const pane = new Pane(paneParams()) - pane.destroyActiveItem() - }) - }) + const pane = new Pane(paneParams()); + pane.destroyActiveItem(); + }); + }); describe('::destroyItems()', () => { it('destroys all items', async () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, item2, item3] = pane.getItems() + ); + const [item1, item2, item3] = pane.getItems(); - await pane.destroyItems() - expect(item1.isDestroyed()).toBe(true) - expect(item2.isDestroyed()).toBe(true) - expect(item3.isDestroyed()).toBe(true) - expect(pane.getItems()).toEqual([]) - }) - }) + await pane.destroyItems(); + expect(item1.isDestroyed()).toBe(true); + expect(item2.isDestroyed()).toBe(true); + expect(item3.isDestroyed()).toBe(true); + expect(pane.getItems()).toEqual([]); + }); + }); describe('::observeItems()', () => { it('invokes the observer with all current and future items', () => { - const pane = new Pane(paneParams({ items: [new Item(), new Item()] })) - const [item1, item2] = pane.getItems() + const pane = new Pane(paneParams({ items: [new Item(), new Item()] })); + const [item1, item2] = pane.getItems(); - const observed = [] - pane.observeItems(item => observed.push(item)) + const observed = []; + pane.observeItems(item => observed.push(item)); - const item3 = new Item() - pane.addItem(item3) + const item3 = new Item(); + pane.addItem(item3); - expect(observed).toEqual([item1, item2, item3]) - }) - }) + expect(observed).toEqual([item1, item2, item3]); + }); + }); describe('when an item emits a destroyed event', () => { it('removes it from the list of items', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [item1, , item3] = pane.getItems() - pane.itemAtIndex(1).destroy() - expect(pane.getItems()).toEqual([item1, item3]) - }) - }) + ); + const [item1, , item3] = pane.getItems(); + pane.itemAtIndex(1).destroy(); + expect(pane.getItems()).toEqual([item1, item3]); + }); + }); describe('::destroyInactiveItems()', () => { it('destroys all items but the active item', () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) - ) - const [, item2] = pane.getItems() - pane.activateItem(item2) - pane.destroyInactiveItems() - expect(pane.getItems()).toEqual([item2]) - }) - }) + ); + const [, item2] = pane.getItems(); + pane.activateItem(item2); + pane.destroyInactiveItems(); + expect(pane.getItems()).toEqual([item2]); + }); + }); describe('::saveActiveItem()', () => { - let pane + let pane; beforeEach(() => { - pane = new Pane(paneParams({ items: [new Item('A')] })) + pane = new Pane(paneParams({ items: [new Item('A')] })); showSaveDialog.andCallFake((options, callback) => callback('/selected/path') - ) - }) + ); + }); describe('when the active item has a uri', () => { beforeEach(() => { - pane.getActiveItem().uri = 'test' - }) + pane.getActiveItem().uri = 'test'; + }); describe('when the active item has a save method', () => { it('saves the current item', () => { - pane.getActiveItem().save = jasmine.createSpy('save') - pane.saveActiveItem() - expect(pane.getActiveItem().save).toHaveBeenCalled() - }) - }) + pane.getActiveItem().save = jasmine.createSpy('save'); + pane.saveActiveItem(); + expect(pane.getActiveItem().save).toHaveBeenCalled(); + }); + }); describe('when the current item has no save method', () => { it('does nothing', () => { - expect(pane.getActiveItem().save).toBeUndefined() - pane.saveActiveItem() - }) - }) - }) + expect(pane.getActiveItem().save).toBeUndefined(); + pane.saveActiveItem(); + }); + }); + }); describe('when the current item has no uri', () => { describe('when the current item has a saveAs method', () => { it('opens a save dialog and saves the current item as the selected path', async () => { - pane.getActiveItem().saveAs = jasmine.createSpy('saveAs') - await pane.saveActiveItem() - expect(showSaveDialog.mostRecentCall.args[0]).toEqual({}) + pane.getActiveItem().saveAs = jasmine.createSpy('saveAs'); + await pane.saveActiveItem(); + expect(showSaveDialog.mostRecentCall.args[0]).toEqual({}); expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith( '/selected/path' - ) - }) - }) + ); + }); + }); describe('when the current item has no saveAs method', () => { it('does nothing', async () => { - expect(pane.getActiveItem().saveAs).toBeUndefined() - await pane.saveActiveItem() - expect(showSaveDialog).not.toHaveBeenCalled() - }) - }) + expect(pane.getActiveItem().saveAs).toBeUndefined(); + await pane.saveActiveItem(); + expect(showSaveDialog).not.toHaveBeenCalled(); + }); + }); it('does nothing if the user cancels choosing a path', async () => { - pane.getActiveItem().saveAs = jasmine.createSpy('saveAs') - showSaveDialog.andCallFake((options, callback) => callback(undefined)) - await pane.saveActiveItem() - expect(pane.getActiveItem().saveAs).not.toHaveBeenCalled() - }) - }) + pane.getActiveItem().saveAs = jasmine.createSpy('saveAs'); + showSaveDialog.andCallFake((options, callback) => callback(undefined)); + await pane.saveActiveItem(); + expect(pane.getActiveItem().saveAs).not.toHaveBeenCalled(); + }); + }); describe("when the item's saveAs rejects with a well-known IO error", () => { it('creates a notification', () => { pane.getActiveItem().saveAs = () => { - const error = new Error("EACCES, permission denied '/foo'") - error.path = '/foo' - error.code = 'EACCES' - return Promise.reject(error) - } + const error = new Error("EACCES, permission denied '/foo'"); + error.path = '/foo'; + error.code = 'EACCES'; + return Promise.reject(error); + }; waitsFor(done => { - const subscription = atom.notifications.onDidAddNotification( - function (notification) { - expect(notification.getType()).toBe('warning') - expect(notification.getMessage()).toContain('Permission denied') - expect(notification.getMessage()).toContain('/foo') - subscription.dispose() - done() - } - ) - pane.saveActiveItem() - }) - }) - }) + const subscription = atom.notifications.onDidAddNotification(function( + notification + ) { + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('/foo'); + subscription.dispose(); + done(); + }); + pane.saveActiveItem(); + }); + }); + }); describe("when the item's saveAs throws a well-known IO error", () => { it('creates a notification', () => { pane.getActiveItem().saveAs = () => { - const error = new Error("EACCES, permission denied '/foo'") - error.path = '/foo' - error.code = 'EACCES' - throw error - } + const error = new Error("EACCES, permission denied '/foo'"); + error.path = '/foo'; + error.code = 'EACCES'; + throw error; + }; waitsFor(done => { - const subscription = atom.notifications.onDidAddNotification( - function (notification) { - expect(notification.getType()).toBe('warning') - expect(notification.getMessage()).toContain('Permission denied') - expect(notification.getMessage()).toContain('/foo') - subscription.dispose() - done() - } - ) - pane.saveActiveItem() - }) - }) - }) - }) + const subscription = atom.notifications.onDidAddNotification(function( + notification + ) { + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('/foo'); + subscription.dispose(); + done(); + }); + pane.saveActiveItem(); + }); + }); + }); + }); describe('::saveActiveItemAs()', () => { - let pane = null + let pane = null; beforeEach(() => { - pane = new Pane(paneParams({ items: [new Item('A')] })) + pane = new Pane(paneParams({ items: [new Item('A')] })); showSaveDialog.andCallFake((options, callback) => callback('/selected/path') - ) - }) + ); + }); describe('when the current item has a saveAs method', () => { it('opens the save dialog and calls saveAs on the item with the selected path', async () => { - jasmine.useRealClock() + jasmine.useRealClock(); - pane.getActiveItem().path = __filename - pane.getActiveItem().saveAs = jasmine.createSpy('saveAs') - pane.saveActiveItemAs() + pane.getActiveItem().path = __filename; + pane.getActiveItem().saveAs = jasmine.createSpy('saveAs'); + pane.saveActiveItemAs(); expect(showSaveDialog.mostRecentCall.args[0]).toEqual({ defaultPath: __filename - }) + }); await conditionPromise( () => pane.getActiveItem().saveAs.callCount === 1 - ) + ); expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith( '/selected/path' - ) - }) - }) + ); + }); + }); describe('when the current item does not have a saveAs method', () => { it('does nothing', () => { - expect(pane.getActiveItem().saveAs).toBeUndefined() - pane.saveActiveItemAs() - expect(showSaveDialog).not.toHaveBeenCalled() - }) - }) + expect(pane.getActiveItem().saveAs).toBeUndefined(); + pane.saveActiveItemAs(); + expect(showSaveDialog).not.toHaveBeenCalled(); + }); + }); describe("when the item's saveAs method throws a well-known IO error", () => { it('creates a notification', () => { pane.getActiveItem().saveAs = () => { - const error = new Error("EACCES, permission denied '/foo'") - error.path = '/foo' - error.code = 'EACCES' - return Promise.reject(error) - } + const error = new Error("EACCES, permission denied '/foo'"); + error.path = '/foo'; + error.code = 'EACCES'; + return Promise.reject(error); + }; waitsFor(done => { - const subscription = atom.notifications.onDidAddNotification( - function (notification) { - expect(notification.getType()).toBe('warning') - expect(notification.getMessage()).toContain('Permission denied') - expect(notification.getMessage()).toContain('/foo') - subscription.dispose() - done() - } - ) - pane.saveActiveItemAs() - }) - }) - }) - }) + const subscription = atom.notifications.onDidAddNotification(function( + notification + ) { + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('/foo'); + subscription.dispose(); + done(); + }); + pane.saveActiveItemAs(); + }); + }); + }); + }); describe('::itemForURI(uri)', () => { it('returns the item for which a call to .getURI() returns the given uri', () => { @@ -1005,615 +1005,615 @@ describe('Pane', () => { paneParams({ items: [new Item('A'), new Item('B'), new Item('C'), new Item('D')] }) - ) - const [item1, item2] = pane.getItems() - item1.uri = 'a' - item2.uri = 'b' - expect(pane.itemForURI('a')).toBe(item1) - expect(pane.itemForURI('b')).toBe(item2) - expect(pane.itemForURI('bogus')).toBeUndefined() - }) - }) + ); + const [item1, item2] = pane.getItems(); + item1.uri = 'a'; + item2.uri = 'b'; + expect(pane.itemForURI('a')).toBe(item1); + expect(pane.itemForURI('b')).toBe(item2); + expect(pane.itemForURI('bogus')).toBeUndefined(); + }); + }); describe('::moveItem(item, index)', () => { - let pane, item1, item2, item3, item4 + let pane, item1, item2, item3, item4; beforeEach(() => { pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C'), new Item('D')] }) - ) - ;[item1, item2, item3, item4] = pane.getItems() - }) + ); + [item1, item2, item3, item4] = pane.getItems(); + }); it('moves the item to the given index and invokes ::onDidMoveItem observers', () => { - pane.moveItem(item1, 2) - expect(pane.getItems()).toEqual([item2, item3, item1, item4]) + pane.moveItem(item1, 2); + expect(pane.getItems()).toEqual([item2, item3, item1, item4]); - pane.moveItem(item2, 3) - expect(pane.getItems()).toEqual([item3, item1, item4, item2]) + pane.moveItem(item2, 3); + expect(pane.getItems()).toEqual([item3, item1, item4, item2]); - pane.moveItem(item2, 1) - expect(pane.getItems()).toEqual([item3, item2, item1, item4]) - }) + pane.moveItem(item2, 1); + expect(pane.getItems()).toEqual([item3, item2, item1, item4]); + }); it('invokes ::onDidMoveItem() observers', () => { - const events = [] - pane.onDidMoveItem(event => events.push(event)) + const events = []; + pane.onDidMoveItem(event => events.push(event)); - pane.moveItem(item1, 2) - pane.moveItem(item2, 3) + pane.moveItem(item1, 2); + pane.moveItem(item2, 3); expect(events).toEqual([ { item: item1, oldIndex: 0, newIndex: 2 }, { item: item2, oldIndex: 0, newIndex: 3 } - ]) - }) - }) + ]); + }); + }); describe('::moveItemToPane(item, pane, index)', () => { - let container, pane1, pane2 - let item1, item2, item3, item4, item5 + let container, pane1, pane2; + let item1, item2, item3, item4, item5; beforeEach(() => { - container = new PaneContainer({ config: atom.config, confirm }) - pane1 = container.getActivePane() - pane1.addItems([new Item('A'), new Item('B'), new Item('C')]) - pane2 = pane1.splitRight({ items: [new Item('D'), new Item('E')] }) - ;[item1, item2, item3] = pane1.getItems() - ;[item4, item5] = pane2.getItems() - }) + container = new PaneContainer({ config: atom.config, confirm }); + pane1 = container.getActivePane(); + pane1.addItems([new Item('A'), new Item('B'), new Item('C')]); + pane2 = pane1.splitRight({ items: [new Item('D'), new Item('E')] }); + [item1, item2, item3] = pane1.getItems(); + [item4, item5] = pane2.getItems(); + }); it('moves the item to the given pane at the given index', () => { - pane1.moveItemToPane(item2, pane2, 1) - expect(pane1.getItems()).toEqual([item1, item3]) - expect(pane2.getItems()).toEqual([item4, item2, item5]) - }) + pane1.moveItemToPane(item2, pane2, 1); + expect(pane1.getItems()).toEqual([item1, item3]); + expect(pane2.getItems()).toEqual([item4, item2, item5]); + }); it('invokes ::onWillRemoveItem() observers', () => { - const events = [] - pane1.onWillRemoveItem(event => events.push(event)) - pane1.moveItemToPane(item2, pane2, 1) + const events = []; + pane1.onWillRemoveItem(event => events.push(event)); + pane1.moveItemToPane(item2, pane2, 1); expect(events).toEqual([ { item: item2, index: 1, moved: true, destroyed: false } - ]) - }) + ]); + }); it('invokes ::onDidRemoveItem() observers', () => { - const events = [] - pane1.onDidRemoveItem(event => events.push(event)) - pane1.moveItemToPane(item2, pane2, 1) + const events = []; + pane1.onDidRemoveItem(event => events.push(event)); + pane1.moveItemToPane(item2, pane2, 1); expect(events).toEqual([ { item: item2, index: 1, moved: true, destroyed: false } - ]) - }) + ]); + }); it('does not invoke ::onDidAddPaneItem observers on the container', () => { - const addedItems = [] - container.onDidAddPaneItem(item => addedItems.push(item)) - pane1.moveItemToPane(item2, pane2, 1) - expect(addedItems).toEqual([]) - }) + const addedItems = []; + container.onDidAddPaneItem(item => addedItems.push(item)); + pane1.moveItemToPane(item2, pane2, 1); + expect(addedItems).toEqual([]); + }); describe('when the moved item the last item in the source pane', () => { - beforeEach(() => item5.destroy()) + beforeEach(() => item5.destroy()); describe("when the 'core.destroyEmptyPanes' config option is false (the default)", () => { it('does not destroy the pane or the item', () => { - pane2.moveItemToPane(item4, pane1, 0) - expect(pane2.isDestroyed()).toBe(false) - expect(item4.isDestroyed()).toBe(false) - }) - }) + pane2.moveItemToPane(item4, pane1, 0); + expect(pane2.isDestroyed()).toBe(false); + expect(item4.isDestroyed()).toBe(false); + }); + }); describe("when the 'core.destroyEmptyPanes' config option is true", () => { it('destroys the pane, but not the item', () => { - atom.config.set('core.destroyEmptyPanes', true) - pane2.moveItemToPane(item4, pane1, 0) - expect(pane2.isDestroyed()).toBe(true) - expect(item4.isDestroyed()).toBe(false) - }) - }) - }) + atom.config.set('core.destroyEmptyPanes', true); + pane2.moveItemToPane(item4, pane1, 0); + expect(pane2.isDestroyed()).toBe(true); + expect(item4.isDestroyed()).toBe(false); + }); + }); + }); describe('when the item being moved is pending', () => { it('is made permanent in the new pane', () => { - const item6 = new Item('F') - pane1.addItem(item6, { pending: true }) - expect(pane1.getPendingItem()).toEqual(item6) - pane1.moveItemToPane(item6, pane2, 0) - expect(pane2.getPendingItem()).not.toEqual(item6) - }) - }) + const item6 = new Item('F'); + pane1.addItem(item6, { pending: true }); + expect(pane1.getPendingItem()).toEqual(item6); + pane1.moveItemToPane(item6, pane2, 0); + expect(pane2.getPendingItem()).not.toEqual(item6); + }); + }); describe('when the target pane has a pending item', () => { it('does not destroy the pending item', () => { - const item6 = new Item('F') - pane1.addItem(item6, { pending: true }) - expect(pane1.getPendingItem()).toEqual(item6) - pane2.moveItemToPane(item5, pane1, 0) - expect(pane1.getPendingItem()).toEqual(item6) - }) - }) - }) + const item6 = new Item('F'); + pane1.addItem(item6, { pending: true }); + expect(pane1.getPendingItem()).toEqual(item6); + pane2.moveItemToPane(item5, pane1, 0); + expect(pane1.getPendingItem()).toEqual(item6); + }); + }); + }); describe('split methods', () => { - let pane1, item1, container + let pane1, item1, container; beforeEach(() => { container = new PaneContainer({ config: atom.config, confirm, deserializerManager: atom.deserializers - }) - pane1 = container.getActivePane() - item1 = new Item('A') - pane1.addItem(item1) - }) + }); + pane1 = container.getActivePane(); + item1 = new Item('A'); + pane1.addItem(item1); + }); describe('::splitLeft(params)', () => { describe('when the parent is the container root', () => { it('replaces itself with a row and inserts a new pane to the left of itself', () => { - const pane2 = pane1.splitLeft({ items: [new Item('B')] }) - const pane3 = pane1.splitLeft({ items: [new Item('C')] }) - expect(container.root.orientation).toBe('horizontal') - expect(container.root.children).toEqual([pane2, pane3, pane1]) - }) - }) + const pane2 = pane1.splitLeft({ items: [new Item('B')] }); + const pane3 = pane1.splitLeft({ items: [new Item('C')] }); + expect(container.root.orientation).toBe('horizontal'); + expect(container.root.children).toEqual([pane2, pane3, pane1]); + }); + }); describe('when `moveActiveItem: true` is passed in the params', () => { it('moves the active item', () => { - const pane2 = pane1.splitLeft({ moveActiveItem: true }) - expect(pane2.getActiveItem()).toBe(item1) - }) - }) + const pane2 = pane1.splitLeft({ moveActiveItem: true }); + expect(pane2.getActiveItem()).toBe(item1); + }); + }); describe('when `copyActiveItem: true` is passed in the params', () => { it('duplicates the active item', () => { - const pane2 = pane1.splitLeft({ copyActiveItem: true }) - expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()) - }) + const pane2 = pane1.splitLeft({ copyActiveItem: true }); + expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()); + }); it("does nothing if the active item doesn't implement .copy()", () => { - item1.copy = null - const pane2 = pane1.splitLeft({ copyActiveItem: true }) - expect(pane2.getActiveItem()).toBeUndefined() - }) - }) + item1.copy = null; + const pane2 = pane1.splitLeft({ copyActiveItem: true }); + expect(pane2.getActiveItem()).toBeUndefined(); + }); + }); describe('when the parent is a column', () => { it('replaces itself with a row and inserts a new pane to the left of itself', () => { - pane1.splitDown() - const pane2 = pane1.splitLeft({ items: [new Item('B')] }) - const pane3 = pane1.splitLeft({ items: [new Item('C')] }) - const row = container.root.children[0] - expect(row.orientation).toBe('horizontal') - expect(row.children).toEqual([pane2, pane3, pane1]) - }) - }) - }) + pane1.splitDown(); + const pane2 = pane1.splitLeft({ items: [new Item('B')] }); + const pane3 = pane1.splitLeft({ items: [new Item('C')] }); + const row = container.root.children[0]; + expect(row.orientation).toBe('horizontal'); + expect(row.children).toEqual([pane2, pane3, pane1]); + }); + }); + }); describe('::splitRight(params)', () => { describe('when the parent is the container root', () => { it('replaces itself with a row and inserts a new pane to the right of itself', () => { - const pane2 = pane1.splitRight({ items: [new Item('B')] }) - const pane3 = pane1.splitRight({ items: [new Item('C')] }) - expect(container.root.orientation).toBe('horizontal') - expect(container.root.children).toEqual([pane1, pane3, pane2]) - }) - }) + const pane2 = pane1.splitRight({ items: [new Item('B')] }); + const pane3 = pane1.splitRight({ items: [new Item('C')] }); + expect(container.root.orientation).toBe('horizontal'); + expect(container.root.children).toEqual([pane1, pane3, pane2]); + }); + }); describe('when `moveActiveItem: true` is passed in the params', () => { it('moves the active item', () => { - const pane2 = pane1.splitRight({ moveActiveItem: true }) - expect(pane2.getActiveItem()).toBe(item1) - }) - }) + const pane2 = pane1.splitRight({ moveActiveItem: true }); + expect(pane2.getActiveItem()).toBe(item1); + }); + }); describe('when `copyActiveItem: true` is passed in the params', () => { it('duplicates the active item', () => { - const pane2 = pane1.splitRight({ copyActiveItem: true }) - expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()) - }) - }) + const pane2 = pane1.splitRight({ copyActiveItem: true }); + expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()); + }); + }); describe('when the parent is a column', () => { it('replaces itself with a row and inserts a new pane to the right of itself', () => { - pane1.splitDown() - const pane2 = pane1.splitRight({ items: [new Item('B')] }) - const pane3 = pane1.splitRight({ items: [new Item('C')] }) - const row = container.root.children[0] - expect(row.orientation).toBe('horizontal') - expect(row.children).toEqual([pane1, pane3, pane2]) - }) - }) - }) + pane1.splitDown(); + const pane2 = pane1.splitRight({ items: [new Item('B')] }); + const pane3 = pane1.splitRight({ items: [new Item('C')] }); + const row = container.root.children[0]; + expect(row.orientation).toBe('horizontal'); + expect(row.children).toEqual([pane1, pane3, pane2]); + }); + }); + }); describe('::splitUp(params)', () => { describe('when the parent is the container root', () => { it('replaces itself with a column and inserts a new pane above itself', () => { - const pane2 = pane1.splitUp({ items: [new Item('B')] }) - const pane3 = pane1.splitUp({ items: [new Item('C')] }) - expect(container.root.orientation).toBe('vertical') - expect(container.root.children).toEqual([pane2, pane3, pane1]) - }) - }) + const pane2 = pane1.splitUp({ items: [new Item('B')] }); + const pane3 = pane1.splitUp({ items: [new Item('C')] }); + expect(container.root.orientation).toBe('vertical'); + expect(container.root.children).toEqual([pane2, pane3, pane1]); + }); + }); describe('when `moveActiveItem: true` is passed in the params', () => { it('moves the active item', () => { - const pane2 = pane1.splitUp({ moveActiveItem: true }) - expect(pane2.getActiveItem()).toBe(item1) - }) - }) + const pane2 = pane1.splitUp({ moveActiveItem: true }); + expect(pane2.getActiveItem()).toBe(item1); + }); + }); describe('when `copyActiveItem: true` is passed in the params', () => { it('duplicates the active item', () => { - const pane2 = pane1.splitUp({ copyActiveItem: true }) - expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()) - }) - }) + const pane2 = pane1.splitUp({ copyActiveItem: true }); + expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()); + }); + }); describe('when the parent is a row', () => { it('replaces itself with a column and inserts a new pane above itself', () => { - pane1.splitRight() - const pane2 = pane1.splitUp({ items: [new Item('B')] }) - const pane3 = pane1.splitUp({ items: [new Item('C')] }) - const column = container.root.children[0] - expect(column.orientation).toBe('vertical') - expect(column.children).toEqual([pane2, pane3, pane1]) - }) - }) - }) + pane1.splitRight(); + const pane2 = pane1.splitUp({ items: [new Item('B')] }); + const pane3 = pane1.splitUp({ items: [new Item('C')] }); + const column = container.root.children[0]; + expect(column.orientation).toBe('vertical'); + expect(column.children).toEqual([pane2, pane3, pane1]); + }); + }); + }); describe('::splitDown(params)', () => { describe('when the parent is the container root', () => { it('replaces itself with a column and inserts a new pane below itself', () => { - const pane2 = pane1.splitDown({ items: [new Item('B')] }) - const pane3 = pane1.splitDown({ items: [new Item('C')] }) - expect(container.root.orientation).toBe('vertical') - expect(container.root.children).toEqual([pane1, pane3, pane2]) - }) - }) + const pane2 = pane1.splitDown({ items: [new Item('B')] }); + const pane3 = pane1.splitDown({ items: [new Item('C')] }); + expect(container.root.orientation).toBe('vertical'); + expect(container.root.children).toEqual([pane1, pane3, pane2]); + }); + }); describe('when `moveActiveItem: true` is passed in the params', () => { it('moves the active item', () => { - const pane2 = pane1.splitDown({ moveActiveItem: true }) - expect(pane2.getActiveItem()).toBe(item1) - }) - }) + const pane2 = pane1.splitDown({ moveActiveItem: true }); + expect(pane2.getActiveItem()).toBe(item1); + }); + }); describe('when `copyActiveItem: true` is passed in the params', () => { it('duplicates the active item', () => { - const pane2 = pane1.splitDown({ copyActiveItem: true }) - expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()) - }) - }) + const pane2 = pane1.splitDown({ copyActiveItem: true }); + expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem()); + }); + }); describe('when the parent is a row', () => { it('replaces itself with a column and inserts a new pane below itself', () => { - pane1.splitRight() - const pane2 = pane1.splitDown({ items: [new Item('B')] }) - const pane3 = pane1.splitDown({ items: [new Item('C')] }) - const column = container.root.children[0] - expect(column.orientation).toBe('vertical') - expect(column.children).toEqual([pane1, pane3, pane2]) - }) - }) - }) + pane1.splitRight(); + const pane2 = pane1.splitDown({ items: [new Item('B')] }); + const pane3 = pane1.splitDown({ items: [new Item('C')] }); + const column = container.root.children[0]; + expect(column.orientation).toBe('vertical'); + expect(column.children).toEqual([pane1, pane3, pane2]); + }); + }); + }); describe('when the pane is empty', () => { describe('when `moveActiveItem: true` is passed in the params', () => { it('gracefully ignores the moveActiveItem parameter', () => { - pane1.destroyItem(item1) - expect(pane1.getActiveItem()).toBe(undefined) + pane1.destroyItem(item1); + expect(pane1.getActiveItem()).toBe(undefined); const pane2 = pane1.split('horizontal', 'before', { moveActiveItem: true - }) - expect(container.root.children).toEqual([pane2, pane1]) + }); + expect(container.root.children).toEqual([pane2, pane1]); - expect(pane2.getActiveItem()).toBe(undefined) - }) - }) + expect(pane2.getActiveItem()).toBe(undefined); + }); + }); describe('when `copyActiveItem: true` is passed in the params', () => { it('gracefully ignores the copyActiveItem parameter', () => { - pane1.destroyItem(item1) - expect(pane1.getActiveItem()).toBe(undefined) + pane1.destroyItem(item1); + expect(pane1.getActiveItem()).toBe(undefined); const pane2 = pane1.split('horizontal', 'before', { copyActiveItem: true - }) - expect(container.root.children).toEqual([pane2, pane1]) + }); + expect(container.root.children).toEqual([pane2, pane1]); - expect(pane2.getActiveItem()).toBe(undefined) - }) - }) - }) + expect(pane2.getActiveItem()).toBe(undefined); + }); + }); + }); it('activates the new pane', () => { - expect(pane1.isActive()).toBe(true) - const pane2 = pane1.splitRight() - expect(pane1.isActive()).toBe(false) - expect(pane2.isActive()).toBe(true) - }) - }) + expect(pane1.isActive()).toBe(true); + const pane2 = pane1.splitRight(); + expect(pane1.isActive()).toBe(false); + expect(pane2.isActive()).toBe(true); + }); + }); describe('::close()', () => { it('prompts to save unsaved items before destroying the pane', async () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - const [item1] = pane.getItems() + ); + const [item1] = pane.getItems(); - item1.shouldPromptToSave = () => true - item1.getURI = () => '/test/path' - item1.save = jasmine.createSpy('save') + item1.shouldPromptToSave = () => true; + item1.getURI = () => '/test/path'; + item1.save = jasmine.createSpy('save'); - confirm.andCallFake((options, callback) => callback(0)) - await pane.close() - expect(confirm).toHaveBeenCalled() - expect(item1.save).toHaveBeenCalled() - expect(pane.isDestroyed()).toBe(true) - }) + confirm.andCallFake((options, callback) => callback(0)); + await pane.close(); + expect(confirm).toHaveBeenCalled(); + expect(item1.save).toHaveBeenCalled(); + expect(pane.isDestroyed()).toBe(true); + }); it('does not destroy the pane if the user clicks cancel', async () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - const [item1] = pane.getItems() + ); + const [item1] = pane.getItems(); - item1.shouldPromptToSave = () => true - item1.getURI = () => '/test/path' - item1.save = jasmine.createSpy('save') + item1.shouldPromptToSave = () => true; + item1.getURI = () => '/test/path'; + item1.save = jasmine.createSpy('save'); - confirm.andCallFake((options, callback) => callback(1)) + confirm.andCallFake((options, callback) => callback(1)); - await pane.close() - expect(confirm).toHaveBeenCalled() - expect(item1.save).not.toHaveBeenCalled() - expect(pane.isDestroyed()).toBe(false) - }) + await pane.close(); + expect(confirm).toHaveBeenCalled(); + expect(item1.save).not.toHaveBeenCalled(); + expect(pane.isDestroyed()).toBe(false); + }); it('does not destroy the pane if the user starts to save but then does not choose a path', async () => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) - ) - const [item1] = pane.getItems() + ); + const [item1] = pane.getItems(); - item1.shouldPromptToSave = () => true - item1.saveAs = jasmine.createSpy('saveAs') + item1.shouldPromptToSave = () => true; + item1.saveAs = jasmine.createSpy('saveAs'); - confirm.andCallFake((options, callback) => callback(0)) - showSaveDialog.andCallFake((options, callback) => callback(undefined)) + confirm.andCallFake((options, callback) => callback(0)); + showSaveDialog.andCallFake((options, callback) => callback(undefined)); - await pane.close() - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - expect(confirm.callCount).toBe(1) - expect(item1.saveAs).not.toHaveBeenCalled() - expect(pane.isDestroyed()).toBe(false) - }) + await pane.close(); + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + expect(confirm.callCount).toBe(1); + expect(item1.saveAs).not.toHaveBeenCalled(); + expect(pane.isDestroyed()).toBe(false); + }); describe('when item fails to save', () => { - let pane, item1 + let pane, item1; beforeEach(() => { pane = new Pane({ items: [new Item('A'), new Item('B')], applicationDelegate: atom.applicationDelegate, config: atom.config - }) - ;[item1] = pane.getItems() + }); + [item1] = pane.getItems(); - item1.shouldPromptToSave = () => true - item1.getURI = () => '/test/path' + item1.shouldPromptToSave = () => true; + item1.getURI = () => '/test/path'; item1.save = jasmine.createSpy('save').andCallFake(() => { - const error = new Error("EACCES, permission denied '/test/path'") - error.path = '/test/path' - error.code = 'EACCES' - throw error - }) - }) + const error = new Error("EACCES, permission denied '/test/path'"); + error.path = '/test/path'; + error.code = 'EACCES'; + throw error; + }); + }); it('does not destroy the pane if save fails and user clicks cancel', async () => { - let confirmations = 0 + let confirmations = 0; confirm.andCallFake((options, callback) => { - confirmations++ + confirmations++; if (confirmations === 1) { - callback(0) // click save + callback(0); // click save } else { - callback(1) + callback(1); } - }) // click cancel + }); // click cancel - await pane.close() - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - expect(confirmations).toBe(2) - expect(item1.save).toHaveBeenCalled() - expect(pane.isDestroyed()).toBe(false) - }) + await pane.close(); + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + expect(confirmations).toBe(2); + expect(item1.save).toHaveBeenCalled(); + expect(pane.isDestroyed()).toBe(false); + }); it('does destroy the pane if the user saves the file under a new name', async () => { - item1.saveAs = jasmine.createSpy('saveAs').andReturn(true) + item1.saveAs = jasmine.createSpy('saveAs').andReturn(true); - let confirmations = 0 + let confirmations = 0; confirm.andCallFake((options, callback) => { - confirmations++ - callback(0) - }) // save and then save as + confirmations++; + callback(0); + }); // save and then save as - showSaveDialog.andCallFake((options, callback) => callback('new/path')) + showSaveDialog.andCallFake((options, callback) => callback('new/path')); - await pane.close() - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - expect(confirmations).toBe(2) + await pane.close(); + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + expect(confirmations).toBe(2); expect( atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0] - ).toEqual({}) - expect(item1.save).toHaveBeenCalled() - expect(item1.saveAs).toHaveBeenCalled() - expect(pane.isDestroyed()).toBe(true) - }) + ).toEqual({}); + expect(item1.save).toHaveBeenCalled(); + expect(item1.saveAs).toHaveBeenCalled(); + expect(pane.isDestroyed()).toBe(true); + }); it('asks again if the saveAs also fails', async () => { item1.saveAs = jasmine.createSpy('saveAs').andCallFake(() => { - const error = new Error("EACCES, permission denied '/test/path'") - error.path = '/test/path' - error.code = 'EACCES' - throw error - }) + const error = new Error("EACCES, permission denied '/test/path'"); + error.path = '/test/path'; + error.code = 'EACCES'; + throw error; + }); - let confirmations = 0 + let confirmations = 0; confirm.andCallFake((options, callback) => { - confirmations++ + confirmations++; if (confirmations < 3) { - callback(0) // save, save as, save as + callback(0); // save, save as, save as } else { - callback(2) // don't save + callback(2); // don't save } - }) + }); - showSaveDialog.andCallFake((options, callback) => callback('new/path')) + showSaveDialog.andCallFake((options, callback) => callback('new/path')); - await pane.close() - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() - expect(confirmations).toBe(3) + await pane.close(); + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); + expect(confirmations).toBe(3); expect( atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0] - ).toEqual({}) - expect(item1.save).toHaveBeenCalled() - expect(item1.saveAs).toHaveBeenCalled() - expect(pane.isDestroyed()).toBe(true) - }) - }) - }) + ).toEqual({}); + expect(item1.save).toHaveBeenCalled(); + expect(item1.saveAs).toHaveBeenCalled(); + expect(pane.isDestroyed()).toBe(true); + }); + }); + }); describe('::destroy()', () => { - let container, pane1, pane2 + let container, pane1, pane2; beforeEach(() => { - container = new PaneContainer({ config: atom.config, confirm }) - pane1 = container.root - pane1.addItems([new Item('A'), new Item('B')]) - pane2 = pane1.splitRight() - }) + container = new PaneContainer({ config: atom.config, confirm }); + pane1 = container.root; + pane1.addItems([new Item('A'), new Item('B')]); + pane2 = pane1.splitRight(); + }); it('invokes ::onWillDestroy observers before destroying items', () => { - let itemsDestroyed = null + let itemsDestroyed = null; pane1.onWillDestroy(() => { - itemsDestroyed = pane1.getItems().map(item => item.isDestroyed()) - }) - pane1.destroy() - expect(itemsDestroyed).toEqual([false, false]) - }) + itemsDestroyed = pane1.getItems().map(item => item.isDestroyed()); + }); + pane1.destroy(); + expect(itemsDestroyed).toEqual([false, false]); + }); it("destroys the pane's destroyable items", () => { - const [item1, item2] = pane1.getItems() - pane1.destroy() - expect(item1.isDestroyed()).toBe(true) - expect(item2.isDestroyed()).toBe(true) - }) + const [item1, item2] = pane1.getItems(); + pane1.destroy(); + expect(item1.isDestroyed()).toBe(true); + expect(item2.isDestroyed()).toBe(true); + }); describe('if the pane is active', () => { it('makes the next pane active', () => { - expect(pane2.isActive()).toBe(true) - pane2.destroy() - expect(pane1.isActive()).toBe(true) - }) - }) + expect(pane2.isActive()).toBe(true); + pane2.destroy(); + expect(pane1.isActive()).toBe(true); + }); + }); describe("if the pane's parent has more than two children", () => { it('removes the pane from its parent', () => { - const pane3 = pane2.splitRight() + const pane3 = pane2.splitRight(); - expect(container.root.children).toEqual([pane1, pane2, pane3]) - pane2.destroy() - expect(container.root.children).toEqual([pane1, pane3]) - }) - }) + expect(container.root.children).toEqual([pane1, pane2, pane3]); + pane2.destroy(); + expect(container.root.children).toEqual([pane1, pane3]); + }); + }); describe("if the pane's parent has two children", () => { it('replaces the parent with its last remaining child', () => { - const pane3 = pane2.splitDown() + const pane3 = pane2.splitDown(); - expect(container.root.children[0]).toBe(pane1) - expect(container.root.children[1].children).toEqual([pane2, pane3]) - pane3.destroy() - expect(container.root.children).toEqual([pane1, pane2]) - pane2.destroy() - expect(container.root).toBe(pane1) - }) - }) - }) + expect(container.root.children[0]).toBe(pane1); + expect(container.root.children[1].children).toEqual([pane2, pane3]); + pane3.destroy(); + expect(container.root.children).toEqual([pane1, pane2]); + pane2.destroy(); + expect(container.root).toBe(pane1); + }); + }); + }); describe('pending state', () => { - let editor1, pane, eventCount + let editor1, pane, eventCount; beforeEach(async () => { - editor1 = await atom.workspace.open('sample.txt', { pending: true }) - pane = atom.workspace.getActivePane() - eventCount = 0 - editor1.onDidTerminatePendingState(() => eventCount++) - }) + editor1 = await atom.workspace.open('sample.txt', { pending: true }); + pane = atom.workspace.getActivePane(); + eventCount = 0; + editor1.onDidTerminatePendingState(() => eventCount++); + }); it('does not open file in pending state by default', async () => { - await atom.workspace.open('sample.js') - expect(pane.getPendingItem()).toBeNull() - }) + await atom.workspace.open('sample.js'); + expect(pane.getPendingItem()).toBeNull(); + }); it("opens file in pending state if 'pending' option is true", () => { - expect(pane.getPendingItem()).toEqual(editor1) - }) + expect(pane.getPendingItem()).toEqual(editor1); + }); it('terminates pending state if ::terminatePendingState is invoked', () => { - editor1.terminatePendingState() + editor1.terminatePendingState(); - expect(pane.getPendingItem()).toBeNull() - expect(eventCount).toBe(1) - }) + expect(pane.getPendingItem()).toBeNull(); + expect(eventCount).toBe(1); + }); it('terminates pending state when buffer is changed', () => { - editor1.insertText("I'll be back!") - advanceClock(editor1.getBuffer().stoppedChangingDelay) + editor1.insertText("I'll be back!"); + advanceClock(editor1.getBuffer().stoppedChangingDelay); - expect(pane.getPendingItem()).toBeNull() - expect(eventCount).toBe(1) - }) + expect(pane.getPendingItem()).toBeNull(); + expect(eventCount).toBe(1); + }); it('only calls terminate handler once when text is modified twice', async () => { - const originalText = editor1.getText() - editor1.insertText('Some text') - advanceClock(editor1.getBuffer().stoppedChangingDelay) + const originalText = editor1.getText(); + editor1.insertText('Some text'); + advanceClock(editor1.getBuffer().stoppedChangingDelay); - await editor1.save() + await editor1.save(); - editor1.insertText('More text') - advanceClock(editor1.getBuffer().stoppedChangingDelay) + editor1.insertText('More text'); + advanceClock(editor1.getBuffer().stoppedChangingDelay); - expect(pane.getPendingItem()).toBeNull() - expect(eventCount).toBe(1) + expect(pane.getPendingItem()).toBeNull(); + expect(eventCount).toBe(1); // Reset fixture back to original state - editor1.setText(originalText) - await editor1.save() - }) + editor1.setText(originalText); + await editor1.save(); + }); it('only calls clearPendingItem if there is a pending item to clear', () => { - spyOn(pane, 'clearPendingItem').andCallThrough() + spyOn(pane, 'clearPendingItem').andCallThrough(); - editor1.terminatePendingState() - editor1.terminatePendingState() + editor1.terminatePendingState(); + editor1.terminatePendingState(); - expect(pane.getPendingItem()).toBeNull() - expect(pane.clearPendingItem.callCount).toBe(1) - }) - }) + expect(pane.getPendingItem()).toBeNull(); + expect(pane.clearPendingItem.callCount).toBe(1); + }); + }); describe('serialization', () => { - let pane = null + let pane = null; beforeEach(() => { pane = new Pane( @@ -1621,80 +1621,80 @@ describe('Pane', () => { items: [new Item('A', 'a'), new Item('B', 'b'), new Item('C', 'c')], flexScale: 2 }) - ) - }) + ); + }); it('can serialize and deserialize the pane and all its items', () => { - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getItems()).toEqual(pane.getItems()) - }) + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getItems()).toEqual(pane.getItems()); + }); it('restores the active item on deserialization', () => { - pane.activateItemAtIndex(1) - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1)) - }) + pane.activateItemAtIndex(1); + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1)); + }); it("restores the active item when it doesn't implement getURI()", () => { - pane.items[1].getURI = null - pane.activateItemAtIndex(1) - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1)) - }) + pane.items[1].getURI = null; + pane.activateItemAtIndex(1); + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1)); + }); it("restores the correct item when it doesn't implement getURI() and some items weren't deserialized", () => { - const unserializable = {} - pane.addItem(unserializable, { index: 0 }) - pane.items[2].getURI = null - pane.activateItemAtIndex(2) - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1)) - }) + const unserializable = {}; + pane.addItem(unserializable, { index: 0 }); + pane.items[2].getURI = null; + pane.activateItemAtIndex(2); + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1)); + }); it('does not include items that cannot be deserialized', () => { - spyOn(console, 'warn') - const unserializable = {} - pane.activateItem(unserializable) + spyOn(console, 'warn'); + const unserializable = {}; + pane.activateItem(unserializable); - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getActiveItem()).toEqual(pane.itemAtIndex(0)) - expect(newPane.getItems().length).toBe(pane.getItems().length - 1) - }) + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getActiveItem()).toEqual(pane.itemAtIndex(0)); + expect(newPane.getItems().length).toBe(pane.getItems().length - 1); + }); it("includes the pane's focus state in the serialized state", () => { - pane.focus() - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.focused).toBe(true) - }) + pane.focus(); + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.focused).toBe(true); + }); it('can serialize and deserialize the order of the items in the itemStack', () => { - const [item1, item2, item3] = pane.getItems() - pane.itemStack = [item3, item1, item2] - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.itemStack).toEqual(pane.itemStack) - expect(newPane.itemStack[2]).toEqual(item2) - }) + const [item1, item2, item3] = pane.getItems(); + pane.itemStack = [item3, item1, item2]; + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.itemStack).toEqual(pane.itemStack); + expect(newPane.itemStack[2]).toEqual(item2); + }); it('builds the itemStack if the itemStack is not serialized', () => { - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getItems()).toEqual(newPane.itemStack) - }) + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getItems()).toEqual(newPane.itemStack); + }); it('rebuilds the itemStack if items.length does not match itemStack.length', () => { - const [, item2, item3] = pane.getItems() - pane.itemStack = [item2, item3] - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.getItems()).toEqual(newPane.itemStack) - }) + const [, item2, item3] = pane.getItems(); + pane.itemStack = [item2, item3]; + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.getItems()).toEqual(newPane.itemStack); + }); it('does not serialize the reference to the items in the itemStack for pane items that will not be serialized', () => { - const [item1, item2, item3] = pane.getItems() - pane.itemStack = [item2, item1, item3] - const unserializable = {} - pane.activateItem(unserializable) + const [item1, item2, item3] = pane.getItems(); + pane.itemStack = [item2, item1, item3]; + const unserializable = {}; + pane.activateItem(unserializable); - const newPane = Pane.deserialize(pane.serialize(), atom) - expect(newPane.itemStack).toEqual([item2, item1, item3]) - }) - }) -}) + const newPane = Pane.deserialize(pane.serialize(), atom); + expect(newPane.itemStack).toEqual([item2, item1, item3]); + }); + }); +}); diff --git a/spec/panel-container-element-spec.js b/spec/panel-container-element-spec.js index 1f082fbdd..649b5414d 100644 --- a/spec/panel-container-element-spec.js +++ b/spec/panel-container-element-spec.js @@ -1,204 +1,204 @@ -'use strict' +'use strict'; -const Panel = require('../src/panel') -const PanelContainer = require('../src/panel-container') +const Panel = require('../src/panel'); +const PanelContainer = require('../src/panel-container'); describe('PanelContainerElement', () => { - let jasmineContent, element, container + let jasmineContent, element, container; class TestPanelContainerItem {} class TestPanelContainerItemElement_ extends HTMLElement { - createdCallback () { - this.classList.add('test-root') + createdCallback() { + this.classList.add('test-root'); } - initialize (model) { - this.model = model - return this + initialize(model) { + this.model = model; + return this; } - focus () {} + focus() {} } const TestPanelContainerItemElement = document.registerElement( 'atom-test-container-item-element', { prototype: TestPanelContainerItemElement_.prototype } - ) + ); beforeEach(() => { - jasmineContent = document.body.querySelector('#jasmine-content') + jasmineContent = document.body.querySelector('#jasmine-content'); atom.views.addViewProvider(TestPanelContainerItem, model => new TestPanelContainerItemElement().initialize(model) - ) + ); container = new PanelContainer({ viewRegistry: atom.views, location: 'left' - }) - element = container.getElement() - jasmineContent.appendChild(element) - }) + }); + element = container.getElement(); + jasmineContent.appendChild(element); + }); it('has a location class with value from the model', () => { - expect(element).toHaveClass('left') - }) + 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) - }) + 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', () => { const panel1 = new Panel( { item: new TestPanelContainerItem(), priority: 10 }, atom.views - ) + ); const panel2 = new Panel( { item: new TestPanelContainerItem(), priority: 5 }, atom.views - ) + ); const panel3 = new Panel( { item: new TestPanelContainerItem(), priority: 8 }, atom.views - ) + ); - container.addPanel(panel1) - container.addPanel(panel2) - container.addPanel(panel3) + container.addPanel(panel1); + container.addPanel(panel2); + container.addPanel(panel3); - expect(element.childNodes[2]).toBe(panel1.getElement()) - expect(element.childNodes[1]).toBe(panel3.getElement()) - expect(element.childNodes[0]).toBe(panel2.getElement()) - }) + expect(element.childNodes[2]).toBe(panel1.getElement()); + expect(element.childNodes[1]).toBe(panel3.getElement()); + expect(element.childNodes[0]).toBe(panel2.getElement()); + }); 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) + expect(element.childNodes.length).toBe(0); const panel1 = new Panel( { item: new TestPanelContainerItem() }, atom.views - ) - 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 + ); + 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() }, atom.views - ) - container.addPanel(panel2) - expect(element.childNodes.length).toBe(2) + ); + container.addPanel(panel2); + expect(element.childNodes.length).toBe(2); - expect(panel1.getElement().style.display).not.toBe('none') - expect(panel2.getElement().style.display).not.toBe('none') + expect(panel1.getElement().style.display).not.toBe('none'); + expect(panel2.getElement().style.display).not.toBe('none'); - panel1.destroy() - expect(element.childNodes.length).toBe(1) + panel1.destroy(); + expect(element.childNodes.length).toBe(1); - panel2.destroy() - expect(element.childNodes.length).toBe(0) - })) + panel2.destroy(); + expect(element.childNodes.length).toBe(0); + })); describe('when the container is at the bottom location', () => { beforeEach(() => { container = new PanelContainer({ viewRegistry: atom.views, location: 'bottom' - }) - element = container.getElement() - jasmineContent.appendChild(element) - }) + }); + element = container.getElement(); + 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) + expect(element.childNodes.length).toBe(0); const panel1 = new Panel( { item: new TestPanelContainerItem(), className: 'one' }, atom.views - ) - 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(panel1.getElement()).toHaveClass('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(panel1.getElement()).toHaveClass('one'); const panel2 = new Panel( { item: new TestPanelContainerItem(), className: 'two' }, atom.views - ) - container.addPanel(panel2) - expect(element.childNodes.length).toBe(2) - expect(panel2.getElement()).toHaveClass('two') + ); + container.addPanel(panel2); + expect(element.childNodes.length).toBe(2); + expect(panel2.getElement()).toHaveClass('two'); - panel1.destroy() - expect(element.childNodes.length).toBe(1) + panel1.destroy(); + expect(element.childNodes.length).toBe(1); - panel2.destroy() - expect(element.childNodes.length).toBe(0) - }) - }) - }) + panel2.destroy(); + expect(element.childNodes.length).toBe(0); + }); + }); + }); describe('when the container is modal', () => { beforeEach(() => { container = new PanelContainer({ viewRegistry: atom.views, location: 'modal' - }) - element = container.getElement() - jasmineContent.appendChild(element) - }) + }); + element = container.getElement(); + jasmineContent.appendChild(element); + }); it('allows only one panel to be visible at a time', () => { const panel1 = new Panel( { item: new TestPanelContainerItem() }, atom.views - ) - container.addPanel(panel1) + ); + container.addPanel(panel1); - expect(panel1.getElement().style.display).not.toBe('none') + expect(panel1.getElement().style.display).not.toBe('none'); const panel2 = new Panel( { item: new TestPanelContainerItem() }, atom.views - ) - container.addPanel(panel2) + ); + container.addPanel(panel2); - expect(panel1.getElement().style.display).toBe('none') - expect(panel2.getElement().style.display).not.toBe('none') + expect(panel1.getElement().style.display).toBe('none'); + expect(panel2.getElement().style.display).not.toBe('none'); - panel1.show() + panel1.show(); - expect(panel1.getElement().style.display).not.toBe('none') - expect(panel2.getElement().style.display).toBe('none') - }) + expect(panel1.getElement().style.display).not.toBe('none'); + expect(panel2.getElement().style.display).toBe('none'); + }); it("adds the 'modal' class to panels", () => { const panel1 = new Panel( { item: new TestPanelContainerItem() }, atom.views - ) - container.addPanel(panel1) + ); + container.addPanel(panel1); - expect(panel1.getElement()).toHaveClass('modal') + expect(panel1.getElement()).toHaveClass('modal'); // legacy selector support - expect(panel1.getElement()).not.toHaveClass('tool-panel') - expect(panel1.getElement()).toHaveClass('overlay') - expect(panel1.getElement()).toHaveClass('from-top') - }) + expect(panel1.getElement()).not.toHaveClass('tool-panel'); + expect(panel1.getElement()).toHaveClass('overlay'); + expect(panel1.getElement()).toHaveClass('from-top'); + }); describe('autoFocus', () => { - function createPanel (autoFocus = true) { + function createPanel(autoFocus = true) { const panel = new Panel( { item: new TestPanelContainerItem(), @@ -206,61 +206,61 @@ describe('PanelContainerElement', () => { visible: false }, atom.views - ) + ); - container.addPanel(panel) - return panel + container.addPanel(panel); + return panel; } it('focuses the first tabbable item if available', () => { - const panel = createPanel() - const panelEl = panel.getElement() - const inputEl = document.createElement('input') + const panel = createPanel(); + const panelEl = panel.getElement(); + const inputEl = document.createElement('input'); - panelEl.appendChild(inputEl) - expect(document.activeElement).not.toBe(inputEl) + panelEl.appendChild(inputEl); + expect(document.activeElement).not.toBe(inputEl); - panel.show() - expect(document.activeElement).toBe(inputEl) - }) + panel.show(); + expect(document.activeElement).toBe(inputEl); + }); it('focuses the autoFocus element if available', () => { - const inputEl1 = document.createElement('input') - const inputEl2 = document.createElement('input') - const panel = createPanel(inputEl2) - const panelEl = panel.getElement() + const inputEl1 = document.createElement('input'); + const inputEl2 = document.createElement('input'); + const panel = createPanel(inputEl2); + const panelEl = panel.getElement(); - panelEl.appendChild(inputEl1) - panelEl.appendChild(inputEl2) - expect(document.activeElement).not.toBe(inputEl2) + panelEl.appendChild(inputEl1); + panelEl.appendChild(inputEl2); + expect(document.activeElement).not.toBe(inputEl2); - panel.show() - expect(document.activeElement).toBe(inputEl2) - }) + panel.show(); + expect(document.activeElement).toBe(inputEl2); + }); it('focuses the entire panel item when no tabbable item is available and the panel is focusable', () => { - const panel = createPanel() - const panelEl = panel.getElement() + const panel = createPanel(); + const panelEl = panel.getElement(); - spyOn(panelEl, 'focus') - panel.show() - expect(panelEl.focus).toHaveBeenCalled() - }) + spyOn(panelEl, 'focus'); + panel.show(); + expect(panelEl.focus).toHaveBeenCalled(); + }); it('returns focus to the original activeElement', () => { - const panel = createPanel() - const previousActiveElement = document.activeElement - const panelEl = panel.getElement() - panelEl.appendChild(document.createElement('input')) + const panel = createPanel(); + const previousActiveElement = document.activeElement; + const panelEl = panel.getElement(); + panelEl.appendChild(document.createElement('input')); - panel.show() - panel.hide() + panel.show(); + panel.hide(); - waitsFor(() => document.activeElement === previousActiveElement) + waitsFor(() => document.activeElement === previousActiveElement); runs(() => { - expect(document.activeElement).toBe(previousActiveElement) - }) - }) - }) - }) -}) + expect(document.activeElement).toBe(previousActiveElement); + }); + }); + }); + }); +}); diff --git a/spec/panel-container-spec.js b/spec/panel-container-spec.js index 35286cd36..80230a594 100644 --- a/spec/panel-container-spec.js +++ b/spec/panel-container-spec.js @@ -1,160 +1,160 @@ -'use strict' +'use strict'; -const Panel = require('../src/panel') -const PanelContainer = require('../src/panel-container') +const Panel = require('../src/panel'); +const PanelContainer = require('../src/panel-container'); describe('PanelContainer', () => { - let container + let container; class TestPanelItem {} beforeEach(() => { - container = new PanelContainer({ viewRegistry: atom.views }) - }) + container = new PanelContainer({ viewRegistry: atom.views }); + }); describe('::addPanel(panel)', () => { it('emits an onDidAddPanel event with the index the panel was inserted at', () => { - const addPanelSpy = jasmine.createSpy() - container.onDidAddPanel(addPanelSpy) + const addPanelSpy = jasmine.createSpy(); + container.onDidAddPanel(addPanelSpy); - const panel1 = new Panel({ item: new TestPanelItem() }, atom.views) - container.addPanel(panel1) - expect(addPanelSpy).toHaveBeenCalledWith({ panel: panel1, index: 0 }) + const panel1 = new Panel({ item: new TestPanelItem() }, atom.views); + container.addPanel(panel1); + expect(addPanelSpy).toHaveBeenCalledWith({ panel: panel1, index: 0 }); - const panel2 = new Panel({ item: new TestPanelItem() }, atom.views) - container.addPanel(panel2) - expect(addPanelSpy).toHaveBeenCalledWith({ panel: panel2, index: 1 }) - }) - }) + const panel2 = new Panel({ item: new TestPanelItem() }, atom.views); + 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', () => { - const removePanelSpy = jasmine.createSpy() - container.onDidRemovePanel(removePanelSpy) + const removePanelSpy = jasmine.createSpy(); + container.onDidRemovePanel(removePanelSpy); - const panel1 = new Panel({ item: new TestPanelItem() }, atom.views) - container.addPanel(panel1) - const panel2 = new Panel({ item: new TestPanelItem() }, atom.views) - container.addPanel(panel2) + const panel1 = new Panel({ item: new TestPanelItem() }, atom.views); + container.addPanel(panel1); + const panel2 = new Panel({ item: new TestPanelItem() }, atom.views); + 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() - expect(removePanelSpy).toHaveBeenCalledWith({ panel: panel1, index: 0 }) - }) - }) + panel1.destroy(); + expect(removePanelSpy).toHaveBeenCalledWith({ panel: panel1, index: 0 }); + }); + }); describe('::destroy()', () => { it('destroys the container and all of its panels', () => { - const destroyedPanels = [] + const destroyedPanels = []; - const panel1 = new Panel({ item: new TestPanelItem() }, atom.views) + const panel1 = new Panel({ item: new TestPanelItem() }, atom.views); panel1.onDidDestroy(() => { - destroyedPanels.push(panel1) - }) - container.addPanel(panel1) + destroyedPanels.push(panel1); + }); + container.addPanel(panel1); - const panel2 = new Panel({ item: new TestPanelItem() }, atom.views) + const panel2 = new Panel({ item: new TestPanelItem() }, atom.views); panel2.onDidDestroy(() => { - destroyedPanels.push(panel2) - }) - container.addPanel(panel2) + destroyedPanels.push(panel2); + }); + container.addPanel(panel2); - container.destroy() + container.destroy(); - expect(container.getPanels().length).toBe(0) - expect(destroyedPanels).toEqual([panel1, panel2]) - }) - }) + expect(container.getPanels().length).toBe(0); + expect(destroyedPanels).toEqual([panel1, panel2]); + }); + }); describe('panel priority', () => { describe('left / top panel container', () => { - let initialPanel + let initialPanel; beforeEach(() => { // 'left' logic is the same as 'top' - container = new PanelContainer({ location: 'left' }) - initialPanel = new Panel({ item: new TestPanelItem() }, atom.views) - container.addPanel(initialPanel) - }) + container = new PanelContainer({ location: 'left' }); + initialPanel = new Panel({ item: new TestPanelItem() }, atom.views); + container.addPanel(initialPanel); + }); 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 addPanelSpy = jasmine.createSpy(); + container.onDidAddPanel(addPanelSpy); const panel = new Panel( { item: new TestPanelItem(), priority: 0 }, atom.views - ) - container.addPanel(panel) + ); + container.addPanel(panel); - expect(addPanelSpy).toHaveBeenCalledWith({ panel, index: 0 }) - expect(container.getPanels()[0]).toBe(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', () => { - const addPanelSpy = jasmine.createSpy() + const addPanelSpy = jasmine.createSpy(); let panel = new Panel( { item: new TestPanelItem(), priority: 1000 }, atom.views - ) - container.addPanel(panel) + ); + container.addPanel(panel); - container.onDidAddPanel(addPanelSpy) + container.onDidAddPanel(addPanelSpy); panel = new Panel( { item: new TestPanelItem(), priority: 101 }, atom.views - ) - container.addPanel(panel) + ); + container.addPanel(panel); - expect(addPanelSpy).toHaveBeenCalledWith({ panel, index: 1 }) - expect(container.getPanels()[1]).toBe(panel) - }) - }) - }) + expect(addPanelSpy).toHaveBeenCalledWith({ panel, index: 1 }); + expect(container.getPanels()[1]).toBe(panel); + }); + }); + }); describe('right / bottom panel container', () => { - let initialPanel + let initialPanel; beforeEach(() => { // 'bottom' logic is the same as 'right' - container = new PanelContainer({ location: 'right' }) - initialPanel = new Panel({ item: new TestPanelItem() }, atom.views) - container.addPanel(initialPanel) - }) + container = new PanelContainer({ location: 'right' }); + initialPanel = new Panel({ item: new TestPanelItem() }, atom.views); + container.addPanel(initialPanel); + }); 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 addPanelSpy = jasmine.createSpy(); + container.onDidAddPanel(addPanelSpy); const panel = new Panel( { item: new TestPanelItem(), priority: 1000 }, atom.views - ) - container.addPanel(panel) + ); + container.addPanel(panel); - expect(addPanelSpy).toHaveBeenCalledWith({ panel, index: 0 }) - expect(container.getPanels()[0]).toBe(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', () => { - const addPanelSpy = jasmine.createSpy() - container.onDidAddPanel(addPanelSpy) + const addPanelSpy = jasmine.createSpy(); + container.onDidAddPanel(addPanelSpy); const panel = new Panel( { item: new TestPanelItem(), priority: 0 }, atom.views - ) - container.addPanel(panel) + ); + container.addPanel(panel); - expect(addPanelSpy).toHaveBeenCalledWith({ panel, index: 1 }) - expect(container.getPanels()[1]).toBe(panel) - }) - }) - }) - }) -}) + expect(addPanelSpy).toHaveBeenCalledWith({ panel, index: 1 }); + expect(container.getPanels()[1]).toBe(panel); + }); + }); + }); + }); +}); diff --git a/spec/panel-spec.js b/spec/panel-spec.js index 5165e550c..d778e295a 100644 --- a/spec/panel-spec.js +++ b/spec/panel-spec.js @@ -1,114 +1,114 @@ -const Panel = require('../src/panel') +const Panel = require('../src/panel'); describe('Panel', () => { class TestPanelItem { - getElement () { + getElement() { if (!this.element) { - this.element = document.createElement('div') - this.element.tabIndex = -1 - this.element.className = 'test-root' + this.element = document.createElement('div'); + this.element.tabIndex = -1; + this.element.className = 'test-root'; } - return this.element + return this.element; } } it("adds the item's element as a child of the panel", () => { - const panel = new Panel({ item: new TestPanelItem() }, atom.views) - const element = panel.getElement() - expect(element.tagName.toLowerCase()).toBe('atom-panel') - expect(element.firstChild).toBe(panel.getItem().getElement()) - }) + const panel = new Panel({ item: new TestPanelItem() }, atom.views); + const element = panel.getElement(); + expect(element.tagName.toLowerCase()).toBe('atom-panel'); + expect(element.firstChild).toBe(panel.getItem().getElement()); + }); describe('destroying the panel', () => { it('removes the element when the panel is destroyed', () => { - const panel = new Panel({ item: new TestPanelItem() }, atom.views) - const element = panel.getElement() - const jasmineContent = document.getElementById('jasmine-content') - jasmineContent.appendChild(element) + const panel = new Panel({ item: new TestPanelItem() }, atom.views); + const element = panel.getElement(); + const jasmineContent = document.getElementById('jasmine-content'); + jasmineContent.appendChild(element); - expect(element.parentNode).toBe(jasmineContent) - panel.destroy() - expect(element.parentNode).not.toBe(jasmineContent) - }) + expect(element.parentNode).toBe(jasmineContent); + panel.destroy(); + expect(element.parentNode).not.toBe(jasmineContent); + }); it('does not try to remove the element twice', () => { - const item = new TestPanelItem() - const panel = new Panel({ item }, atom.views) - const element = panel.getElement() - const jasmineContent = document.getElementById('jasmine-content') - jasmineContent.appendChild(element) + const item = new TestPanelItem(); + const panel = new Panel({ item }, atom.views); + const element = panel.getElement(); + const jasmineContent = document.getElementById('jasmine-content'); + jasmineContent.appendChild(element); - item.getElement().focus() - expect(item.getElement()).toHaveFocus() + item.getElement().focus(); + expect(item.getElement()).toHaveFocus(); // Avoid this error: // NotFoundError: Failed to execute 'remove' on 'Element': // The node to be removed is no longer a child of this node. // Perhaps it was moved in a 'blur' event handler? - item.getElement().addEventListener('blur', () => panel.destroy()) - panel.destroy() - }) - }) + item.getElement().addEventListener('blur', () => panel.destroy()); + panel.destroy(); + }); + }); describe('changing panel visibility', () => { it('notifies observers added with onDidChangeVisible', () => { - const panel = new Panel({ item: new TestPanelItem() }, atom.views) + const panel = new Panel({ item: new TestPanelItem() }, atom.views); - const spy = jasmine.createSpy() - panel.onDidChangeVisible(spy) + const spy = jasmine.createSpy(); + panel.onDidChangeVisible(spy); - panel.hide() - expect(panel.isVisible()).toBe(false) - expect(spy).toHaveBeenCalledWith(false) - spy.reset() + panel.hide(); + expect(panel.isVisible()).toBe(false); + expect(spy).toHaveBeenCalledWith(false); + spy.reset(); - panel.show() - expect(panel.isVisible()).toBe(true) - expect(spy).toHaveBeenCalledWith(true) + panel.show(); + expect(panel.isVisible()).toBe(true); + expect(spy).toHaveBeenCalledWith(true); - panel.destroy() - expect(panel.isVisible()).toBe(false) - expect(spy).toHaveBeenCalledWith(false) - }) + panel.destroy(); + expect(panel.isVisible()).toBe(false); + expect(spy).toHaveBeenCalledWith(false); + }); it('initially renders panel created with visible: false', () => { const panel = new Panel( { visible: false, item: new TestPanelItem() }, atom.views - ) - const element = panel.getElement() - expect(element.style.display).toBe('none') - }) + ); + const element = panel.getElement(); + expect(element.style.display).toBe('none'); + }); it('hides and shows the panel element when Panel::hide() and Panel::show() are called', () => { - const panel = new Panel({ item: new TestPanelItem() }, atom.views) - const element = panel.getElement() - expect(element.style.display).not.toBe('none') + const panel = new Panel({ item: new TestPanelItem() }, atom.views); + const element = panel.getElement(); + expect(element.style.display).not.toBe('none'); - panel.hide() - expect(element.style.display).toBe('none') + panel.hide(); + expect(element.style.display).toBe('none'); - panel.show() - expect(element.style.display).not.toBe('none') - }) - }) + panel.show(); + expect(element.style.display).not.toBe('none'); + }); + }); describe('when a class name is specified', () => { it('initially renders panel created with visible: false', () => { const panel = new Panel( { className: 'some classes', item: new TestPanelItem() }, atom.views - ) - const element = panel.getElement() + ); + const element = panel.getElement(); - expect(element).toHaveClass('some') - expect(element).toHaveClass('classes') - }) - }) + expect(element).toHaveClass('some'); + expect(element).toHaveClass('classes'); + }); + }); describe('creating an atom-panel via markup', () => { it('does not throw an error', () => { - document.createElement('atom-panel') - }) - }) -}) + document.createElement('atom-panel'); + }); + }); +}); diff --git a/spec/path-watcher-spec.js b/spec/path-watcher-spec.js index ec1993f96..a2a9a4e6d 100644 --- a/spec/path-watcher-spec.js +++ b/spec/path-watcher-spec.js @@ -1,174 +1,173 @@ /** @babel */ -import temp from 'temp' -import fs from 'fs-plus' -import path from 'path' -import { promisify } from 'util' +import temp from 'temp'; +import fs from 'fs-plus'; +import path from 'path'; +import { promisify } from 'util'; -import { CompositeDisposable } from 'event-kit' -import { watchPath, stopAllWatchers } from '../src/path-watcher' +import { CompositeDisposable } from 'event-kit'; +import { watchPath, stopAllWatchers } from '../src/path-watcher'; -temp.track() +temp.track(); -const writeFile = promisify(fs.writeFile) -const mkdir = promisify(fs.mkdir) -const appendFile = promisify(fs.appendFile) -const realpath = promisify(fs.realpath) +const writeFile = promisify(fs.writeFile); +const mkdir = promisify(fs.mkdir); +const appendFile = promisify(fs.appendFile); +const realpath = promisify(fs.realpath); -const tempMkdir = promisify(temp.mkdir) +const tempMkdir = promisify(temp.mkdir); -describe('watchPath', function () { - let subs +describe('watchPath', function() { + let subs; - beforeEach(function () { - subs = new CompositeDisposable() - }) + beforeEach(function() { + subs = new CompositeDisposable(); + }); - afterEach(async function () { - subs.dispose() - await stopAllWatchers() - }) + afterEach(async function() { + subs.dispose(); + await stopAllWatchers(); + }); - function waitForChanges (watcher, ...fileNames) { - const waiting = new Set(fileNames) - let fired = false - const relevantEvents = [] + function waitForChanges(watcher, ...fileNames) { + const waiting = new Set(fileNames); + let fired = false; + const relevantEvents = []; return new Promise(resolve => { const sub = watcher.onDidChange(events => { for (const event of events) { if (waiting.delete(event.path)) { - relevantEvents.push(event) + relevantEvents.push(event); } } if (!fired && waiting.size === 0) { - fired = true - resolve(relevantEvents) - sub.dispose() + fired = true; + resolve(relevantEvents); + sub.dispose(); } - }) - }) + }); + }); } - describe('watchPath()', function () { - it('resolves the returned promise when the watcher begins listening', async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + describe('watchPath()', function() { + it('resolves the returned promise when the watcher begins listening', async function() { + const rootDir = await tempMkdir('atom-fsmanager-test-'); - const watcher = await watchPath(rootDir, {}, () => {}) - expect(watcher.constructor.name).toBe('PathWatcher') - }) + const watcher = await watchPath(rootDir, {}, () => {}); + expect(watcher.constructor.name).toBe('PathWatcher'); + }); - it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function() { + const rootDir = await tempMkdir('atom-fsmanager-test-'); - const watcher0 = await watchPath(rootDir, {}, () => {}) - const watcher1 = await watchPath(rootDir, {}, () => {}) + const watcher0 = await watchPath(rootDir, {}, () => {}); + const watcher1 = await watchPath(rootDir, {}, () => {}); - expect(watcher0.native).toBe(watcher1.native) - }) + expect(watcher0.native).toBe(watcher1.native); + }); - it("reuses existing native watchers even while they're still starting", async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + it("reuses existing native watchers even while they're still starting", async function() { + const rootDir = await tempMkdir('atom-fsmanager-test-'); const [watcher0, watcher1] = await Promise.all([ watchPath(rootDir, {}, () => {}), watchPath(rootDir, {}, () => {}) - ]) - expect(watcher0.native).toBe(watcher1.native) - }) + ]); + expect(watcher0.native).toBe(watcher1.native); + }); - it("doesn't attach new watchers to a native watcher that's stopping", async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + it("doesn't attach new watchers to a native watcher that's stopping", async function() { + const rootDir = await tempMkdir('atom-fsmanager-test-'); - const watcher0 = await watchPath(rootDir, {}, () => {}) - const native0 = watcher0.native + const watcher0 = await watchPath(rootDir, {}, () => {}); + const native0 = watcher0.native; - watcher0.dispose() - const watcher1 = await watchPath(rootDir, {}, () => {}) + watcher0.dispose(); + const watcher1 = await watchPath(rootDir, {}, () => {}); - expect(watcher1.native).not.toBe(native0) - }) + expect(watcher1.native).not.toBe(native0); + }); - it('reuses an existing native watcher on a parent directory and filters events', async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-').then(realpath) - const rootFile = path.join(rootDir, 'rootfile.txt') - const subDir = path.join(rootDir, 'subdir') - const subFile = path.join(subDir, 'subfile.txt') + it('reuses an existing native watcher on a parent directory and filters events', async function() { + const rootDir = await tempMkdir('atom-fsmanager-test-').then(realpath); + const rootFile = path.join(rootDir, 'rootfile.txt'); + const subDir = path.join(rootDir, 'subdir'); + const subFile = path.join(subDir, 'subfile.txt'); - await mkdir(subDir) + await mkdir(subDir); // Keep the watchers alive with an undisposed subscription - const rootWatcher = await watchPath(rootDir, {}, () => {}) - const childWatcher = await watchPath(subDir, {}, () => {}) + const rootWatcher = await watchPath(rootDir, {}, () => {}); + const childWatcher = await watchPath(subDir, {}, () => {}); - expect(rootWatcher.native).toBe(childWatcher.native) - expect(rootWatcher.native.isRunning()).toBe(true) + expect(rootWatcher.native).toBe(childWatcher.native); + expect(rootWatcher.native.isRunning()).toBe(true); const firstChanges = Promise.all([ waitForChanges(rootWatcher, subFile), waitForChanges(childWatcher, subFile) - ]) - await writeFile(subFile, 'subfile\n', { encoding: 'utf8' }) - await firstChanges + ]); + await writeFile(subFile, 'subfile\n', { encoding: 'utf8' }); + await firstChanges; - const nextRootEvent = waitForChanges(rootWatcher, rootFile) - await writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }) - await nextRootEvent - }) + const nextRootEvent = waitForChanges(rootWatcher, rootFile); + await writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }); + await nextRootEvent; + }); - it('adopts existing child watchers and filters events appropriately to them', async function () { - const parentDir = await tempMkdir('atom-fsmanager-test-') - .then(realpath) + it('adopts existing child watchers and filters events appropriately to them', async function() { + const parentDir = await tempMkdir('atom-fsmanager-test-').then(realpath); // Create the directory tree - const rootFile = path.join(parentDir, 'rootfile.txt') - const subDir0 = path.join(parentDir, 'subdir0') - const subFile0 = path.join(subDir0, 'subfile0.txt') - const subDir1 = path.join(parentDir, 'subdir1') - const subFile1 = path.join(subDir1, 'subfile1.txt') + const rootFile = path.join(parentDir, 'rootfile.txt'); + const subDir0 = path.join(parentDir, 'subdir0'); + const subFile0 = path.join(subDir0, 'subfile0.txt'); + const subDir1 = path.join(parentDir, 'subdir1'); + const subFile1 = path.join(subDir1, 'subfile1.txt'); - await mkdir(subDir0) - await mkdir(subDir1) + await mkdir(subDir0); + await mkdir(subDir1); await Promise.all([ writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }), writeFile(subFile0, 'subfile 0\n', { encoding: 'utf8' }), writeFile(subFile1, 'subfile 1\n', { encoding: 'utf8' }) - ]) + ]); // Begin the child watchers and keep them alive - const subWatcher0 = await watchPath(subDir0, {}, () => {}) - const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0) + const subWatcher0 = await watchPath(subDir0, {}, () => {}); + const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0); - const subWatcher1 = await watchPath(subDir1, {}, () => {}) - const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1) + const subWatcher1 = await watchPath(subDir1, {}, () => {}); + const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1); - expect(subWatcher0.native).not.toBe(subWatcher1.native) + expect(subWatcher0.native).not.toBe(subWatcher1.native); // Create the parent watcher - const parentWatcher = await watchPath(parentDir, {}, () => {}) + const parentWatcher = await watchPath(parentDir, {}, () => {}); const parentWatcherChanges = waitForChanges( parentWatcher, rootFile, subFile0, subFile1 - ) + ); - expect(subWatcher0.native).toBe(parentWatcher.native) - expect(subWatcher1.native).toBe(parentWatcher.native) + expect(subWatcher0.native).toBe(parentWatcher.native); + expect(subWatcher1.native).toBe(parentWatcher.native); // Ensure events are filtered correctly await Promise.all([ appendFile(rootFile, 'change\n', { encoding: 'utf8' }), appendFile(subFile0, 'change\n', { encoding: 'utf8' }), appendFile(subFile1, 'change\n', { encoding: 'utf8' }) - ]) + ]); await Promise.all([ subWatcherChanges0, subWatcherChanges1, parentWatcherChanges - ]) - }) - }) -}) + ]); + }); + }); +}); diff --git a/spec/project-spec.js b/spec/project-spec.js index 2025cae71..48d85f026 100644 --- a/spec/project-spec.js +++ b/spec/project-spec.js @@ -1,38 +1,38 @@ -const temp = require('temp').track() -const TextBuffer = require('text-buffer') -const Project = require('../src/project') -const fs = require('fs-plus') -const path = require('path') -const { Directory } = require('pathwatcher') -const { stopAllWatchers } = require('../src/path-watcher') -const GitRepository = require('../src/git-repository') +const temp = require('temp').track(); +const TextBuffer = require('text-buffer'); +const Project = require('../src/project'); +const fs = require('fs-plus'); +const path = require('path'); +const { Directory } = require('pathwatcher'); +const { stopAllWatchers } = require('../src/path-watcher'); +const GitRepository = require('../src/git-repository'); describe('Project', () => { beforeEach(() => { - const directory = atom.project.getDirectories()[0] - const paths = directory ? [directory.resolve('dir')] : [null] - atom.project.setPaths(paths) + const directory = atom.project.getDirectories()[0]; + const paths = directory ? [directory.resolve('dir')] : [null]; + atom.project.setPaths(paths); // Wait for project's service consumers to be asynchronously added - waits(1) - }) + waits(1); + }); describe('serialization', () => { - let deserializedProject = null - let notQuittingProject = null - let quittingProject = null + let deserializedProject = null; + let notQuittingProject = null; + let quittingProject = null; afterEach(() => { if (deserializedProject != null) { - deserializedProject.destroy() + deserializedProject.destroy(); } if (notQuittingProject != null) { - notQuittingProject.destroy() + notQuittingProject.destroy(); } if (quittingProject != null) { - quittingProject.destroy() + quittingProject.destroy(); } - }) + }); it("does not deserialize paths to directories that don't exist", () => { deserializedProject = new Project({ @@ -40,693 +40,693 @@ describe('Project', () => { packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - const state = atom.project.serialize() - state.paths.push('/directory/that/does/not/exist') + }); + const state = atom.project.serialize(); + state.paths.push('/directory/that/does/not/exist'); - let err = null + let err = null; waitsForPromise(() => deserializedProject.deserialize(state, atom.deserializers).catch(e => { - err = e + err = e; }) - ) + ); runs(() => { - expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths()) + expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths()); expect(err.missingProjectPaths).toEqual([ '/directory/that/does/not/exist' - ]) - }) - }) + ]); + }); + }); it('does not deserialize paths that are now files', () => { - const childPath = path.join(temp.mkdirSync('atom-spec-project'), 'child') - fs.mkdirSync(childPath) + const childPath = path.join(temp.mkdirSync('atom-spec-project'), 'child'); + fs.mkdirSync(childPath); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - atom.project.setPaths([childPath]) - const state = atom.project.serialize() + }); + atom.project.setPaths([childPath]); + const state = atom.project.serialize(); - fs.rmdirSync(childPath) - fs.writeFileSync(childPath, 'surprise!\n') + fs.rmdirSync(childPath); + fs.writeFileSync(childPath, 'surprise!\n'); - let err = null + let err = null; waitsForPromise(() => deserializedProject.deserialize(state, atom.deserializers).catch(e => { - err = e + err = e; }) - ) + ); runs(() => { - expect(deserializedProject.getPaths()).toEqual([]) - expect(err.missingProjectPaths).toEqual([childPath]) - }) - }) + expect(deserializedProject.getPaths()).toEqual([]); + expect(err.missingProjectPaths).toEqual([childPath]); + }); + }); it('does not include unretained buffers in the serialized state', () => { - waitsForPromise(() => atom.project.bufferForPath('a')) + waitsForPromise(() => atom.project.bufferForPath('a')); runs(() => { - expect(atom.project.getBuffers().length).toBe(1) + expect(atom.project.getBuffers().length).toBe(1); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => deserializedProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)) - }) + runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + }); it('listens for destroyed events on deserialized buffers and removes them when they are destroyed', () => { - waitsForPromise(() => atom.workspace.open('a')) + waitsForPromise(() => atom.workspace.open('a')); runs(() => { - expect(atom.project.getBuffers().length).toBe(1) + expect(atom.project.getBuffers().length).toBe(1); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => deserializedProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); runs(() => { - expect(deserializedProject.getBuffers().length).toBe(1) - deserializedProject.getBuffers()[0].destroy() - expect(deserializedProject.getBuffers().length).toBe(0) - }) - }) + expect(deserializedProject.getBuffers().length).toBe(1); + deserializedProject.getBuffers()[0].destroy(); + expect(deserializedProject.getBuffers().length).toBe(0); + }); + }); it('does not deserialize buffers when their path is now a directory', () => { const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' - ) + ); - waitsForPromise(() => atom.workspace.open(pathToOpen)) + waitsForPromise(() => atom.workspace.open(pathToOpen)); runs(() => { - expect(atom.project.getBuffers().length).toBe(1) - fs.mkdirSync(pathToOpen) + expect(atom.project.getBuffers().length).toBe(1); + fs.mkdirSync(pathToOpen); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => deserializedProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)) - }) + runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + }); it('does not deserialize buffers when their path is inaccessible', () => { if (process.platform === 'win32') { - return + return; } // chmod not supported on win32 const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' - ) - fs.writeFileSync(pathToOpen, '') + ); + fs.writeFileSync(pathToOpen, ''); - waitsForPromise(() => atom.workspace.open(pathToOpen)) + waitsForPromise(() => atom.workspace.open(pathToOpen)); runs(() => { - expect(atom.project.getBuffers().length).toBe(1) - fs.chmodSync(pathToOpen, '000') + expect(atom.project.getBuffers().length).toBe(1); + fs.chmodSync(pathToOpen, '000'); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => deserializedProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)) - }) + runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + }); it('does not deserialize buffers with their path is no longer present', () => { const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' - ) - fs.writeFileSync(pathToOpen, '') + ); + fs.writeFileSync(pathToOpen, ''); - waitsForPromise(() => atom.workspace.open(pathToOpen)) + waitsForPromise(() => atom.workspace.open(pathToOpen)); runs(() => { - expect(atom.project.getBuffers().length).toBe(1) - fs.unlinkSync(pathToOpen) + expect(atom.project.getBuffers().length).toBe(1); + fs.unlinkSync(pathToOpen); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => deserializedProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)) - }) + runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + }); it('deserializes buffers that have never been saved before', () => { const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' - ) + ); - waitsForPromise(() => atom.workspace.open(pathToOpen)) + waitsForPromise(() => atom.workspace.open(pathToOpen)); runs(() => { - atom.workspace.getActiveTextEditor().setText('unsaved\n') - expect(atom.project.getBuffers().length).toBe(1) + atom.workspace.getActiveTextEditor().setText('unsaved\n'); + expect(atom.project.getBuffers().length).toBe(1); deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => deserializedProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); runs(() => { - expect(deserializedProject.getBuffers().length).toBe(1) - expect(deserializedProject.getBuffers()[0].getPath()).toBe(pathToOpen) - expect(deserializedProject.getBuffers()[0].getText()).toBe('unsaved\n') - }) - }) + expect(deserializedProject.getBuffers().length).toBe(1); + expect(deserializedProject.getBuffers()[0].getPath()).toBe(pathToOpen); + expect(deserializedProject.getBuffers()[0].getText()).toBe('unsaved\n'); + }); + }); it('serializes marker layers and history only if Atom is quitting', () => { - waitsForPromise(() => atom.workspace.open('a')) + waitsForPromise(() => atom.workspace.open('a')); - let bufferA = null - let layerA = null - let markerA = null + let bufferA = null; + let layerA = null; + let markerA = null; runs(() => { - bufferA = atom.project.getBuffers()[0] - layerA = bufferA.addMarkerLayer({ persistent: true }) - markerA = layerA.markPosition([0, 3]) - bufferA.append('!') + bufferA = atom.project.getBuffers()[0]; + layerA = bufferA.addMarkerLayer({ persistent: true }); + markerA = layerA.markPosition([0, 3]); + bufferA.append('!'); notQuittingProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => notQuittingProject.deserialize( atom.project.serialize({ isUnloading: false }) ) - ) + ); runs(() => { expect( notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id), x => x.getMarker(markerA.id) - ).toBeUndefined() - expect(notQuittingProject.getBuffers()[0].undo()).toBe(false) + ).toBeUndefined(); + expect(notQuittingProject.getBuffers()[0].undo()).toBe(false); quittingProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, grammarRegistry: atom.grammars - }) - }) + }); + }); waitsForPromise(() => quittingProject.deserialize( atom.project.serialize({ isUnloading: true }) ) - ) + ); runs(() => { expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id), x => x.getMarker(markerA.id) - ).not.toBeUndefined() - expect(quittingProject.getBuffers()[0].undo()).toBe(true) - }) - }) - }) + ).not.toBeUndefined(); + expect(quittingProject.getBuffers()[0].undo()).toBe(true); + }); + }); + }); describe('when an editor is saved and the project has no path', () => { it("sets the project's path to the saved file's parent directory", () => { - const tempFile = temp.openSync().path - atom.project.setPaths([]) - expect(atom.project.getPaths()[0]).toBeUndefined() - let editor = null + const tempFile = temp.openSync().path; + atom.project.setPaths([]); + expect(atom.project.getPaths()[0]).toBeUndefined(); + let editor = null; waitsForPromise(() => atom.workspace.open().then(o => { - editor = o + editor = o; }) - ) + ); - waitsForPromise(() => editor.saveAs(tempFile)) + waitsForPromise(() => editor.saveAs(tempFile)); runs(() => expect(atom.project.getPaths()[0]).toBe(path.dirname(tempFile)) - ) - }) - }) + ); + }); + }); describe('.replace', () => { - let projectSpecification, projectPath1, projectPath2 + let projectSpecification, projectPath1, projectPath2; beforeEach(() => { - atom.project.replace(null) - projectPath1 = temp.mkdirSync('project-path1') - projectPath2 = temp.mkdirSync('project-path2') + atom.project.replace(null); + projectPath1 = temp.mkdirSync('project-path1'); + projectPath2 = temp.mkdirSync('project-path2'); projectSpecification = { paths: [projectPath1, projectPath2], originPath: 'originPath', config: { baz: 'buzz' } - } - }) + }; + }); it('sets a project specification', () => { - expect(atom.config.get('baz')).toBeUndefined() - atom.project.replace(projectSpecification) - expect(atom.project.getPaths()).toEqual([projectPath1, projectPath2]) - expect(atom.config.get('baz')).toBe('buzz') - }) + expect(atom.config.get('baz')).toBeUndefined(); + atom.project.replace(projectSpecification); + expect(atom.project.getPaths()).toEqual([projectPath1, projectPath2]); + expect(atom.config.get('baz')).toBe('buzz'); + }); it('clears a project through replace with no params', () => { - expect(atom.config.get('baz')).toBeUndefined() - atom.project.replace(projectSpecification) - expect(atom.config.get('baz')).toBe('buzz') - expect(atom.project.getPaths()).toEqual([projectPath1, projectPath2]) - atom.project.replace() - expect(atom.config.get('baz')).toBeUndefined() - expect(atom.project.getPaths()).toEqual([]) - }) + expect(atom.config.get('baz')).toBeUndefined(); + atom.project.replace(projectSpecification); + expect(atom.config.get('baz')).toBe('buzz'); + expect(atom.project.getPaths()).toEqual([projectPath1, projectPath2]); + atom.project.replace(); + expect(atom.config.get('baz')).toBeUndefined(); + expect(atom.project.getPaths()).toEqual([]); + }); it('responds to change of project specification', () => { - let wasCalled = false + let wasCalled = false; const callback = () => { - wasCalled = true - } - atom.project.onDidReplace(callback) - atom.project.replace(projectSpecification) - expect(wasCalled).toBe(true) - wasCalled = false - atom.project.replace() - expect(wasCalled).toBe(true) - }) - }) + wasCalled = true; + }; + atom.project.onDidReplace(callback); + atom.project.replace(projectSpecification); + expect(wasCalled).toBe(true); + wasCalled = false; + atom.project.replace(); + expect(wasCalled).toBe(true); + }); + }); describe('before and after saving a buffer', () => { - let buffer + let buffer; beforeEach(() => waitsForPromise(() => atom.project .bufferForPath(path.join(__dirname, 'fixtures', 'sample.js')) .then(o => { - buffer = o - buffer.retain() + buffer = o; + buffer.retain(); }) ) - ) + ); - afterEach(() => buffer.release()) + afterEach(() => buffer.release()); it('emits save events on the main process', () => { - spyOn(atom.project.applicationDelegate, 'emitDidSavePath') - spyOn(atom.project.applicationDelegate, 'emitWillSavePath') + spyOn(atom.project.applicationDelegate, 'emitDidSavePath'); + spyOn(atom.project.applicationDelegate, 'emitWillSavePath'); - waitsForPromise(() => buffer.save()) + waitsForPromise(() => buffer.save()); runs(() => { expect( atom.project.applicationDelegate.emitDidSavePath.calls.length - ).toBe(1) + ).toBe(1); expect( atom.project.applicationDelegate.emitDidSavePath - ).toHaveBeenCalledWith(buffer.getPath()) + ).toHaveBeenCalledWith(buffer.getPath()); expect( atom.project.applicationDelegate.emitWillSavePath.calls.length - ).toBe(1) + ).toBe(1); expect( atom.project.applicationDelegate.emitWillSavePath - ).toHaveBeenCalledWith(buffer.getPath()) - }) - }) - }) + ).toHaveBeenCalledWith(buffer.getPath()); + }); + }); + }); describe('when a watch error is thrown from the TextBuffer', () => { - let editor = null + let editor = null; beforeEach(() => waitsForPromise(() => atom.workspace.open(require.resolve('./fixtures/dir/a')).then(o => { - editor = o + editor = o; }) ) - ) + ); it('creates a warning notification', () => { - let noteSpy - atom.notifications.onDidAddNotification((noteSpy = jasmine.createSpy())) + let noteSpy; + atom.notifications.onDidAddNotification((noteSpy = jasmine.createSpy())); - const error = new Error('SomeError') - error.eventType = 'resurrect' + const error = new Error('SomeError'); + error.eventType = 'resurrect'; editor.buffer.emitter.emit('will-throw-watch-error', { handle: jasmine.createSpy(), error - }) + }); - expect(noteSpy).toHaveBeenCalled() + expect(noteSpy).toHaveBeenCalled(); - const notification = noteSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('warning') - expect(notification.getDetail()).toBe('SomeError') - expect(notification.getMessage()).toContain('`resurrect`') + const notification = noteSpy.mostRecentCall.args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getDetail()).toBe('SomeError'); + expect(notification.getMessage()).toContain('`resurrect`'); expect(notification.getMessage()).toContain( path.join('fixtures', 'dir', 'a') - ) - }) - }) + ); + }); + }); describe('when a custom repository-provider service is provided', () => { - let fakeRepositoryProvider, fakeRepository + let fakeRepositoryProvider, fakeRepository; beforeEach(() => { fakeRepository = { - destroy () { - return null + destroy() { + return null; } - } + }; fakeRepositoryProvider = { - repositoryForDirectory (directory) { - return Promise.resolve(fakeRepository) + repositoryForDirectory(directory) { + return Promise.resolve(fakeRepository); }, - repositoryForDirectorySync (directory) { - return fakeRepository + repositoryForDirectorySync(directory) { + return fakeRepository; } - } - }) + }; + }); it('uses it to create repositories for any directories that need one', () => { - const projectPath = temp.mkdirSync('atom-project') - atom.project.setPaths([projectPath]) - expect(atom.project.getRepositories()).toEqual([null]) + const projectPath = temp.mkdirSync('atom-project'); + atom.project.setPaths([projectPath]); + expect(atom.project.getRepositories()).toEqual([null]); atom.packages.serviceHub.provide( 'atom.repository-provider', '0.1.0', fakeRepositoryProvider - ) - waitsFor(() => atom.project.repositoryProviders.length > 1) - runs(() => atom.project.getRepositories()[0] === fakeRepository) - }) + ); + waitsFor(() => atom.project.repositoryProviders.length > 1); + runs(() => atom.project.getRepositories()[0] === fakeRepository); + }); it('does not create any new repositories if every directory has a repository', () => { - const repositories = atom.project.getRepositories() - expect(repositories.length).toEqual(1) - expect(repositories[0]).toBeTruthy() + const repositories = atom.project.getRepositories(); + expect(repositories.length).toEqual(1); + expect(repositories[0]).toBeTruthy(); atom.packages.serviceHub.provide( 'atom.repository-provider', '0.1.0', fakeRepositoryProvider - ) - waitsFor(() => atom.project.repositoryProviders.length > 1) - runs(() => expect(atom.project.getRepositories()).toBe(repositories)) - }) + ); + waitsFor(() => atom.project.repositoryProviders.length > 1); + runs(() => expect(atom.project.getRepositories()).toBe(repositories)); + }); it('stops using it to create repositories when the service is removed', () => { - atom.project.setPaths([]) + atom.project.setPaths([]); const disposable = atom.packages.serviceHub.provide( 'atom.repository-provider', '0.1.0', fakeRepositoryProvider - ) - waitsFor(() => atom.project.repositoryProviders.length > 1) + ); + waitsFor(() => atom.project.repositoryProviders.length > 1); runs(() => { - disposable.dispose() - atom.project.addPath(temp.mkdirSync('atom-project')) - expect(atom.project.getRepositories()).toEqual([null]) - }) - }) - }) + disposable.dispose(); + atom.project.addPath(temp.mkdirSync('atom-project')); + expect(atom.project.getRepositories()).toEqual([null]); + }); + }); + }); describe('when a custom directory-provider service is provided', () => { class DummyDirectory { - constructor (aPath) { - this.path = aPath + constructor(aPath) { + this.path = aPath; } - getPath () { - return this.path + getPath() { + return this.path; } - getFile () { + getFile() { return { - existsSync () { - return false + existsSync() { + return false; } - } + }; } - getSubdirectory () { + getSubdirectory() { return { - existsSync () { - return false + existsSync() { + return false; } - } + }; } - isRoot () { - return true + isRoot() { + return true; } - existsSync () { - return this.path.endsWith('does-exist') + existsSync() { + return this.path.endsWith('does-exist'); } - contains (filePath) { - return filePath.startsWith(this.path) + contains(filePath) { + return filePath.startsWith(this.path); } - onDidChangeFiles (callback) { - onDidChangeFilesCallback = callback - return { dispose: () => {} } + onDidChangeFiles(callback) { + onDidChangeFilesCallback = callback; + return { dispose: () => {} }; } } - let serviceDisposable = null - let onDidChangeFilesCallback = null + let serviceDisposable = null; + let onDidChangeFilesCallback = null; beforeEach(() => { serviceDisposable = atom.packages.serviceHub.provide( 'atom.directory-provider', '0.1.0', { - directoryForURISync (uri) { + directoryForURISync(uri) { if (uri.startsWith('ssh://')) { - return new DummyDirectory(uri) + return new DummyDirectory(uri); } else { - return null + return null; } } } - ) - onDidChangeFilesCallback = null + ); + onDidChangeFilesCallback = null; - waitsFor(() => atom.project.directoryProviders.length > 0) - }) + waitsFor(() => atom.project.directoryProviders.length > 0); + }); it("uses the provider's custom directories for any paths that it handles", () => { - const localPath = temp.mkdirSync('local-path') - const remotePath = 'ssh://foreign-directory:8080/does-exist' + const localPath = temp.mkdirSync('local-path'); + const remotePath = 'ssh://foreign-directory:8080/does-exist'; - atom.project.setPaths([localPath, remotePath]) + atom.project.setPaths([localPath, remotePath]); - let directories = atom.project.getDirectories() - expect(directories[0].getPath()).toBe(localPath) - expect(directories[0] instanceof Directory).toBe(true) - expect(directories[1].getPath()).toBe(remotePath) - expect(directories[1] instanceof DummyDirectory).toBe(true) + let directories = atom.project.getDirectories(); + expect(directories[0].getPath()).toBe(localPath); + expect(directories[0] instanceof Directory).toBe(true); + expect(directories[1].getPath()).toBe(remotePath); + expect(directories[1] instanceof DummyDirectory).toBe(true); // It does not add new remote paths that do not exist const nonExistentRemotePath = - 'ssh://another-directory:8080/does-not-exist' - atom.project.addPath(nonExistentRemotePath) - expect(atom.project.getDirectories().length).toBe(2) + 'ssh://another-directory:8080/does-not-exist'; + atom.project.addPath(nonExistentRemotePath); + expect(atom.project.getDirectories().length).toBe(2); // It adds new remote paths if their directories exist. - const newRemotePath = 'ssh://another-directory:8080/does-exist' - atom.project.addPath(newRemotePath) - directories = atom.project.getDirectories() - expect(directories[2].getPath()).toBe(newRemotePath) - expect(directories[2] instanceof DummyDirectory).toBe(true) - }) + const newRemotePath = 'ssh://another-directory:8080/does-exist'; + atom.project.addPath(newRemotePath); + directories = atom.project.getDirectories(); + expect(directories[2].getPath()).toBe(newRemotePath); + expect(directories[2] instanceof DummyDirectory).toBe(true); + }); it('stops using the provider when the service is removed', () => { - serviceDisposable.dispose() - atom.project.setPaths(['ssh://foreign-directory:8080/does-exist']) - expect(atom.project.getDirectories().length).toBe(0) - }) + serviceDisposable.dispose(); + atom.project.setPaths(['ssh://foreign-directory:8080/does-exist']); + expect(atom.project.getDirectories().length).toBe(0); + }); it('uses the custom onDidChangeFiles as the watcher if available', () => { // Ensure that all preexisting watchers are stopped - waitsForPromise(() => stopAllWatchers()) + waitsForPromise(() => stopAllWatchers()); - const remotePath = 'ssh://another-directory:8080/does-exist' - runs(() => atom.project.setPaths([remotePath])) - waitsForPromise(() => atom.project.getWatcherPromise(remotePath)) + const remotePath = 'ssh://another-directory:8080/does-exist'; + runs(() => atom.project.setPaths([remotePath])); + waitsForPromise(() => atom.project.getWatcherPromise(remotePath)); runs(() => { - expect(onDidChangeFilesCallback).not.toBeNull() + expect(onDidChangeFilesCallback).not.toBeNull(); - const changeSpy = jasmine.createSpy('atom.project.onDidChangeFiles') - const disposable = atom.project.onDidChangeFiles(changeSpy) + const changeSpy = jasmine.createSpy('atom.project.onDidChangeFiles'); + const disposable = atom.project.onDidChangeFiles(changeSpy); - const events = [{ action: 'created', path: remotePath + '/test.txt' }] - onDidChangeFilesCallback(events) + const events = [{ action: 'created', path: remotePath + '/test.txt' }]; + onDidChangeFilesCallback(events); - expect(changeSpy).toHaveBeenCalledWith(events) - disposable.dispose() - }) - }) - }) + expect(changeSpy).toHaveBeenCalledWith(events); + disposable.dispose(); + }); + }); + }); describe('.open(path)', () => { - let absolutePath, newBufferHandler + let absolutePath, newBufferHandler; beforeEach(() => { - absolutePath = require.resolve('./fixtures/dir/a') - newBufferHandler = jasmine.createSpy('newBufferHandler') - atom.project.onDidAddBuffer(newBufferHandler) - }) + absolutePath = require.resolve('./fixtures/dir/a'); + newBufferHandler = jasmine.createSpy('newBufferHandler'); + atom.project.onDidAddBuffer(newBufferHandler); + }); describe("when given an absolute path that isn't currently open", () => { it("returns a new edit session for the given path and emits 'buffer-created'", () => { - let editor = null + let editor = null; waitsForPromise(() => atom.workspace.open(absolutePath).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - expect(editor.buffer.getPath()).toBe(absolutePath) - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer) - }) - }) - }) + expect(editor.buffer.getPath()).toBe(absolutePath); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); + }); + }); + }); describe("when given a relative path that isn't currently opened", () => { it("returns a new edit session for the given path (relative to the project root) and emits 'buffer-created'", () => { - let editor = null + let editor = null; waitsForPromise(() => atom.workspace.open(absolutePath).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - expect(editor.buffer.getPath()).toBe(absolutePath) - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer) - }) - }) - }) + expect(editor.buffer.getPath()).toBe(absolutePath); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); + }); + }); + }); describe('when passed the path to a buffer that is currently opened', () => { it('returns a new edit session containing currently opened buffer', () => { - let editor = null + let editor = null; waitsForPromise(() => atom.workspace.open(absolutePath).then(o => { - editor = o + editor = o; }) - ) + ); - runs(() => newBufferHandler.reset()) + runs(() => newBufferHandler.reset()); waitsForPromise(() => atom.workspace .open(absolutePath) .then(({ buffer }) => expect(buffer).toBe(editor.buffer)) - ) + ); waitsForPromise(() => atom.workspace.open('a').then(({ buffer }) => { - expect(buffer).toBe(editor.buffer) - expect(newBufferHandler).not.toHaveBeenCalled() + expect(buffer).toBe(editor.buffer); + expect(newBufferHandler).not.toHaveBeenCalled(); }) - ) - }) - }) + ); + }); + }); describe('when not passed a path', () => { it("returns a new edit session and emits 'buffer-created'", () => { - let editor = null + let editor = null; waitsForPromise(() => atom.workspace.open().then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - expect(editor.buffer.getPath()).toBeUndefined() - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer) - }) - }) - }) - }) + expect(editor.buffer.getPath()).toBeUndefined(); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); + }); + }); + }); + }); describe('.bufferForPath(path)', () => { - let buffer = null + let buffer = null; beforeEach(() => waitsForPromise(() => atom.project.bufferForPath('a').then(o => { - buffer = o - buffer.retain() + buffer = o; + buffer.retain(); }) ) - ) + ); - afterEach(() => buffer.release()) + afterEach(() => buffer.release()); describe('when opening a previously opened path', () => { it('does not create a new buffer', () => { @@ -734,151 +734,151 @@ describe('Project', () => { atom.project .bufferForPath('a') .then(anotherBuffer => expect(anotherBuffer).toBe(buffer)) - ) + ); waitsForPromise(() => atom.project .bufferForPath('b') .then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer)) - ) + ); waitsForPromise(() => Promise.all([ atom.project.bufferForPath('c'), atom.project.bufferForPath('c') ]).then(([buffer1, buffer2]) => { - expect(buffer1).toBe(buffer2) + expect(buffer1).toBe(buffer2); }) - ) - }) + ); + }); it('retries loading the buffer if it previously failed', () => { waitsForPromise({ shouldReject: true }, () => { spyOn(TextBuffer, 'load').andCallFake(() => Promise.reject(new Error('Could not open file')) - ) - return atom.project.bufferForPath('b') - }) + ); + return atom.project.bufferForPath('b'); + }); waitsForPromise({ shouldReject: false }, () => { - TextBuffer.load.andCallThrough() - return atom.project.bufferForPath('b') - }) - }) + TextBuffer.load.andCallThrough(); + return atom.project.bufferForPath('b'); + }); + }); it('creates a new buffer if the previous buffer was destroyed', () => { - buffer.release() + buffer.release(); waitsForPromise(() => atom.project .bufferForPath('b') .then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer)) - ) - }) - }) - }) + ); + }); + }); + }); describe('.repositoryForDirectory(directory)', () => { it('resolves to null when the directory does not have a repository', () => { waitsForPromise(() => { - const directory = new Directory('/tmp') + const directory = new Directory('/tmp'); return atom.project.repositoryForDirectory(directory).then(result => { - expect(result).toBeNull() - expect(atom.project.repositoryProviders.length).toBeGreaterThan(0) - expect(atom.project.repositoryPromisesByPath.size).toBe(0) - }) - }) - }) + expect(result).toBeNull(); + expect(atom.project.repositoryProviders.length).toBeGreaterThan(0); + expect(atom.project.repositoryPromisesByPath.size).toBe(0); + }); + }); + }); it('resolves to a GitRepository and is cached when the given directory is a Git repo', () => { waitsForPromise(() => { - const directory = new Directory(path.join(__dirname, '..')) - const promise = atom.project.repositoryForDirectory(directory) + const directory = new Directory(path.join(__dirname, '..')); + const promise = atom.project.repositoryForDirectory(directory); return promise.then(result => { - expect(result).toBeInstanceOf(GitRepository) - const dirPath = directory.getRealPathSync() - expect(result.getPath()).toBe(path.join(dirPath, '.git')) + expect(result).toBeInstanceOf(GitRepository); + const dirPath = directory.getRealPathSync(); + expect(result.getPath()).toBe(path.join(dirPath, '.git')); // Verify that the result is cached. - expect(atom.project.repositoryForDirectory(directory)).toBe(promise) - }) - }) - }) + expect(atom.project.repositoryForDirectory(directory)).toBe(promise); + }); + }); + }); it('creates a new repository if a previous one with the same directory had been destroyed', () => { - let repository = null - const directory = new Directory(path.join(__dirname, '..')) + let repository = null; + const directory = new Directory(path.join(__dirname, '..')); waitsForPromise(() => atom.project.repositoryForDirectory(directory).then(repo => { - repository = repo + repository = repo; }) - ) + ); runs(() => { - expect(repository.isDestroyed()).toBe(false) - repository.destroy() - expect(repository.isDestroyed()).toBe(true) - }) + expect(repository.isDestroyed()).toBe(false); + repository.destroy(); + expect(repository.isDestroyed()).toBe(true); + }); waitsForPromise(() => atom.project.repositoryForDirectory(directory).then(repo => { - repository = repo + repository = repo; }) - ) + ); - runs(() => expect(repository.isDestroyed()).toBe(false)) - }) - }) + runs(() => expect(repository.isDestroyed()).toBe(false)); + }); + }); describe('.setPaths(paths, options)', () => { describe('when path is a file', () => { it("sets its path to the file's parent directory and updates the root directory", () => { - const filePath = require.resolve('./fixtures/dir/a') - atom.project.setPaths([filePath]) - expect(atom.project.getPaths()[0]).toEqual(path.dirname(filePath)) + const filePath = require.resolve('./fixtures/dir/a'); + atom.project.setPaths([filePath]); + expect(atom.project.getPaths()[0]).toEqual(path.dirname(filePath)); expect(atom.project.getDirectories()[0].path).toEqual( path.dirname(filePath) - ) - }) - }) + ); + }); + }); describe('when path is a directory', () => { it('assigns the directories and repositories', () => { - const directory1 = temp.mkdirSync('non-git-repo') - const directory2 = temp.mkdirSync('git-repo1') - const directory3 = temp.mkdirSync('git-repo2') + const directory1 = temp.mkdirSync('non-git-repo'); + const directory2 = temp.mkdirSync('git-repo1'); + const directory3 = temp.mkdirSync('git-repo2'); const gitDirPath = fs.absolute( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - fs.copySync(gitDirPath, path.join(directory2, '.git')) - fs.copySync(gitDirPath, path.join(directory3, '.git')) + ); + fs.copySync(gitDirPath, path.join(directory2, '.git')); + fs.copySync(gitDirPath, path.join(directory3, '.git')); - atom.project.setPaths([directory1, directory2, directory3]) + atom.project.setPaths([directory1, directory2, directory3]); - const [repo1, repo2, repo3] = atom.project.getRepositories() - expect(repo1).toBeNull() - expect(repo2.getShortHead()).toBe('master') + const [repo1, repo2, repo3] = atom.project.getRepositories(); + expect(repo1).toBeNull(); + expect(repo2.getShortHead()).toBe('master'); expect(repo2.getPath()).toBe( fs.realpathSync(path.join(directory2, '.git')) - ) - expect(repo3.getShortHead()).toBe('master') + ); + expect(repo3.getShortHead()).toBe('master'); expect(repo3.getPath()).toBe( fs.realpathSync(path.join(directory3, '.git')) - ) - }) + ); + }); it('calls callbacks registered with ::onDidChangePaths', () => { - const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') - atom.project.onDidChangePaths(onDidChangePathsSpy) + const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy'); + atom.project.onDidChangePaths(onDidChangePathsSpy); - const paths = [temp.mkdirSync('dir1'), temp.mkdirSync('dir2')] - atom.project.setPaths(paths) + const paths = [temp.mkdirSync('dir1'), temp.mkdirSync('dir2')]; + atom.project.setPaths(paths); - expect(onDidChangePathsSpy.callCount).toBe(1) - expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual(paths) - }) + expect(onDidChangePathsSpy.callCount).toBe(1); + expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual(paths); + }); it('optionally throws an error with any paths that did not exist', () => { const paths = [ @@ -886,306 +886,306 @@ describe('Project', () => { '/doesnt-exists/0', temp.mkdirSync('exists1'), '/doesnt-exists/1' - ] + ]; try { - atom.project.setPaths(paths, { mustExist: true }) - expect('no exception thrown').toBeUndefined() + atom.project.setPaths(paths, { mustExist: true }); + expect('no exception thrown').toBeUndefined(); } catch (e) { - expect(e.missingProjectPaths).toEqual([paths[1], paths[3]]) + expect(e.missingProjectPaths).toEqual([paths[1], paths[3]]); } - expect(atom.project.getPaths()).toEqual([paths[0], paths[2]]) - }) - }) + expect(atom.project.getPaths()).toEqual([paths[0], paths[2]]); + }); + }); describe('when no paths are given', () => { it('clears its path', () => { - atom.project.setPaths([]) - expect(atom.project.getPaths()).toEqual([]) - expect(atom.project.getDirectories()).toEqual([]) - }) - }) + atom.project.setPaths([]); + expect(atom.project.getPaths()).toEqual([]); + expect(atom.project.getDirectories()).toEqual([]); + }); + }); it('normalizes the path to remove consecutive slashes, ., and .. segments', () => { atom.project.setPaths([ `${require.resolve('./fixtures/dir/a')}${path.sep}b${path.sep}${ path.sep }..` - ]) + ]); expect(atom.project.getPaths()[0]).toEqual( path.dirname(require.resolve('./fixtures/dir/a')) - ) + ); expect(atom.project.getDirectories()[0].path).toEqual( path.dirname(require.resolve('./fixtures/dir/a')) - ) - }) - }) + ); + }); + }); describe('.addPath(path, options)', () => { it('calls callbacks registered with ::onDidChangePaths', () => { - const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') - atom.project.onDidChangePaths(onDidChangePathsSpy) + const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy'); + atom.project.onDidChangePaths(onDidChangePathsSpy); - const [oldPath] = atom.project.getPaths() + const [oldPath] = atom.project.getPaths(); - const newPath = temp.mkdirSync('dir') - atom.project.addPath(newPath) + const newPath = temp.mkdirSync('dir'); + atom.project.addPath(newPath); - expect(onDidChangePathsSpy.callCount).toBe(1) + expect(onDidChangePathsSpy.callCount).toBe(1); expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual([ oldPath, newPath - ]) - }) + ]); + }); it("doesn't add redundant paths", () => { - const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') - atom.project.onDidChangePaths(onDidChangePathsSpy) - const [oldPath] = atom.project.getPaths() + const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy'); + atom.project.onDidChangePaths(onDidChangePathsSpy); + const [oldPath] = atom.project.getPaths(); // Doesn't re-add an existing root directory - atom.project.addPath(oldPath) - expect(atom.project.getPaths()).toEqual([oldPath]) - expect(onDidChangePathsSpy).not.toHaveBeenCalled() + atom.project.addPath(oldPath); + expect(atom.project.getPaths()).toEqual([oldPath]); + expect(onDidChangePathsSpy).not.toHaveBeenCalled(); // Doesn't add an entry for a file-path within an existing root directory - atom.project.addPath(path.join(oldPath, 'some-file.txt')) - expect(atom.project.getPaths()).toEqual([oldPath]) - expect(onDidChangePathsSpy).not.toHaveBeenCalled() + atom.project.addPath(path.join(oldPath, 'some-file.txt')); + expect(atom.project.getPaths()).toEqual([oldPath]); + expect(onDidChangePathsSpy).not.toHaveBeenCalled(); // Does add an entry for a directory within an existing directory - const newPath = path.join(oldPath, 'a-dir') - atom.project.addPath(newPath) - expect(atom.project.getPaths()).toEqual([oldPath, newPath]) - expect(onDidChangePathsSpy).toHaveBeenCalled() - }) + const newPath = path.join(oldPath, 'a-dir'); + atom.project.addPath(newPath); + expect(atom.project.getPaths()).toEqual([oldPath, newPath]); + expect(onDidChangePathsSpy).toHaveBeenCalled(); + }); it("doesn't add non-existent directories", () => { - const previousPaths = atom.project.getPaths() - atom.project.addPath('/this-definitely/does-not-exist') - expect(atom.project.getPaths()).toEqual(previousPaths) - }) + const previousPaths = atom.project.getPaths(); + atom.project.addPath('/this-definitely/does-not-exist'); + expect(atom.project.getPaths()).toEqual(previousPaths); + }); it('optionally throws on non-existent directories', () => { expect(() => atom.project.addPath('/this-definitely/does-not-exist', { mustExist: true }) - ).toThrow() - }) - }) + ).toThrow(); + }); + }); describe('.removePath(path)', () => { - let onDidChangePathsSpy = null + let onDidChangePathsSpy = null; beforeEach(() => { - onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths listener') - atom.project.onDidChangePaths(onDidChangePathsSpy) - }) + onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths listener'); + atom.project.onDidChangePaths(onDidChangePathsSpy); + }); it('removes the directory and repository for the path', () => { - const result = atom.project.removePath(atom.project.getPaths()[0]) - expect(atom.project.getDirectories()).toEqual([]) - expect(atom.project.getRepositories()).toEqual([]) - expect(atom.project.getPaths()).toEqual([]) - expect(result).toBe(true) - expect(onDidChangePathsSpy).toHaveBeenCalled() - }) + const result = atom.project.removePath(atom.project.getPaths()[0]); + expect(atom.project.getDirectories()).toEqual([]); + expect(atom.project.getRepositories()).toEqual([]); + expect(atom.project.getPaths()).toEqual([]); + expect(result).toBe(true); + expect(onDidChangePathsSpy).toHaveBeenCalled(); + }); it("does nothing if the path is not one of the project's root paths", () => { - const originalPaths = atom.project.getPaths() - const result = atom.project.removePath(originalPaths[0] + 'xyz') - expect(result).toBe(false) - expect(atom.project.getPaths()).toEqual(originalPaths) - expect(onDidChangePathsSpy).not.toHaveBeenCalled() - }) + const originalPaths = atom.project.getPaths(); + const result = atom.project.removePath(originalPaths[0] + 'xyz'); + expect(result).toBe(false); + expect(atom.project.getPaths()).toEqual(originalPaths); + expect(onDidChangePathsSpy).not.toHaveBeenCalled(); + }); it("doesn't destroy the repository if it is shared by another root directory", () => { - atom.project.setPaths([__dirname, path.join(__dirname, '..', 'src')]) - atom.project.removePath(__dirname) + atom.project.setPaths([__dirname, path.join(__dirname, '..', 'src')]); + atom.project.removePath(__dirname); expect(atom.project.getPaths()).toEqual([ path.join(__dirname, '..', 'src') - ]) - expect(atom.project.getRepositories()[0].isSubmodule('src')).toBe(false) - }) + ]); + expect(atom.project.getRepositories()[0].isSubmodule('src')).toBe(false); + }); it('removes a path that is represented as a URI', () => { atom.packages.serviceHub.provide('atom.directory-provider', '0.1.0', { - directoryForURISync (uri) { + directoryForURISync(uri) { return { - getPath () { - return uri + getPath() { + return uri; }, - getSubdirectory () { - return {} + getSubdirectory() { + return {}; }, - isRoot () { - return true + isRoot() { + return true; }, - existsSync () { - return true + existsSync() { + return true; }, - off () {} - } + off() {} + }; } - }) + }); - const ftpURI = 'ftp://example.com/some/folder' + const ftpURI = 'ftp://example.com/some/folder'; - atom.project.setPaths([ftpURI]) - expect(atom.project.getPaths()).toEqual([ftpURI]) + atom.project.setPaths([ftpURI]); + expect(atom.project.getPaths()).toEqual([ftpURI]); - atom.project.removePath(ftpURI) - expect(atom.project.getPaths()).toEqual([]) - }) - }) + atom.project.removePath(ftpURI); + expect(atom.project.getPaths()).toEqual([]); + }); + }); describe('.onDidChangeFiles()', () => { - let sub = [] - const events = [] - let checkCallback = () => {} + let sub = []; + const events = []; + let checkCallback = () => {}; beforeEach(() => { sub = atom.project.onDidChangeFiles(incoming => { - events.push(...incoming) - checkCallback() - }) - }) + events.push(...incoming); + checkCallback(); + }); + }); - afterEach(() => sub.dispose()) + afterEach(() => sub.dispose()); const waitForEvents = paths => { - const remaining = new Set(paths.map(p => fs.realpathSync(p))) + const remaining = new Set(paths.map(p => fs.realpathSync(p))); return new Promise((resolve, reject) => { checkCallback = () => { for (let event of events) { - remaining.delete(event.path) + remaining.delete(event.path); } if (remaining.size === 0) { - resolve() + resolve(); } - } + }; const expire = () => { - checkCallback = () => {} - console.error('Paths not seen:', remaining) + checkCallback = () => {}; + console.error('Paths not seen:', remaining); reject( new Error('Expired before all expected events were delivered.') - ) - } + ); + }; - checkCallback() - setTimeout(expire, 2000) - }) - } + checkCallback(); + setTimeout(expire, 2000); + }); + }; it('reports filesystem changes within project paths', () => { - const dirOne = temp.mkdirSync('atom-spec-project-one') - const fileOne = path.join(dirOne, 'file-one.txt') - const fileTwo = path.join(dirOne, 'file-two.txt') - const dirTwo = temp.mkdirSync('atom-spec-project-two') - const fileThree = path.join(dirTwo, 'file-three.txt') + const dirOne = temp.mkdirSync('atom-spec-project-one'); + const fileOne = path.join(dirOne, 'file-one.txt'); + const fileTwo = path.join(dirOne, 'file-two.txt'); + const dirTwo = temp.mkdirSync('atom-spec-project-two'); + const fileThree = path.join(dirTwo, 'file-three.txt'); // Ensure that all preexisting watchers are stopped - waitsForPromise(() => stopAllWatchers()) + waitsForPromise(() => stopAllWatchers()); - runs(() => atom.project.setPaths([dirOne])) - waitsForPromise(() => atom.project.getWatcherPromise(dirOne)) + runs(() => atom.project.setPaths([dirOne])); + waitsForPromise(() => atom.project.getWatcherPromise(dirOne)); runs(() => { - expect(atom.project.watcherPromisesByPath[dirTwo]).toEqual(undefined) + expect(atom.project.watcherPromisesByPath[dirTwo]).toEqual(undefined); - fs.writeFileSync(fileThree, 'three\n') - fs.writeFileSync(fileTwo, 'two\n') - fs.writeFileSync(fileOne, 'one\n') - }) + fs.writeFileSync(fileThree, 'three\n'); + fs.writeFileSync(fileTwo, 'two\n'); + fs.writeFileSync(fileOne, 'one\n'); + }); - waitsForPromise(() => waitForEvents([fileOne, fileTwo])) + waitsForPromise(() => waitForEvents([fileOne, fileTwo])); runs(() => expect(events.some(event => event.path === fileThree)).toBeFalsy() - ) - }) - }) + ); + }); + }); describe('.onDidAddBuffer()', () => { it('invokes the callback with added text buffers', () => { - const buffers = [] - const added = [] + const buffers = []; + const added = []; waitsForPromise(() => atom.project .buildBuffer(require.resolve('./fixtures/dir/a')) .then(o => buffers.push(o)) - ) + ); runs(() => { - expect(buffers.length).toBe(1) - atom.project.onDidAddBuffer(buffer => added.push(buffer)) - }) + expect(buffers.length).toBe(1); + atom.project.onDidAddBuffer(buffer => added.push(buffer)); + }); waitsForPromise(() => atom.project .buildBuffer(require.resolve('./fixtures/dir/b')) .then(o => buffers.push(o)) - ) + ); runs(() => { - expect(buffers.length).toBe(2) - expect(added).toEqual([buffers[1]]) - }) - }) - }) + expect(buffers.length).toBe(2); + expect(added).toEqual([buffers[1]]); + }); + }); + }); describe('.observeBuffers()', () => { it('invokes the observer with current and future text buffers', () => { - const buffers = [] - const observed = [] + const buffers = []; + const observed = []; waitsForPromise(() => atom.project .buildBuffer(require.resolve('./fixtures/dir/a')) .then(o => buffers.push(o)) - ) + ); waitsForPromise(() => atom.project .buildBuffer(require.resolve('./fixtures/dir/b')) .then(o => buffers.push(o)) - ) + ); runs(() => { - expect(buffers.length).toBe(2) - atom.project.observeBuffers(buffer => observed.push(buffer)) - expect(observed).toEqual(buffers) - }) + expect(buffers.length).toBe(2); + atom.project.observeBuffers(buffer => observed.push(buffer)); + expect(observed).toEqual(buffers); + }); waitsForPromise(() => atom.project .buildBuffer(require.resolve('./fixtures/dir/b')) .then(o => buffers.push(o)) - ) + ); runs(() => { - expect(observed.length).toBe(3) - expect(buffers.length).toBe(3) - expect(observed).toEqual(buffers) - }) - }) - }) + expect(observed.length).toBe(3); + expect(buffers.length).toBe(3); + expect(observed).toEqual(buffers); + }); + }); + }); describe('.observeRepositories()', () => { it('invokes the observer with current and future repositories', () => { - const observed = [] + const observed = []; - const directory1 = temp.mkdirSync('git-repo1') + const directory1 = temp.mkdirSync('git-repo1'); const gitDirPath1 = fs.absolute( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - fs.copySync(gitDirPath1, path.join(directory1, '.git')) + ); + fs.copySync(gitDirPath1, path.join(directory1, '.git')); - const directory2 = temp.mkdirSync('git-repo2') + const directory2 = temp.mkdirSync('git-repo2'); const gitDirPath2 = fs.absolute( path.join( __dirname, @@ -1194,180 +1194,180 @@ describe('Project', () => { 'repo-with-submodules', 'git.git' ) - ) - fs.copySync(gitDirPath2, path.join(directory2, '.git')) + ); + fs.copySync(gitDirPath2, path.join(directory2, '.git')); - atom.project.setPaths([directory1]) + atom.project.setPaths([directory1]); const disposable = atom.project.observeRepositories(repo => observed.push(repo) - ) - expect(observed.length).toBe(1) + ); + expect(observed.length).toBe(1); expect(observed[0].getReferenceTarget('refs/heads/master')).toBe( 'ef046e9eecaa5255ea5e9817132d4001724d6ae1' - ) + ); - atom.project.addPath(directory2) - expect(observed.length).toBe(2) + atom.project.addPath(directory2); + expect(observed.length).toBe(2); expect(observed[1].getReferenceTarget('refs/heads/master')).toBe( 'd2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5' - ) + ); - disposable.dispose() - }) - }) + disposable.dispose(); + }); + }); describe('.onDidAddRepository()', () => { it('invokes callback when a path is added and the path is the root of a repository', () => { - const observed = [] + const observed = []; const disposable = atom.project.onDidAddRepository(repo => observed.push(repo) - ) + ); - const projectRootPath = temp.mkdirSync() + const projectRootPath = temp.mkdirSync(); const fixtureRepoPath = fs.absolute( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - fs.copySync(fixtureRepoPath, path.join(projectRootPath, '.git')) + ); + fs.copySync(fixtureRepoPath, path.join(projectRootPath, '.git')); - atom.project.addPath(projectRootPath) - expect(observed.length).toBe(1) + atom.project.addPath(projectRootPath); + expect(observed.length).toBe(1); expect(observed[0].getOriginURL()).toEqual( 'https://github.com/example-user/example-repo.git' - ) + ); - disposable.dispose() - }) + disposable.dispose(); + }); it('invokes callback when a path is added and the path is subdirectory of a repository', () => { - const observed = [] + const observed = []; const disposable = atom.project.onDidAddRepository(repo => observed.push(repo) - ) + ); - const projectRootPath = temp.mkdirSync() + const projectRootPath = temp.mkdirSync(); const fixtureRepoPath = fs.absolute( path.join(__dirname, 'fixtures', 'git', 'master.git') - ) - fs.copySync(fixtureRepoPath, path.join(projectRootPath, '.git')) + ); + fs.copySync(fixtureRepoPath, path.join(projectRootPath, '.git')); - const projectSubDirPath = path.join(projectRootPath, 'sub-dir') - fs.mkdirSync(projectSubDirPath) + const projectSubDirPath = path.join(projectRootPath, 'sub-dir'); + fs.mkdirSync(projectSubDirPath); - atom.project.addPath(projectSubDirPath) - expect(observed.length).toBe(1) + atom.project.addPath(projectSubDirPath); + expect(observed.length).toBe(1); expect(observed[0].getOriginURL()).toEqual( 'https://github.com/example-user/example-repo.git' - ) + ); - disposable.dispose() - }) + disposable.dispose(); + }); it('does not invoke callback when a path is added and the path is not part of a repository', () => { - const observed = [] + const observed = []; const disposable = atom.project.onDidAddRepository(repo => observed.push(repo) - ) + ); - atom.project.addPath(temp.mkdirSync('not-a-repository')) - expect(observed.length).toBe(0) + atom.project.addPath(temp.mkdirSync('not-a-repository')); + expect(observed.length).toBe(0); - disposable.dispose() - }) - }) + disposable.dispose(); + }); + }); describe('.relativize(path)', () => { it('returns the path, relative to whichever root directory it is inside of', () => { - atom.project.addPath(temp.mkdirSync('another-path')) + atom.project.addPath(temp.mkdirSync('another-path')); - let rootPath = atom.project.getPaths()[0] - let childPath = path.join(rootPath, 'some', 'child', 'directory') + let rootPath = atom.project.getPaths()[0]; + let childPath = path.join(rootPath, 'some', 'child', 'directory'); expect(atom.project.relativize(childPath)).toBe( path.join('some', 'child', 'directory') - ) + ); - rootPath = atom.project.getPaths()[1] - childPath = path.join(rootPath, 'some', 'child', 'directory') + rootPath = atom.project.getPaths()[1]; + childPath = path.join(rootPath, 'some', 'child', 'directory'); expect(atom.project.relativize(childPath)).toBe( path.join('some', 'child', 'directory') - ) - }) + ); + }); it('returns the given path if it is not in any of the root directories', () => { - const randomPath = path.join('some', 'random', 'path') - expect(atom.project.relativize(randomPath)).toBe(randomPath) - }) - }) + const randomPath = path.join('some', 'random', 'path'); + expect(atom.project.relativize(randomPath)).toBe(randomPath); + }); + }); describe('.relativizePath(path)', () => { it('returns the root path that contains the given path, and the path relativized to that root path', () => { - atom.project.addPath(temp.mkdirSync('another-path')) + atom.project.addPath(temp.mkdirSync('another-path')); - let rootPath = atom.project.getPaths()[0] - let childPath = path.join(rootPath, 'some', 'child', 'directory') + let rootPath = atom.project.getPaths()[0]; + let childPath = path.join(rootPath, 'some', 'child', 'directory'); expect(atom.project.relativizePath(childPath)).toEqual([ rootPath, path.join('some', 'child', 'directory') - ]) + ]); - rootPath = atom.project.getPaths()[1] - childPath = path.join(rootPath, 'some', 'child', 'directory') + rootPath = atom.project.getPaths()[1]; + childPath = path.join(rootPath, 'some', 'child', 'directory'); expect(atom.project.relativizePath(childPath)).toEqual([ rootPath, path.join('some', 'child', 'directory') - ]) - }) + ]); + }); describe("when the given path isn't inside of any of the project's path", () => { it('returns null for the root path, and the given path unchanged', () => { - const randomPath = path.join('some', 'random', 'path') + const randomPath = path.join('some', 'random', 'path'); expect(atom.project.relativizePath(randomPath)).toEqual([ null, randomPath - ]) - }) - }) + ]); + }); + }); describe('when the given path is a URL', () => { it('returns null for the root path, and the given path unchanged', () => { - const url = 'http://the-path' - expect(atom.project.relativizePath(url)).toEqual([null, url]) - }) - }) + const url = 'http://the-path'; + expect(atom.project.relativizePath(url)).toEqual([null, url]); + }); + }); describe('when the given path is inside more than one root folder', () => { it('uses the root folder that is closest to the given path', () => { - atom.project.addPath(path.join(atom.project.getPaths()[0], 'a-dir')) + atom.project.addPath(path.join(atom.project.getPaths()[0], 'a-dir')); const inputPath = path.join( atom.project.getPaths()[1], 'somewhere/something.txt' - ) + ); - expect(atom.project.getDirectories()[0].contains(inputPath)).toBe(true) - expect(atom.project.getDirectories()[1].contains(inputPath)).toBe(true) + expect(atom.project.getDirectories()[0].contains(inputPath)).toBe(true); + expect(atom.project.getDirectories()[1].contains(inputPath)).toBe(true); expect(atom.project.relativizePath(inputPath)).toEqual([ atom.project.getPaths()[1], path.join('somewhere', 'something.txt') - ]) - }) - }) - }) + ]); + }); + }); + }); describe('.contains(path)', () => { it('returns whether or not the given path is in one of the root directories', () => { - const rootPath = atom.project.getPaths()[0] - const childPath = path.join(rootPath, 'some', 'child', 'directory') - expect(atom.project.contains(childPath)).toBe(true) + const rootPath = atom.project.getPaths()[0]; + const childPath = path.join(rootPath, 'some', 'child', 'directory'); + expect(atom.project.contains(childPath)).toBe(true); - const randomPath = path.join('some', 'random', 'path') - expect(atom.project.contains(randomPath)).toBe(false) - }) - }) + const randomPath = path.join('some', 'random', 'path'); + expect(atom.project.contains(randomPath)).toBe(false); + }); + }); describe('.resolvePath(uri)', () => { it('normalizes disk drive letter in passed path on #win32', () => { - expect(atom.project.resolvePath('d:\\file.txt')).toEqual('D:\\file.txt') - }) - }) -}) + expect(atom.project.resolvePath('d:\\file.txt')).toEqual('D:\\file.txt'); + }); + }); +}); diff --git a/spec/reopen-project-menu-manager-spec.js b/spec/reopen-project-menu-manager-spec.js index 82d8eddf5..c10d9db87 100644 --- a/spec/reopen-project-menu-manager-spec.js +++ b/spec/reopen-project-menu-manager-spec.js @@ -1,311 +1,314 @@ /** @babel */ -import { Disposable } from 'event-kit' +import { Disposable } from 'event-kit'; -const ReopenProjectMenuManager = require('../src/reopen-project-menu-manager') +const ReopenProjectMenuManager = require('../src/reopen-project-menu-manager'); -function numberRange (low, high) { - const size = high - low - const result = new Array(size) - for (var i = 0; i < size; i++) result[i] = low + i - return result +function numberRange(low, high) { + const size = high - low; + const result = new Array(size); + for (var i = 0; i < size; i++) result[i] = low + i; + return result; } describe('ReopenProjectMenuManager', () => { - let menuManager, commandRegistry, config, historyManager, reopenProjects - let commandDisposable, configDisposable, historyDisposable - let openFunction + let menuManager, commandRegistry, config, historyManager, reopenProjects; + let commandDisposable, configDisposable, historyDisposable; + let openFunction; beforeEach(() => { - menuManager = jasmine.createSpyObj('MenuManager', ['add']) - menuManager.add.andReturn(new Disposable()) + menuManager = jasmine.createSpyObj('MenuManager', ['add']); + menuManager.add.andReturn(new Disposable()); - commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']) - commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - commandRegistry.add.andReturn(commandDisposable) + commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']); + commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']); + commandRegistry.add.andReturn(commandDisposable); - config = jasmine.createSpyObj('Config', ['onDidChange', 'get']) - config.get.andReturn(10) - configDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - config.didChangeListener = {} + config = jasmine.createSpyObj('Config', ['onDidChange', 'get']); + config.get.andReturn(10); + configDisposable = jasmine.createSpyObj('Disposable', ['dispose']); + config.didChangeListener = {}; config.onDidChange.andCallFake((key, fn) => { - config.didChangeListener[key] = fn - return configDisposable - }) + config.didChangeListener[key] = fn; + return configDisposable; + }); historyManager = jasmine.createSpyObj('historyManager', [ 'getProjects', 'onDidChangeProjects' - ]) - historyManager.getProjects.andReturn([]) - historyDisposable = jasmine.createSpyObj('Disposable', ['dispose']) + ]); + historyManager.getProjects.andReturn([]); + historyDisposable = jasmine.createSpyObj('Disposable', ['dispose']); historyManager.onDidChangeProjects.andCallFake(fn => { - historyManager.changeProjectsListener = fn - return historyDisposable - }) + historyManager.changeProjectsListener = fn; + return historyDisposable; + }); - openFunction = jasmine.createSpy() + openFunction = jasmine.createSpy(); reopenProjects = new ReopenProjectMenuManager({ menu: menuManager, commands: commandRegistry, history: historyManager, config, open: openFunction - }) - }) + }); + }); describe('constructor', () => { it("registers the 'reopen-project' command function", () => { - expect(commandRegistry.add).toHaveBeenCalled() - const cmdCall = commandRegistry.add.calls[0] - expect(cmdCall.args.length).toBe(2) - expect(cmdCall.args[0]).toBe('atom-workspace') + 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:reopen-project']).toBe( 'function' - ) - }) - }) + ); + }); + }); describe('dispose', () => { it('disposes of the history, command and config disposables', () => { - reopenProjects.dispose() - expect(historyDisposable.dispose).toHaveBeenCalled() - expect(configDisposable.dispose).toHaveBeenCalled() - expect(commandDisposable.dispose).toHaveBeenCalled() - }) + reopenProjects.dispose(); + expect(historyDisposable.dispose).toHaveBeenCalled(); + expect(configDisposable.dispose).toHaveBeenCalled(); + expect(commandDisposable.dispose).toHaveBeenCalled(); + }); it('disposes of the menu disposable once used', () => { - const menuDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - menuManager.add.andReturn(menuDisposable) - reopenProjects.update() - expect(menuDisposable.dispose).not.toHaveBeenCalled() - reopenProjects.dispose() - expect(menuDisposable.dispose).toHaveBeenCalled() - }) - }) + const menuDisposable = jasmine.createSpyObj('Disposable', ['dispose']); + menuManager.add.andReturn(menuDisposable); + reopenProjects.update(); + expect(menuDisposable.dispose).not.toHaveBeenCalled(); + reopenProjects.dispose(); + expect(menuDisposable.dispose).toHaveBeenCalled(); + }); + }); describe('the command', () => { it('calls open with the paths of the project specified by the detail index', () => { historyManager.getProjects.andReturn([ { paths: ['/a'] }, { paths: ['/b', 'c:\\'] } - ]) - reopenProjects.update() + ]); + reopenProjects.update(); const reopenProjectCommand = - commandRegistry.add.calls[0].args[1]['application:reopen-project'] - reopenProjectCommand({ detail: { index: 1 } }) + commandRegistry.add.calls[0].args[1]['application:reopen-project']; + reopenProjectCommand({ detail: { index: 1 } }); - expect(openFunction).toHaveBeenCalled() - expect(openFunction.calls[0].args[0]).toEqual(['/b', 'c:\\']) - }) + expect(openFunction).toHaveBeenCalled(); + expect(openFunction.calls[0].args[0]).toEqual(['/b', 'c:\\']); + }); it('does not call open when no command detail is supplied', () => { const reopenProjectCommand = - commandRegistry.add.calls[0].args[1]['application:reopen-project'] - reopenProjectCommand({}) + commandRegistry.add.calls[0].args[1]['application:reopen-project']; + reopenProjectCommand({}); - expect(openFunction).not.toHaveBeenCalled() - }) + expect(openFunction).not.toHaveBeenCalled(); + }); it('does not call open when no command detail index is supplied', () => { const reopenProjectCommand = - commandRegistry.add.calls[0].args[1]['application:reopen-project'] - reopenProjectCommand({ detail: { anything: 'here' } }) + commandRegistry.add.calls[0].args[1]['application:reopen-project']; + reopenProjectCommand({ detail: { anything: 'here' } }); - expect(openFunction).not.toHaveBeenCalled() - }) - }) + expect(openFunction).not.toHaveBeenCalled(); + }); + }); describe('update', () => { it('adds menu items to MenuManager based on projects from HistoryManager', () => { historyManager.getProjects.andReturn([ { paths: ['/a'] }, { paths: ['/b', 'c:\\'] } - ]) - reopenProjects.update() - expect(historyManager.getProjects).toHaveBeenCalled() - expect(menuManager.add).toHaveBeenCalled() - const menuArg = menuManager.add.calls[0].args[0] - expect(menuArg.length).toBe(1) - expect(menuArg[0].label).toBe('File') - expect(menuArg[0].submenu.length).toBe(1) - const projectsMenu = menuArg[0].submenu[0] - expect(projectsMenu.label).toBe('Reopen Project') - expect(projectsMenu.submenu.length).toBe(2) + ]); + reopenProjects.update(); + expect(historyManager.getProjects).toHaveBeenCalled(); + expect(menuManager.add).toHaveBeenCalled(); + const menuArg = menuManager.add.calls[0].args[0]; + expect(menuArg.length).toBe(1); + expect(menuArg[0].label).toBe('File'); + expect(menuArg[0].submenu.length).toBe(1); + const projectsMenu = menuArg[0].submenu[0]; + expect(projectsMenu.label).toBe('Reopen Project'); + expect(projectsMenu.submenu.length).toBe(2); - const first = projectsMenu.submenu[0] - expect(first.label).toBe('/a') - expect(first.command).toBe('application:reopen-project') - expect(first.commandDetail).toEqual({ index: 0, paths: ['/a'] }) + const first = projectsMenu.submenu[0]; + expect(first.label).toBe('/a'); + expect(first.command).toBe('application:reopen-project'); + expect(first.commandDetail).toEqual({ index: 0, paths: ['/a'] }); - const second = projectsMenu.submenu[1] - expect(second.label).toBe('b, c:\\') - expect(second.command).toBe('application:reopen-project') - expect(second.commandDetail).toEqual({ index: 1, paths: ['/b', 'c:\\'] }) - }) + const second = projectsMenu.submenu[1]; + expect(second.label).toBe('b, c:\\'); + expect(second.command).toBe('application:reopen-project'); + expect(second.commandDetail).toEqual({ index: 1, paths: ['/b', 'c:\\'] }); + }); it("adds only the number of menu items specified in the 'core.reopenProjectMenuCount' config", () => { historyManager.getProjects.andReturn( numberRange(1, 100).map(i => ({ paths: ['/test/' + i] })) - ) - reopenProjects.update() - expect(menuManager.add).toHaveBeenCalled() - const menu = menuManager.add.calls[0].args[0][0] - expect(menu.label).toBe('File') - expect(menu.submenu.length).toBe(1) - expect(menu.submenu[0].label).toBe('Reopen Project') - expect(menu.submenu[0].submenu.length).toBe(10) - }) + ); + reopenProjects.update(); + expect(menuManager.add).toHaveBeenCalled(); + const menu = menuManager.add.calls[0].args[0][0]; + expect(menu.label).toBe('File'); + expect(menu.submenu.length).toBe(1); + expect(menu.submenu[0].label).toBe('Reopen Project'); + expect(menu.submenu[0].submenu.length).toBe(10); + }); it('disposes the previously menu built', () => { - const menuDisposable = jasmine.createSpyObj('Disposable', ['dispose']) - menuManager.add.andReturn(menuDisposable) - reopenProjects.update() - expect(menuDisposable.dispose).not.toHaveBeenCalled() - reopenProjects.update() - expect(menuDisposable.dispose).toHaveBeenCalled() - }) + const menuDisposable = jasmine.createSpyObj('Disposable', ['dispose']); + menuManager.add.andReturn(menuDisposable); + reopenProjects.update(); + expect(menuDisposable.dispose).not.toHaveBeenCalled(); + reopenProjects.update(); + expect(menuDisposable.dispose).toHaveBeenCalled(); + }); it("is called when the Config changes for 'core.reopenProjectMenuCount'", () => { historyManager.getProjects.andReturn( numberRange(1, 100).map(i => ({ paths: ['/test/' + i] })) - ) - reopenProjects.update() - config.get.andReturn(25) + ); + reopenProjects.update(); + config.get.andReturn(25); config.didChangeListener['core.reopenProjectMenuCount']({ oldValue: 10, newValue: 25 - }) + }); - const finalArgs = menuManager.add.calls[1].args[0] - const projectsMenu = finalArgs[0].submenu[0].submenu + const finalArgs = menuManager.add.calls[1].args[0]; + const projectsMenu = finalArgs[0].submenu[0].submenu; - expect(projectsMenu.length).toBe(25) - }) + expect(projectsMenu.length).toBe(25); + }); it("is called when the HistoryManager's projects change", () => { - reopenProjects.update() + reopenProjects.update(); historyManager.getProjects.andReturn([ { paths: ['/a'] }, { paths: ['/b', 'c:\\'] } - ]) - historyManager.changeProjectsListener() - expect(menuManager.add.calls.length).toBe(2) + ]); + historyManager.changeProjectsListener(); + expect(menuManager.add.calls.length).toBe(2); - const finalArgs = menuManager.add.calls[1].args[0] - const projectsMenu = finalArgs[0].submenu[0] + const finalArgs = menuManager.add.calls[1].args[0]; + const projectsMenu = finalArgs[0].submenu[0]; - const first = projectsMenu.submenu[0] - expect(first.label).toBe('/a') - expect(first.command).toBe('application:reopen-project') - expect(first.commandDetail).toEqual({ index: 0, paths: ['/a'] }) + const first = projectsMenu.submenu[0]; + expect(first.label).toBe('/a'); + expect(first.command).toBe('application:reopen-project'); + expect(first.commandDetail).toEqual({ index: 0, paths: ['/a'] }); - const second = projectsMenu.submenu[1] - expect(second.label).toBe('b, c:\\') - expect(second.command).toBe('application:reopen-project') - expect(second.commandDetail).toEqual({ index: 1, paths: ['/b', 'c:\\'] }) - }) - }) + const second = projectsMenu.submenu[1]; + expect(second.label).toBe('b, c:\\'); + expect(second.command).toBe('application:reopen-project'); + expect(second.commandDetail).toEqual({ index: 1, paths: ['/b', 'c:\\'] }); + }); + }); describe('updateProjects', () => { it('creates correct menu items commands for recent projects', () => { const projects = [ { paths: ['/users/neila'] }, { paths: ['/users/buzza', 'users/michaelc'] } - ] + ]; - const menu = ReopenProjectMenuManager.createProjectsMenu(projects) - expect(menu.label).toBe('File') - expect(menu.submenu.length).toBe(1) + const menu = ReopenProjectMenuManager.createProjectsMenu(projects); + expect(menu.label).toBe('File'); + expect(menu.submenu.length).toBe(1); - const recentMenu = menu.submenu[0] - expect(recentMenu.label).toBe('Reopen Project') - expect(recentMenu.submenu.length).toBe(2) + const recentMenu = menu.submenu[0]; + expect(recentMenu.label).toBe('Reopen Project'); + expect(recentMenu.submenu.length).toBe(2); - const first = recentMenu.submenu[0] - expect(first.label).toBe('/users/neila') - expect(first.command).toBe('application:reopen-project') - expect(first.commandDetail).toEqual({ index: 0, paths: ['/users/neila'] }) + const first = recentMenu.submenu[0]; + expect(first.label).toBe('/users/neila'); + expect(first.command).toBe('application:reopen-project'); + expect(first.commandDetail).toEqual({ + index: 0, + paths: ['/users/neila'] + }); - const second = recentMenu.submenu[1] - expect(second.label).toBe('buzza, michaelc') - expect(second.command).toBe('application:reopen-project') + const second = recentMenu.submenu[1]; + expect(second.label).toBe('buzza, michaelc'); + expect(second.command).toBe('application:reopen-project'); expect(second.commandDetail).toEqual({ index: 1, paths: ['/users/buzza', 'users/michaelc'] - }) - }) - }) + }); + }); + }); describe('createLabel', () => { it('returns the Unix path unchanged if there is only one', () => { const label = ReopenProjectMenuManager.createLabel({ paths: ['/a/b/c/d/e/f'] - }) - expect(label).toBe('/a/b/c/d/e/f') - }) + }); + expect(label).toBe('/a/b/c/d/e/f'); + }); it('returns the Windows path unchanged if there is only one', () => { const label = ReopenProjectMenuManager.createLabel({ paths: ['c:\\missions\\apollo11'] - }) - expect(label).toBe('c:\\missions\\apollo11') - }) + }); + expect(label).toBe('c:\\missions\\apollo11'); + }); it('returns the URL unchanged if there is only one', () => { const label = ReopenProjectMenuManager.createLabel({ paths: ['https://launch.pad/apollo/11'] - }) - expect(label).toBe('https://launch.pad/apollo/11') - }) + }); + expect(label).toBe('https://launch.pad/apollo/11'); + }); it('returns a comma-separated list of base names if there are multiple', () => { const project = { paths: ['/var/one', '/usr/bin/two', '/etc/mission/control/three'] - } - const label = ReopenProjectMenuManager.createLabel(project) - expect(label).toBe('one, two, three') - }) + }; + const label = ReopenProjectMenuManager.createLabel(project); + expect(label).toBe('one, two, three'); + }); describe('betterBaseName', () => { it('returns the standard base name for an absolute Unix path', () => { - const name = ReopenProjectMenuManager.betterBaseName('/one/to/three') - expect(name).toBe('three') - }) + const name = ReopenProjectMenuManager.betterBaseName('/one/to/three'); + expect(name).toBe('three'); + }); it('returns the standard base name for a relative Windows path', () => { if (process.platform === 'win32') { - const name = ReopenProjectMenuManager.betterBaseName('.\\one\\two') - expect(name).toBe('two') + const name = ReopenProjectMenuManager.betterBaseName('.\\one\\two'); + expect(name).toBe('two'); } - }) + }); it('returns the standard base name for an absolute Windows path', () => { if (process.platform === 'win32') { const name = ReopenProjectMenuManager.betterBaseName( 'c:\\missions\\apollo\\11' - ) - expect(name).toBe('11') + ); + expect(name).toBe('11'); } - }) + }); it('returns the drive root for a Windows drive name', () => { - const name = ReopenProjectMenuManager.betterBaseName('d:') - expect(name).toBe('d:\\') - }) + const name = ReopenProjectMenuManager.betterBaseName('d:'); + expect(name).toBe('d:\\'); + }); it('returns the drive root for a Windows drive root', () => { - const name = ReopenProjectMenuManager.betterBaseName('e:\\') - expect(name).toBe('e:\\') - }) + const name = ReopenProjectMenuManager.betterBaseName('e:\\'); + expect(name).toBe('e:\\'); + }); it('returns the final path for a URI', () => { const name = ReopenProjectMenuManager.betterBaseName( 'https://something/else' - ) - expect(name).toBe('else') - }) - }) - }) -}) + ); + expect(name).toBe('else'); + }); + }); + }); +}); diff --git a/spec/selection-spec.js b/spec/selection-spec.js index 5dc6cd199..19094c48a 100644 --- a/spec/selection-spec.js +++ b/spec/selection-spec.js @@ -1,192 +1,192 @@ -const TextEditor = require('../src/text-editor') +const TextEditor = require('../src/text-editor'); describe('Selection', () => { - let buffer, editor, selection + let buffer, editor, selection; beforeEach(() => { - buffer = atom.project.bufferForPathSync('sample.js') - editor = new TextEditor({ buffer, tabLength: 2 }) - selection = editor.getLastSelection() - }) + buffer = atom.project.bufferForPathSync('sample.js'); + editor = new TextEditor({ buffer, tabLength: 2 }); + selection = editor.getLastSelection(); + }); - afterEach(() => buffer.destroy()) + afterEach(() => buffer.destroy()); describe('.deleteSelectedText()', () => { describe('when nothing is selected', () => { it('deletes nothing', () => { - selection.setBufferRange([[0, 3], [0, 3]]) - selection.deleteSelectedText() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - }) - }) + selection.setBufferRange([[0, 3], [0, 3]]); + selection.deleteSelectedText(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + }); + }); describe('when one line is selected', () => { it('deletes selected text and clears the selection', () => { - selection.setBufferRange([[0, 4], [0, 14]]) - selection.deleteSelectedText() - expect(buffer.lineForRow(0)).toBe('var = function () {') + selection.setBufferRange([[0, 4], [0, 14]]); + selection.deleteSelectedText(); + expect(buffer.lineForRow(0)).toBe('var = function () {'); - const endOfLine = buffer.lineForRow(0).length - selection.setBufferRange([[0, 0], [0, endOfLine]]) - selection.deleteSelectedText() - expect(buffer.lineForRow(0)).toBe('') + const endOfLine = buffer.lineForRow(0).length; + selection.setBufferRange([[0, 0], [0, endOfLine]]); + selection.deleteSelectedText(); + expect(buffer.lineForRow(0)).toBe(''); - expect(selection.isEmpty()).toBeTruthy() - }) - }) + expect(selection.isEmpty()).toBeTruthy(); + }); + }); describe('when multiple lines are selected', () => { it('deletes selected text and clears the selection', () => { - selection.setBufferRange([[0, 1], [2, 39]]) - selection.deleteSelectedText() - expect(buffer.lineForRow(0)).toBe('v;') - expect(selection.isEmpty()).toBeTruthy() - }) - }) + selection.setBufferRange([[0, 1], [2, 39]]); + selection.deleteSelectedText(); + expect(buffer.lineForRow(0)).toBe('v;'); + expect(selection.isEmpty()).toBeTruthy(); + }); + }); describe('when the cursor precedes the tail', () => { it('deletes selected text and clears the selection', () => { - selection.cursor.setScreenPosition([0, 13]) - selection.selectToScreenPosition([0, 4]) + selection.cursor.setScreenPosition([0, 13]); + selection.selectToScreenPosition([0, 4]); - selection.delete() - expect(buffer.lineForRow(0)).toBe('var = function () {') - expect(selection.isEmpty()).toBeTruthy() - }) - }) - }) + selection.delete(); + expect(buffer.lineForRow(0)).toBe('var = function () {'); + expect(selection.isEmpty()).toBeTruthy(); + }); + }); + }); describe('.isReversed()', () => { it('returns true if the cursor precedes the tail', () => { - selection.cursor.setScreenPosition([0, 20]) - selection.selectToScreenPosition([0, 10]) - expect(selection.isReversed()).toBeTruthy() + selection.cursor.setScreenPosition([0, 20]); + selection.selectToScreenPosition([0, 10]); + expect(selection.isReversed()).toBeTruthy(); - selection.selectToScreenPosition([0, 25]) - expect(selection.isReversed()).toBeFalsy() - }) - }) + selection.selectToScreenPosition([0, 25]); + expect(selection.isReversed()).toBeFalsy(); + }); + }); describe('.selectLine(row)', () => { describe('when passed a row', () => { it('selects the specified row', () => { - selection.setBufferRange([[2, 4], [3, 4]]) - selection.selectLine(5) - expect(selection.getBufferRange()).toEqual([[5, 0], [6, 0]]) - }) - }) + selection.setBufferRange([[2, 4], [3, 4]]); + selection.selectLine(5); + expect(selection.getBufferRange()).toEqual([[5, 0], [6, 0]]); + }); + }); describe('when not passed a row', () => { it('selects all rows spanned by the selection', () => { - selection.setBufferRange([[2, 4], [3, 4]]) - selection.selectLine() - expect(selection.getBufferRange()).toEqual([[2, 0], [4, 0]]) - }) - }) - }) + selection.setBufferRange([[2, 4], [3, 4]]); + selection.selectLine(); + expect(selection.getBufferRange()).toEqual([[2, 0], [4, 0]]); + }); + }); + }); describe("when the selection's range is moved", () => { it('notifies ::onDidChangeRange observers', () => { - selection.setBufferRange([[2, 0], [2, 10]]) + selection.setBufferRange([[2, 0], [2, 10]]); const changeScreenRangeHandler = jasmine.createSpy( 'changeScreenRangeHandler' - ) - selection.onDidChangeRange(changeScreenRangeHandler) - buffer.insert([2, 5], 'abc') - expect(changeScreenRangeHandler).toHaveBeenCalled() + ); + selection.onDidChangeRange(changeScreenRangeHandler); + buffer.insert([2, 5], 'abc'); + expect(changeScreenRangeHandler).toHaveBeenCalled(); expect( changeScreenRangeHandler.mostRecentCall.args[0] - ).not.toBeUndefined() - }) - }) + ).not.toBeUndefined(); + }); + }); describe("when only the selection's tail is moved (regression)", () => { it('notifies ::onDidChangeRange observers', () => { - selection.setBufferRange([[2, 0], [2, 10]], { reversed: true }) + selection.setBufferRange([[2, 0], [2, 10]], { reversed: true }); const changeScreenRangeHandler = jasmine.createSpy( 'changeScreenRangeHandler' - ) - selection.onDidChangeRange(changeScreenRangeHandler) + ); + selection.onDidChangeRange(changeScreenRangeHandler); - buffer.insert([2, 5], 'abc') - expect(changeScreenRangeHandler).toHaveBeenCalled() + buffer.insert([2, 5], 'abc'); + expect(changeScreenRangeHandler).toHaveBeenCalled(); expect( changeScreenRangeHandler.mostRecentCall.args[0] - ).not.toBeUndefined() - }) - }) + ).not.toBeUndefined(); + }); + }); describe('when the selection is destroyed', () => { it('destroys its marker', () => { - selection.setBufferRange([[2, 0], [2, 10]]) - const { marker } = selection - selection.destroy() - expect(marker.isDestroyed()).toBeTruthy() - }) - }) + selection.setBufferRange([[2, 0], [2, 10]]); + const { marker } = selection; + selection.destroy(); + expect(marker.isDestroyed()).toBeTruthy(); + }); + }); describe('.insertText(text, options)', () => { it('allows pasting white space only lines when autoIndent is enabled', () => { - selection.setBufferRange([[0, 0], [0, 0]]) - selection.insertText(' \n \n\n', { autoIndent: true }) - expect(buffer.lineForRow(0)).toBe(' ') - expect(buffer.lineForRow(1)).toBe(' ') - expect(buffer.lineForRow(2)).toBe('') - }) + selection.setBufferRange([[0, 0], [0, 0]]); + selection.insertText(' \n \n\n', { autoIndent: true }); + expect(buffer.lineForRow(0)).toBe(' '); + expect(buffer.lineForRow(1)).toBe(' '); + expect(buffer.lineForRow(2)).toBe(''); + }); it('auto-indents if only a newline is inserted', () => { - selection.setBufferRange([[2, 0], [3, 0]]) - selection.insertText('\n', { autoIndent: true }) - expect(buffer.lineForRow(2)).toBe(' ') - }) + selection.setBufferRange([[2, 0], [3, 0]]); + selection.insertText('\n', { autoIndent: true }); + expect(buffer.lineForRow(2)).toBe(' '); + }); it('auto-indents if only a carriage return + newline is inserted', () => { - selection.setBufferRange([[2, 0], [3, 0]]) - selection.insertText('\r\n', { autoIndent: true }) - expect(buffer.lineForRow(2)).toBe(' ') - }) + selection.setBufferRange([[2, 0], [3, 0]]); + selection.insertText('\r\n', { autoIndent: true }); + expect(buffer.lineForRow(2)).toBe(' '); + }); it('does not adjust the indent of trailing lines if preserveTrailingLineIndentation is true', () => { - selection.setBufferRange([[5, 0], [5, 0]]) + selection.setBufferRange([[5, 0], [5, 0]]); selection.insertText(' foo\n bar\n', { preserveTrailingLineIndentation: true, indentBasis: 1 - }) - expect(buffer.lineForRow(6)).toBe(' bar') - }) - }) + }); + expect(buffer.lineForRow(6)).toBe(' bar'); + }); + }); describe('.fold()', () => { it('folds the buffer range spanned by the selection', () => { - selection.setBufferRange([[0, 3], [1, 6]]) - selection.fold() + selection.setBufferRange([[0, 3], [1, 6]]); + selection.fold(); - expect(selection.getScreenRange()).toEqual([[0, 4], [0, 4]]) - expect(selection.getBufferRange()).toEqual([[1, 6], [1, 6]]) + expect(selection.getScreenRange()).toEqual([[0, 4], [0, 4]]); + expect(selection.getBufferRange()).toEqual([[1, 6], [1, 6]]); expect(editor.lineTextForScreenRow(0)).toBe( `var${editor.displayLayer.foldCharacter}sort = function(items) {` - ) - expect(editor.isFoldedAtBufferRow(0)).toBe(true) - }) + ); + expect(editor.isFoldedAtBufferRow(0)).toBe(true); + }); it("doesn't create a fold when the selection is empty", () => { - selection.setBufferRange([[0, 3], [0, 3]]) - selection.fold() + selection.setBufferRange([[0, 3], [0, 3]]); + selection.fold(); - expect(selection.getScreenRange()).toEqual([[0, 3], [0, 3]]) - expect(selection.getBufferRange()).toEqual([[0, 3], [0, 3]]) + expect(selection.getScreenRange()).toEqual([[0, 3], [0, 3]]); + expect(selection.getBufferRange()).toEqual([[0, 3], [0, 3]]); expect(editor.lineTextForScreenRow(0)).toBe( 'var quicksort = function () {' - ) - expect(editor.isFoldedAtBufferRow(0)).toBe(false) - }) - }) + ); + expect(editor.isFoldedAtBufferRow(0)).toBe(false); + }); + }); describe('within a read-only editor', () => { beforeEach(() => { - editor.setReadOnly(true) - selection.setBufferRange([[0, 0], [0, 13]]) - }) + editor.setReadOnly(true); + selection.setBufferRange([[0, 0], [0, 13]]); + }); const modifications = [ { @@ -277,22 +277,22 @@ describe('Selection', () => { name: 'indentSelectedRows', op: opts => selection.indentSelectedRows(opts) } - ] + ]; describe('without bypassReadOnly', () => { for (const { name, op } of modifications) { it(`throws an error on ${name}`, () => { - expect(op).toThrow() - }) + expect(op).toThrow(); + }); } - }) + }); describe('with bypassReadOnly', () => { for (const { name, op } of modifications) { it(`permits ${name}`, () => { - op({ bypassReadOnly: true }) - }) + op({ bypassReadOnly: true }); + }); } - }) - }) -}) + }); + }); +}); diff --git a/spec/state-store-spec.js b/spec/state-store-spec.js index 7b206cdb2..6bb3f1117 100644 --- a/spec/state-store-spec.js +++ b/spec/state-store-spec.js @@ -1,39 +1,39 @@ /** @babel */ -const StateStore = require('../src/state-store.js') +const StateStore = require('../src/state-store.js'); describe('StateStore', () => { - let databaseName = `test-database-${Date.now()}` - let version = 1 + let databaseName = `test-database-${Date.now()}`; + let version = 1; it('can save, load, and delete states', () => { - const store = new StateStore(databaseName, version) + const store = new StateStore(databaseName, version); return store .save('key', { foo: 'bar' }) .then(() => store.load('key')) .then(state => { - expect(state).toEqual({ foo: 'bar' }) + expect(state).toEqual({ foo: 'bar' }); }) .then(() => store.delete('key')) .then(() => store.load('key')) .then(value => { - expect(value).toBeNull() + expect(value).toBeNull(); }) .then(() => store.count()) .then(count => { - expect(count).toBe(0) - }) - }) + expect(count).toBe(0); + }); + }); it('resolves with null when a non-existent key is loaded', () => { - const store = new StateStore(databaseName, version) + const store = new StateStore(databaseName, version); return store.load('no-such-key').then(value => { - expect(value).toBeNull() - }) - }) + expect(value).toBeNull(); + }); + }); it('can clear the state object store', () => { - const store = new StateStore(databaseName, version) + const store = new StateStore(databaseName, version); return store .save('key', { foo: 'bar' }) .then(() => store.count()) @@ -41,30 +41,32 @@ describe('StateStore', () => { .then(() => store.clear()) .then(() => store.count()) .then(count => { - expect(count).toBe(0) - }) - }) + expect(count).toBe(0); + }); + }); describe('when there is an error reading from the database', () => { it('rejects the promise returned by load', () => { - const store = new StateStore(databaseName, version) + const store = new StateStore(databaseName, version); - const fakeErrorEvent = { target: { errorCode: 'Something bad happened' } } + const fakeErrorEvent = { + target: { errorCode: 'Something bad happened' } + }; spyOn(IDBObjectStore.prototype, 'get').andCallFake(key => { - let request = {} - process.nextTick(() => request.onerror(fakeErrorEvent)) - return request - }) + let request = {}; + process.nextTick(() => request.onerror(fakeErrorEvent)); + return request; + }); return store .load('nonexistentKey') .then(() => { - throw new Error('Promise should have been rejected') + throw new Error('Promise should have been rejected'); }) .catch(event => { - expect(event).toBe(fakeErrorEvent) - }) - }) - }) -}) + expect(event).toBe(fakeErrorEvent); + }); + }); + }); +}); diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js index 88560e989..29e8fbc95 100644 --- a/spec/style-manager-spec.js +++ b/spec/style-manager-spec.js @@ -1,56 +1,56 @@ -const temp = require('temp').track() -const StyleManager = require('../src/style-manager') +const temp = require('temp').track(); +const StyleManager = require('../src/style-manager'); describe('StyleManager', () => { - let [styleManager, addEvents, removeEvents, updateEvents] = [] + let [styleManager, addEvents, removeEvents, updateEvents] = []; beforeEach(() => { styleManager = new StyleManager({ configDirPath: temp.mkdirSync('atom-config') - }) - addEvents = [] - removeEvents = [] - updateEvents = [] + }); + addEvents = []; + removeEvents = []; + updateEvents = []; styleManager.onDidAddStyleElement(event => { - addEvents.push(event) - }) + addEvents.push(event); + }); styleManager.onDidRemoveStyleElement(event => { - removeEvents.push(event) - }) + removeEvents.push(event); + }); styleManager.onDidUpdateStyleElement(event => { - updateEvents.push(event) - }) - }) + updateEvents.push(event); + }); + }); afterEach(() => { try { - temp.cleanupSync() + temp.cleanupSync(); } catch (e) { // Do nothing } - }) + }); describe('::addStyleSheet(source, params)', () => { it('adds a style sheet based on the given source and returns a disposable allowing it to be removed', () => { - const disposable = styleManager.addStyleSheet('a {color: red}') - expect(addEvents.length).toBe(1) - expect(addEvents[0].textContent).toBe('a {color: red}') - const styleElements = styleManager.getStyleElements() - expect(styleElements.length).toBe(1) - expect(styleElements[0].textContent).toBe('a {color: red}') - disposable.dispose() - expect(removeEvents.length).toBe(1) - expect(removeEvents[0].textContent).toBe('a {color: red}') - expect(styleManager.getStyleElements().length).toBe(0) - }) + const disposable = styleManager.addStyleSheet('a {color: red}'); + expect(addEvents.length).toBe(1); + expect(addEvents[0].textContent).toBe('a {color: red}'); + const styleElements = styleManager.getStyleElements(); + expect(styleElements.length).toBe(1); + expect(styleElements[0].textContent).toBe('a {color: red}'); + disposable.dispose(); + expect(removeEvents.length).toBe(1); + expect(removeEvents[0].textContent).toBe('a {color: red}'); + expect(styleManager.getStyleElements().length).toBe(0); + }); describe('atom-text-editor shadow DOM selectors upgrades', () => { beforeEach(() => { // attach styles element to the DOM to parse CSS rules styleManager.onDidAddStyleElement(styleElement => { - jasmine.attachToDOM(styleElement) - }) - }) + jasmine.attachToDOM(styleElement); + }); + }); it('removes the ::shadow pseudo-element from atom-text-editor selectors', () => { styleManager.addStyleSheet(` @@ -59,7 +59,7 @@ describe('StyleManager', () => { atom-text-editor .class-4 { color: blue } atom-text-editor[data-grammar*="js"]::shadow .class-6 { color: green; } atom-text-editor[mini].is-focused::shadow .class-7 { color: green; } - `) + `); expect( Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map( r => r.selectorText @@ -70,8 +70,8 @@ describe('StyleManager', () => { 'atom-text-editor .class-4', 'atom-text-editor[data-grammar*="js"].editor .class-6', 'atom-text-editor[mini].is-focused.editor .class-7' - ]) - }) + ]); + }); describe('when a selector targets the atom-text-editor shadow DOM', () => { it('prepends "--syntax" to class selectors matching a grammar scope name and not already starting with "syntax--"', () => { @@ -83,7 +83,7 @@ describe('StyleManager', () => { #id-1 { color: blue } `, { context: 'atom-text-editor' } - ) + ); expect( Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map( r => r.selectorText @@ -93,14 +93,14 @@ describe('StyleManager', () => { '.syntax--source > .syntax--js, .syntax--source.syntax--coffee', '.syntax--source', '#id-1' - ]) + ]); styleManager.addStyleSheet(` .source > .js, .source.coffee { color: green } atom-text-editor::shadow .source > .js { color: yellow } atom-text-editor[mini].is-focused::shadow .source > .js { color: gray } atom-text-editor .source > .js { color: red } - `) + `); expect( Array.from(styleManager.getStyleElements()[1].sheet.cssRules).map( r => r.selectorText @@ -110,70 +110,70 @@ describe('StyleManager', () => { 'atom-text-editor.editor .syntax--source > .syntax--js', 'atom-text-editor[mini].is-focused.editor .syntax--source > .syntax--js', 'atom-text-editor .source > .js' - ]) - }) - }) + ]); + }); + }); it('replaces ":host" with "atom-text-editor" only when the context of a style sheet is "atom-text-editor"', () => { styleManager.addStyleSheet( ':host .class-1, :host .class-2 { color: red; }' - ) + ); expect( Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map( r => r.selectorText ) - ).toEqual([':host .class-1, :host .class-2']) + ).toEqual([':host .class-1, :host .class-2']); styleManager.addStyleSheet( ':host .class-1, :host .class-2 { color: red; }', { context: 'atom-text-editor' } - ) + ); expect( Array.from(styleManager.getStyleElements()[1].sheet.cssRules).map( r => r.selectorText ) - ).toEqual(['atom-text-editor .class-1, atom-text-editor .class-2']) - }) + ).toEqual(['atom-text-editor .class-1, atom-text-editor .class-2']); + }); it('does not throw exceptions on rules with no selectors', () => { styleManager.addStyleSheet('@media screen {font-size: 10px}', { context: 'atom-text-editor' - }) - }) - }) + }); + }); + }); describe('when a sourcePath parameter is specified', () => { it('ensures a maximum of one style element for the given source path, updating a previous if it exists', () => { styleManager.addStyleSheet('a {color: red}', { sourcePath: '/foo/bar' - }) - expect(addEvents.length).toBe(1) - expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar') + }); + expect(addEvents.length).toBe(1); + expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar'); const disposable2 = styleManager.addStyleSheet('a {color: blue}', { sourcePath: '/foo/bar' - }) - expect(addEvents.length).toBe(1) - expect(updateEvents.length).toBe(1) - expect(updateEvents[0].getAttribute('source-path')).toBe('/foo/bar') - expect(updateEvents[0].textContent).toBe('a {color: blue}') - disposable2.dispose() + }); + expect(addEvents.length).toBe(1); + expect(updateEvents.length).toBe(1); + expect(updateEvents[0].getAttribute('source-path')).toBe('/foo/bar'); + expect(updateEvents[0].textContent).toBe('a {color: blue}'); + disposable2.dispose(); - addEvents = [] + addEvents = []; styleManager.addStyleSheet('a {color: yellow}', { sourcePath: '/foo/bar' - }) - expect(addEvents.length).toBe(1) - expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar') - expect(addEvents[0].textContent).toBe('a {color: yellow}') - }) - }) + }); + expect(addEvents.length).toBe(1); + expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar'); + expect(addEvents[0].textContent).toBe('a {color: yellow}'); + }); + }); describe('when a priority parameter is specified', () => { it('inserts the style sheet based on the priority', () => { - styleManager.addStyleSheet('a {color: red}', { priority: 1 }) - styleManager.addStyleSheet('a {color: blue}', { priority: 0 }) - styleManager.addStyleSheet('a {color: green}', { priority: 2 }) - styleManager.addStyleSheet('a {color: yellow}', { priority: 1 }) + styleManager.addStyleSheet('a {color: red}', { priority: 1 }); + styleManager.addStyleSheet('a {color: blue}', { priority: 0 }); + styleManager.addStyleSheet('a {color: green}', { priority: 2 }); + styleManager.addStyleSheet('a {color: yellow}', { priority: 1 }); expect( styleManager.getStyleElements().map(elt => elt.textContent) ).toEqual([ @@ -181,8 +181,8 @@ describe('StyleManager', () => { 'a {color: red}', 'a {color: yellow}', 'a {color: green}' - ]) - }) - }) - }) -}) + ]); + }); + }); + }); +}); diff --git a/spec/syntax-scope-map-spec.js b/spec/syntax-scope-map-spec.js index 4b30ef934..5da417aa5 100644 --- a/spec/syntax-scope-map-spec.js +++ b/spec/syntax-scope-map-spec.js @@ -1,4 +1,4 @@ -const SyntaxScopeMap = require('../src/syntax-scope-map') +const SyntaxScopeMap = require('../src/syntax-scope-map'); describe('SyntaxScopeMap', () => { it('can match immediate child selectors', () => { @@ -6,15 +6,15 @@ describe('SyntaxScopeMap', () => { 'a > b > c': 'x', 'b > c': 'y', c: 'z' - }) + }); - expect(map.get(['a', 'b', 'c'], [0, 0, 0])).toBe('x') - expect(map.get(['d', 'b', 'c'], [0, 0, 0])).toBe('y') - expect(map.get(['d', 'e', 'c'], [0, 0, 0])).toBe('z') - expect(map.get(['e', 'c'], [0, 0, 0])).toBe('z') - expect(map.get(['c'], [0, 0, 0])).toBe('z') - expect(map.get(['d'], [0, 0, 0])).toBe(undefined) - }) + expect(map.get(['a', 'b', 'c'], [0, 0, 0])).toBe('x'); + expect(map.get(['d', 'b', 'c'], [0, 0, 0])).toBe('y'); + expect(map.get(['d', 'e', 'c'], [0, 0, 0])).toBe('z'); + expect(map.get(['e', 'c'], [0, 0, 0])).toBe('z'); + expect(map.get(['c'], [0, 0, 0])).toBe('z'); + expect(map.get(['d'], [0, 0, 0])).toBe(undefined); + }); it('can match :nth-child pseudo-selectors on leaves', () => { const map = new SyntaxScopeMap({ @@ -22,28 +22,28 @@ describe('SyntaxScopeMap', () => { 'a > b:nth-child(1)': 'x', b: 'y', 'b:nth-child(2)': 'z' - }) + }); - expect(map.get(['a', 'b'], [0, 0])).toBe('w') - expect(map.get(['a', 'b'], [0, 1])).toBe('x') - expect(map.get(['a', 'b'], [0, 2])).toBe('w') - expect(map.get(['b'], [0])).toBe('y') - expect(map.get(['b'], [1])).toBe('y') - expect(map.get(['b'], [2])).toBe('z') - }) + expect(map.get(['a', 'b'], [0, 0])).toBe('w'); + expect(map.get(['a', 'b'], [0, 1])).toBe('x'); + expect(map.get(['a', 'b'], [0, 2])).toBe('w'); + expect(map.get(['b'], [0])).toBe('y'); + expect(map.get(['b'], [1])).toBe('y'); + expect(map.get(['b'], [2])).toBe('z'); + }); it('can match :nth-child pseudo-selectors on interior nodes', () => { const map = new SyntaxScopeMap({ 'b:nth-child(1) > c': 'w', 'a > b > c': 'x', 'a > b:nth-child(2) > c': 'y' - }) + }); - expect(map.get(['b', 'c'], [0, 0])).toBe(undefined) - expect(map.get(['b', 'c'], [1, 0])).toBe('w') - expect(map.get(['a', 'b', 'c'], [1, 0, 0])).toBe('x') - expect(map.get(['a', 'b', 'c'], [1, 2, 0])).toBe('y') - }) + expect(map.get(['b', 'c'], [0, 0])).toBe(undefined); + expect(map.get(['b', 'c'], [1, 0])).toBe('w'); + expect(map.get(['a', 'b', 'c'], [1, 0, 0])).toBe('x'); + expect(map.get(['a', 'b', 'c'], [1, 2, 0])).toBe('y'); + }); it('allows anonymous tokens to be referred to by their string value', () => { const map = new SyntaxScopeMap({ @@ -51,14 +51,14 @@ describe('SyntaxScopeMap', () => { 'a > "b"': 'x', 'a > "b":nth-child(1)': 'y', '"\\""': 'z' - }) + }); - expect(map.get(['b'], [0], true)).toBe(undefined) - expect(map.get(['b'], [0], false)).toBe('w') - expect(map.get(['a', 'b'], [0, 0], false)).toBe('x') - expect(map.get(['a', 'b'], [0, 1], false)).toBe('y') - expect(map.get(['a', '"'], [0, 1], false)).toBe('z') - }) + expect(map.get(['b'], [0], true)).toBe(undefined); + expect(map.get(['b'], [0], false)).toBe('w'); + expect(map.get(['a', 'b'], [0, 0], false)).toBe('x'); + expect(map.get(['a', 'b'], [0, 1], false)).toBe('y'); + expect(map.get(['a', '"'], [0, 1], false)).toBe('z'); + }); it('supports the wildcard selector', () => { const map = new SyntaxScopeMap({ @@ -66,24 +66,24 @@ describe('SyntaxScopeMap', () => { 'a > *': 'x', 'a > *:nth-child(1)': 'y', 'a > *:nth-child(1) > b': 'z' - }) + }); - expect(map.get(['b'], [0])).toBe('w') - expect(map.get(['c'], [0])).toBe('w') - expect(map.get(['a', 'b'], [0, 0])).toBe('x') - expect(map.get(['a', 'b'], [0, 1])).toBe('y') - expect(map.get(['a', 'c'], [0, 1])).toBe('y') - expect(map.get(['a', 'c', 'b'], [0, 1, 1])).toBe('z') - expect(map.get(['a', 'c', 'b'], [0, 2, 1])).toBe('w') - }) + expect(map.get(['b'], [0])).toBe('w'); + expect(map.get(['c'], [0])).toBe('w'); + expect(map.get(['a', 'b'], [0, 0])).toBe('x'); + expect(map.get(['a', 'b'], [0, 1])).toBe('y'); + expect(map.get(['a', 'c'], [0, 1])).toBe('y'); + expect(map.get(['a', 'c', 'b'], [0, 1, 1])).toBe('z'); + expect(map.get(['a', 'c', 'b'], [0, 2, 1])).toBe('w'); + }); it('distinguishes between an anonymous * token and the wildcard selector', () => { const map = new SyntaxScopeMap({ '"*"': 'x', 'a > "b"': 'y' - }) + }); - expect(map.get(['b'], [0], false)).toBe(undefined) - expect(map.get(['*'], [0], false)).toBe('x') - }) -}) + expect(map.get(['b'], [0], false)).toBe(undefined); + expect(map.get(['*'], [0], false)).toBe('x'); + }); +}); diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 5529f8730..1f2752cd0 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1,81 +1,81 @@ -const { conditionPromise } = require('./async-spec-helpers') +const { conditionPromise } = require('./async-spec-helpers'); -const Random = require('../script/node_modules/random-seed') -const { getRandomBufferRange, buildRandomLines } = require('./helpers/random') -const TextEditorComponent = require('../src/text-editor-component') -const TextEditorElement = require('../src/text-editor-element') -const TextEditor = require('../src/text-editor') -const TextBuffer = require('text-buffer') -const { Point } = TextBuffer -const fs = require('fs') -const path = require('path') -const Grim = require('grim') -const electron = require('electron') -const clipboard = electron.clipboard +const Random = require('../script/node_modules/random-seed'); +const { getRandomBufferRange, buildRandomLines } = require('./helpers/random'); +const TextEditorComponent = require('../src/text-editor-component'); +const TextEditorElement = require('../src/text-editor-element'); +const TextEditor = require('../src/text-editor'); +const TextBuffer = require('text-buffer'); +const { Point } = TextBuffer; +const fs = require('fs'); +const path = require('path'); +const Grim = require('grim'); +const electron = require('electron'); +const clipboard = electron.clipboard; const SAMPLE_TEXT = fs.readFileSync( path.join(__dirname, 'fixtures', 'sample.js'), 'utf8' -) +); document.registerElement('text-editor-component-test-element', { prototype: Object.create(HTMLElement.prototype, { attachedCallback: { - value: function () { - this.didAttach() + value: function() { + this.didAttach(); } } }) -}) +}); -const editors = [] -let verticalScrollbarWidth, horizontalScrollbarHeight +const editors = []; +let verticalScrollbarWidth, horizontalScrollbarHeight; describe('TextEditorComponent', () => { beforeEach(() => { - jasmine.useRealClock() + jasmine.useRealClock(); // Force scrollbars to be visible regardless of local system configuration - const scrollbarStyle = document.createElement('style') + const scrollbarStyle = document.createElement('style'); scrollbarStyle.textContent = - 'atom-text-editor ::-webkit-scrollbar { -webkit-appearance: none }' - jasmine.attachToDOM(scrollbarStyle) + 'atom-text-editor ::-webkit-scrollbar { -webkit-appearance: none }'; + jasmine.attachToDOM(scrollbarStyle); if (verticalScrollbarWidth == null) { const { component, element } = buildComponent({ text: 'abcdefgh\n'.repeat(10), width: 30, height: 30 - }) - verticalScrollbarWidth = getVerticalScrollbarWidth(component) - horizontalScrollbarHeight = getHorizontalScrollbarHeight(component) - element.remove() + }); + verticalScrollbarWidth = getVerticalScrollbarWidth(component); + horizontalScrollbarHeight = getHorizontalScrollbarHeight(component); + element.remove(); } - }) + }); afterEach(() => { for (const editor of editors) { - editor.destroy() + editor.destroy(); } - editors.length = 0 - }) + editors.length = 0; + }); describe('rendering', () => { it('renders lines and line numbers for the visible region', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) + }); - expect(queryOnScreenLineNumberElements(element).length).toBe(13) - expect(queryOnScreenLineElements(element).length).toBe(13) + expect(queryOnScreenLineNumberElements(element).length).toBe(13); + expect(queryOnScreenLineElements(element).length).toBe(13); - element.style.height = 4 * component.measurements.lineHeight + 'px' - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(9) - expect(queryOnScreenLineElements(element).length).toBe(9) + element.style.height = 4 * component.measurements.lineHeight + 'px'; + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(9); + expect(queryOnScreenLineElements(element).length).toBe(9); - await setScrollTop(component, 5 * component.getLineHeight()) + await setScrollTop(component, 5 * component.getLineHeight()); // After scrolling down beyond > 3 rows, the order of line numbers and lines // in the DOM is a bit weird because the first tile is recycled to the bottom @@ -84,12 +84,12 @@ describe('TextEditorComponent', () => { queryOnScreenLineNumberElements(element).map(element => element.textContent.trim() ) - ).toEqual(['10', '11', '12', '4', '5', '6', '7', '8', '9']) + ).toEqual(['10', '11', '12', '4', '5', '6', '7', '8', '9']); expect( queryOnScreenLineElements(element).map( element => element.dataset.screenRow ) - ).toEqual(['9', '10', '11', '3', '4', '5', '6', '7', '8']) + ).toEqual(['9', '10', '11', '3', '4', '5', '6', '7', '8']); expect( queryOnScreenLineElements(element).map(element => element.textContent) ).toEqual([ @@ -102,19 +102,19 @@ describe('TextEditorComponent', () => { editor.lineTextForScreenRow(6), editor.lineTextForScreenRow(7), editor.lineTextForScreenRow(8) - ]) + ]); - await setScrollTop(component, 2.5 * component.getLineHeight()) + await setScrollTop(component, 2.5 * component.getLineHeight()); expect( queryOnScreenLineNumberElements(element).map(element => element.textContent.trim() ) - ).toEqual(['1', '2', '3', '4', '5', '6', '7', '8', '9']) + ).toEqual(['1', '2', '3', '4', '5', '6', '7', '8', '9']); expect( queryOnScreenLineElements(element).map( element => element.dataset.screenRow ) - ).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']) + ).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']); expect( queryOnScreenLineElements(element).map(element => element.textContent) ).toEqual([ @@ -127,44 +127,44 @@ describe('TextEditorComponent', () => { editor.lineTextForScreenRow(6), editor.lineTextForScreenRow(7), editor.lineTextForScreenRow(8) - ]) - }) + ]); + }); it('bases the width of the lines div on the width of the longest initially-visible screen line', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 2, height: 20, width: 100 - }) + }); { - expect(editor.getApproximateLongestScreenRow()).toBe(3) + expect(editor.getApproximateLongestScreenRow()).toBe(3); const expectedWidth = Math.ceil( component.pixelPositionForScreenPosition(Point(3, Infinity)).left + component.getBaseCharacterWidth() - ) + ); expect(element.querySelector('.lines').style.width).toBe( expectedWidth + 'px' - ) + ); } { // Get the next update promise synchronously here to ensure we don't // miss the update while polling the condition. - const nextUpdatePromise = component.getNextUpdatePromise() + const nextUpdatePromise = component.getNextUpdatePromise(); await conditionPromise( () => editor.getApproximateLongestScreenRow() === 6 - ) - await nextUpdatePromise + ); + await nextUpdatePromise; // Capture the width of the lines before requesting the width of // longest line, because making that request forces a DOM update - const actualWidth = element.querySelector('.lines').style.width + const actualWidth = element.querySelector('.lines').style.width; const expectedWidth = Math.ceil( component.pixelPositionForScreenPosition(Point(6, Infinity)).left + component.getBaseCharacterWidth() - ) - expect(actualWidth).toBe(expectedWidth + 'px') + ); + expect(actualWidth).toBe(expectedWidth + 'px'); } // eslint-disable-next-line no-lone-blocks @@ -172,703 +172,704 @@ describe('TextEditorComponent', () => { // Make sure we do not throw an error if a synchronous update is // triggered before measuring the longest line from a // previously-scheduled update. - editor.getBuffer().insert(Point(12, Infinity), 'x'.repeat(100)) - expect(editor.getLongestScreenRow()).toBe(12) + editor.getBuffer().insert(Point(12, Infinity), 'x'.repeat(100)); + expect(editor.getLongestScreenRow()).toBe(12); TextEditorComponent.getScheduler().readDocument(() => { // This will happen before the measurement phase of the update // triggered above. - component.pixelPositionForScreenPosition(Point(11, Infinity)) - }) + component.pixelPositionForScreenPosition(Point(11, Infinity)); + }); - await component.getNextUpdatePromise() + await component.getNextUpdatePromise(); } - }) + }); it('re-renders lines when their height changes', async () => { const { component, element } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) - element.style.height = 4 * component.measurements.lineHeight + 'px' - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(9) - expect(queryOnScreenLineElements(element).length).toBe(9) + }); + element.style.height = 4 * component.measurements.lineHeight + 'px'; + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(9); + expect(queryOnScreenLineElements(element).length).toBe(9); - element.style.lineHeight = '2.0' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(6) - expect(queryOnScreenLineElements(element).length).toBe(6) + element.style.lineHeight = '2.0'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(6); + expect(queryOnScreenLineElements(element).length).toBe(6); - element.style.lineHeight = '0.7' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(12) - expect(queryOnScreenLineElements(element).length).toBe(12) + element.style.lineHeight = '0.7'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(12); + expect(queryOnScreenLineElements(element).length).toBe(12); - element.style.lineHeight = '0.05' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(13) - expect(queryOnScreenLineElements(element).length).toBe(13) + element.style.lineHeight = '0.05'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(13); + expect(queryOnScreenLineElements(element).length).toBe(13); - element.style.lineHeight = '0' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(13) - expect(queryOnScreenLineElements(element).length).toBe(13) + element.style.lineHeight = '0'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(13); + expect(queryOnScreenLineElements(element).length).toBe(13); - element.style.lineHeight = '1' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(queryOnScreenLineNumberElements(element).length).toBe(9) - expect(queryOnScreenLineElements(element).length).toBe(9) - }) + element.style.lineHeight = '1'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(queryOnScreenLineNumberElements(element).length).toBe(9); + expect(queryOnScreenLineElements(element).length).toBe(9); + }); it('makes the content at least as tall as the scroll container client height', async () => { const { component, editor } = buildComponent({ text: 'a'.repeat(100), width: 50, height: 100 - }) + }); expect(component.refs.content.offsetHeight).toBe( 100 - getHorizontalScrollbarHeight(component) - ) + ); - editor.setText('a\n'.repeat(30)) - await component.getNextUpdatePromise() - expect(component.refs.content.offsetHeight).toBeGreaterThan(100) + editor.setText('a\n'.repeat(30)); + await component.getNextUpdatePromise(); + expect(component.refs.content.offsetHeight).toBeGreaterThan(100); expect(component.refs.content.offsetHeight).toBe( component.getContentHeight() - ) - }) + ); + }); it('honors the scrollPastEnd option by adding empty space equivalent to the clientHeight to the end of the content area', async () => { const { component, editor } = buildComponent({ autoHeight: false, autoWidth: false - }) + }); - await editor.update({ scrollPastEnd: true }) - await setEditorHeightInLines(component, 6) + await editor.update({ scrollPastEnd: true }); + await setEditorHeightInLines(component, 6); // scroll to end - await setScrollTop(component, Infinity) + await setScrollTop(component, Infinity); expect(component.getFirstVisibleRow()).toBe( editor.getScreenLineCount() - 3 - ) + ); - editor.update({ scrollPastEnd: false }) - await component.getNextUpdatePromise() // wait for scrollable content resize + editor.update({ scrollPastEnd: false }); + await component.getNextUpdatePromise(); // wait for scrollable content resize expect(component.getFirstVisibleRow()).toBe( editor.getScreenLineCount() - 6 - ) + ); // Always allows at least 3 lines worth of overscroll if the editor is short - await setEditorHeightInLines(component, 2) - await editor.update({ scrollPastEnd: true }) - await setScrollTop(component, Infinity) + await setEditorHeightInLines(component, 2); + await editor.update({ scrollPastEnd: true }); + await setScrollTop(component, Infinity); expect(component.getFirstVisibleRow()).toBe( editor.getScreenLineCount() + 1 - ) - }) + ); + }); it('does not fire onDidChangeScrollTop listeners when assigning the same maximal value and the content height has fractional pixels (regression)', async () => { const { component, element, editor } = buildComponent({ autoHeight: false, autoWidth: false - }) - await setEditorHeightInLines(component, 3) + }); + await setEditorHeightInLines(component, 3); // Force a fractional content height with a block decoration - const item = document.createElement('div') - item.style.height = '10.6px' + const item = document.createElement('div'); + item.style.height = '10.6px'; editor.decorateMarker(editor.markBufferPosition([0, 0]), { type: 'block', item - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); - component.setScrollTop(Infinity) + component.setScrollTop(Infinity); element.onDidChangeScrollTop(newScrollTop => { - throw new Error('Scroll top should not have changed') - }) - component.setScrollTop(component.getScrollTop()) - }) + throw new Error('Scroll top should not have changed'); + }); + component.setScrollTop(component.getScrollTop()); + }); it('gives the line number tiles an explicit width and height so their layout can be strictly contained', async () => { - const { component, editor } = buildComponent({ rowsPerTile: 3 }) + const { component, editor } = buildComponent({ rowsPerTile: 3 }); const lineNumberGutterElement = - component.refs.gutterContainer.refs.lineNumberGutter.element + component.refs.gutterContainer.refs.lineNumberGutter.element; expect(lineNumberGutterElement.offsetHeight).toBe( component.getScrollHeight() - ) + ); for (const child of lineNumberGutterElement.children) { - expect(child.offsetWidth).toBe(lineNumberGutterElement.offsetWidth) + expect(child.offsetWidth).toBe(lineNumberGutterElement.offsetWidth); if (!child.classList.contains('line-number')) { for (const lineNumberElement of child.children) { expect(lineNumberElement.offsetWidth).toBe( lineNumberGutterElement.offsetWidth - ) + ); } } } - editor.setText('x\n'.repeat(99)) - await component.getNextUpdatePromise() + editor.setText('x\n'.repeat(99)); + await component.getNextUpdatePromise(); expect(lineNumberGutterElement.offsetHeight).toBe( component.getScrollHeight() - ) + ); for (const child of lineNumberGutterElement.children) { - expect(child.offsetWidth).toBe(lineNumberGutterElement.offsetWidth) + expect(child.offsetWidth).toBe(lineNumberGutterElement.offsetWidth); if (!child.classList.contains('line-number')) { for (const lineNumberElement of child.children) { expect(lineNumberElement.offsetWidth).toBe( lineNumberGutterElement.offsetWidth - ) + ); } } } - }) + }); it('keeps the number of tiles stable when the visible line count changes during vertical scrolling', async () => { const { component } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) - await setEditorHeightInLines(component, 5.5) - expect(component.refs.lineTiles.children.length).toBe(3 + 2) // account for cursors and highlights containers + }); + await setEditorHeightInLines(component, 5.5); + expect(component.refs.lineTiles.children.length).toBe(3 + 2); // account for cursors and highlights containers - await setScrollTop(component, 0.5 * component.getLineHeight()) - expect(component.refs.lineTiles.children.length).toBe(3 + 2) // account for cursors and highlights containers + await setScrollTop(component, 0.5 * component.getLineHeight()); + expect(component.refs.lineTiles.children.length).toBe(3 + 2); // account for cursors and highlights containers - await setScrollTop(component, 1 * component.getLineHeight()) - expect(component.refs.lineTiles.children.length).toBe(3 + 2) // account for cursors and highlights containers - }) + await setScrollTop(component, 1 * component.getLineHeight()); + expect(component.refs.lineTiles.children.length).toBe(3 + 2); // account for cursors and highlights containers + }); it('recycles tiles on resize', async () => { const { component } = buildComponent({ rowsPerTile: 2, autoHeight: false - }) - await setEditorHeightInLines(component, 7) - await setScrollTop(component, 3.5 * component.getLineHeight()) - const lineNode = lineNodeForScreenRow(component, 7) - await setEditorHeightInLines(component, 4) - expect(lineNodeForScreenRow(component, 7)).toBe(lineNode) - }) + }); + await setEditorHeightInLines(component, 7); + await setScrollTop(component, 3.5 * component.getLineHeight()); + const lineNode = lineNodeForScreenRow(component, 7); + await setEditorHeightInLines(component, 4); + expect(lineNodeForScreenRow(component, 7)).toBe(lineNode); + }); it("updates lines numbers when a row's foldability changes (regression)", async () => { - const { component, editor } = buildComponent({ text: 'abc\n' }) - editor.setCursorBufferPosition([1, 0]) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent({ text: 'abc\n' }); + editor.setCursorBufferPosition([1, 0]); + await component.getNextUpdatePromise(); expect( lineNumberNodeForScreenRow(component, 0).querySelector('.foldable') - ).toBeNull() + ).toBeNull(); - editor.insertText(' def') - await component.getNextUpdatePromise() + editor.insertText(' def'); + await component.getNextUpdatePromise(); expect( lineNumberNodeForScreenRow(component, 0).querySelector('.foldable') - ).toBeDefined() + ).toBeDefined(); - editor.undo() - await component.getNextUpdatePromise() + editor.undo(); + await component.getNextUpdatePromise(); expect( lineNumberNodeForScreenRow(component, 0).querySelector('.foldable') - ).toBeNull() - }) + ).toBeNull(); + }); it('shows the foldable icon on the last screen row of a buffer row that can be folded', async () => { const { component } = buildComponent({ text: 'abc\n de\nfghijklm\n no', softWrapped: true - }) - await setEditorWidthInCharacters(component, 5) + }); + await setEditorWidthInCharacters(component, 5); expect( lineNumberNodeForScreenRow(component, 0).classList.contains('foldable') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('foldable') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 2).classList.contains('foldable') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 3).classList.contains('foldable') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 4).classList.contains('foldable') - ).toBe(false) - }) + ).toBe(false); + }); it('renders dummy vertical and horizontal scrollbars when content overflows', async () => { const { component, editor } = buildComponent({ height: 100, width: 100 - }) - const verticalScrollbar = component.refs.verticalScrollbar.element - const horizontalScrollbar = component.refs.horizontalScrollbar.element - expect(verticalScrollbar.scrollHeight).toBe(component.getContentHeight()) - expect(horizontalScrollbar.scrollWidth).toBe(component.getContentWidth()) - expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(0) - expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(0) + }); + const verticalScrollbar = component.refs.verticalScrollbar.element; + const horizontalScrollbar = component.refs.horizontalScrollbar.element; + expect(verticalScrollbar.scrollHeight).toBe(component.getContentHeight()); + expect(horizontalScrollbar.scrollWidth).toBe(component.getContentWidth()); + expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(0); + expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(0); expect(verticalScrollbar.style.bottom).toBe( getVerticalScrollbarWidth(component) + 'px' - ) - expect(verticalScrollbar.style.visibility).toBe('') + ); + expect(verticalScrollbar.style.visibility).toBe(''); expect(horizontalScrollbar.style.right).toBe( getHorizontalScrollbarHeight(component) + 'px' - ) - expect(horizontalScrollbar.style.visibility).toBe('') - expect(component.refs.scrollbarCorner).toBeDefined() + ); + expect(horizontalScrollbar.style.visibility).toBe(''); + expect(component.refs.scrollbarCorner).toBeDefined(); - setScrollTop(component, 100) - await setScrollLeft(component, 100) - expect(verticalScrollbar.scrollTop).toBe(100) - expect(horizontalScrollbar.scrollLeft).toBe(100) + setScrollTop(component, 100); + await setScrollLeft(component, 100); + expect(verticalScrollbar.scrollTop).toBe(100); + expect(horizontalScrollbar.scrollLeft).toBe(100); - verticalScrollbar.scrollTop = 120 - horizontalScrollbar.scrollLeft = 120 - await component.getNextUpdatePromise() - expect(component.getScrollTop()).toBe(120) - expect(component.getScrollLeft()).toBe(120) + verticalScrollbar.scrollTop = 120; + horizontalScrollbar.scrollLeft = 120; + await component.getNextUpdatePromise(); + expect(component.getScrollTop()).toBe(120); + expect(component.getScrollLeft()).toBe(120); - editor.setText('a\n'.repeat(15)) - await component.getNextUpdatePromise() - expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(0) - expect(getHorizontalScrollbarHeight(component)).toBe(0) - expect(verticalScrollbar.style.visibility).toBe('') - expect(horizontalScrollbar.style.visibility).toBe('hidden') + editor.setText('a\n'.repeat(15)); + await component.getNextUpdatePromise(); + expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(0); + expect(getHorizontalScrollbarHeight(component)).toBe(0); + expect(verticalScrollbar.style.visibility).toBe(''); + expect(horizontalScrollbar.style.visibility).toBe('hidden'); - editor.setText('a'.repeat(100)) - await component.getNextUpdatePromise() - expect(getVerticalScrollbarWidth(component)).toBe(0) - expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(0) - expect(verticalScrollbar.style.visibility).toBe('hidden') - expect(horizontalScrollbar.style.visibility).toBe('') + editor.setText('a'.repeat(100)); + await component.getNextUpdatePromise(); + expect(getVerticalScrollbarWidth(component)).toBe(0); + expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(0); + expect(verticalScrollbar.style.visibility).toBe('hidden'); + expect(horizontalScrollbar.style.visibility).toBe(''); - editor.setText('') - await component.getNextUpdatePromise() - expect(getVerticalScrollbarWidth(component)).toBe(0) - expect(getHorizontalScrollbarHeight(component)).toBe(0) - expect(verticalScrollbar.style.visibility).toBe('hidden') - expect(horizontalScrollbar.style.visibility).toBe('hidden') - }) + editor.setText(''); + await component.getNextUpdatePromise(); + expect(getVerticalScrollbarWidth(component)).toBe(0); + expect(getHorizontalScrollbarHeight(component)).toBe(0); + expect(verticalScrollbar.style.visibility).toBe('hidden'); + expect(horizontalScrollbar.style.visibility).toBe('hidden'); + }); describe('when scrollbar styles change or the editor element is detached and then reattached', () => { it('updates the bottom/right of dummy scrollbars and client height/width measurements', async () => { const { component, element, editor } = buildComponent({ height: 100, width: 100 - }) - expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(10) - expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(10) - setScrollTop(component, 20) - setScrollLeft(component, 10) - await component.getNextUpdatePromise() + }); + expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(10); + expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(10); + setScrollTop(component, 20); + setScrollLeft(component, 10); + await component.getNextUpdatePromise(); // Updating scrollbar styles. - const style = document.createElement('style') - style.textContent = '::-webkit-scrollbar { height: 10px; width: 10px; }' - jasmine.attachToDOM(style) - TextEditor.didUpdateScrollbarStyles() - await component.getNextUpdatePromise() + const style = document.createElement('style'); + style.textContent = + '::-webkit-scrollbar { height: 10px; width: 10px; }'; + jasmine.attachToDOM(style); + TextEditor.didUpdateScrollbarStyles(); + await component.getNextUpdatePromise(); - expect(getHorizontalScrollbarHeight(component)).toBe(10) - expect(getVerticalScrollbarWidth(component)).toBe(10) + expect(getHorizontalScrollbarHeight(component)).toBe(10); + expect(getVerticalScrollbarWidth(component)).toBe(10); expect(component.refs.horizontalScrollbar.element.style.right).toBe( '10px' - ) + ); expect(component.refs.verticalScrollbar.element.style.bottom).toBe( '10px' - ) - expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(10) - expect(component.refs.verticalScrollbar.element.scrollTop).toBe(20) - expect(component.getScrollContainerClientHeight()).toBe(100 - 10) + ); + expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(10); + expect(component.refs.verticalScrollbar.element.scrollTop).toBe(20); + expect(component.getScrollContainerClientHeight()).toBe(100 - 10); expect(component.getScrollContainerClientWidth()).toBe( 100 - component.getGutterContainerWidth() - 10 - ) + ); // Detaching and re-attaching the editor element. - element.remove() - jasmine.attachToDOM(element) + element.remove(); + jasmine.attachToDOM(element); - expect(getHorizontalScrollbarHeight(component)).toBe(10) - expect(getVerticalScrollbarWidth(component)).toBe(10) + expect(getHorizontalScrollbarHeight(component)).toBe(10); + expect(getVerticalScrollbarWidth(component)).toBe(10); expect(component.refs.horizontalScrollbar.element.style.right).toBe( '10px' - ) + ); expect(component.refs.verticalScrollbar.element.style.bottom).toBe( '10px' - ) - expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(10) - expect(component.refs.verticalScrollbar.element.scrollTop).toBe(20) - expect(component.getScrollContainerClientHeight()).toBe(100 - 10) + ); + expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(10); + expect(component.refs.verticalScrollbar.element.scrollTop).toBe(20); + expect(component.getScrollContainerClientHeight()).toBe(100 - 10); expect(component.getScrollContainerClientWidth()).toBe( 100 - component.getGutterContainerWidth() - 10 - ) + ); // Ensure we don't throw an error trying to remeasure non-existent scrollbars for mini editors. - await editor.update({ mini: true }) - TextEditor.didUpdateScrollbarStyles() - component.scheduleUpdate() - await component.getNextUpdatePromise() - }) - }) + await editor.update({ mini: true }); + TextEditor.didUpdateScrollbarStyles(); + component.scheduleUpdate(); + await component.getNextUpdatePromise(); + }); + }); it('renders cursors within the visible row range', async () => { const { component, element, editor } = buildComponent({ height: 40, rowsPerTile: 2 - }) - await setScrollTop(component, 100) + }); + await setScrollTop(component, 100); - expect(component.getRenderedStartRow()).toBe(4) - expect(component.getRenderedEndRow()).toBe(10) + expect(component.getRenderedStartRow()).toBe(4); + expect(component.getRenderedEndRow()).toBe(10); - editor.setCursorScreenPosition([0, 0], { autoscroll: false }) // out of view - editor.addCursorAtScreenPosition([2, 2], { autoscroll: false }) // out of view - editor.addCursorAtScreenPosition([4, 0], { autoscroll: false }) // line start - editor.addCursorAtScreenPosition([4, 4], { autoscroll: false }) // at token boundary - editor.addCursorAtScreenPosition([4, 6], { autoscroll: false }) // within token - editor.addCursorAtScreenPosition([5, Infinity], { autoscroll: false }) // line end - editor.addCursorAtScreenPosition([10, 2], { autoscroll: false }) // out of view - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([0, 0], { autoscroll: false }); // out of view + editor.addCursorAtScreenPosition([2, 2], { autoscroll: false }); // out of view + editor.addCursorAtScreenPosition([4, 0], { autoscroll: false }); // line start + editor.addCursorAtScreenPosition([4, 4], { autoscroll: false }); // at token boundary + editor.addCursorAtScreenPosition([4, 6], { autoscroll: false }); // within token + editor.addCursorAtScreenPosition([5, Infinity], { autoscroll: false }); // line end + editor.addCursorAtScreenPosition([10, 2], { autoscroll: false }); // out of view + await component.getNextUpdatePromise(); - let cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(4) - verifyCursorPosition(component, cursorNodes[0], 4, 0) - verifyCursorPosition(component, cursorNodes[1], 4, 4) - verifyCursorPosition(component, cursorNodes[2], 4, 6) - verifyCursorPosition(component, cursorNodes[3], 5, 30) + let cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(4); + verifyCursorPosition(component, cursorNodes[0], 4, 0); + verifyCursorPosition(component, cursorNodes[1], 4, 4); + verifyCursorPosition(component, cursorNodes[2], 4, 6); + verifyCursorPosition(component, cursorNodes[3], 5, 30); - editor.setCursorScreenPosition([8, 11], { autoscroll: false }) - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([8, 11], { autoscroll: false }); + await component.getNextUpdatePromise(); - cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(1) - verifyCursorPosition(component, cursorNodes[0], 8, 11) + cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(1); + verifyCursorPosition(component, cursorNodes[0], 8, 11); - editor.setCursorScreenPosition([0, 0], { autoscroll: false }) - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([0, 0], { autoscroll: false }); + await component.getNextUpdatePromise(); - cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(0) + cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(0); - editor.setSelectedScreenRange([[8, 0], [12, 0]], { autoscroll: false }) - await component.getNextUpdatePromise() - cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(0) - }) + editor.setSelectedScreenRange([[8, 0], [12, 0]], { autoscroll: false }); + await component.getNextUpdatePromise(); + cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(0); + }); it('hides cursors with non-empty selections when showCursorOnSelection is false', async () => { - const { component, element, editor } = buildComponent() - editor.setSelectedScreenRanges([[[0, 0], [0, 3]], [[1, 0], [1, 0]]]) - await component.getNextUpdatePromise() + const { component, element, editor } = buildComponent(); + editor.setSelectedScreenRanges([[[0, 0], [0, 3]], [[1, 0], [1, 0]]]); + await component.getNextUpdatePromise(); { - const cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(2) - verifyCursorPosition(component, cursorNodes[0], 0, 3) - verifyCursorPosition(component, cursorNodes[1], 1, 0) + const cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(2); + verifyCursorPosition(component, cursorNodes[0], 0, 3); + verifyCursorPosition(component, cursorNodes[1], 1, 0); } - editor.update({ showCursorOnSelection: false }) - await component.getNextUpdatePromise() + editor.update({ showCursorOnSelection: false }); + await component.getNextUpdatePromise(); { - const cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(1) - verifyCursorPosition(component, cursorNodes[0], 1, 0) + const cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(1); + verifyCursorPosition(component, cursorNodes[0], 1, 0); } - editor.setSelectedScreenRanges([[[0, 0], [0, 3]], [[1, 0], [1, 4]]]) - await component.getNextUpdatePromise() + editor.setSelectedScreenRanges([[[0, 0], [0, 3]], [[1, 0], [1, 4]]]); + await component.getNextUpdatePromise(); { - const cursorNodes = Array.from(element.querySelectorAll('.cursor')) - expect(cursorNodes.length).toBe(0) + const cursorNodes = Array.from(element.querySelectorAll('.cursor')); + expect(cursorNodes.length).toBe(0); } - }) + }); it('blinks cursors when the editor is focused and the cursors are not moving', async () => { - assertDocumentFocused() - const { component, element, editor } = buildComponent() - component.props.cursorBlinkPeriod = 40 - component.props.cursorBlinkResumeDelay = 40 - editor.addCursorAtScreenPosition([1, 0]) + assertDocumentFocused(); + const { component, element, editor } = buildComponent(); + component.props.cursorBlinkPeriod = 40; + component.props.cursorBlinkResumeDelay = 40; + editor.addCursorAtScreenPosition([1, 0]); - element.focus() - await component.getNextUpdatePromise() - const [cursor1, cursor2] = element.querySelectorAll('.cursor') + element.focus(); + await component.getNextUpdatePromise(); + const [cursor1, cursor2] = element.querySelectorAll('.cursor'); await conditionPromise( () => getComputedStyle(cursor1).opacity === '1' && getComputedStyle(cursor2).opacity === '1' - ) + ); await conditionPromise( () => getComputedStyle(cursor1).opacity === '0' && getComputedStyle(cursor2).opacity === '0' - ) + ); await conditionPromise( () => getComputedStyle(cursor1).opacity === '1' && getComputedStyle(cursor2).opacity === '1' - ) + ); - editor.moveRight() - await component.getNextUpdatePromise() + editor.moveRight(); + await component.getNextUpdatePromise(); - expect(getComputedStyle(cursor1).opacity).toBe('1') - expect(getComputedStyle(cursor2).opacity).toBe('1') - }) + expect(getComputedStyle(cursor1).opacity).toBe('1'); + expect(getComputedStyle(cursor2).opacity).toBe('1'); + }); it('gives cursors at the end of lines the width of an "x" character', async () => { - const { component, element, editor } = buildComponent() - editor.setText('abcde') - await setEditorWidthInCharacters(component, 5.5) + const { component, element, editor } = buildComponent(); + editor.setText('abcde'); + await setEditorWidthInCharacters(component, 5.5); - editor.setCursorScreenPosition([0, Infinity]) - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([0, Infinity]); + await component.getNextUpdatePromise(); expect(element.querySelector('.cursor').offsetWidth).toBe( Math.round(component.getBaseCharacterWidth()) - ) + ); // Clip cursor width when soft-wrap is on and the cursor is at the end of // the line. This prevents the parent tile from disabling sub-pixel // anti-aliasing. For some reason, adding overflow: hidden to the cursor // container doesn't solve this issue so we're adding this workaround instead. - editor.setSoftWrapped(true) - await component.getNextUpdatePromise() + editor.setSoftWrapped(true); + await component.getNextUpdatePromise(); expect(element.querySelector('.cursor').offsetWidth).toBeLessThan( Math.round(component.getBaseCharacterWidth()) - ) - }) + ); + }); it('positions and sizes cursors correctly when they are located next to a fold marker', async () => { - const { component, element, editor } = buildComponent() - editor.foldBufferRange([[0, 3], [0, 6]]) + const { component, element, editor } = buildComponent(); + editor.foldBufferRange([[0, 3], [0, 6]]); - editor.setCursorScreenPosition([0, 3]) - await component.getNextUpdatePromise() - verifyCursorPosition(component, element.querySelector('.cursor'), 0, 3) + editor.setCursorScreenPosition([0, 3]); + await component.getNextUpdatePromise(); + verifyCursorPosition(component, element.querySelector('.cursor'), 0, 3); - editor.setCursorScreenPosition([0, 4]) - await component.getNextUpdatePromise() - verifyCursorPosition(component, element.querySelector('.cursor'), 0, 4) - }) + editor.setCursorScreenPosition([0, 4]); + await component.getNextUpdatePromise(); + verifyCursorPosition(component, element.querySelector('.cursor'), 0, 4); + }); it('positions cursors and placeholder text correctly when the lines container has a margin and/or is padded', async () => { const { component, element, editor } = buildComponent({ placeholderText: 'testing' - }) + }); - component.refs.lineTiles.style.marginLeft = '10px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() + component.refs.lineTiles.style.marginLeft = '10px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); - editor.setCursorBufferPosition([0, 3]) - await component.getNextUpdatePromise() - verifyCursorPosition(component, element.querySelector('.cursor'), 0, 3) + editor.setCursorBufferPosition([0, 3]); + await component.getNextUpdatePromise(); + verifyCursorPosition(component, element.querySelector('.cursor'), 0, 3); - editor.setCursorScreenPosition([1, 0]) - await component.getNextUpdatePromise() - verifyCursorPosition(component, element.querySelector('.cursor'), 1, 0) + editor.setCursorScreenPosition([1, 0]); + await component.getNextUpdatePromise(); + verifyCursorPosition(component, element.querySelector('.cursor'), 1, 0); - component.refs.lineTiles.style.paddingTop = '5px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - verifyCursorPosition(component, element.querySelector('.cursor'), 1, 0) + component.refs.lineTiles.style.paddingTop = '5px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + verifyCursorPosition(component, element.querySelector('.cursor'), 1, 0); - editor.setCursorScreenPosition([2, 2]) - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - verifyCursorPosition(component, element.querySelector('.cursor'), 2, 2) + editor.setCursorScreenPosition([2, 2]); + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + verifyCursorPosition(component, element.querySelector('.cursor'), 2, 2); - editor.setText('') - await component.getNextUpdatePromise() + editor.setText(''); + await component.getNextUpdatePromise(); const placeholderTextLeft = element .querySelector('.placeholder-text') - .getBoundingClientRect().left - const linesLeft = component.refs.lineTiles.getBoundingClientRect().left - expect(placeholderTextLeft).toBe(linesLeft) - }) + .getBoundingClientRect().left; + const linesLeft = component.refs.lineTiles.getBoundingClientRect().left; + expect(placeholderTextLeft).toBe(linesLeft); + }); it('places the hidden input element at the location of the last cursor if it is visible', async () => { const { component, editor } = buildComponent({ height: 60, width: 120, rowsPerTile: 2 - }) - const { hiddenInput } = component.refs.cursorsAndInput.refs - setScrollTop(component, 100) - await setScrollLeft(component, 40) + }); + const { hiddenInput } = component.refs.cursorsAndInput.refs; + setScrollTop(component, 100); + await setScrollLeft(component, 40); - expect(component.getRenderedStartRow()).toBe(4) - expect(component.getRenderedEndRow()).toBe(10) + expect(component.getRenderedStartRow()).toBe(4); + expect(component.getRenderedEndRow()).toBe(10); // When out of view, the hidden input is positioned at 0, 0 - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - expect(hiddenInput.offsetTop).toBe(0) - expect(hiddenInput.offsetLeft).toBe(0) + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + expect(hiddenInput.offsetTop).toBe(0); + expect(hiddenInput.offsetLeft).toBe(0); // Otherwise it is positioned at the last cursor position - editor.addCursorAtScreenPosition([7, 4]) - await component.getNextUpdatePromise() + editor.addCursorAtScreenPosition([7, 4]); + await component.getNextUpdatePromise(); expect(hiddenInput.getBoundingClientRect().top).toBe( clientTopForLine(component, 7) - ) + ); expect(Math.round(hiddenInput.getBoundingClientRect().left)).toBe( clientLeftForCharacter(component, 7, 4) - ) - }) + ); + }); it('soft wraps lines based on the content width when soft wrap is enabled', async () => { - let baseCharacterWidth, gutterContainerWidth + let baseCharacterWidth, gutterContainerWidth; { - const { component, editor } = buildComponent() - baseCharacterWidth = component.getBaseCharacterWidth() - gutterContainerWidth = component.getGutterContainerWidth() - editor.destroy() + const { component, editor } = buildComponent(); + baseCharacterWidth = component.getBaseCharacterWidth(); + gutterContainerWidth = component.getGutterContainerWidth(); + editor.destroy(); } const { component, element, editor } = buildComponent({ width: gutterContainerWidth + baseCharacterWidth * 55, attach: false - }) - editor.setSoftWrapped(true) - jasmine.attachToDOM(element) + }); + editor.setSoftWrapped(true); + jasmine.attachToDOM(element); - expect(getEditorWidthInBaseCharacters(component)).toBe(55) + expect(getEditorWidthInBaseCharacters(component)).toBe(55); expect(lineNodeForScreenRow(component, 3).textContent).toBe( ' var pivot = items.shift(), current, left = [], ' - ) + ); expect(lineNodeForScreenRow(component, 4).textContent).toBe( ' right = [];' - ) + ); - await setEditorWidthInCharacters(component, 45) + await setEditorWidthInCharacters(component, 45); expect(lineNodeForScreenRow(component, 3).textContent).toBe( ' var pivot = items.shift(), current, left ' - ) + ); expect(lineNodeForScreenRow(component, 4).textContent).toBe( ' = [], right = [];' - ) + ); - const { scrollContainer } = component.refs - expect(scrollContainer.clientWidth).toBe(scrollContainer.scrollWidth) - }) + const { scrollContainer } = component.refs; + expect(scrollContainer.clientWidth).toBe(scrollContainer.scrollWidth); + }); it('correctly forces the display layer to index visible rows when resizing (regression)', async () => { - const text = 'a'.repeat(30) + '\n' + 'b'.repeat(1000) + const text = 'a'.repeat(30) + '\n' + 'b'.repeat(1000); const { component, element, editor } = buildComponent({ height: 300, width: 800, attach: false, text - }) - editor.setSoftWrapped(true) - jasmine.attachToDOM(element) + }); + editor.setSoftWrapped(true); + jasmine.attachToDOM(element); - element.style.width = 200 + 'px' - await component.getNextUpdatePromise() - expect(queryOnScreenLineElements(element).length).toBe(24) - }) + element.style.width = 200 + 'px'; + await component.getNextUpdatePromise(); + expect(queryOnScreenLineElements(element).length).toBe(24); + }); it('decorates the line numbers of folded lines', async () => { - const { component, editor } = buildComponent() - editor.foldBufferRow(1) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent(); + editor.foldBufferRow(1); + await component.getNextUpdatePromise(); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('folded') - ).toBe(true) - }) + ).toBe(true); + }); it('makes lines at least as wide as the scrollContainer', async () => { - const { component, element, editor } = buildComponent() - const { scrollContainer } = component.refs - editor.setText('a') - await component.getNextUpdatePromise() + const { component, element, editor } = buildComponent(); + const { scrollContainer } = component.refs; + editor.setText('a'); + await component.getNextUpdatePromise(); expect(element.querySelector('.line').offsetWidth).toBe( scrollContainer.offsetWidth - verticalScrollbarWidth - ) - }) + ); + }); it('resizes based on the content when the autoHeight and/or autoWidth options are true', async () => { const { component, element, editor } = buildComponent({ autoHeight: true, autoWidth: true - }) - const editorPadding = 3 - element.style.padding = editorPadding + 'px' - const initialWidth = element.offsetWidth - const initialHeight = element.offsetHeight + }); + const editorPadding = 3; + element.style.padding = editorPadding + 'px'; + const initialWidth = element.offsetWidth; + const initialHeight = element.offsetHeight; expect(initialWidth).toBe( component.getGutterContainerWidth() + component.getContentWidth() + verticalScrollbarWidth + 2 * editorPadding - ) + ); expect(initialHeight).toBe( component.getContentHeight() + horizontalScrollbarHeight + 2 * editorPadding - ) + ); // When autoWidth is enabled, width adjusts to content - editor.setCursorScreenPosition([6, Infinity]) - editor.insertText('x'.repeat(50)) - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([6, Infinity]); + editor.insertText('x'.repeat(50)); + await component.getNextUpdatePromise(); expect(element.offsetWidth).toBe( component.getGutterContainerWidth() + component.getContentWidth() + verticalScrollbarWidth + 2 * editorPadding - ) - expect(element.offsetWidth).toBeGreaterThan(initialWidth) + ); + expect(element.offsetWidth).toBeGreaterThan(initialWidth); // When autoHeight is enabled, height adjusts to content - editor.insertText('\n'.repeat(5)) - await component.getNextUpdatePromise() + editor.insertText('\n'.repeat(5)); + await component.getNextUpdatePromise(); expect(element.offsetHeight).toBe( component.getContentHeight() + horizontalScrollbarHeight + 2 * editorPadding - ) - expect(element.offsetHeight).toBeGreaterThan(initialHeight) - }) + ); + expect(element.offsetHeight).toBeGreaterThan(initialHeight); + }); it('does not render the line number gutter at all if the isLineNumberGutterVisible parameter is false', () => { const { element } = buildComponent({ lineNumberGutterVisible: false - }) - expect(element.querySelector('.line-number')).toBe(null) - }) + }); + expect(element.querySelector('.line-number')).toBe(null); + }); it('does not render the line numbers but still renders the line number gutter if showLineNumbers is false', async () => { - function checkScrollContainerLeft (component) { - const { scrollContainer, gutterContainer } = component.refs + function checkScrollContainerLeft(component) { + const { scrollContainer, gutterContainer } = component.refs; expect(scrollContainer.getBoundingClientRect().left).toBe( Math.round(gutterContainer.element.getBoundingClientRect().right) - ) + ); } const { component, element, editor } = buildComponent({ showLineNumbers: false - }) + }); expect( Array.from(element.querySelectorAll('.line-number')).every( e => e.textContent === '' ) - ).toBe(true) - checkScrollContainerLeft(component) + ).toBe(true); + checkScrollContainerLeft(component); - await editor.update({ showLineNumbers: true }) + await editor.update({ showLineNumbers: true }); expect( Array.from(element.querySelectorAll('.line-number')).map( e => e.textContent @@ -888,69 +889,69 @@ describe('TextEditorComponent', () => { '11', '12', '13' - ]) - checkScrollContainerLeft(component) + ]); + checkScrollContainerLeft(component); - await editor.update({ showLineNumbers: false }) + await editor.update({ showLineNumbers: false }); expect( Array.from(element.querySelectorAll('.line-number')).every( e => e.textContent === '' ) - ).toBe(true) - checkScrollContainerLeft(component) - }) + ).toBe(true); + checkScrollContainerLeft(component); + }); it('supports the placeholderText parameter', () => { - const placeholderText = 'Placeholder Test' - const { element } = buildComponent({ placeholderText, text: '' }) - expect(element.textContent).toContain(placeholderText) - }) + const placeholderText = 'Placeholder Test'; + const { element } = buildComponent({ placeholderText, text: '' }); + expect(element.textContent).toContain(placeholderText); + }); it('adds the data-grammar attribute and updates it when the grammar changes', async () => { - await atom.packages.activatePackage('language-javascript') + await atom.packages.activatePackage('language-javascript'); - const { editor, element, component } = buildComponent() - expect(element.dataset.grammar).toBe('text plain null-grammar') + const { editor, element, component } = buildComponent(); + expect(element.dataset.grammar).toBe('text plain null-grammar'); - atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.js') - await component.getNextUpdatePromise() - expect(element.dataset.grammar).toBe('source js') - }) + atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.js'); + await component.getNextUpdatePromise(); + expect(element.dataset.grammar).toBe('source js'); + }); it('adds the data-encoding attribute and updates it when the encoding changes', async () => { - const { editor, element, component } = buildComponent() - expect(element.dataset.encoding).toBe('utf8') + const { editor, element, component } = buildComponent(); + expect(element.dataset.encoding).toBe('utf8'); - editor.setEncoding('ascii') - await component.getNextUpdatePromise() - expect(element.dataset.encoding).toBe('ascii') - }) + editor.setEncoding('ascii'); + await component.getNextUpdatePromise(); + expect(element.dataset.encoding).toBe('ascii'); + }); it('adds the has-selection class when the editor has a non-empty selection', async () => { - const { editor, element, component } = buildComponent() - expect(element.classList.contains('has-selection')).toBe(false) + const { editor, element, component } = buildComponent(); + expect(element.classList.contains('has-selection')).toBe(false); - editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 10]]]) - await component.getNextUpdatePromise() - expect(element.classList.contains('has-selection')).toBe(true) + editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 10]]]); + await component.getNextUpdatePromise(); + expect(element.classList.contains('has-selection')).toBe(true); - editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 0]]]) - await component.getNextUpdatePromise() - expect(element.classList.contains('has-selection')).toBe(false) - }) + editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 0]]]); + await component.getNextUpdatePromise(); + expect(element.classList.contains('has-selection')).toBe(false); + }); it('assigns buffer-row and screen-row to each line number as data fields', async () => { - const { editor, element, component } = buildComponent() - editor.setSoftWrapped(true) - await component.getNextUpdatePromise() - await setEditorWidthInCharacters(component, 40) + const { editor, element, component } = buildComponent(); + editor.setSoftWrapped(true); + await component.getNextUpdatePromise(); + await setEditorWidthInCharacters(component, 40); { const bufferRows = queryOnScreenLineNumberElements(element).map( e => e.dataset.bufferRow - ) + ); const screenRows = queryOnScreenLineNumberElements(element).map( e => e.dataset.screenRow - ) + ); expect(bufferRows).toEqual([ '0', '1', @@ -971,7 +972,7 @@ describe('TextEditorComponent', () => { '11', '11', '12' - ]) + ]); expect(screenRows).toEqual([ '0', '1', @@ -992,18 +993,18 @@ describe('TextEditorComponent', () => { '16', '17', '18' - ]) + ]); } - editor.getBuffer().insert([2, 0], '\n') - await component.getNextUpdatePromise() + editor.getBuffer().insert([2, 0], '\n'); + await component.getNextUpdatePromise(); { const bufferRows = queryOnScreenLineNumberElements(element).map( e => e.dataset.bufferRow - ) + ); const screenRows = queryOnScreenLineNumberElements(element).map( e => e.dataset.screenRow - ) + ); expect(bufferRows).toEqual([ '0', '1', @@ -1025,7 +1026,7 @@ describe('TextEditorComponent', () => { '12', '12', '13' - ]) + ]); expect(screenRows).toEqual([ '0', '1', @@ -1047,1479 +1048,1483 @@ describe('TextEditorComponent', () => { '17', '18', '19' - ]) + ]); } - }) + }); it('does not blow away class names added to the element by packages when changing the class name', async () => { - assertDocumentFocused() - const { component, element } = buildComponent() - element.classList.add('a', 'b') - expect(element.className).toBe('editor a b') - element.focus() - await component.getNextUpdatePromise() - expect(element.className).toBe('editor a b is-focused') - document.body.focus() - await component.getNextUpdatePromise() - expect(element.className).toBe('editor a b') - }) + assertDocumentFocused(); + const { component, element } = buildComponent(); + element.classList.add('a', 'b'); + expect(element.className).toBe('editor a b'); + element.focus(); + await component.getNextUpdatePromise(); + expect(element.className).toBe('editor a b is-focused'); + document.body.focus(); + await component.getNextUpdatePromise(); + expect(element.className).toBe('editor a b'); + }); it('does not blow away class names managed by the component when packages change the element class name', async () => { - assertDocumentFocused() - const { component, element } = buildComponent({ mini: true }) - element.classList.add('a', 'b') - element.focus() - await component.getNextUpdatePromise() - expect(element.className).toBe('editor mini a b is-focused') - element.className = 'a c d' - await component.getNextUpdatePromise() - expect(element.className).toBe('a c d editor is-focused mini') - }) + assertDocumentFocused(); + const { component, element } = buildComponent({ mini: true }); + element.classList.add('a', 'b'); + element.focus(); + await component.getNextUpdatePromise(); + expect(element.className).toBe('editor mini a b is-focused'); + element.className = 'a c d'; + await component.getNextUpdatePromise(); + expect(element.className).toBe('a c d editor is-focused mini'); + }); it('ignores resize events when the editor is hidden', async () => { const { component, element } = buildComponent({ autoHeight: false - }) - element.style.height = 5 * component.getLineHeight() + 'px' - await component.getNextUpdatePromise() - const originalClientContainerHeight = component.getClientContainerHeight() - const originalGutterContainerWidth = component.getGutterContainerWidth() - const originalLineNumberGutterWidth = component.getLineNumberGutterWidth() - expect(originalClientContainerHeight).toBeGreaterThan(0) - expect(originalGutterContainerWidth).toBeGreaterThan(0) - expect(originalLineNumberGutterWidth).toBeGreaterThan(0) + }); + element.style.height = 5 * component.getLineHeight() + 'px'; + await component.getNextUpdatePromise(); + const originalClientContainerHeight = component.getClientContainerHeight(); + const originalGutterContainerWidth = component.getGutterContainerWidth(); + const originalLineNumberGutterWidth = component.getLineNumberGutterWidth(); + expect(originalClientContainerHeight).toBeGreaterThan(0); + expect(originalGutterContainerWidth).toBeGreaterThan(0); + expect(originalLineNumberGutterWidth).toBeGreaterThan(0); - element.style.display = 'none' + element.style.display = 'none'; // In production, resize events are triggered before the intersection // observer detects the editor's visibility has changed. In tests, we are // unable to reproduce this scenario and so we simulate them. - expect(component.visible).toBe(true) - component.didResize() - component.didResizeGutterContainer() + expect(component.visible).toBe(true); + component.didResize(); + component.didResizeGutterContainer(); expect(component.getClientContainerHeight()).toBe( originalClientContainerHeight - ) + ); expect(component.getGutterContainerWidth()).toBe( originalGutterContainerWidth - ) + ); expect(component.getLineNumberGutterWidth()).toBe( originalLineNumberGutterWidth - ) + ); // Ensure measurements stay the same after receiving the intersection // observer events. - await conditionPromise(() => !component.visible) + await conditionPromise(() => !component.visible); expect(component.getClientContainerHeight()).toBe( originalClientContainerHeight - ) + ); expect(component.getGutterContainerWidth()).toBe( originalGutterContainerWidth - ) + ); expect(component.getLineNumberGutterWidth()).toBe( originalLineNumberGutterWidth - ) - }) + ); + }); describe('randomized tests', () => { - let originalTimeout + let originalTimeout; beforeEach(() => { - originalTimeout = jasmine.getEnv().defaultTimeoutInterval - jasmine.getEnv().defaultTimeoutInterval = 60 * 1000 - }) + originalTimeout = jasmine.getEnv().defaultTimeoutInterval; + jasmine.getEnv().defaultTimeoutInterval = 60 * 1000; + }); afterEach(() => { - jasmine.getEnv().defaultTimeoutInterval = originalTimeout - }) + jasmine.getEnv().defaultTimeoutInterval = originalTimeout; + }); it('renders the visible rows correctly after randomly mutating the editor', async () => { - const initialSeed = Date.now() + const initialSeed = Date.now(); for (var i = 0; i < 20; i++) { - let seed = initialSeed + i + let seed = initialSeed + i; // seed = 1520247533732 - const failureMessage = 'Randomized test failed with seed: ' + seed - const random = Random(seed) + const failureMessage = 'Randomized test failed with seed: ' + seed; + const random = Random(seed); - const rowsPerTile = random.intBetween(1, 6) + const rowsPerTile = random.intBetween(1, 6); const { component, element, editor } = buildComponent({ rowsPerTile, autoHeight: false - }) - editor.setSoftWrapped(Boolean(random(2))) - await setEditorWidthInCharacters(component, random(20)) - await setEditorHeightInLines(component, random(10)) + }); + editor.setSoftWrapped(Boolean(random(2))); + await setEditorWidthInCharacters(component, random(20)); + await setEditorHeightInLines(component, random(10)); - element.style.fontSize = random(20) + 'px' - element.style.lineHeight = random.floatBetween(0.1, 2.0) - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() + element.style.fontSize = random(20) + 'px'; + element.style.lineHeight = random.floatBetween(0.1, 2.0); + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); - element.focus() + element.focus(); for (var j = 0; j < 5; j++) { - const k = random(100) - const range = getRandomBufferRange(random, editor.buffer) + const k = random(100); + const range = getRandomBufferRange(random, editor.buffer); if (k < 10) { - editor.setSoftWrapped(!editor.isSoftWrapped()) + editor.setSoftWrapped(!editor.isSoftWrapped()); } else if (k < 15) { - if (random(2)) setEditorWidthInCharacters(component, random(20)) - if (random(2)) setEditorHeightInLines(component, random(10)) + if (random(2)) setEditorWidthInCharacters(component, random(20)); + if (random(2)) setEditorHeightInLines(component, random(10)); } else if (k < 40) { - editor.setSelectedBufferRange(range) - editor.backspace() + editor.setSelectedBufferRange(range); + editor.backspace(); } else if (k < 80) { - const linesToInsert = buildRandomLines(random, 5) - editor.setCursorBufferPosition(range.start) - editor.insertText(linesToInsert) + const linesToInsert = buildRandomLines(random, 5); + editor.setCursorBufferPosition(range.start); + editor.insertText(linesToInsert); } else if (k < 90) { if (random(2)) { - editor.foldBufferRange(range) + editor.foldBufferRange(range); } else { - editor.destroyFoldsIntersectingBufferRange(range) + editor.destroyFoldsIntersectingBufferRange(range); } } else if (k < 95) { - editor.setSelectedBufferRange(range) + editor.setSelectedBufferRange(range); } else { if (random(2)) { - component.setScrollTop(random(component.getScrollHeight())) + component.setScrollTop(random(component.getScrollHeight())); } if (random(2)) { - component.setScrollLeft(random(component.getScrollWidth())) + component.setScrollLeft(random(component.getScrollWidth())); } } - component.scheduleUpdate() - await component.getNextUpdatePromise() + component.scheduleUpdate(); + await component.getNextUpdatePromise(); const renderedLines = queryOnScreenLineElements(element).sort( (a, b) => a.dataset.screenRow - b.dataset.screenRow - ) + ); const renderedLineNumbers = queryOnScreenLineNumberElements( element - ).sort((a, b) => a.dataset.screenRow - b.dataset.screenRow) - const renderedStartRow = component.getRenderedStartRow() + ).sort((a, b) => a.dataset.screenRow - b.dataset.screenRow); + const renderedStartRow = component.getRenderedStartRow(); const expectedLines = editor.displayLayer.getScreenLines( renderedStartRow, component.getRenderedEndRow() - ) + ); expect(renderedLines.length).toBe( expectedLines.length, failureMessage - ) + ); expect(renderedLineNumbers.length).toBe( expectedLines.length, failureMessage - ) + ); for (let k = 0; k < renderedLines.length; k++) { - const expectedLine = expectedLines[k] - const expectedText = expectedLine.lineText || ' ' + const expectedLine = expectedLines[k]; + const expectedText = expectedLine.lineText || ' '; - const renderedLine = renderedLines[k] - const renderedLineNumber = renderedLineNumbers[k] - let renderedText = renderedLine.textContent + const renderedLine = renderedLines[k]; + const renderedLineNumber = renderedLineNumbers[k]; + let renderedText = renderedLine.textContent; // We append zero width NBSPs after folds at the end of the // line in order to support measurement. if (expectedText.endsWith(editor.displayLayer.foldCharacter)) { renderedText = renderedText.substring( 0, renderedText.length - 1 - ) + ); } - expect(renderedText).toBe(expectedText, failureMessage) + expect(renderedText).toBe(expectedText, failureMessage); expect(parseInt(renderedLine.dataset.screenRow)).toBe( renderedStartRow + k, failureMessage - ) + ); expect(parseInt(renderedLineNumber.dataset.screenRow)).toBe( renderedStartRow + k, failureMessage - ) + ); } } - element.remove() - editor.destroy() + element.remove(); + editor.destroy(); } - }) - }) - }) + }); + }); + }); describe('mini editors', () => { it('adds the mini attribute and class even when the element is not attached', () => { { - const { element } = buildComponent({ mini: true }) - expect(element.hasAttribute('mini')).toBe(true) - expect(element.classList.contains('mini')).toBe(true) + const { element } = buildComponent({ mini: true }); + expect(element.hasAttribute('mini')).toBe(true); + expect(element.classList.contains('mini')).toBe(true); } { const { element } = buildComponent({ mini: true, attach: false - }) - expect(element.hasAttribute('mini')).toBe(true) - expect(element.classList.contains('mini')).toBe(true) + }); + expect(element.hasAttribute('mini')).toBe(true); + expect(element.classList.contains('mini')).toBe(true); } - }) + }); it('does not render the gutter container', () => { - const { component, element } = buildComponent({ mini: true }) - expect(component.refs.gutterContainer).toBeUndefined() - expect(element.querySelector('gutter-container')).toBeNull() - }) + const { component, element } = buildComponent({ mini: true }); + expect(component.refs.gutterContainer).toBeUndefined(); + expect(element.querySelector('gutter-container')).toBeNull(); + }); it('does not render line decorations for the cursor line', async () => { - const { component, element, editor } = buildComponent({ mini: true }) + const { component, element, editor } = buildComponent({ mini: true }); expect( element.querySelector('.line').classList.contains('cursor-line') - ).toBe(false) + ).toBe(false); - editor.update({ mini: false }) - await component.getNextUpdatePromise() + editor.update({ mini: false }); + await component.getNextUpdatePromise(); expect( element.querySelector('.line').classList.contains('cursor-line') - ).toBe(true) + ).toBe(true); - editor.update({ mini: true }) - await component.getNextUpdatePromise() + editor.update({ mini: true }); + await component.getNextUpdatePromise(); expect( element.querySelector('.line').classList.contains('cursor-line') - ).toBe(false) - }) + ).toBe(false); + }); it('does not render scrollbars', async () => { const { component, editor } = buildComponent({ mini: true, autoHeight: false - }) - await setEditorWidthInCharacters(component, 10) + }); + await setEditorWidthInCharacters(component, 10); - editor.setText('x'.repeat(20) + 'y'.repeat(20)) - await component.getNextUpdatePromise() + editor.setText('x'.repeat(20) + 'y'.repeat(20)); + await component.getNextUpdatePromise(); - expect(component.canScrollVertically()).toBe(false) - expect(component.canScrollHorizontally()).toBe(false) - expect(component.refs.horizontalScrollbar).toBeUndefined() - expect(component.refs.verticalScrollbar).toBeUndefined() - }) - }) + expect(component.canScrollVertically()).toBe(false); + expect(component.canScrollHorizontally()).toBe(false); + expect(component.refs.horizontalScrollbar).toBeUndefined(); + expect(component.refs.verticalScrollbar).toBeUndefined(); + }); + }); describe('focus', () => { beforeEach(() => { - assertDocumentFocused() - }) + assertDocumentFocused(); + }); it('focuses the hidden input element and adds the is-focused class when focused', async () => { - const { component, element } = buildComponent() - const { hiddenInput } = component.refs.cursorsAndInput.refs + const { component, element } = buildComponent(); + const { hiddenInput } = component.refs.cursorsAndInput.refs; - expect(document.activeElement).not.toBe(hiddenInput) - element.focus() - expect(document.activeElement).toBe(hiddenInput) - await component.getNextUpdatePromise() - expect(element.classList.contains('is-focused')).toBe(true) + expect(document.activeElement).not.toBe(hiddenInput); + element.focus(); + expect(document.activeElement).toBe(hiddenInput); + await component.getNextUpdatePromise(); + expect(element.classList.contains('is-focused')).toBe(true); - element.focus() // focusing back to the element does not blur - expect(document.activeElement).toBe(hiddenInput) - expect(element.classList.contains('is-focused')).toBe(true) + element.focus(); // focusing back to the element does not blur + expect(document.activeElement).toBe(hiddenInput); + expect(element.classList.contains('is-focused')).toBe(true); - document.body.focus() - expect(document.activeElement).not.toBe(hiddenInput) - await component.getNextUpdatePromise() - expect(element.classList.contains('is-focused')).toBe(false) - }) + document.body.focus(); + expect(document.activeElement).not.toBe(hiddenInput); + await component.getNextUpdatePromise(); + expect(element.classList.contains('is-focused')).toBe(false); + }); it('updates the component when the hidden input is focused directly', async () => { - const { component, element } = buildComponent() - const { hiddenInput } = component.refs.cursorsAndInput.refs - expect(element.classList.contains('is-focused')).toBe(false) - expect(document.activeElement).not.toBe(hiddenInput) + const { component, element } = buildComponent(); + const { hiddenInput } = component.refs.cursorsAndInput.refs; + expect(element.classList.contains('is-focused')).toBe(false); + expect(document.activeElement).not.toBe(hiddenInput); - hiddenInput.focus() - await component.getNextUpdatePromise() - expect(element.classList.contains('is-focused')).toBe(true) - }) + hiddenInput.focus(); + await component.getNextUpdatePromise(); + expect(element.classList.contains('is-focused')).toBe(true); + }); it('gracefully handles a focus event that occurs prior to the attachedCallback of the element', () => { - const { component, element } = buildComponent({ attach: false }) + const { component, element } = buildComponent({ attach: false }); const parent = document.createElement( 'text-editor-component-test-element' - ) - parent.appendChild(element) - parent.didAttach = () => element.focus() - jasmine.attachToDOM(parent) + ); + parent.appendChild(element); + parent.didAttach = () => element.focus(); + jasmine.attachToDOM(parent); expect(document.activeElement).toBe( component.refs.cursorsAndInput.refs.hiddenInput - ) - }) + ); + }); it('gracefully handles a focus event that occurs prior to detecting the element has become visible', async () => { - const { component, element } = buildComponent({ attach: false }) - element.style.display = 'none' - jasmine.attachToDOM(element) - element.style.display = 'block' - element.focus() - await component.getNextUpdatePromise() + const { component, element } = buildComponent({ attach: false }); + element.style.display = 'none'; + jasmine.attachToDOM(element); + element.style.display = 'block'; + element.focus(); + await component.getNextUpdatePromise(); expect(document.activeElement).toBe( component.refs.cursorsAndInput.refs.hiddenInput - ) - }) + ); + }); it('emits blur events only when focus shifts to something other than the editor itself or its hidden input', () => { - const { element } = buildComponent() + const { element } = buildComponent(); - let blurEventCount = 0 - element.addEventListener('blur', () => blurEventCount++) + let blurEventCount = 0; + element.addEventListener('blur', () => blurEventCount++); - element.focus() - expect(blurEventCount).toBe(0) - element.focus() - expect(blurEventCount).toBe(0) - document.body.focus() - expect(blurEventCount).toBe(1) - }) - }) + element.focus(); + expect(blurEventCount).toBe(0); + element.focus(); + expect(blurEventCount).toBe(0); + document.body.focus(); + expect(blurEventCount).toBe(1); + }); + }); describe('autoscroll', () => { it('automatically scrolls vertically when the requested range is within the vertical scroll margin of the top or bottom', async () => { const { component, editor } = buildComponent({ height: 120 + horizontalScrollbarHeight - }) - expect(component.getLastVisibleRow()).toBe(7) + }); + expect(component.getLastVisibleRow()).toBe(7); - editor.scrollToScreenRange([[4, 0], [6, 0]]) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[4, 0], [6, 0]]); + await component.getNextUpdatePromise(); expect(component.getScrollBottom()).toBe( (6 + 1 + editor.verticalScrollMargin) * component.getLineHeight() - ) + ); - editor.scrollToScreenPosition([8, 0]) - await component.getNextUpdatePromise() + editor.scrollToScreenPosition([8, 0]); + await component.getNextUpdatePromise(); expect(component.getScrollBottom()).toBe( (8 + 1 + editor.verticalScrollMargin) * component.measurements.lineHeight - ) + ); - editor.scrollToScreenPosition([3, 0]) - await component.getNextUpdatePromise() + editor.scrollToScreenPosition([3, 0]); + await component.getNextUpdatePromise(); expect(component.getScrollTop()).toBe( (3 - editor.verticalScrollMargin) * component.measurements.lineHeight - ) + ); - editor.scrollToScreenPosition([2, 0]) - await component.getNextUpdatePromise() - expect(component.getScrollTop()).toBe(0) - }) + editor.scrollToScreenPosition([2, 0]); + await component.getNextUpdatePromise(); + expect(component.getScrollTop()).toBe(0); + }); it('does not vertically autoscroll by more than half of the visible lines if the editor is shorter than twice the scroll margin', async () => { const { component, element, editor } = buildComponent({ autoHeight: false - }) + }); element.style.height = 5.5 * component.measurements.lineHeight + horizontalScrollbarHeight + - 'px' - await component.getNextUpdatePromise() - expect(component.getLastVisibleRow()).toBe(5) - const scrollMarginInLines = 2 + 'px'; + await component.getNextUpdatePromise(); + expect(component.getLastVisibleRow()).toBe(5); + const scrollMarginInLines = 2; - editor.scrollToScreenPosition([6, 0]) - await component.getNextUpdatePromise() + editor.scrollToScreenPosition([6, 0]); + await component.getNextUpdatePromise(); expect(component.getScrollBottom()).toBe( (6 + 1 + scrollMarginInLines) * component.measurements.lineHeight - ) + ); - editor.scrollToScreenPosition([6, 4]) - await component.getNextUpdatePromise() + editor.scrollToScreenPosition([6, 4]); + await component.getNextUpdatePromise(); expect(component.getScrollBottom()).toBe( (6 + 1 + scrollMarginInLines) * component.measurements.lineHeight - ) + ); - editor.scrollToScreenRange([[4, 4], [6, 4]]) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[4, 4], [6, 4]]); + await component.getNextUpdatePromise(); expect(component.getScrollTop()).toBe( (4 - scrollMarginInLines) * component.measurements.lineHeight - ) + ); - editor.scrollToScreenRange([[4, 4], [6, 4]], { reversed: false }) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[4, 4], [6, 4]], { reversed: false }); + await component.getNextUpdatePromise(); expect(component.getScrollBottom()).toBe( (6 + 1 + scrollMarginInLines) * component.measurements.lineHeight - ) - }) + ); + }); it('autoscrolls the given range to the center of the screen if the `center` option is true', async () => { - const { component, editor } = buildComponent({ height: 50 }) - expect(component.getLastVisibleRow()).toBe(2) + const { component, editor } = buildComponent({ height: 50 }); + expect(component.getLastVisibleRow()).toBe(2); - editor.scrollToScreenRange([[4, 0], [6, 0]], { center: true }) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[4, 0], [6, 0]], { center: true }); + await component.getNextUpdatePromise(); const actualScrollCenter = - (component.getScrollTop() + component.getScrollBottom()) / 2 - const expectedScrollCenter = ((4 + 7) / 2) * component.getLineHeight() - expect(actualScrollCenter).toBeCloseTo(expectedScrollCenter, 0) - }) + (component.getScrollTop() + component.getScrollBottom()) / 2; + const expectedScrollCenter = ((4 + 7) / 2) * component.getLineHeight(); + expect(actualScrollCenter).toBeCloseTo(expectedScrollCenter, 0); + }); it('automatically scrolls horizontally when the requested range is within the horizontal scroll margin of the right edge of the gutter or right edge of the scroll container', async () => { - const { component, element, editor } = buildComponent() + const { component, element, editor } = buildComponent(); element.style.width = component.getGutterContainerWidth() + 3 * editor.horizontalScrollMargin * component.measurements.baseCharacterWidth + - 'px' - await component.getNextUpdatePromise() + 'px'; + await component.getNextUpdatePromise(); - editor.scrollToScreenRange([[1, 12], [2, 28]]) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[1, 12], [2, 28]]); + await component.getNextUpdatePromise(); let expectedScrollLeft = clientLeftForCharacter(component, 1, 12) - lineNodeForScreenRow(component, 1).getBoundingClientRect().left - editor.horizontalScrollMargin * - component.measurements.baseCharacterWidth - expect(component.getScrollLeft()).toBeCloseTo(expectedScrollLeft, 0) + component.measurements.baseCharacterWidth; + expect(component.getScrollLeft()).toBeCloseTo(expectedScrollLeft, 0); - editor.scrollToScreenRange([[1, 12], [2, 28]], { reversed: false }) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[1, 12], [2, 28]], { reversed: false }); + await component.getNextUpdatePromise(); expectedScrollLeft = component.getGutterContainerWidth() + clientLeftForCharacter(component, 2, 28) - lineNodeForScreenRow(component, 2).getBoundingClientRect().left + editor.horizontalScrollMargin * component.measurements.baseCharacterWidth - - component.getScrollContainerClientWidth() - expect(component.getScrollLeft()).toBeCloseTo(expectedScrollLeft, 0) - }) + component.getScrollContainerClientWidth(); + expect(component.getScrollLeft()).toBeCloseTo(expectedScrollLeft, 0); + }); it('does not horizontally autoscroll by more than half of the visible "base-width" characters if the editor is narrower than twice the scroll margin', async () => { - const { component, editor } = buildComponent({ autoHeight: false }) + const { component, editor } = buildComponent({ autoHeight: false }); await setEditorWidthInCharacters( component, 1.5 * editor.horizontalScrollMargin - ) + ); const editorWidthInChars = component.getScrollContainerClientWidth() / - component.getBaseCharacterWidth() - expect(Math.round(editorWidthInChars)).toBe(9) + component.getBaseCharacterWidth(); + expect(Math.round(editorWidthInChars)).toBe(9); - editor.scrollToScreenRange([[6, 10], [6, 15]]) - await component.getNextUpdatePromise() + editor.scrollToScreenRange([[6, 10], [6, 15]]); + await component.getNextUpdatePromise(); let expectedScrollLeft = Math.floor( clientLeftForCharacter(component, 6, 10) - lineNodeForScreenRow(component, 1).getBoundingClientRect().left - Math.floor((editorWidthInChars - 1) / 2) * component.getBaseCharacterWidth() - ) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) - }) + ); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); + }); it('correctly autoscrolls after inserting a line that exceeds the current content width', async () => { - const { component, element, editor } = buildComponent() + const { component, element, editor } = buildComponent(); element.style.width = - component.getGutterContainerWidth() + component.getContentWidth() + 'px' - await component.getNextUpdatePromise() + component.getGutterContainerWidth() + + component.getContentWidth() + + 'px'; + await component.getNextUpdatePromise(); - editor.setCursorScreenPosition([0, Infinity]) - editor.insertText('x'.repeat(100)) - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([0, Infinity]); + editor.insertText('x'.repeat(100)); + await component.getNextUpdatePromise(); expect(component.getScrollLeft()).toBe( component.getScrollWidth() - component.getScrollContainerClientWidth() - ) - }) + ); + }); it('does not try to measure lines that do not exist when the animation frame is delivered', async () => { const { component, editor } = buildComponent({ autoHeight: false, height: 30, rowsPerTile: 2 - }) - editor.scrollToBufferPosition([11, 5]) - editor.getBuffer().deleteRows(11, 12) - await component.getNextUpdatePromise() + }); + editor.scrollToBufferPosition([11, 5]); + editor.getBuffer().deleteRows(11, 12); + await component.getNextUpdatePromise(); expect(component.getScrollBottom()).toBe( (10 + 1) * component.measurements.lineHeight - ) - }) + ); + }); it('accounts for the presence of horizontal scrollbars that appear during the same frame as the autoscroll', async () => { const { component, element, editor } = buildComponent({ autoHeight: false - }) - element.style.height = component.getContentHeight() / 2 + 'px' - element.style.width = component.getScrollWidth() + 'px' - await component.getNextUpdatePromise() + }); + element.style.height = component.getContentHeight() / 2 + 'px'; + element.style.width = component.getScrollWidth() + 'px'; + await component.getNextUpdatePromise(); - editor.setCursorScreenPosition([10, Infinity]) - editor.insertText('\n\n' + 'x'.repeat(100)) - await component.getNextUpdatePromise() + editor.setCursorScreenPosition([10, Infinity]); + editor.insertText('\n\n' + 'x'.repeat(100)); + await component.getNextUpdatePromise(); expect(component.getScrollTop()).toBe( component.getScrollHeight() - component.getScrollContainerClientHeight() - ) + ); expect(component.getScrollLeft()).toBe( component.getScrollWidth() - component.getScrollContainerClientWidth() - ) + ); // Scrolling to the top should not throw an error. This failed // previously due to horizontalPositionsToMeasure not being empty after // autoscrolling vertically to account for the horizontal scrollbar. - spyOn(window, 'onerror') - await setScrollTop(component, 0) - expect(window.onerror).not.toHaveBeenCalled() - }) - }) + spyOn(window, 'onerror'); + await setScrollTop(component, 0); + expect(window.onerror).not.toHaveBeenCalled(); + }); + }); describe('logical scroll positions', () => { it('allows the scrollTop to be changed and queried in terms of rows via setScrollTopRow and getScrollTopRow', () => { const { component, element } = buildComponent({ attach: false, height: 80 - }) + }); // Caches the scrollTopRow if we don't have measurements - component.setScrollTopRow(6) - expect(component.getScrollTopRow()).toBe(6) + component.setScrollTopRow(6); + expect(component.getScrollTopRow()).toBe(6); // Assigns the scrollTop based on the logical position when attached - jasmine.attachToDOM(element) - const expectedScrollTop = Math.round(6 * component.getLineHeight()) - expect(component.getScrollTopRow()).toBe(6) - expect(component.getScrollTop()).toBe(expectedScrollTop) + jasmine.attachToDOM(element); + const expectedScrollTop = Math.round(6 * component.getLineHeight()); + expect(component.getScrollTopRow()).toBe(6); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) + ); // Allows the scrollTopRow to be updated while attached - component.setScrollTopRow(4) - expect(component.getScrollTopRow()).toBe(4) + component.setScrollTopRow(4); + expect(component.getScrollTopRow()).toBe(4); expect(component.getScrollTop()).toBe( Math.round(4 * component.getLineHeight()) - ) + ); // Preserves the scrollTopRow when detached - element.remove() - expect(component.getScrollTopRow()).toBe(4) + element.remove(); + expect(component.getScrollTopRow()).toBe(4); expect(component.getScrollTop()).toBe( Math.round(4 * component.getLineHeight()) - ) + ); - component.setScrollTopRow(6) - expect(component.getScrollTopRow()).toBe(6) + component.setScrollTopRow(6); + expect(component.getScrollTopRow()).toBe(6); expect(component.getScrollTop()).toBe( Math.round(6 * component.getLineHeight()) - ) + ); - jasmine.attachToDOM(element) - element.style.height = '60px' - expect(component.getScrollTopRow()).toBe(6) + jasmine.attachToDOM(element); + element.style.height = '60px'; + expect(component.getScrollTopRow()).toBe(6); expect(component.getScrollTop()).toBe( Math.round(6 * component.getLineHeight()) - ) - }) + ); + }); it('allows the scrollLeft to be changed and queried in terms of base character columns via setScrollLeftColumn and getScrollLeftColumn', () => { const { component, element } = buildComponent({ attach: false, width: 80 - }) + }); // Caches the scrollTopRow if we don't have measurements - component.setScrollLeftColumn(2) - expect(component.getScrollLeftColumn()).toBe(2) + component.setScrollLeftColumn(2); + expect(component.getScrollLeftColumn()).toBe(2); // Assigns the scrollTop based on the logical position when attached - jasmine.attachToDOM(element) + jasmine.attachToDOM(element); expect(component.getScrollLeft()).toBeCloseTo( 2 * component.getBaseCharacterWidth(), 0 - ) + ); // Allows the scrollTopRow to be updated while attached - component.setScrollLeftColumn(4) + component.setScrollLeftColumn(4); expect(component.getScrollLeft()).toBeCloseTo( 4 * component.getBaseCharacterWidth(), 0 - ) + ); // Preserves the scrollTopRow when detached - element.remove() + element.remove(); expect(component.getScrollLeft()).toBeCloseTo( 4 * component.getBaseCharacterWidth(), 0 - ) + ); - component.setScrollLeftColumn(6) + component.setScrollLeftColumn(6); expect(component.getScrollLeft()).toBeCloseTo( 6 * component.getBaseCharacterWidth(), 0 - ) + ); - jasmine.attachToDOM(element) - element.style.width = '60px' + jasmine.attachToDOM(element); + element.style.width = '60px'; expect(component.getScrollLeft()).toBeCloseTo( 6 * component.getBaseCharacterWidth(), 0 - ) - }) - }) + ); + }); + }); describe('scrolling via the mouse wheel', () => { it('scrolls vertically or horizontally depending on whether deltaX or deltaY is larger', () => { - const scrollSensitivity = 30 + const scrollSensitivity = 30; const { component } = buildComponent({ height: 50, width: 50, scrollSensitivity - }) + }); { - const expectedScrollTop = 20 * (scrollSensitivity / 100) - const expectedScrollLeft = component.getScrollLeft() - component.didMouseWheel({ wheelDeltaX: -5, wheelDeltaY: -20 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + const expectedScrollTop = 20 * (scrollSensitivity / 100); + const expectedScrollLeft = component.getScrollLeft(); + component.didMouseWheel({ wheelDeltaX: -5, wheelDeltaY: -20 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)` - ) + ); } { const expectedScrollTop = - component.getScrollTop() - 10 * (scrollSensitivity / 100) - const expectedScrollLeft = component.getScrollLeft() - component.didMouseWheel({ wheelDeltaX: -5, wheelDeltaY: 10 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + component.getScrollTop() - 10 * (scrollSensitivity / 100); + const expectedScrollLeft = component.getScrollLeft(); + component.didMouseWheel({ wheelDeltaX: -5, wheelDeltaY: 10 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)` - ) + ); } { - const expectedScrollTop = component.getScrollTop() - const expectedScrollLeft = 20 * (scrollSensitivity / 100) - component.didMouseWheel({ wheelDeltaX: -20, wheelDeltaY: 10 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + const expectedScrollTop = component.getScrollTop(); + const expectedScrollLeft = 20 * (scrollSensitivity / 100); + component.didMouseWheel({ wheelDeltaX: -20, wheelDeltaY: 10 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)` - ) + ); } { - const expectedScrollTop = component.getScrollTop() + const expectedScrollTop = component.getScrollTop(); const expectedScrollLeft = - component.getScrollLeft() - 10 * (scrollSensitivity / 100) - component.didMouseWheel({ wheelDeltaX: 10, wheelDeltaY: -8 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + component.getScrollLeft() - 10 * (scrollSensitivity / 100); + component.didMouseWheel({ wheelDeltaX: 10, wheelDeltaY: -8 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)` - ) + ); } - }) + }); it('inverts deltaX and deltaY when holding shift on Windows and Linux', async () => { - const scrollSensitivity = 50 + const scrollSensitivity = 50; const { component } = buildComponent({ height: 50, width: 50, scrollSensitivity - }) + }); - component.props.platform = 'linux' + component.props.platform = 'linux'; { - const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) + const expectedScrollTop = 20 * (scrollSensitivity / 100); + component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) - await setScrollTop(component, 0) + ); + await setScrollTop(component, 0); } { - const expectedScrollLeft = 20 * (scrollSensitivity / 100) + const expectedScrollLeft = 20 * (scrollSensitivity / 100); component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true - }) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + }); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(-${expectedScrollLeft}px, 0px)` - ) - await setScrollLeft(component, 0) + ); + await setScrollLeft(component, 0); } { - const expectedScrollTop = 20 * (scrollSensitivity / 100) + const expectedScrollTop = 20 * (scrollSensitivity / 100); component.didMouseWheel({ wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true - }) - expect(component.getScrollTop()).toBe(expectedScrollTop) + }); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) - await setScrollTop(component, 0) + ); + await setScrollTop(component, 0); } - component.props.platform = 'win32' + component.props.platform = 'win32'; { - const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) + const expectedScrollTop = 20 * (scrollSensitivity / 100); + component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) - await setScrollTop(component, 0) + ); + await setScrollTop(component, 0); } { - const expectedScrollLeft = 20 * (scrollSensitivity / 100) + const expectedScrollLeft = 20 * (scrollSensitivity / 100); component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true - }) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + }); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(-${expectedScrollLeft}px, 0px)` - ) - await setScrollLeft(component, 0) + ); + await setScrollLeft(component, 0); } { - const expectedScrollTop = 20 * (scrollSensitivity / 100) + const expectedScrollTop = 20 * (scrollSensitivity / 100); component.didMouseWheel({ wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true - }) - expect(component.getScrollTop()).toBe(expectedScrollTop) + }); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) - await setScrollTop(component, 0) + ); + await setScrollTop(component, 0); } - component.props.platform = 'darwin' + component.props.platform = 'darwin'; { - const expectedScrollTop = 20 * (scrollSensitivity / 100) - component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20 }) - expect(component.getScrollTop()).toBe(expectedScrollTop) + const expectedScrollTop = 20 * (scrollSensitivity / 100); + component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20 }); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) - await setScrollTop(component, 0) + ); + await setScrollTop(component, 0); } { - const expectedScrollTop = 20 * (scrollSensitivity / 100) + const expectedScrollTop = 20 * (scrollSensitivity / 100); component.didMouseWheel({ wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true - }) - expect(component.getScrollTop()).toBe(expectedScrollTop) + }); + expect(component.getScrollTop()).toBe(expectedScrollTop); expect(component.refs.content.style.transform).toBe( `translate(0px, -${expectedScrollTop}px)` - ) - await setScrollTop(component, 0) + ); + await setScrollTop(component, 0); } { - const expectedScrollLeft = 20 * (scrollSensitivity / 100) + const expectedScrollLeft = 20 * (scrollSensitivity / 100); component.didMouseWheel({ wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true - }) - expect(component.getScrollLeft()).toBe(expectedScrollLeft) + }); + expect(component.getScrollLeft()).toBe(expectedScrollLeft); expect(component.refs.content.style.transform).toBe( `translate(-${expectedScrollLeft}px, 0px)` - ) - await setScrollLeft(component, 0) + ); + await setScrollLeft(component, 0); } - }) - }) + }); + }); describe('scrolling via the API', () => { it('ignores scroll requests to NaN, null or undefined positions', async () => { const { component } = buildComponent({ rowsPerTile: 2, autoHeight: false - }) - await setEditorHeightInLines(component, 3) - await setEditorWidthInCharacters(component, 10) + }); + await setEditorHeightInLines(component, 3); + await setEditorWidthInCharacters(component, 10); - const initialScrollTop = Math.round(2 * component.getLineHeight()) + const initialScrollTop = Math.round(2 * component.getLineHeight()); const initialScrollLeft = Math.round( 5 * component.getBaseCharacterWidth() - ) - setScrollTop(component, initialScrollTop) - setScrollLeft(component, initialScrollLeft) - await component.getNextUpdatePromise() + ); + setScrollTop(component, initialScrollTop); + setScrollLeft(component, initialScrollLeft); + await component.getNextUpdatePromise(); - setScrollTop(component, NaN) - setScrollLeft(component, NaN) - await component.getNextUpdatePromise() - expect(component.getScrollTop()).toBe(initialScrollTop) - expect(component.getScrollLeft()).toBe(initialScrollLeft) + setScrollTop(component, NaN); + setScrollLeft(component, NaN); + await component.getNextUpdatePromise(); + expect(component.getScrollTop()).toBe(initialScrollTop); + expect(component.getScrollLeft()).toBe(initialScrollLeft); - setScrollTop(component, null) - setScrollLeft(component, null) - await component.getNextUpdatePromise() - expect(component.getScrollTop()).toBe(initialScrollTop) - expect(component.getScrollLeft()).toBe(initialScrollLeft) + setScrollTop(component, null); + setScrollLeft(component, null); + await component.getNextUpdatePromise(); + expect(component.getScrollTop()).toBe(initialScrollTop); + expect(component.getScrollLeft()).toBe(initialScrollLeft); - setScrollTop(component, undefined) - setScrollLeft(component, undefined) - await component.getNextUpdatePromise() - expect(component.getScrollTop()).toBe(initialScrollTop) - expect(component.getScrollLeft()).toBe(initialScrollLeft) - }) - }) + setScrollTop(component, undefined); + setScrollLeft(component, undefined); + await component.getNextUpdatePromise(); + expect(component.getScrollTop()).toBe(initialScrollTop); + expect(component.getScrollLeft()).toBe(initialScrollLeft); + }); + }); describe('line and line number decorations', () => { it('adds decoration classes on screen lines spanned by decorated markers', async () => { const { component, editor } = buildComponent({ softWrapped: true - }) - await setEditorWidthInCharacters(component, 55) + }); + await setEditorWidthInCharacters(component, 55); expect(lineNodeForScreenRow(component, 3).textContent).toBe( ' var pivot = items.shift(), current, left = [], ' - ) + ); expect(lineNodeForScreenRow(component, 4).textContent).toBe( ' right = [];' - ) + ); - const marker1 = editor.markScreenRange([[1, 10], [3, 10]]) - const layer = editor.addMarkerLayer() - layer.markScreenPosition([5, 0]) - layer.markScreenPosition([8, 0]) - const marker4 = layer.markScreenPosition([10, 0]) + const marker1 = editor.markScreenRange([[1, 10], [3, 10]]); + const layer = editor.addMarkerLayer(); + layer.markScreenPosition([5, 0]); + layer.markScreenPosition([8, 0]); + const marker4 = layer.markScreenPosition([10, 0]); editor.decorateMarker(marker1, { type: ['line', 'line-number'], class: 'a' - }) + }); const layerDecoration = editor.decorateMarkerLayer(layer, { type: ['line', 'line-number'], class: 'b' - }) + }); layerDecoration.setPropertiesForMarker(marker4, { type: 'line', class: 'c' - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 1).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 2).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 3).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 4).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 5).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 8).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 10).classList.contains('b')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 10).classList.contains('c')).toBe( true - ) + ); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 2).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 3).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 4).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 5).classList.contains('b') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 8).classList.contains('b') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 10).classList.contains('b') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 10).classList.contains('c') - ).toBe(false) + ).toBe(false); - marker1.setScreenRange([[5, 0], [8, 0]]) - await component.getNextUpdatePromise() + marker1.setScreenRange([[5, 0], [8, 0]]); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 1).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 2).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 3).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 4).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 5).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 5).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 6).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 7).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 8).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 8).classList.contains('b')).toBe( true - ) + ); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 2).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 3).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 4).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 5).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 5).classList.contains('b') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 6).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 7).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 8).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 8).classList.contains('b') - ).toBe(true) - }) + ).toBe(true); + }); it('honors the onlyEmpty and onlyNonEmpty decoration options', async () => { - const { component, editor } = buildComponent() - const marker = editor.markScreenPosition([1, 0]) + const { component, editor } = buildComponent(); + const marker = editor.markScreenPosition([1, 0]); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'a', onlyEmpty: true - }) + }); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'b', onlyNonEmpty: true - }) + }); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'c' - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 1).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 1).classList.contains('b')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 1).classList.contains('c')).toBe( true - ) + ); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('a') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('b') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('c') - ).toBe(true) + ).toBe(true); - marker.setScreenRange([[1, 0], [2, 4]]) - await component.getNextUpdatePromise() + marker.setScreenRange([[1, 0], [2, 4]]); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 1).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 1).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 1).classList.contains('c')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 2).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 2).classList.contains('c')).toBe( true - ) + ); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('b') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('c') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 2).classList.contains('b') - ).toBe(true) + ).toBe(true); expect( lineNumberNodeForScreenRow(component, 2).classList.contains('c') - ).toBe(true) - }) + ).toBe(true); + }); it('honors the onlyHead option', async () => { - const { component, editor } = buildComponent() - const marker = editor.markScreenRange([[1, 4], [3, 4]]) + const { component, editor } = buildComponent(); + const marker = editor.markScreenRange([[1, 4], [3, 4]]); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'a', onlyHead: true - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 1).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 3).classList.contains('a')).toBe( true - ) + ); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('a') - ).toBe(false) + ).toBe(false); expect( lineNumberNodeForScreenRow(component, 3).classList.contains('a') - ).toBe(true) - }) + ).toBe(true); + }); it('only decorates the last row of non-empty ranges that end at column 0 if omitEmptyLastRow is false', async () => { - const { component, editor } = buildComponent() - const marker = editor.markScreenRange([[1, 0], [3, 0]]) + const { component, editor } = buildComponent(); + const marker = editor.markScreenRange([[1, 0], [3, 0]]); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'a' - }) + }); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'b', omitEmptyLastRow: false - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 1).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 2).classList.contains('a')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 3).classList.contains('a')).toBe( false - ) + ); expect(lineNodeForScreenRow(component, 1).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 2).classList.contains('b')).toBe( true - ) + ); expect(lineNodeForScreenRow(component, 3).classList.contains('b')).toBe( true - ) - }) + ); + }); it('does not decorate invalidated markers', async () => { - const { component, editor } = buildComponent() + const { component, editor } = buildComponent(); const marker = editor.markScreenRange([[1, 0], [3, 0]], { invalidate: 'touch' - }) + }); editor.decorateMarker(marker, { type: ['line', 'line-number'], class: 'a' - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 2).classList.contains('a')).toBe( true - ) + ); - editor.getBuffer().insert([2, 0], 'x') - expect(marker.isValid()).toBe(false) - await component.getNextUpdatePromise() + editor.getBuffer().insert([2, 0], 'x'); + expect(marker.isValid()).toBe(false); + await component.getNextUpdatePromise(); expect(lineNodeForScreenRow(component, 2).classList.contains('a')).toBe( false - ) - }) - }) + ); + }); + }); describe('highlight decorations', () => { it('renders single-line highlights', async () => { - const { component, element, editor } = buildComponent() - const marker = editor.markScreenRange([[1, 2], [1, 10]]) - editor.decorateMarker(marker, { type: 'highlight', class: 'a' }) - await component.getNextUpdatePromise() + const { component, element, editor } = buildComponent(); + const marker = editor.markScreenRange([[1, 2], [1, 10]]); + editor.decorateMarker(marker, { type: 'highlight', class: 'a' }); + await component.getNextUpdatePromise(); { - const regions = element.querySelectorAll('.highlight.a .region.a') - expect(regions.length).toBe(1) - const regionRect = regions[0].getBoundingClientRect() + const regions = element.querySelectorAll('.highlight.a .region.a'); + expect(regions.length).toBe(1); + const regionRect = regions[0].getBoundingClientRect(); expect(regionRect.top).toBe( lineNodeForScreenRow(component, 1).getBoundingClientRect().top - ) + ); expect(Math.round(regionRect.left)).toBe( clientLeftForCharacter(component, 1, 2) - ) + ); expect(Math.round(regionRect.right)).toBe( clientLeftForCharacter(component, 1, 10) - ) + ); } - marker.setScreenRange([[1, 4], [1, 8]]) - await component.getNextUpdatePromise() + marker.setScreenRange([[1, 4], [1, 8]]); + await component.getNextUpdatePromise(); { - const regions = element.querySelectorAll('.highlight.a .region.a') - expect(regions.length).toBe(1) - const regionRect = regions[0].getBoundingClientRect() + const regions = element.querySelectorAll('.highlight.a .region.a'); + expect(regions.length).toBe(1); + const regionRect = regions[0].getBoundingClientRect(); expect(regionRect.top).toBe( lineNodeForScreenRow(component, 1).getBoundingClientRect().top - ) + ); expect(regionRect.bottom).toBe( lineNodeForScreenRow(component, 1).getBoundingClientRect().bottom - ) + ); expect(Math.round(regionRect.left)).toBe( clientLeftForCharacter(component, 1, 4) - ) + ); expect(Math.round(regionRect.right)).toBe( clientLeftForCharacter(component, 1, 8) - ) + ); } - }) + }); it('renders multi-line highlights', async () => { - const { component, element, editor } = buildComponent({ rowsPerTile: 3 }) - const marker = editor.markScreenRange([[2, 4], [3, 4]]) - editor.decorateMarker(marker, { type: 'highlight', class: 'a' }) + const { component, element, editor } = buildComponent({ rowsPerTile: 3 }); + const marker = editor.markScreenRange([[2, 4], [3, 4]]); + editor.decorateMarker(marker, { type: 'highlight', class: 'a' }); - await component.getNextUpdatePromise() + await component.getNextUpdatePromise(); { - expect(element.querySelectorAll('.highlight.a').length).toBe(1) + expect(element.querySelectorAll('.highlight.a').length).toBe(1); - const regions = element.querySelectorAll('.highlight.a .region.a') - expect(regions.length).toBe(2) - const region0Rect = regions[0].getBoundingClientRect() + const regions = element.querySelectorAll('.highlight.a .region.a'); + expect(regions.length).toBe(2); + const region0Rect = regions[0].getBoundingClientRect(); expect(region0Rect.top).toBe( lineNodeForScreenRow(component, 2).getBoundingClientRect().top - ) + ); expect(region0Rect.bottom).toBe( lineNodeForScreenRow(component, 2).getBoundingClientRect().bottom - ) + ); expect(Math.round(region0Rect.left)).toBe( clientLeftForCharacter(component, 2, 4) - ) + ); expect(Math.round(region0Rect.right)).toBe( component.refs.content.getBoundingClientRect().right - ) + ); - const region1Rect = regions[1].getBoundingClientRect() + const region1Rect = regions[1].getBoundingClientRect(); expect(region1Rect.top).toBe( lineNodeForScreenRow(component, 3).getBoundingClientRect().top - ) + ); expect(region1Rect.bottom).toBe( lineNodeForScreenRow(component, 3).getBoundingClientRect().bottom - ) + ); expect(Math.round(region1Rect.left)).toBe( clientLeftForCharacter(component, 3, 0) - ) + ); expect(Math.round(region1Rect.right)).toBe( clientLeftForCharacter(component, 3, 4) - ) + ); } - marker.setScreenRange([[2, 4], [5, 4]]) - await component.getNextUpdatePromise() + marker.setScreenRange([[2, 4], [5, 4]]); + await component.getNextUpdatePromise(); { - expect(element.querySelectorAll('.highlight.a').length).toBe(1) + expect(element.querySelectorAll('.highlight.a').length).toBe(1); - const regions = element.querySelectorAll('.highlight.a .region.a') - expect(regions.length).toBe(3) + const regions = element.querySelectorAll('.highlight.a .region.a'); + expect(regions.length).toBe(3); - const region0Rect = regions[0].getBoundingClientRect() + const region0Rect = regions[0].getBoundingClientRect(); expect(region0Rect.top).toBe( lineNodeForScreenRow(component, 2).getBoundingClientRect().top - ) + ); expect(region0Rect.bottom).toBe( lineNodeForScreenRow(component, 2).getBoundingClientRect().bottom - ) + ); expect(Math.round(region0Rect.left)).toBe( clientLeftForCharacter(component, 2, 4) - ) + ); expect(Math.round(region0Rect.right)).toBe( component.refs.content.getBoundingClientRect().right - ) + ); - const region1Rect = regions[1].getBoundingClientRect() + const region1Rect = regions[1].getBoundingClientRect(); expect(region1Rect.top).toBe( lineNodeForScreenRow(component, 3).getBoundingClientRect().top - ) + ); expect(region1Rect.bottom).toBe( lineNodeForScreenRow(component, 5).getBoundingClientRect().top - ) + ); expect(Math.round(region1Rect.left)).toBe( component.refs.content.getBoundingClientRect().left - ) + ); expect(Math.round(region1Rect.right)).toBe( component.refs.content.getBoundingClientRect().right - ) + ); - const region2Rect = regions[2].getBoundingClientRect() + const region2Rect = regions[2].getBoundingClientRect(); expect(region2Rect.top).toBe( lineNodeForScreenRow(component, 5).getBoundingClientRect().top - ) + ); expect(region2Rect.bottom).toBe( lineNodeForScreenRow(component, 6).getBoundingClientRect().top - ) + ); expect(Math.round(region2Rect.left)).toBe( component.refs.content.getBoundingClientRect().left - ) + ); expect(Math.round(region2Rect.right)).toBe( clientLeftForCharacter(component, 5, 4) - ) + ); } - }) + }); it('can flash highlight decorations', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, height: 200 - }) - const marker = editor.markScreenRange([[2, 4], [3, 4]]) + }); + const marker = editor.markScreenRange([[2, 4], [3, 4]]); const decoration = editor.decorateMarker(marker, { type: 'highlight', class: 'a' - }) - decoration.flash('b', 10) + }); + decoration.flash('b', 10); // Flash on initial appearance of highlight - await component.getNextUpdatePromise() - const highlights = element.querySelectorAll('.highlight.a') - expect(highlights.length).toBe(1) + await component.getNextUpdatePromise(); + const highlights = element.querySelectorAll('.highlight.a'); + expect(highlights.length).toBe(1); - expect(highlights[0].classList.contains('b')).toBe(true) + expect(highlights[0].classList.contains('b')).toBe(true); - await conditionPromise(() => !highlights[0].classList.contains('b')) + await conditionPromise(() => !highlights[0].classList.contains('b')); // Don't flash on next update if another flash wasn't requested - await setScrollTop(component, 100) - expect(highlights[0].classList.contains('b')).toBe(false) + await setScrollTop(component, 100); + expect(highlights[0].classList.contains('b')).toBe(false); // Flashing the same class again before the first flash completes // removes the flash class and adds it back on the next frame to ensure // CSS transitions apply to the second flash. - decoration.flash('e', 100) - await component.getNextUpdatePromise() - expect(highlights[0].classList.contains('e')).toBe(true) + decoration.flash('e', 100); + await component.getNextUpdatePromise(); + expect(highlights[0].classList.contains('e')).toBe(true); - decoration.flash('e', 100) - await component.getNextUpdatePromise() - expect(highlights[0].classList.contains('e')).toBe(false) + decoration.flash('e', 100); + await component.getNextUpdatePromise(); + expect(highlights[0].classList.contains('e')).toBe(false); - await conditionPromise(() => highlights[0].classList.contains('e')) - await conditionPromise(() => !highlights[0].classList.contains('e')) - }) + await conditionPromise(() => highlights[0].classList.contains('e')); + await conditionPromise(() => !highlights[0].classList.contains('e')); + }); it("flashing a highlight decoration doesn't unflash other highlight decorations", async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, height: 200 - }) - const marker = editor.markScreenRange([[2, 4], [3, 4]]) + }); + const marker = editor.markScreenRange([[2, 4], [3, 4]]); const decoration = editor.decorateMarker(marker, { type: 'highlight', class: 'a' - }) + }); // Flash one class - decoration.flash('c', 1000) - await component.getNextUpdatePromise() - const highlights = element.querySelectorAll('.highlight.a') - expect(highlights.length).toBe(1) - expect(highlights[0].classList.contains('c')).toBe(true) + decoration.flash('c', 1000); + await component.getNextUpdatePromise(); + const highlights = element.querySelectorAll('.highlight.a'); + expect(highlights.length).toBe(1); + expect(highlights[0].classList.contains('c')).toBe(true); // Flash another class while the previously-flashed class is still highlighted - decoration.flash('d', 100) - await component.getNextUpdatePromise() - expect(highlights[0].classList.contains('c')).toBe(true) - expect(highlights[0].classList.contains('d')).toBe(true) - }) + decoration.flash('d', 100); + await component.getNextUpdatePromise(); + expect(highlights[0].classList.contains('c')).toBe(true); + expect(highlights[0].classList.contains('d')).toBe(true); + }); it('supports layer decorations', async () => { - const { component, element, editor } = buildComponent({ rowsPerTile: 12 }) - const markerLayer = editor.addMarkerLayer() - const marker1 = markerLayer.markScreenRange([[2, 4], [3, 4]]) - const marker2 = markerLayer.markScreenRange([[5, 6], [7, 8]]) + const { component, element, editor } = buildComponent({ + rowsPerTile: 12 + }); + const markerLayer = editor.addMarkerLayer(); + const marker1 = markerLayer.markScreenRange([[2, 4], [3, 4]]); + const marker2 = markerLayer.markScreenRange([[5, 6], [7, 8]]); const decoration = editor.decorateMarkerLayer(markerLayer, { type: 'highlight', class: 'a' - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); - const highlights = element.querySelectorAll('.highlight') - expect(highlights[0].classList.contains('a')).toBe(true) - expect(highlights[1].classList.contains('a')).toBe(true) + const highlights = element.querySelectorAll('.highlight'); + expect(highlights[0].classList.contains('a')).toBe(true); + expect(highlights[1].classList.contains('a')).toBe(true); decoration.setPropertiesForMarker(marker1, { type: 'highlight', class: 'b' - }) - await component.getNextUpdatePromise() - expect(highlights[0].classList.contains('b')).toBe(true) - expect(highlights[1].classList.contains('a')).toBe(true) + }); + await component.getNextUpdatePromise(); + expect(highlights[0].classList.contains('b')).toBe(true); + expect(highlights[1].classList.contains('a')).toBe(true); - decoration.setPropertiesForMarker(marker1, null) + decoration.setPropertiesForMarker(marker1, null); decoration.setPropertiesForMarker(marker2, { type: 'highlight', class: 'c' - }) - await component.getNextUpdatePromise() - expect(highlights[0].classList.contains('a')).toBe(true) - expect(highlights[1].classList.contains('c')).toBe(true) - }) + }); + await component.getNextUpdatePromise(); + expect(highlights[0].classList.contains('a')).toBe(true); + expect(highlights[1].classList.contains('c')).toBe(true); + }); it('clears highlights when recycling a tile that previously contained highlights and now does not', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false - }) - await setEditorHeightInLines(component, 2) - const marker = editor.markScreenRange([[1, 2], [1, 10]]) - editor.decorateMarker(marker, { type: 'highlight', class: 'a' }) + }); + await setEditorHeightInLines(component, 2); + const marker = editor.markScreenRange([[1, 2], [1, 10]]); + editor.decorateMarker(marker, { type: 'highlight', class: 'a' }); - await component.getNextUpdatePromise() - expect(element.querySelectorAll('.highlight.a').length).toBe(1) + await component.getNextUpdatePromise(); + expect(element.querySelectorAll('.highlight.a').length).toBe(1); - await setScrollTop(component, component.getLineHeight() * 3) - expect(element.querySelectorAll('.highlight.a').length).toBe(0) - }) + await setScrollTop(component, component.getLineHeight() * 3); + expect(element.querySelectorAll('.highlight.a').length).toBe(0); + }); it('does not move existing highlights when adding or removing other highlight decorations (regression)', async () => { - const { component, element, editor } = buildComponent() + const { component, element, editor } = buildComponent(); - const marker1 = editor.markScreenRange([[1, 6], [1, 10]]) - editor.decorateMarker(marker1, { type: 'highlight', class: 'a' }) - await component.getNextUpdatePromise() - const marker1Region = element.querySelector('.highlight.a') + const marker1 = editor.markScreenRange([[1, 6], [1, 10]]); + editor.decorateMarker(marker1, { type: 'highlight', class: 'a' }); + await component.getNextUpdatePromise(); + const marker1Region = element.querySelector('.highlight.a'); expect( Array.from(marker1Region.parentElement.children).indexOf(marker1Region) - ).toBe(0) + ).toBe(0); - const marker2 = editor.markScreenRange([[1, 2], [1, 4]]) - editor.decorateMarker(marker2, { type: 'highlight', class: 'b' }) - await component.getNextUpdatePromise() - const marker2Region = element.querySelector('.highlight.b') + const marker2 = editor.markScreenRange([[1, 2], [1, 4]]); + editor.decorateMarker(marker2, { type: 'highlight', class: 'b' }); + await component.getNextUpdatePromise(); + const marker2Region = element.querySelector('.highlight.b'); expect( Array.from(marker1Region.parentElement.children).indexOf(marker1Region) - ).toBe(0) + ).toBe(0); expect( Array.from(marker2Region.parentElement.children).indexOf(marker2Region) - ).toBe(1) + ).toBe(1); - marker2.destroy() - await component.getNextUpdatePromise() + marker2.destroy(); + await component.getNextUpdatePromise(); expect( Array.from(marker1Region.parentElement.children).indexOf(marker1Region) - ).toBe(0) - }) + ).toBe(0); + }); it('correctly positions highlights that end on rows preceding or following block decorations', async () => { - const { editor, element, component } = buildComponent() + const { editor, element, component } = buildComponent(); - const item1 = document.createElement('div') - item1.style.height = '30px' - item1.style.backgroundColor = 'blue' + const item1 = document.createElement('div'); + item1.style.height = '30px'; + item1.style.backgroundColor = 'blue'; editor.decorateMarker(editor.markBufferPosition([4, 0]), { type: 'block', position: 'after', item: item1 - }) - const item2 = document.createElement('div') - item2.style.height = '30px' - item2.style.backgroundColor = 'yellow' + }); + const item2 = document.createElement('div'); + item2.style.height = '30px'; + item2.style.backgroundColor = 'yellow'; editor.decorateMarker(editor.markBufferPosition([4, 0]), { type: 'block', position: 'before', item: item2 - }) + }); editor.decorateMarker(editor.markBufferRange([[3, 0], [4, Infinity]]), { type: 'highlight', class: 'highlight' - }) + }); - await component.getNextUpdatePromise() - const regions = element.querySelectorAll('.highlight .region') - expect(regions[0].offsetTop).toBe(3 * component.getLineHeight()) - expect(regions[0].offsetHeight).toBe(component.getLineHeight()) - expect(regions[1].offsetTop).toBe(4 * component.getLineHeight() + 30) - }) - }) + await component.getNextUpdatePromise(); + const regions = element.querySelectorAll('.highlight .region'); + expect(regions[0].offsetTop).toBe(3 * component.getLineHeight()); + expect(regions[0].offsetHeight).toBe(component.getLineHeight()); + expect(regions[1].offsetTop).toBe(4 * component.getLineHeight() + 30); + }); + }); describe('overlay decorations', () => { - function attachFakeWindow (component) { - const fakeWindow = document.createElement('div') - fakeWindow.style.position = 'absolute' - fakeWindow.style.padding = 20 + 'px' - fakeWindow.style.backgroundColor = 'blue' - fakeWindow.appendChild(component.element) - jasmine.attachToDOM(fakeWindow) + function attachFakeWindow(component) { + const fakeWindow = document.createElement('div'); + fakeWindow.style.position = 'absolute'; + fakeWindow.style.padding = 20 + 'px'; + fakeWindow.style.backgroundColor = 'blue'; + fakeWindow.appendChild(component.element); + jasmine.attachToDOM(fakeWindow); spyOn(component, 'getWindowInnerWidth').andCallFake( () => fakeWindow.getBoundingClientRect().width - ) + ); spyOn(component, 'getWindowInnerHeight').andCallFake( () => fakeWindow.getBoundingClientRect().height - ) - return fakeWindow + ); + return fakeWindow; } it('renders overlay elements at the specified screen position unless it would overflow the window', async () => { @@ -2527,344 +2532,355 @@ describe('TextEditorComponent', () => { width: 200, height: 100, attach: false - }) - const fakeWindow = attachFakeWindow(component) + }); + const fakeWindow = attachFakeWindow(component); - await setScrollTop(component, 50) - await setScrollLeft(component, 100) + await setScrollTop(component, 50); + await setScrollLeft(component, 100); - const marker = editor.markScreenPosition([4, 25]) + const marker = editor.markScreenPosition([4, 25]); - const overlayElement = document.createElement('div') - overlayElement.style.width = '50px' - overlayElement.style.height = '50px' - overlayElement.style.margin = '3px' - overlayElement.style.backgroundColor = 'red' + const overlayElement = document.createElement('div'); + overlayElement.style.width = '50px'; + overlayElement.style.height = '50px'; + overlayElement.style.margin = '3px'; + overlayElement.style.backgroundColor = 'red'; const decoration = editor.decorateMarker(marker, { type: 'overlay', item: overlayElement, class: 'a' - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); - const overlayComponent = component.overlayComponents.values().next().value + const overlayComponent = component.overlayComponents.values().next() + .value; - const overlayWrapper = overlayElement.parentElement - expect(overlayWrapper.classList.contains('a')).toBe(true) + const overlayWrapper = overlayElement.parentElement; + expect(overlayWrapper.classList.contains('a')).toBe(true); expect(overlayWrapper.getBoundingClientRect().top).toBe( clientTopForLine(component, 5) - ) + ); expect(overlayWrapper.getBoundingClientRect().left).toBe( clientLeftForCharacter(component, 4, 25) - ) + ); // Updates the horizontal position on scroll - await setScrollLeft(component, 150) + await setScrollLeft(component, 150); expect(overlayWrapper.getBoundingClientRect().left).toBe( clientLeftForCharacter(component, 4, 25) - ) + ); // Shifts the overlay horizontally to ensure the overlay element does not // overflow the window - await setScrollLeft(component, 30) + await setScrollLeft(component, 30); expect(overlayElement.getBoundingClientRect().right).toBe( fakeWindow.getBoundingClientRect().right - ) - await setScrollLeft(component, 280) + ); + await setScrollLeft(component, 280); expect(overlayElement.getBoundingClientRect().left).toBe( fakeWindow.getBoundingClientRect().left - ) + ); // Updates the vertical position on scroll - await setScrollTop(component, 60) + await setScrollTop(component, 60); expect(overlayWrapper.getBoundingClientRect().top).toBe( clientTopForLine(component, 5) - ) + ); // Flips the overlay vertically to ensure the overlay element does not // overflow the bottom of the window - setScrollLeft(component, 100) - await setScrollTop(component, 0) + setScrollLeft(component, 100); + await setScrollTop(component, 0); expect(overlayWrapper.getBoundingClientRect().bottom).toBe( clientTopForLine(component, 4) - ) + ); // Flips the overlay vertically on overlay resize if necessary - await setScrollTop(component, 20) + await setScrollTop(component, 20); expect(overlayWrapper.getBoundingClientRect().top).toBe( clientTopForLine(component, 5) - ) - overlayElement.style.height = 60 + 'px' - await overlayComponent.getNextUpdatePromise() + ); + overlayElement.style.height = 60 + 'px'; + await overlayComponent.getNextUpdatePromise(); expect(overlayWrapper.getBoundingClientRect().bottom).toBe( clientTopForLine(component, 4) - ) + ); // Does not flip the overlay vertically if it would overflow the top of the window - overlayElement.style.height = 80 + 'px' - await overlayComponent.getNextUpdatePromise() + overlayElement.style.height = 80 + 'px'; + await overlayComponent.getNextUpdatePromise(); expect(overlayWrapper.getBoundingClientRect().top).toBe( clientTopForLine(component, 5) - ) + ); // Can update overlay wrapper class decoration.setProperties({ type: 'overlay', item: overlayElement, class: 'b' - }) - await component.getNextUpdatePromise() - expect(overlayWrapper.classList.contains('a')).toBe(false) - expect(overlayWrapper.classList.contains('b')).toBe(true) + }); + await component.getNextUpdatePromise(); + expect(overlayWrapper.classList.contains('a')).toBe(false); + expect(overlayWrapper.classList.contains('b')).toBe(true); - decoration.setProperties({ type: 'overlay', item: overlayElement }) - await component.getNextUpdatePromise() - expect(overlayWrapper.classList.contains('b')).toBe(false) - }) + decoration.setProperties({ type: 'overlay', item: overlayElement }); + await component.getNextUpdatePromise(); + expect(overlayWrapper.classList.contains('b')).toBe(false); + }); it('does not attempt to avoid overflowing the window if `avoidOverflow` is false on the decoration', async () => { const { component, editor } = buildComponent({ width: 200, height: 100, attach: false - }) - const fakeWindow = attachFakeWindow(component) - const overlayElement = document.createElement('div') - overlayElement.style.width = '50px' - overlayElement.style.height = '50px' - overlayElement.style.margin = '3px' - overlayElement.style.backgroundColor = 'red' - const marker = editor.markScreenPosition([4, 25]) + }); + const fakeWindow = attachFakeWindow(component); + const overlayElement = document.createElement('div'); + overlayElement.style.width = '50px'; + overlayElement.style.height = '50px'; + overlayElement.style.margin = '3px'; + overlayElement.style.backgroundColor = 'red'; + const marker = editor.markScreenPosition([4, 25]); editor.decorateMarker(marker, { type: 'overlay', item: overlayElement, avoidOverflow: false - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); - await setScrollLeft(component, 30) + await setScrollLeft(component, 30); expect(overlayElement.getBoundingClientRect().right).toBeGreaterThan( fakeWindow.getBoundingClientRect().right - ) - await setScrollLeft(component, 280) + ); + await setScrollLeft(component, 280); expect(overlayElement.getBoundingClientRect().left).toBeLessThan( fakeWindow.getBoundingClientRect().left - ) - }) - }) + ); + }); + }); describe('custom gutter decorations', () => { it('arranges custom gutters based on their priority', async () => { - const { component, editor } = buildComponent() - editor.addGutter({ name: 'e', priority: 2 }) - editor.addGutter({ name: 'a', priority: -2 }) - editor.addGutter({ name: 'd', priority: 1 }) - editor.addGutter({ name: 'b', priority: -1 }) - editor.addGutter({ name: 'c', priority: 0 }) + const { component, editor } = buildComponent(); + editor.addGutter({ name: 'e', priority: 2 }); + editor.addGutter({ name: 'a', priority: -2 }); + editor.addGutter({ name: 'd', priority: 1 }); + editor.addGutter({ name: 'b', priority: -1 }); + editor.addGutter({ name: 'c', priority: 0 }); - await component.getNextUpdatePromise() + await component.getNextUpdatePromise(); const gutters = component.refs.gutterContainer.element.querySelectorAll( '.gutter' - ) + ); expect( Array.from(gutters).map(g => g.getAttribute('gutter-name')) - ).toEqual(['a', 'b', 'c', 'line-number', 'd', 'e']) - }) + ).toEqual(['a', 'b', 'c', 'line-number', 'd', 'e']); + }); it('adjusts the left edge of the scroll container based on changes to the gutter container width', async () => { - const { component, editor } = buildComponent() - const { scrollContainer, gutterContainer } = component.refs + const { component, editor } = buildComponent(); + const { scrollContainer, gutterContainer } = component.refs; - function checkScrollContainerLeft () { + function checkScrollContainerLeft() { expect(scrollContainer.getBoundingClientRect().left).toBe( Math.round(gutterContainer.element.getBoundingClientRect().right) - ) + ); } - checkScrollContainerLeft() - const gutterA = editor.addGutter({ name: 'a' }) - await component.getNextUpdatePromise() - checkScrollContainerLeft() + checkScrollContainerLeft(); + const gutterA = editor.addGutter({ name: 'a' }); + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); - const gutterB = editor.addGutter({ name: 'b' }) - await component.getNextUpdatePromise() - checkScrollContainerLeft() + const gutterB = editor.addGutter({ name: 'b' }); + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); - gutterA.getElement().style.width = 100 + 'px' - await component.getNextUpdatePromise() - checkScrollContainerLeft() + gutterA.getElement().style.width = 100 + 'px'; + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); - gutterA.hide() - await component.getNextUpdatePromise() - checkScrollContainerLeft() + gutterA.hide(); + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); - gutterA.show() - await component.getNextUpdatePromise() - checkScrollContainerLeft() + gutterA.show(); + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); - gutterA.destroy() - await component.getNextUpdatePromise() - checkScrollContainerLeft() + gutterA.destroy(); + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); - gutterB.destroy() - await component.getNextUpdatePromise() - checkScrollContainerLeft() - }) + gutterB.destroy(); + await component.getNextUpdatePromise(); + checkScrollContainerLeft(); + }); it('allows the element of custom gutters to be retrieved before being rendered in the editor component', async () => { - const { component, element, editor } = buildComponent() - const [lineNumberGutter] = editor.getGutters() - const gutterA = editor.addGutter({ name: 'a', priority: -1 }) - const gutterB = editor.addGutter({ name: 'b', priority: 1 }) + const { component, element, editor } = buildComponent(); + const [lineNumberGutter] = editor.getGutters(); + const gutterA = editor.addGutter({ name: 'a', priority: -1 }); + const gutterB = editor.addGutter({ name: 'b', priority: 1 }); - const lineNumberGutterElement = lineNumberGutter.getElement() - const gutterAElement = gutterA.getElement() - const gutterBElement = gutterB.getElement() + const lineNumberGutterElement = lineNumberGutter.getElement(); + const gutterAElement = gutterA.getElement(); + const gutterBElement = gutterB.getElement(); - await component.getNextUpdatePromise() + await component.getNextUpdatePromise(); - expect(element.contains(lineNumberGutterElement)).toBe(true) - expect(element.contains(gutterAElement)).toBe(true) - expect(element.contains(gutterBElement)).toBe(true) - }) + expect(element.contains(lineNumberGutterElement)).toBe(true); + expect(element.contains(gutterAElement)).toBe(true); + expect(element.contains(gutterBElement)).toBe(true); + }); it('can show and hide custom gutters', async () => { - const { component, editor } = buildComponent() - const gutterA = editor.addGutter({ name: 'a', priority: -1 }) - const gutterB = editor.addGutter({ name: 'b', priority: 1 }) - const gutterAElement = gutterA.getElement() - const gutterBElement = gutterB.getElement() + const { component, editor } = buildComponent(); + const gutterA = editor.addGutter({ name: 'a', priority: -1 }); + const gutterB = editor.addGutter({ name: 'b', priority: 1 }); + const gutterAElement = gutterA.getElement(); + const gutterBElement = gutterB.getElement(); - await component.getNextUpdatePromise() - expect(gutterAElement.style.display).toBe('') - expect(gutterBElement.style.display).toBe('') + await component.getNextUpdatePromise(); + expect(gutterAElement.style.display).toBe(''); + expect(gutterBElement.style.display).toBe(''); - gutterA.hide() - await component.getNextUpdatePromise() - expect(gutterAElement.style.display).toBe('none') - expect(gutterBElement.style.display).toBe('') + gutterA.hide(); + await component.getNextUpdatePromise(); + expect(gutterAElement.style.display).toBe('none'); + expect(gutterBElement.style.display).toBe(''); - gutterB.hide() - await component.getNextUpdatePromise() - expect(gutterAElement.style.display).toBe('none') - expect(gutterBElement.style.display).toBe('none') + gutterB.hide(); + await component.getNextUpdatePromise(); + expect(gutterAElement.style.display).toBe('none'); + expect(gutterBElement.style.display).toBe('none'); - gutterA.show() - await component.getNextUpdatePromise() - expect(gutterAElement.style.display).toBe('') - expect(gutterBElement.style.display).toBe('none') - }) + gutterA.show(); + await component.getNextUpdatePromise(); + expect(gutterAElement.style.display).toBe(''); + expect(gutterBElement.style.display).toBe('none'); + }); it('renders decorations in custom gutters', async () => { - const { component, element, editor } = buildComponent() - const gutterA = editor.addGutter({ name: 'a', priority: -1 }) - const gutterB = editor.addGutter({ name: 'b', priority: 1 }) - const marker1 = editor.markScreenRange([[2, 0], [4, 0]]) - const marker2 = editor.markScreenRange([[6, 0], [7, 0]]) - const marker3 = editor.markScreenRange([[9, 0], [12, 0]]) - const decorationElement1 = document.createElement('div') - const decorationElement2 = document.createElement('div') + const { component, element, editor } = buildComponent(); + const gutterA = editor.addGutter({ name: 'a', priority: -1 }); + const gutterB = editor.addGutter({ name: 'b', priority: 1 }); + const marker1 = editor.markScreenRange([[2, 0], [4, 0]]); + const marker2 = editor.markScreenRange([[6, 0], [7, 0]]); + const marker3 = editor.markScreenRange([[9, 0], [12, 0]]); + const decorationElement1 = document.createElement('div'); + const decorationElement2 = document.createElement('div'); // Packages may adopt this class name for decorations to be styled the same as line numbers - decorationElement2.className = 'line-number' + decorationElement2.className = 'line-number'; - const decoration1 = gutterA.decorateMarker(marker1, { class: 'a' }) + const decoration1 = gutterA.decorateMarker(marker1, { class: 'a' }); const decoration2 = gutterA.decorateMarker(marker2, { class: 'b', item: decorationElement1 - }) + }); const decoration3 = gutterB.decorateMarker(marker3, { item: decorationElement2 - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); let [ decorationNode1, decorationNode2 - ] = gutterA.getElement().firstChild.children - const [decorationNode3] = gutterB.getElement().firstChild.children + ] = gutterA.getElement().firstChild.children; + const [decorationNode3] = gutterB.getElement().firstChild.children; - expect(decorationNode1.className).toBe('decoration a') + expect(decorationNode1.className).toBe('decoration a'); expect(decorationNode1.getBoundingClientRect().top).toBe( clientTopForLine(component, 2) - ) + ); expect(decorationNode1.getBoundingClientRect().bottom).toBe( clientTopForLine(component, 5) - ) - expect(decorationNode1.firstChild).toBeNull() + ); + expect(decorationNode1.firstChild).toBeNull(); - expect(decorationNode2.className).toBe('decoration b') + expect(decorationNode2.className).toBe('decoration b'); expect(decorationNode2.getBoundingClientRect().top).toBe( clientTopForLine(component, 6) - ) + ); expect(decorationNode2.getBoundingClientRect().bottom).toBe( clientTopForLine(component, 8) - ) - expect(decorationNode2.firstChild).toBe(decorationElement1) - expect(decorationElement1.offsetHeight).toBe(decorationNode2.offsetHeight) - expect(decorationElement1.offsetWidth).toBe(decorationNode2.offsetWidth) + ); + expect(decorationNode2.firstChild).toBe(decorationElement1); + expect(decorationElement1.offsetHeight).toBe( + decorationNode2.offsetHeight + ); + expect(decorationElement1.offsetWidth).toBe(decorationNode2.offsetWidth); - expect(decorationNode3.className).toBe('decoration') + expect(decorationNode3.className).toBe('decoration'); expect(decorationNode3.getBoundingClientRect().top).toBe( clientTopForLine(component, 9) - ) + ); expect(decorationNode3.getBoundingClientRect().bottom).toBe( clientTopForLine(component, 12) + component.getLineHeight() - ) - expect(decorationNode3.firstChild).toBe(decorationElement2) - expect(decorationElement2.offsetHeight).toBe(decorationNode3.offsetHeight) - expect(decorationElement2.offsetWidth).toBe(decorationNode3.offsetWidth) + ); + expect(decorationNode3.firstChild).toBe(decorationElement2); + expect(decorationElement2.offsetHeight).toBe( + decorationNode3.offsetHeight + ); + expect(decorationElement2.offsetWidth).toBe(decorationNode3.offsetWidth); // Inline styled height is updated when line height changes element.style.fontSize = - parseInt(getComputedStyle(element).fontSize) + 10 + 'px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(decorationElement1.offsetHeight).toBe(decorationNode2.offsetHeight) - expect(decorationElement2.offsetHeight).toBe(decorationNode3.offsetHeight) + parseInt(getComputedStyle(element).fontSize) + 10 + 'px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(decorationElement1.offsetHeight).toBe( + decorationNode2.offsetHeight + ); + expect(decorationElement2.offsetHeight).toBe( + decorationNode3.offsetHeight + ); decoration1.setProperties({ type: 'gutter', gutterName: 'a', class: 'c', item: decorationElement1 - }) - decoration2.setProperties({ type: 'gutter', gutterName: 'a' }) - decoration3.destroy() - await component.getNextUpdatePromise() - expect(decorationNode1.className).toBe('decoration c') - expect(decorationNode1.firstChild).toBe(decorationElement1) - expect(decorationElement1.offsetHeight).toBe(decorationNode1.offsetHeight) - expect(decorationNode2.className).toBe('decoration') - expect(decorationNode2.firstChild).toBeNull() - expect(gutterB.getElement().firstChild.children.length).toBe(0) - }) + }); + decoration2.setProperties({ type: 'gutter', gutterName: 'a' }); + decoration3.destroy(); + await component.getNextUpdatePromise(); + expect(decorationNode1.className).toBe('decoration c'); + expect(decorationNode1.firstChild).toBe(decorationElement1); + expect(decorationElement1.offsetHeight).toBe( + decorationNode1.offsetHeight + ); + expect(decorationNode2.className).toBe('decoration'); + expect(decorationNode2.firstChild).toBeNull(); + expect(gutterB.getElement().firstChild.children.length).toBe(0); + }); it('renders custom line number gutters', async () => { - const { component, editor } = buildComponent() + const { component, editor } = buildComponent(); const gutterA = editor.addGutter({ name: 'a', priority: 1, type: 'line-number', class: 'a-number', labelFn: ({ bufferRow }) => `a - ${bufferRow}` - }) + }); const gutterB = editor.addGutter({ name: 'b', priority: 1, type: 'line-number', class: 'b-number', labelFn: ({ bufferRow }) => `b - ${bufferRow}` - }) - editor.setText('0000\n0001\n0002\n0003\n0004\n') + }); + editor.setText('0000\n0001\n0002\n0003\n0004\n'); - await component.getNextUpdatePromise() + await component.getNextUpdatePromise(); - const gutterAElement = gutterA.getElement() + const gutterAElement = gutterA.getElement(); const aNumbers = gutterAElement.querySelectorAll( 'div.line-number[data-buffer-row]' - ) - const aLabels = Array.from(aNumbers, e => e.textContent) + ); + const aLabels = Array.from(aNumbers, e => e.textContent); expect(aLabels).toEqual([ 'a - 0', 'a - 1', @@ -2872,13 +2888,13 @@ describe('TextEditorComponent', () => { 'a - 3', 'a - 4', 'a - 5' - ]) + ]); - const gutterBElement = gutterB.getElement() + const gutterBElement = gutterB.getElement(); const bNumbers = gutterBElement.querySelectorAll( 'div.line-number[data-buffer-row]' - ) - const bLabels = Array.from(bNumbers, e => e.textContent) + ); + const bLabels = Array.from(bNumbers, e => e.textContent); expect(bLabels).toEqual([ 'b - 0', 'b - 1', @@ -2886,8 +2902,8 @@ describe('TextEditorComponent', () => { 'b - 3', 'b - 4', 'b - 5' - ]) - }) + ]); + }); it("updates the editor's soft wrap width when a custom gutter's measurement is available", () => { const { component, element, editor } = buildComponent({ @@ -2895,33 +2911,33 @@ describe('TextEditorComponent', () => { width: 400, softWrapped: true, attach: false - }) - const gutter = editor.addGutter({ name: 'a', priority: 10 }) - gutter.getElement().style.width = '100px' + }); + const gutter = editor.addGutter({ name: 'a', priority: 10 }); + gutter.getElement().style.width = '100px'; - jasmine.attachToDOM(element) + jasmine.attachToDOM(element); - expect(component.getGutterContainerWidth()).toBe(100) + expect(component.getGutterContainerWidth()).toBe(100); // Component client width - gutter container width - vertical scrollbar width const softWrapColumn = Math.floor( (400 - 100 - component.getVerticalScrollbarWidth()) / component.getBaseCharacterWidth() - ) - expect(editor.getSoftWrapColumn()).toBe(softWrapColumn) - }) - }) + ); + expect(editor.getSoftWrapColumn()).toBe(softWrapColumn); + }); + }); describe('block decorations', () => { it('renders visible block decorations between the appropriate lines, refreshing and measuring them as needed', async () => { - const editor = buildEditor({ autoHeight: false }) + const editor = buildEditor({ autoHeight: false }); const { item: item1, decoration: decoration1 } = createBlockDecorationAtScreenRow(editor, 0, { height: 11, position: 'before' - }) + }); const { item: item2, decoration: decoration2 @@ -2929,20 +2945,20 @@ describe('TextEditorComponent', () => { height: 22, margin: 10, position: 'before' - }) + }); // render an editor that already contains some block decorations - const { component, element } = buildComponent({ editor, rowsPerTile: 3 }) + const { component, element } = buildComponent({ editor, rowsPerTile: 3 }); element.style.height = - 4 * component.getLineHeight() + horizontalScrollbarHeight + 'px' - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + 4 * component.getLineHeight() + horizontalScrollbarHeight + 'px'; + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item1) + getElementHeight(item2) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -2952,13 +2968,13 @@ describe('TextEditorComponent', () => { getElementHeight(item2) }, { tileStartRow: 3, height: 3 * component.getLineHeight() } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(item1.previousSibling).toBeNull() - expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 2)) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(item1.previousSibling).toBeNull(); + expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 2)); // add block decorations const { @@ -2967,24 +2983,24 @@ describe('TextEditorComponent', () => { } = createBlockDecorationAtScreenRow(editor, 4, { height: 33, position: 'before' - }) + }); const { item: item4 } = createBlockDecorationAtScreenRow(editor, 7, { height: 44, position: 'before' - }) + }); const { item: item5 } = createBlockDecorationAtScreenRow(editor, 7, { height: 50, marginBottom: 5, position: 'after' - }) + }); const { item: item6 } = createBlockDecorationAtScreenRow(editor, 12, { height: 60, marginTop: 6, position: 'after' - }) - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + }); + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item1) + @@ -2993,7 +3009,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3006,24 +3022,24 @@ describe('TextEditorComponent', () => { tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item3) } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(item1.previousSibling).toBeNull() - expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 2)) - expect(item3.previousSibling).toBe(lineNodeForScreenRow(component, 3)) - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 4)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(item1.previousSibling).toBeNull(); + expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 2)); + expect(item3.previousSibling).toBe(lineNodeForScreenRow(component, 3)); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 4)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(element.contains(item6)).toBe(false); // destroy decoration1 - decoration1.destroy() - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + decoration1.destroy(); + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3031,7 +3047,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3041,24 +3057,24 @@ describe('TextEditorComponent', () => { tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item3) } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 2)) - expect(item3.previousSibling).toBe(lineNodeForScreenRow(component, 3)) - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 4)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 2)); + expect(item3.previousSibling).toBe(lineNodeForScreenRow(component, 3)); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 4)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(element.contains(item6)).toBe(false); // move decoration2 and decoration3 - decoration2.getMarker().setHeadScreenPosition([1, 0]) - decoration3.getMarker().setHeadScreenPosition([0, 0]) - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + decoration2.getMarker().setHeadScreenPosition([1, 0]); + decoration3.getMarker().setHeadScreenPosition([0, 0]); + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3066,7 +3082,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3076,23 +3092,23 @@ describe('TextEditorComponent', () => { getElementHeight(item3) }, { tileStartRow: 3, height: 3 * component.getLineHeight() } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item3.previousSibling).toBeNull() - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item3.previousSibling).toBeNull(); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(element.contains(item6)).toBe(false); // change the text - editor.getBuffer().setTextInRange([[0, 5], [0, 5]], '\n\n') - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + editor.getBuffer().setTextInRange([[0, 5], [0, 5]], '\n\n'); + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3100,7 +3116,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3110,25 +3126,25 @@ describe('TextEditorComponent', () => { tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item2) } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBeNull() - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 3)) - expect(item3.previousSibling).toBeNull() - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(element.contains(item4)).toBe(false) - expect(element.contains(item5)).toBe(false) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBeNull(); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 3)); + expect(item3.previousSibling).toBeNull(); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(element.contains(item4)).toBe(false); + expect(element.contains(item5)).toBe(false); + expect(element.contains(item6)).toBe(false); // scroll past the first tile await setScrollTop( component, 3 * component.getLineHeight() + getElementHeight(item3) - ) - expect(component.getRenderedStartRow()).toBe(3) - expect(component.getRenderedEndRow()).toBe(12) + ); + expect(component.getRenderedStartRow()).toBe(3); + expect(component.getRenderedEndRow()).toBe(12); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3136,30 +3152,30 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item2) }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBeNull() - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 3)) - expect(element.contains(item3)).toBe(false) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 9)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 9)) - expect(element.contains(item6)).toBe(false) - await setScrollTop(component, 0) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBeNull(); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 3)); + expect(element.contains(item3)).toBe(false); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 9)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 9)); + expect(element.contains(item6)).toBe(false); + await setScrollTop(component, 0); // undo the previous change - editor.undo() - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + editor.undo(); + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3167,7 +3183,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3177,28 +3193,28 @@ describe('TextEditorComponent', () => { getElementHeight(item3) }, { tileStartRow: 3, height: 3 * component.getLineHeight() } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item3.previousSibling).toBeNull() - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item3.previousSibling).toBeNull(); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(element.contains(item6)).toBe(false); // invalidate decorations. this also tests a case where two decorations in // the same tile change their height without affecting the tile height nor // the content height. - item3.style.height = '22px' - item3.style.margin = '10px' - item2.style.height = '33px' - item2.style.margin = '0px' - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + item3.style.height = '22px'; + item3.style.margin = '10px'; + item2.style.height = '33px'; + item2.style.margin = '0px'; + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3206,7 +3222,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3216,39 +3232,39 @@ describe('TextEditorComponent', () => { getElementHeight(item3) }, { tileStartRow: 3, height: 3 * component.getLineHeight() } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item3.previousSibling).toBeNull() - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item3.previousSibling).toBeNull(); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(element.contains(item6)).toBe(false); // make decoration before row 0 as wide as the editor, and insert some text into it so that it wraps. - item3.style.height = '' - item3.style.margin = '' - item3.style.width = '' - item3.style.wordWrap = 'break-word' + item3.style.height = ''; + item3.style.margin = ''; + item3.style.width = ''; + item3.style.wordWrap = 'break-word'; const contentWidthInCharacters = Math.floor( component.getScrollContainerClientWidth() / component.getBaseCharacterWidth() - ) - item3.textContent = 'x'.repeat(contentWidthInCharacters * 2) - await component.getNextUpdatePromise() + ); + item3.textContent = 'x'.repeat(contentWidthInCharacters * 2); + await component.getNextUpdatePromise(); // make the editor wider, so that the decoration doesn't wrap anymore. component.element.style.width = component.getGutterContainerWidth() + component.getScrollContainerClientWidth() * 2 + verticalScrollbarWidth + - 'px' - await component.getNextUpdatePromise() - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(9) + 'px'; + await component.getNextUpdatePromise(); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(9); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3256,7 +3272,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3266,24 +3282,24 @@ describe('TextEditorComponent', () => { getElementHeight(item3) }, { tileStartRow: 3, height: 3 * component.getLineHeight() } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(9) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item3.previousSibling).toBeNull() - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(element.contains(item6)).toBe(false) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(9); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item3.previousSibling).toBeNull(); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(element.contains(item6)).toBe(false); // make the editor taller and wider and the same time, ensuring the number // of rendered lines is correct. - setEditorHeightInLines(component, 13) - await setEditorWidthInCharacters(component, 50) - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(13) + setEditorHeightInLines(component, 13); + await setEditorWidthInCharacters(component, 50); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(13); expect(component.getScrollHeight()).toBe( editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item2) + @@ -3291,7 +3307,7 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) - ) + ); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, @@ -3308,46 +3324,46 @@ describe('TextEditorComponent', () => { getElementHeight(item4) + getElementHeight(item5) } - ]) - assertLinesAreAlignedWithLineNumbers(component) - expect(queryOnScreenLineElements(element).length).toBe(13) - expect(element.contains(item1)).toBe(false) - expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)) - expect(item3.previousSibling).toBeNull() - expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)) - expect(item4.previousSibling).toBe(lineNodeForScreenRow(component, 6)) - expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)) - expect(item5.nextSibling).toBe(lineNodeForScreenRow(component, 8)) - expect(item6.previousSibling).toBe(lineNodeForScreenRow(component, 12)) - }) + ]); + assertLinesAreAlignedWithLineNumbers(component); + expect(queryOnScreenLineElements(element).length).toBe(13); + expect(element.contains(item1)).toBe(false); + expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 1)); + expect(item3.previousSibling).toBeNull(); + expect(item3.nextSibling).toBe(lineNodeForScreenRow(component, 0)); + expect(item4.previousSibling).toBe(lineNodeForScreenRow(component, 6)); + expect(item4.nextSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); + expect(item5.nextSibling).toBe(lineNodeForScreenRow(component, 8)); + expect(item6.previousSibling).toBe(lineNodeForScreenRow(component, 12)); + }); it('correctly positions line numbers when block decorations are located at tile boundaries', async () => { - const { editor, component } = buildComponent({ rowsPerTile: 3 }) + const { editor, component } = buildComponent({ rowsPerTile: 3 }); createBlockDecorationAtScreenRow(editor, 0, { height: 5, position: 'before' - }) + }); createBlockDecorationAtScreenRow(editor, 2, { height: 7, position: 'after' - }) + }); createBlockDecorationAtScreenRow(editor, 3, { height: 9, position: 'before' - }) + }); createBlockDecorationAtScreenRow(editor, 3, { height: 11, position: 'after' - }) + }); createBlockDecorationAtScreenRow(editor, 5, { height: 13, position: 'after' - }) + }); - await component.getNextUpdatePromise() - assertLinesAreAlignedWithLineNumbers(component) + await component.getNextUpdatePromise(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() + 5 + 7 }, { @@ -3355,199 +3371,199 @@ describe('TextEditorComponent', () => { height: 3 * component.getLineHeight() + 9 + 11 + 13 }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) - }) + ]); + }); it('removes block decorations whose markers have been destroyed', async () => { - const { editor, component } = buildComponent({ rowsPerTile: 3 }) + const { editor, component } = buildComponent({ rowsPerTile: 3 }); const { marker } = createBlockDecorationAtScreenRow(editor, 2, { height: 5, position: 'before' - }) - await component.getNextUpdatePromise() - assertLinesAreAlignedWithLineNumbers(component) + }); + await component.getNextUpdatePromise(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() + 5 }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) + ]); - marker.destroy() - await component.getNextUpdatePromise() - assertLinesAreAlignedWithLineNumbers(component) + marker.destroy(); + await component.getNextUpdatePromise(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) - }) + ]); + }); it('removes block decorations whose markers are invalidated, and adds them back when they become valid again', async () => { - const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }) + const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }); const { item, decoration, marker } = createBlockDecorationAtScreenRow( editor, 3, { height: 44, position: 'before', invalidate: 'touch' } - ) - const { component } = buildComponent({ editor, rowsPerTile: 3 }) + ); + const { component } = buildComponent({ editor, rowsPerTile: 3 }); // Invalidating the marker removes the block decoration. - editor.getBuffer().deleteRows(2, 3) - await component.getNextUpdatePromise() - expect(item.parentElement).toBeNull() - assertLinesAreAlignedWithLineNumbers(component) + editor.getBuffer().deleteRows(2, 3); + await component.getNextUpdatePromise(); + expect(item.parentElement).toBeNull(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) + ]); // Moving invalid markers is ignored. - marker.setScreenRange([[2, 0], [2, 0]]) - await component.getNextUpdatePromise() - expect(item.parentElement).toBeNull() - assertLinesAreAlignedWithLineNumbers(component) + marker.setScreenRange([[2, 0], [2, 0]]); + await component.getNextUpdatePromise(); + expect(item.parentElement).toBeNull(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) + ]); // Making the marker valid again adds back the block decoration. - marker.bufferMarker.valid = true - marker.setScreenRange([[3, 0], [3, 0]]) - await component.getNextUpdatePromise() - expect(item.nextSibling).toBe(lineNodeForScreenRow(component, 3)) - assertLinesAreAlignedWithLineNumbers(component) + marker.bufferMarker.valid = true; + marker.setScreenRange([[3, 0], [3, 0]]); + await component.getNextUpdatePromise(); + expect(item.nextSibling).toBe(lineNodeForScreenRow(component, 3)); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() + 44 }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) + ]); // Destroying the decoration and invalidating the marker at the same time // removes the block decoration correctly. - editor.getBuffer().deleteRows(2, 3) - decoration.destroy() - await component.getNextUpdatePromise() - expect(item.parentElement).toBeNull() - assertLinesAreAlignedWithLineNumbers(component) + editor.getBuffer().deleteRows(2, 3); + decoration.destroy(); + await component.getNextUpdatePromise(); + expect(item.parentElement).toBeNull(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) - }) + ]); + }); it('does not render block decorations when decorating invalid markers', async () => { - const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }) - const { component } = buildComponent({ editor, rowsPerTile: 3 }) + const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }); + const { component } = buildComponent({ editor, rowsPerTile: 3 }); - const marker = editor.markScreenPosition([3, 0], { invalidate: 'touch' }) - const item = document.createElement('div') - item.style.height = 30 + 'px' - item.style.width = 30 + 'px' - editor.getBuffer().deleteRows(1, 4) + const marker = editor.markScreenPosition([3, 0], { invalidate: 'touch' }); + const item = document.createElement('div'); + item.style.height = 30 + 'px'; + item.style.width = 30 + 'px'; + editor.getBuffer().deleteRows(1, 4); editor.decorateMarker(marker, { type: 'block', item, position: 'before' - }) - await component.getNextUpdatePromise() - expect(item.parentElement).toBeNull() - assertLinesAreAlignedWithLineNumbers(component) + }); + await component.getNextUpdatePromise(); + expect(item.parentElement).toBeNull(); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) + ]); // Making the marker valid again causes the corresponding block decoration // to be added to the editor. - marker.bufferMarker.valid = true - marker.setScreenRange([[2, 0], [2, 0]]) - await component.getNextUpdatePromise() - expect(item.nextSibling).toBe(lineNodeForScreenRow(component, 2)) - assertLinesAreAlignedWithLineNumbers(component) + marker.bufferMarker.valid = true; + marker.setScreenRange([[2, 0], [2, 0]]); + await component.getNextUpdatePromise(); + expect(item.nextSibling).toBe(lineNodeForScreenRow(component, 2)); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() + 30 }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) - }) + ]); + }); it('does not try to remeasure block decorations whose markers are invalid (regression)', async () => { - const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }) - const { component } = buildComponent({ editor, rowsPerTile: 3 }) + const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }); + const { component } = buildComponent({ editor, rowsPerTile: 3 }); createBlockDecorationAtScreenRow(editor, 2, { height: '12px', invalidate: 'touch' - }) - editor.getBuffer().deleteRows(0, 3) - await component.getNextUpdatePromise() + }); + editor.getBuffer().deleteRows(0, 3); + await component.getNextUpdatePromise(); // Trigger a re-measurement of all block decorations. - await setEditorWidthInCharacters(component, 20) - assertLinesAreAlignedWithLineNumbers(component) + await setEditorWidthInCharacters(component, 20); + assertLinesAreAlignedWithLineNumbers(component); assertTilesAreSizedAndPositionedCorrectly(component, [ { tileStartRow: 0, height: 3 * component.getLineHeight() }, { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } - ]) - }) + ]); + }); it('does not throw exceptions when destroying a block decoration inside a marker change event (regression)', async () => { - const { editor, component } = buildComponent({ rowsPerTile: 3 }) + const { editor, component } = buildComponent({ rowsPerTile: 3 }); - const marker = editor.markScreenPosition([2, 0]) + const marker = editor.markScreenPosition([2, 0]); marker.onDidChange(() => { - marker.destroy() - }) - const item = document.createElement('div') - editor.decorateMarker(marker, { type: 'block', item }) + marker.destroy(); + }); + const item = document.createElement('div'); + editor.decorateMarker(marker, { type: 'block', item }); - await component.getNextUpdatePromise() - expect(item.nextSibling).toBe(lineNodeForScreenRow(component, 2)) + await component.getNextUpdatePromise(); + expect(item.nextSibling).toBe(lineNodeForScreenRow(component, 2)); - marker.setBufferRange([[0, 0], [0, 0]]) - expect(marker.isDestroyed()).toBe(true) + marker.setBufferRange([[0, 0], [0, 0]]); + expect(marker.isDestroyed()).toBe(true); - await component.getNextUpdatePromise() - expect(item.parentElement).toBeNull() - }) + await component.getNextUpdatePromise(); + expect(item.parentElement).toBeNull(); + }); it('does not attempt to render block decorations located outside the visible range', async () => { const { editor, component } = buildComponent({ autoHeight: false, rowsPerTile: 2 - }) - await setEditorHeightInLines(component, 2) - expect(component.getRenderedStartRow()).toBe(0) - expect(component.getRenderedEndRow()).toBe(4) + }); + await setEditorHeightInLines(component, 2); + expect(component.getRenderedStartRow()).toBe(0); + expect(component.getRenderedEndRow()).toBe(4); const marker1 = editor.markScreenRange([[3, 0], [5, 0]], { reversed: false - }) - const item1 = document.createElement('div') - editor.decorateMarker(marker1, { type: 'block', item: item1 }) + }); + const item1 = document.createElement('div'); + editor.decorateMarker(marker1, { type: 'block', item: item1 }); const marker2 = editor.markScreenRange([[3, 0], [5, 0]], { reversed: true - }) - const item2 = document.createElement('div') - editor.decorateMarker(marker2, { type: 'block', item: item2 }) + }); + const item2 = document.createElement('div'); + editor.decorateMarker(marker2, { type: 'block', item: item2 }); - await component.getNextUpdatePromise() - expect(item1.parentElement).toBeNull() - expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 3)) + await component.getNextUpdatePromise(); + expect(item1.parentElement).toBeNull(); + expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 3)); - await setScrollTop(component, 4 * component.getLineHeight()) - expect(component.getRenderedStartRow()).toBe(4) - expect(component.getRenderedEndRow()).toBe(8) - expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 5)) - expect(item2.parentElement).toBeNull() - }) + await setScrollTop(component, 4 * component.getLineHeight()); + expect(component.getRenderedStartRow()).toBe(4); + expect(component.getRenderedEndRow()).toBe(8); + expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 5)); + expect(item2.parentElement).toBeNull(); + }); it('measures block decorations correctly when they are added before the component width has been updated', async () => { { @@ -3555,93 +3571,93 @@ describe('TextEditorComponent', () => { autoHeight: false, width: 500, attach: false - }) - const marker = editor.markScreenPosition([0, 0]) - const item = document.createElement('div') - item.textContent = 'block decoration' + }); + const marker = editor.markScreenPosition([0, 0]); + const item = document.createElement('div'); + item.textContent = 'block decoration'; editor.decorateMarker(marker, { type: 'block', item - }) + }); - jasmine.attachToDOM(element) - assertLinesAreAlignedWithLineNumbers(component) + jasmine.attachToDOM(element); + assertLinesAreAlignedWithLineNumbers(component); } { const { editor, component, element } = buildComponent({ autoHeight: false, width: 800 - }) - const marker = editor.markScreenPosition([0, 0]) - const item = document.createElement('div') - item.textContent = 'block decoration that could wrap many times' + }); + const marker = editor.markScreenPosition([0, 0]); + const item = document.createElement('div'); + item.textContent = 'block decoration that could wrap many times'; editor.decorateMarker(marker, { type: 'block', item - }) + }); - element.style.width = '50px' - await component.getNextUpdatePromise() - assertLinesAreAlignedWithLineNumbers(component) + element.style.width = '50px'; + await component.getNextUpdatePromise(); + assertLinesAreAlignedWithLineNumbers(component); } - }) + }); it('bases the width of the block decoration measurement area on the editor scroll width', async () => { const { component, element } = buildComponent({ autoHeight: false, width: 150 - }) + }); expect(component.refs.blockDecorationMeasurementArea.offsetWidth).toBe( component.getScrollWidth() - ) + ); - element.style.width = '800px' - await component.getNextUpdatePromise() + element.style.width = '800px'; + await component.getNextUpdatePromise(); expect(component.refs.blockDecorationMeasurementArea.offsetWidth).toBe( component.getScrollWidth() - ) - }) + ); + }); it('does not change the cursor position when clicking on a block decoration', async () => { - const { editor, component } = buildComponent() + const { editor, component } = buildComponent(); - const decorationElement = document.createElement('div') - decorationElement.textContent = 'Parent' - const childElement = document.createElement('div') - childElement.textContent = 'Child' - decorationElement.appendChild(childElement) - const marker = editor.markScreenPosition([4, 0]) - editor.decorateMarker(marker, { type: 'block', item: decorationElement }) - await component.getNextUpdatePromise() + const decorationElement = document.createElement('div'); + decorationElement.textContent = 'Parent'; + const childElement = document.createElement('div'); + childElement.textContent = 'Child'; + decorationElement.appendChild(childElement); + const marker = editor.markScreenPosition([4, 0]); + editor.decorateMarker(marker, { type: 'block', item: decorationElement }); + await component.getNextUpdatePromise(); - const decorationElementClientRect = decorationElement.getBoundingClientRect() + const decorationElementClientRect = decorationElement.getBoundingClientRect(); component.didMouseDownOnContent({ target: decorationElement, detail: 1, button: 0, clientX: decorationElementClientRect.left, clientY: decorationElementClientRect.top - }) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) + }); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); - const childElementClientRect = childElement.getBoundingClientRect() + const childElementClientRect = childElement.getBoundingClientRect(); component.didMouseDownOnContent({ target: childElement, detail: 1, button: 0, clientX: childElementClientRect.left, clientY: childElementClientRect.top - }) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - }) + }); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + }); it('uses the order property to control the order of block decorations at the same screen row', async () => { - const editor = buildEditor({ autoHeight: false }) - const { component, element } = buildComponent({ editor }) + const editor = buildEditor({ autoHeight: false }); + const { component, element } = buildComponent({ editor }); element.style.height = - 10 * component.getLineHeight() + horizontalScrollbarHeight + 'px' - await component.getNextUpdatePromise() + 10 * component.getLineHeight() + horizontalScrollbarHeight + 'px'; + await component.getNextUpdatePromise(); // Order parameters that differ from creation order; that collide; and that are not provided. const [beforeItems, beforeDecorations] = [ @@ -3657,13 +3673,16 @@ describe('TextEditorComponent', () => { height: 10, position: 'before', order - }) + }); }) - .reduce((lists, result) => { - lists[0].push(result.item) - lists[1].push(result.decoration) - return lists - }, [[], []]) + .reduce( + (lists, result) => { + lists[0].push(result.item); + lists[1].push(result.decoration); + return lists; + }, + [[], []] + ); const [afterItems] = [undefined, 1, 6, undefined, 6, 2] .map(order => { @@ -3671,342 +3690,345 @@ describe('TextEditorComponent', () => { height: 10, position: 'after', order - }) + }); }) - .reduce((lists, result) => { - lists[0].push(result.item) - lists[1].push(result.decoration) - return lists - }, [[], []]) + .reduce( + (lists, result) => { + lists[0].push(result.item); + lists[1].push(result.decoration); + return lists; + }, + [[], []] + ); - await component.getNextUpdatePromise() + await component.getNextUpdatePromise(); expect(beforeItems[4].previousSibling).toBe( lineNodeForScreenRow(component, 1) - ) - expect(beforeItems[4].nextSibling).toBe(beforeItems[1]) - expect(beforeItems[1].nextSibling).toBe(beforeItems[3]) - expect(beforeItems[3].nextSibling).toBe(beforeItems[0]) - expect(beforeItems[0].nextSibling).toBe(beforeItems[2]) - expect(beforeItems[2].nextSibling).toBe(beforeItems[5]) + ); + expect(beforeItems[4].nextSibling).toBe(beforeItems[1]); + expect(beforeItems[1].nextSibling).toBe(beforeItems[3]); + expect(beforeItems[3].nextSibling).toBe(beforeItems[0]); + expect(beforeItems[0].nextSibling).toBe(beforeItems[2]); + expect(beforeItems[2].nextSibling).toBe(beforeItems[5]); expect(beforeItems[5].nextSibling).toBe( lineNodeForScreenRow(component, 2) - ) + ); expect(afterItems[1].previousSibling).toBe( lineNodeForScreenRow(component, 2) - ) - expect(afterItems[1].nextSibling).toBe(afterItems[5]) - expect(afterItems[5].nextSibling).toBe(afterItems[2]) - expect(afterItems[2].nextSibling).toBe(afterItems[4]) - expect(afterItems[4].nextSibling).toBe(afterItems[0]) - expect(afterItems[0].nextSibling).toBe(afterItems[3]) + ); + expect(afterItems[1].nextSibling).toBe(afterItems[5]); + expect(afterItems[5].nextSibling).toBe(afterItems[2]); + expect(afterItems[2].nextSibling).toBe(afterItems[4]); + expect(afterItems[4].nextSibling).toBe(afterItems[0]); + expect(afterItems[0].nextSibling).toBe(afterItems[3]); // Create a decoration somewhere else and move it to the same screen row as the existing decorations const { item: later, decoration } = createBlockDecorationAtScreenRow( editor, 4, { height: 20, position: 'after', order: 3 } - ) - await component.getNextUpdatePromise() - expect(later.previousSibling).toBe(lineNodeForScreenRow(component, 4)) - expect(later.nextSibling).toBe(lineNodeForScreenRow(component, 5)) + ); + await component.getNextUpdatePromise(); + expect(later.previousSibling).toBe(lineNodeForScreenRow(component, 4)); + expect(later.nextSibling).toBe(lineNodeForScreenRow(component, 5)); - decoration.getMarker().setHeadScreenPosition([2, 0]) - await component.getNextUpdatePromise() - expect(later.previousSibling).toBe(afterItems[5]) - expect(later.nextSibling).toBe(afterItems[2]) + decoration.getMarker().setHeadScreenPosition([2, 0]); + await component.getNextUpdatePromise(); + expect(later.previousSibling).toBe(afterItems[5]); + expect(later.nextSibling).toBe(afterItems[2]); // Move a decoration away from its screen row and ensure the rest maintain their order - beforeDecorations[3].getMarker().setHeadScreenPosition([5, 0]) - await component.getNextUpdatePromise() + beforeDecorations[3].getMarker().setHeadScreenPosition([5, 0]); + await component.getNextUpdatePromise(); expect(beforeItems[3].previousSibling).toBe( lineNodeForScreenRow(component, 4) - ) + ); expect(beforeItems[3].nextSibling).toBe( lineNodeForScreenRow(component, 5) - ) + ); expect(beforeItems[4].previousSibling).toBe( lineNodeForScreenRow(component, 1) - ) - expect(beforeItems[4].nextSibling).toBe(beforeItems[1]) - expect(beforeItems[1].nextSibling).toBe(beforeItems[0]) - expect(beforeItems[0].nextSibling).toBe(beforeItems[2]) - expect(beforeItems[2].nextSibling).toBe(beforeItems[5]) + ); + expect(beforeItems[4].nextSibling).toBe(beforeItems[1]); + expect(beforeItems[1].nextSibling).toBe(beforeItems[0]); + expect(beforeItems[0].nextSibling).toBe(beforeItems[2]); + expect(beforeItems[2].nextSibling).toBe(beforeItems[5]); expect(beforeItems[5].nextSibling).toBe( lineNodeForScreenRow(component, 2) - ) - }) + ); + }); - function createBlockDecorationAtScreenRow ( + function createBlockDecorationAtScreenRow( editor, screenRow, { height, margin, marginTop, marginBottom, position, order, invalidate } ) { const marker = editor.markScreenPosition([screenRow, 0], { invalidate: invalidate || 'never' - }) - const item = document.createElement('div') - item.style.height = height + 'px' - if (margin != null) item.style.margin = margin + 'px' - if (marginTop != null) item.style.marginTop = marginTop + 'px' - if (marginBottom != null) item.style.marginBottom = marginBottom + 'px' - item.style.width = 30 + 'px' + }); + const item = document.createElement('div'); + item.style.height = height + 'px'; + if (margin != null) item.style.margin = margin + 'px'; + if (marginTop != null) item.style.marginTop = marginTop + 'px'; + if (marginBottom != null) item.style.marginBottom = marginBottom + 'px'; + item.style.width = 30 + 'px'; const decoration = editor.decorateMarker(marker, { type: 'block', item, position, order - }) - return { item, decoration, marker } + }); + return { item, decoration, marker }; } - function assertTilesAreSizedAndPositionedCorrectly (component, tiles) { - let top = 0 + function assertTilesAreSizedAndPositionedCorrectly(component, tiles) { + let top = 0; for (let tile of tiles) { const linesTileElement = lineNodeForScreenRow( component, tile.tileStartRow - ).parentElement - const linesTileBoundingRect = linesTileElement.getBoundingClientRect() - expect(linesTileBoundingRect.height).toBe(tile.height) - expect(linesTileBoundingRect.top).toBe(top) + ).parentElement; + const linesTileBoundingRect = linesTileElement.getBoundingClientRect(); + expect(linesTileBoundingRect.height).toBe(tile.height); + expect(linesTileBoundingRect.top).toBe(top); const lineNumbersTileElement = lineNumberNodeForScreenRow( component, tile.tileStartRow - ).parentElement - const lineNumbersTileBoundingRect = lineNumbersTileElement.getBoundingClientRect() - expect(lineNumbersTileBoundingRect.height).toBe(tile.height) - expect(lineNumbersTileBoundingRect.top).toBe(top) + ).parentElement; + const lineNumbersTileBoundingRect = lineNumbersTileElement.getBoundingClientRect(); + expect(lineNumbersTileBoundingRect.height).toBe(tile.height); + expect(lineNumbersTileBoundingRect.top).toBe(top); - top += tile.height + top += tile.height; } } - function assertLinesAreAlignedWithLineNumbers (component) { - const startRow = component.getRenderedStartRow() - const endRow = component.getRenderedEndRow() + function assertLinesAreAlignedWithLineNumbers(component) { + const startRow = component.getRenderedStartRow(); + const endRow = component.getRenderedEndRow(); for (let row = startRow; row < endRow; row++) { - const lineNode = lineNodeForScreenRow(component, row) - const lineNumberNode = lineNumberNodeForScreenRow(component, row) + const lineNode = lineNodeForScreenRow(component, row); + const lineNumberNode = lineNumberNodeForScreenRow(component, row); expect(lineNumberNode.getBoundingClientRect().top).toBe( lineNode.getBoundingClientRect().top - ) + ); } } - }) + }); describe('cursor decorations', () => { it('allows default cursors to be customized', async () => { - const { component, element, editor } = buildComponent() + const { component, element, editor } = buildComponent(); - editor.addCursorAtScreenPosition([1, 0]) + editor.addCursorAtScreenPosition([1, 0]); const [cursorMarker1, cursorMarker2] = editor .getCursors() - .map(c => c.getMarker()) + .map(c => c.getMarker()); - editor.decorateMarker(cursorMarker1, { type: 'cursor', class: 'a' }) + editor.decorateMarker(cursorMarker1, { type: 'cursor', class: 'a' }); editor.decorateMarker(cursorMarker2, { type: 'cursor', class: 'b', style: { visibility: 'hidden' } - }) + }); editor.decorateMarker(cursorMarker2, { type: 'cursor', style: { backgroundColor: 'red' } - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); - const cursorNodes = element.querySelectorAll('.cursor') - expect(cursorNodes.length).toBe(2) + const cursorNodes = element.querySelectorAll('.cursor'); + expect(cursorNodes.length).toBe(2); - expect(cursorNodes[0].className).toBe('cursor a') - expect(cursorNodes[1].className).toBe('cursor b') - expect(cursorNodes[1].style.visibility).toBe('hidden') - expect(cursorNodes[1].style.backgroundColor).toBe('red') - }) + expect(cursorNodes[0].className).toBe('cursor a'); + expect(cursorNodes[1].className).toBe('cursor b'); + expect(cursorNodes[1].style.visibility).toBe('hidden'); + expect(cursorNodes[1].style.backgroundColor).toBe('red'); + }); it('allows markers that are not actually associated with cursors to be decorated as if they were cursors', async () => { - const { component, element, editor } = buildComponent() - const marker = editor.markScreenPosition([1, 0]) - editor.decorateMarker(marker, { type: 'cursor', class: 'a' }) - await component.getNextUpdatePromise() + const { component, element, editor } = buildComponent(); + const marker = editor.markScreenPosition([1, 0]); + editor.decorateMarker(marker, { type: 'cursor', class: 'a' }); + await component.getNextUpdatePromise(); - const cursorNodes = element.querySelectorAll('.cursor') - expect(cursorNodes.length).toBe(2) - expect(cursorNodes[0].className).toBe('cursor') - expect(cursorNodes[1].className).toBe('cursor a') - }) - }) + const cursorNodes = element.querySelectorAll('.cursor'); + expect(cursorNodes.length).toBe(2); + expect(cursorNodes[0].className).toBe('cursor'); + expect(cursorNodes[1].className).toBe('cursor a'); + }); + }); describe('text decorations', () => { it('injects spans with custom class names and inline styles based on text decorations', async () => { - const { component, element, editor } = buildComponent({ rowsPerTile: 2 }) + const { component, element, editor } = buildComponent({ rowsPerTile: 2 }); - const markerLayer = editor.addMarkerLayer() - const marker1 = markerLayer.markBufferRange([[0, 2], [2, 7]]) - const marker2 = markerLayer.markBufferRange([[0, 2], [3, 8]]) - const marker3 = markerLayer.markBufferRange([[1, 13], [2, 7]]) + const markerLayer = editor.addMarkerLayer(); + const marker1 = markerLayer.markBufferRange([[0, 2], [2, 7]]); + const marker2 = markerLayer.markBufferRange([[0, 2], [3, 8]]); + const marker3 = markerLayer.markBufferRange([[1, 13], [2, 7]]); editor.decorateMarker(marker1, { type: 'text', class: 'a', style: { color: 'red' } - }) + }); editor.decorateMarker(marker2, { type: 'text', class: 'b', style: { color: 'blue' } - }) + }); editor.decorateMarker(marker3, { type: 'text', class: 'c', style: { color: 'green' } - }) - await component.getNextUpdatePromise() + }); + await component.getNextUpdatePromise(); expect(textContentOnRowMatchingSelector(component, 0, '.a')).toBe( editor.lineTextForScreenRow(0).slice(2) - ) + ); expect(textContentOnRowMatchingSelector(component, 1, '.a')).toBe( editor.lineTextForScreenRow(1) - ) + ); expect(textContentOnRowMatchingSelector(component, 2, '.a')).toBe( editor.lineTextForScreenRow(2).slice(0, 7) - ) - expect(textContentOnRowMatchingSelector(component, 3, '.a')).toBe('') + ); + expect(textContentOnRowMatchingSelector(component, 3, '.a')).toBe(''); expect(textContentOnRowMatchingSelector(component, 0, '.b')).toBe( editor.lineTextForScreenRow(0).slice(2) - ) + ); expect(textContentOnRowMatchingSelector(component, 1, '.b')).toBe( editor.lineTextForScreenRow(1) - ) + ); expect(textContentOnRowMatchingSelector(component, 2, '.b')).toBe( editor.lineTextForScreenRow(2) - ) + ); expect(textContentOnRowMatchingSelector(component, 3, '.b')).toBe( editor.lineTextForScreenRow(3).slice(0, 8) - ) + ); - expect(textContentOnRowMatchingSelector(component, 0, '.c')).toBe('') + expect(textContentOnRowMatchingSelector(component, 0, '.c')).toBe(''); expect(textContentOnRowMatchingSelector(component, 1, '.c')).toBe( editor.lineTextForScreenRow(1).slice(13) - ) + ); expect(textContentOnRowMatchingSelector(component, 2, '.c')).toBe( editor.lineTextForScreenRow(2).slice(0, 7) - ) - expect(textContentOnRowMatchingSelector(component, 3, '.c')).toBe('') + ); + expect(textContentOnRowMatchingSelector(component, 3, '.c')).toBe(''); for (const span of element.querySelectorAll('.a:not(.c)')) { - expect(span.style.color).toBe('red') + expect(span.style.color).toBe('red'); } for (const span of element.querySelectorAll('.b:not(.c):not(.a)')) { - expect(span.style.color).toBe('blue') + expect(span.style.color).toBe('blue'); } for (const span of element.querySelectorAll('.c')) { - expect(span.style.color).toBe('green') + expect(span.style.color).toBe('green'); } - marker2.setHeadScreenPosition([3, 10]) - await component.getNextUpdatePromise() + marker2.setHeadScreenPosition([3, 10]); + await component.getNextUpdatePromise(); expect(textContentOnRowMatchingSelector(component, 3, '.b')).toBe( editor.lineTextForScreenRow(3).slice(0, 10) - ) - }) + ); + }); it('correctly handles text decorations starting before the first rendered row and/or ending after the last rendered row', async () => { const { component, element, editor } = buildComponent({ autoHeight: false, rowsPerTile: 1 - }) - element.style.height = 4 * component.getLineHeight() + 'px' - await component.getNextUpdatePromise() - await setScrollTop(component, 4 * component.getLineHeight()) - expect(component.getRenderedStartRow()).toBe(4) - expect(component.getRenderedEndRow()).toBe(9) + }); + element.style.height = 4 * component.getLineHeight() + 'px'; + await component.getNextUpdatePromise(); + await setScrollTop(component, 4 * component.getLineHeight()); + expect(component.getRenderedStartRow()).toBe(4); + expect(component.getRenderedEndRow()).toBe(9); - const markerLayer = editor.addMarkerLayer() - const marker1 = markerLayer.markBufferRange([[0, 0], [4, 5]]) - const marker2 = markerLayer.markBufferRange([[7, 2], [10, 8]]) - editor.decorateMarker(marker1, { type: 'text', class: 'a' }) - editor.decorateMarker(marker2, { type: 'text', class: 'b' }) - await component.getNextUpdatePromise() + const markerLayer = editor.addMarkerLayer(); + const marker1 = markerLayer.markBufferRange([[0, 0], [4, 5]]); + const marker2 = markerLayer.markBufferRange([[7, 2], [10, 8]]); + editor.decorateMarker(marker1, { type: 'text', class: 'a' }); + editor.decorateMarker(marker2, { type: 'text', class: 'b' }); + await component.getNextUpdatePromise(); expect(textContentOnRowMatchingSelector(component, 4, '.a')).toBe( editor.lineTextForScreenRow(4).slice(0, 5) - ) - expect(textContentOnRowMatchingSelector(component, 5, '.a')).toBe('') - expect(textContentOnRowMatchingSelector(component, 6, '.a')).toBe('') - expect(textContentOnRowMatchingSelector(component, 7, '.a')).toBe('') - expect(textContentOnRowMatchingSelector(component, 8, '.a')).toBe('') + ); + expect(textContentOnRowMatchingSelector(component, 5, '.a')).toBe(''); + expect(textContentOnRowMatchingSelector(component, 6, '.a')).toBe(''); + expect(textContentOnRowMatchingSelector(component, 7, '.a')).toBe(''); + expect(textContentOnRowMatchingSelector(component, 8, '.a')).toBe(''); - expect(textContentOnRowMatchingSelector(component, 4, '.b')).toBe('') - expect(textContentOnRowMatchingSelector(component, 5, '.b')).toBe('') - expect(textContentOnRowMatchingSelector(component, 6, '.b')).toBe('') + expect(textContentOnRowMatchingSelector(component, 4, '.b')).toBe(''); + expect(textContentOnRowMatchingSelector(component, 5, '.b')).toBe(''); + expect(textContentOnRowMatchingSelector(component, 6, '.b')).toBe(''); expect(textContentOnRowMatchingSelector(component, 7, '.b')).toBe( editor.lineTextForScreenRow(7).slice(2) - ) + ); expect(textContentOnRowMatchingSelector(component, 8, '.b')).toBe( editor.lineTextForScreenRow(8) - ) - }) + ); + }); it('does not create empty spans when a text decoration contains a row but another text decoration starts or ends at the beginning of it', async () => { - const { component, element, editor } = buildComponent() - const markerLayer = editor.addMarkerLayer() - const marker1 = markerLayer.markBufferRange([[0, 2], [4, 0]]) - const marker2 = markerLayer.markBufferRange([[2, 0], [5, 8]]) - editor.decorateMarker(marker1, { type: 'text', class: 'a' }) - editor.decorateMarker(marker2, { type: 'text', class: 'b' }) - await component.getNextUpdatePromise() + const { component, element, editor } = buildComponent(); + const markerLayer = editor.addMarkerLayer(); + const marker1 = markerLayer.markBufferRange([[0, 2], [4, 0]]); + const marker2 = markerLayer.markBufferRange([[2, 0], [5, 8]]); + editor.decorateMarker(marker1, { type: 'text', class: 'a' }); + editor.decorateMarker(marker2, { type: 'text', class: 'b' }); + await component.getNextUpdatePromise(); for (const decorationSpan of element.querySelectorAll('.a, .b')) { - expect(decorationSpan.textContent).not.toBe('') + expect(decorationSpan.textContent).not.toBe(''); } - }) + }); it('does not create empty text nodes when a text decoration ends right after a text tag', async () => { - const { component, editor } = buildComponent() - const marker = editor.markBufferRange([[0, 8], [0, 29]]) - editor.decorateMarker(marker, { type: 'text', class: 'a' }) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent(); + const marker = editor.markBufferRange([[0, 8], [0, 29]]); + editor.decorateMarker(marker, { type: 'text', class: 'a' }); + await component.getNextUpdatePromise(); for (const textNode of textNodesForScreenRow(component, 0)) { - expect(textNode.textContent).not.toBe('') + expect(textNode.textContent).not.toBe(''); } - }) + }); - function textContentOnRowMatchingSelector (component, row, selector) { + function textContentOnRowMatchingSelector(component, row, selector) { return Array.from( lineNodeForScreenRow(component, row).querySelectorAll(selector) ) .map(span => span.textContent) - .join('') + .join(''); } - }) + }); describe('mouse input', () => { describe('on the lines', () => { describe('when there is only one cursor', () => { it('positions the cursor on single-click or when middle-clicking', async () => { for (const button of [0, 1]) { - const { component, editor } = buildComponent() - const { lineHeight } = component.measurements + const { component, editor } = buildComponent(); + const { lineHeight } = component.measurements; editor.setCursorScreenPosition([Infinity, Infinity], { autoscroll: false - }) + }); component.didMouseDownOnContent({ detail: 1, button, clientX: clientLeftForCharacter(component, 0, 0) - 1, clientY: clientTopForLine(component, 0) - 1 - }) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) + }); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); - const maxRow = editor.getLastScreenRow() + const maxRow = editor.getLastScreenRow(); editor.setCursorScreenPosition([Infinity, Infinity], { autoscroll: false - }) + }); component.didMouseDownOnContent({ detail: 1, button, @@ -4017,11 +4039,11 @@ describe('TextEditorComponent', () => { editor.lineLengthForScreenRow(maxRow) ) + 1, clientY: clientTopForLine(component, maxRow) + 1 - }) + }); expect(editor.getCursorScreenPosition()).toEqual([ maxRow, editor.lineLengthForScreenRow(maxRow) - ]) + ]); component.didMouseDownOnContent({ detail: 1, @@ -4033,11 +4055,11 @@ describe('TextEditorComponent', () => { editor.lineLengthForScreenRow(0) ) + 1, clientY: clientTopForLine(component, 0) + lineHeight / 2 - }) + }); expect(editor.getCursorScreenPosition()).toEqual([ 0, editor.lineLengthForScreenRow(0) - ]) + ]); component.didMouseDownOnContent({ detail: 1, @@ -4047,8 +4069,8 @@ describe('TextEditorComponent', () => { clientLeftForCharacter(component, 3, 1)) / 2, clientY: clientTopForLine(component, 1) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([1, 0]) + }); + expect(editor.getCursorScreenPosition()).toEqual([1, 0]); component.didMouseDownOnContent({ detail: 1, @@ -4058,8 +4080,8 @@ describe('TextEditorComponent', () => { clientLeftForCharacter(component, 3, 15)) / 2, clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 14]) + }); + expect(editor.getCursorScreenPosition()).toEqual([3, 14]); component.didMouseDownOnContent({ detail: 1, @@ -4070,11 +4092,11 @@ describe('TextEditorComponent', () => { 2 + 1, clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 15]) + }); + expect(editor.getCursorScreenPosition()).toEqual([3, 15]); - editor.getBuffer().setTextInRange([[3, 14], [3, 15]], '🐣') - await component.getNextUpdatePromise() + editor.getBuffer().setTextInRange([[3, 14], [3, 15]], '🐣'); + await component.getNextUpdatePromise(); component.didMouseDownOnContent({ detail: 1, @@ -4084,8 +4106,8 @@ describe('TextEditorComponent', () => { clientLeftForCharacter(component, 3, 16)) / 2, clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 14]) + }); + expect(editor.getCursorScreenPosition()).toEqual([3, 14]); component.didMouseDownOnContent({ detail: 1, @@ -4096,70 +4118,70 @@ describe('TextEditorComponent', () => { 2 + 1, clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 16]) + }); + expect(editor.getCursorScreenPosition()).toEqual([3, 16]); - expect(editor.testAutoscrollRequests).toEqual([]) + expect(editor.testAutoscrollRequests).toEqual([]); } - }) - }) + }); + }); describe('when the input is for the primary mouse button', () => { it('selects words on double-click', () => { - const { component, editor } = buildComponent() + const { component, editor } = buildComponent(); const { clientX, clientY } = clientPositionForCharacter( component, 1, 16 - ) + ); component.didMouseDownOnContent({ detail: 1, button: 0, clientX, clientY - }) + }); component.didMouseDownOnContent({ detail: 2, button: 0, clientX, clientY - }) - expect(editor.getSelectedScreenRange()).toEqual([[1, 13], [1, 21]]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + }); + expect(editor.getSelectedScreenRange()).toEqual([[1, 13], [1, 21]]); + expect(editor.testAutoscrollRequests).toEqual([]); + }); it('selects lines on triple-click', () => { - const { component, editor } = buildComponent() + const { component, editor } = buildComponent(); const { clientX, clientY } = clientPositionForCharacter( component, 1, 16 - ) + ); component.didMouseDownOnContent({ detail: 1, button: 0, clientX, clientY - }) + }); component.didMouseDownOnContent({ detail: 2, button: 0, clientX, clientY - }) + }); component.didMouseDownOnContent({ detail: 3, button: 0, clientX, clientY - }) - expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [2, 0]]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + }); + expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [2, 0]]); + expect(editor.testAutoscrollRequests).toEqual([]); + }); it('adds or removes cursors when holding cmd or ctrl when single-clicking', () => { - const { component, editor } = buildComponent({ platform: 'darwin' }) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0]]) + const { component, editor } = buildComponent({ platform: 'darwin' }); + expect(editor.getCursorScreenPositions()).toEqual([[0, 0]]); // add cursor at 1, 16 component.didMouseDownOnContent( @@ -4168,8 +4190,8 @@ describe('TextEditorComponent', () => { button: 0, metaKey: true }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + ); + expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]); // remove cursor at 0, 0 component.didMouseDownOnContent( @@ -4178,8 +4200,8 @@ describe('TextEditorComponent', () => { button: 0, metaKey: true }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]) + ); + expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]); // cmd-click cursor at 1, 16 but don't remove it because it's the last one component.didMouseDownOnContent( @@ -4188,25 +4210,27 @@ describe('TextEditorComponent', () => { button: 0, metaKey: true }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]) + ); + expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]); // cmd-clicking within a selection destroys it editor.addSelectionForScreenRange([[2, 10], [2, 15]], { autoscroll: false - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[1, 16], [1, 16]], [[2, 10], [2, 15]] - ]) + ]); component.didMouseDownOnContent( Object.assign(clientPositionForCharacter(component, 2, 13), { detail: 1, button: 0, metaKey: true }) - ) - expect(editor.getSelectedScreenRanges()).toEqual([[[1, 16], [1, 16]]]) + ); + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 16], [1, 16]] + ]); // ctrl-click does not add cursors on macOS, nor does it move the cursor component.didMouseDownOnContent( @@ -4215,28 +4239,30 @@ describe('TextEditorComponent', () => { button: 0, ctrlKey: true }) - ) - expect(editor.getSelectedScreenRanges()).toEqual([[[1, 16], [1, 16]]]) + ); + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 16], [1, 16]] + ]); // ctrl-click adds cursors on platforms *other* than macOS - component.props.platform = 'win32' - editor.setCursorScreenPosition([1, 4], { autoscroll: false }) + component.props.platform = 'win32'; + editor.setCursorScreenPosition([1, 4], { autoscroll: false }); component.didMouseDownOnContent( Object.assign(clientPositionForCharacter(component, 1, 16), { detail: 1, button: 0, ctrlKey: true }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[1, 4], [1, 16]]) + ); + expect(editor.getCursorScreenPositions()).toEqual([[1, 4], [1, 16]]); - expect(editor.testAutoscrollRequests).toEqual([]) - }) + expect(editor.testAutoscrollRequests).toEqual([]); + }); it('adds word selections when holding cmd or ctrl when double-clicking', () => { - const { component, editor } = buildComponent() - editor.addCursorAtScreenPosition([1, 16], { autoscroll: false }) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + const { component, editor } = buildComponent(); + editor.addCursorAtScreenPosition([1, 16], { autoscroll: false }); + expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]); component.didMouseDownOnContent( Object.assign(clientPositionForCharacter(component, 1, 16), { @@ -4244,64 +4270,64 @@ describe('TextEditorComponent', () => { button: 0, metaKey: true }) - ) + ); component.didMouseDownOnContent( Object.assign(clientPositionForCharacter(component, 1, 16), { detail: 2, button: 0, metaKey: true }) - ) + ); expect(editor.getSelectedScreenRanges()).toEqual([ [[0, 0], [0, 0]], [[1, 13], [1, 21]] - ]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + ]); + expect(editor.testAutoscrollRequests).toEqual([]); + }); it('adds line selections when holding cmd or ctrl when triple-clicking', () => { - const { component, editor } = buildComponent() - editor.addCursorAtScreenPosition([1, 16], { autoscroll: false }) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + const { component, editor } = buildComponent(); + editor.addCursorAtScreenPosition([1, 16], { autoscroll: false }); + expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]); const { clientX, clientY } = clientPositionForCharacter( component, 1, 16 - ) + ); component.didMouseDownOnContent({ detail: 1, button: 0, metaKey: true, clientX, clientY - }) + }); component.didMouseDownOnContent({ detail: 2, button: 0, metaKey: true, clientX, clientY - }) + }); component.didMouseDownOnContent({ detail: 3, button: 0, metaKey: true, clientX, clientY - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[0, 0], [0, 0]], [[1, 0], [2, 0]] - ]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + ]); + expect(editor.testAutoscrollRequests).toEqual([]); + }); it('expands the last selection on shift-click', () => { - const { component, editor } = buildComponent() + const { component, editor } = buildComponent(); - editor.setCursorScreenPosition([2, 18], { autoscroll: false }) + editor.setCursorScreenPosition([2, 18], { autoscroll: false }); component.didMouseDownOnContent( Object.assign( { @@ -4311,8 +4337,8 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 1, 4) ) - ) - expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [2, 18]]) + ); + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [2, 18]]); component.didMouseDownOnContent( Object.assign( @@ -4323,13 +4349,13 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 4, 4) ) - ) - expect(editor.getSelectedScreenRange()).toEqual([[2, 18], [4, 4]]) + ); + expect(editor.getSelectedScreenRange()).toEqual([[2, 18], [4, 4]]); // reorients word-wise selections to keep the word selected regardless of // where the subsequent shift-click occurs - editor.setCursorScreenPosition([2, 18], { autoscroll: false }) - editor.getLastSelection().selectWord({ autoscroll: false }) + editor.setCursorScreenPosition([2, 18], { autoscroll: false }); + editor.getLastSelection().selectWord({ autoscroll: false }); component.didMouseDownOnContent( Object.assign( { @@ -4339,8 +4365,8 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 1, 4) ) - ) - expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 20]]) + ); + expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 20]]); component.didMouseDownOnContent( Object.assign( @@ -4351,13 +4377,13 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 3, 11) ) - ) - expect(editor.getSelectedScreenRange()).toEqual([[2, 14], [3, 13]]) + ); + expect(editor.getSelectedScreenRange()).toEqual([[2, 14], [3, 13]]); // reorients line-wise selections to keep the line selected regardless of // where the subsequent shift-click occurs - editor.setCursorScreenPosition([2, 18], { autoscroll: false }) - editor.getLastSelection().selectLine(null, { autoscroll: false }) + editor.setCursorScreenPosition([2, 18], { autoscroll: false }); + editor.getLastSelection().selectLine(null, { autoscroll: false }); component.didMouseDownOnContent( Object.assign( { @@ -4367,8 +4393,8 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 1, 4) ) - ) - expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]) + ); + expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]); component.didMouseDownOnContent( Object.assign( @@ -4379,15 +4405,15 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 3, 11) ) - ) - expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [4, 0]]) + ); + expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [4, 0]]); - expect(editor.testAutoscrollRequests).toEqual([]) - }) + expect(editor.testAutoscrollRequests).toEqual([]); + }); it('expands the last selection on drag', () => { - const { component, editor } = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') + const { component, editor } = buildComponent(); + spyOn(component, 'handleMouseDragUntilMouseUp'); component.didMouseDownOnContent( Object.assign( @@ -4397,19 +4423,19 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 1, 4) ) - ) + ); { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0] - didDrag(clientPositionForCharacter(component, 8, 8)) - expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [8, 8]]) - didDrag(clientPositionForCharacter(component, 4, 8)) - expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [4, 8]]) - didStopDragging() - expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [4, 8]]) + } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + didDrag(clientPositionForCharacter(component, 8, 8)); + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [8, 8]]); + didDrag(clientPositionForCharacter(component, 4, 8)); + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [4, 8]]); + didStopDragging(); + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [4, 8]]); } // Click-drag a second selection... selections are not merged until the @@ -4423,35 +4449,37 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 8, 8) ) - ) + ); { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[1][0] - didDrag(clientPositionForCharacter(component, 2, 8)) + } = component.handleMouseDragUntilMouseUp.argsForCall[1][0]; + didDrag(clientPositionForCharacter(component, 2, 8)); expect(editor.getSelectedScreenRanges()).toEqual([ [[1, 4], [4, 8]], [[2, 8], [8, 8]] - ]) - didDrag(clientPositionForCharacter(component, 6, 8)) + ]); + didDrag(clientPositionForCharacter(component, 6, 8)); expect(editor.getSelectedScreenRanges()).toEqual([ [[1, 4], [4, 8]], [[6, 8], [8, 8]] - ]) - didDrag(clientPositionForCharacter(component, 2, 8)) + ]); + didDrag(clientPositionForCharacter(component, 2, 8)); expect(editor.getSelectedScreenRanges()).toEqual([ [[1, 4], [4, 8]], [[2, 8], [8, 8]] - ]) - didStopDragging() - expect(editor.getSelectedScreenRanges()).toEqual([[[1, 4], [8, 8]]]) + ]); + didStopDragging(); + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 4], [8, 8]] + ]); } - }) + }); it('expands the selection word-wise on double-click-drag', () => { - const { component, editor } = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') + const { component, editor } = buildComponent(); + spyOn(component, 'handleMouseDragUntilMouseUp'); component.didMouseDownOnContent( Object.assign( @@ -4461,7 +4489,7 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 1, 4) ) - ) + ); component.didMouseDownOnContent( Object.assign( { @@ -4470,90 +4498,90 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 1, 4) ) - ) + ); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[1][0] - didDrag(clientPositionForCharacter(component, 0, 8)) - expect(editor.getSelectedScreenRange()).toEqual([[0, 4], [1, 5]]) - didDrag(clientPositionForCharacter(component, 2, 10)) - expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 13]]) - }) + } = component.handleMouseDragUntilMouseUp.argsForCall[1][0]; + didDrag(clientPositionForCharacter(component, 0, 8)); + expect(editor.getSelectedScreenRange()).toEqual([[0, 4], [1, 5]]); + didDrag(clientPositionForCharacter(component, 2, 10)); + expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 13]]); + }); it('expands the selection line-wise on triple-click-drag', () => { - const { component, editor } = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') + const { component, editor } = buildComponent(); + spyOn(component, 'handleMouseDragUntilMouseUp'); const tripleClickPosition = clientPositionForCharacter( component, 2, 8 - ) + ); component.didMouseDownOnContent( Object.assign({ detail: 1, button: 0 }, tripleClickPosition) - ) + ); component.didMouseDownOnContent( Object.assign({ detail: 2, button: 0 }, tripleClickPosition) - ) + ); component.didMouseDownOnContent( Object.assign({ detail: 3, button: 0 }, tripleClickPosition) - ) + ); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[2][0] - didDrag(clientPositionForCharacter(component, 1, 8)) - expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]) - didDrag(clientPositionForCharacter(component, 4, 10)) - expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [5, 0]]) - }) + } = component.handleMouseDragUntilMouseUp.argsForCall[2][0]; + didDrag(clientPositionForCharacter(component, 1, 8)); + expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]); + didDrag(clientPositionForCharacter(component, 4, 10)); + expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [5, 0]]); + }); it('destroys folds when clicking on their fold markers', async () => { - const { component, element, editor } = buildComponent() - editor.foldBufferRow(1) - await component.getNextUpdatePromise() + const { component, element, editor } = buildComponent(); + editor.foldBufferRow(1); + await component.getNextUpdatePromise(); - const target = element.querySelector('.fold-marker') + const target = element.querySelector('.fold-marker'); const { clientX, clientY } = clientPositionForCharacter( component, 1, editor.lineLengthForScreenRow(1) - ) + ); component.didMouseDownOnContent({ detail: 1, button: 0, target, clientX, clientY - }) - expect(editor.isFoldedAtBufferRow(1)).toBe(false) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - }) + }); + expect(editor.isFoldedAtBufferRow(1)).toBe(false); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + }); it('autoscrolls the content when dragging near the edge of the scroll container', async () => { const { component } = buildComponent({ width: 200, height: 200 - }) - spyOn(component, 'handleMouseDragUntilMouseUp') + }); + spyOn(component, 'handleMouseDragUntilMouseUp'); - let previousScrollTop = 0 - let previousScrollLeft = 0 - function assertScrolledDownAndRight () { - expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop) - previousScrollTop = component.getScrollTop() + let previousScrollTop = 0; + let previousScrollLeft = 0; + function assertScrolledDownAndRight() { + expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop); + previousScrollTop = component.getScrollTop(); expect(component.getScrollLeft()).toBeGreaterThan( previousScrollLeft - ) - previousScrollLeft = component.getScrollLeft() + ); + previousScrollLeft = component.getScrollLeft(); } - function assertScrolledUpAndLeft () { - expect(component.getScrollTop()).toBeLessThan(previousScrollTop) - previousScrollTop = component.getScrollTop() - expect(component.getScrollLeft()).toBeLessThan(previousScrollLeft) - previousScrollLeft = component.getScrollLeft() + function assertScrolledUpAndLeft() { + expect(component.getScrollTop()).toBeLessThan(previousScrollTop); + previousScrollTop = component.getScrollTop(); + expect(component.getScrollLeft()).toBeLessThan(previousScrollLeft); + previousScrollLeft = component.getScrollLeft(); } component.didMouseDownOnContent({ @@ -4561,181 +4589,181 @@ describe('TextEditorComponent', () => { button: 0, clientX: 100, clientY: 100 - }) + }); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0] + } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; - didDrag({ clientX: 199, clientY: 199 }) - assertScrolledDownAndRight() - didDrag({ clientX: 199, clientY: 199 }) - assertScrolledDownAndRight() - didDrag({ clientX: 199, clientY: 199 }) - assertScrolledDownAndRight() + didDrag({ clientX: 199, clientY: 199 }); + assertScrolledDownAndRight(); + didDrag({ clientX: 199, clientY: 199 }); + assertScrolledDownAndRight(); + didDrag({ clientX: 199, clientY: 199 }); + assertScrolledDownAndRight(); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - assertScrolledUpAndLeft() + }); + assertScrolledUpAndLeft(); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - assertScrolledUpAndLeft() + }); + assertScrolledUpAndLeft(); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - assertScrolledUpAndLeft() + }); + assertScrolledUpAndLeft(); // Don't artificially update scroll position beyond possible values - expect(component.getScrollTop()).toBe(0) - expect(component.getScrollLeft()).toBe(0) + expect(component.getScrollTop()).toBe(0); + expect(component.getScrollLeft()).toBe(0); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - expect(component.getScrollTop()).toBe(0) - expect(component.getScrollLeft()).toBe(0) + }); + expect(component.getScrollTop()).toBe(0); + expect(component.getScrollLeft()).toBe(0); - const maxScrollTop = component.getMaxScrollTop() - const maxScrollLeft = component.getMaxScrollLeft() - setScrollTop(component, maxScrollTop) - await setScrollLeft(component, maxScrollLeft) + const maxScrollTop = component.getMaxScrollTop(); + const maxScrollLeft = component.getMaxScrollLeft(); + setScrollTop(component, maxScrollTop); + await setScrollLeft(component, maxScrollLeft); - didDrag({ clientX: 199, clientY: 199 }) - didDrag({ clientX: 199, clientY: 199 }) - didDrag({ clientX: 199, clientY: 199 }) - expect(component.getScrollTop()).toBe(maxScrollTop) - expect(component.getScrollLeft()).toBe(maxScrollLeft) - }) - }) + didDrag({ clientX: 199, clientY: 199 }); + didDrag({ clientX: 199, clientY: 199 }); + didDrag({ clientX: 199, clientY: 199 }); + expect(component.getScrollTop()).toBe(maxScrollTop); + expect(component.getScrollLeft()).toBe(maxScrollLeft); + }); + }); it('pastes the previously selected text when clicking the middle mouse button on Linux', async () => { - spyOn(electron.ipcRenderer, 'send').andCallFake(function ( + spyOn(electron.ipcRenderer, 'send').andCallFake(function( eventName, selectedText ) { if (eventName === 'write-text-to-selection-clipboard') { - clipboard.writeText(selectedText, 'selection') + clipboard.writeText(selectedText, 'selection'); } - }) + }); - const { component, editor } = buildComponent({ platform: 'linux' }) + const { component, editor } = buildComponent({ platform: 'linux' }); // Middle mouse pasting. - editor.setSelectedBufferRange([[1, 6], [1, 10]]) - await conditionPromise(() => TextEditor.clipboard.read() === 'sort') + editor.setSelectedBufferRange([[1, 6], [1, 10]]); + await conditionPromise(() => TextEditor.clipboard.read() === 'sort'); component.didMouseDownOnContent({ button: 1, clientX: clientLeftForCharacter(component, 10, 0), clientY: clientTopForLine(component, 10) - }) - expect(TextEditor.clipboard.read()).toBe('sort') - expect(editor.lineTextForBufferRow(10)).toBe('sort') - editor.undo() + }); + expect(TextEditor.clipboard.read()).toBe('sort'); + expect(editor.lineTextForBufferRow(10)).toBe('sort'); + editor.undo(); // Ensure left clicks don't interfere. - editor.setSelectedBufferRange([[1, 2], [1, 5]]) - await conditionPromise(() => TextEditor.clipboard.read() === 'var') + editor.setSelectedBufferRange([[1, 2], [1, 5]]); + await conditionPromise(() => TextEditor.clipboard.read() === 'var'); component.didMouseDownOnContent({ button: 0, detail: 1, clientX: clientLeftForCharacter(component, 10, 0), clientY: clientTopForLine(component, 10) - }) + }); component.didMouseDownOnContent({ button: 1, clientX: clientLeftForCharacter(component, 10, 0), clientY: clientTopForLine(component, 10) - }) - expect(editor.lineTextForBufferRow(10)).toBe('var') - }) + }); + expect(editor.lineTextForBufferRow(10)).toBe('var'); + }); it('does not paste into a read only editor when clicking the middle mouse button on Linux', async () => { - spyOn(electron.ipcRenderer, 'send').andCallFake(function ( + spyOn(electron.ipcRenderer, 'send').andCallFake(function( eventName, selectedText ) { if (eventName === 'write-text-to-selection-clipboard') { - clipboard.writeText(selectedText, 'selection') + clipboard.writeText(selectedText, 'selection'); } - }) + }); const { component, editor } = buildComponent({ platform: 'linux', readOnly: true - }) + }); // Select the word 'sort' on line 2 and copy to clipboard - editor.setSelectedBufferRange([[1, 6], [1, 10]]) - await conditionPromise(() => TextEditor.clipboard.read() === 'sort') + editor.setSelectedBufferRange([[1, 6], [1, 10]]); + await conditionPromise(() => TextEditor.clipboard.read() === 'sort'); // Middle-click in the buffer at line 11, column 1 component.didMouseDownOnContent({ button: 1, clientX: clientLeftForCharacter(component, 10, 0), clientY: clientTopForLine(component, 10) - }) + }); // Ensure that the correct text was copied but not pasted - expect(TextEditor.clipboard.read()).toBe('sort') - expect(editor.lineTextForBufferRow(10)).toBe('') - }) - }) + expect(TextEditor.clipboard.read()).toBe('sort'); + expect(editor.lineTextForBufferRow(10)).toBe(''); + }); + }); describe('on the line number gutter', () => { it('selects all buffer rows intersecting the clicked screen row when a line number is clicked', async () => { - const { component, editor } = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') - editor.setSoftWrapped(true) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent(); + spyOn(component, 'handleMouseDragUntilMouseUp'); + editor.setSoftWrapped(true); + await component.getNextUpdatePromise(); - await setEditorWidthInCharacters(component, 50) - editor.foldBufferRange([[4, Infinity], [7, Infinity]]) - await component.getNextUpdatePromise() + await setEditorWidthInCharacters(component, 50); + editor.foldBufferRange([[4, Infinity], [7, Infinity]]); + await component.getNextUpdatePromise(); // Selects entire buffer line when clicked screen line is soft-wrapped component.didMouseDownOnLineNumberGutter({ button: 0, clientY: clientTopForLine(component, 3) - }) - expect(editor.getSelectedScreenRange()).toEqual([[3, 0], [5, 0]]) - expect(editor.getSelectedBufferRange()).toEqual([[3, 0], [4, 0]]) + }); + expect(editor.getSelectedScreenRange()).toEqual([[3, 0], [5, 0]]); + expect(editor.getSelectedBufferRange()).toEqual([[3, 0], [4, 0]]); // Selects entire screen line, even if folds cause that selection to // span multiple buffer lines component.didMouseDownOnLineNumberGutter({ button: 0, clientY: clientTopForLine(component, 5) - }) - expect(editor.getSelectedScreenRange()).toEqual([[5, 0], [6, 0]]) - expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [8, 0]]) - }) + }); + expect(editor.getSelectedScreenRange()).toEqual([[5, 0], [6, 0]]); + expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [8, 0]]); + }); it('adds new selections when a line number is meta-clicked', async () => { - const { component, editor } = buildComponent() - editor.setSoftWrapped(true) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent(); + editor.setSoftWrapped(true); + await component.getNextUpdatePromise(); - await setEditorWidthInCharacters(component, 50) - editor.foldBufferRange([[4, Infinity], [7, Infinity]]) - await component.getNextUpdatePromise() + await setEditorWidthInCharacters(component, 50); + editor.foldBufferRange([[4, Infinity], [7, Infinity]]); + await component.getNextUpdatePromise(); // Selects entire buffer line when clicked screen line is soft-wrapped component.didMouseDownOnLineNumberGutter({ button: 0, metaKey: true, clientY: clientTopForLine(component, 3) - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[0, 0], [0, 0]], [[3, 0], [5, 0]] - ]) + ]); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 0], [0, 0]], [[3, 0], [4, 0]] - ]) + ]); // Selects entire screen line, even if folds cause that selection to // span multiple buffer lines @@ -4743,174 +4771,174 @@ describe('TextEditorComponent', () => { button: 0, metaKey: true, clientY: clientTopForLine(component, 5) - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[0, 0], [0, 0]], [[3, 0], [5, 0]], [[5, 0], [6, 0]] - ]) + ]); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 0], [0, 0]], [[3, 0], [4, 0]], [[4, 0], [8, 0]] - ]) - }) + ]); + }); it('expands the last selection when a line number is shift-clicked', async () => { - const { component, editor } = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') - editor.setSoftWrapped(true) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent(); + spyOn(component, 'handleMouseDragUntilMouseUp'); + editor.setSoftWrapped(true); + await component.getNextUpdatePromise(); - await setEditorWidthInCharacters(component, 50) - editor.foldBufferRange([[4, Infinity], [7, Infinity]]) - await component.getNextUpdatePromise() + await setEditorWidthInCharacters(component, 50); + editor.foldBufferRange([[4, Infinity], [7, Infinity]]); + await component.getNextUpdatePromise(); - editor.setSelectedScreenRange([[3, 4], [3, 8]]) - editor.addCursorAtScreenPosition([2, 10]) + editor.setSelectedScreenRange([[3, 4], [3, 8]]); + editor.addCursorAtScreenPosition([2, 10]); component.didMouseDownOnLineNumberGutter({ button: 0, shiftKey: true, clientY: clientTopForLine(component, 5) - }) + }); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 4], [3, 8]], [[2, 10], [8, 0]] - ]) + ]); // Original selection is preserved when shift-click-dragging const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0] + } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; didDrag({ clientY: clientTopForLine(component, 1) - }) + }); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 4], [3, 8]], [[1, 0], [2, 10]] - ]) + ]); didDrag({ clientY: clientTopForLine(component, 5) - }) + }); - didStopDragging() - expect(editor.getSelectedBufferRanges()).toEqual([[[2, 10], [8, 0]]]) - }) + didStopDragging(); + expect(editor.getSelectedBufferRanges()).toEqual([[[2, 10], [8, 0]]]); + }); it('expands the selection when dragging', async () => { - const { component, editor } = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') - editor.setSoftWrapped(true) - await component.getNextUpdatePromise() + const { component, editor } = buildComponent(); + spyOn(component, 'handleMouseDragUntilMouseUp'); + editor.setSoftWrapped(true); + await component.getNextUpdatePromise(); - await setEditorWidthInCharacters(component, 50) - editor.foldBufferRange([[4, Infinity], [7, Infinity]]) - await component.getNextUpdatePromise() + await setEditorWidthInCharacters(component, 50); + editor.foldBufferRange([[4, Infinity], [7, Infinity]]); + await component.getNextUpdatePromise(); - editor.setSelectedScreenRange([[3, 4], [3, 6]]) + editor.setSelectedScreenRange([[3, 4], [3, 6]]); component.didMouseDownOnLineNumberGutter({ button: 0, metaKey: true, clientY: clientTopForLine(component, 2) - }) + }); const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0] + } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; didDrag({ clientY: clientTopForLine(component, 1) - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[3, 4], [3, 6]], [[1, 0], [3, 0]] - ]) + ]); didDrag({ clientY: clientTopForLine(component, 5) - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[3, 4], [3, 6]], [[2, 0], [6, 0]] - ]) - expect(editor.isFoldedAtBufferRow(4)).toBe(true) + ]); + expect(editor.isFoldedAtBufferRow(4)).toBe(true); didDrag({ clientY: clientTopForLine(component, 3) - }) + }); expect(editor.getSelectedScreenRanges()).toEqual([ [[3, 4], [3, 6]], [[2, 0], [4, 4]] - ]) + ]); - didStopDragging() - expect(editor.getSelectedScreenRanges()).toEqual([[[2, 0], [4, 4]]]) - }) + didStopDragging(); + expect(editor.getSelectedScreenRanges()).toEqual([[[2, 0], [4, 4]]]); + }); it('toggles folding when clicking on the right icon of a foldable line number', async () => { - const { component, element, editor } = buildComponent() + const { component, element, editor } = buildComponent(); let target = element .querySelectorAll('.line-number')[1] - .querySelector('.icon-right') - expect(editor.isFoldedAtScreenRow(1)).toBe(false) + .querySelector('.icon-right'); + expect(editor.isFoldedAtScreenRow(1)).toBe(false); component.didMouseDownOnLineNumberGutter({ target, button: 0, clientY: clientTopForLine(component, 1) - }) - expect(editor.isFoldedAtScreenRow(1)).toBe(true) - await component.getNextUpdatePromise() + }); + expect(editor.isFoldedAtScreenRow(1)).toBe(true); + await component.getNextUpdatePromise(); component.didMouseDownOnLineNumberGutter({ target, button: 0, clientY: clientTopForLine(component, 1) - }) - await component.getNextUpdatePromise() - expect(editor.isFoldedAtScreenRow(1)).toBe(false) + }); + await component.getNextUpdatePromise(); + expect(editor.isFoldedAtScreenRow(1)).toBe(false); - editor.foldBufferRange([[5, 12], [5, 17]]) - await component.getNextUpdatePromise() - expect(editor.isFoldedAtScreenRow(5)).toBe(true) + editor.foldBufferRange([[5, 12], [5, 17]]); + await component.getNextUpdatePromise(); + expect(editor.isFoldedAtScreenRow(5)).toBe(true); target = element .querySelectorAll('.line-number')[4] - .querySelector('.icon-right') + .querySelector('.icon-right'); component.didMouseDownOnLineNumberGutter({ target, button: 0, clientY: clientTopForLine(component, 4) - }) - expect(editor.isFoldedAtScreenRow(4)).toBe(false) - }) + }); + expect(editor.isFoldedAtScreenRow(4)).toBe(false); + }); it('autoscrolls when dragging near the top or bottom of the gutter', async () => { const { component } = buildComponent({ width: 200, height: 200 - }) - spyOn(component, 'handleMouseDragUntilMouseUp') + }); + spyOn(component, 'handleMouseDragUntilMouseUp'); - let previousScrollTop = 0 - let previousScrollLeft = 0 - function assertScrolledDown () { - expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop) - previousScrollTop = component.getScrollTop() - expect(component.getScrollLeft()).toBe(previousScrollLeft) - previousScrollLeft = component.getScrollLeft() + let previousScrollTop = 0; + let previousScrollLeft = 0; + function assertScrolledDown() { + expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop); + previousScrollTop = component.getScrollTop(); + expect(component.getScrollLeft()).toBe(previousScrollLeft); + previousScrollLeft = component.getScrollLeft(); } - function assertScrolledUp () { - expect(component.getScrollTop()).toBeLessThan(previousScrollTop) - previousScrollTop = component.getScrollTop() - expect(component.getScrollLeft()).toBe(previousScrollLeft) - previousScrollLeft = component.getScrollLeft() + function assertScrolledUp() { + expect(component.getScrollTop()).toBeLessThan(previousScrollTop); + previousScrollTop = component.getScrollTop(); + expect(component.getScrollLeft()).toBe(previousScrollLeft); + previousScrollLeft = component.getScrollLeft(); } component.didMouseDownOnLineNumberGutter({ @@ -4918,792 +4946,793 @@ describe('TextEditorComponent', () => { button: 0, clientX: 0, clientY: 100 - }) + }); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0] - didDrag({ clientX: 199, clientY: 199 }) - assertScrolledDown() - didDrag({ clientX: 199, clientY: 199 }) - assertScrolledDown() - didDrag({ clientX: 199, clientY: 199 }) - assertScrolledDown() + } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + didDrag({ clientX: 199, clientY: 199 }); + assertScrolledDown(); + didDrag({ clientX: 199, clientY: 199 }); + assertScrolledDown(); + didDrag({ clientX: 199, clientY: 199 }); + assertScrolledDown(); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - assertScrolledUp() + }); + assertScrolledUp(); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - assertScrolledUp() + }); + assertScrolledUp(); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - assertScrolledUp() + }); + assertScrolledUp(); // Don't artificially update scroll measurements beyond the minimum or // maximum possible scroll positions - expect(component.getScrollTop()).toBe(0) - expect(component.getScrollLeft()).toBe(0) + expect(component.getScrollTop()).toBe(0); + expect(component.getScrollLeft()).toBe(0); didDrag({ clientX: component.getGutterContainerWidth() + 1, clientY: 1 - }) - expect(component.getScrollTop()).toBe(0) - expect(component.getScrollLeft()).toBe(0) + }); + expect(component.getScrollTop()).toBe(0); + expect(component.getScrollLeft()).toBe(0); - const maxScrollTop = component.getMaxScrollTop() - const maxScrollLeft = component.getMaxScrollLeft() - setScrollTop(component, maxScrollTop) - await setScrollLeft(component, maxScrollLeft) + const maxScrollTop = component.getMaxScrollTop(); + const maxScrollLeft = component.getMaxScrollLeft(); + setScrollTop(component, maxScrollTop); + await setScrollLeft(component, maxScrollLeft); - didDrag({ clientX: 199, clientY: 199 }) - didDrag({ clientX: 199, clientY: 199 }) - didDrag({ clientX: 199, clientY: 199 }) - expect(component.getScrollTop()).toBe(maxScrollTop) - expect(component.getScrollLeft()).toBe(maxScrollLeft) - }) - }) + didDrag({ clientX: 199, clientY: 199 }); + didDrag({ clientX: 199, clientY: 199 }); + didDrag({ clientX: 199, clientY: 199 }); + expect(component.getScrollTop()).toBe(maxScrollTop); + expect(component.getScrollLeft()).toBe(maxScrollLeft); + }); + }); describe('on the scrollbars', () => { it('delegates the mousedown events to the parent component unless the mousedown was on the actual scrollbar', async () => { - const { component, editor } = buildComponent({ height: 100 }) - await setEditorWidthInCharacters(component, 6) + const { component, editor } = buildComponent({ height: 100 }); + await setEditorWidthInCharacters(component, 6); - const verticalScrollbar = component.refs.verticalScrollbar - const horizontalScrollbar = component.refs.horizontalScrollbar + const verticalScrollbar = component.refs.verticalScrollbar; + const horizontalScrollbar = component.refs.horizontalScrollbar; const leftEdgeOfVerticalScrollbar = verticalScrollbar.element.getBoundingClientRect().right - - verticalScrollbarWidth + verticalScrollbarWidth; const topEdgeOfHorizontalScrollbar = horizontalScrollbar.element.getBoundingClientRect().bottom - - horizontalScrollbarHeight + horizontalScrollbarHeight; verticalScrollbar.didMouseDown({ button: 0, detail: 1, clientY: clientTopForLine(component, 4), clientX: leftEdgeOfVerticalScrollbar - }) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) + }); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); verticalScrollbar.didMouseDown({ button: 0, detail: 1, clientY: clientTopForLine(component, 4), clientX: leftEdgeOfVerticalScrollbar - 1 - }) - expect(editor.getCursorScreenPosition()).toEqual([4, 6]) + }); + expect(editor.getCursorScreenPosition()).toEqual([4, 6]); horizontalScrollbar.didMouseDown({ button: 0, detail: 1, clientY: topEdgeOfHorizontalScrollbar, clientX: component.refs.content.getBoundingClientRect().left - }) - expect(editor.getCursorScreenPosition()).toEqual([4, 6]) + }); + expect(editor.getCursorScreenPosition()).toEqual([4, 6]); horizontalScrollbar.didMouseDown({ button: 0, detail: 1, clientY: topEdgeOfHorizontalScrollbar - 1, clientX: component.refs.content.getBoundingClientRect().left - }) - expect(editor.getCursorScreenPosition()).toEqual([4, 0]) - }) - }) - }) + }); + expect(editor.getCursorScreenPosition()).toEqual([4, 0]); + }); + }); + }); describe('paste event', () => { it("prevents the browser's default processing for the event on Linux", () => { - const { component } = buildComponent({ platform: 'linux' }) - const event = { preventDefault: () => {} } - spyOn(event, 'preventDefault') + const { component } = buildComponent({ platform: 'linux' }); + const event = { preventDefault: () => {} }; + spyOn(event, 'preventDefault'); - component.didPaste(event) - expect(event.preventDefault).toHaveBeenCalled() - }) - }) + component.didPaste(event); + expect(event.preventDefault).toHaveBeenCalled(); + }); + }); describe('keyboard input', () => { it('handles inserted accented characters via the press-and-hold menu on macOS correctly', () => { const { editor, component } = buildComponent({ text: '', chromeVersion: 57 - }) - editor.insertText('x') - editor.setCursorBufferPosition([0, 1]) + }); + editor.insertText('x'); + editor.setCursorBufferPosition([0, 1]); // Simulate holding the A key to open the press-and-hold menu, // then closing it via ESC. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeyup({ code: 'KeyA' }) - component.didKeydown({ code: 'Escape' }) - component.didKeyup({ code: 'Escape' }) - expect(editor.getText()).toBe('xa') + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeyup({ code: 'KeyA' }); + component.didKeydown({ code: 'Escape' }); + component.didKeyup({ code: 'Escape' }); + expect(editor.getText()).toBe('xa'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - expect(editor.getText()).toBe('xaa') - editor.undo() - expect(editor.getText()).toBe('x') + }); + component.didKeyup({ code: 'KeyA' }); + expect(editor.getText()).toBe('xaa'); + editor.undo(); + expect(editor.getText()).toBe('x'); // Simulate holding the A key to open the press-and-hold menu, // then selecting an alternative by typing a number. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeyup({ code: 'KeyA' }) - component.didKeydown({ code: 'Digit2' }) - component.didKeyup({ code: 'Digit2' }) + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeyup({ code: 'KeyA' }); + component.didKeydown({ code: 'Digit2' }); + component.didKeyup({ code: 'Digit2' }); component.didTextInput({ data: 'á', stopPropagation: () => {}, preventDefault: () => {} - }) - expect(editor.getText()).toBe('xá') + }); + expect(editor.getText()).toBe('xá'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - expect(editor.getText()).toBe('xáa') - editor.undo() - expect(editor.getText()).toBe('x') + }); + component.didKeyup({ code: 'KeyA' }); + expect(editor.getText()).toBe('xáa'); + editor.undo(); + expect(editor.getText()).toBe('x'); // Simulate holding the A key to open the press-and-hold menu, // then selecting an alternative by clicking on it. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeyup({ code: 'KeyA' }) + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeyup({ code: 'KeyA' }); component.didTextInput({ data: 'á', stopPropagation: () => {}, preventDefault: () => {} - }) - expect(editor.getText()).toBe('xá') + }); + expect(editor.getText()).toBe('xá'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - expect(editor.getText()).toBe('xáa') - editor.undo() - expect(editor.getText()).toBe('x') + }); + component.didKeyup({ code: 'KeyA' }); + expect(editor.getText()).toBe('xáa'); + editor.undo(); + expect(editor.getText()).toBe('x'); // Simulate holding the A key to open the press-and-hold menu, // cycling through the alternatives with the arrows, then selecting one of them with Enter. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeyup({ code: 'KeyA' }) - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionStart({ data: '' }) - component.didCompositionUpdate({ data: 'à' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xà') - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionUpdate({ data: 'á' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xá') - component.didKeydown({ code: 'Enter' }) - component.didCompositionUpdate({ data: 'á' }) + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeyup({ code: 'KeyA' }); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionStart({ data: '' }); + component.didCompositionUpdate({ data: 'à' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xà'); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionUpdate({ data: 'á' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xá'); + component.didKeydown({ code: 'Enter' }); + component.didCompositionUpdate({ data: 'á' }); component.didTextInput({ data: 'á', stopPropagation: () => {}, preventDefault: () => {} - }) + }); component.didCompositionEnd({ data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput - }) - component.didKeyup({ code: 'Enter' }) - expect(editor.getText()).toBe('xá') + }); + component.didKeyup({ code: 'Enter' }); + expect(editor.getText()).toBe('xá'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - expect(editor.getText()).toBe('xáa') - editor.undo() - expect(editor.getText()).toBe('x') + }); + component.didKeyup({ code: 'KeyA' }); + expect(editor.getText()).toBe('xáa'); + editor.undo(); + expect(editor.getText()).toBe('x'); // Simulate holding the A key to open the press-and-hold menu, // cycling through the alternatives with the arrows, then closing it via ESC. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeyup({ code: 'KeyA' }) - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionStart({ data: '' }) - component.didCompositionUpdate({ data: 'à' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xà') - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionUpdate({ data: 'á' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xá') - component.didKeydown({ code: 'Escape' }) - component.didCompositionUpdate({ data: 'a' }) + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeyup({ code: 'KeyA' }); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionStart({ data: '' }); + component.didCompositionUpdate({ data: 'à' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xà'); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionUpdate({ data: 'á' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xá'); + component.didKeydown({ code: 'Escape' }); + component.didCompositionUpdate({ data: 'a' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) + }); component.didCompositionEnd({ data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput - }) - component.didKeyup({ code: 'Escape' }) - expect(editor.getText()).toBe('xa') + }); + component.didKeyup({ code: 'Escape' }); + expect(editor.getText()).toBe('xa'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - expect(editor.getText()).toBe('xaa') - editor.undo() - expect(editor.getText()).toBe('x') + }); + component.didKeyup({ code: 'KeyA' }); + expect(editor.getText()).toBe('xaa'); + editor.undo(); + expect(editor.getText()).toBe('x'); // Simulate pressing the O key and holding the A key to open the press-and-hold menu right before releasing the O key, // cycling through the alternatives with the arrows, then closing it via ESC. - component.didKeydown({ code: 'KeyO' }) - component.didKeypress({ code: 'KeyO' }) + component.didKeydown({ code: 'KeyO' }); + component.didKeypress({ code: 'KeyO' }); component.didTextInput({ data: 'o', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyO' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionStart({ data: '' }) - component.didCompositionUpdate({ data: 'à' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xoà') - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionUpdate({ data: 'á' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xoá') - component.didKeydown({ code: 'Escape' }) - component.didCompositionUpdate({ data: 'a' }) + }); + component.didKeyup({ code: 'KeyO' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionStart({ data: '' }); + component.didCompositionUpdate({ data: 'à' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xoà'); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionUpdate({ data: 'á' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xoá'); + component.didKeydown({ code: 'Escape' }); + component.didCompositionUpdate({ data: 'a' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) + }); component.didCompositionEnd({ data: 'a', target: component.refs.cursorsAndInput.refs.hiddenInput - }) - component.didKeyup({ code: 'Escape' }) - expect(editor.getText()).toBe('xoa') + }); + component.didKeyup({ code: 'Escape' }); + expect(editor.getText()).toBe('xoa'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - editor.undo() - expect(editor.getText()).toBe('x') + }); + component.didKeyup({ code: 'KeyA' }); + editor.undo(); + expect(editor.getText()).toBe('x'); // Simulate holding the A key to open the press-and-hold menu, // cycling through the alternatives with the arrows, then closing it by changing focus. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeydown({ code: 'KeyA' }) - component.didKeydown({ code: 'KeyA' }) - component.didKeyup({ code: 'KeyA' }) - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionStart({ data: '' }) - component.didCompositionUpdate({ data: 'à' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xà') - component.didKeydown({ code: 'ArrowRight' }) - component.didCompositionUpdate({ data: 'á' }) - component.didKeyup({ code: 'ArrowRight' }) - expect(editor.getText()).toBe('xá') - component.didCompositionUpdate({ data: 'á' }) + }); + component.didKeydown({ code: 'KeyA' }); + component.didKeydown({ code: 'KeyA' }); + component.didKeyup({ code: 'KeyA' }); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionStart({ data: '' }); + component.didCompositionUpdate({ data: 'à' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xà'); + component.didKeydown({ code: 'ArrowRight' }); + component.didCompositionUpdate({ data: 'á' }); + component.didKeyup({ code: 'ArrowRight' }); + expect(editor.getText()).toBe('xá'); + component.didCompositionUpdate({ data: 'á' }); component.didTextInput({ data: 'á', stopPropagation: () => {}, preventDefault: () => {} - }) + }); component.didCompositionEnd({ data: 'á', target: component.refs.cursorsAndInput.refs.hiddenInput - }) - expect(editor.getText()).toBe('xá') + }); + expect(editor.getText()).toBe('xá'); // Ensure another "a" can be typed correctly. - component.didKeydown({ code: 'KeyA' }) - component.didKeypress({ code: 'KeyA' }) + component.didKeydown({ code: 'KeyA' }); + component.didKeypress({ code: 'KeyA' }); component.didTextInput({ data: 'a', stopPropagation: () => {}, preventDefault: () => {} - }) - component.didKeyup({ code: 'KeyA' }) - expect(editor.getText()).toBe('xáa') - editor.undo() - expect(editor.getText()).toBe('x') - }) - }) + }); + component.didKeyup({ code: 'KeyA' }); + expect(editor.getText()).toBe('xáa'); + editor.undo(); + expect(editor.getText()).toBe('x'); + }); + }); describe('styling changes', () => { it('updates the rendered content based on new measurements when the font dimensions change', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 1, autoHeight: false - }) - await setEditorHeightInLines(component, 3) - editor.setCursorScreenPosition([1, 29], { autoscroll: false }) - await component.getNextUpdatePromise() + }); + await setEditorHeightInLines(component, 3); + editor.setCursorScreenPosition([1, 29], { autoscroll: false }); + await component.getNextUpdatePromise(); - let cursorNode = element.querySelector('.cursor') - const initialBaseCharacterWidth = editor.getDefaultCharWidth() - const initialDoubleCharacterWidth = editor.getDoubleWidthCharWidth() - const initialHalfCharacterWidth = editor.getHalfWidthCharWidth() - const initialKoreanCharacterWidth = editor.getKoreanCharWidth() - const initialRenderedLineCount = queryOnScreenLineElements(element).length - const initialFontSize = parseInt(getComputedStyle(element).fontSize) + let cursorNode = element.querySelector('.cursor'); + const initialBaseCharacterWidth = editor.getDefaultCharWidth(); + const initialDoubleCharacterWidth = editor.getDoubleWidthCharWidth(); + const initialHalfCharacterWidth = editor.getHalfWidthCharWidth(); + const initialKoreanCharacterWidth = editor.getKoreanCharWidth(); + const initialRenderedLineCount = queryOnScreenLineElements(element) + .length; + const initialFontSize = parseInt(getComputedStyle(element).fontSize); - expect(initialKoreanCharacterWidth).toBeDefined() - expect(initialDoubleCharacterWidth).toBeDefined() - expect(initialHalfCharacterWidth).toBeDefined() - expect(initialBaseCharacterWidth).toBeDefined() - expect(initialDoubleCharacterWidth).not.toBe(initialBaseCharacterWidth) - expect(initialHalfCharacterWidth).not.toBe(initialBaseCharacterWidth) - expect(initialKoreanCharacterWidth).not.toBe(initialBaseCharacterWidth) - verifyCursorPosition(component, cursorNode, 1, 29) + expect(initialKoreanCharacterWidth).toBeDefined(); + expect(initialDoubleCharacterWidth).toBeDefined(); + expect(initialHalfCharacterWidth).toBeDefined(); + expect(initialBaseCharacterWidth).toBeDefined(); + expect(initialDoubleCharacterWidth).not.toBe(initialBaseCharacterWidth); + expect(initialHalfCharacterWidth).not.toBe(initialBaseCharacterWidth); + expect(initialKoreanCharacterWidth).not.toBe(initialBaseCharacterWidth); + verifyCursorPosition(component, cursorNode, 1, 29); - element.style.fontSize = initialFontSize - 5 + 'px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() + element.style.fontSize = initialFontSize - 5 + 'px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); expect(editor.getDefaultCharWidth()).toBeLessThan( initialBaseCharacterWidth - ) + ); expect(editor.getDoubleWidthCharWidth()).toBeLessThan( initialDoubleCharacterWidth - ) + ); expect(editor.getHalfWidthCharWidth()).toBeLessThan( initialHalfCharacterWidth - ) + ); expect(editor.getKoreanCharWidth()).toBeLessThan( initialKoreanCharacterWidth - ) + ); expect(queryOnScreenLineElements(element).length).toBeGreaterThan( initialRenderedLineCount - ) - verifyCursorPosition(component, cursorNode, 1, 29) + ); + verifyCursorPosition(component, cursorNode, 1, 29); - element.style.fontSize = initialFontSize + 10 + 'px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() + element.style.fontSize = initialFontSize + 10 + 'px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); expect(editor.getDefaultCharWidth()).toBeGreaterThan( initialBaseCharacterWidth - ) + ); expect(editor.getDoubleWidthCharWidth()).toBeGreaterThan( initialDoubleCharacterWidth - ) + ); expect(editor.getHalfWidthCharWidth()).toBeGreaterThan( initialHalfCharacterWidth - ) + ); expect(editor.getKoreanCharWidth()).toBeGreaterThan( initialKoreanCharacterWidth - ) + ); expect(queryOnScreenLineElements(element).length).toBeLessThan( initialRenderedLineCount - ) - verifyCursorPosition(component, cursorNode, 1, 29) - }) + ); + verifyCursorPosition(component, cursorNode, 1, 29); + }); it('maintains the scrollTopRow and scrollLeftColumn when the font size changes', async () => { const { component, element } = buildComponent({ rowsPerTile: 1, autoHeight: false - }) - await setEditorHeightInLines(component, 3) - await setEditorWidthInCharacters(component, 20) - component.setScrollTopRow(4) - component.setScrollLeftColumn(10) - await component.getNextUpdatePromise() + }); + await setEditorHeightInLines(component, 3); + await setEditorWidthInCharacters(component, 20); + component.setScrollTopRow(4); + component.setScrollLeftColumn(10); + await component.getNextUpdatePromise(); - const initialFontSize = parseInt(getComputedStyle(element).fontSize) - element.style.fontSize = initialFontSize - 5 + 'px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(component.getScrollTopRow()).toBe(4) + const initialFontSize = parseInt(getComputedStyle(element).fontSize); + element.style.fontSize = initialFontSize - 5 + 'px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(component.getScrollTopRow()).toBe(4); - element.style.fontSize = initialFontSize + 5 + 'px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - expect(component.getScrollTopRow()).toBe(4) - }) + element.style.fontSize = initialFontSize + 5 + 'px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + expect(component.getScrollTopRow()).toBe(4); + }); it('gracefully handles the editor being hidden after a styling change', async () => { const { component, element } = buildComponent({ autoHeight: false - }) + }); element.style.fontSize = - parseInt(getComputedStyle(element).fontSize) + 5 + 'px' - TextEditor.didUpdateStyles() - element.style.display = 'none' - await component.getNextUpdatePromise() - }) + parseInt(getComputedStyle(element).fontSize) + 5 + 'px'; + TextEditor.didUpdateStyles(); + element.style.display = 'none'; + await component.getNextUpdatePromise(); + }); it('does not throw an exception when the editor is soft-wrapped and changing the font size changes also the longest screen line', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) + }); editor.setText( 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n' + 'eiusmod tempor incididunt ut labore et dolore magna' + 'aliqua. Ut enim ad minim veniam, quis nostrud exercitation' - ) - editor.setSoftWrapped(true) - await setEditorHeightInLines(component, 2) - await setEditorWidthInCharacters(component, 56) - await setScrollTop(component, 3 * component.getLineHeight()) + ); + editor.setSoftWrapped(true); + await setEditorHeightInLines(component, 2); + await setEditorWidthInCharacters(component, 56); + await setScrollTop(component, 3 * component.getLineHeight()); - element.style.fontSize = '20px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() - }) + element.style.fontSize = '20px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); + }); it('updates the width of the lines div based on the longest screen line', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 1, autoHeight: false - }) + }); editor.setText( 'Lorem ipsum dolor sit\n' + 'amet, consectetur adipisicing\n' + 'elit, sed do\n' + 'eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation' - ) - await setEditorHeightInLines(component, 2) + ); + await setEditorHeightInLines(component, 2); - element.style.fontSize = '20px' - TextEditor.didUpdateStyles() - await component.getNextUpdatePromise() + element.style.fontSize = '20px'; + TextEditor.didUpdateStyles(); + await component.getNextUpdatePromise(); // Capture the width of the lines before requesting the width of // longest line, because making that request forces a DOM update - const actualWidth = element.querySelector('.lines').style.width + const actualWidth = element.querySelector('.lines').style.width; const expectedWidth = Math.ceil( component.pixelPositionForScreenPosition(Point(3, Infinity)).left + component.getBaseCharacterWidth() - ) - expect(actualWidth).toBe(expectedWidth + 'px') - }) - }) + ); + expect(actualWidth).toBe(expectedWidth + 'px'); + }); + }); describe('synchronous updates', () => { - let editorElementWasUpdatedSynchronously + let editorElementWasUpdatedSynchronously; beforeEach(() => { editorElementWasUpdatedSynchronously = - TextEditorElement.prototype.updatedSynchronously - }) + TextEditorElement.prototype.updatedSynchronously; + }); afterEach(() => { TextEditorElement.prototype.setUpdatedSynchronously( editorElementWasUpdatedSynchronously - ) - }) + ); + }); it('updates synchronously when updatedSynchronously is true', () => { - const editor = buildEditor() + const editor = buildEditor(); const { element } = new TextEditorComponent({ model: editor, updatedSynchronously: true - }) - jasmine.attachToDOM(element) + }); + jasmine.attachToDOM(element); - editor.setText('Lorem ipsum dolor') + editor.setText('Lorem ipsum dolor'); expect( queryOnScreenLineElements(element).map(l => l.textContent) - ).toEqual([editor.lineTextForScreenRow(0)]) - }) + ).toEqual([editor.lineTextForScreenRow(0)]); + }); it('does not throw an exception on attachment when setting the soft-wrap column', () => { const { element, editor } = buildComponent({ width: 435, attach: false, updatedSynchronously: true - }) - editor.setSoftWrapped(true) - spyOn(window, 'onerror').andCallThrough() - jasmine.attachToDOM(element) // should not throw an exception - expect(window.onerror).not.toHaveBeenCalled() - }) + }); + editor.setSoftWrapped(true); + spyOn(window, 'onerror').andCallThrough(); + jasmine.attachToDOM(element); // should not throw an exception + expect(window.onerror).not.toHaveBeenCalled(); + }); it('updates synchronously when creating a component via TextEditor and TextEditorElement.prototype.updatedSynchronously is true', () => { - TextEditorElement.prototype.setUpdatedSynchronously(true) - const editor = buildEditor() - const element = editor.element - jasmine.attachToDOM(element) + TextEditorElement.prototype.setUpdatedSynchronously(true); + const editor = buildEditor(); + const element = editor.element; + jasmine.attachToDOM(element); - editor.setText('Lorem ipsum dolor') + editor.setText('Lorem ipsum dolor'); expect( queryOnScreenLineElements(element).map(l => l.textContent) - ).toEqual([editor.lineTextForScreenRow(0)]) - }) + ).toEqual([editor.lineTextForScreenRow(0)]); + }); it('measures dimensions synchronously when measureDimensions is called on the component', () => { - TextEditorElement.prototype.setUpdatedSynchronously(true) - const editor = buildEditor({ autoHeight: false }) - const element = editor.element - jasmine.attachToDOM(element) + TextEditorElement.prototype.setUpdatedSynchronously(true); + const editor = buildEditor({ autoHeight: false }); + const element = editor.element; + jasmine.attachToDOM(element); - element.style.height = '100px' - expect(element.component.getClientContainerHeight()).not.toBe(100) - element.component.measureDimensions() - expect(element.component.getClientContainerHeight()).toBe(100) - }) - }) + element.style.height = '100px'; + expect(element.component.getClientContainerHeight()).not.toBe(100); + element.component.measureDimensions(); + expect(element.component.getClientContainerHeight()).toBe(100); + }); + }); describe('pixelPositionForScreenPosition(point)', () => { it('returns the pixel position for the given point, regardless of whether or not it is currently on screen', async () => { const { component, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false - }) - await setEditorHeightInLines(component, 3) - await setScrollTop(component, 3 * component.getLineHeight()) + }); + await setEditorHeightInLines(component, 3); + await setScrollTop(component, 3 * component.getLineHeight()); - const { component: referenceComponent } = buildComponent() - const referenceContentRect = referenceComponent.refs.content.getBoundingClientRect() + const { component: referenceComponent } = buildComponent(); + const referenceContentRect = referenceComponent.refs.content.getBoundingClientRect(); { const { top, left } = component.pixelPositionForScreenPosition({ row: 0, column: 0 - }) + }); expect(top).toBe( clientTopForLine(referenceComponent, 0) - referenceContentRect.top - ) + ); expect(left).toBe( clientLeftForCharacter(referenceComponent, 0, 0) - referenceContentRect.left - ) + ); } { const { top, left } = component.pixelPositionForScreenPosition({ row: 0, column: 5 - }) + }); expect(top).toBe( clientTopForLine(referenceComponent, 0) - referenceContentRect.top - ) + ); expect(left).toBe( clientLeftForCharacter(referenceComponent, 0, 5) - referenceContentRect.left - ) + ); } { const { top, left } = component.pixelPositionForScreenPosition({ row: 12, column: 1 - }) + }); expect(top).toBe( clientTopForLine(referenceComponent, 12) - referenceContentRect.top - ) + ); expect(left).toBe( clientLeftForCharacter(referenceComponent, 12, 1) - referenceContentRect.left - ) + ); } // Measuring a currently rendered line while an autoscroll that causes // that line to go off-screen is in progress. { - editor.setCursorScreenPosition([10, 0]) + editor.setCursorScreenPosition([10, 0]); const { top, left } = component.pixelPositionForScreenPosition({ row: 3, column: 5 - }) + }); expect(top).toBe( clientTopForLine(referenceComponent, 3) - referenceContentRect.top - ) + ); expect(left).toBe( clientLeftForCharacter(referenceComponent, 3, 5) - referenceContentRect.left - ) + ); } - }) + }); it('does not get the component into an inconsistent state when the model has unflushed changes (regression)', async () => { const { component, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false, text: '' - }) - await setEditorHeightInLines(component, 10) + }); + await setEditorHeightInLines(component, 10); - const updatePromise = editor.getBuffer().append('hi\n') - component.screenPositionForPixelPosition({ top: 800, left: 1 }) - await updatePromise - }) + const updatePromise = editor.getBuffer().append('hi\n'); + component.screenPositionForPixelPosition({ top: 800, left: 1 }); + await updatePromise; + }); it('does not shift cursors downward or render off-screen content when measuring off-screen lines (regression)', async () => { const { component, element } = buildComponent({ rowsPerTile: 2, autoHeight: false - }) - await setEditorHeightInLines(component, 3) + }); + await setEditorHeightInLines(component, 3); component.pixelPositionForScreenPosition({ row: 12, column: 1 - }) + }); expect(element.querySelector('.cursor').getBoundingClientRect().top).toBe( component.refs.lineTiles.getBoundingClientRect().top - ) + ); expect( element.querySelector('.line[data-screen-row="12"]').style.visibility - ).toBe('hidden') + ).toBe('hidden'); // Ensure previously measured off screen lines don't have any weird // styling when they come on screen in the next frame - await setEditorHeightInLines(component, 13) + await setEditorHeightInLines(component, 13); const previouslyMeasuredLineElement = element.querySelector( '.line[data-screen-row="12"]' - ) - expect(previouslyMeasuredLineElement.style.display).toBe('') - expect(previouslyMeasuredLineElement.style.visibility).toBe('') - }) - }) + ); + expect(previouslyMeasuredLineElement.style.display).toBe(''); + expect(previouslyMeasuredLineElement.style.visibility).toBe(''); + }); + }); describe('screenPositionForPixelPosition', () => { it('returns the screen position for the given pixel position, regardless of whether or not it is currently on screen', async () => { const { component, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false - }) - await setEditorHeightInLines(component, 3) - await setScrollTop(component, 3 * component.getLineHeight()) - const { component: referenceComponent } = buildComponent() + }); + await setEditorHeightInLines(component, 3); + await setScrollTop(component, 3 * component.getLineHeight()); + const { component: referenceComponent } = buildComponent(); { const pixelPosition = referenceComponent.pixelPositionForScreenPosition( { row: 0, column: 0 } - ) - pixelPosition.top += component.getLineHeight() / 3 - pixelPosition.left += component.getBaseCharacterWidth() / 3 + ); + pixelPosition.top += component.getLineHeight() / 3; + pixelPosition.left += component.getBaseCharacterWidth() / 3; expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual( [0, 0] - ) + ); } { const pixelPosition = referenceComponent.pixelPositionForScreenPosition( { row: 0, column: 5 } - ) - pixelPosition.top += component.getLineHeight() / 3 - pixelPosition.left += component.getBaseCharacterWidth() / 3 + ); + pixelPosition.top += component.getLineHeight() / 3; + pixelPosition.left += component.getBaseCharacterWidth() / 3; expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual( [0, 5] - ) + ); } { const pixelPosition = referenceComponent.pixelPositionForScreenPosition( { row: 5, column: 7 } - ) - pixelPosition.top += component.getLineHeight() / 3 - pixelPosition.left += component.getBaseCharacterWidth() / 3 + ); + pixelPosition.top += component.getLineHeight() / 3; + pixelPosition.left += component.getBaseCharacterWidth() / 3; expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual( [5, 7] - ) + ); } { const pixelPosition = referenceComponent.pixelPositionForScreenPosition( { row: 12, column: 1 } - ) - pixelPosition.top += component.getLineHeight() / 3 - pixelPosition.left += component.getBaseCharacterWidth() / 3 + ); + pixelPosition.top += component.getLineHeight() / 3; + pixelPosition.left += component.getBaseCharacterWidth() / 3; expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual( [12, 1] - ) + ); } // Measuring a currently rendered line while an autoscroll that causes @@ -5711,237 +5740,239 @@ describe('TextEditorComponent', () => { { const pixelPosition = referenceComponent.pixelPositionForScreenPosition( { row: 3, column: 4 } - ) - pixelPosition.top += component.getLineHeight() / 3 - pixelPosition.left += component.getBaseCharacterWidth() / 3 - editor.setCursorBufferPosition([10, 0]) + ); + pixelPosition.top += component.getLineHeight() / 3; + pixelPosition.left += component.getBaseCharacterWidth() / 3; + editor.setCursorBufferPosition([10, 0]); expect(component.screenPositionForPixelPosition(pixelPosition)).toEqual( [3, 4] - ) + ); } - }) - }) + }); + }); describe('model methods that delegate to the component / element', () => { it('delegates setHeight and getHeight to the component', async () => { const { component, editor } = buildComponent({ autoHeight: false - }) - spyOn(Grim, 'deprecate') - expect(editor.getHeight()).toBe(component.getScrollContainerHeight()) - expect(Grim.deprecate.callCount).toBe(1) + }); + spyOn(Grim, 'deprecate'); + expect(editor.getHeight()).toBe(component.getScrollContainerHeight()); + expect(Grim.deprecate.callCount).toBe(1); - editor.setHeight(100) - await component.getNextUpdatePromise() - expect(component.getScrollContainerHeight()).toBe(100) - expect(Grim.deprecate.callCount).toBe(2) - }) + editor.setHeight(100); + await component.getNextUpdatePromise(); + expect(component.getScrollContainerHeight()).toBe(100); + expect(Grim.deprecate.callCount).toBe(2); + }); it('delegates setWidth and getWidth to the component', async () => { - const { component, editor } = buildComponent() - spyOn(Grim, 'deprecate') - expect(editor.getWidth()).toBe(component.getScrollContainerWidth()) - expect(Grim.deprecate.callCount).toBe(1) + const { component, editor } = buildComponent(); + spyOn(Grim, 'deprecate'); + expect(editor.getWidth()).toBe(component.getScrollContainerWidth()); + expect(Grim.deprecate.callCount).toBe(1); - editor.setWidth(100) - await component.getNextUpdatePromise() - expect(component.getScrollContainerWidth()).toBe(100) - expect(Grim.deprecate.callCount).toBe(2) - }) + editor.setWidth(100); + await component.getNextUpdatePromise(); + expect(component.getScrollContainerWidth()).toBe(100); + expect(Grim.deprecate.callCount).toBe(2); + }); it('delegates getFirstVisibleScreenRow, getLastVisibleScreenRow, and getVisibleRowRange to the component', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) - element.style.height = 4 * component.measurements.lineHeight + 'px' - await component.getNextUpdatePromise() - await setScrollTop(component, 5 * component.getLineHeight()) + }); + element.style.height = 4 * component.measurements.lineHeight + 'px'; + await component.getNextUpdatePromise(); + await setScrollTop(component, 5 * component.getLineHeight()); expect(editor.getFirstVisibleScreenRow()).toBe( component.getFirstVisibleRow() - ) + ); expect(editor.getLastVisibleScreenRow()).toBe( component.getLastVisibleRow() - ) + ); expect(editor.getVisibleRowRange()).toEqual([ component.getFirstVisibleRow(), component.getLastVisibleRow() - ]) - }) + ]); + }); it('assigns scrollTop on the component when calling setFirstVisibleScreenRow', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) + }); element.style.height = - 4 * component.measurements.lineHeight + horizontalScrollbarHeight + 'px' - await component.getNextUpdatePromise() + 4 * component.measurements.lineHeight + + horizontalScrollbarHeight + + 'px'; + await component.getNextUpdatePromise(); - expect(component.getMaxScrollTop() / component.getLineHeight()).toBe(9) + expect(component.getMaxScrollTop() / component.getLineHeight()).toBe(9); expect(component.refs.verticalScrollbar.element.scrollTop).toBe( 0 * component.getLineHeight() - ) + ); - editor.setFirstVisibleScreenRow(1) - expect(component.getFirstVisibleRow()).toBe(1) - await component.getNextUpdatePromise() + editor.setFirstVisibleScreenRow(1); + expect(component.getFirstVisibleRow()).toBe(1); + await component.getNextUpdatePromise(); expect(component.refs.verticalScrollbar.element.scrollTop).toBe( 1 * component.getLineHeight() - ) + ); - editor.setFirstVisibleScreenRow(5) - expect(component.getFirstVisibleRow()).toBe(5) - await component.getNextUpdatePromise() + editor.setFirstVisibleScreenRow(5); + expect(component.getFirstVisibleRow()).toBe(5); + await component.getNextUpdatePromise(); expect(component.refs.verticalScrollbar.element.scrollTop).toBe( 5 * component.getLineHeight() - ) + ); - editor.setFirstVisibleScreenRow(11) - expect(component.getFirstVisibleRow()).toBe(9) - await component.getNextUpdatePromise() + editor.setFirstVisibleScreenRow(11); + expect(component.getFirstVisibleRow()).toBe(9); + await component.getNextUpdatePromise(); expect(component.refs.verticalScrollbar.element.scrollTop).toBe( 9 * component.getLineHeight() - ) - }) + ); + }); it('delegates setFirstVisibleScreenColumn and getFirstVisibleScreenColumn to the component', async () => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false - }) - element.style.width = 30 * component.getBaseCharacterWidth() + 'px' - await component.getNextUpdatePromise() - expect(editor.getFirstVisibleScreenColumn()).toBe(0) - expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(0) + }); + element.style.width = 30 * component.getBaseCharacterWidth() + 'px'; + await component.getNextUpdatePromise(); + expect(editor.getFirstVisibleScreenColumn()).toBe(0); + expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(0); - setScrollLeft(component, 5.5 * component.getBaseCharacterWidth()) - expect(editor.getFirstVisibleScreenColumn()).toBe(5) - await component.getNextUpdatePromise() + setScrollLeft(component, 5.5 * component.getBaseCharacterWidth()); + expect(editor.getFirstVisibleScreenColumn()).toBe(5); + await component.getNextUpdatePromise(); expect(component.refs.horizontalScrollbar.element.scrollLeft).toBeCloseTo( 5.5 * component.getBaseCharacterWidth(), -1 - ) + ); - editor.setFirstVisibleScreenColumn(12) + editor.setFirstVisibleScreenColumn(12); expect(component.getScrollLeft()).toBeCloseTo( 12 * component.getBaseCharacterWidth(), -1 - ) - await component.getNextUpdatePromise() + ); + await component.getNextUpdatePromise(); expect(component.refs.horizontalScrollbar.element.scrollLeft).toBeCloseTo( 12 * component.getBaseCharacterWidth(), -1 - ) - }) - }) + ); + }); + }); describe('handleMouseDragUntilMouseUp', () => { it('repeatedly schedules `didDrag` calls on new animation frames after moving the mouse, and calls `didStopDragging` on mouseup', async () => { - const { component } = buildComponent() + const { component } = buildComponent(); - let dragEvents - let dragging = false + let dragEvents; + let dragging = false; component.handleMouseDragUntilMouseUp({ didDrag: event => { - dragging = true - dragEvents.push(event) + dragging = true; + dragEvents.push(event); }, didStopDragging: () => { - dragging = false + dragging = false; } - }) - expect(dragging).toBe(false) + }); + expect(dragging).toBe(false); - dragEvents = [] - const moveEvent1 = new MouseEvent('mousemove') - window.dispatchEvent(moveEvent1) - expect(dragging).toBe(false) - await getNextAnimationFramePromise() - expect(dragging).toBe(true) - expect(dragEvents).toEqual([moveEvent1]) - await getNextAnimationFramePromise() - expect(dragging).toBe(true) - expect(dragEvents).toEqual([moveEvent1, moveEvent1]) + dragEvents = []; + const moveEvent1 = new MouseEvent('mousemove'); + window.dispatchEvent(moveEvent1); + expect(dragging).toBe(false); + await getNextAnimationFramePromise(); + expect(dragging).toBe(true); + expect(dragEvents).toEqual([moveEvent1]); + await getNextAnimationFramePromise(); + expect(dragging).toBe(true); + expect(dragEvents).toEqual([moveEvent1, moveEvent1]); - dragEvents = [] - const moveEvent2 = new MouseEvent('mousemove') - window.dispatchEvent(moveEvent2) - expect(dragging).toBe(true) - expect(dragEvents).toEqual([]) - await getNextAnimationFramePromise() - expect(dragging).toBe(true) - expect(dragEvents).toEqual([moveEvent2]) - await getNextAnimationFramePromise() - expect(dragging).toBe(true) - expect(dragEvents).toEqual([moveEvent2, moveEvent2]) + dragEvents = []; + const moveEvent2 = new MouseEvent('mousemove'); + window.dispatchEvent(moveEvent2); + expect(dragging).toBe(true); + expect(dragEvents).toEqual([]); + await getNextAnimationFramePromise(); + expect(dragging).toBe(true); + expect(dragEvents).toEqual([moveEvent2]); + await getNextAnimationFramePromise(); + expect(dragging).toBe(true); + expect(dragEvents).toEqual([moveEvent2, moveEvent2]); - dragEvents = [] - window.dispatchEvent(new MouseEvent('mouseup')) - expect(dragging).toBe(false) - expect(dragEvents).toEqual([]) - window.dispatchEvent(new MouseEvent('mousemove')) - await getNextAnimationFramePromise() - expect(dragging).toBe(false) - expect(dragEvents).toEqual([]) - }) + dragEvents = []; + window.dispatchEvent(new MouseEvent('mouseup')); + expect(dragging).toBe(false); + expect(dragEvents).toEqual([]); + window.dispatchEvent(new MouseEvent('mousemove')); + await getNextAnimationFramePromise(); + expect(dragging).toBe(false); + expect(dragEvents).toEqual([]); + }); it('calls `didStopDragging` if the user interacts with the keyboard while dragging', async () => { - const { component, editor } = buildComponent() + const { component, editor } = buildComponent(); - let dragging = false - function startDragging () { + let dragging = false; + function startDragging() { component.handleMouseDragUntilMouseUp({ didDrag: event => { - dragging = true + dragging = true; }, didStopDragging: () => { - dragging = false + dragging = false; } - }) + }); } - startDragging() - window.dispatchEvent(new MouseEvent('mousemove')) - await getNextAnimationFramePromise() - expect(dragging).toBe(true) + startDragging(); + window.dispatchEvent(new MouseEvent('mousemove')); + await getNextAnimationFramePromise(); + expect(dragging).toBe(true); // Buffer changes don't cause dragging to be stopped. - editor.insertText('X') - expect(dragging).toBe(true) + editor.insertText('X'); + expect(dragging).toBe(true); // Keyboard interaction prevents users from dragging further. - component.didKeydown({ code: 'KeyX' }) - expect(dragging).toBe(false) + component.didKeydown({ code: 'KeyX' }); + expect(dragging).toBe(false); - window.dispatchEvent(new MouseEvent('mousemove')) - await getNextAnimationFramePromise() - expect(dragging).toBe(false) + window.dispatchEvent(new MouseEvent('mousemove')); + await getNextAnimationFramePromise(); + expect(dragging).toBe(false); // Pressing a modifier key does not terminate dragging, (to ensure we can add new selections with the mouse) - startDragging() - window.dispatchEvent(new MouseEvent('mousemove')) - await getNextAnimationFramePromise() - expect(dragging).toBe(true) - component.didKeydown({ key: 'Control' }) - component.didKeydown({ key: 'Alt' }) - component.didKeydown({ key: 'Shift' }) - component.didKeydown({ key: 'Meta' }) - expect(dragging).toBe(true) - }) + startDragging(); + window.dispatchEvent(new MouseEvent('mousemove')); + await getNextAnimationFramePromise(); + expect(dragging).toBe(true); + component.didKeydown({ key: 'Control' }); + component.didKeydown({ key: 'Alt' }); + component.didKeydown({ key: 'Shift' }); + component.didKeydown({ key: 'Meta' }); + expect(dragging).toBe(true); + }); - function getNextAnimationFramePromise () { - return new Promise(resolve => requestAnimationFrame(resolve)) + function getNextAnimationFramePromise() { + return new Promise(resolve => requestAnimationFrame(resolve)); } - }) -}) + }); +}); -function buildEditor (params = {}) { - const text = params.text != null ? params.text : SAMPLE_TEXT - const buffer = new TextBuffer({ text }) - const editorParams = { buffer, readOnly: params.readOnly } - if (params.height != null) params.autoHeight = false +function buildEditor(params = {}) { + const text = params.text != null ? params.text : SAMPLE_TEXT; + const buffer = new TextBuffer({ text }); + const editorParams = { buffer, readOnly: params.readOnly }; + if (params.height != null) params.autoHeight = false; for (const paramName of [ 'mini', 'autoHeight', @@ -5952,173 +5983,173 @@ function buildEditor (params = {}) { 'softWrapped', 'scrollSensitivity' ]) { - if (params[paramName] != null) editorParams[paramName] = params[paramName] + if (params[paramName] != null) editorParams[paramName] = params[paramName]; } - atom.grammars.autoAssignLanguageMode(buffer) - const editor = new TextEditor(editorParams) - editor.testAutoscrollRequests = [] + atom.grammars.autoAssignLanguageMode(buffer); + const editor = new TextEditor(editorParams); + editor.testAutoscrollRequests = []; editor.onDidRequestAutoscroll(request => { - editor.testAutoscrollRequests.push(request) - }) - editors.push(editor) - return editor + editor.testAutoscrollRequests.push(request); + }); + editors.push(editor); + return editor; } -function buildComponent (params = {}) { - const editor = params.editor || buildEditor(params) +function buildComponent(params = {}) { + const editor = params.editor || buildEditor(params); const component = new TextEditorComponent({ model: editor, rowsPerTile: params.rowsPerTile, updatedSynchronously: params.updatedSynchronously || false, platform: params.platform, chromeVersion: params.chromeVersion - }) - const { element } = component + }); + const { element } = component; if (!editor.getAutoHeight()) { - element.style.height = params.height ? params.height + 'px' : '600px' + element.style.height = params.height ? params.height + 'px' : '600px'; } if (!editor.getAutoWidth()) { - element.style.width = params.width ? params.width + 'px' : '800px' + element.style.width = params.width ? params.width + 'px' : '800px'; } - if (params.attach !== false) jasmine.attachToDOM(element) - return { component, element, editor } + if (params.attach !== false) jasmine.attachToDOM(element); + return { component, element, editor }; } -function getEditorWidthInBaseCharacters (component) { +function getEditorWidthInBaseCharacters(component) { return Math.round( component.getScrollContainerWidth() / component.getBaseCharacterWidth() - ) + ); } -async function setEditorHeightInLines (component, heightInLines) { +async function setEditorHeightInLines(component, heightInLines) { component.element.style.height = - component.getLineHeight() * heightInLines + 'px' - await component.getNextUpdatePromise() + component.getLineHeight() * heightInLines + 'px'; + await component.getNextUpdatePromise(); } -async function setEditorWidthInCharacters (component, widthInCharacters) { +async function setEditorWidthInCharacters(component, widthInCharacters) { component.element.style.width = component.getGutterContainerWidth() + widthInCharacters * component.measurements.baseCharacterWidth + verticalScrollbarWidth + - 'px' - await component.getNextUpdatePromise() + 'px'; + await component.getNextUpdatePromise(); } -function verifyCursorPosition (component, cursorNode, row, column) { - const rect = cursorNode.getBoundingClientRect() - expect(Math.round(rect.top)).toBe(clientTopForLine(component, row)) +function verifyCursorPosition(component, cursorNode, row, column) { + const rect = cursorNode.getBoundingClientRect(); + expect(Math.round(rect.top)).toBe(clientTopForLine(component, row)); expect(Math.round(rect.left)).toBe( Math.round(clientLeftForCharacter(component, row, column)) - ) + ); } -function clientTopForLine (component, row) { - return lineNodeForScreenRow(component, row).getBoundingClientRect().top +function clientTopForLine(component, row) { + return lineNodeForScreenRow(component, row).getBoundingClientRect().top; } -function clientLeftForCharacter (component, row, column) { - const textNodes = textNodesForScreenRow(component, row) - let textNodeStartColumn = 0 +function clientLeftForCharacter(component, row, column) { + const textNodes = textNodesForScreenRow(component, row); + let textNodeStartColumn = 0; for (const textNode of textNodes) { - const textNodeEndColumn = textNodeStartColumn + textNode.textContent.length + const textNodeEndColumn = textNodeStartColumn + textNode.textContent.length; if (column < textNodeEndColumn) { - const range = document.createRange() - range.setStart(textNode, column - textNodeStartColumn) - range.setEnd(textNode, column - textNodeStartColumn) - return range.getBoundingClientRect().left + const range = document.createRange(); + range.setStart(textNode, column - textNodeStartColumn); + range.setEnd(textNode, column - textNodeStartColumn); + return range.getBoundingClientRect().left; } - textNodeStartColumn = textNodeEndColumn + textNodeStartColumn = textNodeEndColumn; } - const lastTextNode = textNodes[textNodes.length - 1] - const range = document.createRange() - range.setStart(lastTextNode, 0) - range.setEnd(lastTextNode, lastTextNode.textContent.length) - return range.getBoundingClientRect().right + const lastTextNode = textNodes[textNodes.length - 1]; + const range = document.createRange(); + range.setStart(lastTextNode, 0); + range.setEnd(lastTextNode, lastTextNode.textContent.length); + return range.getBoundingClientRect().right; } -function clientPositionForCharacter (component, row, column) { +function clientPositionForCharacter(component, row, column) { return { clientX: clientLeftForCharacter(component, row, column), clientY: clientTopForLine(component, row) - } + }; } -function lineNumberNodeForScreenRow (component, row) { +function lineNumberNodeForScreenRow(component, row) { const gutterElement = - component.refs.gutterContainer.refs.lineNumberGutter.element - const tileStartRow = component.tileStartRowForRow(row) - const tileIndex = component.renderedTileStartRows.indexOf(tileStartRow) - return gutterElement.children[tileIndex + 1].children[row - tileStartRow] + component.refs.gutterContainer.refs.lineNumberGutter.element; + const tileStartRow = component.tileStartRowForRow(row); + const tileIndex = component.renderedTileStartRows.indexOf(tileStartRow); + return gutterElement.children[tileIndex + 1].children[row - tileStartRow]; } -function lineNodeForScreenRow (component, row) { - const renderedScreenLine = component.renderedScreenLineForRow(row) +function lineNodeForScreenRow(component, row) { + const renderedScreenLine = component.renderedScreenLineForRow(row); return component.lineComponentsByScreenLineId.get(renderedScreenLine.id) - .element + .element; } -function textNodesForScreenRow (component, row) { - const screenLine = component.renderedScreenLineForRow(row) - return component.lineComponentsByScreenLineId.get(screenLine.id).textNodes +function textNodesForScreenRow(component, row) { + const screenLine = component.renderedScreenLineForRow(row); + return component.lineComponentsByScreenLineId.get(screenLine.id).textNodes; } -function setScrollTop (component, scrollTop) { - component.setScrollTop(scrollTop) - component.scheduleUpdate() - return component.getNextUpdatePromise() +function setScrollTop(component, scrollTop) { + component.setScrollTop(scrollTop); + component.scheduleUpdate(); + return component.getNextUpdatePromise(); } -function setScrollLeft (component, scrollLeft) { - component.setScrollLeft(scrollLeft) - component.scheduleUpdate() - return component.getNextUpdatePromise() +function setScrollLeft(component, scrollLeft) { + component.setScrollLeft(scrollLeft); + component.scheduleUpdate(); + return component.getNextUpdatePromise(); } -function getHorizontalScrollbarHeight (component) { - const element = component.refs.horizontalScrollbar.element - return element.offsetHeight - element.clientHeight +function getHorizontalScrollbarHeight(component) { + const element = component.refs.horizontalScrollbar.element; + return element.offsetHeight - element.clientHeight; } -function getVerticalScrollbarWidth (component) { - const element = component.refs.verticalScrollbar.element - return element.offsetWidth - element.clientWidth +function getVerticalScrollbarWidth(component) { + const element = component.refs.verticalScrollbar.element; + return element.offsetWidth - element.clientWidth; } -function assertDocumentFocused () { +function assertDocumentFocused() { if (!document.hasFocus()) { - throw new Error('The document needs to be focused to run this test') + throw new Error('The document needs to be focused to run this test'); } } -function getElementHeight (element) { - const topRuler = document.createElement('div') - const bottomRuler = document.createElement('div') - let height +function getElementHeight(element) { + const topRuler = document.createElement('div'); + const bottomRuler = document.createElement('div'); + let height; if (document.body.contains(element)) { - element.parentElement.insertBefore(topRuler, element) - element.parentElement.insertBefore(bottomRuler, element.nextSibling) - height = bottomRuler.offsetTop - topRuler.offsetTop + element.parentElement.insertBefore(topRuler, element); + element.parentElement.insertBefore(bottomRuler, element.nextSibling); + height = bottomRuler.offsetTop - topRuler.offsetTop; } else { - jasmine.attachToDOM(topRuler) - jasmine.attachToDOM(element) - jasmine.attachToDOM(bottomRuler) - height = bottomRuler.offsetTop - topRuler.offsetTop - element.remove() + jasmine.attachToDOM(topRuler); + jasmine.attachToDOM(element); + jasmine.attachToDOM(bottomRuler); + height = bottomRuler.offsetTop - topRuler.offsetTop; + element.remove(); } - topRuler.remove() - bottomRuler.remove() - return height + topRuler.remove(); + bottomRuler.remove(); + return height; } -function queryOnScreenLineNumberElements (element) { - return Array.from(element.querySelectorAll('.line-number:not(.dummy)')) +function queryOnScreenLineNumberElements(element) { + return Array.from(element.querySelectorAll('.line-number:not(.dummy)')); } -function queryOnScreenLineElements (element) { +function queryOnScreenLineElements(element) { return Array.from( element.querySelectorAll('.line:not(.dummy):not([data-off-screen])') - ) + ); } diff --git a/spec/text-editor-element-spec.js b/spec/text-editor-element-spec.js index 0d3f23c04..b370abfe7 100644 --- a/spec/text-editor-element-spec.js +++ b/spec/text-editor-element-spec.js @@ -1,513 +1,515 @@ -const TextEditor = require('../src/text-editor') -const TextEditorElement = require('../src/text-editor-element') +const TextEditor = require('../src/text-editor'); +const TextEditorElement = require('../src/text-editor-element'); describe('TextEditorElement', () => { - let jasmineContent + let jasmineContent; beforeEach(() => { - jasmineContent = document.body.querySelector('#jasmine-content') + jasmineContent = document.body.querySelector('#jasmine-content'); // Force scrollbars to be visible regardless of local system configuration - const scrollbarStyle = document.createElement('style') + const scrollbarStyle = document.createElement('style'); scrollbarStyle.textContent = - 'atom-text-editor ::-webkit-scrollbar { -webkit-appearance: none }' - jasmine.attachToDOM(scrollbarStyle) - }) + 'atom-text-editor ::-webkit-scrollbar { -webkit-appearance: none }'; + jasmine.attachToDOM(scrollbarStyle); + }); - function buildTextEditorElement (options = {}) { - const element = new TextEditorElement() - element.setUpdatedSynchronously(false) - if (options.attach !== false) jasmine.attachToDOM(element) - return element + function buildTextEditorElement(options = {}) { + const element = new TextEditorElement(); + element.setUpdatedSynchronously(false); + if (options.attach !== false) jasmine.attachToDOM(element); + return element; } it("honors the 'mini' attribute", () => { - jasmineContent.innerHTML = '' - const element = jasmineContent.firstChild - expect(element.getModel().isMini()).toBe(true) + jasmineContent.innerHTML = ''; + const element = jasmineContent.firstChild; + expect(element.getModel().isMini()).toBe(true); - element.removeAttribute('mini') - expect(element.getModel().isMini()).toBe(false) - expect(element.getComponent().getGutterContainerWidth()).toBe(0) + element.removeAttribute('mini'); + expect(element.getModel().isMini()).toBe(false); + expect(element.getComponent().getGutterContainerWidth()).toBe(0); - element.setAttribute('mini', '') - expect(element.getModel().isMini()).toBe(true) - }) + element.setAttribute('mini', ''); + expect(element.getModel().isMini()).toBe(true); + }); it('sets the editor to mini if the model is accessed prior to attaching the element', () => { - const parent = document.createElement('div') - parent.innerHTML = '' - const element = parent.firstChild - expect(element.getModel().isMini()).toBe(true) - }) + const parent = document.createElement('div'); + parent.innerHTML = ''; + const element = parent.firstChild; + expect(element.getModel().isMini()).toBe(true); + }); it("honors the 'placeholder-text' attribute", () => { - jasmineContent.innerHTML = "" - const element = jasmineContent.firstChild - expect(element.getModel().getPlaceholderText()).toBe('testing') + jasmineContent.innerHTML = ""; + const element = jasmineContent.firstChild; + expect(element.getModel().getPlaceholderText()).toBe('testing'); - element.setAttribute('placeholder-text', 'placeholder') - expect(element.getModel().getPlaceholderText()).toBe('placeholder') + element.setAttribute('placeholder-text', 'placeholder'); + expect(element.getModel().getPlaceholderText()).toBe('placeholder'); - element.removeAttribute('placeholder-text') - expect(element.getModel().getPlaceholderText()).toBeNull() - }) + element.removeAttribute('placeholder-text'); + expect(element.getModel().getPlaceholderText()).toBeNull(); + }); it("only assigns 'placeholder-text' on the model if the attribute is present", () => { - const editor = new TextEditor({ placeholderText: 'placeholder' }) - editor.getElement() - expect(editor.getPlaceholderText()).toBe('placeholder') - }) + const editor = new TextEditor({ placeholderText: 'placeholder' }); + editor.getElement(); + expect(editor.getPlaceholderText()).toBe('placeholder'); + }); it("honors the 'gutter-hidden' attribute", () => { - jasmineContent.innerHTML = '' - const element = jasmineContent.firstChild - expect(element.getModel().isLineNumberGutterVisible()).toBe(false) + jasmineContent.innerHTML = ''; + const element = jasmineContent.firstChild; + expect(element.getModel().isLineNumberGutterVisible()).toBe(false); - element.removeAttribute('gutter-hidden') - expect(element.getModel().isLineNumberGutterVisible()).toBe(true) + element.removeAttribute('gutter-hidden'); + expect(element.getModel().isLineNumberGutterVisible()).toBe(true); - element.setAttribute('gutter-hidden', '') - expect(element.getModel().isLineNumberGutterVisible()).toBe(false) - }) + element.setAttribute('gutter-hidden', ''); + expect(element.getModel().isLineNumberGutterVisible()).toBe(false); + }); - it("honors the 'readonly' attribute", async function () { - jasmineContent.innerHTML = '' - const element = jasmineContent.firstChild + it("honors the 'readonly' attribute", async function() { + jasmineContent.innerHTML = ''; + const element = jasmineContent.firstChild; - expect(element.getComponent().isInputEnabled()).toBe(false) + expect(element.getComponent().isInputEnabled()).toBe(false); - element.removeAttribute('readonly') - expect(element.getComponent().isInputEnabled()).toBe(true) + element.removeAttribute('readonly'); + expect(element.getComponent().isInputEnabled()).toBe(true); - element.setAttribute('readonly', true) - expect(element.getComponent().isInputEnabled()).toBe(false) - }) + element.setAttribute('readonly', true); + expect(element.getComponent().isInputEnabled()).toBe(false); + }); it('honors the text content', () => { - jasmineContent.innerHTML = 'testing' - const element = jasmineContent.firstChild - expect(element.getModel().getText()).toBe('testing') - }) + jasmineContent.innerHTML = 'testing'; + const element = jasmineContent.firstChild; + expect(element.getModel().getText()).toBe('testing'); + }); describe('tabIndex', () => { it('uses a default value of -1', () => { - jasmineContent.innerHTML = '' - const element = jasmineContent.firstChild - expect(element.tabIndex).toBe(-1) - expect(element.querySelector('input').tabIndex).toBe(-1) - }) + jasmineContent.innerHTML = ''; + const element = jasmineContent.firstChild; + expect(element.tabIndex).toBe(-1); + expect(element.querySelector('input').tabIndex).toBe(-1); + }); it('uses the custom value when given', () => { - jasmineContent.innerHTML = '' - const element = jasmineContent.firstChild - expect(element.tabIndex).toBe(-1) - expect(element.querySelector('input').tabIndex).toBe(42) - }) - }) + jasmineContent.innerHTML = ''; + const element = jasmineContent.firstChild; + expect(element.tabIndex).toBe(-1); + expect(element.querySelector('input').tabIndex).toBe(42); + }); + }); describe('when the model is assigned', () => it("adds the 'mini' attribute if .isMini() returns true on the model", async () => { - const element = buildTextEditorElement() - element.getModel().update({ mini: true }) - await atom.views.getNextUpdatePromise() - expect(element.hasAttribute('mini')).toBe(true) - })) + const element = buildTextEditorElement(); + element.getModel().update({ mini: true }); + await atom.views.getNextUpdatePromise(); + expect(element.hasAttribute('mini')).toBe(true); + })); describe('when the editor is attached to the DOM', () => it('mounts the component and unmounts when removed from the dom', () => { - const element = buildTextEditorElement() + const element = buildTextEditorElement(); - const { component } = element - expect(component.attached).toBe(true) - element.remove() - expect(component.attached).toBe(false) + const { component } = element; + expect(component.attached).toBe(true); + element.remove(); + expect(component.attached).toBe(false); - jasmine.attachToDOM(element) - expect(element.component.attached).toBe(true) - })) + jasmine.attachToDOM(element); + expect(element.component.attached).toBe(true); + })); describe('when the editor is detached from the DOM and then reattached', () => { it('does not render duplicate line numbers', () => { - const editor = new TextEditor() - editor.setText('1\n2\n3') - const element = editor.getElement() - jasmine.attachToDOM(element) + const editor = new TextEditor(); + editor.setText('1\n2\n3'); + const element = editor.getElement(); + jasmine.attachToDOM(element); - const initialCount = element.querySelectorAll('.line-number').length + const initialCount = element.querySelectorAll('.line-number').length; - element.remove() - jasmine.attachToDOM(element) - expect(element.querySelectorAll('.line-number').length).toBe(initialCount) - }) + element.remove(); + jasmine.attachToDOM(element); + expect(element.querySelectorAll('.line-number').length).toBe( + initialCount + ); + }); it('does not render duplicate decorations in custom gutters', () => { - const editor = new TextEditor() - editor.setText('1\n2\n3') - editor.addGutter({ name: 'test-gutter' }) - const marker = editor.markBufferRange([[0, 0], [2, 0]]) + const editor = new TextEditor(); + editor.setText('1\n2\n3'); + editor.addGutter({ name: 'test-gutter' }); + const marker = editor.markBufferRange([[0, 0], [2, 0]]); editor.decorateMarker(marker, { type: 'gutter', gutterName: 'test-gutter' - }) - const element = editor.getElement() + }); + const element = editor.getElement(); - jasmine.attachToDOM(element) + jasmine.attachToDOM(element); const initialDecorationCount = element.querySelectorAll('.decoration') - .length + .length; - element.remove() - jasmine.attachToDOM(element) + element.remove(); + jasmine.attachToDOM(element); expect(element.querySelectorAll('.decoration').length).toBe( initialDecorationCount - ) - }) + ); + }); it('can be re-focused using the previous `document.activeElement`', () => { - const editorElement = buildTextEditorElement() - editorElement.focus() + const editorElement = buildTextEditorElement(); + editorElement.focus(); - const { activeElement } = document + const { activeElement } = document; - editorElement.remove() - jasmine.attachToDOM(editorElement) - activeElement.focus() + editorElement.remove(); + jasmine.attachToDOM(editorElement); + activeElement.focus(); - expect(editorElement.hasFocus()).toBe(true) - }) - }) + expect(editorElement.hasFocus()).toBe(true); + }); + }); describe('focus and blur handling', () => { it('proxies focus/blur events to/from the hidden input', () => { - const element = buildTextEditorElement() - jasmineContent.appendChild(element) + const element = buildTextEditorElement(); + jasmineContent.appendChild(element); - let blurCalled = false + let blurCalled = false; element.addEventListener('blur', () => { - blurCalled = true - }) + blurCalled = true; + }); - element.focus() - expect(blurCalled).toBe(false) - expect(element.hasFocus()).toBe(true) - expect(document.activeElement).toBe(element.querySelector('input')) + element.focus(); + expect(blurCalled).toBe(false); + expect(element.hasFocus()).toBe(true); + expect(document.activeElement).toBe(element.querySelector('input')); - document.body.focus() - expect(blurCalled).toBe(true) - }) + document.body.focus(); + expect(blurCalled).toBe(true); + }); it("doesn't trigger a blur event on the editor element when focusing an already focused editor element", () => { - let blurCalled = false - const element = buildTextEditorElement() + let blurCalled = false; + const element = buildTextEditorElement(); element.addEventListener('blur', () => { - blurCalled = true - }) + blurCalled = true; + }); - jasmineContent.appendChild(element) - expect(document.activeElement).toBe(document.body) - expect(blurCalled).toBe(false) + jasmineContent.appendChild(element); + expect(document.activeElement).toBe(document.body); + expect(blurCalled).toBe(false); - element.focus() - expect(document.activeElement).toBe(element.querySelector('input')) - expect(blurCalled).toBe(false) + element.focus(); + expect(document.activeElement).toBe(element.querySelector('input')); + expect(blurCalled).toBe(false); - element.focus() - expect(document.activeElement).toBe(element.querySelector('input')) - expect(blurCalled).toBe(false) - }) + element.focus(); + expect(document.activeElement).toBe(element.querySelector('input')); + expect(blurCalled).toBe(false); + }); describe('when focused while a parent node is being attached to the DOM', () => { class ElementThatFocusesChild extends HTMLDivElement { - attachedCallback () { - this.firstChild.focus() + attachedCallback() { + this.firstChild.focus(); } } document.registerElement('element-that-focuses-child', { prototype: ElementThatFocusesChild.prototype - }) + }); it('proxies the focus event to the hidden input', () => { - const element = buildTextEditorElement() + const element = buildTextEditorElement(); const parentElement = document.createElement( 'element-that-focuses-child' - ) - parentElement.appendChild(element) - jasmineContent.appendChild(parentElement) - expect(document.activeElement).toBe(element.querySelector('input')) - }) - }) + ); + parentElement.appendChild(element); + jasmineContent.appendChild(parentElement); + expect(document.activeElement).toBe(element.querySelector('input')); + }); + }); describe('if focused when invisible due to a zero height and width', () => { it('focuses the hidden input and does not throw an exception', () => { - const parentElement = document.createElement('div') - parentElement.style.position = 'absolute' - parentElement.style.width = '0px' - parentElement.style.height = '0px' + const parentElement = document.createElement('div'); + parentElement.style.position = 'absolute'; + parentElement.style.width = '0px'; + parentElement.style.height = '0px'; - const element = buildTextEditorElement({ attach: false }) - parentElement.appendChild(element) - jasmineContent.appendChild(parentElement) + const element = buildTextEditorElement({ attach: false }); + parentElement.appendChild(element); + jasmineContent.appendChild(parentElement); - element.focus() - expect(document.activeElement).toBe(element.component.getHiddenInput()) - }) - }) - }) + element.focus(); + expect(document.activeElement).toBe(element.component.getHiddenInput()); + }); + }); + }); describe('::setModel', () => { describe('when the element does not have an editor yet', () => { it('uses the supplied one', () => { - const element = buildTextEditorElement({ attach: false }) - const editor = new TextEditor() - element.setModel(editor) - jasmine.attachToDOM(element) - expect(editor.element).toBe(element) - expect(element.getModel()).toBe(editor) - }) - }) + const element = buildTextEditorElement({ attach: false }); + const editor = new TextEditor(); + element.setModel(editor); + jasmine.attachToDOM(element); + expect(editor.element).toBe(element); + expect(element.getModel()).toBe(editor); + }); + }); describe('when the element already has an editor', () => { it('unbinds it and then swaps it with the supplied one', async () => { - const element = buildTextEditorElement({ attach: true }) - const previousEditor = element.getModel() - expect(previousEditor.element).toBe(element) + const element = buildTextEditorElement({ attach: true }); + const previousEditor = element.getModel(); + expect(previousEditor.element).toBe(element); - const newEditor = new TextEditor() - element.setModel(newEditor) - expect(previousEditor.element).not.toBe(element) - expect(newEditor.element).toBe(element) - expect(element.getModel()).toBe(newEditor) - }) - }) - }) + const newEditor = new TextEditor(); + element.setModel(newEditor); + expect(previousEditor.element).not.toBe(element); + expect(newEditor.element).toBe(element); + expect(element.getModel()).toBe(newEditor); + }); + }); + }); describe('::onDidAttach and ::onDidDetach', () => it('invokes callbacks when the element is attached and detached', () => { - const element = buildTextEditorElement({ attach: false }) + const element = buildTextEditorElement({ attach: false }); - const attachedCallback = jasmine.createSpy('attachedCallback') - const detachedCallback = jasmine.createSpy('detachedCallback') + const attachedCallback = jasmine.createSpy('attachedCallback'); + const detachedCallback = jasmine.createSpy('detachedCallback'); - element.onDidAttach(attachedCallback) - element.onDidDetach(detachedCallback) + element.onDidAttach(attachedCallback); + element.onDidDetach(detachedCallback); - jasmine.attachToDOM(element) - expect(attachedCallback).toHaveBeenCalled() - expect(detachedCallback).not.toHaveBeenCalled() + jasmine.attachToDOM(element); + expect(attachedCallback).toHaveBeenCalled(); + expect(detachedCallback).not.toHaveBeenCalled(); - attachedCallback.reset() - element.remove() + attachedCallback.reset(); + element.remove(); - expect(attachedCallback).not.toHaveBeenCalled() - expect(detachedCallback).toHaveBeenCalled() - })) + expect(attachedCallback).not.toHaveBeenCalled(); + expect(detachedCallback).toHaveBeenCalled(); + })); describe('::setUpdatedSynchronously', () => { it('controls whether the text editor is updated synchronously', () => { - spyOn(window, 'requestAnimationFrame').andCallFake(fn => fn()) + spyOn(window, 'requestAnimationFrame').andCallFake(fn => fn()); - const element = buildTextEditorElement() + const element = buildTextEditorElement(); - expect(element.isUpdatedSynchronously()).toBe(false) + expect(element.isUpdatedSynchronously()).toBe(false); - element.getModel().setText('hello') - expect(window.requestAnimationFrame).toHaveBeenCalled() + element.getModel().setText('hello'); + expect(window.requestAnimationFrame).toHaveBeenCalled(); - expect(element.textContent).toContain('hello') + expect(element.textContent).toContain('hello'); - window.requestAnimationFrame.reset() - element.setUpdatedSynchronously(true) - element.getModel().setText('goodbye') - expect(window.requestAnimationFrame).not.toHaveBeenCalled() - expect(element.textContent).toContain('goodbye') - }) - }) + window.requestAnimationFrame.reset(); + element.setUpdatedSynchronously(true); + element.getModel().setText('goodbye'); + expect(window.requestAnimationFrame).not.toHaveBeenCalled(); + expect(element.textContent).toContain('goodbye'); + }); + }); describe('::getDefaultCharacterWidth', () => { it('returns 0 before the element is attached', () => { - const element = buildTextEditorElement({ attach: false }) - expect(element.getDefaultCharacterWidth()).toBe(0) - }) + const element = buildTextEditorElement({ attach: false }); + expect(element.getDefaultCharacterWidth()).toBe(0); + }); it('returns the width of a character in the root scope', () => { - const element = buildTextEditorElement() - jasmine.attachToDOM(element) - expect(element.getDefaultCharacterWidth()).toBeGreaterThan(0) - }) - }) + const element = buildTextEditorElement(); + jasmine.attachToDOM(element); + expect(element.getDefaultCharacterWidth()).toBeGreaterThan(0); + }); + }); describe('::getMaxScrollTop', () => it('returns the maximum scroll top that can be applied to the element', async () => { - const editor = new TextEditor() - editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16') - const element = editor.getElement() - element.style.lineHeight = '10px' - element.style.width = '200px' - jasmine.attachToDOM(element) + const editor = new TextEditor(); + editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16'); + const element = editor.getElement(); + element.style.lineHeight = '10px'; + element.style.width = '200px'; + jasmine.attachToDOM(element); - const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight() + const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight(); - expect(element.getMaxScrollTop()).toBe(0) - await editor.update({ autoHeight: false }) + expect(element.getMaxScrollTop()).toBe(0); + await editor.update({ autoHeight: false }); - element.style.height = 100 + horizontalScrollbarHeight + 'px' - await element.getNextUpdatePromise() - expect(element.getMaxScrollTop()).toBe(60) + element.style.height = 100 + horizontalScrollbarHeight + 'px'; + await element.getNextUpdatePromise(); + expect(element.getMaxScrollTop()).toBe(60); - element.style.height = 120 + horizontalScrollbarHeight + 'px' - await element.getNextUpdatePromise() - expect(element.getMaxScrollTop()).toBe(40) + element.style.height = 120 + horizontalScrollbarHeight + 'px'; + await element.getNextUpdatePromise(); + expect(element.getMaxScrollTop()).toBe(40); - element.style.height = 200 + horizontalScrollbarHeight + 'px' - await element.getNextUpdatePromise() - expect(element.getMaxScrollTop()).toBe(0) - })) + element.style.height = 200 + horizontalScrollbarHeight + 'px'; + await element.getNextUpdatePromise(); + expect(element.getMaxScrollTop()).toBe(0); + })); describe('::setScrollTop and ::setScrollLeft', () => { it('changes the scroll position', async () => { - const element = buildTextEditorElement() - element.getModel().update({ autoHeight: false }) - element.getModel().setText('lorem\nipsum\ndolor\nsit\namet') - element.setHeight(20) - await element.getNextUpdatePromise() - element.setWidth(20) - await element.getNextUpdatePromise() + const element = buildTextEditorElement(); + element.getModel().update({ autoHeight: false }); + element.getModel().setText('lorem\nipsum\ndolor\nsit\namet'); + element.setHeight(20); + await element.getNextUpdatePromise(); + element.setWidth(20); + await element.getNextUpdatePromise(); - element.setScrollTop(22) - await element.getNextUpdatePromise() - expect(element.getScrollTop()).toBe(22) + element.setScrollTop(22); + await element.getNextUpdatePromise(); + expect(element.getScrollTop()).toBe(22); - element.setScrollLeft(32) - await element.getNextUpdatePromise() - expect(element.getScrollLeft()).toBe(32) - }) - }) + element.setScrollLeft(32); + await element.getNextUpdatePromise(); + expect(element.getScrollLeft()).toBe(32); + }); + }); describe('on TextEditor::setMini', () => it("changes the element's 'mini' attribute", async () => { - const element = buildTextEditorElement() - expect(element.hasAttribute('mini')).toBe(false) - element.getModel().setMini(true) - await element.getNextUpdatePromise() - expect(element.hasAttribute('mini')).toBe(true) - element.getModel().setMini(false) - await element.getNextUpdatePromise() - expect(element.hasAttribute('mini')).toBe(false) - })) + const element = buildTextEditorElement(); + expect(element.hasAttribute('mini')).toBe(false); + element.getModel().setMini(true); + await element.getNextUpdatePromise(); + expect(element.hasAttribute('mini')).toBe(true); + element.getModel().setMini(false); + await element.getNextUpdatePromise(); + expect(element.hasAttribute('mini')).toBe(false); + })); describe('::intersectsVisibleRowRange(start, end)', () => { it('returns true if the given row range intersects the visible row range', async () => { - const element = buildTextEditorElement() - const editor = element.getModel() - const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight() + const element = buildTextEditorElement(); + const editor = element.getModel(); + const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight(); - editor.update({ autoHeight: false }) - element.getModel().setText('x\n'.repeat(20)) - element.style.height = 120 + horizontalScrollbarHeight + 'px' - await element.getNextUpdatePromise() + editor.update({ autoHeight: false }); + element.getModel().setText('x\n'.repeat(20)); + element.style.height = 120 + horizontalScrollbarHeight + 'px'; + await element.getNextUpdatePromise(); - element.setScrollTop(80) - await element.getNextUpdatePromise() - expect(element.getVisibleRowRange()).toEqual([4, 11]) + element.setScrollTop(80); + await element.getNextUpdatePromise(); + expect(element.getVisibleRowRange()).toEqual([4, 11]); - expect(element.intersectsVisibleRowRange(0, 4)).toBe(false) - expect(element.intersectsVisibleRowRange(0, 5)).toBe(true) - expect(element.intersectsVisibleRowRange(5, 8)).toBe(true) - expect(element.intersectsVisibleRowRange(11, 12)).toBe(false) - expect(element.intersectsVisibleRowRange(12, 13)).toBe(false) - }) - }) + expect(element.intersectsVisibleRowRange(0, 4)).toBe(false); + expect(element.intersectsVisibleRowRange(0, 5)).toBe(true); + expect(element.intersectsVisibleRowRange(5, 8)).toBe(true); + expect(element.intersectsVisibleRowRange(11, 12)).toBe(false); + expect(element.intersectsVisibleRowRange(12, 13)).toBe(false); + }); + }); describe('::pixelRectForScreenRange(range)', () => { it('returns a {top/left/width/height} object describing the rectangle between two screen positions, even if they are not on screen', async () => { - const element = buildTextEditorElement() - const editor = element.getModel() - const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight() + const element = buildTextEditorElement(); + const editor = element.getModel(); + const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight(); - editor.update({ autoHeight: false }) - element.getModel().setText('xxxxxxxxxxxxxxxxxxxxxx\n'.repeat(20)) - element.style.height = 120 + horizontalScrollbarHeight + 'px' - await element.getNextUpdatePromise() - element.setScrollTop(80) - await element.getNextUpdatePromise() - expect(element.getVisibleRowRange()).toEqual([4, 11]) + editor.update({ autoHeight: false }); + element.getModel().setText('xxxxxxxxxxxxxxxxxxxxxx\n'.repeat(20)); + element.style.height = 120 + horizontalScrollbarHeight + 'px'; + await element.getNextUpdatePromise(); + element.setScrollTop(80); + await element.getNextUpdatePromise(); + expect(element.getVisibleRowRange()).toEqual([4, 11]); - const top = 2 * editor.getLineHeightInPixels() - const bottom = 13 * editor.getLineHeightInPixels() - const left = Math.round(3 * editor.getDefaultCharWidth()) - const right = Math.round(11 * editor.getDefaultCharWidth()) + const top = 2 * editor.getLineHeightInPixels(); + const bottom = 13 * editor.getLineHeightInPixels(); + const left = Math.round(3 * editor.getDefaultCharWidth()); + const right = Math.round(11 * editor.getDefaultCharWidth()); expect(element.pixelRectForScreenRange([[2, 3], [13, 11]])).toEqual({ top, left, height: bottom + editor.getLineHeightInPixels() - top, width: right - left - }) - }) - }) + }); + }); + }); describe('events', () => { - let element = null + let element = null; beforeEach(async () => { - element = buildTextEditorElement() - element.getModel().update({ autoHeight: false }) - element.getModel().setText('lorem\nipsum\ndolor\nsit\namet') - element.setHeight(20) - await element.getNextUpdatePromise() - element.setWidth(20) - await element.getNextUpdatePromise() - }) + element = buildTextEditorElement(); + element.getModel().update({ autoHeight: false }); + element.getModel().setText('lorem\nipsum\ndolor\nsit\namet'); + element.setHeight(20); + await element.getNextUpdatePromise(); + element.setWidth(20); + await element.getNextUpdatePromise(); + }); describe('::onDidChangeScrollTop(callback)', () => it('triggers even when subscribing before attaching the element', () => { - const positions = [] + const positions = []; const subscription1 = element.onDidChangeScrollTop(p => positions.push(p) - ) - element.onDidChangeScrollTop(p => positions.push(p)) + ); + element.onDidChangeScrollTop(p => positions.push(p)); - positions.length = 0 - element.setScrollTop(10) - expect(positions).toEqual([10, 10]) + positions.length = 0; + element.setScrollTop(10); + expect(positions).toEqual([10, 10]); - element.remove() - jasmine.attachToDOM(element) + element.remove(); + jasmine.attachToDOM(element); - positions.length = 0 - element.setScrollTop(20) - expect(positions).toEqual([20, 20]) + positions.length = 0; + element.setScrollTop(20); + expect(positions).toEqual([20, 20]); - subscription1.dispose() + subscription1.dispose(); - positions.length = 0 - element.setScrollTop(30) - expect(positions).toEqual([30]) - })) + positions.length = 0; + element.setScrollTop(30); + expect(positions).toEqual([30]); + })); describe('::onDidChangeScrollLeft(callback)', () => it('triggers even when subscribing before attaching the element', () => { - const positions = [] + const positions = []; const subscription1 = element.onDidChangeScrollLeft(p => positions.push(p) - ) - element.onDidChangeScrollLeft(p => positions.push(p)) + ); + element.onDidChangeScrollLeft(p => positions.push(p)); - positions.length = 0 - element.setScrollLeft(10) - expect(positions).toEqual([10, 10]) + positions.length = 0; + element.setScrollLeft(10); + expect(positions).toEqual([10, 10]); - element.remove() - jasmine.attachToDOM(element) + element.remove(); + jasmine.attachToDOM(element); - positions.length = 0 - element.setScrollLeft(20) - expect(positions).toEqual([20, 20]) + positions.length = 0; + element.setScrollLeft(20); + expect(positions).toEqual([20, 20]); - subscription1.dispose() + subscription1.dispose(); - positions.length = 0 - element.setScrollLeft(30) - expect(positions).toEqual([30]) - })) - }) -}) + positions.length = 0; + element.setScrollLeft(30); + expect(positions).toEqual([30]); + })); + }); +}); diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js index 034550010..40388c276 100644 --- a/spec/text-editor-registry-spec.js +++ b/spec/text-editor-registry-spec.js @@ -1,14 +1,14 @@ -const TextEditorRegistry = require('../src/text-editor-registry') -const TextEditor = require('../src/text-editor') -const TextBuffer = require('text-buffer') -const { Point, Range } = TextBuffer -const dedent = require('dedent') +const TextEditorRegistry = require('../src/text-editor-registry'); +const TextEditor = require('../src/text-editor'); +const TextBuffer = require('text-buffer'); +const { Point, Range } = TextBuffer; +const dedent = require('dedent'); -describe('TextEditorRegistry', function () { - let registry, editor, initialPackageActivation +describe('TextEditorRegistry', function() { + let registry, editor, initialPackageActivation; - beforeEach(function () { - initialPackageActivation = Promise.resolve() + beforeEach(function() { + initialPackageActivation = Promise.resolve(); registry = new TextEditorRegistry({ assert: atom.assert, @@ -16,289 +16,289 @@ describe('TextEditorRegistry', function () { grammarRegistry: atom.grammars, packageManager: { getActivatePromise() { - return initialPackageActivation + return initialPackageActivation; } } - }) + }); - editor = new TextEditor({ autoHeight: false }) + editor = new TextEditor({ autoHeight: false }); expect( atom.grammars.assignLanguageMode(editor, 'text.plain.null-grammar') - ).toBe(true) - }) + ).toBe(true); + }); - afterEach(function () { - registry.destroy() - }) + afterEach(function() { + registry.destroy(); + }); - describe('.add', function () { - it('adds an editor to the list of registered editors', function () { - registry.add(editor) - expect(editor.registered).toBe(true) - expect(registry.editors.size).toBe(1) - expect(registry.editors.has(editor)).toBe(true) - }) + describe('.add', function() { + it('adds an editor to the list of registered editors', function() { + registry.add(editor); + expect(editor.registered).toBe(true); + expect(registry.editors.size).toBe(1); + expect(registry.editors.has(editor)).toBe(true); + }); - it('returns a Disposable that can unregister the editor', function () { - const disposable = registry.add(editor) - expect(registry.editors.size).toBe(1) - disposable.dispose() - expect(registry.editors.size).toBe(0) - expect(editor.registered).toBe(false) - expect(retainedEditorCount(registry)).toBe(0) - }) - }) + it('returns a Disposable that can unregister the editor', function() { + const disposable = registry.add(editor); + expect(registry.editors.size).toBe(1); + disposable.dispose(); + expect(registry.editors.size).toBe(0); + expect(editor.registered).toBe(false); + expect(retainedEditorCount(registry)).toBe(0); + }); + }); - describe('.observe', function () { - it('calls the callback for current and future editors until unsubscribed', function () { - const spy = jasmine.createSpy() - const [editor1, editor2, editor3] = [{}, {}, {}] - registry.add(editor1) - const subscription = registry.observe(spy) - expect(spy.calls.length).toBe(1) + describe('.observe', function() { + it('calls the callback for current and future editors until unsubscribed', function() { + const spy = jasmine.createSpy(); + const [editor1, editor2, editor3] = [{}, {}, {}]; + registry.add(editor1); + const subscription = registry.observe(spy); + expect(spy.calls.length).toBe(1); - registry.add(editor2) - expect(spy.calls.length).toBe(2) - expect(spy.argsForCall[0][0]).toBe(editor1) - expect(spy.argsForCall[1][0]).toBe(editor2) - subscription.dispose() + registry.add(editor2); + expect(spy.calls.length).toBe(2); + expect(spy.argsForCall[0][0]).toBe(editor1); + expect(spy.argsForCall[1][0]).toBe(editor2); + subscription.dispose(); - registry.add(editor3) - expect(spy.calls.length).toBe(2) - }) - }) + registry.add(editor3); + expect(spy.calls.length).toBe(2); + }); + }); - describe('.build', function () { - it('constructs a TextEditor with the right parameters based on its path and text', async function () { - await atom.packages.activatePackage('language-javascript') - await atom.packages.activatePackage('language-c') + describe('.build', function() { + it('constructs a TextEditor with the right parameters based on its path and text', async function() { + await atom.packages.activatePackage('language-javascript'); + await atom.packages.activatePackage('language-c'); - atom.config.set('editor.tabLength', 8, { scope: '.source.js' }) + atom.config.set('editor.tabLength', 8, { scope: '.source.js' }); const editor = registry.build({ buffer: new TextBuffer({ filePath: 'test.js' }) - }) - expect(editor.getTabLength()).toBe(8) - }) - }) + }); + expect(editor.getTabLength()).toBe(8); + }); + }); - describe('.maintainConfig(editor)', function () { - it('does not update the editor when config settings change for unrelated scope selectors', async function () { - await atom.packages.activatePackage('language-javascript') + describe('.maintainConfig(editor)', function() { + it('does not update the editor when config settings change for unrelated scope selectors', async function() { + await atom.packages.activatePackage('language-javascript'); - const editor2 = new TextEditor() + const editor2 = new TextEditor(); - atom.grammars.assignLanguageMode(editor2, 'source.js') + atom.grammars.assignLanguageMode(editor2, 'source.js'); - registry.maintainConfig(editor) - registry.maintainConfig(editor2) - await initialPackageActivation + registry.maintainConfig(editor); + registry.maintainConfig(editor2); + await initialPackageActivation; expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual([ 'text.plain.null-grammar' - ]) + ]); expect(editor2.getRootScopeDescriptor().getScopesArray()).toEqual([ 'source.js' - ]) + ]); - expect(editor.getEncoding()).toBe('utf8') - expect(editor2.getEncoding()).toBe('utf8') + expect(editor.getEncoding()).toBe('utf8'); + expect(editor2.getEncoding()).toBe('utf8'); atom.config.set('core.fileEncoding', 'utf16le', { scopeSelector: '.text.plain.null-grammar' - }) + }); atom.config.set('core.fileEncoding', 'utf16be', { scopeSelector: '.source.js' - }) + }); - expect(editor.getEncoding()).toBe('utf16le') - expect(editor2.getEncoding()).toBe('utf16be') - }) + expect(editor.getEncoding()).toBe('utf16le'); + expect(editor2.getEncoding()).toBe('utf16be'); + }); - it('does not update the editor before the initial packages have loaded', async function () { - let resolveActivatePromise + it('does not update the editor before the initial packages have loaded', async function() { + let resolveActivatePromise; initialPackageActivation = new Promise(resolve => { - resolveActivatePromise = resolve - }) + resolveActivatePromise = resolve; + }); - atom.config.set('core.fileEncoding', 'utf16le') + atom.config.set('core.fileEncoding', 'utf16le'); - registry.maintainConfig(editor) - await Promise.resolve() - expect(editor.getEncoding()).toBe('utf8') + registry.maintainConfig(editor); + await Promise.resolve(); + expect(editor.getEncoding()).toBe('utf8'); - atom.config.set('core.fileEncoding', 'utf16be') - await Promise.resolve() - expect(editor.getEncoding()).toBe('utf8') + atom.config.set('core.fileEncoding', 'utf16be'); + await Promise.resolve(); + expect(editor.getEncoding()).toBe('utf8'); - resolveActivatePromise() - await initialPackageActivation - expect(editor.getEncoding()).toBe('utf16be') - }) + resolveActivatePromise(); + await initialPackageActivation; + expect(editor.getEncoding()).toBe('utf16be'); + }); - it("updates the editor's settings when its grammar changes", async function () { - await atom.packages.activatePackage('language-javascript') + it("updates the editor's settings when its grammar changes", async function() { + await atom.packages.activatePackage('language-javascript'); - registry.maintainConfig(editor) - await initialPackageActivation + registry.maintainConfig(editor); + await initialPackageActivation; atom.config.set('core.fileEncoding', 'utf16be', { scopeSelector: '.source.js' - }) - expect(editor.getEncoding()).toBe('utf8') + }); + expect(editor.getEncoding()).toBe('utf8'); atom.config.set('core.fileEncoding', 'utf16le', { scopeSelector: '.source.js' - }) - expect(editor.getEncoding()).toBe('utf8') + }); + expect(editor.getEncoding()).toBe('utf8'); - atom.grammars.assignLanguageMode(editor, 'source.js') - await initialPackageActivation - expect(editor.getEncoding()).toBe('utf16le') + atom.grammars.assignLanguageMode(editor, 'source.js'); + await initialPackageActivation; + expect(editor.getEncoding()).toBe('utf16le'); atom.config.set('core.fileEncoding', 'utf16be', { scopeSelector: '.source.js' - }) - expect(editor.getEncoding()).toBe('utf16be') + }); + expect(editor.getEncoding()).toBe('utf16be'); - atom.grammars.assignLanguageMode(editor, 'text.plain.null-grammar') - await initialPackageActivation - expect(editor.getEncoding()).toBe('utf8') - }) + atom.grammars.assignLanguageMode(editor, 'text.plain.null-grammar'); + await initialPackageActivation; + expect(editor.getEncoding()).toBe('utf8'); + }); - it("preserves editor settings that haven't changed between previous and current language modes", async function () { - await atom.packages.activatePackage('language-javascript') + it("preserves editor settings that haven't changed between previous and current language modes", async function() { + await atom.packages.activatePackage('language-javascript'); - registry.maintainConfig(editor) - await initialPackageActivation + registry.maintainConfig(editor); + await initialPackageActivation; - expect(editor.getEncoding()).toBe('utf8') - editor.setEncoding('utf16le') - expect(editor.getEncoding()).toBe('utf16le') + expect(editor.getEncoding()).toBe('utf8'); + editor.setEncoding('utf16le'); + expect(editor.getEncoding()).toBe('utf16le'); - expect(editor.isSoftWrapped()).toBe(false) - editor.setSoftWrapped(true) - expect(editor.isSoftWrapped()).toBe(true) + expect(editor.isSoftWrapped()).toBe(false); + editor.setSoftWrapped(true); + expect(editor.isSoftWrapped()).toBe(true); - atom.grammars.assignLanguageMode(editor, 'source.js') - await initialPackageActivation - expect(editor.getEncoding()).toBe('utf16le') - expect(editor.isSoftWrapped()).toBe(true) - }) + atom.grammars.assignLanguageMode(editor, 'source.js'); + await initialPackageActivation; + expect(editor.getEncoding()).toBe('utf16le'); + expect(editor.isSoftWrapped()).toBe(true); + }); - it('updates editor settings that have changed between previous and current language modes', async function () { - await atom.packages.activatePackage('language-javascript') + it('updates editor settings that have changed between previous and current language modes', async function() { + await atom.packages.activatePackage('language-javascript'); - registry.maintainConfig(editor) - await initialPackageActivation + registry.maintainConfig(editor); + await initialPackageActivation; - expect(editor.getEncoding()).toBe('utf8') + expect(editor.getEncoding()).toBe('utf8'); atom.config.set('core.fileEncoding', 'utf16be', { scopeSelector: '.text.plain.null-grammar' - }) + }); atom.config.set('core.fileEncoding', 'utf16le', { scopeSelector: '.source.js' - }) - expect(editor.getEncoding()).toBe('utf16be') + }); + expect(editor.getEncoding()).toBe('utf16be'); - editor.setEncoding('utf8') - expect(editor.getEncoding()).toBe('utf8') + editor.setEncoding('utf8'); + expect(editor.getEncoding()).toBe('utf8'); - atom.grammars.assignLanguageMode(editor, 'source.js') - await initialPackageActivation - expect(editor.getEncoding()).toBe('utf16le') - }) + atom.grammars.assignLanguageMode(editor, 'source.js'); + await initialPackageActivation; + expect(editor.getEncoding()).toBe('utf16le'); + }); - it("returns a disposable that can be used to stop the registry from updating the editor's config", async function () { - await atom.packages.activatePackage('language-javascript') + it("returns a disposable that can be used to stop the registry from updating the editor's config", async function() { + await atom.packages.activatePackage('language-javascript'); - const previousSubscriptionCount = getSubscriptionCount(editor) - const disposable = registry.maintainConfig(editor) - await initialPackageActivation + const previousSubscriptionCount = getSubscriptionCount(editor); + const disposable = registry.maintainConfig(editor); + await initialPackageActivation; expect(getSubscriptionCount(editor)).toBeGreaterThan( previousSubscriptionCount - ) - expect(registry.editorsWithMaintainedConfig.size).toBe(1) + ); + expect(registry.editorsWithMaintainedConfig.size).toBe(1); - atom.config.set('core.fileEncoding', 'utf16be') - expect(editor.getEncoding()).toBe('utf16be') - atom.config.set('core.fileEncoding', 'utf8') - expect(editor.getEncoding()).toBe('utf8') + atom.config.set('core.fileEncoding', 'utf16be'); + expect(editor.getEncoding()).toBe('utf16be'); + atom.config.set('core.fileEncoding', 'utf8'); + expect(editor.getEncoding()).toBe('utf8'); - disposable.dispose() + disposable.dispose(); - atom.config.set('core.fileEncoding', 'utf16be') - expect(editor.getEncoding()).toBe('utf8') - expect(getSubscriptionCount(editor)).toBe(previousSubscriptionCount) - expect(retainedEditorCount(registry)).toBe(0) - }) + atom.config.set('core.fileEncoding', 'utf16be'); + expect(editor.getEncoding()).toBe('utf8'); + expect(getSubscriptionCount(editor)).toBe(previousSubscriptionCount); + expect(retainedEditorCount(registry)).toBe(0); + }); - it('sets the encoding based on the config', async function () { - editor.update({ encoding: 'utf8' }) - expect(editor.getEncoding()).toBe('utf8') + it('sets the encoding based on the config', async function() { + editor.update({ encoding: 'utf8' }); + expect(editor.getEncoding()).toBe('utf8'); - atom.config.set('core.fileEncoding', 'utf16le') - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getEncoding()).toBe('utf16le') + atom.config.set('core.fileEncoding', 'utf16le'); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getEncoding()).toBe('utf16le'); - atom.config.set('core.fileEncoding', 'utf8') - expect(editor.getEncoding()).toBe('utf8') - }) + atom.config.set('core.fileEncoding', 'utf8'); + expect(editor.getEncoding()).toBe('utf8'); + }); - it('sets the tab length based on the config', async function () { - editor.update({ tabLength: 4 }) - expect(editor.getTabLength()).toBe(4) + it('sets the tab length based on the config', async function() { + editor.update({ tabLength: 4 }); + expect(editor.getTabLength()).toBe(4); - atom.config.set('editor.tabLength', 8) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getTabLength()).toBe(8) + atom.config.set('editor.tabLength', 8); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getTabLength()).toBe(8); - atom.config.set('editor.tabLength', 4) - expect(editor.getTabLength()).toBe(4) - }) + atom.config.set('editor.tabLength', 4); + expect(editor.getTabLength()).toBe(4); + }); - it('enables soft tabs when the tabType config setting is "soft"', async function () { - atom.config.set('editor.tabType', 'soft') - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getSoftTabs()).toBe(true) - }) + it('enables soft tabs when the tabType config setting is "soft"', async function() { + atom.config.set('editor.tabType', 'soft'); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getSoftTabs()).toBe(true); + }); - it('disables soft tabs when the tabType config setting is "hard"', async function () { - atom.config.set('editor.tabType', 'hard') - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getSoftTabs()).toBe(false) - }) + it('disables soft tabs when the tabType config setting is "hard"', async function() { + atom.config.set('editor.tabType', 'hard'); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getSoftTabs()).toBe(false); + }); - describe('when the "tabType" config setting is "auto"', function () { - it("enables or disables soft tabs based on the editor's content", async function () { - await initialPackageActivation - await atom.packages.activatePackage('language-javascript') - atom.grammars.assignLanguageMode(editor, 'source.js') - atom.config.set('editor.tabType', 'auto') - await initialPackageActivation + describe('when the "tabType" config setting is "auto"', function() { + it("enables or disables soft tabs based on the editor's content", async function() { + await initialPackageActivation; + await atom.packages.activatePackage('language-javascript'); + atom.grammars.assignLanguageMode(editor, 'source.js'); + atom.config.set('editor.tabType', 'auto'); + await initialPackageActivation; editor.setText(dedent` { hello; } - `) - let disposable = registry.maintainConfig(editor) - expect(editor.getSoftTabs()).toBe(true) + `); + let disposable = registry.maintainConfig(editor); + expect(editor.getSoftTabs()).toBe(true); /* eslint-disable no-tabs */ editor.setText(dedent` { hello; } - `) + `); /* eslint-enable no-tabs */ - disposable.dispose() - disposable = registry.maintainConfig(editor) - expect(editor.getSoftTabs()).toBe(false) + disposable.dispose(); + disposable = registry.maintainConfig(editor); + expect(editor.getSoftTabs()).toBe(false); editor.setTextInBufferRange( new Range(Point.ZERO, Point.ZERO), @@ -307,10 +307,10 @@ describe('TextEditorRegistry', function () { * Comment with a leading space. */ ` + '\n' - ) - disposable.dispose() - disposable = registry.maintainConfig(editor) - expect(editor.getSoftTabs()).toBe(false) + ); + disposable.dispose(); + disposable = registry.maintainConfig(editor); + expect(editor.getSoftTabs()).toBe(false); /* eslint-disable no-tabs */ editor.setText(dedent` @@ -321,11 +321,11 @@ describe('TextEditorRegistry', function () { { hello; } - `) + `); /* eslint-enable no-tabs */ - disposable.dispose() - disposable = registry.maintainConfig(editor) - expect(editor.getSoftTabs()).toBe(false) + disposable.dispose(); + disposable = registry.maintainConfig(editor); + expect(editor.getSoftTabs()).toBe(false); editor.setText(dedent` /* @@ -335,295 +335,295 @@ describe('TextEditorRegistry', function () { { hello; } - `) - disposable.dispose() - disposable = registry.maintainConfig(editor) - expect(editor.getSoftTabs()).toBe(true) - }) - }) + `); + disposable.dispose(); + disposable = registry.maintainConfig(editor); + expect(editor.getSoftTabs()).toBe(true); + }); + }); - describe('when the "tabType" config setting is "auto"', function () { - it('enables or disables soft tabs based on the "softTabs" config setting', async function () { - registry.maintainConfig(editor) - await initialPackageActivation + describe('when the "tabType" config setting is "auto"', function() { + it('enables or disables soft tabs based on the "softTabs" config setting', async function() { + registry.maintainConfig(editor); + await initialPackageActivation; - editor.setText('abc\ndef') - atom.config.set('editor.softTabs', true) - atom.config.set('editor.tabType', 'auto') - expect(editor.getSoftTabs()).toBe(true) + editor.setText('abc\ndef'); + atom.config.set('editor.softTabs', true); + atom.config.set('editor.tabType', 'auto'); + expect(editor.getSoftTabs()).toBe(true); - atom.config.set('editor.softTabs', false) - expect(editor.getSoftTabs()).toBe(false) - }) - }) + atom.config.set('editor.softTabs', false); + expect(editor.getSoftTabs()).toBe(false); + }); + }); - it('enables or disables soft tabs based on the config', async function () { - editor.update({ softTabs: true }) - expect(editor.getSoftTabs()).toBe(true) + it('enables or disables soft tabs based on the config', async function() { + editor.update({ softTabs: true }); + expect(editor.getSoftTabs()).toBe(true); - atom.config.set('editor.tabType', 'hard') - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getSoftTabs()).toBe(false) + atom.config.set('editor.tabType', 'hard'); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getSoftTabs()).toBe(false); - atom.config.set('editor.tabType', 'soft') - expect(editor.getSoftTabs()).toBe(true) + atom.config.set('editor.tabType', 'soft'); + expect(editor.getSoftTabs()).toBe(true); - atom.config.set('editor.tabType', 'auto') - atom.config.set('editor.softTabs', true) - expect(editor.getSoftTabs()).toBe(true) - }) + atom.config.set('editor.tabType', 'auto'); + atom.config.set('editor.softTabs', true); + expect(editor.getSoftTabs()).toBe(true); + }); - it('enables or disables atomic soft tabs based on the config', async function () { - editor.update({ atomicSoftTabs: true }) - expect(editor.hasAtomicSoftTabs()).toBe(true) + it('enables or disables atomic soft tabs based on the config', async function() { + editor.update({ atomicSoftTabs: true }); + expect(editor.hasAtomicSoftTabs()).toBe(true); - atom.config.set('editor.atomicSoftTabs', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.hasAtomicSoftTabs()).toBe(false) + atom.config.set('editor.atomicSoftTabs', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.hasAtomicSoftTabs()).toBe(false); - atom.config.set('editor.atomicSoftTabs', true) - expect(editor.hasAtomicSoftTabs()).toBe(true) - }) + atom.config.set('editor.atomicSoftTabs', true); + expect(editor.hasAtomicSoftTabs()).toBe(true); + }); - it('enables or disables cursor on selection visibility based on the config', async function () { - editor.update({ showCursorOnSelection: true }) - expect(editor.getShowCursorOnSelection()).toBe(true) + it('enables or disables cursor on selection visibility based on the config', async function() { + editor.update({ showCursorOnSelection: true }); + expect(editor.getShowCursorOnSelection()).toBe(true); - atom.config.set('editor.showCursorOnSelection', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getShowCursorOnSelection()).toBe(false) + atom.config.set('editor.showCursorOnSelection', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getShowCursorOnSelection()).toBe(false); - atom.config.set('editor.showCursorOnSelection', true) - expect(editor.getShowCursorOnSelection()).toBe(true) - }) + atom.config.set('editor.showCursorOnSelection', true); + expect(editor.getShowCursorOnSelection()).toBe(true); + }); - it('enables or disables line numbers based on the config', async function () { - editor.update({ showLineNumbers: true }) - expect(editor.showLineNumbers).toBe(true) + it('enables or disables line numbers based on the config', async function() { + editor.update({ showLineNumbers: true }); + expect(editor.showLineNumbers).toBe(true); - atom.config.set('editor.showLineNumbers', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.showLineNumbers).toBe(false) + atom.config.set('editor.showLineNumbers', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.showLineNumbers).toBe(false); - atom.config.set('editor.showLineNumbers', true) - expect(editor.showLineNumbers).toBe(true) - }) + atom.config.set('editor.showLineNumbers', true); + expect(editor.showLineNumbers).toBe(true); + }); - it('sets the invisibles based on the config', async function () { - const invisibles1 = { tab: 'a', cr: false, eol: false, space: false } - const invisibles2 = { tab: 'b', cr: false, eol: false, space: false } + it('sets the invisibles based on the config', async function() { + const invisibles1 = { tab: 'a', cr: false, eol: false, space: false }; + const invisibles2 = { tab: 'b', cr: false, eol: false, space: false }; editor.update({ showInvisibles: true, invisibles: invisibles1 - }) - expect(editor.getInvisibles()).toEqual(invisibles1) + }); + expect(editor.getInvisibles()).toEqual(invisibles1); - atom.config.set('editor.showInvisibles', true) - atom.config.set('editor.invisibles', invisibles2) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getInvisibles()).toEqual(invisibles2) + atom.config.set('editor.showInvisibles', true); + atom.config.set('editor.invisibles', invisibles2); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getInvisibles()).toEqual(invisibles2); - atom.config.set('editor.invisibles', invisibles1) - expect(editor.getInvisibles()).toEqual(invisibles1) + atom.config.set('editor.invisibles', invisibles1); + expect(editor.getInvisibles()).toEqual(invisibles1); - atom.config.set('editor.showInvisibles', false) - expect(editor.getInvisibles()).toEqual({}) - }) + atom.config.set('editor.showInvisibles', false); + expect(editor.getInvisibles()).toEqual({}); + }); - it('enables or disables the indent guide based on the config', async function () { - editor.update({ showIndentGuide: true }) - expect(editor.doesShowIndentGuide()).toBe(true) + it('enables or disables the indent guide based on the config', async function() { + editor.update({ showIndentGuide: true }); + expect(editor.doesShowIndentGuide()).toBe(true); - atom.config.set('editor.showIndentGuide', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.doesShowIndentGuide()).toBe(false) + atom.config.set('editor.showIndentGuide', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.doesShowIndentGuide()).toBe(false); - atom.config.set('editor.showIndentGuide', true) - expect(editor.doesShowIndentGuide()).toBe(true) - }) + atom.config.set('editor.showIndentGuide', true); + expect(editor.doesShowIndentGuide()).toBe(true); + }); - it('enables or disables soft wrap based on the config', async function () { - editor.update({ softWrapped: true }) - expect(editor.isSoftWrapped()).toBe(true) + it('enables or disables soft wrap based on the config', async function() { + editor.update({ softWrapped: true }); + expect(editor.isSoftWrapped()).toBe(true); - atom.config.set('editor.softWrap', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.isSoftWrapped()).toBe(false) + atom.config.set('editor.softWrap', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.isSoftWrapped()).toBe(false); - atom.config.set('editor.softWrap', true) - expect(editor.isSoftWrapped()).toBe(true) - }) + atom.config.set('editor.softWrap', true); + expect(editor.isSoftWrapped()).toBe(true); + }); - it('sets the soft wrap indent length based on the config', async function () { - editor.update({ softWrapHangingIndentLength: 4 }) - expect(editor.getSoftWrapHangingIndentLength()).toBe(4) + it('sets the soft wrap indent length based on the config', async function() { + editor.update({ softWrapHangingIndentLength: 4 }); + expect(editor.getSoftWrapHangingIndentLength()).toBe(4); - atom.config.set('editor.softWrapHangingIndent', 2) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getSoftWrapHangingIndentLength()).toBe(2) + atom.config.set('editor.softWrapHangingIndent', 2); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getSoftWrapHangingIndentLength()).toBe(2); - atom.config.set('editor.softWrapHangingIndent', 4) - expect(editor.getSoftWrapHangingIndentLength()).toBe(4) - }) + atom.config.set('editor.softWrapHangingIndent', 4); + expect(editor.getSoftWrapHangingIndentLength()).toBe(4); + }); - it('enables or disables preferred line length-based soft wrap based on the config', async function () { + it('enables or disables preferred line length-based soft wrap based on the config', async function() { editor.update({ softWrapped: true, preferredLineLength: 80, editorWidthInChars: 120, softWrapAtPreferredLineLength: true - }) + }); - expect(editor.getSoftWrapColumn()).toBe(80) + expect(editor.getSoftWrapColumn()).toBe(80); - atom.config.set('editor.softWrap', true) - atom.config.set('editor.softWrapAtPreferredLineLength', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getSoftWrapColumn()).toBe(120) + atom.config.set('editor.softWrap', true); + atom.config.set('editor.softWrapAtPreferredLineLength', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getSoftWrapColumn()).toBe(120); - atom.config.set('editor.softWrapAtPreferredLineLength', true) - expect(editor.getSoftWrapColumn()).toBe(80) - }) + atom.config.set('editor.softWrapAtPreferredLineLength', true); + expect(editor.getSoftWrapColumn()).toBe(80); + }); - it('allows for custom definition of maximum soft wrap based on config', async function () { + it('allows for custom definition of maximum soft wrap based on config', async function() { editor.update({ softWrapped: false, maxScreenLineLength: 1500 - }) + }); - expect(editor.getSoftWrapColumn()).toBe(1500) + expect(editor.getSoftWrapColumn()).toBe(1500); - atom.config.set('editor.softWrap', false) - atom.config.set('editor.maxScreenLineLength', 500) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getSoftWrapColumn()).toBe(500) - }) + atom.config.set('editor.softWrap', false); + atom.config.set('editor.maxScreenLineLength', 500); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getSoftWrapColumn()).toBe(500); + }); - it('sets the preferred line length based on the config', async function () { - editor.update({ preferredLineLength: 80 }) - expect(editor.getPreferredLineLength()).toBe(80) + it('sets the preferred line length based on the config', async function() { + editor.update({ preferredLineLength: 80 }); + expect(editor.getPreferredLineLength()).toBe(80); - atom.config.set('editor.preferredLineLength', 110) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getPreferredLineLength()).toBe(110) + atom.config.set('editor.preferredLineLength', 110); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getPreferredLineLength()).toBe(110); - atom.config.set('editor.preferredLineLength', 80) - expect(editor.getPreferredLineLength()).toBe(80) - }) + atom.config.set('editor.preferredLineLength', 80); + expect(editor.getPreferredLineLength()).toBe(80); + }); - it('enables or disables auto-indent based on the config', async function () { - editor.update({ autoIndent: true }) - expect(editor.shouldAutoIndent()).toBe(true) + it('enables or disables auto-indent based on the config', async function() { + editor.update({ autoIndent: true }); + expect(editor.shouldAutoIndent()).toBe(true); - atom.config.set('editor.autoIndent', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.shouldAutoIndent()).toBe(false) + atom.config.set('editor.autoIndent', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.shouldAutoIndent()).toBe(false); - atom.config.set('editor.autoIndent', true) - expect(editor.shouldAutoIndent()).toBe(true) - }) + atom.config.set('editor.autoIndent', true); + expect(editor.shouldAutoIndent()).toBe(true); + }); - it('enables or disables auto-indent-on-paste based on the config', async function () { - editor.update({ autoIndentOnPaste: true }) - expect(editor.shouldAutoIndentOnPaste()).toBe(true) + it('enables or disables auto-indent-on-paste based on the config', async function() { + editor.update({ autoIndentOnPaste: true }); + expect(editor.shouldAutoIndentOnPaste()).toBe(true); - atom.config.set('editor.autoIndentOnPaste', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.shouldAutoIndentOnPaste()).toBe(false) + atom.config.set('editor.autoIndentOnPaste', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.shouldAutoIndentOnPaste()).toBe(false); - atom.config.set('editor.autoIndentOnPaste', true) - expect(editor.shouldAutoIndentOnPaste()).toBe(true) - }) + atom.config.set('editor.autoIndentOnPaste', true); + expect(editor.shouldAutoIndentOnPaste()).toBe(true); + }); - it('enables or disables scrolling past the end of the buffer based on the config', async function () { - editor.update({ scrollPastEnd: true }) - expect(editor.getScrollPastEnd()).toBe(true) + it('enables or disables scrolling past the end of the buffer based on the config', async function() { + editor.update({ scrollPastEnd: true }); + expect(editor.getScrollPastEnd()).toBe(true); - atom.config.set('editor.scrollPastEnd', false) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getScrollPastEnd()).toBe(false) + atom.config.set('editor.scrollPastEnd', false); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getScrollPastEnd()).toBe(false); - atom.config.set('editor.scrollPastEnd', true) - expect(editor.getScrollPastEnd()).toBe(true) - }) + atom.config.set('editor.scrollPastEnd', true); + expect(editor.getScrollPastEnd()).toBe(true); + }); - it('sets the undo grouping interval based on the config', async function () { - editor.update({ undoGroupingInterval: 300 }) - expect(editor.getUndoGroupingInterval()).toBe(300) + it('sets the undo grouping interval based on the config', async function() { + editor.update({ undoGroupingInterval: 300 }); + expect(editor.getUndoGroupingInterval()).toBe(300); - atom.config.set('editor.undoGroupingInterval', 600) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getUndoGroupingInterval()).toBe(600) + atom.config.set('editor.undoGroupingInterval', 600); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getUndoGroupingInterval()).toBe(600); - atom.config.set('editor.undoGroupingInterval', 300) - expect(editor.getUndoGroupingInterval()).toBe(300) - }) + atom.config.set('editor.undoGroupingInterval', 300); + expect(editor.getUndoGroupingInterval()).toBe(300); + }); - it('sets the scroll sensitivity based on the config', async function () { - editor.update({ scrollSensitivity: 50 }) - expect(editor.getScrollSensitivity()).toBe(50) + it('sets the scroll sensitivity based on the config', async function() { + editor.update({ scrollSensitivity: 50 }); + expect(editor.getScrollSensitivity()).toBe(50); - atom.config.set('editor.scrollSensitivity', 60) - registry.maintainConfig(editor) - await initialPackageActivation - expect(editor.getScrollSensitivity()).toBe(60) + atom.config.set('editor.scrollSensitivity', 60); + registry.maintainConfig(editor); + await initialPackageActivation; + expect(editor.getScrollSensitivity()).toBe(60); - atom.config.set('editor.scrollSensitivity', 70) - expect(editor.getScrollSensitivity()).toBe(70) - }) + atom.config.set('editor.scrollSensitivity', 70); + expect(editor.getScrollSensitivity()).toBe(70); + }); - describe('when called twice with a given editor', function () { - it('does nothing the second time', async function () { - editor.update({ scrollSensitivity: 50 }) + describe('when called twice with a given editor', function() { + it('does nothing the second time', async function() { + editor.update({ scrollSensitivity: 50 }); - const disposable1 = registry.maintainConfig(editor) - const disposable2 = registry.maintainConfig(editor) - await initialPackageActivation + const disposable1 = registry.maintainConfig(editor); + const disposable2 = registry.maintainConfig(editor); + await initialPackageActivation; - atom.config.set('editor.scrollSensitivity', 60) - expect(editor.getScrollSensitivity()).toBe(60) + atom.config.set('editor.scrollSensitivity', 60); + expect(editor.getScrollSensitivity()).toBe(60); - disposable2.dispose() - atom.config.set('editor.scrollSensitivity', 70) - expect(editor.getScrollSensitivity()).toBe(70) + disposable2.dispose(); + atom.config.set('editor.scrollSensitivity', 70); + expect(editor.getScrollSensitivity()).toBe(70); - disposable1.dispose() - atom.config.set('editor.scrollSensitivity', 80) - expect(editor.getScrollSensitivity()).toBe(70) - }) - }) - }) -}) + disposable1.dispose(); + atom.config.set('editor.scrollSensitivity', 80); + expect(editor.getScrollSensitivity()).toBe(70); + }); + }); + }); +}); -function getSubscriptionCount (editor) { +function getSubscriptionCount(editor) { return ( editor.emitter.getTotalListenerCount() + editor.tokenizedBuffer.emitter.getTotalListenerCount() + editor.buffer.emitter.getTotalListenerCount() + editor.displayLayer.emitter.getTotalListenerCount() - ) + ); } -function retainedEditorCount (registry) { - const editors = new Set() - registry.editors.forEach(e => editors.add(e)) - registry.editorsWithMaintainedConfig.forEach(e => editors.add(e)) - registry.editorsWithMaintainedGrammar.forEach(e => editors.add(e)) - return editors.size +function retainedEditorCount(registry) { + const editors = new Set(); + registry.editors.forEach(e => editors.add(e)); + registry.editorsWithMaintainedConfig.forEach(e => editors.add(e)); + registry.editorsWithMaintainedGrammar.forEach(e => editors.add(e)); + return editors.size; } diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index c36755231..009bdf0a3 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -1,63 +1,63 @@ -const fs = require('fs') -const path = require('path') -const temp = require('temp').track() -const dedent = require('dedent') -const { clipboard } = require('electron') -const TextEditor = require('../src/text-editor') -const TextBuffer = require('text-buffer') -const TextMateLanguageMode = require('../src/text-mate-language-mode') -const TreeSitterLanguageMode = require('../src/tree-sitter-language-mode') +const fs = require('fs'); +const path = require('path'); +const temp = require('temp').track(); +const dedent = require('dedent'); +const { clipboard } = require('electron'); +const TextEditor = require('../src/text-editor'); +const TextBuffer = require('text-buffer'); +const TextMateLanguageMode = require('../src/text-mate-language-mode'); +const TreeSitterLanguageMode = require('../src/tree-sitter-language-mode'); describe('TextEditor', () => { - let buffer, editor, lineLengths + let buffer, editor, lineLengths; beforeEach(async () => { - editor = await atom.workspace.open('sample.js') - buffer = editor.buffer - editor.update({ autoIndent: false }) - lineLengths = buffer.getLines().map(line => line.length) - await atom.packages.activatePackage('language-javascript') - }) + editor = await atom.workspace.open('sample.js'); + buffer = editor.buffer; + editor.update({ autoIndent: false }); + lineLengths = buffer.getLines().map(line => line.length); + await atom.packages.activatePackage('language-javascript'); + }); it('generates unique ids for each editor', async () => { // Deserialized editors are initialized with the serialized id. We can // initialize an editor with what we expect to be the next id: - const deserialized = new TextEditor({ id: editor.id + 1 }) - expect(deserialized.id).toEqual(editor.id + 1) + const deserialized = new TextEditor({ id: editor.id + 1 }); + expect(deserialized.id).toEqual(editor.id + 1); // The id generator should skip the id used up by the deserialized one: - const fresh = new TextEditor() - expect(fresh.id).toNotEqual(deserialized.id) - }) + const fresh = new TextEditor(); + expect(fresh.id).toNotEqual(deserialized.id); + }); describe('when the editor is deserialized', () => { it('restores selections and folds based on markers in the buffer', async () => { - editor.setSelectedBufferRange([[1, 2], [3, 4]]) - editor.addSelectionForBufferRange([[5, 6], [7, 5]], { reversed: true }) - editor.foldBufferRow(4) - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() + editor.setSelectedBufferRange([[1, 2], [3, 4]]); + editor.addSelectionForBufferRange([[5, 6], [7, 5]], { reversed: true }); + editor.foldBufferRow(4); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); - const buffer2 = await TextBuffer.deserialize(editor.buffer.serialize()) + const buffer2 = await TextBuffer.deserialize(editor.buffer.serialize()); const editor2 = TextEditor.deserialize(editor.serialize(), { assert: atom.assert, textEditors: atom.textEditors, project: { - bufferForIdSync () { - return buffer2 + bufferForIdSync() { + return buffer2; } } - }) + }); - expect(editor2.id).toBe(editor.id) - expect(editor2.getBuffer().getPath()).toBe(editor.getBuffer().getPath()) + expect(editor2.id).toBe(editor.id); + expect(editor2.getBuffer().getPath()).toBe(editor.getBuffer().getPath()); expect(editor2.getSelectedBufferRanges()).toEqual([ [[1, 2], [3, 4]], [[5, 6], [7, 5]] - ]) - expect(editor2.getSelections()[1].isReversed()).toBeTruthy() - expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy() - editor2.destroy() - }) + ]); + expect(editor2.getSelections()[1].isReversed()).toBeTruthy(); + expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy(); + editor2.destroy(); + }); it("restores the editor's layout configuration", async () => { editor.update({ @@ -70,110 +70,110 @@ describe('TextEditor', () => { invisibles: { space: 'S' }, showInvisibles: true, editorWidthInChars: 120 - }) + }); // Force buffer and display layer to be deserialized as well, rather than // reusing the same buffer instance - const buffer2 = await TextBuffer.deserialize(editor.buffer.serialize()) + const buffer2 = await TextBuffer.deserialize(editor.buffer.serialize()); const editor2 = TextEditor.deserialize(editor.serialize(), { assert: atom.assert, textEditors: atom.textEditors, project: { - bufferForIdSync () { - return buffer2 + bufferForIdSync() { + return buffer2; } } - }) + }); - expect(editor2.getSoftTabs()).toBe(editor.getSoftTabs()) - expect(editor2.hasAtomicSoftTabs()).toBe(editor.hasAtomicSoftTabs()) - expect(editor2.getTabLength()).toBe(editor.getTabLength()) - expect(editor2.getSoftWrapColumn()).toBe(editor.getSoftWrapColumn()) + expect(editor2.getSoftTabs()).toBe(editor.getSoftTabs()); + expect(editor2.hasAtomicSoftTabs()).toBe(editor.hasAtomicSoftTabs()); + expect(editor2.getTabLength()).toBe(editor.getTabLength()); + expect(editor2.getSoftWrapColumn()).toBe(editor.getSoftWrapColumn()); expect(editor2.getSoftWrapHangingIndentLength()).toBe( editor.getSoftWrapHangingIndentLength() - ) - expect(editor2.getInvisibles()).toEqual(editor.getInvisibles()) + ); + expect(editor2.getInvisibles()).toEqual(editor.getInvisibles()); expect(editor2.getEditorWidthInChars()).toBe( editor.getEditorWidthInChars() - ) - expect(editor2.displayLayer.tabLength).toBe(editor2.getTabLength()) + ); + expect(editor2.displayLayer.tabLength).toBe(editor2.getTabLength()); expect(editor2.displayLayer.softWrapColumn).toBe( editor2.getSoftWrapColumn() - ) - }) + ); + }); it('ignores buffers with retired IDs', () => { const editor2 = TextEditor.deserialize(editor.serialize(), { assert: atom.assert, textEditors: atom.textEditors, project: { - bufferForIdSync () { - return null + bufferForIdSync() { + return null; } } - }) + }); - expect(editor2).toBeNull() - }) - }) + expect(editor2).toBeNull(); + }); + }); describe('.copy()', () => { it('returns a different editor with the same initial state', () => { - expect(editor.getAutoHeight()).toBeFalsy() - expect(editor.getAutoWidth()).toBeFalsy() - expect(editor.getShowCursorOnSelection()).toBeTruthy() + expect(editor.getAutoHeight()).toBeFalsy(); + expect(editor.getAutoWidth()).toBeFalsy(); + expect(editor.getShowCursorOnSelection()).toBeTruthy(); - const element = editor.getElement() - element.setHeight(100) - element.setWidth(100) - jasmine.attachToDOM(element) + const element = editor.getElement(); + element.setHeight(100); + element.setWidth(100); + jasmine.attachToDOM(element); - editor.update({ showCursorOnSelection: false }) - editor.setSelectedBufferRange([[1, 2], [3, 4]]) - editor.addSelectionForBufferRange([[5, 6], [7, 8]], { reversed: true }) - editor.setScrollTopRow(3) - expect(editor.getScrollTopRow()).toBe(3) - editor.setScrollLeftColumn(4) - expect(editor.getScrollLeftColumn()).toBe(4) - editor.foldBufferRow(4) - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() + editor.update({ showCursorOnSelection: false }); + editor.setSelectedBufferRange([[1, 2], [3, 4]]); + editor.addSelectionForBufferRange([[5, 6], [7, 8]], { reversed: true }); + editor.setScrollTopRow(3); + expect(editor.getScrollTopRow()).toBe(3); + editor.setScrollLeftColumn(4); + expect(editor.getScrollLeftColumn()).toBe(4); + editor.foldBufferRow(4); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); - const editor2 = editor.copy() - const element2 = editor2.getElement() - element2.setHeight(100) - element2.setWidth(100) - jasmine.attachToDOM(element2) - expect(editor2.id).not.toBe(editor.id) + const editor2 = editor.copy(); + const element2 = editor2.getElement(); + element2.setHeight(100); + element2.setWidth(100); + jasmine.attachToDOM(element2); + expect(editor2.id).not.toBe(editor.id); expect(editor2.getSelectedBufferRanges()).toEqual( editor.getSelectedBufferRanges() - ) - expect(editor2.getSelections()[1].isReversed()).toBeTruthy() - expect(editor2.getScrollTopRow()).toBe(3) - expect(editor2.getScrollLeftColumn()).toBe(4) - expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor2.getAutoWidth()).toBe(false) - expect(editor2.getAutoHeight()).toBe(false) - expect(editor2.getShowCursorOnSelection()).toBeFalsy() + ); + expect(editor2.getSelections()[1].isReversed()).toBeTruthy(); + expect(editor2.getScrollTopRow()).toBe(3); + expect(editor2.getScrollLeftColumn()).toBe(4); + expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor2.getAutoWidth()).toBe(false); + expect(editor2.getAutoHeight()).toBe(false); + expect(editor2.getShowCursorOnSelection()).toBeFalsy(); // editor2 can now diverge from its origin edit session - editor2.getLastSelection().setBufferRange([[2, 1], [4, 3]]) + editor2.getLastSelection().setBufferRange([[2, 1], [4, 3]]); expect(editor2.getSelectedBufferRanges()).not.toEqual( editor.getSelectedBufferRanges() - ) - editor2.unfoldBufferRow(4) + ); + editor2.unfoldBufferRow(4); expect(editor2.isFoldedAtBufferRow(4)).not.toBe( editor.isFoldedAtBufferRow(4) - ) - }) - }) + ); + }); + }); describe('.update()', () => { it('updates the editor with the supplied config parameters', () => { - let changeSpy - const { element } = editor // force element initialization - element.setUpdatedSynchronously(false) - editor.update({ showInvisibles: true }) - editor.onDidChange((changeSpy = jasmine.createSpy('onDidChange'))) + let changeSpy; + const { element } = editor; // force element initialization + element.setUpdatedSynchronously(false); + editor.update({ showInvisibles: true }); + editor.onDidChange((changeSpy = jasmine.createSpy('onDidChange'))); const returnedPromise = editor.update({ tabLength: 6, @@ -186,1202 +186,1205 @@ describe('TextEditor', () => { scrollPastEnd: true, autoHeight: false, maxScreenLineLength: 1000 - }) + }); - expect(returnedPromise).toBe(element.component.getNextUpdatePromise()) - expect(changeSpy.callCount).toBe(1) - expect(editor.getTabLength()).toBe(6) - expect(editor.getSoftTabs()).toBe(false) - expect(editor.isSoftWrapped()).toBe(true) - expect(editor.getEditorWidthInChars()).toBe(40) - expect(editor.getInvisibles()).toEqual({}) - expect(editor.isMini()).toBe(false) - expect(editor.isLineNumberGutterVisible()).toBe(false) - expect(editor.getScrollPastEnd()).toBe(true) - expect(editor.getAutoHeight()).toBe(false) - }) - }) + expect(returnedPromise).toBe(element.component.getNextUpdatePromise()); + expect(changeSpy.callCount).toBe(1); + expect(editor.getTabLength()).toBe(6); + expect(editor.getSoftTabs()).toBe(false); + expect(editor.isSoftWrapped()).toBe(true); + expect(editor.getEditorWidthInChars()).toBe(40); + expect(editor.getInvisibles()).toEqual({}); + expect(editor.isMini()).toBe(false); + expect(editor.isLineNumberGutterVisible()).toBe(false); + expect(editor.getScrollPastEnd()).toBe(true); + expect(editor.getAutoHeight()).toBe(false); + }); + }); describe('title', () => { describe('.getTitle()', () => { it("uses the basename of the buffer's path as its title, or 'untitled' if the path is undefined", () => { - expect(editor.getTitle()).toBe('sample.js') - buffer.setPath(undefined) - expect(editor.getTitle()).toBe('untitled') - }) - }) + expect(editor.getTitle()).toBe('sample.js'); + buffer.setPath(undefined); + expect(editor.getTitle()).toBe('untitled'); + }); + }); describe('.getLongTitle()', () => { it('returns file name when there is no opened file with identical name', () => { - expect(editor.getLongTitle()).toBe('sample.js') - buffer.setPath(undefined) - expect(editor.getLongTitle()).toBe('untitled') - }) + expect(editor.getLongTitle()).toBe('sample.js'); + buffer.setPath(undefined); + expect(editor.getLongTitle()).toBe('untitled'); + }); it("returns '' when opened files have identical file names", async () => { const editor1 = await atom.workspace.open( path.join('sample-theme-1', 'readme') - ) + ); const editor2 = await atom.workspace.open( path.join('sample-theme-2', 'readme') - ) - expect(editor1.getLongTitle()).toBe('readme \u2014 sample-theme-1') - expect(editor2.getLongTitle()).toBe('readme \u2014 sample-theme-2') - }) + ); + expect(editor1.getLongTitle()).toBe('readme \u2014 sample-theme-1'); + expect(editor2.getLongTitle()).toBe('readme \u2014 sample-theme-2'); + }); it("returns '' when opened files have identical file names in subdirectories", async () => { - const path1 = path.join('sample-theme-1', 'src', 'js') - const path2 = path.join('sample-theme-2', 'src', 'js') - const editor1 = await atom.workspace.open(path.join(path1, 'main.js')) - const editor2 = await atom.workspace.open(path.join(path2, 'main.js')) - expect(editor1.getLongTitle()).toBe(`main.js \u2014 ${path1}`) - expect(editor2.getLongTitle()).toBe(`main.js \u2014 ${path2}`) - }) + const path1 = path.join('sample-theme-1', 'src', 'js'); + const path2 = path.join('sample-theme-2', 'src', 'js'); + const editor1 = await atom.workspace.open(path.join(path1, 'main.js')); + const editor2 = await atom.workspace.open(path.join(path2, 'main.js')); + expect(editor1.getLongTitle()).toBe(`main.js \u2014 ${path1}`); + expect(editor2.getLongTitle()).toBe(`main.js \u2014 ${path2}`); + }); it("returns '' when opened files have identical file and same parent dir name", async () => { const editor1 = await atom.workspace.open( path.join('sample-theme-2', 'src', 'js', 'main.js') - ) + ); const editor2 = await atom.workspace.open( path.join('sample-theme-2', 'src', 'js', 'plugin', 'main.js') - ) - expect(editor1.getLongTitle()).toBe('main.js \u2014 js') + ); + expect(editor1.getLongTitle()).toBe('main.js \u2014 js'); expect(editor2.getLongTitle()).toBe( `main.js \u2014 ${path.join('js', 'plugin')}` - ) - }) + ); + }); it('returns the filename when the editor is not in the workspace', async () => { editor.onDidDestroy(() => { - expect(editor.getLongTitle()).toBe('sample.js') - }) + expect(editor.getLongTitle()).toBe('sample.js'); + }); - await atom.workspace.getActivePane().close() - expect(editor.isDestroyed()).toBe(true) - }) - }) + await atom.workspace.getActivePane().close(); + expect(editor.isDestroyed()).toBe(true); + }); + }); it('notifies ::onDidChangeTitle observers when the underlying buffer path changes', () => { - const observed = [] - editor.onDidChangeTitle(title => observed.push(title)) + const observed = []; + editor.onDidChangeTitle(title => observed.push(title)); - buffer.setPath('/foo/bar/baz.txt') - buffer.setPath(undefined) + buffer.setPath('/foo/bar/baz.txt'); + buffer.setPath(undefined); - expect(observed).toEqual(['baz.txt', 'untitled']) - }) - }) + expect(observed).toEqual(['baz.txt', 'untitled']); + }); + }); describe('path', () => { it('notifies ::onDidChangePath observers when the underlying buffer path changes', () => { - const observed = [] - editor.onDidChangePath(filePath => observed.push(filePath)) + const observed = []; + editor.onDidChangePath(filePath => observed.push(filePath)); - buffer.setPath(__filename) - buffer.setPath(undefined) + buffer.setPath(__filename); + buffer.setPath(undefined); - expect(observed).toEqual([__filename, undefined]) - }) - }) + expect(observed).toEqual([__filename, undefined]); + }); + }); describe('encoding', () => { it('notifies ::onDidChangeEncoding observers when the editor encoding changes', () => { - const observed = [] - editor.onDidChangeEncoding(encoding => observed.push(encoding)) + const observed = []; + editor.onDidChangeEncoding(encoding => observed.push(encoding)); - editor.setEncoding('utf16le') - editor.setEncoding('utf16le') - editor.setEncoding('utf16be') - editor.setEncoding() - editor.setEncoding() + editor.setEncoding('utf16le'); + editor.setEncoding('utf16le'); + editor.setEncoding('utf16be'); + editor.setEncoding(); + editor.setEncoding(); - expect(observed).toEqual(['utf16le', 'utf16be', 'utf8']) - }) - }) + expect(observed).toEqual(['utf16le', 'utf16be', 'utf8']); + }); + }); describe('cursor', () => { describe('.getLastCursor()', () => { it('returns the most recently created cursor', () => { - editor.addCursorAtScreenPosition([1, 0]) - const lastCursor = editor.addCursorAtScreenPosition([2, 0]) - expect(editor.getLastCursor()).toBe(lastCursor) - }) + editor.addCursorAtScreenPosition([1, 0]); + const lastCursor = editor.addCursorAtScreenPosition([2, 0]); + expect(editor.getLastCursor()).toBe(lastCursor); + }); it('creates a new cursor at (0, 0) if the last cursor has been destroyed', () => { - editor.getLastCursor().destroy() - expect(editor.getLastCursor().getBufferPosition()).toEqual([0, 0]) - }) - }) + editor.getLastCursor().destroy(); + expect(editor.getLastCursor().getBufferPosition()).toEqual([0, 0]); + }); + }); describe('.getCursors()', () => { it('creates a new cursor at (0, 0) if the last cursor has been destroyed', () => { - editor.getLastCursor().destroy() - expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 0]) - }) - }) + editor.getLastCursor().destroy(); + expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 0]); + }); + }); describe('when the cursor moves', () => { it('clears a goal column established by vertical movement', () => { - editor.setText('b') - editor.setCursorBufferPosition([0, 0]) - editor.insertNewline() - editor.moveUp() - editor.insertText('a') - editor.moveDown() - expect(editor.getCursorBufferPosition()).toEqual([1, 1]) - }) + editor.setText('b'); + editor.setCursorBufferPosition([0, 0]); + editor.insertNewline(); + editor.moveUp(); + editor.insertText('a'); + editor.moveDown(); + expect(editor.getCursorBufferPosition()).toEqual([1, 1]); + }); it('emits an event with the old position, new position, and the cursor that moved', () => { - const cursorCallback = jasmine.createSpy('cursor-changed-position') + const cursorCallback = jasmine.createSpy('cursor-changed-position'); const editorCallback = jasmine.createSpy( 'editor-changed-cursor-position' - ) + ); - editor.getLastCursor().onDidChangePosition(cursorCallback) - editor.onDidChangeCursorPosition(editorCallback) + editor.getLastCursor().onDidChangePosition(cursorCallback); + editor.onDidChangeCursorPosition(editorCallback); - editor.setCursorBufferPosition([2, 4]) + editor.setCursorBufferPosition([2, 4]); - expect(editorCallback).toHaveBeenCalled() - expect(cursorCallback).toHaveBeenCalled() - const eventObject = editorCallback.mostRecentCall.args[0] - expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject) + expect(editorCallback).toHaveBeenCalled(); + expect(cursorCallback).toHaveBeenCalled(); + const eventObject = editorCallback.mostRecentCall.args[0]; + expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject); - expect(eventObject.oldBufferPosition).toEqual([0, 0]) - expect(eventObject.oldScreenPosition).toEqual([0, 0]) - expect(eventObject.newBufferPosition).toEqual([2, 4]) - expect(eventObject.newScreenPosition).toEqual([2, 4]) - expect(eventObject.cursor).toBe(editor.getLastCursor()) - }) - }) + expect(eventObject.oldBufferPosition).toEqual([0, 0]); + expect(eventObject.oldScreenPosition).toEqual([0, 0]); + expect(eventObject.newBufferPosition).toEqual([2, 4]); + expect(eventObject.newScreenPosition).toEqual([2, 4]); + expect(eventObject.cursor).toBe(editor.getLastCursor()); + }); + }); describe('.setCursorScreenPosition(screenPosition)', () => { it('clears a goal column established by vertical movement', () => { // set a goal column by moving down - editor.setCursorScreenPosition({ row: 3, column: lineLengths[3] }) - editor.moveDown() - expect(editor.getCursorScreenPosition().column).not.toBe(6) + editor.setCursorScreenPosition({ row: 3, column: lineLengths[3] }); + editor.moveDown(); + expect(editor.getCursorScreenPosition().column).not.toBe(6); // clear the goal column by explicitly setting the cursor position - editor.setCursorScreenPosition([4, 6]) - expect(editor.getCursorScreenPosition().column).toBe(6) + editor.setCursorScreenPosition([4, 6]); + expect(editor.getCursorScreenPosition().column).toBe(6); - editor.moveDown() - expect(editor.getCursorScreenPosition().column).toBe(6) - }) + editor.moveDown(); + expect(editor.getCursorScreenPosition().column).toBe(6); + }); it('merges multiple cursors', () => { - editor.setCursorScreenPosition([0, 0]) - editor.addCursorAtScreenPosition([0, 1]) - const [cursor1] = editor.getCursors() - editor.setCursorScreenPosition([4, 7]) - expect(editor.getCursors().length).toBe(1) - expect(editor.getCursors()).toEqual([cursor1]) - expect(editor.getCursorScreenPosition()).toEqual([4, 7]) - }) + editor.setCursorScreenPosition([0, 0]); + editor.addCursorAtScreenPosition([0, 1]); + const [cursor1] = editor.getCursors(); + editor.setCursorScreenPosition([4, 7]); + expect(editor.getCursors().length).toBe(1); + expect(editor.getCursors()).toEqual([cursor1]); + expect(editor.getCursorScreenPosition()).toEqual([4, 7]); + }); describe('when soft-wrap is enabled and code is folded', () => { beforeEach(() => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(50) - editor.foldBufferRowRange(2, 3) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(50); + editor.foldBufferRowRange(2, 3); + }); it('positions the cursor at the buffer position that corresponds to the given screen position', () => { - editor.setCursorScreenPosition([9, 0]) - expect(editor.getCursorBufferPosition()).toEqual([8, 11]) - }) - }) - }) + editor.setCursorScreenPosition([9, 0]); + expect(editor.getCursorBufferPosition()).toEqual([8, 11]); + }); + }); + }); describe('.moveUp()', () => { it('moves the cursor up', () => { - editor.setCursorScreenPosition([2, 2]) - editor.moveUp() - expect(editor.getCursorScreenPosition()).toEqual([1, 2]) - }) + editor.setCursorScreenPosition([2, 2]); + editor.moveUp(); + expect(editor.getCursorScreenPosition()).toEqual([1, 2]); + }); it('retains the goal column across lines of differing length', () => { - expect(lineLengths[6]).toBeGreaterThan(32) - editor.setCursorScreenPosition({ row: 6, column: 32 }) + expect(lineLengths[6]).toBeGreaterThan(32); + editor.setCursorScreenPosition({ row: 6, column: 32 }); - editor.moveUp() - expect(editor.getCursorScreenPosition().column).toBe(lineLengths[5]) + editor.moveUp(); + expect(editor.getCursorScreenPosition().column).toBe(lineLengths[5]); - editor.moveUp() - expect(editor.getCursorScreenPosition().column).toBe(lineLengths[4]) + editor.moveUp(); + expect(editor.getCursorScreenPosition().column).toBe(lineLengths[4]); - editor.moveUp() - expect(editor.getCursorScreenPosition().column).toBe(32) - }) + editor.moveUp(); + expect(editor.getCursorScreenPosition().column).toBe(32); + }); describe('when the cursor is on the first line', () => { it('moves the cursor to the beginning of the line, but retains the goal column', () => { - editor.setCursorScreenPosition([0, 4]) - editor.moveUp() - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) + editor.setCursorScreenPosition([0, 4]); + editor.moveUp(); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); - editor.moveDown() - expect(editor.getCursorScreenPosition()).toEqual([1, 4]) - }) - }) + editor.moveDown(); + expect(editor.getCursorScreenPosition()).toEqual([1, 4]); + }); + }); describe('when there is a selection', () => { - beforeEach(() => editor.setSelectedBufferRange([[4, 9], [5, 10]])) + beforeEach(() => editor.setSelectedBufferRange([[4, 9], [5, 10]])); it('moves above the selection', () => { - const cursor = editor.getLastCursor() - editor.moveUp() - expect(cursor.getBufferPosition()).toEqual([3, 9]) - }) - }) + const cursor = editor.getLastCursor(); + editor.moveUp(); + expect(cursor.getBufferPosition()).toEqual([3, 9]); + }); + }); it('merges cursors when they overlap', () => { - editor.addCursorAtScreenPosition([1, 0]) - const [cursor1] = editor.getCursors() + editor.addCursorAtScreenPosition([1, 0]); + const [cursor1] = editor.getCursors(); - editor.moveUp() - expect(editor.getCursors()).toEqual([cursor1]) - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - }) + editor.moveUp(); + expect(editor.getCursors()).toEqual([cursor1]); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + }); describe('when the cursor was moved down from the beginning of an indented soft-wrapped line', () => { it('moves to the beginning of the previous line', () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(50) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(50); - editor.setCursorScreenPosition([3, 0]) - editor.moveDown() - editor.moveDown() - editor.moveUp() - expect(editor.getCursorScreenPosition()).toEqual([4, 4]) - }) - }) - }) + editor.setCursorScreenPosition([3, 0]); + editor.moveDown(); + editor.moveDown(); + editor.moveUp(); + expect(editor.getCursorScreenPosition()).toEqual([4, 4]); + }); + }); + }); describe('.moveDown()', () => { it('moves the cursor down', () => { - editor.setCursorScreenPosition([2, 2]) - editor.moveDown() - expect(editor.getCursorScreenPosition()).toEqual([3, 2]) - }) + editor.setCursorScreenPosition([2, 2]); + editor.moveDown(); + expect(editor.getCursorScreenPosition()).toEqual([3, 2]); + }); it('retains the goal column across lines of differing length', () => { - editor.setCursorScreenPosition({ row: 3, column: lineLengths[3] }) + editor.setCursorScreenPosition({ row: 3, column: lineLengths[3] }); - editor.moveDown() - expect(editor.getCursorScreenPosition().column).toBe(lineLengths[4]) + editor.moveDown(); + expect(editor.getCursorScreenPosition().column).toBe(lineLengths[4]); - editor.moveDown() - expect(editor.getCursorScreenPosition().column).toBe(lineLengths[5]) + editor.moveDown(); + expect(editor.getCursorScreenPosition().column).toBe(lineLengths[5]); - editor.moveDown() - expect(editor.getCursorScreenPosition().column).toBe(lineLengths[3]) - }) + editor.moveDown(); + expect(editor.getCursorScreenPosition().column).toBe(lineLengths[3]); + }); describe('when the cursor is on the last line', () => { it('moves the cursor to the end of line, but retains the goal column when moving back up', () => { - const lastLineIndex = buffer.getLines().length - 1 - const lastLine = buffer.lineForRow(lastLineIndex) - expect(lastLine.length).toBeGreaterThan(0) + const lastLineIndex = buffer.getLines().length - 1; + const lastLine = buffer.lineForRow(lastLineIndex); + expect(lastLine.length).toBeGreaterThan(0); editor.setCursorScreenPosition({ row: lastLineIndex, column: editor.getTabLength() - }) - editor.moveDown() + }); + editor.moveDown(); expect(editor.getCursorScreenPosition()).toEqual({ row: lastLineIndex, column: lastLine.length - }) + }); - editor.moveUp() + editor.moveUp(); expect(editor.getCursorScreenPosition().column).toBe( editor.getTabLength() - ) - }) + ); + }); it('retains a goal column of 0 when moving back up', () => { - const lastLineIndex = buffer.getLines().length - 1 - const lastLine = buffer.lineForRow(lastLineIndex) - expect(lastLine.length).toBeGreaterThan(0) + const lastLineIndex = buffer.getLines().length - 1; + const lastLine = buffer.lineForRow(lastLineIndex); + expect(lastLine.length).toBeGreaterThan(0); - editor.setCursorScreenPosition({ row: lastLineIndex, column: 0 }) - editor.moveDown() - editor.moveUp() - expect(editor.getCursorScreenPosition().column).toBe(0) - }) - }) + editor.setCursorScreenPosition({ row: lastLineIndex, column: 0 }); + editor.moveDown(); + editor.moveUp(); + expect(editor.getCursorScreenPosition().column).toBe(0); + }); + }); describe('when the cursor is at the beginning of an indented soft-wrapped line', () => { it("moves to the beginning of the line's continuation on the next screen row", () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(50) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(50); - editor.setCursorScreenPosition([3, 0]) - editor.moveDown() - expect(editor.getCursorScreenPosition()).toEqual([4, 4]) - }) - }) + editor.setCursorScreenPosition([3, 0]); + editor.moveDown(); + expect(editor.getCursorScreenPosition()).toEqual([4, 4]); + }); + }); describe('when there is a selection', () => { - beforeEach(() => editor.setSelectedBufferRange([[4, 9], [5, 10]])) + beforeEach(() => editor.setSelectedBufferRange([[4, 9], [5, 10]])); it('moves below the selection', () => { - const cursor = editor.getLastCursor() - editor.moveDown() - expect(cursor.getBufferPosition()).toEqual([6, 10]) - }) - }) + const cursor = editor.getLastCursor(); + editor.moveDown(); + expect(cursor.getBufferPosition()).toEqual([6, 10]); + }); + }); it('merges cursors when they overlap', () => { - editor.setCursorScreenPosition([12, 2]) - editor.addCursorAtScreenPosition([11, 2]) - const [cursor1] = editor.getCursors() + editor.setCursorScreenPosition([12, 2]); + editor.addCursorAtScreenPosition([11, 2]); + const [cursor1] = editor.getCursors(); - editor.moveDown() - expect(editor.getCursors()).toEqual([cursor1]) - expect(cursor1.getBufferPosition()).toEqual([12, 2]) - }) - }) + editor.moveDown(); + expect(editor.getCursors()).toEqual([cursor1]); + expect(cursor1.getBufferPosition()).toEqual([12, 2]); + }); + }); describe('.moveLeft()', () => { it('moves the cursor by one column to the left', () => { - editor.setCursorScreenPosition([1, 8]) - editor.moveLeft() - expect(editor.getCursorScreenPosition()).toEqual([1, 7]) - }) + editor.setCursorScreenPosition([1, 8]); + editor.moveLeft(); + expect(editor.getCursorScreenPosition()).toEqual([1, 7]); + }); it('moves the cursor by n columns to the left', () => { - editor.setCursorScreenPosition([1, 8]) - editor.moveLeft(4) - expect(editor.getCursorScreenPosition()).toEqual([1, 4]) - }) + editor.setCursorScreenPosition([1, 8]); + editor.moveLeft(4); + expect(editor.getCursorScreenPosition()).toEqual([1, 4]); + }); it('moves the cursor by two rows up when the columnCount is longer than an entire line', () => { - editor.setCursorScreenPosition([2, 2]) - editor.moveLeft(34) - expect(editor.getCursorScreenPosition()).toEqual([0, 29]) - }) + editor.setCursorScreenPosition([2, 2]); + editor.moveLeft(34); + expect(editor.getCursorScreenPosition()).toEqual([0, 29]); + }); it('moves the cursor to the beginning columnCount is longer than the position in the buffer', () => { - editor.setCursorScreenPosition([1, 0]) - editor.moveLeft(100) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - }) + editor.setCursorScreenPosition([1, 0]); + editor.moveLeft(100); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + }); describe('when the cursor is in the first column', () => { describe('when there is a previous line', () => { it('wraps to the end of the previous line', () => { - editor.setCursorScreenPosition({ row: 1, column: 0 }) - editor.moveLeft() + editor.setCursorScreenPosition({ row: 1, column: 0 }); + editor.moveLeft(); expect(editor.getCursorScreenPosition()).toEqual({ row: 0, column: buffer.lineForRow(0).length - }) - }) + }); + }); it('moves the cursor by one row up and n columns to the left', () => { - editor.setCursorScreenPosition([1, 0]) - editor.moveLeft(4) - expect(editor.getCursorScreenPosition()).toEqual([0, 26]) - }) - }) + editor.setCursorScreenPosition([1, 0]); + editor.moveLeft(4); + expect(editor.getCursorScreenPosition()).toEqual([0, 26]); + }); + }); describe('when the next line is empty', () => { it('wraps to the beginning of the previous line', () => { - editor.setCursorScreenPosition([11, 0]) - editor.moveLeft() - expect(editor.getCursorScreenPosition()).toEqual([10, 0]) - }) - }) + editor.setCursorScreenPosition([11, 0]); + editor.moveLeft(); + expect(editor.getCursorScreenPosition()).toEqual([10, 0]); + }); + }); describe('when line is wrapped and follow previous line indentation', () => { beforeEach(() => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(50) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(50); + }); it('wraps to the end of the previous line', () => { - editor.setCursorScreenPosition([4, 4]) - editor.moveLeft() - expect(editor.getCursorScreenPosition()).toEqual([3, 46]) - }) - }) + editor.setCursorScreenPosition([4, 4]); + editor.moveLeft(); + expect(editor.getCursorScreenPosition()).toEqual([3, 46]); + }); + }); describe('when the cursor is on the first line', () => { it('remains in the same position (0,0)', () => { - editor.setCursorScreenPosition({ row: 0, column: 0 }) - editor.moveLeft() + editor.setCursorScreenPosition({ row: 0, column: 0 }); + editor.moveLeft(); expect(editor.getCursorScreenPosition()).toEqual({ row: 0, column: 0 - }) - }) + }); + }); it('remains in the same position (0,0) when columnCount is specified', () => { - editor.setCursorScreenPosition([0, 0]) - editor.moveLeft(4) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - }) - }) - }) + editor.setCursorScreenPosition([0, 0]); + editor.moveLeft(4); + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + }); + }); + }); describe('when softTabs is enabled and the cursor is preceded by leading whitespace', () => { it('skips tabLength worth of whitespace at a time', () => { - editor.setCursorBufferPosition([5, 6]) + editor.setCursorBufferPosition([5, 6]); - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([5, 4]) - }) - }) + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([5, 4]); + }); + }); describe('when there is a selection', () => { - beforeEach(() => editor.setSelectedBufferRange([[5, 22], [5, 27]])) + beforeEach(() => editor.setSelectedBufferRange([[5, 22], [5, 27]])); it('moves to the left of the selection', () => { - const cursor = editor.getLastCursor() - editor.moveLeft() - expect(cursor.getBufferPosition()).toEqual([5, 22]) + const cursor = editor.getLastCursor(); + editor.moveLeft(); + expect(cursor.getBufferPosition()).toEqual([5, 22]); - editor.moveLeft() - expect(cursor.getBufferPosition()).toEqual([5, 21]) - }) - }) + editor.moveLeft(); + expect(cursor.getBufferPosition()).toEqual([5, 21]); + }); + }); it('merges cursors when they overlap', () => { - editor.setCursorScreenPosition([0, 0]) - editor.addCursorAtScreenPosition([0, 1]) + editor.setCursorScreenPosition([0, 0]); + editor.addCursorAtScreenPosition([0, 1]); - const [cursor1] = editor.getCursors() - editor.moveLeft() - expect(editor.getCursors()).toEqual([cursor1]) - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - }) - }) + const [cursor1] = editor.getCursors(); + editor.moveLeft(); + expect(editor.getCursors()).toEqual([cursor1]); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + }); + }); describe('.moveRight()', () => { it('moves the cursor by one column to the right', () => { - editor.setCursorScreenPosition([3, 3]) - editor.moveRight() - expect(editor.getCursorScreenPosition()).toEqual([3, 4]) - }) + editor.setCursorScreenPosition([3, 3]); + editor.moveRight(); + expect(editor.getCursorScreenPosition()).toEqual([3, 4]); + }); it('moves the cursor by n columns to the right', () => { - editor.setCursorScreenPosition([3, 7]) - editor.moveRight(4) - expect(editor.getCursorScreenPosition()).toEqual([3, 11]) - }) + editor.setCursorScreenPosition([3, 7]); + editor.moveRight(4); + expect(editor.getCursorScreenPosition()).toEqual([3, 11]); + }); it('moves the cursor by two rows down when the columnCount is longer than an entire line', () => { - editor.setCursorScreenPosition([0, 29]) - editor.moveRight(34) - expect(editor.getCursorScreenPosition()).toEqual([2, 2]) - }) + editor.setCursorScreenPosition([0, 29]); + editor.moveRight(34); + expect(editor.getCursorScreenPosition()).toEqual([2, 2]); + }); it('moves the cursor to the end of the buffer when columnCount is longer than the number of characters following the cursor position', () => { - editor.setCursorScreenPosition([11, 5]) - editor.moveRight(100) - expect(editor.getCursorScreenPosition()).toEqual([12, 2]) - }) + editor.setCursorScreenPosition([11, 5]); + editor.moveRight(100); + expect(editor.getCursorScreenPosition()).toEqual([12, 2]); + }); describe('when the cursor is on the last column of a line', () => { describe('when there is a subsequent line', () => { it('wraps to the beginning of the next line', () => { - editor.setCursorScreenPosition([0, buffer.lineForRow(0).length]) - editor.moveRight() - expect(editor.getCursorScreenPosition()).toEqual([1, 0]) - }) + editor.setCursorScreenPosition([0, buffer.lineForRow(0).length]); + editor.moveRight(); + expect(editor.getCursorScreenPosition()).toEqual([1, 0]); + }); it('moves the cursor by one row down and n columns to the right', () => { - editor.setCursorScreenPosition([0, buffer.lineForRow(0).length]) - editor.moveRight(4) - expect(editor.getCursorScreenPosition()).toEqual([1, 3]) - }) - }) + editor.setCursorScreenPosition([0, buffer.lineForRow(0).length]); + editor.moveRight(4); + expect(editor.getCursorScreenPosition()).toEqual([1, 3]); + }); + }); describe('when the next line is empty', () => { it('wraps to the beginning of the next line', () => { - editor.setCursorScreenPosition([9, 4]) - editor.moveRight() - expect(editor.getCursorScreenPosition()).toEqual([10, 0]) - }) - }) + editor.setCursorScreenPosition([9, 4]); + editor.moveRight(); + expect(editor.getCursorScreenPosition()).toEqual([10, 0]); + }); + }); describe('when the cursor is on the last line', () => { it('remains in the same position', () => { - const lastLineIndex = buffer.getLines().length - 1 - const lastLine = buffer.lineForRow(lastLineIndex) - expect(lastLine.length).toBeGreaterThan(0) + const lastLineIndex = buffer.getLines().length - 1; + const lastLine = buffer.lineForRow(lastLineIndex); + expect(lastLine.length).toBeGreaterThan(0); - const lastPosition = { row: lastLineIndex, column: lastLine.length } - editor.setCursorScreenPosition(lastPosition) - editor.moveRight() + const lastPosition = { + row: lastLineIndex, + column: lastLine.length + }; + editor.setCursorScreenPosition(lastPosition); + editor.moveRight(); - expect(editor.getCursorScreenPosition()).toEqual(lastPosition) - }) - }) - }) + expect(editor.getCursorScreenPosition()).toEqual(lastPosition); + }); + }); + }); describe('when there is a selection', () => { - beforeEach(() => editor.setSelectedBufferRange([[5, 22], [5, 27]])) + beforeEach(() => editor.setSelectedBufferRange([[5, 22], [5, 27]])); it('moves to the left of the selection', () => { - const cursor = editor.getLastCursor() - editor.moveRight() - expect(cursor.getBufferPosition()).toEqual([5, 27]) + const cursor = editor.getLastCursor(); + editor.moveRight(); + expect(cursor.getBufferPosition()).toEqual([5, 27]); - editor.moveRight() - expect(cursor.getBufferPosition()).toEqual([5, 28]) - }) - }) + editor.moveRight(); + expect(cursor.getBufferPosition()).toEqual([5, 28]); + }); + }); it('merges cursors when they overlap', () => { - editor.setCursorScreenPosition([12, 2]) - editor.addCursorAtScreenPosition([12, 1]) - const [cursor1] = editor.getCursors() + editor.setCursorScreenPosition([12, 2]); + editor.addCursorAtScreenPosition([12, 1]); + const [cursor1] = editor.getCursors(); - editor.moveRight() - expect(editor.getCursors()).toEqual([cursor1]) - expect(cursor1.getBufferPosition()).toEqual([12, 2]) - }) - }) + editor.moveRight(); + expect(editor.getCursors()).toEqual([cursor1]); + expect(cursor1.getBufferPosition()).toEqual([12, 2]); + }); + }); describe('.moveToTop()', () => { it('moves the cursor to the top of the buffer', () => { - editor.setCursorScreenPosition([11, 1]) - editor.addCursorAtScreenPosition([12, 0]) - editor.moveToTop() - expect(editor.getCursors().length).toBe(1) - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) - }) + editor.setCursorScreenPosition([11, 1]); + editor.addCursorAtScreenPosition([12, 0]); + editor.moveToTop(); + expect(editor.getCursors().length).toBe(1); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); + }); describe('.moveToBottom()', () => { it('moves the cursor to the bottom of the buffer', () => { - editor.setCursorScreenPosition([0, 0]) - editor.addCursorAtScreenPosition([1, 0]) - editor.moveToBottom() - expect(editor.getCursors().length).toBe(1) - expect(editor.getCursorBufferPosition()).toEqual([12, 2]) - }) - }) + editor.setCursorScreenPosition([0, 0]); + editor.addCursorAtScreenPosition([1, 0]); + editor.moveToBottom(); + expect(editor.getCursors().length).toBe(1); + expect(editor.getCursorBufferPosition()).toEqual([12, 2]); + }); + }); describe('.moveToBeginningOfScreenLine()', () => { describe('when soft wrap is on', () => { it('moves cursor to the beginning of the screen line', () => { - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(10) - editor.setCursorScreenPosition([1, 2]) - editor.moveToBeginningOfScreenLine() - const cursor = editor.getLastCursor() - expect(cursor.getScreenPosition()).toEqual([1, 0]) - }) - }) + editor.setSoftWrapped(true); + editor.setEditorWidthInChars(10); + editor.setCursorScreenPosition([1, 2]); + editor.moveToBeginningOfScreenLine(); + const cursor = editor.getLastCursor(); + expect(cursor.getScreenPosition()).toEqual([1, 0]); + }); + }); describe('when soft wrap is off', () => { it('moves cursor to the beginning of the line', () => { - editor.setCursorScreenPosition([0, 5]) - editor.addCursorAtScreenPosition([1, 7]) - editor.moveToBeginningOfScreenLine() - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 0]) - }) - }) - }) + editor.setCursorScreenPosition([0, 5]); + editor.addCursorAtScreenPosition([1, 7]); + editor.moveToBeginningOfScreenLine(); + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 0]); + }); + }); + }); describe('.moveToEndOfScreenLine()', () => { describe('when soft wrap is on', () => { it('moves cursor to the beginning of the screen line', () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(10) - editor.setCursorScreenPosition([1, 2]) - editor.moveToEndOfScreenLine() - const cursor = editor.getLastCursor() - expect(cursor.getScreenPosition()).toEqual([1, 9]) - }) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(10); + editor.setCursorScreenPosition([1, 2]); + editor.moveToEndOfScreenLine(); + const cursor = editor.getLastCursor(); + expect(cursor.getScreenPosition()).toEqual([1, 9]); + }); + }); describe('when soft wrap is off', () => { it('moves cursor to the end of line', () => { - editor.setCursorScreenPosition([0, 0]) - editor.addCursorAtScreenPosition([1, 0]) - editor.moveToEndOfScreenLine() - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 29]) - expect(cursor2.getBufferPosition()).toEqual([1, 30]) - }) - }) - }) + editor.setCursorScreenPosition([0, 0]); + editor.addCursorAtScreenPosition([1, 0]); + editor.moveToEndOfScreenLine(); + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 29]); + expect(cursor2.getBufferPosition()).toEqual([1, 30]); + }); + }); + }); describe('.moveToBeginningOfLine()', () => { it('moves cursor to the beginning of the buffer line', () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(10) - editor.setCursorScreenPosition([1, 2]) - editor.moveToBeginningOfLine() - const cursor = editor.getLastCursor() - expect(cursor.getScreenPosition()).toEqual([0, 0]) - }) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(10); + editor.setCursorScreenPosition([1, 2]); + editor.moveToBeginningOfLine(); + const cursor = editor.getLastCursor(); + expect(cursor.getScreenPosition()).toEqual([0, 0]); + }); + }); describe('.moveToEndOfLine()', () => { it('moves cursor to the end of the buffer line', () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(10) - editor.setCursorScreenPosition([0, 2]) - editor.moveToEndOfLine() - const cursor = editor.getLastCursor() - expect(cursor.getScreenPosition()).toEqual([4, 4]) - }) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(10); + editor.setCursorScreenPosition([0, 2]); + editor.moveToEndOfLine(); + const cursor = editor.getLastCursor(); + expect(cursor.getScreenPosition()).toEqual([4, 4]); + }); + }); describe('.moveToFirstCharacterOfLine()', () => { describe('when soft wrap is on', () => { it("moves to the first character of the current screen line or the beginning of the screen line if it's already on the first character", () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(10) - editor.setCursorScreenPosition([2, 5]) - editor.addCursorAtScreenPosition([8, 7]) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(10); + editor.setCursorScreenPosition([2, 5]); + editor.addCursorAtScreenPosition([8, 7]); - editor.moveToFirstCharacterOfLine() - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getScreenPosition()).toEqual([2, 0]) - expect(cursor2.getScreenPosition()).toEqual([8, 2]) + editor.moveToFirstCharacterOfLine(); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getScreenPosition()).toEqual([2, 0]); + expect(cursor2.getScreenPosition()).toEqual([8, 2]); - editor.moveToFirstCharacterOfLine() - expect(cursor1.getScreenPosition()).toEqual([2, 0]) - expect(cursor2.getScreenPosition()).toEqual([8, 2]) - }) - }) + editor.moveToFirstCharacterOfLine(); + expect(cursor1.getScreenPosition()).toEqual([2, 0]); + expect(cursor2.getScreenPosition()).toEqual([8, 2]); + }); + }); describe('when soft wrap is off', () => { it("moves to the first character of the current line or the beginning of the line if it's already on the first character", () => { - editor.setCursorScreenPosition([0, 5]) - editor.addCursorAtScreenPosition([1, 7]) + editor.setCursorScreenPosition([0, 5]); + editor.addCursorAtScreenPosition([1, 7]); - editor.moveToFirstCharacterOfLine() - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 2]) + editor.moveToFirstCharacterOfLine(); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 2]); - editor.moveToFirstCharacterOfLine() - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 0]) - }) + editor.moveToFirstCharacterOfLine(); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 0]); + }); it('moves to the beginning of the line if it only contains whitespace ', () => { - editor.setText('first\n \nthird') - editor.setCursorScreenPosition([1, 2]) - editor.moveToFirstCharacterOfLine() - const cursor = editor.getLastCursor() - expect(cursor.getBufferPosition()).toEqual([1, 0]) - }) + editor.setText('first\n \nthird'); + editor.setCursorScreenPosition([1, 2]); + editor.moveToFirstCharacterOfLine(); + const cursor = editor.getLastCursor(); + expect(cursor.getBufferPosition()).toEqual([1, 0]); + }); describe('when invisible characters are enabled with soft tabs', () => { it('moves to the first character of the current line without being confused by the invisible characters', () => { - editor.update({ showInvisibles: true }) - editor.setCursorScreenPosition([1, 7]) - editor.moveToFirstCharacterOfLine() - expect(editor.getCursorBufferPosition()).toEqual([1, 2]) - editor.moveToFirstCharacterOfLine() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - }) - }) + editor.update({ showInvisibles: true }); + editor.setCursorScreenPosition([1, 7]); + editor.moveToFirstCharacterOfLine(); + expect(editor.getCursorBufferPosition()).toEqual([1, 2]); + editor.moveToFirstCharacterOfLine(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + }); + }); describe('when invisible characters are enabled with hard tabs', () => { it('moves to the first character of the current line without being confused by the invisible characters', () => { - editor.update({ showInvisibles: true }) + editor.update({ showInvisibles: true }); buffer.setTextInRange([[1, 0], [1, Infinity]], '\t\t\ta', { normalizeLineEndings: false - }) + }); - editor.setCursorScreenPosition([1, 7]) - editor.moveToFirstCharacterOfLine() - expect(editor.getCursorBufferPosition()).toEqual([1, 3]) - editor.moveToFirstCharacterOfLine() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - }) - }) - }) + editor.setCursorScreenPosition([1, 7]); + editor.moveToFirstCharacterOfLine(); + expect(editor.getCursorBufferPosition()).toEqual([1, 3]); + editor.moveToFirstCharacterOfLine(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + }); + }); + }); it('clears the goal column', () => { - editor.setText('first\n\nthird') - editor.setCursorScreenPosition([0, 3]) - editor.moveDown() - editor.moveToFirstCharacterOfLine() - editor.moveDown() - expect(editor.getCursorBufferPosition()).toEqual([2, 0]) - }) - }) + editor.setText('first\n\nthird'); + editor.setCursorScreenPosition([0, 3]); + editor.moveDown(); + editor.moveToFirstCharacterOfLine(); + editor.moveDown(); + expect(editor.getCursorBufferPosition()).toEqual([2, 0]); + }); + }); describe('.moveToBeginningOfWord()', () => { it('moves the cursor to the beginning of the word', () => { - editor.setCursorBufferPosition([0, 8]) - editor.addCursorAtBufferPosition([1, 12]) - editor.addCursorAtBufferPosition([3, 0]) - const [cursor1, cursor2, cursor3] = editor.getCursors() + editor.setCursorBufferPosition([0, 8]); + editor.addCursorAtBufferPosition([1, 12]); + editor.addCursorAtBufferPosition([3, 0]); + const [cursor1, cursor2, cursor3] = editor.getCursors(); - editor.moveToBeginningOfWord() + editor.moveToBeginningOfWord(); - expect(cursor1.getBufferPosition()).toEqual([0, 4]) - expect(cursor2.getBufferPosition()).toEqual([1, 11]) - expect(cursor3.getBufferPosition()).toEqual([2, 39]) - }) + expect(cursor1.getBufferPosition()).toEqual([0, 4]); + expect(cursor2.getBufferPosition()).toEqual([1, 11]); + expect(cursor3.getBufferPosition()).toEqual([2, 39]); + }); it('does not fail at position [0, 0]', () => { - editor.setCursorBufferPosition([0, 0]) - editor.moveToBeginningOfWord() - }) + editor.setCursorBufferPosition([0, 0]); + editor.moveToBeginningOfWord(); + }); it('treats lines with only whitespace as a word', () => { - editor.setCursorBufferPosition([11, 0]) - editor.moveToBeginningOfWord() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) - }) + editor.setCursorBufferPosition([11, 0]); + editor.moveToBeginningOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); + }); it('treats lines with only whitespace as a word (CRLF line ending)', () => { - editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')) - editor.setCursorBufferPosition([11, 0]) - editor.moveToBeginningOfWord() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) - }) + editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')); + editor.setCursorBufferPosition([11, 0]); + editor.moveToBeginningOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); + }); it('works when the current line is blank', () => { - editor.setCursorBufferPosition([10, 0]) - editor.moveToBeginningOfWord() - expect(editor.getCursorBufferPosition()).toEqual([9, 2]) - }) + editor.setCursorBufferPosition([10, 0]); + editor.moveToBeginningOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([9, 2]); + }); it('works when the current line is blank (CRLF line ending)', () => { - editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')) - editor.setCursorBufferPosition([10, 0]) - editor.moveToBeginningOfWord() - expect(editor.getCursorBufferPosition()).toEqual([9, 2]) - editor.buffer.setText(buffer.getText().replace(/\r\n/g, '\n')) - }) - }) + editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')); + editor.setCursorBufferPosition([10, 0]); + editor.moveToBeginningOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([9, 2]); + editor.buffer.setText(buffer.getText().replace(/\r\n/g, '\n')); + }); + }); describe('.moveToPreviousWordBoundary()', () => { it('moves the cursor to the previous word boundary', () => { - editor.setCursorBufferPosition([0, 8]) - editor.addCursorAtBufferPosition([2, 0]) - editor.addCursorAtBufferPosition([2, 4]) - editor.addCursorAtBufferPosition([3, 14]) - const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors() + editor.setCursorBufferPosition([0, 8]); + editor.addCursorAtBufferPosition([2, 0]); + editor.addCursorAtBufferPosition([2, 4]); + editor.addCursorAtBufferPosition([3, 14]); + const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors(); - editor.moveToPreviousWordBoundary() + editor.moveToPreviousWordBoundary(); - expect(cursor1.getBufferPosition()).toEqual([0, 4]) - expect(cursor2.getBufferPosition()).toEqual([1, 30]) - expect(cursor3.getBufferPosition()).toEqual([2, 0]) - expect(cursor4.getBufferPosition()).toEqual([3, 13]) - }) - }) + expect(cursor1.getBufferPosition()).toEqual([0, 4]); + expect(cursor2.getBufferPosition()).toEqual([1, 30]); + expect(cursor3.getBufferPosition()).toEqual([2, 0]); + expect(cursor4.getBufferPosition()).toEqual([3, 13]); + }); + }); describe('.moveToNextWordBoundary()', () => { it('moves the cursor to the previous word boundary', () => { - editor.setCursorBufferPosition([0, 8]) - editor.addCursorAtBufferPosition([2, 40]) - editor.addCursorAtBufferPosition([3, 0]) - editor.addCursorAtBufferPosition([3, 30]) - const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors() + editor.setCursorBufferPosition([0, 8]); + editor.addCursorAtBufferPosition([2, 40]); + editor.addCursorAtBufferPosition([3, 0]); + editor.addCursorAtBufferPosition([3, 30]); + const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors(); - editor.moveToNextWordBoundary() + editor.moveToNextWordBoundary(); - expect(cursor1.getBufferPosition()).toEqual([0, 13]) - expect(cursor2.getBufferPosition()).toEqual([3, 0]) - expect(cursor3.getBufferPosition()).toEqual([3, 4]) - expect(cursor4.getBufferPosition()).toEqual([3, 31]) - }) - }) + expect(cursor1.getBufferPosition()).toEqual([0, 13]); + expect(cursor2.getBufferPosition()).toEqual([3, 0]); + expect(cursor3.getBufferPosition()).toEqual([3, 4]); + expect(cursor4.getBufferPosition()).toEqual([3, 31]); + }); + }); describe('.moveToEndOfWord()', () => { it('moves the cursor to the end of the word', () => { - editor.setCursorBufferPosition([0, 6]) - editor.addCursorAtBufferPosition([1, 10]) - editor.addCursorAtBufferPosition([2, 40]) - const [cursor1, cursor2, cursor3] = editor.getCursors() + editor.setCursorBufferPosition([0, 6]); + editor.addCursorAtBufferPosition([1, 10]); + editor.addCursorAtBufferPosition([2, 40]); + const [cursor1, cursor2, cursor3] = editor.getCursors(); - editor.moveToEndOfWord() + editor.moveToEndOfWord(); - expect(cursor1.getBufferPosition()).toEqual([0, 13]) - expect(cursor2.getBufferPosition()).toEqual([1, 12]) - expect(cursor3.getBufferPosition()).toEqual([3, 7]) - }) + expect(cursor1.getBufferPosition()).toEqual([0, 13]); + expect(cursor2.getBufferPosition()).toEqual([1, 12]); + expect(cursor3.getBufferPosition()).toEqual([3, 7]); + }); it('does not blow up when there is no next word', () => { - editor.setCursorBufferPosition([Infinity, Infinity]) - const endPosition = editor.getCursorBufferPosition() - editor.moveToEndOfWord() - expect(editor.getCursorBufferPosition()).toEqual(endPosition) - }) + editor.setCursorBufferPosition([Infinity, Infinity]); + const endPosition = editor.getCursorBufferPosition(); + editor.moveToEndOfWord(); + expect(editor.getCursorBufferPosition()).toEqual(endPosition); + }); it('treats lines with only whitespace as a word', () => { - editor.setCursorBufferPosition([9, 4]) - editor.moveToEndOfWord() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) - }) + editor.setCursorBufferPosition([9, 4]); + editor.moveToEndOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); + }); it('treats lines with only whitespace as a word (CRLF line ending)', () => { - editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')) - editor.setCursorBufferPosition([9, 4]) - editor.moveToEndOfWord() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) - }) + editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')); + editor.setCursorBufferPosition([9, 4]); + editor.moveToEndOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); + }); it('works when the current line is blank', () => { - editor.setCursorBufferPosition([10, 0]) - editor.moveToEndOfWord() - expect(editor.getCursorBufferPosition()).toEqual([11, 8]) - }) + editor.setCursorBufferPosition([10, 0]); + editor.moveToEndOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([11, 8]); + }); it('works when the current line is blank (CRLF line ending)', () => { - editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')) - editor.setCursorBufferPosition([10, 0]) - editor.moveToEndOfWord() - expect(editor.getCursorBufferPosition()).toEqual([11, 8]) - }) - }) + editor.buffer.setText(buffer.getText().replace(/\n/g, '\r\n')); + editor.setCursorBufferPosition([10, 0]); + editor.moveToEndOfWord(); + expect(editor.getCursorBufferPosition()).toEqual([11, 8]); + }); + }); describe('.moveToBeginningOfNextWord()', () => { it('moves the cursor before the first character of the next word', () => { - editor.setCursorBufferPosition([0, 6]) - editor.addCursorAtBufferPosition([1, 11]) - editor.addCursorAtBufferPosition([2, 0]) - const [cursor1, cursor2, cursor3] = editor.getCursors() + editor.setCursorBufferPosition([0, 6]); + editor.addCursorAtBufferPosition([1, 11]); + editor.addCursorAtBufferPosition([2, 0]); + const [cursor1, cursor2, cursor3] = editor.getCursors(); - editor.moveToBeginningOfNextWord() + editor.moveToBeginningOfNextWord(); - expect(cursor1.getBufferPosition()).toEqual([0, 14]) - expect(cursor2.getBufferPosition()).toEqual([1, 13]) - expect(cursor3.getBufferPosition()).toEqual([2, 4]) + expect(cursor1.getBufferPosition()).toEqual([0, 14]); + expect(cursor2.getBufferPosition()).toEqual([1, 13]); + expect(cursor3.getBufferPosition()).toEqual([2, 4]); // When the cursor is on whitespace - editor.setText('ab cde- ') - editor.setCursorBufferPosition([0, 2]) - const cursor = editor.getLastCursor() - editor.moveToBeginningOfNextWord() + editor.setText('ab cde- '); + editor.setCursorBufferPosition([0, 2]); + const cursor = editor.getLastCursor(); + editor.moveToBeginningOfNextWord(); - expect(cursor.getBufferPosition()).toEqual([0, 3]) - }) + expect(cursor.getBufferPosition()).toEqual([0, 3]); + }); it('does not blow up when there is no next word', () => { - editor.setCursorBufferPosition([Infinity, Infinity]) - const endPosition = editor.getCursorBufferPosition() - editor.moveToBeginningOfNextWord() - expect(editor.getCursorBufferPosition()).toEqual(endPosition) - }) + editor.setCursorBufferPosition([Infinity, Infinity]); + const endPosition = editor.getCursorBufferPosition(); + editor.moveToBeginningOfNextWord(); + expect(editor.getCursorBufferPosition()).toEqual(endPosition); + }); it('treats lines with only whitespace as a word', () => { - editor.setCursorBufferPosition([9, 4]) - editor.moveToBeginningOfNextWord() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) - }) + editor.setCursorBufferPosition([9, 4]); + editor.moveToBeginningOfNextWord(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); + }); it('works when the current line is blank', () => { - editor.setCursorBufferPosition([10, 0]) - editor.moveToBeginningOfNextWord() - expect(editor.getCursorBufferPosition()).toEqual([11, 9]) - }) - }) + editor.setCursorBufferPosition([10, 0]); + editor.moveToBeginningOfNextWord(); + expect(editor.getCursorBufferPosition()).toEqual([11, 9]); + }); + }); describe('.moveToPreviousSubwordBoundary', () => { it('does not move the cursor when there is no previous subword boundary', () => { - editor.setText('') - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) + editor.setText(''); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); it('stops at word and underscore boundaries', () => { - editor.setText('sub_word \n') - editor.setCursorBufferPosition([0, 9]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 8]) + editor.setText('sub_word \n'); + editor.setCursorBufferPosition([0, 9]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 8]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); - editor.setText(' word\n') - editor.setCursorBufferPosition([0, 3]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) - }) + editor.setText(' word\n'); + editor.setCursorBufferPosition([0, 3]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); + }); it('stops at camelCase boundaries', () => { - editor.setText(' getPreviousWord\n') - editor.setCursorBufferPosition([0, 16]) + editor.setText(' getPreviousWord\n'); + editor.setCursorBufferPosition([0, 16]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 12]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 12]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) - }) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); + }); it('stops at camelCase boundaries with non-ascii characters', () => { - editor.setText(' gétÁrevìôüsWord\n') - editor.setCursorBufferPosition([0, 16]) + editor.setText(' gétÁrevìôüsWord\n'); + editor.setCursorBufferPosition([0, 16]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 12]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 12]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) - }) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); + }); it('skips consecutive non-word characters', () => { - editor.setText('e, => \n') - editor.setCursorBufferPosition([0, 6]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 3]) + editor.setText('e, => \n'); + editor.setCursorBufferPosition([0, 6]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 3]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) - }) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); + }); it('skips consecutive uppercase characters', () => { - editor.setText(' AAADF \n') - editor.setCursorBufferPosition([0, 7]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) + editor.setText(' AAADF \n'); + editor.setCursorBufferPosition([0, 7]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); - editor.setText('ALPhA\n') - editor.setCursorBufferPosition([0, 4]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - }) + editor.setText('ALPhA\n'); + editor.setCursorBufferPosition([0, 4]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + }); it('skips consecutive uppercase non-ascii letters', () => { - editor.setText(' ÀÁÅDF \n') - editor.setCursorBufferPosition([0, 7]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) + editor.setText(' ÀÁÅDF \n'); + editor.setCursorBufferPosition([0, 7]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); - editor.setText('ALPhA\n') - editor.setCursorBufferPosition([0, 4]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - }) + editor.setText('ALPhA\n'); + editor.setCursorBufferPosition([0, 4]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + }); it('skips consecutive numbers', () => { - editor.setText(' 88 \n') - editor.setCursorBufferPosition([0, 4]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 3]) + editor.setText(' 88 \n'); + editor.setCursorBufferPosition([0, 4]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 3]); - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) - }) + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); + }); it('works with multiple cursors', () => { - editor.setText('curOp\ncursorOptions\n') - editor.setCursorBufferPosition([0, 8]) - editor.addCursorAtBufferPosition([1, 13]) - const [cursor1, cursor2] = editor.getCursors() + editor.setText('curOp\ncursorOptions\n'); + editor.setCursorBufferPosition([0, 8]); + editor.addCursorAtBufferPosition([1, 13]); + const [cursor1, cursor2] = editor.getCursors(); - editor.moveToPreviousSubwordBoundary() + editor.moveToPreviousSubwordBoundary(); - expect(cursor1.getBufferPosition()).toEqual([0, 3]) - expect(cursor2.getBufferPosition()).toEqual([1, 6]) - }) + expect(cursor1.getBufferPosition()).toEqual([0, 3]); + expect(cursor2.getBufferPosition()).toEqual([1, 6]); + }); it('works with non-English characters', () => { - editor.setText('supåTøåst \n') - editor.setCursorBufferPosition([0, 9]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + editor.setText('supåTøåst \n'); + editor.setCursorBufferPosition([0, 9]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - editor.setText('supaÖast \n') - editor.setCursorBufferPosition([0, 8]) - editor.moveToPreviousSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - }) - }) + editor.setText('supaÖast \n'); + editor.setCursorBufferPosition([0, 8]); + editor.moveToPreviousSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + }); + }); describe('.moveToNextSubwordBoundary', () => { it('does not move the cursor when there is no next subword boundary', () => { - editor.setText('') - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) + editor.setText(''); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); it('stops at word and underscore boundaries', () => { - editor.setText(' sub_word \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.setText(' sub_word \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 9]) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 9]); - editor.setText('word \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - }) + editor.setText('word \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + }); it('stops at camelCase boundaries', () => { - editor.setText('getPreviousWord \n') - editor.setCursorBufferPosition([0, 0]) + editor.setText('getPreviousWord \n'); + editor.setCursorBufferPosition([0, 0]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 3]) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 3]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 11]) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 11]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 15]) - }) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 15]); + }); it('skips consecutive non-word characters', () => { - editor.setText(', => \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.setText(', => \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - }) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + }); it('skips consecutive uppercase characters', () => { - editor.setText(' AAADF \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.setText(' AAADF \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); - editor.setText('ALPhA\n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - }) + editor.setText('ALPhA\n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + }); it('skips consecutive numbers', () => { - editor.setText(' 88 \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.setText(' 88 \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 3]) - }) + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 3]); + }); it('works with multiple cursors', () => { - editor.setText('curOp\ncursorOptions\n') - editor.setCursorBufferPosition([0, 0]) - editor.addCursorAtBufferPosition([1, 0]) - const [cursor1, cursor2] = editor.getCursors() + editor.setText('curOp\ncursorOptions\n'); + editor.setCursorBufferPosition([0, 0]); + editor.addCursorAtBufferPosition([1, 0]); + const [cursor1, cursor2] = editor.getCursors(); - editor.moveToNextSubwordBoundary() - expect(cursor1.getBufferPosition()).toEqual([0, 3]) - expect(cursor2.getBufferPosition()).toEqual([1, 6]) - }) + editor.moveToNextSubwordBoundary(); + expect(cursor1.getBufferPosition()).toEqual([0, 3]); + expect(cursor2.getBufferPosition()).toEqual([1, 6]); + }); it('works with non-English characters', () => { - editor.setText('supåTøåst \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) + editor.setText('supåTøåst \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); - editor.setText('supaÖast \n') - editor.setCursorBufferPosition([0, 0]) - editor.moveToNextSubwordBoundary() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - }) - }) + editor.setText('supaÖast \n'); + editor.setCursorBufferPosition([0, 0]); + editor.moveToNextSubwordBoundary(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + }); + }); describe('.moveToBeginningOfNextParagraph()', () => { it('moves the cursor before the first line of the next paragraph', () => { - editor.setCursorBufferPosition([0, 6]) - editor.foldBufferRow(4) + editor.setCursorBufferPosition([0, 6]); + editor.foldBufferRow(4); - editor.moveToBeginningOfNextParagraph() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) + editor.moveToBeginningOfNextParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); - editor.setText('') - editor.setCursorBufferPosition([0, 0]) - editor.moveToBeginningOfNextParagraph() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) + editor.setText(''); + editor.setCursorBufferPosition([0, 0]); + editor.moveToBeginningOfNextParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); it('moves the cursor before the first line of the next paragraph (CRLF line endings)', () => { - editor.setText(editor.getText().replace(/\n/g, '\r\n')) + editor.setText(editor.getText().replace(/\n/g, '\r\n')); - editor.setCursorBufferPosition([0, 6]) - editor.foldBufferRow(4) + editor.setCursorBufferPosition([0, 6]); + editor.foldBufferRow(4); - editor.moveToBeginningOfNextParagraph() - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) + editor.moveToBeginningOfNextParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); - editor.setText('') - editor.setCursorBufferPosition([0, 0]) - editor.moveToBeginningOfNextParagraph() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) - }) + editor.setText(''); + editor.setCursorBufferPosition([0, 0]); + editor.moveToBeginningOfNextParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); + }); describe('.moveToBeginningOfPreviousParagraph()', () => { it('moves the cursor before the first line of the previous paragraph', () => { - editor.setCursorBufferPosition([10, 0]) - editor.foldBufferRow(4) + editor.setCursorBufferPosition([10, 0]); + editor.foldBufferRow(4); - editor.moveToBeginningOfPreviousParagraph() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + editor.moveToBeginningOfPreviousParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); - editor.setText('') - editor.setCursorBufferPosition([0, 0]) - editor.moveToBeginningOfPreviousParagraph() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) + editor.setText(''); + editor.setCursorBufferPosition([0, 0]); + editor.moveToBeginningOfPreviousParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); it('moves the cursor before the first line of the previous paragraph (CRLF line endings)', () => { - editor.setText(editor.getText().replace(/\n/g, '\r\n')) + editor.setText(editor.getText().replace(/\n/g, '\r\n')); - editor.setCursorBufferPosition([10, 0]) - editor.foldBufferRow(4) + editor.setCursorBufferPosition([10, 0]); + editor.foldBufferRow(4); - editor.moveToBeginningOfPreviousParagraph() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + editor.moveToBeginningOfPreviousParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); - editor.setText('') - editor.setCursorBufferPosition([0, 0]) - editor.moveToBeginningOfPreviousParagraph() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) - }) + editor.setText(''); + editor.setCursorBufferPosition([0, 0]); + editor.moveToBeginningOfPreviousParagraph(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); + }); describe('.getCurrentParagraphBufferRange()', () => { it('returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file', () => { @@ -1400,34 +1403,34 @@ describe('TextEditor', () => { I am the last paragraph, bordered by the end of the file.\ ` - ) + ); // in a paragraph - editor.setCursorBufferPosition([1, 7]) + editor.setCursorBufferPosition([1, 7]); expect(editor.getCurrentParagraphBufferRange()).toEqual([ [0, 0], [2, 8] - ]) + ]); - editor.setCursorBufferPosition([7, 1]) + editor.setCursorBufferPosition([7, 1]); expect(editor.getCurrentParagraphBufferRange()).toEqual([ [5, 0], [7, 3] - ]) + ]); - editor.setCursorBufferPosition([9, 10]) + editor.setCursorBufferPosition([9, 10]); expect(editor.getCurrentParagraphBufferRange()).toEqual([ [9, 0], [10, 32] - ]) + ]); // between paragraphs - editor.setCursorBufferPosition([3, 1]) - expect(editor.getCurrentParagraphBufferRange()).toBeUndefined() - }) + editor.setCursorBufferPosition([3, 1]); + expect(editor.getCurrentParagraphBufferRange()).toBeUndefined(); + }); it('will limit paragraph range to comments', () => { - atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.js') + atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.js'); editor.setText(dedent` var quicksort = function () { /* Single line comment block */ @@ -1451,1372 +1454,1375 @@ describe('TextEditor', () => { } };\ - `) + `); - function paragraphBufferRangeForRow (row) { - editor.setCursorBufferPosition([row, 0]) - return editor.getLastCursor().getCurrentParagraphBufferRange() + function paragraphBufferRangeForRow(row) { + editor.setCursorBufferPosition([row, 0]); + return editor.getLastCursor().getCurrentParagraphBufferRange(); } - expect(paragraphBufferRangeForRow(0)).toEqual([[0, 0], [0, 29]]) - expect(paragraphBufferRangeForRow(1)).toEqual([[1, 0], [1, 33]]) - expect(paragraphBufferRangeForRow(2)).toEqual([[2, 0], [2, 32]]) - expect(paragraphBufferRangeForRow(3)).toBeFalsy() - expect(paragraphBufferRangeForRow(4)).toEqual([[4, 0], [7, 4]]) - expect(paragraphBufferRangeForRow(5)).toEqual([[4, 0], [7, 4]]) - expect(paragraphBufferRangeForRow(6)).toEqual([[4, 0], [7, 4]]) - expect(paragraphBufferRangeForRow(7)).toEqual([[4, 0], [7, 4]]) - expect(paragraphBufferRangeForRow(8)).toEqual([[8, 0], [8, 32]]) - expect(paragraphBufferRangeForRow(9)).toBeFalsy() - expect(paragraphBufferRangeForRow(10)).toEqual([[10, 0], [13, 10]]) - expect(paragraphBufferRangeForRow(11)).toEqual([[10, 0], [13, 10]]) - expect(paragraphBufferRangeForRow(12)).toEqual([[10, 0], [13, 10]]) - expect(paragraphBufferRangeForRow(14)).toEqual([[14, 0], [14, 32]]) - expect(paragraphBufferRangeForRow(15)).toEqual([[15, 0], [15, 26]]) - expect(paragraphBufferRangeForRow(18)).toEqual([[17, 0], [19, 3]]) - }) - }) + expect(paragraphBufferRangeForRow(0)).toEqual([[0, 0], [0, 29]]); + expect(paragraphBufferRangeForRow(1)).toEqual([[1, 0], [1, 33]]); + expect(paragraphBufferRangeForRow(2)).toEqual([[2, 0], [2, 32]]); + expect(paragraphBufferRangeForRow(3)).toBeFalsy(); + expect(paragraphBufferRangeForRow(4)).toEqual([[4, 0], [7, 4]]); + expect(paragraphBufferRangeForRow(5)).toEqual([[4, 0], [7, 4]]); + expect(paragraphBufferRangeForRow(6)).toEqual([[4, 0], [7, 4]]); + expect(paragraphBufferRangeForRow(7)).toEqual([[4, 0], [7, 4]]); + expect(paragraphBufferRangeForRow(8)).toEqual([[8, 0], [8, 32]]); + expect(paragraphBufferRangeForRow(9)).toBeFalsy(); + expect(paragraphBufferRangeForRow(10)).toEqual([[10, 0], [13, 10]]); + expect(paragraphBufferRangeForRow(11)).toEqual([[10, 0], [13, 10]]); + expect(paragraphBufferRangeForRow(12)).toEqual([[10, 0], [13, 10]]); + expect(paragraphBufferRangeForRow(14)).toEqual([[14, 0], [14, 32]]); + expect(paragraphBufferRangeForRow(15)).toEqual([[15, 0], [15, 26]]); + expect(paragraphBufferRangeForRow(18)).toEqual([[17, 0], [19, 3]]); + }); + }); describe('getCursorAtScreenPosition(screenPosition)', () => { it('returns the cursor at the given screenPosition', () => { - const cursor1 = editor.addCursorAtScreenPosition([0, 2]) + const cursor1 = editor.addCursorAtScreenPosition([0, 2]); const cursor2 = editor.getCursorAtScreenPosition( cursor1.getScreenPosition() - ) - expect(cursor2).toBe(cursor1) - }) - }) + ); + expect(cursor2).toBe(cursor1); + }); + }); describe('::getCursorScreenPositions()', () => { it('returns the cursor positions in the order they were added', () => { - editor.foldBufferRow(4) - editor.addCursorAtBufferPosition([8, 5]) - editor.addCursorAtBufferPosition([3, 5]) + editor.foldBufferRow(4); + editor.addCursorAtBufferPosition([8, 5]); + editor.addCursorAtBufferPosition([3, 5]); expect(editor.getCursorScreenPositions()).toEqual([ [0, 0], [5, 5], [3, 5] - ]) - }) - }) + ]); + }); + }); describe('::getCursorsOrderedByBufferPosition()', () => { it('returns all cursors ordered by buffer positions', () => { - const originalCursor = editor.getLastCursor() - const cursor1 = editor.addCursorAtBufferPosition([8, 5]) - const cursor2 = editor.addCursorAtBufferPosition([4, 5]) + const originalCursor = editor.getLastCursor(); + const cursor1 = editor.addCursorAtBufferPosition([8, 5]); + const cursor2 = editor.addCursorAtBufferPosition([4, 5]); expect(editor.getCursorsOrderedByBufferPosition()).toEqual([ originalCursor, cursor2, cursor1 - ]) - }) - }) + ]); + }); + }); describe('addCursorAtScreenPosition(screenPosition)', () => { describe('when a cursor already exists at the position', () => { it('returns the existing cursor', () => { - const cursor1 = editor.addCursorAtScreenPosition([0, 2]) - const cursor2 = editor.addCursorAtScreenPosition([0, 2]) - expect(cursor2).toBe(cursor1) - }) - }) - }) + const cursor1 = editor.addCursorAtScreenPosition([0, 2]); + const cursor2 = editor.addCursorAtScreenPosition([0, 2]); + expect(cursor2).toBe(cursor1); + }); + }); + }); describe('addCursorAtBufferPosition(bufferPosition)', () => { describe('when a cursor already exists at the position', () => { it('returns the existing cursor', () => { - const cursor1 = editor.addCursorAtBufferPosition([1, 4]) - const cursor2 = editor.addCursorAtBufferPosition([1, 4]) - expect(cursor2.marker).toBe(cursor1.marker) - }) - }) - }) + const cursor1 = editor.addCursorAtBufferPosition([1, 4]); + const cursor2 = editor.addCursorAtBufferPosition([1, 4]); + expect(cursor2.marker).toBe(cursor1.marker); + }); + }); + }); describe('.getCursorScope()', () => { it('returns the current scope', () => { - const descriptor = editor.getCursorScope() - expect(descriptor.scopes).toContain('source.js') - }) - }) - }) + const descriptor = editor.getCursorScope(); + expect(descriptor.scopes).toContain('source.js'); + }); + }); + }); describe('selection', () => { - let selection + let selection; beforeEach(() => { - selection = editor.getLastSelection() - }) + selection = editor.getLastSelection(); + }); describe('.getLastSelection()', () => { it('creates a new selection at (0, 0) if the last selection has been destroyed', () => { - editor.getLastSelection().destroy() + editor.getLastSelection().destroy(); expect(editor.getLastSelection().getBufferRange()).toEqual([ [0, 0], [0, 0] - ]) - }) + ]); + }); it("doesn't get stuck in a infinite loop when called from ::onDidAddCursor after the last selection has been destroyed (regression)", () => { - let callCount = 0 - editor.getLastSelection().destroy() - editor.onDidAddCursor(function (cursor) { - callCount++ - editor.getLastSelection() - }) + let callCount = 0; + editor.getLastSelection().destroy(); + editor.onDidAddCursor(function(cursor) { + callCount++; + editor.getLastSelection(); + }); expect(editor.getLastSelection().getBufferRange()).toEqual([ [0, 0], [0, 0] - ]) - expect(callCount).toBe(1) - }) - }) + ]); + expect(callCount).toBe(1); + }); + }); describe('.getSelections()', () => { it('creates a new selection at (0, 0) if the last selection has been destroyed', () => { - editor.getLastSelection().destroy() + editor.getLastSelection().destroy(); expect(editor.getSelections()[0].getBufferRange()).toEqual([ [0, 0], [0, 0] - ]) - }) - }) + ]); + }); + }); describe('when the selection range changes', () => { it('emits an event with the old range, new range, and the selection that moved', () => { - let rangeChangedHandler - editor.setSelectedBufferRange([[3, 0], [4, 5]]) + let rangeChangedHandler; + editor.setSelectedBufferRange([[3, 0], [4, 5]]); editor.onDidChangeSelectionRange( (rangeChangedHandler = jasmine.createSpy()) - ) - editor.selectToBufferPosition([6, 2]) + ); + editor.selectToBufferPosition([6, 2]); - expect(rangeChangedHandler).toHaveBeenCalled() - const eventObject = rangeChangedHandler.mostRecentCall.args[0] + expect(rangeChangedHandler).toHaveBeenCalled(); + const eventObject = rangeChangedHandler.mostRecentCall.args[0]; - expect(eventObject.oldBufferRange).toEqual([[3, 0], [4, 5]]) - expect(eventObject.oldScreenRange).toEqual([[3, 0], [4, 5]]) - expect(eventObject.newBufferRange).toEqual([[3, 0], [6, 2]]) - expect(eventObject.newScreenRange).toEqual([[3, 0], [6, 2]]) - expect(eventObject.selection).toBe(selection) - }) - }) + expect(eventObject.oldBufferRange).toEqual([[3, 0], [4, 5]]); + expect(eventObject.oldScreenRange).toEqual([[3, 0], [4, 5]]); + expect(eventObject.newBufferRange).toEqual([[3, 0], [6, 2]]); + expect(eventObject.newScreenRange).toEqual([[3, 0], [6, 2]]); + expect(eventObject.selection).toBe(selection); + }); + }); describe('.selectUp/Down/Left/Right()', () => { it("expands each selection to its cursor's new location", () => { - editor.setSelectedBufferRanges([[[0, 9], [0, 13]], [[3, 16], [3, 21]]]) - const [selection1, selection2] = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 9], [0, 13]], [[3, 16], [3, 21]]]); + const [selection1, selection2] = editor.getSelections(); - editor.selectRight() - expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 14]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 22]]) + editor.selectRight(); + expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 14]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 22]]); - editor.selectLeft() - editor.selectLeft() - expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 12]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 20]]) + editor.selectLeft(); + editor.selectLeft(); + expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 12]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 20]]); - editor.selectDown() - expect(selection1.getBufferRange()).toEqual([[0, 9], [1, 12]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [4, 20]]) + editor.selectDown(); + expect(selection1.getBufferRange()).toEqual([[0, 9], [1, 12]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [4, 20]]); - editor.selectUp() - expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 12]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 20]]) - }) + editor.selectUp(); + expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 12]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 20]]); + }); it('merges selections when they intersect when moving down', () => { editor.setSelectedBufferRanges([ [[0, 9], [0, 13]], [[1, 10], [1, 20]], [[2, 15], [3, 25]] - ]) - const [selection1] = editor.getSelections() + ]); + const [selection1] = editor.getSelections(); - editor.selectDown() - expect(editor.getSelections()).toEqual([selection1]) - expect(selection1.getScreenRange()).toEqual([[0, 9], [4, 25]]) - expect(selection1.isReversed()).toBeFalsy() - }) + editor.selectDown(); + expect(editor.getSelections()).toEqual([selection1]); + expect(selection1.getScreenRange()).toEqual([[0, 9], [4, 25]]); + expect(selection1.isReversed()).toBeFalsy(); + }); it('merges selections when they intersect when moving up', () => { editor.setSelectedBufferRanges( [[[0, 9], [0, 13]], [[1, 10], [1, 20]]], { reversed: true } - ) - const [selection1] = editor.getSelections() + ); + const [selection1] = editor.getSelections(); - editor.selectUp() - expect(editor.getSelections().length).toBe(1) - expect(editor.getSelections()).toEqual([selection1]) - expect(selection1.getScreenRange()).toEqual([[0, 0], [1, 20]]) - expect(selection1.isReversed()).toBeTruthy() - }) + editor.selectUp(); + expect(editor.getSelections().length).toBe(1); + expect(editor.getSelections()).toEqual([selection1]); + expect(selection1.getScreenRange()).toEqual([[0, 0], [1, 20]]); + expect(selection1.isReversed()).toBeTruthy(); + }); it('merges selections when they intersect when moving left', () => { editor.setSelectedBufferRanges( [[[0, 9], [0, 13]], [[0, 13], [1, 20]]], { reversed: true } - ) - const [selection1] = editor.getSelections() + ); + const [selection1] = editor.getSelections(); - editor.selectLeft() - expect(editor.getSelections()).toEqual([selection1]) - expect(selection1.getScreenRange()).toEqual([[0, 8], [1, 20]]) - expect(selection1.isReversed()).toBeTruthy() - }) + editor.selectLeft(); + expect(editor.getSelections()).toEqual([selection1]); + expect(selection1.getScreenRange()).toEqual([[0, 8], [1, 20]]); + expect(selection1.isReversed()).toBeTruthy(); + }); it('merges selections when they intersect when moving right', () => { - editor.setSelectedBufferRanges([[[0, 9], [0, 14]], [[0, 14], [1, 20]]]) - const [selection1] = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 9], [0, 14]], [[0, 14], [1, 20]]]); + const [selection1] = editor.getSelections(); - editor.selectRight() - expect(editor.getSelections()).toEqual([selection1]) - expect(selection1.getScreenRange()).toEqual([[0, 9], [1, 21]]) - expect(selection1.isReversed()).toBeFalsy() - }) + editor.selectRight(); + expect(editor.getSelections()).toEqual([selection1]); + expect(selection1.getScreenRange()).toEqual([[0, 9], [1, 21]]); + expect(selection1.isReversed()).toBeFalsy(); + }); describe('when counts are passed into the selection functions', () => { it("expands each selection to its cursor's new location", () => { editor.setSelectedBufferRanges([ [[0, 9], [0, 13]], [[3, 16], [3, 21]] - ]) - const [selection1, selection2] = editor.getSelections() + ]); + const [selection1, selection2] = editor.getSelections(); - editor.selectRight(2) - expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 15]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 23]]) + editor.selectRight(2); + expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 15]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 23]]); - editor.selectLeft(3) - expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 12]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 20]]) + editor.selectLeft(3); + expect(selection1.getBufferRange()).toEqual([[0, 9], [0, 12]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [3, 20]]); - editor.selectDown(3) - expect(selection1.getBufferRange()).toEqual([[0, 9], [3, 12]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [6, 20]]) + editor.selectDown(3); + expect(selection1.getBufferRange()).toEqual([[0, 9], [3, 12]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [6, 20]]); - editor.selectUp(2) - expect(selection1.getBufferRange()).toEqual([[0, 9], [1, 12]]) - expect(selection2.getBufferRange()).toEqual([[3, 16], [4, 20]]) - }) - }) - }) + editor.selectUp(2); + expect(selection1.getBufferRange()).toEqual([[0, 9], [1, 12]]); + expect(selection2.getBufferRange()).toEqual([[3, 16], [4, 20]]); + }); + }); + }); describe('.selectToBufferPosition(bufferPosition)', () => { it('expands the last selection to the given position', () => { - editor.setSelectedBufferRange([[3, 0], [4, 5]]) - editor.addCursorAtBufferPosition([5, 6]) - editor.selectToBufferPosition([6, 2]) + editor.setSelectedBufferRange([[3, 0], [4, 5]]); + editor.addCursorAtBufferPosition([5, 6]); + editor.selectToBufferPosition([6, 2]); - const selections = editor.getSelections() - expect(selections.length).toBe(2) - const [selection1, selection2] = selections - expect(selection1.getBufferRange()).toEqual([[3, 0], [4, 5]]) - expect(selection2.getBufferRange()).toEqual([[5, 6], [6, 2]]) - }) - }) + const selections = editor.getSelections(); + expect(selections.length).toBe(2); + const [selection1, selection2] = selections; + expect(selection1.getBufferRange()).toEqual([[3, 0], [4, 5]]); + expect(selection2.getBufferRange()).toEqual([[5, 6], [6, 2]]); + }); + }); describe('.selectToScreenPosition(screenPosition)', () => { it('expands the last selection to the given position', () => { - editor.setSelectedBufferRange([[3, 0], [4, 5]]) - editor.addCursorAtScreenPosition([5, 6]) - editor.selectToScreenPosition([6, 2]) + editor.setSelectedBufferRange([[3, 0], [4, 5]]); + editor.addCursorAtScreenPosition([5, 6]); + editor.selectToScreenPosition([6, 2]); - const selections = editor.getSelections() - expect(selections.length).toBe(2) - const [selection1, selection2] = selections - expect(selection1.getScreenRange()).toEqual([[3, 0], [4, 5]]) - expect(selection2.getScreenRange()).toEqual([[5, 6], [6, 2]]) - }) + const selections = editor.getSelections(); + expect(selections.length).toBe(2); + const [selection1, selection2] = selections; + expect(selection1.getScreenRange()).toEqual([[3, 0], [4, 5]]); + expect(selection2.getScreenRange()).toEqual([[5, 6], [6, 2]]); + }); describe('when selecting with an initial screen range', () => { it('switches the direction of the selection when selecting to positions before/after the start of the initial range', () => { - editor.setCursorScreenPosition([5, 10]) - editor.selectWordsContainingCursors() - editor.selectToScreenPosition([3, 0]) - expect(editor.getLastSelection().isReversed()).toBe(true) - editor.selectToScreenPosition([9, 0]) - expect(editor.getLastSelection().isReversed()).toBe(false) - }) - }) - }) + editor.setCursorScreenPosition([5, 10]); + editor.selectWordsContainingCursors(); + editor.selectToScreenPosition([3, 0]); + expect(editor.getLastSelection().isReversed()).toBe(true); + editor.selectToScreenPosition([9, 0]); + expect(editor.getLastSelection().isReversed()).toBe(false); + }); + }); + }); describe('.selectToBeginningOfNextParagraph()', () => { it('selects from the cursor to first line of the next paragraph', () => { - editor.setSelectedBufferRange([[3, 0], [4, 5]]) - editor.addCursorAtScreenPosition([5, 6]) - editor.selectToScreenPosition([6, 2]) + editor.setSelectedBufferRange([[3, 0], [4, 5]]); + editor.addCursorAtScreenPosition([5, 6]); + editor.selectToScreenPosition([6, 2]); - editor.selectToBeginningOfNextParagraph() + editor.selectToBeginningOfNextParagraph(); - const selections = editor.getSelections() - expect(selections.length).toBe(1) - expect(selections[0].getScreenRange()).toEqual([[3, 0], [10, 0]]) - }) - }) + const selections = editor.getSelections(); + expect(selections.length).toBe(1); + expect(selections[0].getScreenRange()).toEqual([[3, 0], [10, 0]]); + }); + }); describe('.selectToBeginningOfPreviousParagraph()', () => { it('selects from the cursor to the first line of the previous paragraph', () => { - editor.setSelectedBufferRange([[3, 0], [4, 5]]) - editor.addCursorAtScreenPosition([5, 6]) - editor.selectToScreenPosition([6, 2]) + editor.setSelectedBufferRange([[3, 0], [4, 5]]); + editor.addCursorAtScreenPosition([5, 6]); + editor.selectToScreenPosition([6, 2]); - editor.selectToBeginningOfPreviousParagraph() + editor.selectToBeginningOfPreviousParagraph(); - const selections = editor.getSelections() - expect(selections.length).toBe(1) - expect(selections[0].getScreenRange()).toEqual([[0, 0], [5, 6]]) - }) + const selections = editor.getSelections(); + expect(selections.length).toBe(1); + expect(selections[0].getScreenRange()).toEqual([[0, 0], [5, 6]]); + }); it('merges selections if they intersect, maintaining the directionality of the last selection', () => { - editor.setCursorScreenPosition([4, 10]) - editor.selectToScreenPosition([5, 27]) - editor.addCursorAtScreenPosition([3, 10]) - editor.selectToScreenPosition([6, 27]) + editor.setCursorScreenPosition([4, 10]); + editor.selectToScreenPosition([5, 27]); + editor.addCursorAtScreenPosition([3, 10]); + editor.selectToScreenPosition([6, 27]); - let selections = editor.getSelections() - expect(selections.length).toBe(1) - let [selection1] = selections - expect(selection1.getScreenRange()).toEqual([[3, 10], [6, 27]]) - expect(selection1.isReversed()).toBeFalsy() + let selections = editor.getSelections(); + expect(selections.length).toBe(1); + let [selection1] = selections; + expect(selection1.getScreenRange()).toEqual([[3, 10], [6, 27]]); + expect(selection1.isReversed()).toBeFalsy(); - editor.addCursorAtScreenPosition([7, 4]) - editor.selectToScreenPosition([4, 11]) + editor.addCursorAtScreenPosition([7, 4]); + editor.selectToScreenPosition([4, 11]); - selections = editor.getSelections() - expect(selections.length).toBe(1) - ;[selection1] = selections - expect(selection1.getScreenRange()).toEqual([[3, 10], [7, 4]]) - expect(selection1.isReversed()).toBeTruthy() - }) - }) + selections = editor.getSelections(); + expect(selections.length).toBe(1); + [selection1] = selections; + expect(selection1.getScreenRange()).toEqual([[3, 10], [7, 4]]); + expect(selection1.isReversed()).toBeTruthy(); + }); + }); describe('.selectToTop()', () => { it('selects text from cursor position to the top of the buffer', () => { - editor.setCursorScreenPosition([11, 2]) - editor.addCursorAtScreenPosition([10, 0]) - editor.selectToTop() - expect(editor.getCursors().length).toBe(1) - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) + editor.setCursorScreenPosition([11, 2]); + editor.addCursorAtScreenPosition([10, 0]); + editor.selectToTop(); + expect(editor.getCursors().length).toBe(1); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); expect(editor.getLastSelection().getBufferRange()).toEqual([ [0, 0], [11, 2] - ]) - expect(editor.getLastSelection().isReversed()).toBeTruthy() - }) - }) + ]); + expect(editor.getLastSelection().isReversed()).toBeTruthy(); + }); + }); describe('.selectToBottom()', () => { it('selects text from cursor position to the bottom of the buffer', () => { - editor.setCursorScreenPosition([10, 0]) - editor.addCursorAtScreenPosition([9, 3]) - editor.selectToBottom() - expect(editor.getCursors().length).toBe(1) - expect(editor.getCursorBufferPosition()).toEqual([12, 2]) + editor.setCursorScreenPosition([10, 0]); + editor.addCursorAtScreenPosition([9, 3]); + editor.selectToBottom(); + expect(editor.getCursors().length).toBe(1); + expect(editor.getCursorBufferPosition()).toEqual([12, 2]); expect(editor.getLastSelection().getBufferRange()).toEqual([ [9, 3], [12, 2] - ]) - expect(editor.getLastSelection().isReversed()).toBeFalsy() - }) - }) + ]); + expect(editor.getLastSelection().isReversed()).toBeFalsy(); + }); + }); describe('.selectAll()', () => { it('selects the entire buffer', () => { - editor.selectAll() + editor.selectAll(); expect(editor.getLastSelection().getBufferRange()).toEqual( buffer.getRange() - ) - }) - }) + ); + }); + }); describe('.selectToBeginningOfLine()', () => { it('selects text from cursor position to beginning of line', () => { - editor.setCursorScreenPosition([12, 2]) - editor.addCursorAtScreenPosition([11, 3]) + editor.setCursorScreenPosition([12, 2]); + editor.addCursorAtScreenPosition([11, 3]); - editor.selectToBeginningOfLine() + editor.selectToBeginningOfLine(); - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([12, 0]) - expect(cursor2.getBufferPosition()).toEqual([11, 0]) + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([12, 0]); + expect(cursor2.getBufferPosition()).toEqual([11, 0]); - expect(editor.getSelections().length).toBe(2) - const [selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[12, 0], [12, 2]]) - expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual([[11, 0], [11, 3]]) - expect(selection2.isReversed()).toBeTruthy() - }) - }) + expect(editor.getSelections().length).toBe(2); + const [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[12, 0], [12, 2]]); + expect(selection1.isReversed()).toBeTruthy(); + expect(selection2.getBufferRange()).toEqual([[11, 0], [11, 3]]); + expect(selection2.isReversed()).toBeTruthy(); + }); + }); describe('.selectToEndOfLine()', () => { it('selects text from cursor position to end of line', () => { - editor.setCursorScreenPosition([12, 0]) - editor.addCursorAtScreenPosition([11, 3]) + editor.setCursorScreenPosition([12, 0]); + editor.addCursorAtScreenPosition([11, 3]); - editor.selectToEndOfLine() + editor.selectToEndOfLine(); - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([12, 2]) - expect(cursor2.getBufferPosition()).toEqual([11, 44]) + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([12, 2]); + expect(cursor2.getBufferPosition()).toEqual([11, 44]); - expect(editor.getSelections().length).toBe(2) - const [selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[12, 0], [12, 2]]) - expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual([[11, 3], [11, 44]]) - expect(selection2.isReversed()).toBeFalsy() - }) - }) + expect(editor.getSelections().length).toBe(2); + const [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[12, 0], [12, 2]]); + expect(selection1.isReversed()).toBeFalsy(); + expect(selection2.getBufferRange()).toEqual([[11, 3], [11, 44]]); + expect(selection2.isReversed()).toBeFalsy(); + }); + }); describe('.selectLinesContainingCursors()', () => { it('selects to the entire line (including newlines) at given row', () => { - editor.setCursorScreenPosition([1, 2]) - editor.selectLinesContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[1, 0], [2, 0]]) + editor.setCursorScreenPosition([1, 2]); + editor.selectLinesContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[1, 0], [2, 0]]); expect(editor.getSelectedText()).toBe( ' var sort = function(items) {\n' - ) + ); - editor.setCursorScreenPosition([12, 2]) - editor.selectLinesContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[12, 0], [12, 2]]) + editor.setCursorScreenPosition([12, 2]); + editor.selectLinesContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[12, 0], [12, 2]]); - editor.setCursorBufferPosition([0, 2]) - editor.selectLinesContainingCursors() - editor.selectLinesContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [2, 0]]) - }) + editor.setCursorBufferPosition([0, 2]); + editor.selectLinesContainingCursors(); + editor.selectLinesContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [2, 0]]); + }); describe('when the selection spans multiple row', () => { it('selects from the beginning of the first line to the last line', () => { - selection = editor.getLastSelection() - selection.setBufferRange([[1, 10], [3, 20]]) - editor.selectLinesContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[1, 0], [4, 0]]) - }) - }) - }) + selection = editor.getLastSelection(); + selection.setBufferRange([[1, 10], [3, 20]]); + editor.selectLinesContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[1, 0], [4, 0]]); + }); + }); + }); describe('.selectToBeginningOfWord()', () => { it('selects text from cursor position to beginning of word', () => { - editor.setCursorScreenPosition([0, 13]) - editor.addCursorAtScreenPosition([3, 49]) + editor.setCursorScreenPosition([0, 13]); + editor.addCursorAtScreenPosition([3, 49]); - editor.selectToBeginningOfWord() + editor.selectToBeginningOfWord(); - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 4]) - expect(cursor2.getBufferPosition()).toEqual([3, 47]) + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 4]); + expect(cursor2.getBufferPosition()).toEqual([3, 47]); - expect(editor.getSelections().length).toBe(2) - const [selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 4], [0, 13]]) - expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual([[3, 47], [3, 49]]) - expect(selection2.isReversed()).toBeTruthy() - }) - }) + expect(editor.getSelections().length).toBe(2); + const [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 4], [0, 13]]); + expect(selection1.isReversed()).toBeTruthy(); + expect(selection2.getBufferRange()).toEqual([[3, 47], [3, 49]]); + expect(selection2.isReversed()).toBeTruthy(); + }); + }); describe('.selectToEndOfWord()', () => { it('selects text from cursor position to end of word', () => { - editor.setCursorScreenPosition([0, 4]) - editor.addCursorAtScreenPosition([3, 48]) + editor.setCursorScreenPosition([0, 4]); + editor.addCursorAtScreenPosition([3, 48]); - editor.selectToEndOfWord() + editor.selectToEndOfWord(); - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 13]) - expect(cursor2.getBufferPosition()).toEqual([3, 50]) + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 13]); + expect(cursor2.getBufferPosition()).toEqual([3, 50]); - expect(editor.getSelections().length).toBe(2) - const [selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 4], [0, 13]]) - expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual([[3, 48], [3, 50]]) - expect(selection2.isReversed()).toBeFalsy() - }) - }) + expect(editor.getSelections().length).toBe(2); + const [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 4], [0, 13]]); + expect(selection1.isReversed()).toBeFalsy(); + expect(selection2.getBufferRange()).toEqual([[3, 48], [3, 50]]); + expect(selection2.isReversed()).toBeFalsy(); + }); + }); describe('.selectToBeginningOfNextWord()', () => { it('selects text from cursor position to beginning of next word', () => { - editor.setCursorScreenPosition([0, 4]) - editor.addCursorAtScreenPosition([3, 48]) + editor.setCursorScreenPosition([0, 4]); + editor.addCursorAtScreenPosition([3, 48]); - editor.selectToBeginningOfNextWord() + editor.selectToBeginningOfNextWord(); - expect(editor.getCursors().length).toBe(2) - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 14]) - expect(cursor2.getBufferPosition()).toEqual([3, 51]) + expect(editor.getCursors().length).toBe(2); + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 14]); + expect(cursor2.getBufferPosition()).toEqual([3, 51]); - expect(editor.getSelections().length).toBe(2) - const [selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 4], [0, 14]]) - expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual([[3, 48], [3, 51]]) - expect(selection2.isReversed()).toBeFalsy() - }) - }) + expect(editor.getSelections().length).toBe(2); + const [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 4], [0, 14]]); + expect(selection1.isReversed()).toBeFalsy(); + expect(selection2.getBufferRange()).toEqual([[3, 48], [3, 51]]); + expect(selection2.isReversed()).toBeFalsy(); + }); + }); describe('.selectToPreviousWordBoundary()', () => { it('select to the previous word boundary', () => { - editor.setCursorBufferPosition([0, 8]) - editor.addCursorAtBufferPosition([2, 0]) - editor.addCursorAtBufferPosition([3, 4]) - editor.addCursorAtBufferPosition([3, 14]) + editor.setCursorBufferPosition([0, 8]); + editor.addCursorAtBufferPosition([2, 0]); + editor.addCursorAtBufferPosition([3, 4]); + editor.addCursorAtBufferPosition([3, 14]); - editor.selectToPreviousWordBoundary() + editor.selectToPreviousWordBoundary(); - expect(editor.getSelections().length).toBe(4) + expect(editor.getSelections().length).toBe(4); const [ selection1, selection2, selection3, selection4 - ] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 8], [0, 4]]) - expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual([[2, 0], [1, 30]]) - expect(selection2.isReversed()).toBeTruthy() - expect(selection3.getBufferRange()).toEqual([[3, 4], [3, 0]]) - expect(selection3.isReversed()).toBeTruthy() - expect(selection4.getBufferRange()).toEqual([[3, 14], [3, 13]]) - expect(selection4.isReversed()).toBeTruthy() - }) - }) + ] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 8], [0, 4]]); + expect(selection1.isReversed()).toBeTruthy(); + expect(selection2.getBufferRange()).toEqual([[2, 0], [1, 30]]); + expect(selection2.isReversed()).toBeTruthy(); + expect(selection3.getBufferRange()).toEqual([[3, 4], [3, 0]]); + expect(selection3.isReversed()).toBeTruthy(); + expect(selection4.getBufferRange()).toEqual([[3, 14], [3, 13]]); + expect(selection4.isReversed()).toBeTruthy(); + }); + }); describe('.selectToNextWordBoundary()', () => { it('select to the next word boundary', () => { - editor.setCursorBufferPosition([0, 8]) - editor.addCursorAtBufferPosition([2, 40]) - editor.addCursorAtBufferPosition([4, 0]) - editor.addCursorAtBufferPosition([3, 30]) + editor.setCursorBufferPosition([0, 8]); + editor.addCursorAtBufferPosition([2, 40]); + editor.addCursorAtBufferPosition([4, 0]); + editor.addCursorAtBufferPosition([3, 30]); - editor.selectToNextWordBoundary() + editor.selectToNextWordBoundary(); - expect(editor.getSelections().length).toBe(4) + expect(editor.getSelections().length).toBe(4); const [ selection1, selection2, selection3, selection4 - ] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 8], [0, 13]]) - expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual([[2, 40], [3, 0]]) - expect(selection2.isReversed()).toBeFalsy() - expect(selection3.getBufferRange()).toEqual([[4, 0], [4, 4]]) - expect(selection3.isReversed()).toBeFalsy() - expect(selection4.getBufferRange()).toEqual([[3, 30], [3, 31]]) - expect(selection4.isReversed()).toBeFalsy() - }) - }) + ] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 8], [0, 13]]); + expect(selection1.isReversed()).toBeFalsy(); + expect(selection2.getBufferRange()).toEqual([[2, 40], [3, 0]]); + expect(selection2.isReversed()).toBeFalsy(); + expect(selection3.getBufferRange()).toEqual([[4, 0], [4, 4]]); + expect(selection3.isReversed()).toBeFalsy(); + expect(selection4.getBufferRange()).toEqual([[3, 30], [3, 31]]); + expect(selection4.isReversed()).toBeFalsy(); + }); + }); describe('.selectToPreviousSubwordBoundary', () => { it('selects subwords', () => { - editor.setText('') - editor.insertText('_word\n') - editor.insertText(' getPreviousWord\n') - editor.insertText('e, => \n') - editor.insertText(' 88 \n') - editor.setCursorBufferPosition([0, 5]) - editor.addCursorAtBufferPosition([1, 7]) - editor.addCursorAtBufferPosition([2, 5]) - editor.addCursorAtBufferPosition([3, 3]) + editor.setText(''); + editor.insertText('_word\n'); + editor.insertText(' getPreviousWord\n'); + editor.insertText('e, => \n'); + editor.insertText(' 88 \n'); + editor.setCursorBufferPosition([0, 5]); + editor.addCursorAtBufferPosition([1, 7]); + editor.addCursorAtBufferPosition([2, 5]); + editor.addCursorAtBufferPosition([3, 3]); const [ selection1, selection2, selection3, selection4 - ] = editor.getSelections() + ] = editor.getSelections(); - editor.selectToPreviousSubwordBoundary() - expect(selection1.getBufferRange()).toEqual([[0, 1], [0, 5]]) - expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual([[1, 4], [1, 7]]) - expect(selection2.isReversed()).toBeTruthy() - expect(selection3.getBufferRange()).toEqual([[2, 3], [2, 5]]) - expect(selection3.isReversed()).toBeTruthy() - expect(selection4.getBufferRange()).toEqual([[3, 1], [3, 3]]) - expect(selection4.isReversed()).toBeTruthy() - }) - }) + editor.selectToPreviousSubwordBoundary(); + expect(selection1.getBufferRange()).toEqual([[0, 1], [0, 5]]); + expect(selection1.isReversed()).toBeTruthy(); + expect(selection2.getBufferRange()).toEqual([[1, 4], [1, 7]]); + expect(selection2.isReversed()).toBeTruthy(); + expect(selection3.getBufferRange()).toEqual([[2, 3], [2, 5]]); + expect(selection3.isReversed()).toBeTruthy(); + expect(selection4.getBufferRange()).toEqual([[3, 1], [3, 3]]); + expect(selection4.isReversed()).toBeTruthy(); + }); + }); describe('.selectToNextSubwordBoundary', () => { it('selects subwords', () => { - editor.setText('') - editor.insertText('word_\n') - editor.insertText('getPreviousWord\n') - editor.insertText('e, => \n') - editor.insertText(' 88 \n') - editor.setCursorBufferPosition([0, 1]) - editor.addCursorAtBufferPosition([1, 7]) - editor.addCursorAtBufferPosition([2, 2]) - editor.addCursorAtBufferPosition([3, 1]) + editor.setText(''); + editor.insertText('word_\n'); + editor.insertText('getPreviousWord\n'); + editor.insertText('e, => \n'); + editor.insertText(' 88 \n'); + editor.setCursorBufferPosition([0, 1]); + editor.addCursorAtBufferPosition([1, 7]); + editor.addCursorAtBufferPosition([2, 2]); + editor.addCursorAtBufferPosition([3, 1]); const [ selection1, selection2, selection3, selection4 - ] = editor.getSelections() + ] = editor.getSelections(); - editor.selectToNextSubwordBoundary() - expect(selection1.getBufferRange()).toEqual([[0, 1], [0, 4]]) - expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual([[1, 7], [1, 11]]) - expect(selection2.isReversed()).toBeFalsy() - expect(selection3.getBufferRange()).toEqual([[2, 2], [2, 5]]) - expect(selection3.isReversed()).toBeFalsy() - expect(selection4.getBufferRange()).toEqual([[3, 1], [3, 3]]) - expect(selection4.isReversed()).toBeFalsy() - }) - }) + editor.selectToNextSubwordBoundary(); + expect(selection1.getBufferRange()).toEqual([[0, 1], [0, 4]]); + expect(selection1.isReversed()).toBeFalsy(); + expect(selection2.getBufferRange()).toEqual([[1, 7], [1, 11]]); + expect(selection2.isReversed()).toBeFalsy(); + expect(selection3.getBufferRange()).toEqual([[2, 2], [2, 5]]); + expect(selection3.isReversed()).toBeFalsy(); + expect(selection4.getBufferRange()).toEqual([[3, 1], [3, 3]]); + expect(selection4.isReversed()).toBeFalsy(); + }); + }); describe('.deleteToBeginningOfSubword', () => { it('deletes subwords', () => { - editor.setText('') - editor.insertText('_word\n') - editor.insertText(' getPreviousWord\n') - editor.insertText('e, => \n') - editor.insertText(' 88 \n') - editor.setCursorBufferPosition([0, 5]) - editor.addCursorAtBufferPosition([1, 7]) - editor.addCursorAtBufferPosition([2, 5]) - editor.addCursorAtBufferPosition([3, 3]) - const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors() + editor.setText(''); + editor.insertText('_word\n'); + editor.insertText(' getPreviousWord\n'); + editor.insertText('e, => \n'); + editor.insertText(' 88 \n'); + editor.setCursorBufferPosition([0, 5]); + editor.addCursorAtBufferPosition([1, 7]); + editor.addCursorAtBufferPosition([2, 5]); + editor.addCursorAtBufferPosition([3, 3]); + const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors(); - editor.deleteToBeginningOfSubword() - expect(buffer.lineForRow(0)).toBe('_') - expect(buffer.lineForRow(1)).toBe(' getviousWord') - expect(buffer.lineForRow(2)).toBe('e, ') - expect(buffer.lineForRow(3)).toBe(' ') - expect(cursor1.getBufferPosition()).toEqual([0, 1]) - expect(cursor2.getBufferPosition()).toEqual([1, 4]) - expect(cursor3.getBufferPosition()).toEqual([2, 3]) - expect(cursor4.getBufferPosition()).toEqual([3, 1]) + editor.deleteToBeginningOfSubword(); + expect(buffer.lineForRow(0)).toBe('_'); + expect(buffer.lineForRow(1)).toBe(' getviousWord'); + expect(buffer.lineForRow(2)).toBe('e, '); + expect(buffer.lineForRow(3)).toBe(' '); + expect(cursor1.getBufferPosition()).toEqual([0, 1]); + expect(cursor2.getBufferPosition()).toEqual([1, 4]); + expect(cursor3.getBufferPosition()).toEqual([2, 3]); + expect(cursor4.getBufferPosition()).toEqual([3, 1]); - editor.deleteToBeginningOfSubword() - expect(buffer.lineForRow(0)).toBe('') - expect(buffer.lineForRow(1)).toBe(' viousWord') - expect(buffer.lineForRow(2)).toBe('e ') - expect(buffer.lineForRow(3)).toBe(' ') - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 1]) - expect(cursor3.getBufferPosition()).toEqual([2, 1]) - expect(cursor4.getBufferPosition()).toEqual([3, 0]) + editor.deleteToBeginningOfSubword(); + expect(buffer.lineForRow(0)).toBe(''); + expect(buffer.lineForRow(1)).toBe(' viousWord'); + expect(buffer.lineForRow(2)).toBe('e '); + expect(buffer.lineForRow(3)).toBe(' '); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 1]); + expect(cursor3.getBufferPosition()).toEqual([2, 1]); + expect(cursor4.getBufferPosition()).toEqual([3, 0]); - editor.deleteToBeginningOfSubword() - expect(buffer.lineForRow(0)).toBe('') - expect(buffer.lineForRow(1)).toBe('viousWord') - expect(buffer.lineForRow(2)).toBe(' ') - expect(buffer.lineForRow(3)).toBe('') - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 0]) - expect(cursor3.getBufferPosition()).toEqual([2, 0]) - expect(cursor4.getBufferPosition()).toEqual([2, 1]) - }) - }) + editor.deleteToBeginningOfSubword(); + expect(buffer.lineForRow(0)).toBe(''); + expect(buffer.lineForRow(1)).toBe('viousWord'); + expect(buffer.lineForRow(2)).toBe(' '); + expect(buffer.lineForRow(3)).toBe(''); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 0]); + expect(cursor3.getBufferPosition()).toEqual([2, 0]); + expect(cursor4.getBufferPosition()).toEqual([2, 1]); + }); + }); describe('.deleteToEndOfSubword', () => { it('deletes subwords', () => { - editor.setText('') - editor.insertText('word_\n') - editor.insertText('getPreviousWord \n') - editor.insertText('e, => \n') - editor.insertText(' 88 \n') - editor.setCursorBufferPosition([0, 0]) - editor.addCursorAtBufferPosition([1, 0]) - editor.addCursorAtBufferPosition([2, 2]) - editor.addCursorAtBufferPosition([3, 0]) - const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors() + editor.setText(''); + editor.insertText('word_\n'); + editor.insertText('getPreviousWord \n'); + editor.insertText('e, => \n'); + editor.insertText(' 88 \n'); + editor.setCursorBufferPosition([0, 0]); + editor.addCursorAtBufferPosition([1, 0]); + editor.addCursorAtBufferPosition([2, 2]); + editor.addCursorAtBufferPosition([3, 0]); + const [cursor1, cursor2, cursor3, cursor4] = editor.getCursors(); - editor.deleteToEndOfSubword() - expect(buffer.lineForRow(0)).toBe('_') - expect(buffer.lineForRow(1)).toBe('PreviousWord ') - expect(buffer.lineForRow(2)).toBe('e, ') - expect(buffer.lineForRow(3)).toBe('88 ') - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 0]) - expect(cursor3.getBufferPosition()).toEqual([2, 2]) - expect(cursor4.getBufferPosition()).toEqual([3, 0]) + editor.deleteToEndOfSubword(); + expect(buffer.lineForRow(0)).toBe('_'); + expect(buffer.lineForRow(1)).toBe('PreviousWord '); + expect(buffer.lineForRow(2)).toBe('e, '); + expect(buffer.lineForRow(3)).toBe('88 '); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 0]); + expect(cursor3.getBufferPosition()).toEqual([2, 2]); + expect(cursor4.getBufferPosition()).toEqual([3, 0]); - editor.deleteToEndOfSubword() - expect(buffer.lineForRow(0)).toBe('') - expect(buffer.lineForRow(1)).toBe('Word ') - expect(buffer.lineForRow(2)).toBe('e,') - expect(buffer.lineForRow(3)).toBe(' ') - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 0]) - expect(cursor3.getBufferPosition()).toEqual([2, 2]) - expect(cursor4.getBufferPosition()).toEqual([3, 0]) - }) - }) + editor.deleteToEndOfSubword(); + expect(buffer.lineForRow(0)).toBe(''); + expect(buffer.lineForRow(1)).toBe('Word '); + expect(buffer.lineForRow(2)).toBe('e,'); + expect(buffer.lineForRow(3)).toBe(' '); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 0]); + expect(cursor3.getBufferPosition()).toEqual([2, 2]); + expect(cursor4.getBufferPosition()).toEqual([3, 0]); + }); + }); describe('.selectWordsContainingCursors()', () => { describe('when the cursor is inside a word', () => { it('selects the entire word', () => { - editor.setCursorScreenPosition([0, 8]) - editor.selectWordsContainingCursors() - expect(editor.getSelectedText()).toBe('quicksort') - }) - }) + editor.setCursorScreenPosition([0, 8]); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedText()).toBe('quicksort'); + }); + }); describe('when the cursor is between two words', () => { it('selects the word the cursor is on', () => { - editor.setCursorBufferPosition([0, 4]) - editor.selectWordsContainingCursors() - expect(editor.getSelectedText()).toBe('quicksort') + editor.setCursorBufferPosition([0, 4]); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedText()).toBe('quicksort'); - editor.setCursorBufferPosition([0, 3]) - editor.selectWordsContainingCursors() - expect(editor.getSelectedText()).toBe('var') + editor.setCursorBufferPosition([0, 3]); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedText()).toBe('var'); - editor.setCursorBufferPosition([1, 22]) - editor.selectWordsContainingCursors() - expect(editor.getSelectedText()).toBe('items') - }) - }) + editor.setCursorBufferPosition([1, 22]); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedText()).toBe('items'); + }); + }); describe('when the cursor is inside a region of whitespace', () => { it('selects the whitespace region', () => { - editor.setCursorScreenPosition([5, 2]) - editor.selectWordsContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[5, 0], [5, 6]]) + editor.setCursorScreenPosition([5, 2]); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[5, 0], [5, 6]]); - editor.setCursorScreenPosition([5, 0]) - editor.selectWordsContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[5, 0], [5, 6]]) - }) - }) + editor.setCursorScreenPosition([5, 0]); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[5, 0], [5, 6]]); + }); + }); describe('when the cursor is at the end of the text', () => { it('select the previous word', () => { - editor.buffer.append('word') - editor.moveToBottom() - editor.selectWordsContainingCursors() - expect(editor.getSelectedBufferRange()).toEqual([[12, 2], [12, 6]]) - }) - }) + editor.buffer.append('word'); + editor.moveToBottom(); + editor.selectWordsContainingCursors(); + expect(editor.getSelectedBufferRange()).toEqual([[12, 2], [12, 6]]); + }); + }); it("selects words based on the non-word characters configured at the cursor's current scope", () => { - editor.setText("one-one; 'two-two'; three-three") + editor.setText("one-one; 'two-two'; three-three"); - editor.setCursorBufferPosition([0, 1]) - editor.addCursorAtBufferPosition([0, 12]) + editor.setCursorBufferPosition([0, 1]); + editor.addCursorAtBufferPosition([0, 12]); const scopeDescriptors = editor .getCursors() - .map(c => c.getScopeDescriptor()) - expect(scopeDescriptors[0].getScopesArray()).toEqual(['source.js']) + .map(c => c.getScopeDescriptor()); + expect(scopeDescriptors[0].getScopesArray()).toEqual(['source.js']); expect(scopeDescriptors[1].getScopesArray()).toEqual([ 'source.js', 'string.quoted' - ]) + ]); spyOn( editor.getBuffer().getLanguageMode(), 'getNonWordCharacters' - ).andCallFake(function (position) { - const result = '/()"\':,.;<>~!@#$%^&*|+=[]{}`?' + ).andCallFake(function(position) { + const result = '/()"\':,.;<>~!@#$%^&*|+=[]{}`?'; const scopes = this.scopeDescriptorForPosition( position - ).getScopesArray() + ).getScopesArray(); if (scopes.some(scope => scope.startsWith('string'))) { - return result + return result; } else { - return result + '-' + return result + '-'; } - }) + }); - editor.selectWordsContainingCursors() + editor.selectWordsContainingCursors(); - expect(editor.getSelections()[0].getText()).toBe('one') - expect(editor.getSelections()[1].getText()).toBe('two-two') - }) - }) + expect(editor.getSelections()[0].getText()).toBe('one'); + expect(editor.getSelections()[1].getText()).toBe('two-two'); + }); + }); describe('.selectToFirstCharacterOfLine()', () => { it("moves to the first character of the current line or the beginning of the line if it's already on the first character", () => { - editor.setCursorScreenPosition([0, 5]) - editor.addCursorAtScreenPosition([1, 7]) + editor.setCursorScreenPosition([0, 5]); + editor.addCursorAtScreenPosition([1, 7]); - editor.selectToFirstCharacterOfLine() + editor.selectToFirstCharacterOfLine(); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor2.getBufferPosition()).toEqual([1, 2]) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor2.getBufferPosition()).toEqual([1, 2]); - expect(editor.getSelections().length).toBe(2) - let [selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 0], [0, 5]]) - expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual([[1, 2], [1, 7]]) - expect(selection2.isReversed()).toBeTruthy() + expect(editor.getSelections().length).toBe(2); + let [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 0], [0, 5]]); + expect(selection1.isReversed()).toBeTruthy(); + expect(selection2.getBufferRange()).toEqual([[1, 2], [1, 7]]); + expect(selection2.isReversed()).toBeTruthy(); - editor.selectToFirstCharacterOfLine() - ;[selection1, selection2] = editor.getSelections() - expect(selection1.getBufferRange()).toEqual([[0, 0], [0, 5]]) - expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual([[1, 0], [1, 7]]) - expect(selection2.isReversed()).toBeTruthy() - }) - }) + editor.selectToFirstCharacterOfLine(); + [selection1, selection2] = editor.getSelections(); + expect(selection1.getBufferRange()).toEqual([[0, 0], [0, 5]]); + expect(selection1.isReversed()).toBeTruthy(); + expect(selection2.getBufferRange()).toEqual([[1, 0], [1, 7]]); + expect(selection2.isReversed()).toBeTruthy(); + }); + }); describe('.setSelectedBufferRanges(ranges)', () => { it('clears existing selections and creates selections for each of the given ranges', () => { - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]]) + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]]); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 2], [3, 3]], [[4, 4], [5, 5]] - ]) + ]); - editor.setSelectedBufferRanges([[[5, 5], [6, 6]]]) - expect(editor.getSelectedBufferRanges()).toEqual([[[5, 5], [6, 6]]]) - }) + editor.setSelectedBufferRanges([[[5, 5], [6, 6]]]); + expect(editor.getSelectedBufferRanges()).toEqual([[[5, 5], [6, 6]]]); + }); it('merges intersecting selections', () => { - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]]) - expect(editor.getSelectedBufferRanges()).toEqual([[[2, 2], [5, 5]]]) - }) + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]]); + expect(editor.getSelectedBufferRanges()).toEqual([[[2, 2], [5, 5]]]); + }); it('does not merge non-empty adjacent selections', () => { - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 3], [5, 5]]]) + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 3], [5, 5]]]); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 2], [3, 3]], [[3, 3], [5, 5]] - ]) - }) + ]); + }); it('recycles existing selection instances', () => { - selection = editor.getLastSelection() - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]]) + selection = editor.getLastSelection(); + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]]); - const [selection1] = editor.getSelections() - expect(selection1).toBe(selection) - expect(selection1.getBufferRange()).toEqual([[2, 2], [3, 3]]) - }) + const [selection1] = editor.getSelections(); + expect(selection1).toBe(selection); + expect(selection1.getBufferRange()).toEqual([[2, 2], [3, 3]]); + }); describe("when the 'preserveFolds' option is false (the default)", () => { it("removes folds that contain one or both of the selection's end points", () => { - editor.setSelectedBufferRange([[0, 0], [0, 0]]) - editor.foldBufferRowRange(1, 4) - editor.foldBufferRowRange(2, 3) - editor.foldBufferRowRange(6, 8) - editor.foldBufferRowRange(10, 11) + editor.setSelectedBufferRange([[0, 0], [0, 0]]); + editor.foldBufferRowRange(1, 4); + editor.foldBufferRowRange(2, 3); + editor.foldBufferRowRange(6, 8); + editor.foldBufferRowRange(10, 11); - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]]) - expect(editor.isFoldedAtScreenRow(1)).toBeFalsy() - expect(editor.isFoldedAtScreenRow(2)).toBeFalsy() - expect(editor.isFoldedAtScreenRow(6)).toBeFalsy() - expect(editor.isFoldedAtScreenRow(10)).toBeTruthy() + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]]); + expect(editor.isFoldedAtScreenRow(1)).toBeFalsy(); + expect(editor.isFoldedAtScreenRow(2)).toBeFalsy(); + expect(editor.isFoldedAtScreenRow(6)).toBeFalsy(); + expect(editor.isFoldedAtScreenRow(10)).toBeTruthy(); - editor.setSelectedBufferRange([[10, 0], [12, 0]]) - expect(editor.isFoldedAtScreenRow(10)).toBeTruthy() - }) - }) + editor.setSelectedBufferRange([[10, 0], [12, 0]]); + expect(editor.isFoldedAtScreenRow(10)).toBeTruthy(); + }); + }); describe("when the 'preserveFolds' option is true", () => { it('does not remove folds that contain the selections', () => { - editor.setSelectedBufferRange([[0, 0], [0, 0]]) - editor.foldBufferRowRange(1, 4) - editor.foldBufferRowRange(6, 8) + editor.setSelectedBufferRange([[0, 0], [0, 0]]); + editor.foldBufferRowRange(1, 4); + editor.foldBufferRowRange(6, 8); editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 0], [6, 1]]], { preserveFolds: true - }) - expect(editor.isFoldedAtBufferRow(1)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - }) - }) - }) + }); + expect(editor.isFoldedAtBufferRow(1)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + }); + }); + }); describe('.setSelectedScreenRanges(ranges)', () => { - beforeEach(() => editor.foldBufferRow(4)) + beforeEach(() => editor.foldBufferRow(4)); it('clears existing selections and creates selections for each of the given ranges', () => { - editor.setSelectedScreenRanges([[[3, 4], [3, 7]], [[5, 4], [5, 7]]]) + editor.setSelectedScreenRanges([[[3, 4], [3, 7]], [[5, 4], [5, 7]]]); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 4], [3, 7]], [[8, 4], [8, 7]] - ]) + ]); - editor.setSelectedScreenRanges([[[6, 2], [6, 4]]]) - expect(editor.getSelectedScreenRanges()).toEqual([[[6, 2], [6, 4]]]) - }) + editor.setSelectedScreenRanges([[[6, 2], [6, 4]]]); + expect(editor.getSelectedScreenRanges()).toEqual([[[6, 2], [6, 4]]]); + }); it('merges intersecting selections and unfolds the fold which contain them', () => { - editor.foldBufferRow(0) + editor.foldBufferRow(0); // Use buffer ranges because only the first line is on screen - editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]]) - expect(editor.getSelectedBufferRanges()).toEqual([[[2, 2], [5, 5]]]) - }) + editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]]); + expect(editor.getSelectedBufferRanges()).toEqual([[[2, 2], [5, 5]]]); + }); it('recycles existing selection instances', () => { - selection = editor.getLastSelection() - editor.setSelectedScreenRanges([[[2, 2], [3, 4]], [[4, 4], [5, 5]]]) + selection = editor.getLastSelection(); + editor.setSelectedScreenRanges([[[2, 2], [3, 4]], [[4, 4], [5, 5]]]); - const [selection1] = editor.getSelections() - expect(selection1).toBe(selection) - expect(selection1.getScreenRange()).toEqual([[2, 2], [3, 4]]) - }) - }) + const [selection1] = editor.getSelections(); + expect(selection1).toBe(selection); + expect(selection1.getScreenRange()).toEqual([[2, 2], [3, 4]]); + }); + }); describe('.selectMarker(marker)', () => { describe('if the marker is valid', () => { it("selects the marker's range and returns the selected range", () => { - const marker = editor.markBufferRange([[0, 1], [3, 3]]) - expect(editor.selectMarker(marker)).toEqual([[0, 1], [3, 3]]) - expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [3, 3]]) - }) - }) + const marker = editor.markBufferRange([[0, 1], [3, 3]]); + expect(editor.selectMarker(marker)).toEqual([[0, 1], [3, 3]]); + expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [3, 3]]); + }); + }); describe('if the marker is invalid', () => { it('does not change the selection and returns a falsy value', () => { - const marker = editor.markBufferRange([[0, 1], [3, 3]]) - marker.destroy() - expect(editor.selectMarker(marker)).toBeFalsy() - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 0]]) - }) - }) - }) + const marker = editor.markBufferRange([[0, 1], [3, 3]]); + marker.destroy(); + expect(editor.selectMarker(marker)).toBeFalsy(); + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 0]]); + }); + }); + }); describe('.addSelectionForBufferRange(bufferRange)', () => { it('adds a selection for the specified buffer range', () => { - editor.addSelectionForBufferRange([[3, 4], [5, 6]]) + editor.addSelectionForBufferRange([[3, 4], [5, 6]]); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 0], [0, 0]], [[3, 4], [5, 6]] - ]) - }) - }) + ]); + }); + }); describe('.addSelectionBelow()', () => { describe('when the selection is non-empty', () => { it('selects the same region of the line below current selections if possible', () => { - editor.setSelectedBufferRange([[3, 16], [3, 21]]) - editor.addSelectionForBufferRange([[3, 25], [3, 34]]) - editor.addSelectionBelow() + editor.setSelectedBufferRange([[3, 16], [3, 21]]); + editor.addSelectionForBufferRange([[3, 25], [3, 34]]); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 16], [3, 21]], [[3, 25], [3, 34]], [[4, 16], [4, 21]], [[4, 25], [4, 29]] - ]) - }) + ]); + }); it('skips lines that are too short to create a non-empty selection', () => { - editor.setSelectedBufferRange([[3, 31], [3, 38]]) - editor.addSelectionBelow() + editor.setSelectedBufferRange([[3, 31], [3, 38]]); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 31], [3, 38]], [[6, 31], [6, 38]] - ]) - }) + ]); + }); it("honors the original selection's range (goal range) when adding across shorter lines", () => { - editor.setSelectedBufferRange([[3, 22], [3, 38]]) - editor.addSelectionBelow() - editor.addSelectionBelow() - editor.addSelectionBelow() + editor.setSelectedBufferRange([[3, 22], [3, 38]]); + editor.addSelectionBelow(); + editor.addSelectionBelow(); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 22], [3, 38]], [[4, 22], [4, 29]], [[5, 22], [5, 30]], [[6, 22], [6, 38]] - ]) - }) + ]); + }); it('clears selection goal ranges when the selection changes', () => { - editor.setSelectedBufferRange([[3, 22], [3, 38]]) - editor.addSelectionBelow() - editor.selectLeft() - editor.addSelectionBelow() + editor.setSelectedBufferRange([[3, 22], [3, 38]]); + editor.addSelectionBelow(); + editor.selectLeft(); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 22], [3, 37]], [[4, 22], [4, 29]], [[5, 22], [5, 28]] - ]) + ]); // goal range from previous add selection is honored next time - editor.addSelectionBelow() + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 22], [3, 37]], [[4, 22], [4, 29]], [[5, 22], [5, 30]], // select to end of line 5 because line 4's goal range was reset by line 3 previously [[6, 22], [6, 28]] - ]) - }) + ]); + }); it('can add selections to soft-wrapped line segments', () => { - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(40) - editor.setDefaultCharWidth(1) + editor.setSoftWrapped(true); + editor.setEditorWidthInChars(40); + editor.setDefaultCharWidth(1); - editor.setSelectedScreenRange([[3, 10], [3, 15]]) - editor.addSelectionBelow() + editor.setSelectedScreenRange([[3, 10], [3, 15]]); + editor.addSelectionBelow(); expect(editor.getSelectedScreenRanges()).toEqual([ [[3, 10], [3, 15]], [[4, 10], [4, 15]] - ]) - }) + ]); + }); it('takes atomic tokens into account', async () => { editor = await atom.workspace.open( 'sample-with-tabs-and-leading-comment.coffee', { autoIndent: false } - ) - editor.setSelectedBufferRange([[2, 1], [2, 3]]) - editor.addSelectionBelow() + ); + editor.setSelectedBufferRange([[2, 1], [2, 3]]); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 1], [2, 3]], [[3, 1], [3, 2]] - ]) - }) - }) + ]); + }); + }); describe('when the selection is empty', () => { describe('when lines are soft-wrapped', () => { beforeEach(() => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(40) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(40); + }); it('skips soft-wrap indentation tokens', () => { - editor.setCursorScreenPosition([3, 0]) - editor.addSelectionBelow() + editor.setCursorScreenPosition([3, 0]); + editor.addSelectionBelow(); expect(editor.getSelectedScreenRanges()).toEqual([ [[3, 0], [3, 0]], [[4, 4], [4, 4]] - ]) - }) + ]); + }); it("does not skip them if they're shorter than the current column", () => { - editor.setCursorScreenPosition([3, 37]) - editor.addSelectionBelow() + editor.setCursorScreenPosition([3, 37]); + editor.addSelectionBelow(); expect(editor.getSelectedScreenRanges()).toEqual([ [[3, 37], [3, 37]], [[4, 26], [4, 26]] - ]) - }) - }) + ]); + }); + }); it('does not skip lines that are shorter than the current column', () => { - editor.setCursorBufferPosition([3, 36]) - editor.addSelectionBelow() - editor.addSelectionBelow() - editor.addSelectionBelow() + editor.setCursorBufferPosition([3, 36]); + editor.addSelectionBelow(); + editor.addSelectionBelow(); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 36], [3, 36]], [[4, 29], [4, 29]], [[5, 30], [5, 30]], [[6, 36], [6, 36]] - ]) - }) + ]); + }); it('skips empty lines when the column is non-zero', () => { - editor.setCursorBufferPosition([9, 4]) - editor.addSelectionBelow() + editor.setCursorBufferPosition([9, 4]); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[9, 4], [9, 4]], [[11, 4], [11, 4]] - ]) - }) + ]); + }); it('does not skip empty lines when the column is zero', () => { - editor.setCursorBufferPosition([9, 0]) - editor.addSelectionBelow() + editor.setCursorBufferPosition([9, 0]); + editor.addSelectionBelow(); expect(editor.getSelectedBufferRanges()).toEqual([ [[9, 0], [9, 0]], [[10, 0], [10, 0]] - ]) - }) - }) + ]); + }); + }); it('does not create a new selection if it would be fully contained within another selection', () => { - editor.setText('abc\ndef\nghi\njkl\nmno') - editor.setCursorBufferPosition([0, 1]) + editor.setText('abc\ndef\nghi\njkl\nmno'); + editor.setCursorBufferPosition([0, 1]); - let addedSelectionCount = 0 + let addedSelectionCount = 0; editor.onDidAddSelection(() => { - addedSelectionCount++ - }) + addedSelectionCount++; + }); - editor.addSelectionBelow() - editor.addSelectionBelow() - editor.addSelectionBelow() - expect(addedSelectionCount).toBe(3) - }) - }) + editor.addSelectionBelow(); + editor.addSelectionBelow(); + editor.addSelectionBelow(); + expect(addedSelectionCount).toBe(3); + }); + }); describe('.addSelectionAbove()', () => { describe('when the selection is non-empty', () => { it('selects the same region of the line above current selections if possible', () => { - editor.setSelectedBufferRange([[3, 16], [3, 21]]) - editor.addSelectionForBufferRange([[3, 37], [3, 44]]) - editor.addSelectionAbove() + editor.setSelectedBufferRange([[3, 16], [3, 21]]); + editor.addSelectionForBufferRange([[3, 37], [3, 44]]); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 16], [3, 21]], [[3, 37], [3, 44]], [[2, 16], [2, 21]], [[2, 37], [2, 40]] - ]) - }) + ]); + }); it('skips lines that are too short to create a non-empty selection', () => { - editor.setSelectedBufferRange([[6, 31], [6, 38]]) - editor.addSelectionAbove() + editor.setSelectedBufferRange([[6, 31], [6, 38]]); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[6, 31], [6, 38]], [[3, 31], [3, 38]] - ]) - }) + ]); + }); it("honors the original selection's range (goal range) when adding across shorter lines", () => { - editor.setSelectedBufferRange([[6, 22], [6, 38]]) - editor.addSelectionAbove() - editor.addSelectionAbove() - editor.addSelectionAbove() + editor.setSelectedBufferRange([[6, 22], [6, 38]]); + editor.addSelectionAbove(); + editor.addSelectionAbove(); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[6, 22], [6, 38]], [[5, 22], [5, 30]], [[4, 22], [4, 29]], [[3, 22], [3, 38]] - ]) - }) + ]); + }); it('can add selections to soft-wrapped line segments', () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(40) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(40); - editor.setSelectedScreenRange([[4, 10], [4, 15]]) - editor.addSelectionAbove() + editor.setSelectedScreenRange([[4, 10], [4, 15]]); + editor.addSelectionAbove(); expect(editor.getSelectedScreenRanges()).toEqual([ [[4, 10], [4, 15]], [[3, 10], [3, 15]] - ]) - }) + ]); + }); it('takes atomic tokens into account', async () => { editor = await atom.workspace.open( 'sample-with-tabs-and-leading-comment.coffee', { autoIndent: false } - ) - editor.setSelectedBufferRange([[3, 1], [3, 2]]) - editor.addSelectionAbove() + ); + editor.setSelectedBufferRange([[3, 1], [3, 2]]); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 1], [3, 2]], [[2, 1], [2, 3]] - ]) - }) - }) + ]); + }); + }); describe('when the selection is empty', () => { describe('when lines are soft-wrapped', () => { beforeEach(() => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(40) - }) + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(40); + }); it('skips soft-wrap indentation tokens', () => { - editor.setCursorScreenPosition([5, 0]) - editor.addSelectionAbove() + editor.setCursorScreenPosition([5, 0]); + editor.addSelectionAbove(); expect(editor.getSelectedScreenRanges()).toEqual([ [[5, 0], [5, 0]], [[4, 4], [4, 4]] - ]) - }) + ]); + }); it("does not skip them if they're shorter than the current column", () => { - editor.setCursorScreenPosition([5, 29]) - editor.addSelectionAbove() + editor.setCursorScreenPosition([5, 29]); + editor.addSelectionAbove(); expect(editor.getSelectedScreenRanges()).toEqual([ [[5, 29], [5, 29]], [[4, 26], [4, 26]] - ]) - }) - }) + ]); + }); + }); it('does not skip lines that are shorter than the current column', () => { - editor.setCursorBufferPosition([6, 36]) - editor.addSelectionAbove() - editor.addSelectionAbove() - editor.addSelectionAbove() + editor.setCursorBufferPosition([6, 36]); + editor.addSelectionAbove(); + editor.addSelectionAbove(); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[6, 36], [6, 36]], [[5, 30], [5, 30]], [[4, 29], [4, 29]], [[3, 36], [3, 36]] - ]) - }) + ]); + }); it('skips empty lines when the column is non-zero', () => { - editor.setCursorBufferPosition([11, 4]) - editor.addSelectionAbove() + editor.setCursorBufferPosition([11, 4]); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[11, 4], [11, 4]], [[9, 4], [9, 4]] - ]) - }) + ]); + }); it('does not skip empty lines when the column is zero', () => { - editor.setCursorBufferPosition([10, 0]) - editor.addSelectionAbove() + editor.setCursorBufferPosition([10, 0]); + editor.addSelectionAbove(); expect(editor.getSelectedBufferRanges()).toEqual([ [[10, 0], [10, 0]], [[9, 0], [9, 0]] - ]) - }) - }) + ]); + }); + }); it('does not create a new selection if it would be fully contained within another selection', () => { - editor.setText('abc\ndef\nghi\njkl\nmno') - editor.setCursorBufferPosition([4, 1]) + editor.setText('abc\ndef\nghi\njkl\nmno'); + editor.setCursorBufferPosition([4, 1]); - let addedSelectionCount = 0 + let addedSelectionCount = 0; editor.onDidAddSelection(() => { - addedSelectionCount++ - }) + addedSelectionCount++; + }); - editor.addSelectionAbove() - editor.addSelectionAbove() - editor.addSelectionAbove() - expect(addedSelectionCount).toBe(3) - }) - }) + editor.addSelectionAbove(); + editor.addSelectionAbove(); + editor.addSelectionAbove(); + expect(addedSelectionCount).toBe(3); + }); + }); describe('.splitSelectionsIntoLines()', () => { it('splits all multi-line selections into one selection per line', () => { - editor.setSelectedBufferRange([[0, 3], [2, 4]]) - editor.splitSelectionsIntoLines() + editor.setSelectedBufferRange([[0, 3], [2, 4]]); + editor.splitSelectionsIntoLines(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 3], [0, 29]], [[1, 0], [1, 30]], [[2, 0], [2, 4]] - ]) + ]); - editor.setSelectedBufferRange([[0, 3], [1, 10]]) - editor.splitSelectionsIntoLines() + editor.setSelectedBufferRange([[0, 3], [1, 10]]); + editor.splitSelectionsIntoLines(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 3], [0, 29]], [[1, 0], [1, 10]] - ]) + ]); - editor.setSelectedBufferRange([[0, 0], [0, 3]]) - editor.splitSelectionsIntoLines() - expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [0, 3]]]) - }) - }) + editor.setSelectedBufferRange([[0, 0], [0, 3]]); + editor.splitSelectionsIntoLines(); + expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [0, 3]]]); + }); + }); describe('::consolidateSelections()', () => { const makeMultipleSelections = () => { - selection.setBufferRange([[3, 16], [3, 21]]) - const selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]]) - const selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]]) - const selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]]) + selection.setBufferRange([[3, 16], [3, 21]]); + const selection2 = editor.addSelectionForBufferRange([ + [3, 25], + [3, 34] + ]); + const selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]]); + const selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]]); expect(editor.getSelections()).toEqual([ selection, selection2, selection3, selection4 - ]) - return [selection, selection2, selection3, selection4] - } + ]); + return [selection, selection2, selection3, selection4]; + }; it('destroys all selections but the oldest selection and autoscrolls to it, returning true if any selections were destroyed', () => { - const [selection1] = makeMultipleSelections() + const [selection1] = makeMultipleSelections(); - const autoscrollEvents = [] - editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)) + const autoscrollEvents = []; + editor.onDidRequestAutoscroll(event => autoscrollEvents.push(event)); - expect(editor.consolidateSelections()).toBeTruthy() - expect(editor.getSelections()).toEqual([selection1]) - expect(selection1.isEmpty()).toBeFalsy() - expect(editor.consolidateSelections()).toBeFalsy() - expect(editor.getSelections()).toEqual([selection1]) + expect(editor.consolidateSelections()).toBeTruthy(); + expect(editor.getSelections()).toEqual([selection1]); + expect(selection1.isEmpty()).toBeFalsy(); + expect(editor.consolidateSelections()).toBeFalsy(); + expect(editor.getSelections()).toEqual([selection1]); expect(autoscrollEvents).toEqual([ { screenRange: selection1.getScreenRange(), options: { center: true, reversed: false } } - ]) - }) - }) + ]); + }); + }); describe('when the cursor is moved while there is a selection', () => { - const makeSelection = () => selection.setBufferRange([[1, 2], [1, 5]]) + const makeSelection = () => selection.setBufferRange([[1, 2], [1, 5]]); it('clears the selection', () => { - makeSelection() - editor.moveDown() - expect(selection.isEmpty()).toBeTruthy() + makeSelection(); + editor.moveDown(); + expect(selection.isEmpty()).toBeTruthy(); - makeSelection() - editor.moveUp() - expect(selection.isEmpty()).toBeTruthy() + makeSelection(); + editor.moveUp(); + expect(selection.isEmpty()).toBeTruthy(); - makeSelection() - editor.moveLeft() - expect(selection.isEmpty()).toBeTruthy() + makeSelection(); + editor.moveLeft(); + expect(selection.isEmpty()).toBeTruthy(); - makeSelection() - editor.moveRight() - expect(selection.isEmpty()).toBeTruthy() + makeSelection(); + editor.moveRight(); + expect(selection.isEmpty()).toBeTruthy(); - makeSelection() - editor.setCursorScreenPosition([3, 3]) - expect(selection.isEmpty()).toBeTruthy() - }) - }) + makeSelection(); + editor.setCursorScreenPosition([3, 3]); + expect(selection.isEmpty()).toBeTruthy(); + }); + }); it('does not share selections between different edit sessions for the same buffer', async () => { - atom.workspace.getActivePane().splitRight() - const editor2 = await atom.workspace.open(editor.getPath()) + atom.workspace.getActivePane().splitRight(); + const editor2 = await atom.workspace.open(editor.getPath()); - expect(editor2.getText()).toBe(editor.getText()) - editor.setSelectedBufferRanges([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) - editor2.setSelectedBufferRanges([[[8, 7], [6, 5]], [[4, 3], [2, 1]]]) + expect(editor2.getText()).toBe(editor.getText()); + editor.setSelectedBufferRanges([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]); + editor2.setSelectedBufferRanges([[[8, 7], [6, 5]], [[4, 3], [2, 1]]]); expect(editor2.getSelectedBufferRanges()).not.toEqual( editor.getSelectedBufferRanges() - ) - }) - }) + ); + }); + }); describe('buffer manipulation', () => { describe('.moveLineUp', () => { it('moves the line under the cursor up', () => { - editor.setCursorBufferPosition([1, 0]) - editor.moveLineUp() + editor.setCursorBufferPosition([1, 0]); + editor.moveLineUp(); expect(editor.getTextInBufferRange([[0, 0], [0, 30]])).toBe( ' var sort = function(items) {' - ) - expect(editor.indentationForBufferRow(0)).toBe(1) - expect(editor.indentationForBufferRow(1)).toBe(0) - }) + ); + expect(editor.indentationForBufferRow(0)).toBe(1); + expect(editor.indentationForBufferRow(1)).toBe(0); + }); it("updates the line's indentation when the the autoIndent setting is true", () => { - editor.update({ autoIndent: true }) - editor.setCursorBufferPosition([1, 0]) - editor.moveLineUp() - expect(editor.indentationForBufferRow(0)).toBe(0) - expect(editor.indentationForBufferRow(1)).toBe(0) - }) + editor.update({ autoIndent: true }); + editor.setCursorBufferPosition([1, 0]); + editor.moveLineUp(); + expect(editor.indentationForBufferRow(0)).toBe(0); + expect(editor.indentationForBufferRow(1)).toBe(0); + }); describe('when there is a single selection', () => { describe('when the selection spans a single line', () => { @@ -2824,259 +2830,259 @@ describe('TextEditor', () => { it('moves the line to the preceding row', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - editor.setSelectedBufferRange([[3, 2], [3, 9]]) - editor.moveLineUp() + editor.setSelectedBufferRange([[3, 2], [3, 9]]); + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [2, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [2, 9]]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) - })) + ); + })); describe('when the cursor is at the beginning of a fold', () => it('moves the line to the previous row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRange([[4, 2], [4, 9]], { preserveFolds: true - }) - expect(editor.getSelectedBufferRange()).toEqual([[4, 2], [4, 9]]) + }); + expect(editor.getSelectedBufferRange()).toEqual([[4, 2], [4, 9]]); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.moveLineUp() + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [3, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [3, 9]]); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(7)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() - })) + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); + })); describe('when the preceding row consists of folded code', () => it('moves the line above the folded row and perseveres the correct folds', () => { expect(editor.lineTextForBufferRow(8)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) - expect(editor.lineTextForBufferRow(9)).toBe(' };') + ); + expect(editor.lineTextForBufferRow(9)).toBe(' };'); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.setSelectedBufferRange([[8, 0], [8, 4]]) - editor.moveLineUp() + editor.setSelectedBufferRange([[8, 0], [8, 4]]); + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [4, 4]]) + expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [4, 4]]); expect(editor.lineTextForBufferRow(4)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - })) - }) + ); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + })); + }); describe('when the selection spans multiple lines', () => { it('moves the lines spanned by the selection to the preceding row', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.setSelectedBufferRange([[3, 2], [4, 9]]) - editor.moveLineUp() + editor.setSelectedBufferRange([[3, 2], [4, 9]]); + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [3, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [3, 9]]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' if (items.length <= 1) return items;' - ) - }) + ); + }); describe("when the selection's end intersects a fold", () => it('moves the lines to the previous row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRange([[3, 2], [4, 9]], { preserveFolds: true - }) + }); - expect(editor.isFoldedAtBufferRow(3)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(3)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.moveLineUp() + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [3, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [3, 9]]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(7)).toBe( ' if (items.length <= 1) return items;' - ) + ); - expect(editor.isFoldedAtBufferRow(2)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() - })) + expect(editor.isFoldedAtBufferRow(2)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); + })); describe("when the selection's start intersects a fold", () => it('moves the lines to the previous row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRange([[4, 2], [8, 9]], { preserveFolds: true - }) + }); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); - editor.moveLineUp() + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [7, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [7, 9]]); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(7)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); expect(editor.lineTextForBufferRow(8)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() - })) - }) + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); + })); + }); describe('when the selection spans multiple lines, but ends at column 0', () => { it('does not move the last line of the selection', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.setSelectedBufferRange([[3, 2], [4, 0]]) - editor.moveLineUp() + editor.setSelectedBufferRange([[3, 2], [4, 0]]); + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [3, 0]]) + expect(editor.getSelectedBufferRange()).toEqual([[2, 2], [3, 0]]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) - }) - }) + ); + }); + }); describe('when the preceeding row is a folded row', () => { it('moves the lines spanned by the selection to the preceeding row, but preserves the folded code', () => { expect(editor.lineTextForBufferRow(8)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) - expect(editor.lineTextForBufferRow(9)).toBe(' };') + ); + expect(editor.lineTextForBufferRow(9)).toBe(' };'); - editor.foldBufferRowRange(4, 7) - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + editor.foldBufferRowRange(4, 7); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.setSelectedBufferRange([[8, 0], [9, 2]]) - editor.moveLineUp() + editor.setSelectedBufferRange([[8, 0], [9, 2]]); + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [5, 2]]) + expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [5, 2]]); expect(editor.lineTextForBufferRow(4)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) - expect(editor.lineTextForBufferRow(5)).toBe(' };') + ); + expect(editor.lineTextForBufferRow(5)).toBe(' };'); expect(editor.lineTextForBufferRow(6)).toBe( ' while(items.length > 0) {' - ) - expect(editor.isFoldedAtBufferRow(5)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(10)).toBeFalsy() - }) - }) - }) + ); + expect(editor.isFoldedAtBufferRow(5)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(10)).toBeFalsy(); + }); + }); + }); describe('when there are multiple selections', () => { describe('when all the selections span different lines', () => { @@ -3086,173 +3092,173 @@ describe('TextEditor', () => { [[1, 2], [1, 9]], [[3, 2], [3, 9]], [[5, 2], [5, 9]] - ]) - editor.moveLineUp() + ]); + editor.moveLineUp(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 2], [0, 9]], [[2, 2], [2, 9]], [[4, 2], [4, 9]] - ]) + ]); expect(editor.lineTextForBufferRow(0)).toBe( ' var sort = function(items) {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( 'var quicksort = function () {' - ) + ); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' current = items.shift();' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) - })) + ); + })); describe('when one selection intersects a fold', () => it('moves the lines to the previous row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRanges( [[[2, 2], [2, 9]], [[4, 2], [4, 9]]], { preserveFolds: true } - ) + ); - expect(editor.isFoldedAtBufferRow(2)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(3)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(2)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(3)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); - editor.moveLineUp() + editor.moveLineUp(); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 2], [1, 9]], [[3, 2], [3, 9]] - ]) + ]); expect(editor.lineTextForBufferRow(1)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(2)).toBe( ' var sort = function(items) {' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(7)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - expect(editor.isFoldedAtBufferRow(1)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(2)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() - })) + expect(editor.isFoldedAtBufferRow(1)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(2)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); + })); describe('when there is a fold', () => it('moves all lines that spanned by a selection to preceding row, preserving all folds', () => { - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); editor.setSelectedBufferRanges([ [[8, 0], [8, 3]], [[11, 0], [11, 5]] - ]) - editor.moveLineUp() + ]); + editor.moveLineUp(); expect(editor.getSelectedBufferRanges()).toEqual([ [[4, 0], [4, 3]], [[10, 0], [10, 5]] - ]) + ]); expect(editor.lineTextForBufferRow(4)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); expect(editor.lineTextForBufferRow(10)).toBe( ' return sort(Array.apply(this, arguments));' - ) - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - })) - }) + ); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + })); + }); describe('when there are many folds', () => { beforeEach(async () => { editor = await atom.workspace.open('sample-with-many-folds.js', { autoIndent: false - }) - }) + }); + }); describe('and many selections intersects folded rows', () => it('moves and preserves all the folds', () => { - editor.foldBufferRowRange(2, 4) - editor.foldBufferRowRange(7, 9) + editor.foldBufferRowRange(2, 4); + editor.foldBufferRowRange(7, 9); editor.setSelectedBufferRanges( [[[1, 0], [5, 4]], [[7, 0], [7, 4]]], { preserveFolds: true } - ) + ); - editor.moveLineUp() + editor.moveLineUp(); - expect(editor.lineTextForBufferRow(1)).toEqual('function f3() {') - expect(editor.lineTextForBufferRow(4)).toEqual('6;') - expect(editor.lineTextForBufferRow(5)).toEqual('1;') - expect(editor.lineTextForBufferRow(6)).toEqual('function f8() {') - expect(editor.lineTextForBufferRow(9)).toEqual('7;') + expect(editor.lineTextForBufferRow(1)).toEqual('function f3() {'); + expect(editor.lineTextForBufferRow(4)).toEqual('6;'); + expect(editor.lineTextForBufferRow(5)).toEqual('1;'); + expect(editor.lineTextForBufferRow(6)).toEqual('function f8() {'); + expect(editor.lineTextForBufferRow(9)).toEqual('7;'); - expect(editor.isFoldedAtBufferRow(1)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(2)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(1)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(2)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeFalsy(); - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - })) - }) + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + })); + }); describe('when some of the selections span the same lines', () => { it('moves lines that contain multiple selections correctly', () => { editor.setSelectedBufferRanges([ [[3, 2], [3, 9]], [[3, 12], [3, 13]] - ]) - editor.moveLineUp() + ]); + editor.moveLineUp(); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 2], [2, 9]], [[2, 12], [2, 13]] - ]) + ]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - }) - }) + ); + }); + }); describe('when one of the selections spans line 0', () => { it("doesn't move any lines, since line 0 can't move", () => { @@ -3260,58 +3266,58 @@ describe('TextEditor', () => { [[0, 2], [1, 9]], [[2, 2], [2, 9]], [[4, 2], [4, 9]] - ]) + ]); - editor.moveLineUp() + editor.moveLineUp(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 2], [1, 9]], [[2, 2], [2, 9]], [[4, 2], [4, 9]] - ]) - expect(buffer.isModified()).toBe(false) - }) - }) + ]); + expect(buffer.isModified()).toBe(false); + }); + }); describe('when one of the selections spans the last line, and it is empty', () => { it("doesn't move any lines, since the last line can't move", () => { - buffer.append('\n') + buffer.append('\n'); editor.setSelectedBufferRanges([ [[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]] - ]) + ]); - editor.moveLineUp() + editor.moveLineUp(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]] - ]) - }) - }) - }) - }) + ]); + }); + }); + }); + }); describe('.moveLineDown', () => { it('moves the line under the cursor down', () => { - editor.setCursorBufferPosition([0, 0]) - editor.moveLineDown() + editor.setCursorBufferPosition([0, 0]); + editor.moveLineDown(); expect(editor.getTextInBufferRange([[1, 0], [1, 31]])).toBe( 'var quicksort = function () {' - ) - expect(editor.indentationForBufferRow(0)).toBe(1) - expect(editor.indentationForBufferRow(1)).toBe(0) - }) + ); + expect(editor.indentationForBufferRow(0)).toBe(1); + expect(editor.indentationForBufferRow(1)).toBe(0); + }); it("updates the line's indentation when the editor.autoIndent setting is true", () => { - editor.update({ autoIndent: true }) - editor.setCursorBufferPosition([0, 0]) - editor.moveLineDown() - expect(editor.indentationForBufferRow(0)).toBe(1) - expect(editor.indentationForBufferRow(1)).toBe(2) - }) + editor.update({ autoIndent: true }); + editor.setCursorBufferPosition([0, 0]); + editor.moveLineDown(); + expect(editor.indentationForBufferRow(0)).toBe(1); + expect(editor.indentationForBufferRow(1)).toBe(2); + }); describe('when there is a single selection', () => { describe('when the selection spans a single line', () => { @@ -3319,285 +3325,285 @@ describe('TextEditor', () => { it('moves the line to the following row', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - editor.setSelectedBufferRange([[2, 2], [2, 9]]) - editor.moveLineDown() + editor.setSelectedBufferRange([[2, 2], [2, 9]]); + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [3, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [3, 9]]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) - })) + ); + })); describe('when the cursor is at the beginning of a fold', () => it('moves the line to the following row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRange([[4, 2], [4, 9]], { preserveFolds: true - }) + }); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.moveLineDown() + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[5, 2], [5, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[5, 2], [5, 9]]); expect(editor.lineTextForBufferRow(4)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) + ); - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - })) + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + })); describe('when the following row is a folded row', () => it('moves the line below the folded row and preserves the fold', () => { expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.setSelectedBufferRange([[3, 0], [3, 4]]) - editor.moveLineDown() + editor.setSelectedBufferRange([[3, 0], [3, 4]]); + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[7, 0], [7, 4]]) + expect(editor.getSelectedBufferRange()).toEqual([[7, 0], [7, 4]]); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() + ); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); expect(editor.lineTextForBufferRow(7)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - })) - }) + ); + })); + }); describe('when the selection spans multiple lines', () => { it('moves the lines spanned by the selection to the following row', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.setSelectedBufferRange([[2, 2], [3, 9]]) - editor.moveLineDown() + editor.setSelectedBufferRange([[2, 2], [3, 9]]); + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [4, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [4, 9]]); expect(editor.lineTextForBufferRow(2)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - }) - }) + ); + }); + }); describe('when the selection spans multiple lines, but ends at column 0', () => { it('does not move the last line of the selection', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.setSelectedBufferRange([[2, 2], [3, 0]]) - editor.moveLineDown() + editor.setSelectedBufferRange([[2, 2], [3, 0]]); + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [4, 0]]) + expect(editor.getSelectedBufferRange()).toEqual([[3, 2], [4, 0]]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) - }) - }) + ); + }); + }); describe("when the selection's end intersects a fold", () => { it('moves the lines to the following row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRange([[3, 2], [4, 9]], { preserveFolds: true - }) + }); - expect(editor.isFoldedAtBufferRow(3)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(3)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.moveLineDown() + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[4, 2], [5, 9]]) + expect(editor.getSelectedBufferRange()).toEqual([[4, 2], [5, 9]]); expect(editor.lineTextForBufferRow(3)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) + ); - expect(editor.isFoldedAtBufferRow(4)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - }) - }) + expect(editor.isFoldedAtBufferRow(4)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + }); + }); describe("when the selection's start intersects a fold", () => { it('moves the lines to the following row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRange([[4, 2], [8, 9]], { preserveFolds: true - }) + }); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); - editor.moveLineDown() + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[5, 2], [9, 9]]) - expect(editor.lineTextForBufferRow(4)).toBe(' };') + expect(editor.getSelectedBufferRange()).toEqual([[5, 2], [9, 9]]); + expect(editor.lineTextForBufferRow(4)).toBe(' };'); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(9)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); - expect(editor.isFoldedAtBufferRow(4)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(10)).toBeFalsy() - }) - }) + expect(editor.isFoldedAtBufferRow(4)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(10)).toBeFalsy(); + }); + }); describe('when the following row is a folded row', () => { it('moves the lines spanned by the selection to the following row, but preserves the folded code', () => { expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - editor.foldBufferRowRange(4, 7) - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + editor.foldBufferRowRange(4, 7); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); - editor.setSelectedBufferRange([[2, 0], [3, 2]]) - editor.moveLineDown() + editor.setSelectedBufferRange([[2, 0], [3, 2]]); + editor.moveLineDown(); - expect(editor.getSelectedBufferRange()).toEqual([[6, 0], [7, 2]]) + expect(editor.getSelectedBufferRange()).toEqual([[6, 0], [7, 2]]); expect(editor.lineTextForBufferRow(2)).toBe( ' while(items.length > 0) {' - ) - expect(editor.isFoldedAtBufferRow(1)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(2)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeFalsy() + ); + expect(editor.isFoldedAtBufferRow(1)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(2)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeFalsy(); expect(editor.lineTextForBufferRow(6)).toBe( ' if (items.length <= 1) return items;' - ) - }) - }) + ); + }); + }); describe('when the last line of selection does not end with a valid line ending', () => { it('appends line ending to last line and moves the lines spanned by the selection to the preceeding row', () => { - expect(editor.lineTextForBufferRow(9)).toBe(' };') - expect(editor.lineTextForBufferRow(10)).toBe('') + expect(editor.lineTextForBufferRow(9)).toBe(' };'); + expect(editor.lineTextForBufferRow(10)).toBe(''); expect(editor.lineTextForBufferRow(11)).toBe( ' return sort(Array.apply(this, arguments));' - ) - expect(editor.lineTextForBufferRow(12)).toBe('};') + ); + expect(editor.lineTextForBufferRow(12)).toBe('};'); - editor.setSelectedBufferRange([[10, 0], [12, 2]]) - editor.moveLineUp() + editor.setSelectedBufferRange([[10, 0], [12, 2]]); + editor.moveLineUp(); - expect(editor.getSelectedBufferRange()).toEqual([[9, 0], [11, 2]]) - expect(editor.lineTextForBufferRow(9)).toBe('') + expect(editor.getSelectedBufferRange()).toEqual([[9, 0], [11, 2]]); + expect(editor.lineTextForBufferRow(9)).toBe(''); expect(editor.lineTextForBufferRow(10)).toBe( ' return sort(Array.apply(this, arguments));' - ) - expect(editor.lineTextForBufferRow(11)).toBe('};') - expect(editor.lineTextForBufferRow(12)).toBe(' };') - }) - }) - }) + ); + expect(editor.lineTextForBufferRow(11)).toBe('};'); + expect(editor.lineTextForBufferRow(12)).toBe(' };'); + }); + }); + }); describe('when there are multiple selections', () => { describe('when all the selections span different lines', () => { @@ -3607,308 +3613,311 @@ describe('TextEditor', () => { [[1, 2], [1, 9]], [[3, 2], [3, 9]], [[5, 2], [5, 9]] - ]) - editor.moveLineDown() + ]); + editor.moveLineDown(); expect(editor.getSelectedBufferRanges()).toEqual([ [[6, 2], [6, 9]], [[4, 2], [4, 9]], [[2, 2], [2, 9]] - ]) + ]); expect(editor.lineTextForBufferRow(1)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(2)).toBe( ' var sort = function(items) {' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' current < pivot ? left.push(current) : right.push(current);' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' current = items.shift();' - ) - })) + ); + })); describe('when there are many folds', () => { beforeEach(async () => { editor = await atom.workspace.open('sample-with-many-folds.js', { autoIndent: false - }) - }) + }); + }); describe('and many selections intersects folded rows', () => it('moves and preserves all the folds', () => { - editor.foldBufferRowRange(2, 4) - editor.foldBufferRowRange(7, 9) + editor.foldBufferRowRange(2, 4); + editor.foldBufferRowRange(7, 9); editor.setSelectedBufferRanges( [[[2, 0], [2, 4]], [[6, 0], [10, 4]]], { preserveFolds: true } - ) + ); - editor.moveLineDown() + editor.moveLineDown(); - expect(editor.lineTextForBufferRow(2)).toEqual('6;') + expect(editor.lineTextForBufferRow(2)).toEqual('6;'); expect(editor.lineTextForBufferRow(3)).toEqual( 'function f3() {' - ) - expect(editor.lineTextForBufferRow(6)).toEqual('12;') - expect(editor.lineTextForBufferRow(7)).toEqual('7;') + ); + expect(editor.lineTextForBufferRow(6)).toEqual('12;'); + expect(editor.lineTextForBufferRow(7)).toEqual('7;'); expect(editor.lineTextForBufferRow(8)).toEqual( 'function f8() {' - ) - expect(editor.lineTextForBufferRow(11)).toEqual('11;') + ); + expect(editor.lineTextForBufferRow(11)).toEqual('11;'); - expect(editor.isFoldedAtBufferRow(2)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(10)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(11)).toBeFalsy() - })) - }) + expect(editor.isFoldedAtBufferRow(2)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(10)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(11)).toBeFalsy(); + })); + }); describe('when there is a fold below one of the selected row', () => it('moves all lines spanned by a selection to the following row, preserving the fold', () => { - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); editor.setSelectedBufferRanges([ [[1, 2], [1, 6]], [[3, 0], [3, 4]], [[8, 0], [8, 3]] - ]) - editor.moveLineDown() + ]); + editor.moveLineDown(); expect(editor.getSelectedBufferRanges()).toEqual([ [[9, 0], [9, 3]], [[7, 0], [7, 4]], [[2, 2], [2, 6]] - ]) + ]); expect(editor.lineTextForBufferRow(2)).toBe( ' var sort = function(items) {' - ) - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeFalsy() + ); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeFalsy(); expect(editor.lineTextForBufferRow(7)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(9)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) - })) + ); + })); describe('when there is a fold below a group of multiple selections without any lines with no selection in-between', () => it('moves all the lines below the fold, preserving the fold', () => { - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); editor.setSelectedBufferRanges([ [[2, 2], [2, 6]], [[3, 0], [3, 4]] - ]) - editor.moveLineDown() + ]); + editor.moveLineDown(); expect(editor.getSelectedBufferRanges()).toEqual([ [[7, 0], [7, 4]], [[6, 2], [6, 6]] - ]) + ]); expect(editor.lineTextForBufferRow(2)).toBe( ' while(items.length > 0) {' - ) - expect(editor.isFoldedAtBufferRow(2)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(3)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeFalsy() + ); + expect(editor.isFoldedAtBufferRow(2)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(3)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeFalsy(); expect(editor.lineTextForBufferRow(6)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(7)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - })) - }) + ); + })); + }); describe('when one selection intersects a fold', () => { it('moves the lines to the previous row without breaking the fold', () => { expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); - editor.foldBufferRowRange(4, 7) + editor.foldBufferRowRange(4, 7); editor.setSelectedBufferRanges( [[[2, 2], [2, 9]], [[4, 2], [4, 9]]], { preserveFolds: true } - ) + ); - expect(editor.isFoldedAtBufferRow(2)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(3)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(4)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() + expect(editor.isFoldedAtBufferRow(2)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(3)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(4)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); - editor.moveLineDown() + editor.moveLineDown(); expect(editor.getSelectedBufferRanges()).toEqual([ [[5, 2], [5, 9]], [[3, 2], [3, 9]] - ]) + ]); expect(editor.lineTextForBufferRow(2)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) - expect(editor.lineTextForBufferRow(9)).toBe(' };') + ); + expect(editor.lineTextForBufferRow(9)).toBe(' };'); - expect(editor.isFoldedAtBufferRow(2)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(3)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(4)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(5)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(6)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(7)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(8)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeFalsy() - }) - }) + expect(editor.isFoldedAtBufferRow(2)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(3)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(4)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(5)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(6)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(7)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(8)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeFalsy(); + }); + }); describe('when some of the selections span the same lines', () => { it('moves lines that contain multiple selections correctly', () => { editor.setSelectedBufferRanges([ [[3, 2], [3, 9]], [[3, 12], [3, 13]] - ]) - editor.moveLineDown() + ]); + editor.moveLineDown(); expect(editor.getSelectedBufferRanges()).toEqual([ [[4, 12], [4, 13]], [[4, 2], [4, 9]] - ]) + ]); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) - }) - }) + ); + }); + }); describe('when the selections are above a wrapped line', () => { beforeEach(() => { - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(80) + editor.setSoftWrapped(true); + editor.setEditorWidthInChars(80); editor.setText(dedent` 1 2 Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 3 4 - `) - }) + `); + }); it('moves the lines past the soft wrapped line', () => { - editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 0]]]) + editor.setSelectedBufferRanges([ + [[0, 0], [0, 0]], + [[1, 0], [1, 0]] + ]); - editor.moveLineDown() + editor.moveLineDown(); - expect(editor.lineTextForBufferRow(0)).not.toBe('2') - expect(editor.lineTextForBufferRow(1)).toBe('1') - expect(editor.lineTextForBufferRow(2)).toBe('2') - }) - }) - }) + expect(editor.lineTextForBufferRow(0)).not.toBe('2'); + expect(editor.lineTextForBufferRow(1)).toBe('1'); + expect(editor.lineTextForBufferRow(2)).toBe('2'); + }); + }); + }); describe('when the line is the last buffer row', () => { it("doesn't move it", () => { - editor.setText('abc\ndef') - editor.setCursorBufferPosition([1, 0]) - editor.moveLineDown() - expect(editor.getText()).toBe('abc\ndef') - }) - }) - }) + editor.setText('abc\ndef'); + editor.setCursorBufferPosition([1, 0]); + editor.moveLineDown(); + expect(editor.getText()).toBe('abc\ndef'); + }); + }); + }); describe('.insertText(text)', () => { describe('when there is a single selection', () => { - beforeEach(() => editor.setSelectedBufferRange([[1, 0], [1, 2]])) + beforeEach(() => editor.setSelectedBufferRange([[1, 0], [1, 2]])); it('replaces the selection with the given text', () => { - const range = editor.insertText('xxx') - expect(range).toEqual([[[1, 0], [1, 3]]]) - expect(buffer.lineForRow(1)).toBe('xxxvar sort = function(items) {') - }) - }) + const range = editor.insertText('xxx'); + expect(range).toEqual([[[1, 0], [1, 3]]]); + expect(buffer.lineForRow(1)).toBe('xxxvar sort = function(items) {'); + }); + }); describe('when there are multiple empty selections', () => { describe('when the cursors are on the same line', () => { it("inserts the given text at the location of each cursor and moves the cursors to the end of each cursor's inserted text", () => { - editor.setCursorScreenPosition([1, 2]) - editor.addCursorAtScreenPosition([1, 5]) + editor.setCursorScreenPosition([1, 2]); + editor.addCursorAtScreenPosition([1, 5]); - editor.insertText('xxx') + editor.insertText('xxx'); expect(buffer.lineForRow(1)).toBe( ' xxxvarxxx sort = function(items) {' - ) - const [cursor1, cursor2] = editor.getCursors() + ); + const [cursor1, cursor2] = editor.getCursors(); - expect(cursor1.getBufferPosition()).toEqual([1, 5]) - expect(cursor2.getBufferPosition()).toEqual([1, 11]) - }) - }) + expect(cursor1.getBufferPosition()).toEqual([1, 5]); + expect(cursor2.getBufferPosition()).toEqual([1, 11]); + }); + }); describe('when the cursors are on different lines', () => { it("inserts the given text at the location of each cursor and moves the cursors to the end of each cursor's inserted text", () => { - editor.setCursorScreenPosition([1, 2]) - editor.addCursorAtScreenPosition([2, 4]) + editor.setCursorScreenPosition([1, 2]); + editor.addCursorAtScreenPosition([2, 4]); - editor.insertText('xxx') + editor.insertText('xxx'); expect(buffer.lineForRow(1)).toBe( ' xxxvar sort = function(items) {' - ) + ); expect(buffer.lineForRow(2)).toBe( ' xxxif (items.length <= 1) return items;' - ) - const [cursor1, cursor2] = editor.getCursors() + ); + const [cursor1, cursor2] = editor.getCursors(); - expect(cursor1.getBufferPosition()).toEqual([1, 5]) - expect(cursor2.getBufferPosition()).toEqual([2, 7]) - }) - }) - }) + expect(cursor1.getBufferPosition()).toEqual([1, 5]); + expect(cursor2.getBufferPosition()).toEqual([2, 7]); + }); + }); + }); describe('when there are multiple non-empty selections', () => { describe('when the selections are on the same line', () => { @@ -3916,52 +3925,57 @@ describe('TextEditor', () => { editor.setSelectedBufferRanges([ [[0, 4], [0, 13]], [[0, 22], [0, 24]] - ]) - editor.insertText('x') + ]); + editor.insertText('x'); - const [cursor1, cursor2] = editor.getCursors() - const [selection1, selection2] = editor.getSelections() + const [cursor1, cursor2] = editor.getCursors(); + const [selection1, selection2] = editor.getSelections(); - expect(cursor1.getScreenPosition()).toEqual([0, 5]) - expect(cursor2.getScreenPosition()).toEqual([0, 15]) - expect(selection1.isEmpty()).toBeTruthy() - expect(selection2.isEmpty()).toBeTruthy() + expect(cursor1.getScreenPosition()).toEqual([0, 5]); + expect(cursor2.getScreenPosition()).toEqual([0, 15]); + expect(selection1.isEmpty()).toBeTruthy(); + expect(selection2.isEmpty()).toBeTruthy(); - expect(editor.lineTextForBufferRow(0)).toBe('var x = functix () {') - }) - }) + expect(editor.lineTextForBufferRow(0)).toBe('var x = functix () {'); + }); + }); describe('when the selections are on different lines', () => { it("replaces each selection with the given text, clears the selections, and places the cursor at the end of each selection's inserted text", () => { - editor.setSelectedBufferRanges([[[1, 0], [1, 2]], [[2, 0], [2, 4]]]) + editor.setSelectedBufferRanges([ + [[1, 0], [1, 2]], + [[2, 0], [2, 4]] + ]); - editor.insertText('xxx') + editor.insertText('xxx'); - expect(buffer.lineForRow(1)).toBe('xxxvar sort = function(items) {') + expect(buffer.lineForRow(1)).toBe( + 'xxxvar sort = function(items) {' + ); expect(buffer.lineForRow(2)).toBe( 'xxxif (items.length <= 1) return items;' - ) - const [selection1, selection2] = editor.getSelections() + ); + const [selection1, selection2] = editor.getSelections(); - expect(selection1.isEmpty()).toBeTruthy() - expect(selection1.cursor.getBufferPosition()).toEqual([1, 3]) - expect(selection2.isEmpty()).toBeTruthy() - expect(selection2.cursor.getBufferPosition()).toEqual([2, 3]) - }) - }) - }) + expect(selection1.isEmpty()).toBeTruthy(); + expect(selection1.cursor.getBufferPosition()).toEqual([1, 3]); + expect(selection2.isEmpty()).toBeTruthy(); + expect(selection2.cursor.getBufferPosition()).toEqual([2, 3]); + }); + }); + }); describe('when there is a selection that ends on a folded line', () => { it('destroys the selection', () => { - editor.foldBufferRowRange(2, 4) - editor.setSelectedBufferRange([[1, 0], [2, 0]]) - editor.insertText('holy cow') - expect(editor.isFoldedAtScreenRow(2)).toBeFalsy() - }) - }) + editor.foldBufferRowRange(2, 4); + editor.setSelectedBufferRange([[1, 0], [2, 0]]); + editor.insertText('holy cow'); + expect(editor.isFoldedAtScreenRow(2)).toBeFalsy(); + }); + }); describe('when there are ::onWillInsertText and ::onDidInsertText observers', () => { - beforeEach(() => editor.setSelectedBufferRange([[1, 0], [1, 2]])) + beforeEach(() => editor.setSelectedBufferRange([[1, 0], [1, 2]])); it('notifies the observers when inserting text', () => { const willInsertSpy = jasmine @@ -3970,7 +3984,7 @@ describe('TextEditor', () => { expect(buffer.lineForRow(1)).toBe( ' var sort = function(items) {' ) - ) + ); const didInsertSpy = jasmine .createSpy() @@ -3978,849 +3992,861 @@ describe('TextEditor', () => { expect(buffer.lineForRow(1)).toBe( 'xxxvar sort = function(items) {' ) - ) + ); - editor.onWillInsertText(willInsertSpy) - editor.onDidInsertText(didInsertSpy) + editor.onWillInsertText(willInsertSpy); + editor.onDidInsertText(didInsertSpy); - expect(editor.insertText('xxx')).toBeTruthy() - expect(buffer.lineForRow(1)).toBe('xxxvar sort = function(items) {') + expect(editor.insertText('xxx')).toBeTruthy(); + expect(buffer.lineForRow(1)).toBe('xxxvar sort = function(items) {'); - expect(willInsertSpy).toHaveBeenCalled() - expect(didInsertSpy).toHaveBeenCalled() + expect(willInsertSpy).toHaveBeenCalled(); + expect(didInsertSpy).toHaveBeenCalled(); - let options = willInsertSpy.mostRecentCall.args[0] - expect(options.text).toBe('xxx') - expect(options.cancel).toBeDefined() + let options = willInsertSpy.mostRecentCall.args[0]; + expect(options.text).toBe('xxx'); + expect(options.cancel).toBeDefined(); - options = didInsertSpy.mostRecentCall.args[0] - expect(options.text).toBe('xxx') - }) + options = didInsertSpy.mostRecentCall.args[0]; + expect(options.text).toBe('xxx'); + }); it('cancels text insertion when an ::onWillInsertText observer calls cancel on an event', () => { const willInsertSpy = jasmine .createSpy() - .andCallFake(({ cancel }) => cancel()) + .andCallFake(({ cancel }) => cancel()); - const didInsertSpy = jasmine.createSpy() + const didInsertSpy = jasmine.createSpy(); - editor.onWillInsertText(willInsertSpy) - editor.onDidInsertText(didInsertSpy) + editor.onWillInsertText(willInsertSpy); + editor.onDidInsertText(didInsertSpy); - expect(editor.insertText('xxx')).toBe(false) - expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {') + expect(editor.insertText('xxx')).toBe(false); + expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {'); - expect(willInsertSpy).toHaveBeenCalled() - expect(didInsertSpy).not.toHaveBeenCalled() - }) - }) + expect(willInsertSpy).toHaveBeenCalled(); + expect(didInsertSpy).not.toHaveBeenCalled(); + }); + }); describe("when the undo option is set to 'skip'", () => { it('groups the change with the previous change for purposes of undo and redo', () => { - editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 0]]]) - editor.insertText('x') - editor.insertText('y', { undo: 'skip' }) - editor.undo() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {') - }) - }) - }) + editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 0]]]); + editor.insertText('x'); + editor.insertText('y', { undo: 'skip' }); + editor.undo(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {'); + }); + }); + }); describe('.insertNewline()', () => { describe('when there is a single cursor', () => { describe('when the cursor is at the beginning of a line', () => { it('inserts an empty line before it', () => { - editor.setCursorScreenPosition({ row: 1, column: 0 }) + editor.setCursorScreenPosition({ row: 1, column: 0 }); - editor.insertNewline() + editor.insertNewline(); - expect(buffer.lineForRow(1)).toBe('') + expect(buffer.lineForRow(1)).toBe(''); expect(editor.getCursorScreenPosition()).toEqual({ row: 2, column: 0 - }) - }) - }) + }); + }); + }); describe('when the cursor is in the middle of a line', () => { it('splits the current line to form a new line', () => { - editor.setCursorScreenPosition({ row: 1, column: 6 }) - const originalLine = buffer.lineForRow(1) - const lineBelowOriginalLine = buffer.lineForRow(2) + editor.setCursorScreenPosition({ row: 1, column: 6 }); + const originalLine = buffer.lineForRow(1); + const lineBelowOriginalLine = buffer.lineForRow(2); - editor.insertNewline() + editor.insertNewline(); - expect(buffer.lineForRow(1)).toBe(originalLine.slice(0, 6)) - expect(buffer.lineForRow(2)).toBe(originalLine.slice(6)) - expect(buffer.lineForRow(3)).toBe(lineBelowOriginalLine) + expect(buffer.lineForRow(1)).toBe(originalLine.slice(0, 6)); + expect(buffer.lineForRow(2)).toBe(originalLine.slice(6)); + expect(buffer.lineForRow(3)).toBe(lineBelowOriginalLine); expect(editor.getCursorScreenPosition()).toEqual({ row: 2, column: 0 - }) - }) - }) + }); + }); + }); describe('when the cursor is on the end of a line', () => { it('inserts an empty line after it', () => { editor.setCursorScreenPosition({ row: 1, column: buffer.lineForRow(1).length - }) + }); - editor.insertNewline() + editor.insertNewline(); - expect(buffer.lineForRow(2)).toBe('') + expect(buffer.lineForRow(2)).toBe(''); expect(editor.getCursorScreenPosition()).toEqual({ row: 2, column: 0 - }) - }) - }) - }) + }); + }); + }); + }); describe('when there are multiple cursors', () => { describe('when the cursors are on the same line', () => { it('breaks the line at the cursor locations', () => { - editor.setCursorScreenPosition([3, 13]) - editor.addCursorAtScreenPosition([3, 38]) + editor.setCursorScreenPosition([3, 13]); + editor.addCursorAtScreenPosition([3, 38]); - editor.insertNewline() + editor.insertNewline(); - expect(editor.lineTextForBufferRow(3)).toBe(' var pivot') + expect(editor.lineTextForBufferRow(3)).toBe(' var pivot'); expect(editor.lineTextForBufferRow(4)).toBe( ' = items.shift(), current' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ', left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' while(items.length > 0) {' - ) + ); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([4, 0]) - expect(cursor2.getBufferPosition()).toEqual([5, 0]) - }) - }) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([4, 0]); + expect(cursor2.getBufferPosition()).toEqual([5, 0]); + }); + }); describe('when the cursors are on different lines', () => { it('inserts newlines at each cursor location', () => { - editor.setCursorScreenPosition([3, 0]) - editor.addCursorAtScreenPosition([6, 0]) + editor.setCursorScreenPosition([3, 0]); + editor.addCursorAtScreenPosition([6, 0]); - editor.insertText('\n') - expect(editor.lineTextForBufferRow(3)).toBe('') + editor.insertText('\n'); + expect(editor.lineTextForBufferRow(3)).toBe(''); expect(editor.lineTextForBufferRow(4)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' current = items.shift();' - ) - expect(editor.lineTextForBufferRow(7)).toBe('') + ); + expect(editor.lineTextForBufferRow(7)).toBe(''); expect(editor.lineTextForBufferRow(8)).toBe( ' current < pivot ? left.push(current) : right.push(current);' - ) - expect(editor.lineTextForBufferRow(9)).toBe(' }') + ); + expect(editor.lineTextForBufferRow(9)).toBe(' }'); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([4, 0]) - expect(cursor2.getBufferPosition()).toEqual([8, 0]) - }) - }) - }) - }) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([4, 0]); + expect(cursor2.getBufferPosition()).toEqual([8, 0]); + }); + }); + }); + }); describe('.insertNewlineBelow()', () => { describe('when the operation is undone', () => { it('places the cursor back at the previous location', () => { - editor.setCursorBufferPosition([0, 2]) - editor.insertNewlineBelow() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - editor.undo() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - }) - }) + editor.setCursorBufferPosition([0, 2]); + editor.insertNewlineBelow(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + editor.undo(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + }); + }); it("inserts a newline below the cursor's current line, autoindents it, and moves the cursor to the end of the line", () => { - editor.update({ autoIndent: true }) - editor.insertNewlineBelow() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe(' ') - expect(editor.getCursorBufferPosition()).toEqual([1, 2]) - }) - }) + editor.update({ autoIndent: true }); + editor.insertNewlineBelow(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe(' '); + expect(editor.getCursorBufferPosition()).toEqual([1, 2]); + }); + }); describe('.insertNewlineAbove()', () => { describe('when the cursor is on first line', () => { it('inserts a newline on the first line and moves the cursor to the first line', () => { - editor.setCursorBufferPosition([0]) - editor.insertNewlineAbove() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - expect(editor.lineTextForBufferRow(0)).toBe('') + editor.setCursorBufferPosition([0]); + editor.insertNewlineAbove(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + expect(editor.lineTextForBufferRow(0)).toBe(''); expect(editor.lineTextForBufferRow(1)).toBe( 'var quicksort = function () {' - ) - expect(editor.buffer.getLineCount()).toBe(14) - }) - }) + ); + expect(editor.buffer.getLineCount()).toBe(14); + }); + }); describe('when the cursor is not on the first line', () => { it('inserts a newline above the current line and moves the cursor to the inserted line', () => { - editor.setCursorBufferPosition([3, 4]) - editor.insertNewlineAbove() - expect(editor.getCursorBufferPosition()).toEqual([3, 0]) - expect(editor.lineTextForBufferRow(3)).toBe('') + editor.setCursorBufferPosition([3, 4]); + editor.insertNewlineAbove(); + expect(editor.getCursorBufferPosition()).toEqual([3, 0]); + expect(editor.lineTextForBufferRow(3)).toBe(''); expect(editor.lineTextForBufferRow(4)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - expect(editor.buffer.getLineCount()).toBe(14) + ); + expect(editor.buffer.getLineCount()).toBe(14); - editor.undo() - expect(editor.getCursorBufferPosition()).toEqual([3, 4]) - }) - }) + editor.undo(); + expect(editor.getCursorBufferPosition()).toEqual([3, 4]); + }); + }); it('indents the new line to the correct level when editor.autoIndent is true', () => { - editor.update({ autoIndent: true }) + editor.update({ autoIndent: true }); - editor.setText(' var test') - editor.setCursorBufferPosition([0, 2]) - editor.insertNewlineAbove() + editor.setText(' var test'); + editor.setCursorBufferPosition([0, 2]); + editor.insertNewlineAbove(); - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - expect(editor.lineTextForBufferRow(0)).toBe(' ') - expect(editor.lineTextForBufferRow(1)).toBe(' var test') + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + expect(editor.lineTextForBufferRow(0)).toBe(' '); + expect(editor.lineTextForBufferRow(1)).toBe(' var test'); - editor.setText('\n var test') - editor.setCursorBufferPosition([1, 2]) - editor.insertNewlineAbove() + editor.setText('\n var test'); + editor.setCursorBufferPosition([1, 2]); + editor.insertNewlineAbove(); - expect(editor.getCursorBufferPosition()).toEqual([1, 2]) - expect(editor.lineTextForBufferRow(0)).toBe('') - expect(editor.lineTextForBufferRow(1)).toBe(' ') - expect(editor.lineTextForBufferRow(2)).toBe(' var test') + expect(editor.getCursorBufferPosition()).toEqual([1, 2]); + expect(editor.lineTextForBufferRow(0)).toBe(''); + expect(editor.lineTextForBufferRow(1)).toBe(' '); + expect(editor.lineTextForBufferRow(2)).toBe(' var test'); - editor.setText('function() {\n}') - editor.setCursorBufferPosition([1, 1]) - editor.insertNewlineAbove() + editor.setText('function() {\n}'); + editor.setCursorBufferPosition([1, 1]); + editor.insertNewlineAbove(); - expect(editor.getCursorBufferPosition()).toEqual([1, 2]) - expect(editor.lineTextForBufferRow(0)).toBe('function() {') - expect(editor.lineTextForBufferRow(1)).toBe(' ') - expect(editor.lineTextForBufferRow(2)).toBe('}') - }) - }) + expect(editor.getCursorBufferPosition()).toEqual([1, 2]); + expect(editor.lineTextForBufferRow(0)).toBe('function() {'); + expect(editor.lineTextForBufferRow(1)).toBe(' '); + expect(editor.lineTextForBufferRow(2)).toBe('}'); + }); + }); describe('.insertNewLine()', () => { describe('when a new line is appended before a closing tag (e.g. by pressing enter before a selection)', () => { it('moves the line down and keeps the indentation level the same when editor.autoIndent is true', () => { - editor.update({ autoIndent: true }) - editor.setCursorBufferPosition([9, 2]) - editor.insertNewline() - expect(editor.lineTextForBufferRow(10)).toBe(' };') - }) - }) + editor.update({ autoIndent: true }); + editor.setCursorBufferPosition([9, 2]); + editor.insertNewline(); + expect(editor.lineTextForBufferRow(10)).toBe(' };'); + }); + }); describe('when a newline is appended with a trailing closing tag behind the cursor (e.g. by pressing enter in the middel of a line)', () => { it('indents the new line to the correct level when editor.autoIndent is true and using a curly-bracket language', () => { - editor.update({ autoIndent: true }) - atom.grammars.assignLanguageMode(editor, 'source.js') - editor.setText('var test = () => {\n return true;};') - editor.setCursorBufferPosition([1, 14]) - editor.insertNewline() - expect(editor.indentationForBufferRow(1)).toBe(1) - expect(editor.indentationForBufferRow(2)).toBe(0) - }) + editor.update({ autoIndent: true }); + atom.grammars.assignLanguageMode(editor, 'source.js'); + editor.setText('var test = () => {\n return true;};'); + editor.setCursorBufferPosition([1, 14]); + editor.insertNewline(); + expect(editor.indentationForBufferRow(1)).toBe(1); + expect(editor.indentationForBufferRow(2)).toBe(0); + }); it('indents the new line to the current level when editor.autoIndent is true and no increaseIndentPattern is specified', () => { - atom.grammars.assignLanguageMode(editor, null) - editor.update({ autoIndent: true }) - editor.setText(' if true') - editor.setCursorBufferPosition([0, 8]) - editor.insertNewline() - expect(editor.getGrammar()).toBe(atom.grammars.nullGrammar) - expect(editor.indentationForBufferRow(0)).toBe(1) - expect(editor.indentationForBufferRow(1)).toBe(1) - }) + atom.grammars.assignLanguageMode(editor, null); + editor.update({ autoIndent: true }); + editor.setText(' if true'); + editor.setCursorBufferPosition([0, 8]); + editor.insertNewline(); + expect(editor.getGrammar()).toBe(atom.grammars.nullGrammar); + expect(editor.indentationForBufferRow(0)).toBe(1); + expect(editor.indentationForBufferRow(1)).toBe(1); + }); it('indents the new line to the correct level when editor.autoIndent is true and using an off-side rule language', async () => { - await atom.packages.activatePackage('language-coffee-script') - editor.update({ autoIndent: true }) - atom.grammars.assignLanguageMode(editor, 'source.coffee') - editor.setText('if true\n return trueelse\n return false') - editor.setCursorBufferPosition([1, 13]) - editor.insertNewline() - expect(editor.indentationForBufferRow(1)).toBe(1) - expect(editor.indentationForBufferRow(2)).toBe(0) - expect(editor.indentationForBufferRow(3)).toBe(1) - }) - }) + await atom.packages.activatePackage('language-coffee-script'); + editor.update({ autoIndent: true }); + atom.grammars.assignLanguageMode(editor, 'source.coffee'); + editor.setText('if true\n return trueelse\n return false'); + editor.setCursorBufferPosition([1, 13]); + editor.insertNewline(); + expect(editor.indentationForBufferRow(1)).toBe(1); + expect(editor.indentationForBufferRow(2)).toBe(0); + expect(editor.indentationForBufferRow(3)).toBe(1); + }); + }); describe('when a newline is appended on a line that matches the decreaseNextIndentPattern', () => { it('indents the new line to the correct level when editor.autoIndent is true', async () => { - await atom.packages.activatePackage('language-go') - editor.update({ autoIndent: true }) - atom.grammars.assignLanguageMode(editor, 'source.go') - editor.setText('fmt.Printf("some%s",\n "thing")') // eslint-disable-line no-tabs - editor.setCursorBufferPosition([1, 10]) - editor.insertNewline() - expect(editor.indentationForBufferRow(1)).toBe(1) - expect(editor.indentationForBufferRow(2)).toBe(0) - }) - }) - }) + await atom.packages.activatePackage('language-go'); + editor.update({ autoIndent: true }); + atom.grammars.assignLanguageMode(editor, 'source.go'); + editor.setText('fmt.Printf("some%s",\n "thing")'); // eslint-disable-line no-tabs + editor.setCursorBufferPosition([1, 10]); + editor.insertNewline(); + expect(editor.indentationForBufferRow(1)).toBe(1); + expect(editor.indentationForBufferRow(2)).toBe(0); + }); + }); + }); describe('.backspace()', () => { describe('when there is a single cursor', () => { - let changeScreenRangeHandler = null + let changeScreenRangeHandler = null; beforeEach(() => { - const selection = editor.getLastSelection() + const selection = editor.getLastSelection(); changeScreenRangeHandler = jasmine.createSpy( 'changeScreenRangeHandler' - ) - selection.onDidChangeRange(changeScreenRangeHandler) - }) + ); + selection.onDidChangeRange(changeScreenRangeHandler); + }); describe('when the cursor is on the middle of the line', () => { it('removes the character before the cursor', () => { - editor.setCursorScreenPosition({ row: 1, column: 7 }) - expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {') + editor.setCursorScreenPosition({ row: 1, column: 7 }); + expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {'); - editor.backspace() + editor.backspace(); - const line = buffer.lineForRow(1) - expect(line).toBe(' var ort = function(items) {') + const line = buffer.lineForRow(1); + expect(line).toBe(' var ort = function(items) {'); expect(editor.getCursorScreenPosition()).toEqual({ row: 1, column: 6 - }) - expect(changeScreenRangeHandler).toHaveBeenCalled() - }) - }) + }); + expect(changeScreenRangeHandler).toHaveBeenCalled(); + }); + }); describe('when the cursor is at the beginning of a line', () => { it('joins it with the line above', () => { - const originalLine0 = buffer.lineForRow(0) - expect(originalLine0).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {') + const originalLine0 = buffer.lineForRow(0); + expect(originalLine0).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {'); - editor.setCursorScreenPosition({ row: 1, column: 0 }) - editor.backspace() + editor.setCursorScreenPosition({ row: 1, column: 0 }); + editor.backspace(); - const line0 = buffer.lineForRow(0) - const line1 = buffer.lineForRow(1) + const line0 = buffer.lineForRow(0); + const line1 = buffer.lineForRow(1); expect(line0).toBe( 'var quicksort = function () { var sort = function(items) {' - ) - expect(line1).toBe(' if (items.length <= 1) return items;') + ); + expect(line1).toBe(' if (items.length <= 1) return items;'); expect(editor.getCursorScreenPosition()).toEqual([ 0, originalLine0.length - ]) + ]); - expect(changeScreenRangeHandler).toHaveBeenCalled() - }) - }) + expect(changeScreenRangeHandler).toHaveBeenCalled(); + }); + }); describe('when the cursor is at the first column of the first line', () => { it("does nothing, but doesn't raise an error", () => { - editor.setCursorScreenPosition({ row: 0, column: 0 }) - editor.backspace() - }) - }) + editor.setCursorScreenPosition({ row: 0, column: 0 }); + editor.backspace(); + }); + }); describe('when the cursor is after a fold', () => { it('deletes the folded range', () => { - editor.foldBufferRange([[4, 7], [5, 8]]) - editor.setCursorBufferPosition([5, 8]) - editor.backspace() + editor.foldBufferRange([[4, 7], [5, 8]]); + editor.setCursorBufferPosition([5, 8]); + editor.backspace(); - expect(buffer.lineForRow(4)).toBe(' whirrent = items.shift();') - expect(editor.isFoldedAtBufferRow(4)).toBe(false) - }) - }) + expect(buffer.lineForRow(4)).toBe(' whirrent = items.shift();'); + expect(editor.isFoldedAtBufferRow(4)).toBe(false); + }); + }); describe('when the cursor is in the middle of a line below a fold', () => { it('backspaces as normal', () => { - editor.setCursorScreenPosition([4, 0]) - editor.foldCurrentRow() - editor.setCursorScreenPosition([5, 5]) - editor.backspace() + editor.setCursorScreenPosition([4, 0]); + editor.foldCurrentRow(); + editor.setCursorScreenPosition([5, 5]); + editor.backspace(); - expect(buffer.lineForRow(7)).toBe(' }') + expect(buffer.lineForRow(7)).toBe(' }'); expect(buffer.lineForRow(8)).toBe( ' eturn sort(left).concat(pivot).concat(sort(right));' - ) - }) - }) + ); + }); + }); describe('when the cursor is on a folded screen line', () => { it('deletes the contents of the fold before the cursor', () => { - editor.setCursorBufferPosition([3, 0]) - editor.foldCurrentRow() - editor.backspace() + editor.setCursorBufferPosition([3, 0]); + editor.foldCurrentRow(); + editor.backspace(); expect(buffer.lineForRow(1)).toBe( ' var sort = function(items) var pivot = items.shift(), current, left = [], right = [];' - ) - expect(editor.getCursorScreenPosition()).toEqual([1, 29]) - }) - }) - }) + ); + expect(editor.getCursorScreenPosition()).toEqual([1, 29]); + }); + }); + }); describe('when there are multiple cursors', () => { describe('when cursors are on the same line', () => { it('removes the characters preceding each cursor', () => { - editor.setCursorScreenPosition([3, 13]) - editor.addCursorAtScreenPosition([3, 38]) + editor.setCursorScreenPosition([3, 13]); + editor.addCursorAtScreenPosition([3, 38]); - editor.backspace() + editor.backspace(); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivo = items.shift(), curren, left = [], right = [];' - ) + ); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([3, 12]) - expect(cursor2.getBufferPosition()).toEqual([3, 36]) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([3, 12]); + expect(cursor2.getBufferPosition()).toEqual([3, 36]); - const [selection1, selection2] = editor.getSelections() - expect(selection1.isEmpty()).toBeTruthy() - expect(selection2.isEmpty()).toBeTruthy() - }) - }) + const [selection1, selection2] = editor.getSelections(); + expect(selection1.isEmpty()).toBeTruthy(); + expect(selection2.isEmpty()).toBeTruthy(); + }); + }); describe('when cursors are on different lines', () => { describe('when the cursors are in the middle of their lines', () => it('removes the characters preceding each cursor', () => { - editor.setCursorScreenPosition([3, 13]) - editor.addCursorAtScreenPosition([4, 10]) + editor.setCursorScreenPosition([3, 13]); + editor.addCursorAtScreenPosition([4, 10]); - editor.backspace() + editor.backspace(); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivo = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' whileitems.length > 0) {' - ) + ); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([3, 12]) - expect(cursor2.getBufferPosition()).toEqual([4, 9]) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([3, 12]); + expect(cursor2.getBufferPosition()).toEqual([4, 9]); - const [selection1, selection2] = editor.getSelections() - expect(selection1.isEmpty()).toBeTruthy() - expect(selection2.isEmpty()).toBeTruthy() - })) + const [selection1, selection2] = editor.getSelections(); + expect(selection1.isEmpty()).toBeTruthy(); + expect(selection2.isEmpty()).toBeTruthy(); + })); describe('when the cursors are on the first column of their lines', () => it('removes the newlines preceding each cursor', () => { - editor.setCursorScreenPosition([3, 0]) - editor.addCursorAtScreenPosition([6, 0]) + editor.setCursorScreenPosition([3, 0]); + editor.addCursorAtScreenPosition([6, 0]); - editor.backspace() + editor.backspace(); expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items; var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' current = items.shift(); current < pivot ? left.push(current) : right.push(current);' - ) - expect(editor.lineTextForBufferRow(5)).toBe(' }') + ); + expect(editor.lineTextForBufferRow(5)).toBe(' }'); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([2, 40]) - expect(cursor2.getBufferPosition()).toEqual([4, 30]) - })) - }) - }) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([2, 40]); + expect(cursor2.getBufferPosition()).toEqual([4, 30]); + })); + }); + }); describe('when there is a single selection', () => { it('deletes the selection, but not the character before it', () => { - editor.setSelectedBufferRange([[0, 5], [0, 9]]) - editor.backspace() - expect(editor.buffer.lineForRow(0)).toBe('var qsort = function () {') - }) + editor.setSelectedBufferRange([[0, 5], [0, 9]]); + editor.backspace(); + expect(editor.buffer.lineForRow(0)).toBe('var qsort = function () {'); + }); describe('when the selection ends on a folded line', () => { it('preserves the fold', () => { - editor.setSelectedBufferRange([[3, 0], [4, 0]]) - editor.foldBufferRow(4) - editor.backspace() + editor.setSelectedBufferRange([[3, 0], [4, 0]]); + editor.foldBufferRow(4); + editor.backspace(); - expect(buffer.lineForRow(3)).toBe(' while(items.length > 0) {') - expect(editor.isFoldedAtScreenRow(3)).toBe(true) - }) - }) - }) + expect(buffer.lineForRow(3)).toBe(' while(items.length > 0) {'); + expect(editor.isFoldedAtScreenRow(3)).toBe(true); + }); + }); + }); describe('when there are multiple selections', () => { it('removes all selected text', () => { editor.setSelectedBufferRanges([ [[0, 4], [0, 13]], [[0, 16], [0, 24]] - ]) - editor.backspace() - expect(editor.lineTextForBufferRow(0)).toBe('var = () {') - }) - }) - }) + ]); + editor.backspace(); + expect(editor.lineTextForBufferRow(0)).toBe('var = () {'); + }); + }); + }); describe('.deleteToPreviousWordBoundary()', () => { describe('when no text is selected', () => { it('deletes to the previous word boundary', () => { - editor.setCursorBufferPosition([0, 16]) - editor.addCursorAtBufferPosition([1, 21]) - const [cursor1, cursor2] = editor.getCursors() + editor.setCursorBufferPosition([0, 16]); + editor.addCursorAtBufferPosition([1, 21]); + const [cursor1, cursor2] = editor.getCursors(); - editor.deleteToPreviousWordBoundary() - expect(buffer.lineForRow(0)).toBe('var quicksort =function () {') - expect(buffer.lineForRow(1)).toBe(' var sort = (items) {') - expect(cursor1.getBufferPosition()).toEqual([0, 15]) - expect(cursor2.getBufferPosition()).toEqual([1, 13]) + editor.deleteToPreviousWordBoundary(); + expect(buffer.lineForRow(0)).toBe('var quicksort =function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = (items) {'); + expect(cursor1.getBufferPosition()).toEqual([0, 15]); + expect(cursor2.getBufferPosition()).toEqual([1, 13]); - editor.deleteToPreviousWordBoundary() - expect(buffer.lineForRow(0)).toBe('var quicksort function () {') - expect(buffer.lineForRow(1)).toBe(' var sort =(items) {') - expect(cursor1.getBufferPosition()).toEqual([0, 14]) - expect(cursor2.getBufferPosition()).toEqual([1, 12]) - }) - }) + editor.deleteToPreviousWordBoundary(); + expect(buffer.lineForRow(0)).toBe('var quicksort function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort =(items) {'); + expect(cursor1.getBufferPosition()).toEqual([0, 14]); + expect(cursor2.getBufferPosition()).toEqual([1, 12]); + }); + }); describe('when text is selected', () => { it('deletes only selected text', () => { - editor.setSelectedBufferRange([[1, 24], [1, 27]]) - editor.deleteToPreviousWordBoundary() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') - }) - }) - }) + editor.setSelectedBufferRange([[1, 24], [1, 27]]); + editor.deleteToPreviousWordBoundary(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); + }); + }); + }); describe('.deleteToNextWordBoundary()', () => { describe('when no text is selected', () => { it('deletes to the next word boundary', () => { - editor.setCursorBufferPosition([0, 15]) - editor.addCursorAtBufferPosition([1, 24]) - const [cursor1, cursor2] = editor.getCursors() + editor.setCursorBufferPosition([0, 15]); + editor.addCursorAtBufferPosition([1, 24]); + const [cursor1, cursor2] = editor.getCursors(); - editor.deleteToNextWordBoundary() - expect(buffer.lineForRow(0)).toBe('var quicksort =function () {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') - expect(cursor1.getBufferPosition()).toEqual([0, 15]) - expect(cursor2.getBufferPosition()).toEqual([1, 24]) + editor.deleteToNextWordBoundary(); + expect(buffer.lineForRow(0)).toBe('var quicksort =function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); + expect(cursor1.getBufferPosition()).toEqual([0, 15]); + expect(cursor2.getBufferPosition()).toEqual([1, 24]); - editor.deleteToNextWordBoundary() - expect(buffer.lineForRow(0)).toBe('var quicksort = () {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(it {') - expect(cursor1.getBufferPosition()).toEqual([0, 15]) - expect(cursor2.getBufferPosition()).toEqual([1, 24]) + editor.deleteToNextWordBoundary(); + expect(buffer.lineForRow(0)).toBe('var quicksort = () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it {'); + expect(cursor1.getBufferPosition()).toEqual([0, 15]); + expect(cursor2.getBufferPosition()).toEqual([1, 24]); - editor.deleteToNextWordBoundary() - expect(buffer.lineForRow(0)).toBe('var quicksort =() {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(it{') - expect(cursor1.getBufferPosition()).toEqual([0, 15]) - expect(cursor2.getBufferPosition()).toEqual([1, 24]) - }) - }) + editor.deleteToNextWordBoundary(); + expect(buffer.lineForRow(0)).toBe('var quicksort =() {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it{'); + expect(cursor1.getBufferPosition()).toEqual([0, 15]); + expect(cursor2.getBufferPosition()).toEqual([1, 24]); + }); + }); describe('when text is selected', () => { it('deletes only selected text', () => { - editor.setSelectedBufferRange([[1, 24], [1, 27]]) - editor.deleteToNextWordBoundary() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') - }) - }) - }) + editor.setSelectedBufferRange([[1, 24], [1, 27]]); + editor.deleteToNextWordBoundary(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); + }); + }); + }); describe('.deleteToBeginningOfWord()', () => { describe('when no text is selected', () => { it('deletes all text between the cursor and the beginning of the word', () => { - editor.setCursorBufferPosition([1, 24]) - editor.addCursorAtBufferPosition([3, 5]) - const [cursor1, cursor2] = editor.getCursors() + editor.setCursorBufferPosition([1, 24]); + editor.addCursorAtBufferPosition([3, 5]); + const [cursor1, cursor2] = editor.getCursors(); - editor.deleteToBeginningOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = function(ems) {') + editor.deleteToBeginningOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(ems) {'); expect(buffer.lineForRow(3)).toBe( ' ar pivot = items.shift(), current, left = [], right = [];' - ) - expect(cursor1.getBufferPosition()).toEqual([1, 22]) - expect(cursor2.getBufferPosition()).toEqual([3, 4]) + ); + expect(cursor1.getBufferPosition()).toEqual([1, 22]); + expect(cursor2.getBufferPosition()).toEqual([3, 4]); - editor.deleteToBeginningOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = functionems) {') + editor.deleteToBeginningOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = functionems) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return itemsar pivot = items.shift(), current, left = [], right = [];' - ) - expect(cursor1.getBufferPosition()).toEqual([1, 21]) - expect(cursor2.getBufferPosition()).toEqual([2, 39]) + ); + expect(cursor1.getBufferPosition()).toEqual([1, 21]); + expect(cursor2.getBufferPosition()).toEqual([2, 39]); - editor.deleteToBeginningOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = ems) {') + editor.deleteToBeginningOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = ems) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return ar pivot = items.shift(), current, left = [], right = [];' - ) - expect(cursor1.getBufferPosition()).toEqual([1, 13]) - expect(cursor2.getBufferPosition()).toEqual([2, 34]) + ); + expect(cursor1.getBufferPosition()).toEqual([1, 13]); + expect(cursor2.getBufferPosition()).toEqual([2, 34]); - editor.setText(' var sort') - editor.setCursorBufferPosition([0, 2]) - editor.deleteToBeginningOfWord() - expect(buffer.lineForRow(0)).toBe('var sort') - }) - }) + editor.setText(' var sort'); + editor.setCursorBufferPosition([0, 2]); + editor.deleteToBeginningOfWord(); + expect(buffer.lineForRow(0)).toBe('var sort'); + }); + }); describe('when text is selected', () => { it('deletes only selected text', () => { - editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]]) - editor.deleteToBeginningOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') + editor.setSelectedBufferRanges([ + [[1, 24], [1, 27]], + [[2, 0], [2, 4]] + ]); + editor.deleteToBeginningOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); expect(buffer.lineForRow(2)).toBe( 'if (items.length <= 1) return items;' - ) - }) - }) - }) + ); + }); + }); + }); describe('.deleteToEndOfLine()', () => { describe('when no text is selected', () => { it('deletes all text between the cursor and the end of the line', () => { - editor.setCursorBufferPosition([1, 24]) - editor.addCursorAtBufferPosition([2, 5]) - const [cursor1, cursor2] = editor.getCursors() + editor.setCursorBufferPosition([1, 24]); + editor.addCursorAtBufferPosition([2, 5]); + const [cursor1, cursor2] = editor.getCursors(); - editor.deleteToEndOfLine() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it') - expect(buffer.lineForRow(2)).toBe(' i') - expect(cursor1.getBufferPosition()).toEqual([1, 24]) - expect(cursor2.getBufferPosition()).toEqual([2, 5]) - }) + editor.deleteToEndOfLine(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it'); + expect(buffer.lineForRow(2)).toBe(' i'); + expect(cursor1.getBufferPosition()).toEqual([1, 24]); + expect(cursor2.getBufferPosition()).toEqual([2, 5]); + }); describe('when at the end of the line', () => { it('deletes the next newline', () => { - editor.setCursorBufferPosition([1, 30]) - editor.deleteToEndOfLine() + editor.setCursorBufferPosition([1, 30]); + editor.deleteToEndOfLine(); expect(buffer.lineForRow(1)).toBe( ' var sort = function(items) { if (items.length <= 1) return items;' - ) - }) - }) - }) + ); + }); + }); + }); describe('when text is selected', () => { it('deletes only the text in the selection', () => { - editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]]) - editor.deleteToEndOfLine() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') + editor.setSelectedBufferRanges([ + [[1, 24], [1, 27]], + [[2, 0], [2, 4]] + ]); + editor.deleteToEndOfLine(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); expect(buffer.lineForRow(2)).toBe( 'if (items.length <= 1) return items;' - ) - }) - }) - }) + ); + }); + }); + }); describe('.deleteToBeginningOfLine()', () => { describe('when no text is selected', () => { it('deletes all text between the cursor and the beginning of the line', () => { - editor.setCursorBufferPosition([1, 24]) - editor.addCursorAtBufferPosition([2, 5]) - const [cursor1, cursor2] = editor.getCursors() + editor.setCursorBufferPosition([1, 24]); + editor.addCursorAtBufferPosition([2, 5]); + const [cursor1, cursor2] = editor.getCursors(); - editor.deleteToBeginningOfLine() - expect(buffer.lineForRow(1)).toBe('ems) {') + editor.deleteToBeginningOfLine(); + expect(buffer.lineForRow(1)).toBe('ems) {'); expect(buffer.lineForRow(2)).toBe( 'f (items.length <= 1) return items;' - ) - expect(cursor1.getBufferPosition()).toEqual([1, 0]) - expect(cursor2.getBufferPosition()).toEqual([2, 0]) - }) + ); + expect(cursor1.getBufferPosition()).toEqual([1, 0]); + expect(cursor2.getBufferPosition()).toEqual([2, 0]); + }); describe('when at the beginning of the line', () => { it('deletes the newline', () => { - editor.setCursorBufferPosition([2]) - editor.deleteToBeginningOfLine() + editor.setCursorBufferPosition([2]); + editor.deleteToBeginningOfLine(); expect(buffer.lineForRow(1)).toBe( ' var sort = function(items) { if (items.length <= 1) return items;' - ) - }) - }) - }) + ); + }); + }); + }); describe('when text is selected', () => { it('still deletes all text to beginning of the line', () => { - editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]]) - editor.deleteToBeginningOfLine() - expect(buffer.lineForRow(1)).toBe('ems) {') + editor.setSelectedBufferRanges([ + [[1, 24], [1, 27]], + [[2, 0], [2, 4]] + ]); + editor.deleteToBeginningOfLine(); + expect(buffer.lineForRow(1)).toBe('ems) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return items;' - ) - }) - }) - }) + ); + }); + }); + }); describe('.delete()', () => { describe('when there is a single cursor', () => { describe('when the cursor is on the middle of a line', () => { it('deletes the character following the cursor', () => { - editor.setCursorScreenPosition([1, 6]) - editor.delete() - expect(buffer.lineForRow(1)).toBe(' var ort = function(items) {') - }) - }) + editor.setCursorScreenPosition([1, 6]); + editor.delete(); + expect(buffer.lineForRow(1)).toBe(' var ort = function(items) {'); + }); + }); describe('when the cursor is on the end of a line', () => { it('joins the line with the following line', () => { - editor.setCursorScreenPosition([1, buffer.lineForRow(1).length]) - editor.delete() + editor.setCursorScreenPosition([1, buffer.lineForRow(1).length]); + editor.delete(); expect(buffer.lineForRow(1)).toBe( ' var sort = function(items) { if (items.length <= 1) return items;' - ) - }) - }) + ); + }); + }); describe('when the cursor is on the last column of the last line', () => { it("does nothing, but doesn't raise an error", () => { - editor.setCursorScreenPosition([12, buffer.lineForRow(12).length]) - editor.delete() - expect(buffer.lineForRow(12)).toBe('};') - }) - }) + editor.setCursorScreenPosition([12, buffer.lineForRow(12).length]); + editor.delete(); + expect(buffer.lineForRow(12)).toBe('};'); + }); + }); describe('when the cursor is before a fold', () => { it('only deletes the lines inside the fold', () => { - editor.foldBufferRange([[3, 6], [4, 8]]) - editor.setCursorScreenPosition([3, 6]) - const cursorPositionBefore = editor.getCursorScreenPosition() + editor.foldBufferRange([[3, 6], [4, 8]]); + editor.setCursorScreenPosition([3, 6]); + const cursorPositionBefore = editor.getCursorScreenPosition(); - editor.delete() + editor.delete(); - expect(buffer.lineForRow(3)).toBe(' vae(items.length > 0) {') - expect(buffer.lineForRow(4)).toBe(' current = items.shift();') + expect(buffer.lineForRow(3)).toBe(' vae(items.length > 0) {'); + expect(buffer.lineForRow(4)).toBe(' current = items.shift();'); expect(editor.getCursorScreenPosition()).toEqual( cursorPositionBefore - ) - }) - }) + ); + }); + }); describe('when the cursor is in the middle a line above a fold', () => { it('deletes as normal', () => { - editor.foldBufferRow(4) - editor.setCursorScreenPosition([3, 4]) - editor.delete() + editor.foldBufferRow(4); + editor.setCursorScreenPosition([3, 4]); + editor.delete(); expect(buffer.lineForRow(3)).toBe( ' ar pivot = items.shift(), current, left = [], right = [];' - ) - expect(editor.isFoldedAtScreenRow(4)).toBe(true) - expect(editor.getCursorScreenPosition()).toEqual([3, 4]) - }) - }) + ); + expect(editor.isFoldedAtScreenRow(4)).toBe(true); + expect(editor.getCursorScreenPosition()).toEqual([3, 4]); + }); + }); describe('when the cursor is inside a fold', () => { it('removes the folded content after the cursor', () => { - editor.foldBufferRange([[2, 6], [6, 21]]) - editor.setCursorBufferPosition([4, 9]) + editor.foldBufferRange([[2, 6], [6, 21]]); + editor.setCursorBufferPosition([4, 9]); - editor.delete() + editor.delete(); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(buffer.lineForRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(buffer.lineForRow(4)).toBe( ' while ? left.push(current) : right.push(current);' - ) - expect(buffer.lineForRow(5)).toBe(' }') - expect(editor.getCursorBufferPosition()).toEqual([4, 9]) - }) - }) - }) + ); + expect(buffer.lineForRow(5)).toBe(' }'); + expect(editor.getCursorBufferPosition()).toEqual([4, 9]); + }); + }); + }); describe('when there are multiple cursors', () => { describe('when cursors are on the same line', () => { it('removes the characters following each cursor', () => { - editor.setCursorScreenPosition([3, 13]) - editor.addCursorAtScreenPosition([3, 38]) + editor.setCursorScreenPosition([3, 13]); + editor.addCursorAtScreenPosition([3, 38]); - editor.delete() + editor.delete(); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot= items.shift(), current left = [], right = [];' - ) + ); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([3, 13]) - expect(cursor2.getBufferPosition()).toEqual([3, 37]) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([3, 13]); + expect(cursor2.getBufferPosition()).toEqual([3, 37]); - const [selection1, selection2] = editor.getSelections() - expect(selection1.isEmpty()).toBeTruthy() - expect(selection2.isEmpty()).toBeTruthy() - }) - }) + const [selection1, selection2] = editor.getSelections(); + expect(selection1.isEmpty()).toBeTruthy(); + expect(selection2.isEmpty()).toBeTruthy(); + }); + }); describe('when cursors are on different lines', () => { describe('when the cursors are in the middle of the lines', () => it('removes the characters following each cursor', () => { - editor.setCursorScreenPosition([3, 13]) - editor.addCursorAtScreenPosition([4, 10]) + editor.setCursorScreenPosition([3, 13]); + editor.addCursorAtScreenPosition([4, 10]); - editor.delete() + editor.delete(); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot= items.shift(), current, left = [], right = [];' - ) + ); expect(editor.lineTextForBufferRow(4)).toBe( ' while(tems.length > 0) {' - ) + ); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([3, 13]) - expect(cursor2.getBufferPosition()).toEqual([4, 10]) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([3, 13]); + expect(cursor2.getBufferPosition()).toEqual([4, 10]); - const [selection1, selection2] = editor.getSelections() - expect(selection1.isEmpty()).toBeTruthy() - expect(selection2.isEmpty()).toBeTruthy() - })) + const [selection1, selection2] = editor.getSelections(); + expect(selection1.isEmpty()).toBeTruthy(); + expect(selection2.isEmpty()).toBeTruthy(); + })); describe('when the cursors are at the end of their lines', () => it('removes the newlines following each cursor', () => { - editor.setCursorScreenPosition([0, 29]) - editor.addCursorAtScreenPosition([1, 30]) + editor.setCursorScreenPosition([0, 29]); + editor.addCursorAtScreenPosition([1, 30]); - editor.delete() + editor.delete(); expect(editor.lineTextForBufferRow(0)).toBe( 'var quicksort = function () { var sort = function(items) { if (items.length <= 1) return items;' - ) + ); - const [cursor1, cursor2] = editor.getCursors() - expect(cursor1.getBufferPosition()).toEqual([0, 29]) - expect(cursor2.getBufferPosition()).toEqual([0, 59]) - })) - }) - }) + const [cursor1, cursor2] = editor.getCursors(); + expect(cursor1.getBufferPosition()).toEqual([0, 29]); + expect(cursor2.getBufferPosition()).toEqual([0, 59]); + })); + }); + }); describe('when there is a single selection', () => { it('deletes the selection, but not the character following it', () => { - editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]]) - editor.delete() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') + editor.setSelectedBufferRanges([ + [[1, 24], [1, 27]], + [[2, 0], [2, 4]] + ]); + editor.delete(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); expect(buffer.lineForRow(2)).toBe( 'if (items.length <= 1) return items;' - ) - expect(editor.getLastSelection().isEmpty()).toBeTruthy() - }) - }) + ); + expect(editor.getLastSelection().isEmpty()).toBeTruthy(); + }); + }); describe('when there are multiple selections', () => describe('when selections are on the same line', () => { @@ -4828,217 +4854,220 @@ describe('TextEditor', () => { editor.setSelectedBufferRanges([ [[0, 4], [0, 13]], [[0, 16], [0, 24]] - ]) - editor.delete() - expect(editor.lineTextForBufferRow(0)).toBe('var = () {') - }) - })) - }) + ]); + editor.delete(); + expect(editor.lineTextForBufferRow(0)).toBe('var = () {'); + }); + })); + }); describe('.deleteToEndOfWord()', () => { describe('when no text is selected', () => { it('deletes to the end of the word', () => { - editor.setCursorBufferPosition([1, 24]) - editor.addCursorAtBufferPosition([2, 5]) - const [cursor1, cursor2] = editor.getCursors() + editor.setCursorBufferPosition([1, 24]); + editor.addCursorAtBufferPosition([2, 5]); + const [cursor1, cursor2] = editor.getCursors(); - editor.deleteToEndOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') + editor.deleteToEndOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); expect(buffer.lineForRow(2)).toBe( ' i (items.length <= 1) return items;' - ) - expect(cursor1.getBufferPosition()).toEqual([1, 24]) - expect(cursor2.getBufferPosition()).toEqual([2, 5]) + ); + expect(cursor1.getBufferPosition()).toEqual([1, 24]); + expect(cursor2.getBufferPosition()).toEqual([2, 5]); - editor.deleteToEndOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it {') + editor.deleteToEndOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it {'); expect(buffer.lineForRow(2)).toBe( ' iitems.length <= 1) return items;' - ) - expect(cursor1.getBufferPosition()).toEqual([1, 24]) - expect(cursor2.getBufferPosition()).toEqual([2, 5]) - }) - }) + ); + expect(cursor1.getBufferPosition()).toEqual([1, 24]); + expect(cursor2.getBufferPosition()).toEqual([2, 5]); + }); + }); describe('when text is selected', () => { it('deletes only selected text', () => { - editor.setSelectedBufferRange([[1, 24], [1, 27]]) - editor.deleteToEndOfWord() - expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {') - }) - }) - }) + editor.setSelectedBufferRange([[1, 24], [1, 27]]); + editor.deleteToEndOfWord(); + expect(buffer.lineForRow(1)).toBe(' var sort = function(it) {'); + }); + }); + }); describe('.indent()', () => { describe('when the selection is empty', () => { describe('when autoIndent is disabled', () => { describe("if 'softTabs' is true (the default)", () => { it("inserts 'tabLength' spaces into the buffer", () => { - const tabRegex = new RegExp(`^[ ]{${editor.getTabLength()}}`) - expect(buffer.lineForRow(0)).not.toMatch(tabRegex) - editor.indent() - expect(buffer.lineForRow(0)).toMatch(tabRegex) - }) + const tabRegex = new RegExp(`^[ ]{${editor.getTabLength()}}`); + expect(buffer.lineForRow(0)).not.toMatch(tabRegex); + editor.indent(); + expect(buffer.lineForRow(0)).toMatch(tabRegex); + }); it('respects the tab stops when cursor is in the middle of a tab', () => { - editor.setTabLength(4) - buffer.insert([12, 2], '\n ') - editor.setCursorBufferPosition([13, 1]) - editor.indent() - expect(buffer.lineForRow(13)).toMatch(/^\s+$/) - expect(buffer.lineForRow(13).length).toBe(4) - expect(editor.getCursorBufferPosition()).toEqual([13, 4]) + editor.setTabLength(4); + buffer.insert([12, 2], '\n '); + editor.setCursorBufferPosition([13, 1]); + editor.indent(); + expect(buffer.lineForRow(13)).toMatch(/^\s+$/); + expect(buffer.lineForRow(13).length).toBe(4); + expect(editor.getCursorBufferPosition()).toEqual([13, 4]); - buffer.insert([13, 0], ' ') - editor.setCursorBufferPosition([13, 6]) - editor.indent() - expect(buffer.lineForRow(13).length).toBe(8) - }) - }) + buffer.insert([13, 0], ' '); + editor.setCursorBufferPosition([13, 6]); + editor.indent(); + expect(buffer.lineForRow(13).length).toBe(8); + }); + }); describe("if 'softTabs' is false", () => it('insert a \t into the buffer', () => { - editor.setSoftTabs(false) - expect(buffer.lineForRow(0)).not.toMatch(/^\t/) - editor.indent() - expect(buffer.lineForRow(0)).toMatch(/^\t/) - })) - }) + editor.setSoftTabs(false); + expect(buffer.lineForRow(0)).not.toMatch(/^\t/); + editor.indent(); + expect(buffer.lineForRow(0)).toMatch(/^\t/); + })); + }); describe('when autoIndent is enabled', () => { describe("when the cursor's column is less than the suggested level of indentation", () => { describe("when 'softTabs' is true (the default)", () => { it('moves the cursor to the end of the leading whitespace and inserts enough whitespace to bring the line to the suggested level of indentation', () => { - buffer.insert([5, 0], ' \n') - editor.setCursorBufferPosition([5, 0]) - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(5)).toMatch(/^\s+$/) - expect(buffer.lineForRow(5).length).toBe(6) - expect(editor.getCursorBufferPosition()).toEqual([5, 6]) - }) + buffer.insert([5, 0], ' \n'); + editor.setCursorBufferPosition([5, 0]); + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(5)).toMatch(/^\s+$/); + expect(buffer.lineForRow(5).length).toBe(6); + expect(editor.getCursorBufferPosition()).toEqual([5, 6]); + }); it('respects the tab stops when cursor is in the middle of a tab', () => { - editor.setTabLength(4) - buffer.insert([12, 2], '\n ') - editor.setCursorBufferPosition([13, 1]) - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(13)).toMatch(/^\s+$/) - expect(buffer.lineForRow(13).length).toBe(4) - expect(editor.getCursorBufferPosition()).toEqual([13, 4]) + editor.setTabLength(4); + buffer.insert([12, 2], '\n '); + editor.setCursorBufferPosition([13, 1]); + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(13)).toMatch(/^\s+$/); + expect(buffer.lineForRow(13).length).toBe(4); + expect(editor.getCursorBufferPosition()).toEqual([13, 4]); - buffer.insert([13, 0], ' ') - editor.setCursorBufferPosition([13, 6]) - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(13).length).toBe(8) - }) - }) + buffer.insert([13, 0], ' '); + editor.setCursorBufferPosition([13, 6]); + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(13).length).toBe(8); + }); + }); describe("when 'softTabs' is false", () => { it('moves the cursor to the end of the leading whitespace and inserts enough tabs to bring the line to the suggested level of indentation', () => { - convertToHardTabs(buffer) - editor.setSoftTabs(false) - buffer.insert([5, 0], '\t\n') - editor.setCursorBufferPosition([5, 0]) - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(5)).toMatch(/^\t\t\t$/) - expect(editor.getCursorBufferPosition()).toEqual([5, 3]) - }) + convertToHardTabs(buffer); + editor.setSoftTabs(false); + buffer.insert([5, 0], '\t\n'); + editor.setCursorBufferPosition([5, 0]); + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(5)).toMatch(/^\t\t\t$/); + expect(editor.getCursorBufferPosition()).toEqual([5, 3]); + }); describe('when the difference between the suggested level of indentation and the current level of indentation is greater than 0 but less than 1', () => it('inserts one tab', () => { - editor.setSoftTabs(false) - buffer.setText(' \ntest') - editor.setCursorBufferPosition([1, 0]) + editor.setSoftTabs(false); + buffer.setText(' \ntest'); + editor.setCursorBufferPosition([1, 0]); - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(1)).toBe('\ttest') - expect(editor.getCursorBufferPosition()).toEqual([1, 1]) - })) - }) - }) + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(1)).toBe('\ttest'); + expect(editor.getCursorBufferPosition()).toEqual([1, 1]); + })); + }); + }); describe("when the line's indent level is greater than the suggested level of indentation", () => { describe("when 'softTabs' is true (the default)", () => it("moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", () => { - buffer.insert([7, 0], ' \n') - editor.setCursorBufferPosition([7, 2]) - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(7)).toMatch(/^\s+$/) - expect(buffer.lineForRow(7).length).toBe(8) - expect(editor.getCursorBufferPosition()).toEqual([7, 8]) - })) + buffer.insert([7, 0], ' \n'); + editor.setCursorBufferPosition([7, 2]); + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(7)).toMatch(/^\s+$/); + expect(buffer.lineForRow(7).length).toBe(8); + expect(editor.getCursorBufferPosition()).toEqual([7, 8]); + })); describe("when 'softTabs' is false", () => it('moves the cursor to the end of the leading whitespace and inserts \t into the buffer', () => { - convertToHardTabs(buffer) - editor.setSoftTabs(false) - buffer.insert([7, 0], '\t\t\t\n') - editor.setCursorBufferPosition([7, 1]) - editor.indent({ autoIndent: true }) - expect(buffer.lineForRow(7)).toMatch(/^\t\t\t\t$/) - expect(editor.getCursorBufferPosition()).toEqual([7, 4]) - })) - }) - }) - }) + convertToHardTabs(buffer); + editor.setSoftTabs(false); + buffer.insert([7, 0], '\t\t\t\n'); + editor.setCursorBufferPosition([7, 1]); + editor.indent({ autoIndent: true }); + expect(buffer.lineForRow(7)).toMatch(/^\t\t\t\t$/); + expect(editor.getCursorBufferPosition()).toEqual([7, 4]); + })); + }); + }); + }); describe('when the selection is not empty', () => { it('indents the selected lines', () => { - editor.setSelectedBufferRange([[0, 0], [10, 0]]) - const selection = editor.getLastSelection() - spyOn(selection, 'indentSelectedRows') - editor.indent() - expect(selection.indentSelectedRows).toHaveBeenCalled() - }) - }) + editor.setSelectedBufferRange([[0, 0], [10, 0]]); + const selection = editor.getLastSelection(); + spyOn(selection, 'indentSelectedRows'); + editor.indent(); + expect(selection.indentSelectedRows).toHaveBeenCalled(); + }); + }); describe('if editor.softTabs is false', () => { it('inserts a tab character into the buffer', () => { - editor.setSoftTabs(false) - expect(buffer.lineForRow(0)).not.toMatch(/^\t/) - editor.indent() - expect(buffer.lineForRow(0)).toMatch(/^\t/) - expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + editor.setSoftTabs(false); + expect(buffer.lineForRow(0)).not.toMatch(/^\t/); + editor.indent(); + expect(buffer.lineForRow(0)).toMatch(/^\t/); + expect(editor.getCursorBufferPosition()).toEqual([0, 1]); expect(editor.getCursorScreenPosition()).toEqual([ 0, editor.getTabLength() - ]) + ]); - editor.indent() - expect(buffer.lineForRow(0)).toMatch(/^\t\t/) - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) + editor.indent(); + expect(buffer.lineForRow(0)).toMatch(/^\t\t/); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); expect(editor.getCursorScreenPosition()).toEqual([ 0, editor.getTabLength() * 2 - ]) - }) - }) - }) + ]); + }); + }); + }); describe('clipboard operations', () => { describe('.cutSelectedText()', () => { it('removes the selected text from the buffer and places it on the clipboard', () => { - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]]) - editor.cutSelectedText() - expect(buffer.lineForRow(0)).toBe('var = function () {') - expect(buffer.lineForRow(1)).toBe(' var = function(items) {') - expect(clipboard.readText()).toBe('quicksort\nsort') - }) + editor.setSelectedBufferRanges([ + [[0, 4], [0, 13]], + [[1, 6], [1, 10]] + ]); + editor.cutSelectedText(); + expect(buffer.lineForRow(0)).toBe('var = function () {'); + expect(buffer.lineForRow(1)).toBe(' var = function(items) {'); + expect(clipboard.readText()).toBe('quicksort\nsort'); + }); describe('when no text is selected', () => { beforeEach(() => editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[5, 0], [5, 0]]]) - ) + ); it('cuts the lines on which there are cursors', () => { - editor.cutSelectedText() - expect(buffer.getLineCount()).toBe(11) + editor.cutSelectedText(); + expect(buffer.getLineCount()).toBe(11); expect(buffer.lineForRow(1)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(buffer.lineForRow(4)).toBe( ' current < pivot ? left.push(current) : right.push(current);' - ) + ); expect(atom.clipboard.read()).toEqual( [ 'var quicksort = function () {', @@ -5046,9 +5075,9 @@ describe('TextEditor', () => { ' current = items.shift();', '' ].join('\n') - ) - }) - }) + ); + }); + }); describe('when many selections get added in shuffle order', () => { it('cuts them in order', () => { @@ -5056,92 +5085,92 @@ describe('TextEditor', () => { [[2, 8], [2, 13]], [[0, 4], [0, 13]], [[1, 6], [1, 10]] - ]) - editor.cutSelectedText() - expect(atom.clipboard.read()).toEqual(`quicksort\nsort\nitems`) - }) - }) - }) + ]); + editor.cutSelectedText(); + expect(atom.clipboard.read()).toEqual(`quicksort\nsort\nitems`); + }); + }); + }); describe('.cutToEndOfLine()', () => { describe('when soft wrap is on', () => { it('cuts up to the end of the line', () => { - editor.setSoftWrapped(true) - editor.setDefaultCharWidth(1) - editor.setEditorWidthInChars(25) - editor.setCursorScreenPosition([2, 6]) - editor.cutToEndOfLine() + editor.setSoftWrapped(true); + editor.setDefaultCharWidth(1); + editor.setEditorWidthInChars(25); + editor.setCursorScreenPosition([2, 6]); + editor.cutToEndOfLine(); expect(editor.lineTextForScreenRow(2)).toBe( ' var function(items) {' - ) - }) - }) + ); + }); + }); describe('when soft wrap is off', () => { describe('when nothing is selected', () => it('cuts up to the end of the line', () => { - editor.setCursorBufferPosition([2, 20]) - editor.addCursorAtBufferPosition([3, 20]) - editor.cutToEndOfLine() - expect(buffer.lineForRow(2)).toBe(' if (items.length') - expect(buffer.lineForRow(3)).toBe(' var pivot = item') + editor.setCursorBufferPosition([2, 20]); + editor.addCursorAtBufferPosition([3, 20]); + editor.cutToEndOfLine(); + expect(buffer.lineForRow(2)).toBe(' if (items.length'); + expect(buffer.lineForRow(3)).toBe(' var pivot = item'); expect(atom.clipboard.read()).toBe( ' <= 1) return items;\ns.shift(), current, left = [], right = [];' - ) - })) + ); + })); describe('when text is selected', () => it('only cuts the selected text, not to the end of the line', () => { editor.setSelectedBufferRanges([ [[2, 20], [2, 30]], [[3, 20], [3, 20]] - ]) - editor.cutToEndOfLine() + ]); + editor.cutToEndOfLine(); expect(buffer.lineForRow(2)).toBe( ' if (items.lengthurn items;' - ) - expect(buffer.lineForRow(3)).toBe(' var pivot = item') + ); + expect(buffer.lineForRow(3)).toBe(' var pivot = item'); expect(atom.clipboard.read()).toBe( ' <= 1) ret\ns.shift(), current, left = [], right = [];' - ) - })) - }) - }) + ); + })); + }); + }); describe('.cutToEndOfBufferLine()', () => { beforeEach(() => { - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(10) - }) + editor.setSoftWrapped(true); + editor.setEditorWidthInChars(10); + }); describe('when nothing is selected', () => { it('cuts up to the end of the buffer line', () => { - editor.setCursorBufferPosition([2, 20]) - editor.addCursorAtBufferPosition([3, 20]) - editor.cutToEndOfBufferLine() - expect(buffer.lineForRow(2)).toBe(' if (items.length') - expect(buffer.lineForRow(3)).toBe(' var pivot = item') + editor.setCursorBufferPosition([2, 20]); + editor.addCursorAtBufferPosition([3, 20]); + editor.cutToEndOfBufferLine(); + expect(buffer.lineForRow(2)).toBe(' if (items.length'); + expect(buffer.lineForRow(3)).toBe(' var pivot = item'); expect(atom.clipboard.read()).toBe( ' <= 1) return items;\ns.shift(), current, left = [], right = [];' - ) - }) - }) + ); + }); + }); describe('when text is selected', () => { it('only cuts the selected text, not to the end of the buffer line', () => { editor.setSelectedBufferRanges([ [[2, 20], [2, 30]], [[3, 20], [3, 20]] - ]) - editor.cutToEndOfBufferLine() - expect(buffer.lineForRow(2)).toBe(' if (items.lengthurn items;') - expect(buffer.lineForRow(3)).toBe(' var pivot = item') + ]); + editor.cutToEndOfBufferLine(); + expect(buffer.lineForRow(2)).toBe(' if (items.lengthurn items;'); + expect(buffer.lineForRow(3)).toBe(' var pivot = item'); expect(atom.clipboard.read()).toBe( ' <= 1) ret\ns.shift(), current, left = [], right = [];' - ) - }) - }) - }) + ); + }); + }); + }); describe('.copySelectedText()', () => { it('copies selected text onto the clipboard', () => { @@ -5149,37 +5178,40 @@ describe('TextEditor', () => { [[0, 4], [0, 13]], [[1, 6], [1, 10]], [[2, 8], [2, 13]] - ]) - editor.copySelectedText() + ]); + editor.copySelectedText(); - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {') + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return items;' - ) - expect(clipboard.readText()).toBe('quicksort\nsort\nitems') - expect(atom.clipboard.read()).toEqual('quicksort\nsort\nitems') - }) + ); + expect(clipboard.readText()).toBe('quicksort\nsort\nitems'); + expect(atom.clipboard.read()).toEqual('quicksort\nsort\nitems'); + }); describe('when no text is selected', () => { beforeEach(() => { - editor.setSelectedBufferRanges([[[1, 5], [1, 5]], [[5, 8], [5, 8]]]) - }) + editor.setSelectedBufferRanges([ + [[1, 5], [1, 5]], + [[5, 8], [5, 8]] + ]); + }); it('copies the lines on which there are cursors', () => { - editor.copySelectedText() + editor.copySelectedText(); expect(atom.clipboard.read()).toEqual( [ ' var sort = function(items) {\n', ' current = items.shift();\n' ].join('\n') - ) + ); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 5], [1, 5]], [[5, 8], [5, 8]] - ]) - }) - }) + ]); + }); + }); describe('when many selections get added in shuffle order', () => { it('copies them in order', () => { @@ -5187,12 +5219,12 @@ describe('TextEditor', () => { [[2, 8], [2, 13]], [[0, 4], [0, 13]], [[1, 6], [1, 10]] - ]) - editor.copySelectedText() - expect(atom.clipboard.read()).toEqual(`quicksort\nsort\nitems`) - }) - }) - }) + ]); + editor.copySelectedText(); + expect(atom.clipboard.read()).toEqual(`quicksort\nsort\nitems`); + }); + }); + }); describe('.copyOnlySelectedText()', () => { describe('when thee are multiple selections', () => { @@ -5201,133 +5233,136 @@ describe('TextEditor', () => { [[0, 4], [0, 13]], [[1, 6], [1, 10]], [[2, 8], [2, 13]] - ]) + ]); - editor.copyOnlySelectedText() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {') + editor.copyOnlySelectedText(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe(' var sort = function(items) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return items;' - ) - expect(clipboard.readText()).toBe('quicksort\nsort\nitems') - expect(atom.clipboard.read()).toEqual(`quicksort\nsort\nitems`) - }) - }) + ); + expect(clipboard.readText()).toBe('quicksort\nsort\nitems'); + expect(atom.clipboard.read()).toEqual(`quicksort\nsort\nitems`); + }); + }); describe('when no text is selected', () => { it('does not copy anything', () => { - editor.setCursorBufferPosition([1, 5]) - editor.copyOnlySelectedText() - expect(atom.clipboard.read()).toEqual('initial clipboard content') - }) - }) - }) + editor.setCursorBufferPosition([1, 5]); + editor.copyOnlySelectedText(); + expect(atom.clipboard.read()).toEqual('initial clipboard content'); + }); + }); + }); describe('.pasteText()', () => { it('pastes text into the buffer', () => { - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]]) - atom.clipboard.write('first') - editor.pasteText() + editor.setSelectedBufferRanges([ + [[0, 4], [0, 13]], + [[1, 6], [1, 10]] + ]); + atom.clipboard.write('first'); + editor.pasteText(); expect(editor.lineTextForBufferRow(0)).toBe( 'var first = function () {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( ' var first = function(items) {' - ) - }) + ); + }); it('notifies ::onWillInsertText observers', () => { - const insertedStrings = [] - editor.onWillInsertText(function ({ text, cancel }) { - insertedStrings.push(text) - cancel() - }) + const insertedStrings = []; + editor.onWillInsertText(function({ text, cancel }) { + insertedStrings.push(text); + cancel(); + }); - atom.clipboard.write('hello') - editor.pasteText() + atom.clipboard.write('hello'); + editor.pasteText(); - expect(insertedStrings).toEqual(['hello']) - }) + expect(insertedStrings).toEqual(['hello']); + }); it('notifies ::onDidInsertText observers', () => { - const insertedStrings = [] + const insertedStrings = []; editor.onDidInsertText(({ text, range }) => insertedStrings.push(text) - ) + ); - atom.clipboard.write('hello') - editor.pasteText() + atom.clipboard.write('hello'); + editor.pasteText(); - expect(insertedStrings).toEqual(['hello']) - }) + expect(insertedStrings).toEqual(['hello']); + }); describe('when `autoIndentOnPaste` is true', () => { - beforeEach(() => editor.update({ autoIndentOnPaste: true })) + beforeEach(() => editor.update({ autoIndentOnPaste: true })); describe('when pasting multiple lines before any non-whitespace characters', () => { it('auto-indents the lines spanned by the pasted text, based on the first pasted line', () => { atom.clipboard.write('a(x);\n b(x);\n c(x);\n', { indentBasis: 0 - }) - editor.setCursorBufferPosition([5, 0]) - editor.pasteText() + }); + editor.setCursorBufferPosition([5, 0]); + editor.pasteText(); // Adjust the indentation of the pasted lines while preserving // their indentation relative to each other. Also preserve the // indentation of the following line. - expect(editor.lineTextForBufferRow(5)).toBe(' a(x);') - expect(editor.lineTextForBufferRow(6)).toBe(' b(x);') - expect(editor.lineTextForBufferRow(7)).toBe(' c(x);') + expect(editor.lineTextForBufferRow(5)).toBe(' a(x);'); + expect(editor.lineTextForBufferRow(6)).toBe(' b(x);'); + expect(editor.lineTextForBufferRow(7)).toBe(' c(x);'); expect(editor.lineTextForBufferRow(8)).toBe( ' current = items.shift();' - ) - }) + ); + }); it('auto-indents lines with a mix of hard tabs and spaces without removing spaces', () => { - editor.setSoftTabs(false) - expect(editor.indentationForBufferRow(5)).toBe(3) + editor.setSoftTabs(false); + expect(editor.indentationForBufferRow(5)).toBe(3); atom.clipboard.write('/**\n\t * testing\n\t * indent\n\t **/\n', { indentBasis: 1 - }) - editor.setCursorBufferPosition([5, 0]) - editor.pasteText() + }); + editor.setCursorBufferPosition([5, 0]); + editor.pasteText(); // Do not lose the alignment spaces - expect(editor.lineTextForBufferRow(5)).toBe('\t\t\t/**') - expect(editor.lineTextForBufferRow(6)).toBe('\t\t\t * testing') - expect(editor.lineTextForBufferRow(7)).toBe('\t\t\t * indent') - expect(editor.lineTextForBufferRow(8)).toBe('\t\t\t **/') - }) - }) + expect(editor.lineTextForBufferRow(5)).toBe('\t\t\t/**'); + expect(editor.lineTextForBufferRow(6)).toBe('\t\t\t * testing'); + expect(editor.lineTextForBufferRow(7)).toBe('\t\t\t * indent'); + expect(editor.lineTextForBufferRow(8)).toBe('\t\t\t **/'); + }); + }); describe('when pasting line(s) above a line that matches the decreaseIndentPattern', () => it('auto-indents based on the pasted line(s) only', () => { atom.clipboard.write('a(x);\n b(x);\n c(x);\n', { indentBasis: 0 - }) - editor.setCursorBufferPosition([7, 0]) - editor.pasteText() + }); + editor.setCursorBufferPosition([7, 0]); + editor.pasteText(); - expect(editor.lineTextForBufferRow(7)).toBe(' a(x);') - expect(editor.lineTextForBufferRow(8)).toBe(' b(x);') - expect(editor.lineTextForBufferRow(9)).toBe(' c(x);') - expect(editor.lineTextForBufferRow(10)).toBe(' }') - })) + expect(editor.lineTextForBufferRow(7)).toBe(' a(x);'); + expect(editor.lineTextForBufferRow(8)).toBe(' b(x);'); + expect(editor.lineTextForBufferRow(9)).toBe(' c(x);'); + expect(editor.lineTextForBufferRow(10)).toBe(' }'); + })); describe('when pasting a line of text without line ending', () => it('does not auto-indent the text', () => { - atom.clipboard.write('a(x);', { indentBasis: 0 }) - editor.setCursorBufferPosition([5, 0]) - editor.pasteText() + atom.clipboard.write('a(x);', { indentBasis: 0 }); + editor.setCursorBufferPosition([5, 0]); + editor.pasteText(); expect(editor.lineTextForBufferRow(5)).toBe( 'a(x); current = items.shift();' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' current < pivot ? left.push(current) : right.push(current);' - ) - })) + ); + })); describe('when pasting on a line after non-whitespace characters', () => it('does not auto-indent the affected line', () => { @@ -5336,580 +5371,586 @@ describe('TextEditor', () => { if (x) { y(); }\ - `) + `); - atom.clipboard.write(' z();\n h();') - editor.setCursorBufferPosition([1, Infinity]) + atom.clipboard.write(' z();\n h();'); + editor.setCursorBufferPosition([1, Infinity]); // The indentation of the non-standard line is unchanged. - editor.pasteText() - expect(editor.lineTextForBufferRow(1)).toBe(' y(); z();') - expect(editor.lineTextForBufferRow(2)).toBe(' h();') - })) - }) + editor.pasteText(); + expect(editor.lineTextForBufferRow(1)).toBe(' y(); z();'); + expect(editor.lineTextForBufferRow(2)).toBe(' h();'); + })); + }); describe('when `autoIndentOnPaste` is false', () => { - beforeEach(() => editor.update({ autoIndentOnPaste: false })) + beforeEach(() => editor.update({ autoIndentOnPaste: false })); describe('when the cursor is indented further than the original copied text', () => it('increases the indentation of the copied lines to match', () => { - editor.setSelectedBufferRange([[1, 2], [3, 0]]) - editor.copySelectedText() + editor.setSelectedBufferRange([[1, 2], [3, 0]]); + editor.copySelectedText(); - editor.setCursorBufferPosition([5, 6]) - editor.pasteText() + editor.setCursorBufferPosition([5, 6]); + editor.pasteText(); expect(editor.lineTextForBufferRow(5)).toBe( ' var sort = function(items) {' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' if (items.length <= 1) return items;' - ) - })) + ); + })); describe('when the cursor is indented less far than the original copied text', () => it('decreases the indentation of the copied lines to match', () => { - editor.setSelectedBufferRange([[6, 6], [8, 0]]) - editor.copySelectedText() + editor.setSelectedBufferRange([[6, 6], [8, 0]]); + editor.copySelectedText(); - editor.setCursorBufferPosition([1, 2]) - editor.pasteText() + editor.setCursorBufferPosition([1, 2]); + editor.pasteText(); expect(editor.lineTextForBufferRow(1)).toBe( ' current < pivot ? left.push(current) : right.push(current);' - ) - expect(editor.lineTextForBufferRow(2)).toBe('}') - })) + ); + expect(editor.lineTextForBufferRow(2)).toBe('}'); + })); describe('when the first copied line has leading whitespace', () => it("preserves the line's leading whitespace", () => { - editor.setSelectedBufferRange([[4, 0], [6, 0]]) - editor.copySelectedText() + editor.setSelectedBufferRange([[4, 0], [6, 0]]); + editor.copySelectedText(); - editor.setCursorBufferPosition([0, 0]) - editor.pasteText() + editor.setCursorBufferPosition([0, 0]); + editor.pasteText(); expect(editor.lineTextForBufferRow(0)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( ' current = items.shift();' - ) - })) - }) + ); + })); + }); describe('when the clipboard has many selections', () => { beforeEach(() => { - editor.update({ autoIndentOnPaste: false }) + editor.update({ autoIndentOnPaste: false }); editor.setSelectedBufferRanges([ [[0, 4], [0, 13]], [[1, 6], [1, 10]] - ]) - editor.copySelectedText() - }) + ]); + editor.copySelectedText(); + }); it('pastes each selection in order separately into the buffer', () => { editor.setSelectedBufferRanges([ [[1, 6], [1, 10]], [[0, 4], [0, 13]] - ]) + ]); - editor.moveRight() - editor.insertText('_') - editor.pasteText() + editor.moveRight(); + editor.insertText('_'); + editor.pasteText(); expect(editor.lineTextForBufferRow(0)).toBe( 'var quicksort_quicksort = function () {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( ' var sort_sort = function(items) {' - ) - }) + ); + }); describe('and the selections count does not match', () => { beforeEach(() => editor.setSelectedBufferRanges([[[0, 4], [0, 13]]]) - ) + ); it('pastes the whole text into the buffer', () => { - editor.pasteText() - expect(editor.lineTextForBufferRow(0)).toBe('var quicksort') + editor.pasteText(); + expect(editor.lineTextForBufferRow(0)).toBe('var quicksort'); expect(editor.lineTextForBufferRow(1)).toBe( 'sort = function () {' - ) - }) - }) - }) + ); + }); + }); + }); describe('when a full line was cut', () => { beforeEach(() => { - editor.setCursorBufferPosition([2, 13]) - editor.cutSelectedText() - editor.setCursorBufferPosition([2, 13]) - }) + editor.setCursorBufferPosition([2, 13]); + editor.cutSelectedText(); + editor.setCursorBufferPosition([2, 13]); + }); it("pastes the line above the cursor and retains the cursor's column", () => { - editor.pasteText() + editor.pasteText(); expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - expect(editor.getCursorBufferPosition()).toEqual([3, 13]) - }) - }) + ); + expect(editor.getCursorBufferPosition()).toEqual([3, 13]); + }); + }); describe('when a full line was copied', () => { beforeEach(() => { - editor.setCursorBufferPosition([2, 13]) - editor.copySelectedText() - }) + editor.setCursorBufferPosition([2, 13]); + editor.copySelectedText(); + }); describe('when there is a selection', () => it('overwrites the selection as with any copied text', () => { - editor.setSelectedBufferRange([[1, 2], [1, Infinity]]) - editor.pasteText() + editor.setSelectedBufferRange([[1, 2], [1, Infinity]]); + editor.pasteText(); expect(editor.lineTextForBufferRow(1)).toBe( ' if (items.length <= 1) return items;' - ) - expect(editor.lineTextForBufferRow(2)).toBe('') + ); + expect(editor.lineTextForBufferRow(2)).toBe(''); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) - expect(editor.getCursorBufferPosition()).toEqual([2, 0]) - })) + ); + expect(editor.getCursorBufferPosition()).toEqual([2, 0]); + })); describe('when there is no selection', () => it("pastes the line above the cursor and retains the cursor's column", () => { - editor.pasteText() + editor.pasteText(); expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(editor.lineTextForBufferRow(3)).toBe( ' if (items.length <= 1) return items;' - ) - expect(editor.getCursorBufferPosition()).toEqual([3, 13]) - })) - }) + ); + expect(editor.getCursorBufferPosition()).toEqual([3, 13]); + })); + }); it('respects options that preserve the formatting of the pasted text', () => { - editor.update({ autoIndentOnPaste: true }) - atom.clipboard.write('a(x);\n b(x);\r\nc(x);\n', { indentBasis: 0 }) - editor.setCursorBufferPosition([5, 0]) - editor.insertText(' ') + editor.update({ autoIndentOnPaste: true }); + atom.clipboard.write('a(x);\n b(x);\r\nc(x);\n', { indentBasis: 0 }); + editor.setCursorBufferPosition([5, 0]); + editor.insertText(' '); editor.pasteText({ autoIndent: false, preserveTrailingLineIndentation: true, normalizeLineEndings: false - }) + }); - expect(editor.lineTextForBufferRow(5)).toBe(' a(x);') - expect(editor.lineTextForBufferRow(6)).toBe(' b(x);') - expect(editor.buffer.lineEndingForRow(6)).toBe('\r\n') - expect(editor.lineTextForBufferRow(7)).toBe('c(x);') + expect(editor.lineTextForBufferRow(5)).toBe(' a(x);'); + expect(editor.lineTextForBufferRow(6)).toBe(' b(x);'); + expect(editor.buffer.lineEndingForRow(6)).toBe('\r\n'); + expect(editor.lineTextForBufferRow(7)).toBe('c(x);'); expect(editor.lineTextForBufferRow(8)).toBe( ' current = items.shift();' - ) - }) - }) - }) + ); + }); + }); + }); describe('.indentSelectedRows()', () => { describe('when nothing is selected', () => { describe('when softTabs is enabled', () => { it('indents line and retains selection', () => { - editor.setSelectedBufferRange([[0, 3], [0, 3]]) - editor.indentSelectedRows() - expect(buffer.lineForRow(0)).toBe(' var quicksort = function () {') + editor.setSelectedBufferRange([[0, 3], [0, 3]]); + editor.indentSelectedRows(); + expect(buffer.lineForRow(0)).toBe( + ' var quicksort = function () {' + ); expect(editor.getSelectedBufferRange()).toEqual([ [0, 3 + editor.getTabLength()], [0, 3 + editor.getTabLength()] - ]) - }) - }) + ]); + }); + }); describe('when softTabs is disabled', () => { it('indents line and retains selection', () => { - convertToHardTabs(buffer) - editor.setSoftTabs(false) - editor.setSelectedBufferRange([[0, 3], [0, 3]]) - editor.indentSelectedRows() - expect(buffer.lineForRow(0)).toBe('\tvar quicksort = function () {') + convertToHardTabs(buffer); + editor.setSoftTabs(false); + editor.setSelectedBufferRange([[0, 3], [0, 3]]); + editor.indentSelectedRows(); + expect(buffer.lineForRow(0)).toBe( + '\tvar quicksort = function () {' + ); expect(editor.getSelectedBufferRange()).toEqual([ [0, 3 + 1], [0, 3 + 1] - ]) - }) - }) - }) + ]); + }); + }); + }); describe('when one line is selected', () => { describe('when softTabs is enabled', () => { it('indents line and retains selection', () => { - editor.setSelectedBufferRange([[0, 4], [0, 14]]) - editor.indentSelectedRows() + editor.setSelectedBufferRange([[0, 4], [0, 14]]); + editor.indentSelectedRows(); expect(buffer.lineForRow(0)).toBe( `${editor.getTabText()}var quicksort = function () {` - ) + ); expect(editor.getSelectedBufferRange()).toEqual([ [0, 4 + editor.getTabLength()], [0, 14 + editor.getTabLength()] - ]) - }) - }) + ]); + }); + }); describe('when softTabs is disabled', () => { it('indents line and retains selection', () => { - convertToHardTabs(buffer) - editor.setSoftTabs(false) - editor.setSelectedBufferRange([[0, 4], [0, 14]]) - editor.indentSelectedRows() - expect(buffer.lineForRow(0)).toBe('\tvar quicksort = function () {') + convertToHardTabs(buffer); + editor.setSoftTabs(false); + editor.setSelectedBufferRange([[0, 4], [0, 14]]); + editor.indentSelectedRows(); + expect(buffer.lineForRow(0)).toBe( + '\tvar quicksort = function () {' + ); expect(editor.getSelectedBufferRange()).toEqual([ [0, 4 + 1], [0, 14 + 1] - ]) - }) - }) - }) + ]); + }); + }); + }); describe('when multiple lines are selected', () => { describe('when softTabs is enabled', () => { it('indents selected lines (that are not empty) and retains selection', () => { - editor.setSelectedBufferRange([[9, 1], [11, 15]]) - editor.indentSelectedRows() - expect(buffer.lineForRow(9)).toBe(' };') - expect(buffer.lineForRow(10)).toBe('') + editor.setSelectedBufferRange([[9, 1], [11, 15]]); + editor.indentSelectedRows(); + expect(buffer.lineForRow(9)).toBe(' };'); + expect(buffer.lineForRow(10)).toBe(''); expect(buffer.lineForRow(11)).toBe( ' return sort(Array.apply(this, arguments));' - ) + ); expect(editor.getSelectedBufferRange()).toEqual([ [9, 1 + editor.getTabLength()], [11, 15 + editor.getTabLength()] - ]) - }) + ]); + }); it('does not indent the last row if the selection ends at column 0', () => { - editor.setSelectedBufferRange([[9, 1], [11, 0]]) - editor.indentSelectedRows() - expect(buffer.lineForRow(9)).toBe(' };') - expect(buffer.lineForRow(10)).toBe('') + editor.setSelectedBufferRange([[9, 1], [11, 0]]); + editor.indentSelectedRows(); + expect(buffer.lineForRow(9)).toBe(' };'); + expect(buffer.lineForRow(10)).toBe(''); expect(buffer.lineForRow(11)).toBe( ' return sort(Array.apply(this, arguments));' - ) + ); expect(editor.getSelectedBufferRange()).toEqual([ [9, 1 + editor.getTabLength()], [11, 0] - ]) - }) - }) + ]); + }); + }); describe('when softTabs is disabled', () => { it('indents selected lines (that are not empty) and retains selection', () => { - convertToHardTabs(buffer) - editor.setSoftTabs(false) - editor.setSelectedBufferRange([[9, 1], [11, 15]]) - editor.indentSelectedRows() - expect(buffer.lineForRow(9)).toBe('\t\t};') - expect(buffer.lineForRow(10)).toBe('') + convertToHardTabs(buffer); + editor.setSoftTabs(false); + editor.setSelectedBufferRange([[9, 1], [11, 15]]); + editor.indentSelectedRows(); + expect(buffer.lineForRow(9)).toBe('\t\t};'); + expect(buffer.lineForRow(10)).toBe(''); expect(buffer.lineForRow(11)).toBe( '\t\treturn sort(Array.apply(this, arguments));' - ) + ); expect(editor.getSelectedBufferRange()).toEqual([ [9, 1 + 1], [11, 15 + 1] - ]) - }) - }) - }) - }) + ]); + }); + }); + }); + }); describe('.outdentSelectedRows()', () => { describe('when nothing is selected', () => { it('outdents line and retains selection', () => { - editor.setSelectedBufferRange([[1, 3], [1, 3]]) - editor.outdentSelectedRows() - expect(buffer.lineForRow(1)).toBe('var sort = function(items) {') + editor.setSelectedBufferRange([[1, 3], [1, 3]]); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(1)).toBe('var sort = function(items) {'); expect(editor.getSelectedBufferRange()).toEqual([ [1, 3 - editor.getTabLength()], [1, 3 - editor.getTabLength()] - ]) - }) + ]); + }); it('outdents when indent is less than a tab length', () => { - editor.insertText(' ') - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - }) + editor.insertText(' '); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + }); it('outdents a single hard tab when indent is multiple hard tabs and and the session is using soft tabs', () => { - editor.insertText('\t\t') - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe('\tvar quicksort = function () {') - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - }) + editor.insertText('\t\t'); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe('\tvar quicksort = function () {'); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + }); it('outdents when a mix of hard tabs and soft tabs are used', () => { - editor.insertText('\t ') - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe(' var quicksort = function () {') - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe(' var quicksort = function () {') - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - }) + editor.insertText('\t '); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe(' var quicksort = function () {'); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe(' var quicksort = function () {'); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + }); it('outdents only up to the first non-space non-tab character', () => { - editor.insertText(' \tfoo\t ') - editor.outdentSelectedRows() + editor.insertText(' \tfoo\t '); + editor.outdentSelectedRows(); expect(buffer.lineForRow(0)).toBe( '\tfoo\t var quicksort = function () {' - ) - editor.outdentSelectedRows() + ); + editor.outdentSelectedRows(); expect(buffer.lineForRow(0)).toBe( 'foo\t var quicksort = function () {' - ) - editor.outdentSelectedRows() + ); + editor.outdentSelectedRows(); expect(buffer.lineForRow(0)).toBe( 'foo\t var quicksort = function () {' - ) - }) - }) + ); + }); + }); describe('when one line is selected', () => { it('outdents line and retains editor', () => { - editor.setSelectedBufferRange([[1, 4], [1, 14]]) - editor.outdentSelectedRows() - expect(buffer.lineForRow(1)).toBe('var sort = function(items) {') + editor.setSelectedBufferRange([[1, 4], [1, 14]]); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(1)).toBe('var sort = function(items) {'); expect(editor.getSelectedBufferRange()).toEqual([ [1, 4 - editor.getTabLength()], [1, 14 - editor.getTabLength()] - ]) - }) - }) + ]); + }); + }); describe('when multiple lines are selected', () => { it('outdents selected lines and retains editor', () => { - editor.setSelectedBufferRange([[0, 1], [3, 15]]) - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe('var sort = function(items) {') + editor.setSelectedBufferRange([[0, 1], [3, 15]]); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe('var sort = function(items) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(buffer.lineForRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); expect(editor.getSelectedBufferRange()).toEqual([ [0, 1], [3, 15 - editor.getTabLength()] - ]) - }) + ]); + }); it('does not outdent the last line of the selection if it ends at column 0', () => { - editor.setSelectedBufferRange([[0, 1], [3, 0]]) - editor.outdentSelectedRows() - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') - expect(buffer.lineForRow(1)).toBe('var sort = function(items) {') + editor.setSelectedBufferRange([[0, 1], [3, 0]]); + editor.outdentSelectedRows(); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); + expect(buffer.lineForRow(1)).toBe('var sort = function(items) {'); expect(buffer.lineForRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); expect(buffer.lineForRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) + ); - expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [3, 0]]) - }) - }) - }) + expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [3, 0]]); + }); + }); + }); describe('.autoIndentSelectedRows', () => { it('auto-indents the selection', () => { - editor.setCursorBufferPosition([2, 0]) - editor.insertText('function() {\ninside=true\n}\n i=1\n') - editor.getLastSelection().setBufferRange([[2, 0], [6, 0]]) - editor.autoIndentSelectedRows() + editor.setCursorBufferPosition([2, 0]); + editor.insertText('function() {\ninside=true\n}\n i=1\n'); + editor.getLastSelection().setBufferRange([[2, 0], [6, 0]]); + editor.autoIndentSelectedRows(); - expect(editor.lineTextForBufferRow(2)).toBe(' function() {') - expect(editor.lineTextForBufferRow(3)).toBe(' inside=true') - expect(editor.lineTextForBufferRow(4)).toBe(' }') - expect(editor.lineTextForBufferRow(5)).toBe(' i=1') - }) - }) + expect(editor.lineTextForBufferRow(2)).toBe(' function() {'); + expect(editor.lineTextForBufferRow(3)).toBe(' inside=true'); + expect(editor.lineTextForBufferRow(4)).toBe(' }'); + expect(editor.lineTextForBufferRow(5)).toBe(' i=1'); + }); + }); describe('.undo() and .redo()', () => { it('undoes/redoes the last change', () => { - editor.insertText('foo') - editor.undo() - expect(buffer.lineForRow(0)).not.toContain('foo') + editor.insertText('foo'); + editor.undo(); + expect(buffer.lineForRow(0)).not.toContain('foo'); - editor.redo() - expect(buffer.lineForRow(0)).toContain('foo') - }) + editor.redo(); + expect(buffer.lineForRow(0)).toContain('foo'); + }); it('batches the undo / redo of changes caused by multiple cursors', () => { - editor.setCursorScreenPosition([0, 0]) - editor.addCursorAtScreenPosition([1, 0]) + editor.setCursorScreenPosition([0, 0]); + editor.addCursorAtScreenPosition([1, 0]); - editor.insertText('foo') - editor.backspace() + editor.insertText('foo'); + editor.backspace(); - expect(buffer.lineForRow(0)).toContain('fovar') - expect(buffer.lineForRow(1)).toContain('fo ') + expect(buffer.lineForRow(0)).toContain('fovar'); + expect(buffer.lineForRow(1)).toContain('fo '); - editor.undo() + editor.undo(); - expect(buffer.lineForRow(0)).toContain('foo') - expect(buffer.lineForRow(1)).toContain('foo') + expect(buffer.lineForRow(0)).toContain('foo'); + expect(buffer.lineForRow(1)).toContain('foo'); - editor.redo() + editor.redo(); - expect(buffer.lineForRow(0)).not.toContain('foo') - expect(buffer.lineForRow(0)).toContain('fovar') - }) + expect(buffer.lineForRow(0)).not.toContain('foo'); + expect(buffer.lineForRow(0)).toContain('fovar'); + }); it('restores cursors and selections to their states before and after undone and redone changes', () => { - editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 3]]]) - editor.insertText('abc') + editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 3]]]); + editor.insertText('abc'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 3], [0, 3]], [[1, 3], [1, 3]] - ]) + ]); - editor.setCursorBufferPosition([0, 0]) + editor.setCursorBufferPosition([0, 0]); editor.setSelectedBufferRanges([ [[2, 0], [2, 0]], [[3, 0], [3, 0]], [[4, 0], [4, 3]] - ]) - editor.insertText('def') + ]); + editor.insertText('def'); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 3], [2, 3]], [[3, 3], [3, 3]], [[4, 3], [4, 3]] - ]) + ]); - editor.setCursorBufferPosition([0, 0]) - editor.undo() + editor.setCursorBufferPosition([0, 0]); + editor.undo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 0], [2, 0]], [[3, 0], [3, 0]], [[4, 0], [4, 3]] - ]) + ]); - editor.undo() + editor.undo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 0], [0, 0]], [[1, 0], [1, 3]] - ]) + ]); - editor.redo() + editor.redo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 3], [0, 3]], [[1, 3], [1, 3]] - ]) + ]); - editor.redo() + editor.redo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 3], [2, 3]], [[3, 3], [3, 3]], [[4, 3], [4, 3]] - ]) - }) + ]); + }); it('restores the selected ranges after undo and redo', () => { - editor.setSelectedBufferRanges([[[1, 6], [1, 10]], [[1, 22], [1, 27]]]) - editor.delete() - editor.delete() + editor.setSelectedBufferRanges([[[1, 6], [1, 10]], [[1, 22], [1, 27]]]); + editor.delete(); + editor.delete(); - expect(buffer.lineForRow(1)).toBe(' var = function( {') + expect(buffer.lineForRow(1)).toBe(' var = function( {'); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 6], [1, 6]], [[1, 17], [1, 17]] - ]) + ]); - editor.undo() + editor.undo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 6], [1, 6]], [[1, 18], [1, 18]] - ]) + ]); - editor.undo() + editor.undo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 6], [1, 10]], [[1, 22], [1, 27]] - ]) + ]); - editor.redo() + editor.redo(); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 6], [1, 6]], [[1, 18], [1, 18]] - ]) - }) + ]); + }); xit('restores folds after undo and redo', () => { - editor.foldBufferRow(1) + editor.foldBufferRow(1); editor.setSelectedBufferRange([[1, 0], [10, Infinity]], { preserveFolds: true - }) - expect(editor.isFoldedAtBufferRow(1)).toBeTruthy() + }); + expect(editor.isFoldedAtBufferRow(1)).toBeTruthy(); editor.insertText(dedent`\ // testing function foo() { return 1 + 2; }\ - `) - expect(editor.isFoldedAtBufferRow(1)).toBeFalsy() - editor.foldBufferRow(2) + `); + expect(editor.isFoldedAtBufferRow(1)).toBeFalsy(); + editor.foldBufferRow(2); - editor.undo() - expect(editor.isFoldedAtBufferRow(1)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(9)).toBeTruthy() - expect(editor.isFoldedAtBufferRow(10)).toBeFalsy() + editor.undo(); + expect(editor.isFoldedAtBufferRow(1)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(9)).toBeTruthy(); + expect(editor.isFoldedAtBufferRow(10)).toBeFalsy(); - editor.redo() - expect(editor.isFoldedAtBufferRow(1)).toBeFalsy() - expect(editor.isFoldedAtBufferRow(2)).toBeTruthy() - }) - }) + editor.redo(); + expect(editor.isFoldedAtBufferRow(1)).toBeFalsy(); + expect(editor.isFoldedAtBufferRow(2)).toBeTruthy(); + }); + }); describe('::transact', () => { it('restores the selection when the transaction is undone/redone', () => { - buffer.setText('1234') - editor.setSelectedBufferRange([[0, 1], [0, 3]]) + buffer.setText('1234'); + editor.setSelectedBufferRange([[0, 1], [0, 3]]); editor.transact(() => { - editor.delete() - editor.moveToEndOfLine() - editor.insertText('5') - expect(buffer.getText()).toBe('145') - }) + editor.delete(); + editor.moveToEndOfLine(); + editor.insertText('5'); + expect(buffer.getText()).toBe('145'); + }); - editor.undo() - expect(buffer.getText()).toBe('1234') - expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [0, 3]]) + editor.undo(); + expect(buffer.getText()).toBe('1234'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [0, 3]]); - editor.redo() - expect(buffer.getText()).toBe('145') - expect(editor.getSelectedBufferRange()).toEqual([[0, 3], [0, 3]]) - }) - }) + editor.redo(); + expect(buffer.getText()).toBe('145'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 3], [0, 3]]); + }); + }); describe('undo/redo restore selections of editor which initiated original change', () => { - let editor1, editor2 + let editor1, editor2; beforeEach(async () => { - editor1 = editor - editor2 = new TextEditor({ buffer: editor1.buffer }) + editor1 = editor; + editor2 = new TextEditor({ buffer: editor1.buffer }); editor1.setText(dedent` aaaaaa @@ -5917,18 +5958,18 @@ describe('TextEditor', () => { cccccc dddddd eeeeee - `) - }) + `); + }); it('[editor.transact] restore selection of change-initiated-editor', () => { - editor1.setCursorBufferPosition([0, 0]) - editor1.transact(() => editor1.insertText('1')) - editor2.setCursorBufferPosition([1, 0]) - editor2.transact(() => editor2.insertText('2')) - editor1.setCursorBufferPosition([2, 0]) - editor1.transact(() => editor1.insertText('3')) - editor2.setCursorBufferPosition([3, 0]) - editor2.transact(() => editor2.insertText('4')) + editor1.setCursorBufferPosition([0, 0]); + editor1.transact(() => editor1.insertText('1')); + editor2.setCursorBufferPosition([1, 0]); + editor2.transact(() => editor2.insertText('2')); + editor1.setCursorBufferPosition([2, 0]); + editor1.transact(() => editor1.insertText('3')); + editor2.setCursorBufferPosition([3, 0]); + editor2.transact(() => editor2.insertText('4')); expect(editor1.getText()).toBe(dedent` 1aaaaaa @@ -5936,66 +5977,66 @@ describe('TextEditor', () => { 3cccccc 4dddddd eeeeee - `) + `); - editor2.setCursorBufferPosition([4, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([3, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([2, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([1, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([0, 0]) - expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + editor2.setCursorBufferPosition([4, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([3, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([2, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([1, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([0, 0]); + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([0, 1]) - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([1, 1]) - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([2, 1]) - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([3, 1]) - expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([0, 1]); + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([1, 1]); + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([2, 1]); + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([3, 1]); + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged - editor1.setCursorBufferPosition([4, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([3, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([2, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([1, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([0, 0]) - expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + editor1.setCursorBufferPosition([4, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([3, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([2, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([1, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([0, 0]); + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([0, 1]) - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([1, 1]) - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([2, 1]) - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([3, 1]) - expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged - }) + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([0, 1]); + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([1, 1]); + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([2, 1]); + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([3, 1]); + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged + }); it('[manually group checkpoint] restore selection of change-initiated-editor', () => { const transact = (editor, fn) => { - const checkpoint = editor.createCheckpoint() - fn() - editor.groupChangesSinceCheckpoint(checkpoint) - } + const checkpoint = editor.createCheckpoint(); + fn(); + editor.groupChangesSinceCheckpoint(checkpoint); + }; - editor1.setCursorBufferPosition([0, 0]) - transact(editor1, () => editor1.insertText('1')) - editor2.setCursorBufferPosition([1, 0]) - transact(editor2, () => editor2.insertText('2')) - editor1.setCursorBufferPosition([2, 0]) - transact(editor1, () => editor1.insertText('3')) - editor2.setCursorBufferPosition([3, 0]) - transact(editor2, () => editor2.insertText('4')) + editor1.setCursorBufferPosition([0, 0]); + transact(editor1, () => editor1.insertText('1')); + editor2.setCursorBufferPosition([1, 0]); + transact(editor2, () => editor2.insertText('2')); + editor1.setCursorBufferPosition([2, 0]); + transact(editor1, () => editor1.insertText('3')); + editor2.setCursorBufferPosition([3, 0]); + transact(editor2, () => editor2.insertText('4')); expect(editor1.getText()).toBe(dedent` 1aaaaaa @@ -6003,384 +6044,384 @@ describe('TextEditor', () => { 3cccccc 4dddddd eeeeee - `) + `); - editor2.setCursorBufferPosition([4, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([3, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([2, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([1, 0]) - editor1.undo() - expect(editor1.getCursorBufferPosition()).toEqual([0, 0]) - expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + editor2.setCursorBufferPosition([4, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([3, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([2, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([1, 0]); + editor1.undo(); + expect(editor1.getCursorBufferPosition()).toEqual([0, 0]); + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([0, 1]) - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([1, 1]) - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([2, 1]) - editor1.redo() - expect(editor1.getCursorBufferPosition()).toEqual([3, 1]) - expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([0, 1]); + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([1, 1]); + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([2, 1]); + editor1.redo(); + expect(editor1.getCursorBufferPosition()).toEqual([3, 1]); + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged - editor1.setCursorBufferPosition([4, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([3, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([2, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([1, 0]) - editor2.undo() - expect(editor2.getCursorBufferPosition()).toEqual([0, 0]) - expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + editor1.setCursorBufferPosition([4, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([3, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([2, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([1, 0]); + editor2.undo(); + expect(editor2.getCursorBufferPosition()).toEqual([0, 0]); + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([0, 1]) - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([1, 1]) - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([2, 1]) - editor2.redo() - expect(editor2.getCursorBufferPosition()).toEqual([3, 1]) - expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged - }) - }) + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([0, 1]); + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([1, 1]); + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([2, 1]); + editor2.redo(); + expect(editor2.getCursorBufferPosition()).toEqual([3, 1]); + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]); // remain unchanged + }); + }); describe('when the buffer is changed (via its direct api, rather than via than edit session)', () => { it('moves the cursor so it is in the same relative position of the buffer', () => { - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - editor.addCursorAtScreenPosition([0, 5]) - editor.addCursorAtScreenPosition([1, 0]) - const [cursor1, cursor2, cursor3] = editor.getCursors() + expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + editor.addCursorAtScreenPosition([0, 5]); + editor.addCursorAtScreenPosition([1, 0]); + const [cursor1, cursor2, cursor3] = editor.getCursors(); - buffer.insert([0, 1], 'abc') + buffer.insert([0, 1], 'abc'); - expect(cursor1.getScreenPosition()).toEqual([0, 0]) - expect(cursor2.getScreenPosition()).toEqual([0, 8]) - expect(cursor3.getScreenPosition()).toEqual([1, 0]) - }) + expect(cursor1.getScreenPosition()).toEqual([0, 0]); + expect(cursor2.getScreenPosition()).toEqual([0, 8]); + expect(cursor3.getScreenPosition()).toEqual([1, 0]); + }); it('does not destroy cursors or selections when a change encompasses them', () => { - const cursor = editor.getLastCursor() - cursor.setBufferPosition([3, 3]) - editor.buffer.delete([[3, 1], [3, 5]]) - expect(cursor.getBufferPosition()).toEqual([3, 1]) - expect(editor.getCursors().indexOf(cursor)).not.toBe(-1) + const cursor = editor.getLastCursor(); + cursor.setBufferPosition([3, 3]); + editor.buffer.delete([[3, 1], [3, 5]]); + expect(cursor.getBufferPosition()).toEqual([3, 1]); + expect(editor.getCursors().indexOf(cursor)).not.toBe(-1); - const selection = editor.getLastSelection() - selection.setBufferRange([[3, 5], [3, 10]]) - editor.buffer.delete([[3, 3], [3, 8]]) - expect(selection.getBufferRange()).toEqual([[3, 3], [3, 5]]) - expect(editor.getSelections().indexOf(selection)).not.toBe(-1) - }) + const selection = editor.getLastSelection(); + selection.setBufferRange([[3, 5], [3, 10]]); + editor.buffer.delete([[3, 3], [3, 8]]); + expect(selection.getBufferRange()).toEqual([[3, 3], [3, 5]]); + expect(editor.getSelections().indexOf(selection)).not.toBe(-1); + }); it('merges cursors when the change causes them to overlap', () => { - editor.setCursorScreenPosition([0, 0]) - editor.addCursorAtScreenPosition([0, 2]) - editor.addCursorAtScreenPosition([1, 2]) + editor.setCursorScreenPosition([0, 0]); + editor.addCursorAtScreenPosition([0, 2]); + editor.addCursorAtScreenPosition([1, 2]); - const [cursor1, , cursor3] = editor.getCursors() - expect(editor.getCursors().length).toBe(3) + const [cursor1, , cursor3] = editor.getCursors(); + expect(editor.getCursors().length).toBe(3); - buffer.delete([[0, 0], [0, 2]]) + buffer.delete([[0, 0], [0, 2]]); - expect(editor.getCursors().length).toBe(2) - expect(editor.getCursors()).toEqual([cursor1, cursor3]) - expect(cursor1.getBufferPosition()).toEqual([0, 0]) - expect(cursor3.getBufferPosition()).toEqual([1, 2]) - }) - }) + expect(editor.getCursors().length).toBe(2); + expect(editor.getCursors()).toEqual([cursor1, cursor3]); + expect(cursor1.getBufferPosition()).toEqual([0, 0]); + expect(cursor3.getBufferPosition()).toEqual([1, 2]); + }); + }); describe('.moveSelectionLeft()', () => { it('moves one active selection on one line one column to the left', () => { - editor.setSelectedBufferRange([[0, 4], [0, 13]]) - expect(editor.getSelectedText()).toBe('quicksort') + editor.setSelectedBufferRange([[0, 4], [0, 13]]); + expect(editor.getSelectedText()).toBe('quicksort'); - editor.moveSelectionLeft() + editor.moveSelectionLeft(); - expect(editor.getSelectedText()).toBe('quicksort') - expect(editor.getSelectedBufferRange()).toEqual([[0, 3], [0, 12]]) - }) + expect(editor.getSelectedText()).toBe('quicksort'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 3], [0, 12]]); + }); it('moves multiple active selections on one line one column to the left', () => { - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]]) - const selections = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('function') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('function'); - editor.moveSelectionLeft() + editor.moveSelectionLeft(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('function') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('function'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 3], [0, 12]], [[0, 15], [0, 23]] - ]) - }) + ]); + }); it('moves multiple active selections on multiple lines one column to the left', () => { - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]]) - const selections = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('sort') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('sort'); - editor.moveSelectionLeft() + editor.moveSelectionLeft(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('sort') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('sort'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 3], [0, 12]], [[1, 5], [1, 9]] - ]) - }) + ]); + }); describe('when a selection is at the first column of a line', () => { it('does not change the selection', () => { - editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[1, 0], [1, 3]]]) - const selections = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[1, 0], [1, 3]]]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('var') - expect(selections[1].getText()).toBe(' v') + expect(selections[0].getText()).toBe('var'); + expect(selections[1].getText()).toBe(' v'); - editor.moveSelectionLeft() - editor.moveSelectionLeft() + editor.moveSelectionLeft(); + editor.moveSelectionLeft(); - expect(selections[0].getText()).toBe('var') - expect(selections[1].getText()).toBe(' v') + expect(selections[0].getText()).toBe('var'); + expect(selections[1].getText()).toBe(' v'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 0], [0, 3]], [[1, 0], [1, 3]] - ]) - }) + ]); + }); describe('when multiple selections are active on one line', () => { it('does not change the selection', () => { editor.setSelectedBufferRanges([ [[0, 0], [0, 3]], [[0, 4], [0, 13]] - ]) - const selections = editor.getSelections() + ]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('var') - expect(selections[1].getText()).toBe('quicksort') + expect(selections[0].getText()).toBe('var'); + expect(selections[1].getText()).toBe('quicksort'); - editor.moveSelectionLeft() + editor.moveSelectionLeft(); - expect(selections[0].getText()).toBe('var') - expect(selections[1].getText()).toBe('quicksort') + expect(selections[0].getText()).toBe('var'); + expect(selections[1].getText()).toBe('quicksort'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 0], [0, 3]], [[0, 4], [0, 13]] - ]) - }) - }) - }) - }) + ]); + }); + }); + }); + }); describe('.moveSelectionRight()', () => { it('moves one active selection on one line one column to the right', () => { - editor.setSelectedBufferRange([[0, 4], [0, 13]]) - expect(editor.getSelectedText()).toBe('quicksort') + editor.setSelectedBufferRange([[0, 4], [0, 13]]); + expect(editor.getSelectedText()).toBe('quicksort'); - editor.moveSelectionRight() + editor.moveSelectionRight(); - expect(editor.getSelectedText()).toBe('quicksort') - expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 14]]) - }) + expect(editor.getSelectedText()).toBe('quicksort'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 14]]); + }); it('moves multiple active selections on one line one column to the right', () => { - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]]) - const selections = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('function') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('function'); - editor.moveSelectionRight() + editor.moveSelectionRight(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('function') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('function'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 5], [0, 14]], [[0, 17], [0, 25]] - ]) - }) + ]); + }); it('moves multiple active selections on multiple lines one column to the right', () => { - editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]]) - const selections = editor.getSelections() + editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('sort') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('sort'); - editor.moveSelectionRight() + editor.moveSelectionRight(); - expect(selections[0].getText()).toBe('quicksort') - expect(selections[1].getText()).toBe('sort') + expect(selections[0].getText()).toBe('quicksort'); + expect(selections[1].getText()).toBe('sort'); expect(editor.getSelectedBufferRanges()).toEqual([ [[0, 5], [0, 14]], [[1, 7], [1, 11]] - ]) - }) + ]); + }); describe('when a selection is at the last column of a line', () => { it('does not change the selection', () => { editor.setSelectedBufferRanges([ [[2, 34], [2, 40]], [[5, 22], [5, 30]] - ]) - const selections = editor.getSelections() + ]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('items;') - expect(selections[1].getText()).toBe('shift();') + expect(selections[0].getText()).toBe('items;'); + expect(selections[1].getText()).toBe('shift();'); - editor.moveSelectionRight() - editor.moveSelectionRight() + editor.moveSelectionRight(); + editor.moveSelectionRight(); - expect(selections[0].getText()).toBe('items;') - expect(selections[1].getText()).toBe('shift();') + expect(selections[0].getText()).toBe('items;'); + expect(selections[1].getText()).toBe('shift();'); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 34], [2, 40]], [[5, 22], [5, 30]] - ]) - }) + ]); + }); describe('when multiple selections are active on one line', () => { it('does not change the selection', () => { editor.setSelectedBufferRanges([ [[2, 27], [2, 33]], [[2, 34], [2, 40]] - ]) - const selections = editor.getSelections() + ]); + const selections = editor.getSelections(); - expect(selections[0].getText()).toBe('return') - expect(selections[1].getText()).toBe('items;') + expect(selections[0].getText()).toBe('return'); + expect(selections[1].getText()).toBe('items;'); - editor.moveSelectionRight() + editor.moveSelectionRight(); - expect(selections[0].getText()).toBe('return') - expect(selections[1].getText()).toBe('items;') + expect(selections[0].getText()).toBe('return'); + expect(selections[1].getText()).toBe('items;'); expect(editor.getSelectedBufferRanges()).toEqual([ [[2, 27], [2, 33]], [[2, 34], [2, 40]] - ]) - }) - }) - }) - }) + ]); + }); + }); + }); + }); describe('when readonly', () => { beforeEach(() => { - editor.setReadOnly(true) - }) + editor.setReadOnly(true); + }); const modifications = [ { name: 'moveLineUp', op: (opts = {}) => { - editor.setCursorBufferPosition([1, 0]) - editor.moveLineUp(opts) + editor.setCursorBufferPosition([1, 0]); + editor.moveLineUp(opts); } }, { name: 'moveLineDown', op: (opts = {}) => { - editor.setCursorBufferPosition([0, 0]) - editor.moveLineDown(opts) + editor.setCursorBufferPosition([0, 0]); + editor.moveLineDown(opts); } }, { name: 'insertText', op: (opts = {}) => { - editor.setSelectedBufferRange([[1, 0], [1, 2]]) - editor.insertText('xxx', opts) + editor.setSelectedBufferRange([[1, 0], [1, 2]]); + editor.insertText('xxx', opts); } }, { name: 'insertNewline', op: (opts = {}) => { - editor.setCursorScreenPosition({ row: 1, column: 0 }) - editor.insertNewline(opts) + editor.setCursorScreenPosition({ row: 1, column: 0 }); + editor.insertNewline(opts); } }, { name: 'insertNewlineBelow', op: (opts = {}) => { - editor.setCursorBufferPosition([0, 2]) - editor.insertNewlineBelow(opts) + editor.setCursorBufferPosition([0, 2]); + editor.insertNewlineBelow(opts); } }, { name: 'insertNewlineAbove', op: (opts = {}) => { - editor.setCursorBufferPosition([0]) - editor.insertNewlineAbove(opts) + editor.setCursorBufferPosition([0]); + editor.insertNewlineAbove(opts); } }, { name: 'backspace', op: (opts = {}) => { - editor.setCursorScreenPosition({ row: 1, column: 7 }) - editor.backspace(opts) + editor.setCursorScreenPosition({ row: 1, column: 7 }); + editor.backspace(opts); } }, { name: 'deleteToPreviousWordBoundary', op: (opts = {}) => { - editor.setCursorBufferPosition([0, 16]) - editor.deleteToPreviousWordBoundary(opts) + editor.setCursorBufferPosition([0, 16]); + editor.deleteToPreviousWordBoundary(opts); } }, { name: 'deleteToNextWordBoundary', op: (opts = {}) => { - editor.setCursorBufferPosition([0, 15]) - editor.deleteToNextWordBoundary(opts) + editor.setCursorBufferPosition([0, 15]); + editor.deleteToNextWordBoundary(opts); } }, { name: 'deleteToBeginningOfWord', op: (opts = {}) => { - editor.setCursorBufferPosition([1, 24]) - editor.deleteToBeginningOfWord(opts) + editor.setCursorBufferPosition([1, 24]); + editor.deleteToBeginningOfWord(opts); } }, { name: 'deleteToEndOfLine', op: (opts = {}) => { - editor.setCursorBufferPosition([1, 24]) - editor.deleteToEndOfLine(opts) + editor.setCursorBufferPosition([1, 24]); + editor.deleteToEndOfLine(opts); } }, { name: 'deleteToBeginningOfLine', op: (opts = {}) => { - editor.setCursorBufferPosition([1, 24]) - editor.deleteToBeginningOfLine(opts) + editor.setCursorBufferPosition([1, 24]); + editor.deleteToBeginningOfLine(opts); } }, { name: 'delete', op: (opts = {}) => { - editor.setCursorScreenPosition([1, 6]) - editor.delete(opts) + editor.setCursorScreenPosition([1, 6]); + editor.delete(opts); } }, { name: 'deleteToEndOfWord', op: (opts = {}) => { - editor.setCursorBufferPosition([1, 24]) - editor.deleteToEndOfWord(opts) + editor.setCursorBufferPosition([1, 24]); + editor.deleteToEndOfWord(opts); } }, { name: 'indent', op: (opts = {}) => { - editor.indent(opts) + editor.indent(opts); } }, { @@ -6389,22 +6430,22 @@ describe('TextEditor', () => { editor.setSelectedBufferRanges([ [[0, 4], [0, 13]], [[1, 6], [1, 10]] - ]) - editor.cutSelectedText(opts) + ]); + editor.cutSelectedText(opts); } }, { name: 'cutToEndOfLine', op: (opts = {}) => { - editor.setCursorBufferPosition([2, 20]) - editor.cutToEndOfLine(opts) + editor.setCursorBufferPosition([2, 20]); + editor.cutToEndOfLine(opts); } }, { name: 'cutToEndOfBufferLine', op: (opts = {}) => { - editor.setCursorBufferPosition([2, 20]) - editor.cutToEndOfBufferLine(opts) + editor.setCursorBufferPosition([2, 20]); + editor.cutToEndOfBufferLine(opts); } }, { @@ -6413,688 +6454,688 @@ describe('TextEditor', () => { editor.setSelectedBufferRanges([ [[0, 4], [0, 13]], [[1, 6], [1, 10]] - ]) - atom.clipboard.write('first') - editor.pasteText(opts) + ]); + atom.clipboard.write('first'); + editor.pasteText(opts); } }, { name: 'indentSelectedRows', op: (opts = {}) => { - editor.setSelectedBufferRange([[0, 3], [0, 3]]) - editor.indentSelectedRows(opts) + editor.setSelectedBufferRange([[0, 3], [0, 3]]); + editor.indentSelectedRows(opts); } }, { name: 'outdentSelectedRows', op: (opts = {}) => { - editor.setSelectedBufferRange([[1, 3], [1, 3]]) - editor.outdentSelectedRows(opts) + editor.setSelectedBufferRange([[1, 3], [1, 3]]); + editor.outdentSelectedRows(opts); } }, { name: 'autoIndentSelectedRows', op: (opts = {}) => { - editor.setCursorBufferPosition([2, 0]) - editor.insertText('function() {\ninside=true\n}\n i=1\n', opts) - editor.getLastSelection().setBufferRange([[2, 0], [6, 0]]) - editor.autoIndentSelectedRows(opts) + editor.setCursorBufferPosition([2, 0]); + editor.insertText('function() {\ninside=true\n}\n i=1\n', opts); + editor.getLastSelection().setBufferRange([[2, 0], [6, 0]]); + editor.autoIndentSelectedRows(opts); } }, { name: 'undo/redo', op: (opts = {}) => { - editor.insertText('foo', opts) - editor.undo(opts) - editor.redo(opts) + editor.insertText('foo', opts); + editor.undo(opts); + editor.redo(opts); } } - ] + ]; describe('without bypassReadOnly', () => { for (const { name, op } of modifications) { it(`throws an error on ${name}`, () => { - expect(op).toThrow() - }) + expect(op).toThrow(); + }); } - }) + }); describe('with bypassReadOnly', () => { for (const { name, op } of modifications) { it(`permits ${name}`, () => { - op({ bypassReadOnly: true }) - }) + op({ bypassReadOnly: true }); + }); } - }) - }) - }) + }); + }); + }); describe('reading text', () => { it('.lineTextForScreenRow(row)', () => { - editor.foldBufferRow(4) + editor.foldBufferRow(4); expect(editor.lineTextForScreenRow(5)).toEqual( ' return sort(left).concat(pivot).concat(sort(right));' - ) - expect(editor.lineTextForScreenRow(9)).toEqual('};') - expect(editor.lineTextForScreenRow(10)).toBeUndefined() - }) - }) + ); + expect(editor.lineTextForScreenRow(9)).toEqual('};'); + expect(editor.lineTextForScreenRow(10)).toBeUndefined(); + }); + }); describe('.deleteLine()', () => { it('deletes the first line when the cursor is there', () => { - editor.getLastCursor().moveToTop() - const line1 = buffer.lineForRow(1) - const count = buffer.getLineCount() - expect(buffer.lineForRow(0)).not.toBe(line1) - editor.deleteLine() - expect(buffer.lineForRow(0)).toBe(line1) - expect(buffer.getLineCount()).toBe(count - 1) - }) + editor.getLastCursor().moveToTop(); + const line1 = buffer.lineForRow(1); + const count = buffer.getLineCount(); + expect(buffer.lineForRow(0)).not.toBe(line1); + editor.deleteLine(); + expect(buffer.lineForRow(0)).toBe(line1); + expect(buffer.getLineCount()).toBe(count - 1); + }); it('deletes the last line when the cursor is there', () => { - const count = buffer.getLineCount() - const secondToLastLine = buffer.lineForRow(count - 2) - expect(buffer.lineForRow(count - 1)).not.toBe(secondToLastLine) - editor.getLastCursor().moveToBottom() - editor.deleteLine() - const newCount = buffer.getLineCount() - expect(buffer.lineForRow(newCount - 1)).toBe(secondToLastLine) - expect(newCount).toBe(count - 1) - }) + const count = buffer.getLineCount(); + const secondToLastLine = buffer.lineForRow(count - 2); + expect(buffer.lineForRow(count - 1)).not.toBe(secondToLastLine); + editor.getLastCursor().moveToBottom(); + editor.deleteLine(); + const newCount = buffer.getLineCount(); + expect(buffer.lineForRow(newCount - 1)).toBe(secondToLastLine); + expect(newCount).toBe(count - 1); + }); it('deletes whole lines when partial lines are selected', () => { - editor.setSelectedBufferRange([[0, 2], [1, 2]]) - const line2 = buffer.lineForRow(2) - const count = buffer.getLineCount() - expect(buffer.lineForRow(0)).not.toBe(line2) - expect(buffer.lineForRow(1)).not.toBe(line2) - editor.deleteLine() - expect(buffer.lineForRow(0)).toBe(line2) - expect(buffer.getLineCount()).toBe(count - 2) - }) + editor.setSelectedBufferRange([[0, 2], [1, 2]]); + const line2 = buffer.lineForRow(2); + const count = buffer.getLineCount(); + expect(buffer.lineForRow(0)).not.toBe(line2); + expect(buffer.lineForRow(1)).not.toBe(line2); + editor.deleteLine(); + expect(buffer.lineForRow(0)).toBe(line2); + expect(buffer.getLineCount()).toBe(count - 2); + }); it('restores cursor position for multiple cursors', () => { - const line = '0123456789'.repeat(8) - editor.setText((line + '\n').repeat(5)) - editor.setCursorScreenPosition([0, 5]) - editor.addCursorAtScreenPosition([2, 8]) - editor.deleteLine() + const line = '0123456789'.repeat(8); + editor.setText((line + '\n').repeat(5)); + editor.setCursorScreenPosition([0, 5]); + editor.addCursorAtScreenPosition([2, 8]); + editor.deleteLine(); - const cursors = editor.getCursors() - expect(cursors.length).toBe(2) - expect(cursors[0].getScreenPosition()).toEqual([0, 5]) - expect(cursors[1].getScreenPosition()).toEqual([1, 8]) - }) + const cursors = editor.getCursors(); + expect(cursors.length).toBe(2); + expect(cursors[0].getScreenPosition()).toEqual([0, 5]); + expect(cursors[1].getScreenPosition()).toEqual([1, 8]); + }); it('restores cursor position for multiple selections', () => { - const line = '0123456789'.repeat(8) - editor.setText((line + '\n').repeat(5)) - editor.setSelectedBufferRanges([[[0, 5], [0, 8]], [[2, 4], [2, 15]]]) - editor.deleteLine() + const line = '0123456789'.repeat(8); + editor.setText((line + '\n').repeat(5)); + editor.setSelectedBufferRanges([[[0, 5], [0, 8]], [[2, 4], [2, 15]]]); + editor.deleteLine(); - const cursors = editor.getCursors() - expect(cursors.length).toBe(2) - expect(cursors[0].getScreenPosition()).toEqual([0, 5]) - expect(cursors[1].getScreenPosition()).toEqual([1, 4]) - }) + const cursors = editor.getCursors(); + expect(cursors.length).toBe(2); + expect(cursors[0].getScreenPosition()).toEqual([0, 5]); + expect(cursors[1].getScreenPosition()).toEqual([1, 4]); + }); it('deletes a line only once when multiple selections are on the same line', () => { - const line1 = buffer.lineForRow(1) - const count = buffer.getLineCount() - editor.setSelectedBufferRanges([[[0, 1], [0, 2]], [[0, 4], [0, 5]]]) - expect(buffer.lineForRow(0)).not.toBe(line1) + const line1 = buffer.lineForRow(1); + const count = buffer.getLineCount(); + editor.setSelectedBufferRanges([[[0, 1], [0, 2]], [[0, 4], [0, 5]]]); + expect(buffer.lineForRow(0)).not.toBe(line1); - editor.deleteLine() + editor.deleteLine(); - expect(buffer.lineForRow(0)).toBe(line1) - expect(buffer.getLineCount()).toBe(count - 1) - }) + expect(buffer.lineForRow(0)).toBe(line1); + expect(buffer.getLineCount()).toBe(count - 1); + }); it('only deletes first line if only newline is selected on second line', () => { - editor.setSelectedBufferRange([[0, 2], [1, 0]]) - const line1 = buffer.lineForRow(1) - const count = buffer.getLineCount() - expect(buffer.lineForRow(0)).not.toBe(line1) - editor.deleteLine() - expect(buffer.lineForRow(0)).toBe(line1) - expect(buffer.getLineCount()).toBe(count - 1) - }) + editor.setSelectedBufferRange([[0, 2], [1, 0]]); + const line1 = buffer.lineForRow(1); + const count = buffer.getLineCount(); + expect(buffer.lineForRow(0)).not.toBe(line1); + editor.deleteLine(); + expect(buffer.lineForRow(0)).toBe(line1); + expect(buffer.getLineCount()).toBe(count - 1); + }); it('deletes the entire region when invoke on a folded region', () => { - editor.foldBufferRow(1) - editor.getLastCursor().moveToTop() - editor.getLastCursor().moveDown() - expect(buffer.getLineCount()).toBe(13) - editor.deleteLine() - expect(buffer.getLineCount()).toBe(4) - }) + editor.foldBufferRow(1); + editor.getLastCursor().moveToTop(); + editor.getLastCursor().moveDown(); + expect(buffer.getLineCount()).toBe(13); + editor.deleteLine(); + expect(buffer.getLineCount()).toBe(4); + }); it('deletes the entire file from the bottom up', () => { - const count = buffer.getLineCount() - expect(count).toBeGreaterThan(0) + const count = buffer.getLineCount(); + expect(count).toBeGreaterThan(0); for (let i = 0; i < count; i++) { - editor.getLastCursor().moveToBottom() - editor.deleteLine() + editor.getLastCursor().moveToBottom(); + editor.deleteLine(); } - expect(buffer.getLineCount()).toBe(1) - expect(buffer.getText()).toBe('') - }) + expect(buffer.getLineCount()).toBe(1); + expect(buffer.getText()).toBe(''); + }); it('deletes the entire file from the top down', () => { - const count = buffer.getLineCount() - expect(count).toBeGreaterThan(0) + const count = buffer.getLineCount(); + expect(count).toBeGreaterThan(0); for (let i = 0; i < count; i++) { - editor.getLastCursor().moveToTop() - editor.deleteLine() + editor.getLastCursor().moveToTop(); + editor.deleteLine(); } - expect(buffer.getLineCount()).toBe(1) - expect(buffer.getText()).toBe('') - }) + expect(buffer.getLineCount()).toBe(1); + expect(buffer.getText()).toBe(''); + }); describe('when soft wrap is enabled', () => { it('deletes the entire line that the cursor is on', () => { - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(10) - editor.setCursorBufferPosition([6]) + editor.setSoftWrapped(true); + editor.setEditorWidthInChars(10); + editor.setCursorBufferPosition([6]); - const line7 = buffer.lineForRow(7) - const count = buffer.getLineCount() - expect(buffer.lineForRow(6)).not.toBe(line7) - editor.deleteLine() - expect(buffer.lineForRow(6)).toBe(line7) - expect(buffer.getLineCount()).toBe(count - 1) - }) - }) + const line7 = buffer.lineForRow(7); + const count = buffer.getLineCount(); + expect(buffer.lineForRow(6)).not.toBe(line7); + editor.deleteLine(); + expect(buffer.lineForRow(6)).toBe(line7); + expect(buffer.getLineCount()).toBe(count - 1); + }); + }); describe('when the line being deleted precedes a fold, and the command is undone', () => { it('restores the line and preserves the fold', () => { - editor.setCursorBufferPosition([4]) - editor.foldCurrentRow() - expect(editor.isFoldedAtScreenRow(4)).toBeTruthy() - editor.setCursorBufferPosition([3]) - editor.deleteLine() - expect(editor.isFoldedAtScreenRow(3)).toBeTruthy() - expect(buffer.lineForRow(3)).toBe(' while(items.length > 0) {') - editor.undo() - expect(editor.isFoldedAtScreenRow(4)).toBeTruthy() + editor.setCursorBufferPosition([4]); + editor.foldCurrentRow(); + expect(editor.isFoldedAtScreenRow(4)).toBeTruthy(); + editor.setCursorBufferPosition([3]); + editor.deleteLine(); + expect(editor.isFoldedAtScreenRow(3)).toBeTruthy(); + expect(buffer.lineForRow(3)).toBe(' while(items.length > 0) {'); + editor.undo(); + expect(editor.isFoldedAtScreenRow(4)).toBeTruthy(); expect(buffer.lineForRow(3)).toBe( ' var pivot = items.shift(), current, left = [], right = [];' - ) - }) - }) - }) + ); + }); + }); + }); describe('.replaceSelectedText(options, fn)', () => { describe('when no text is selected', () => { it('inserts the text returned from the function at the cursor position', () => { - editor.replaceSelectedText({}, () => '123') - expect(buffer.lineForRow(0)).toBe('123var quicksort = function () {') + editor.replaceSelectedText({}, () => '123'); + expect(buffer.lineForRow(0)).toBe('123var quicksort = function () {'); - editor.setCursorBufferPosition([0]) - editor.replaceSelectedText({ selectWordIfEmpty: true }, () => 'var') - expect(buffer.lineForRow(0)).toBe('var quicksort = function () {') + editor.setCursorBufferPosition([0]); + editor.replaceSelectedText({ selectWordIfEmpty: true }, () => 'var'); + expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); - editor.setCursorBufferPosition([10]) - editor.replaceSelectedText(null, () => '') - expect(buffer.lineForRow(10)).toBe('') - }) - }) + editor.setCursorBufferPosition([10]); + editor.replaceSelectedText(null, () => ''); + expect(buffer.lineForRow(10)).toBe(''); + }); + }); describe('when text is selected', () => { it('replaces the selected text with the text returned from the function', () => { - editor.setSelectedBufferRange([[0, 1], [0, 3]]) - editor.replaceSelectedText({}, () => 'ia') - expect(buffer.lineForRow(0)).toBe('via quicksort = function () {') - }) + editor.setSelectedBufferRange([[0, 1], [0, 3]]); + 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]]) - }) - }) - }) + 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') - editor.setCursorScreenPosition([0, 1]) - editor.transpose() - expect(editor.lineTextForBufferRow(0)).toBe('bac') - }) + editor.buffer.setText('abc'); + editor.setCursorScreenPosition([0, 1]); + editor.transpose(); + expect(editor.lineTextForBufferRow(0)).toBe('bac'); + }); it('reverses a selection', () => { - editor.buffer.setText('xabcz') - editor.setSelectedBufferRange([[0, 1], [0, 4]]) - editor.transpose() - expect(editor.lineTextForBufferRow(0)).toBe('xcbaz') - }) - }) + editor.buffer.setText('xabcz'); + editor.setSelectedBufferRange([[0, 1], [0, 4]]); + editor.transpose(); + expect(editor.lineTextForBufferRow(0)).toBe('xcbaz'); + }); + }); describe('.upperCase()', () => { describe('when there is no selection', () => { it('upper cases the current word', () => { - editor.buffer.setText('aBc') - editor.setCursorScreenPosition([0, 1]) - editor.upperCase() - expect(editor.lineTextForBufferRow(0)).toBe('ABC') - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 3]]) - }) - }) + editor.buffer.setText('aBc'); + editor.setCursorScreenPosition([0, 1]); + editor.upperCase(); + expect(editor.lineTextForBufferRow(0)).toBe('ABC'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 3]]); + }); + }); describe('when there is a selection', () => { it('upper cases the current selection', () => { - editor.buffer.setText('abc') - editor.setSelectedBufferRange([[0, 0], [0, 2]]) - editor.upperCase() - expect(editor.lineTextForBufferRow(0)).toBe('ABc') - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 2]]) - }) - }) - }) + editor.buffer.setText('abc'); + editor.setSelectedBufferRange([[0, 0], [0, 2]]); + editor.upperCase(); + expect(editor.lineTextForBufferRow(0)).toBe('ABc'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 2]]); + }); + }); + }); describe('.lowerCase()', () => { describe('when there is no selection', () => { it('lower cases the current word', () => { - editor.buffer.setText('aBC') - editor.setCursorScreenPosition([0, 1]) - editor.lowerCase() - expect(editor.lineTextForBufferRow(0)).toBe('abc') - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 3]]) - }) - }) + editor.buffer.setText('aBC'); + editor.setCursorScreenPosition([0, 1]); + editor.lowerCase(); + expect(editor.lineTextForBufferRow(0)).toBe('abc'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 3]]); + }); + }); describe('when there is a selection', () => { it('lower cases the current selection', () => { - editor.buffer.setText('ABC') - editor.setSelectedBufferRange([[0, 0], [0, 2]]) - editor.lowerCase() - expect(editor.lineTextForBufferRow(0)).toBe('abC') - expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 2]]) - }) - }) - }) + editor.buffer.setText('ABC'); + editor.setSelectedBufferRange([[0, 0], [0, 2]]); + editor.lowerCase(); + expect(editor.lineTextForBufferRow(0)).toBe('abC'); + expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 2]]); + }); + }); + }); describe('.setTabLength(tabLength)', () => { it('clips atomic soft tabs to the given tab length', () => { - expect(editor.getTabLength()).toBe(2) + expect(editor.getTabLength()).toBe(2); expect( editor.clipScreenPosition([5, 1], { clipDirection: 'forward' }) - ).toEqual([5, 2]) + ).toEqual([5, 2]); - editor.setTabLength(6) - expect(editor.getTabLength()).toBe(6) + editor.setTabLength(6); + expect(editor.getTabLength()).toBe(6); expect( editor.clipScreenPosition([5, 1], { clipDirection: 'forward' }) - ).toEqual([5, 6]) + ).toEqual([5, 6]); - const changeHandler = jasmine.createSpy('changeHandler') - editor.onDidChange(changeHandler) - editor.setTabLength(6) - expect(changeHandler).not.toHaveBeenCalled() - }) + const changeHandler = jasmine.createSpy('changeHandler'); + editor.onDidChange(changeHandler); + editor.setTabLength(6); + expect(changeHandler).not.toHaveBeenCalled(); + }); it('does not change its tab length when the given tab length is null', () => { - editor.setTabLength(4) - editor.setTabLength(null) - expect(editor.getTabLength()).toBe(4) - }) - }) + editor.setTabLength(4); + editor.setTabLength(null); + expect(editor.getTabLength()).toBe(4); + }); + }); describe('.indentLevelForLine(line)', () => { it('returns the indent level when the line has only leading whitespace', () => { - expect(editor.indentLevelForLine(' hello')).toBe(2) - expect(editor.indentLevelForLine(' hello')).toBe(1.5) - }) + expect(editor.indentLevelForLine(' hello')).toBe(2); + expect(editor.indentLevelForLine(' hello')).toBe(1.5); + }); it('returns the indent level when the line has only leading tabs', () => - expect(editor.indentLevelForLine('\t\thello')).toBe(2)) + expect(editor.indentLevelForLine('\t\thello')).toBe(2)); it('returns the indent level based on the character starting the line when the leading whitespace contains both spaces and tabs', () => { - expect(editor.indentLevelForLine('\t hello')).toBe(2) - expect(editor.indentLevelForLine(' \thello')).toBe(2) - expect(editor.indentLevelForLine(' \t hello')).toBe(2.5) - expect(editor.indentLevelForLine(' \t \thello')).toBe(4) - expect(editor.indentLevelForLine(' \t \thello')).toBe(4) - expect(editor.indentLevelForLine(' \t \t hello')).toBe(4.5) - }) - }) + expect(editor.indentLevelForLine('\t hello')).toBe(2); + expect(editor.indentLevelForLine(' \thello')).toBe(2); + expect(editor.indentLevelForLine(' \t hello')).toBe(2.5); + expect(editor.indentLevelForLine(' \t \thello')).toBe(4); + expect(editor.indentLevelForLine(' \t \thello')).toBe(4); + expect(editor.indentLevelForLine(' \t \t hello')).toBe(4.5); + }); + }); describe("when the buffer's language mode changes", () => { beforeEach(() => { - atom.config.set('core.useTreeSitterParsers', false) - }) + atom.config.set('core.useTreeSitterParsers', false); + }); it('notifies onDidTokenize observers when retokenization is finished', async () => { // Exercise the full `tokenizeInBackground` code path, which bails out early if // `.setVisible` has not been called with `true`. - jasmine.unspy(TextMateLanguageMode.prototype, 'tokenizeInBackground') - jasmine.attachToDOM(editor.getElement()) + jasmine.unspy(TextMateLanguageMode.prototype, 'tokenizeInBackground'); + jasmine.attachToDOM(editor.getElement()); - const events = [] - editor.onDidTokenize(event => events.push(event)) + const events = []; + editor.onDidTokenize(event => events.push(event)); - await atom.packages.activatePackage('language-c') + await atom.packages.activatePackage('language-c'); expect( atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.c') - ).toBe(true) - advanceClock(1) - expect(events.length).toBe(1) - }) + ).toBe(true); + advanceClock(1); + expect(events.length).toBe(1); + }); it('notifies onDidChangeGrammar observers', async () => { - const events = [] - editor.onDidChangeGrammar(grammar => events.push(grammar)) + const events = []; + editor.onDidChangeGrammar(grammar => events.push(grammar)); - await atom.packages.activatePackage('language-c') + await atom.packages.activatePackage('language-c'); expect( atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.c') - ).toBe(true) - expect(events.length).toBe(1) - expect(events[0].name).toBe('C') - }) - }) + ).toBe(true); + expect(events.length).toBe(1); + expect(events[0].name).toBe('C'); + }); + }); describe('editor.autoIndent', () => { describe('when editor.autoIndent is false (default)', () => { describe('when `indent` is triggered', () => { it('does not auto-indent the line', () => { - editor.setCursorBufferPosition([1, 30]) - editor.insertText('\n ') - expect(editor.lineTextForBufferRow(2)).toBe(' ') + editor.setCursorBufferPosition([1, 30]); + editor.insertText('\n '); + expect(editor.lineTextForBufferRow(2)).toBe(' '); - editor.update({ autoIndent: false }) - editor.indent() - expect(editor.lineTextForBufferRow(2)).toBe(' ') - }) - }) - }) + editor.update({ autoIndent: false }); + editor.indent(); + expect(editor.lineTextForBufferRow(2)).toBe(' '); + }); + }); + }); describe('when editor.autoIndent is true', () => { - beforeEach(() => editor.update({ autoIndent: true })) + beforeEach(() => editor.update({ autoIndent: true })); describe('when `indent` is triggered', () => { it('auto-indents the line', () => { - editor.setCursorBufferPosition([1, 30]) - editor.insertText('\n ') - expect(editor.lineTextForBufferRow(2)).toBe(' ') + editor.setCursorBufferPosition([1, 30]); + editor.insertText('\n '); + expect(editor.lineTextForBufferRow(2)).toBe(' '); - editor.update({ autoIndent: true }) - editor.indent() - expect(editor.lineTextForBufferRow(2)).toBe(' ') - }) - }) + editor.update({ autoIndent: true }); + editor.indent(); + expect(editor.lineTextForBufferRow(2)).toBe(' '); + }); + }); describe('when a newline is added', () => { describe('when the line preceding the newline adds a new level of indentation', () => { it('indents the newline to one additional level of indentation beyond the preceding line', () => { - editor.setCursorBufferPosition([1, Infinity]) - editor.insertText('\n') + editor.setCursorBufferPosition([1, Infinity]); + editor.insertText('\n'); expect(editor.indentationForBufferRow(2)).toBe( editor.indentationForBufferRow(1) + 1 - ) - }) - }) + ); + }); + }); describe("when the line preceding the newline doesn't add a level of indentation", () => { it('indents the new line to the same level as the preceding line', () => { - editor.setCursorBufferPosition([5, 14]) - editor.insertText('\n') + editor.setCursorBufferPosition([5, 14]); + editor.insertText('\n'); expect(editor.indentationForBufferRow(6)).toBe( editor.indentationForBufferRow(5) - ) - }) - }) + ); + }); + }); describe('when the line preceding the newline is a comment', () => { it('maintains the indent of the commented line', () => { - editor.setCursorBufferPosition([0, 0]) - editor.insertText(' //') - editor.setCursorBufferPosition([0, Infinity]) - editor.insertText('\n') - expect(editor.indentationForBufferRow(1)).toBe(2) - }) - }) + editor.setCursorBufferPosition([0, 0]); + editor.insertText(' //'); + editor.setCursorBufferPosition([0, Infinity]); + editor.insertText('\n'); + expect(editor.indentationForBufferRow(1)).toBe(2); + }); + }); describe('when the line preceding the newline contains only whitespace', () => { it("bases the new line's indentation on only the preceding line", () => { - editor.setCursorBufferPosition([6, Infinity]) - editor.insertText('\n ') - expect(editor.getCursorBufferPosition()).toEqual([7, 2]) + editor.setCursorBufferPosition([6, Infinity]); + editor.insertText('\n '); + expect(editor.getCursorBufferPosition()).toEqual([7, 2]); - editor.insertNewline() - expect(editor.lineTextForBufferRow(8)).toBe(' ') - }) - }) + editor.insertNewline(); + expect(editor.lineTextForBufferRow(8)).toBe(' '); + }); + }); it('does not indent the line preceding the newline', () => { - editor.setCursorBufferPosition([2, 0]) - editor.insertText(' var this-line-should-be-indented-more\n') - expect(editor.indentationForBufferRow(1)).toBe(1) + editor.setCursorBufferPosition([2, 0]); + editor.insertText(' var this-line-should-be-indented-more\n'); + expect(editor.indentationForBufferRow(1)).toBe(1); - editor.update({ autoIndent: true }) - editor.setCursorBufferPosition([2, Infinity]) - editor.insertText('\n') - expect(editor.indentationForBufferRow(1)).toBe(1) - expect(editor.indentationForBufferRow(2)).toBe(1) - }) + editor.update({ autoIndent: true }); + editor.setCursorBufferPosition([2, Infinity]); + editor.insertText('\n'); + expect(editor.indentationForBufferRow(1)).toBe(1); + expect(editor.indentationForBufferRow(2)).toBe(1); + }); describe('when the cursor is before whitespace', () => { it('retains the whitespace following the cursor on the new line', () => { - editor.setText(' var sort = function() {}') - editor.setCursorScreenPosition([0, 12]) - editor.insertNewline() + editor.setText(' var sort = function() {}'); + editor.setCursorScreenPosition([0, 12]); + editor.insertNewline(); - expect(buffer.lineForRow(0)).toBe(' var sort =') - expect(buffer.lineForRow(1)).toBe(' function() {}') - expect(editor.getCursorScreenPosition()).toEqual([1, 2]) - }) - }) - }) + expect(buffer.lineForRow(0)).toBe(' var sort ='); + expect(buffer.lineForRow(1)).toBe(' function() {}'); + expect(editor.getCursorScreenPosition()).toEqual([1, 2]); + }); + }); + }); describe('when inserted text matches a decrease indent pattern', () => { describe('when the preceding line matches an increase indent pattern', () => { it('decreases the indentation to match that of the preceding line', () => { - editor.setCursorBufferPosition([1, Infinity]) - editor.insertText('\n') + editor.setCursorBufferPosition([1, Infinity]); + editor.insertText('\n'); expect(editor.indentationForBufferRow(2)).toBe( editor.indentationForBufferRow(1) + 1 - ) - editor.insertText('}') + ); + editor.insertText('}'); expect(editor.indentationForBufferRow(2)).toBe( editor.indentationForBufferRow(1) - ) - }) - }) + ); + }); + }); describe("when the preceding line doesn't match an increase indent pattern", () => { it('decreases the indentation to be one level below that of the preceding line', () => { - editor.setCursorBufferPosition([3, Infinity]) - editor.insertText('\n ') + editor.setCursorBufferPosition([3, Infinity]); + editor.insertText('\n '); expect(editor.indentationForBufferRow(4)).toBe( editor.indentationForBufferRow(3) - ) - editor.insertText('}') + ); + editor.insertText('}'); expect(editor.indentationForBufferRow(4)).toBe( editor.indentationForBufferRow(3) - 1 - ) - }) + ); + }); it("doesn't break when decreasing the indentation on a row that has no indentation", () => { - editor.setCursorBufferPosition([12, Infinity]) - editor.insertText('\n}; # too many closing brackets!') + editor.setCursorBufferPosition([12, Infinity]); + editor.insertText('\n}; # too many closing brackets!'); expect(editor.lineTextForBufferRow(13)).toBe( '}; # too many closing brackets!' - ) - }) - }) - }) + ); + }); + }); + }); describe('when inserted text does not match a decrease indent pattern', () => { it('does not decrease the indentation', () => { - editor.setCursorBufferPosition([12, 0]) - editor.insertText(' ') - expect(editor.lineTextForBufferRow(12)).toBe(' };') - editor.insertText('\t\t') - expect(editor.lineTextForBufferRow(12)).toBe(' \t\t};') - }) - }) + editor.setCursorBufferPosition([12, 0]); + editor.insertText(' '); + expect(editor.lineTextForBufferRow(12)).toBe(' };'); + editor.insertText('\t\t'); + expect(editor.lineTextForBufferRow(12)).toBe(' \t\t};'); + }); + }); describe('when the current line does not match a decrease indent pattern', () => { it('leaves the line unchanged', () => { - editor.setCursorBufferPosition([2, 4]) + editor.setCursorBufferPosition([2, 4]); expect(editor.indentationForBufferRow(2)).toBe( editor.indentationForBufferRow(1) + 1 - ) - editor.insertText('foo') + ); + editor.insertText('foo'); expect(editor.indentationForBufferRow(2)).toBe( editor.indentationForBufferRow(1) + 1 - ) - }) - }) - }) - }) + ); + }); + }); + }); + }); describe('atomic soft tabs', () => { it('skips tab-length runs of leading whitespace when moving the cursor', () => { - editor.update({ tabLength: 4, atomicSoftTabs: true }) + editor.update({ tabLength: 4, atomicSoftTabs: true }); - editor.setCursorScreenPosition([2, 3]) - expect(editor.getCursorScreenPosition()).toEqual([2, 4]) + editor.setCursorScreenPosition([2, 3]); + expect(editor.getCursorScreenPosition()).toEqual([2, 4]); - editor.update({ atomicSoftTabs: false }) - editor.setCursorScreenPosition([2, 3]) - expect(editor.getCursorScreenPosition()).toEqual([2, 3]) + editor.update({ atomicSoftTabs: false }); + editor.setCursorScreenPosition([2, 3]); + expect(editor.getCursorScreenPosition()).toEqual([2, 3]); - editor.update({ atomicSoftTabs: true }) - editor.setCursorScreenPosition([2, 3]) - expect(editor.getCursorScreenPosition()).toEqual([2, 4]) - }) - }) + editor.update({ atomicSoftTabs: true }); + editor.setCursorScreenPosition([2, 3]); + expect(editor.getCursorScreenPosition()).toEqual([2, 4]); + }); + }); describe('.destroy()', () => { it('destroys marker layers associated with the text editor', () => { - buffer.retain() - const selectionsMarkerLayerId = editor.selectionsMarkerLayer.id - const foldsMarkerLayerId = editor.displayLayer.foldsMarkerLayer.id - editor.destroy() - expect(buffer.getMarkerLayer(selectionsMarkerLayerId)).toBeUndefined() - expect(buffer.getMarkerLayer(foldsMarkerLayerId)).toBeUndefined() - buffer.release() - }) + buffer.retain(); + const selectionsMarkerLayerId = editor.selectionsMarkerLayer.id; + const foldsMarkerLayerId = editor.displayLayer.foldsMarkerLayer.id; + editor.destroy(); + expect(buffer.getMarkerLayer(selectionsMarkerLayerId)).toBeUndefined(); + expect(buffer.getMarkerLayer(foldsMarkerLayerId)).toBeUndefined(); + buffer.release(); + }); it('notifies ::onDidDestroy observers when the editor is destroyed', () => { - let destroyObserverCalled = false - editor.onDidDestroy(() => (destroyObserverCalled = true)) + let destroyObserverCalled = false; + editor.onDidDestroy(() => (destroyObserverCalled = true)); - editor.destroy() - expect(destroyObserverCalled).toBe(true) - }) + editor.destroy(); + expect(destroyObserverCalled).toBe(true); + }); it('does not blow up when query methods are called afterward', () => { - editor.destroy() - editor.getGrammar() - editor.getLastCursor() - editor.lineTextForBufferRow(0) - }) + editor.destroy(); + editor.getGrammar(); + editor.getLastCursor(); + editor.lineTextForBufferRow(0); + }); it("emits the destroy event after destroying the editor's buffer", () => { - const events = [] + const events = []; editor.getBuffer().onDidDestroy(() => { - expect(editor.isDestroyed()).toBe(true) - events.push('buffer-destroyed') - }) + expect(editor.isDestroyed()).toBe(true); + events.push('buffer-destroyed'); + }); editor.onDidDestroy(() => { - expect(buffer.isDestroyed()).toBe(true) - events.push('editor-destroyed') - }) - editor.destroy() - expect(events).toEqual(['buffer-destroyed', 'editor-destroyed']) - }) - }) + expect(buffer.isDestroyed()).toBe(true); + events.push('editor-destroyed'); + }); + editor.destroy(); + expect(events).toEqual(['buffer-destroyed', 'editor-destroyed']); + }); + }); describe('.joinLines()', () => { describe('when no text is selected', () => { describe("when the line below isn't empty", () => { it('joins the line below with the current line separated by a space and moves the cursor to the start of line that was moved up', () => { - editor.setCursorBufferPosition([0, Infinity]) - editor.insertText(' ') - editor.setCursorBufferPosition([0]) - editor.joinLines() + editor.setCursorBufferPosition([0, Infinity]); + editor.insertText(' '); + editor.setCursorBufferPosition([0]); + editor.joinLines(); expect(editor.lineTextForBufferRow(0)).toBe( 'var quicksort = function () { var sort = function(items) {' - ) - expect(editor.getCursorBufferPosition()).toEqual([0, 29]) - }) - }) + ); + expect(editor.getCursorBufferPosition()).toEqual([0, 29]); + }); + }); describe('when the line below is empty', () => { it('deletes the line below and moves the cursor to the end of the line', () => { - editor.setCursorBufferPosition([9]) - editor.joinLines() - expect(editor.lineTextForBufferRow(9)).toBe(' };') + editor.setCursorBufferPosition([9]); + editor.joinLines(); + expect(editor.lineTextForBufferRow(9)).toBe(' };'); expect(editor.lineTextForBufferRow(10)).toBe( ' return sort(Array.apply(this, arguments));' - ) - expect(editor.getCursorBufferPosition()).toEqual([9, 4]) - }) - }) + ); + expect(editor.getCursorBufferPosition()).toEqual([9, 4]); + }); + }); describe('when the cursor is on the last row', () => { it('does nothing', () => { - editor.setCursorBufferPosition([Infinity, Infinity]) - editor.joinLines() - expect(editor.lineTextForBufferRow(12)).toBe('};') - }) - }) + editor.setCursorBufferPosition([Infinity, Infinity]); + editor.joinLines(); + expect(editor.lineTextForBufferRow(12)).toBe('};'); + }); + }); describe('when the line is empty', () => { it('joins the line below with the current line with no added space', () => { - editor.setCursorBufferPosition([10]) - editor.joinLines() + editor.setCursorBufferPosition([10]); + editor.joinLines(); expect(editor.lineTextForBufferRow(10)).toBe( 'return sort(Array.apply(this, arguments));' - ) - expect(editor.getCursorBufferPosition()).toEqual([10, 0]) - }) - }) - }) + ); + expect(editor.getCursorBufferPosition()).toEqual([10, 0]); + }); + }); + }); describe('when text is selected', () => { describe('when the selection does not span multiple lines', () => { it('joins the line below with the current line separated by a space and retains the selected text', () => { - editor.setSelectedBufferRange([[0, 1], [0, 3]]) - editor.joinLines() + editor.setSelectedBufferRange([[0, 1], [0, 3]]); + editor.joinLines(); expect(editor.lineTextForBufferRow(0)).toBe( 'var quicksort = function () { var sort = function(items) {' - ) - expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [0, 3]]) - }) - }) + ); + expect(editor.getSelectedBufferRange()).toEqual([[0, 1], [0, 3]]); + }); + }); describe('when the selection spans multiple lines', () => { it('joins all selected lines separated by a space and retains the selected text', () => { - editor.setSelectedBufferRange([[9, 3], [12, 1]]) - editor.joinLines() + editor.setSelectedBufferRange([[9, 3], [12, 1]]); + editor.joinLines(); expect(editor.lineTextForBufferRow(9)).toBe( ' }; return sort(Array.apply(this, arguments)); };' - ) - expect(editor.getSelectedBufferRange()).toEqual([[9, 3], [9, 49]]) - }) - }) - }) - }) + ); + expect(editor.getSelectedBufferRange()).toEqual([[9, 3], [9, 49]]); + }); + }); + }); + }); describe('.duplicateLines()', () => { it('for each selection, duplicates all buffer lines intersected by the selection', () => { - editor.foldBufferRow(4) - editor.setCursorBufferPosition([2, 5]) + editor.foldBufferRow(4); + editor.setCursorBufferPosition([2, 5]); editor.addSelectionForBufferRange([[3, 0], [8, 0]], { preserveFolds: true - }) + }); - editor.duplicateLines() + editor.duplicateLines(); expect(editor.getTextInBufferRange([[2, 0], [13, 5]])).toBe( dedent` @@ -7114,28 +7155,28 @@ describe('TextEditor', () => { .split('\n') .map(l => ` ${l}`) .join('\n') - ) + ); expect(editor.getSelectedBufferRanges()).toEqual([ [[3, 5], [3, 5]], [[9, 0], [14, 0]] - ]) + ]); // folds are also duplicated - expect(editor.isFoldedAtScreenRow(5)).toBe(true) - expect(editor.isFoldedAtScreenRow(7)).toBe(true) + expect(editor.isFoldedAtScreenRow(5)).toBe(true); + expect(editor.isFoldedAtScreenRow(7)).toBe(true); expect(editor.lineTextForScreenRow(7)).toBe( ` while(items.length > 0) {${editor.displayLayer.foldCharacter}}` - ) + ); expect(editor.lineTextForScreenRow(8)).toBe( ' return sort(left).concat(pivot).concat(sort(right));' - ) - }) + ); + }); it('duplicates all folded lines for empty selections on lines containing folds', () => { - editor.foldBufferRow(4) - editor.setCursorBufferPosition([4, 0]) + editor.foldBufferRow(4); + editor.setCursorBufferPosition([4, 0]); - editor.duplicateLines() + editor.duplicateLines(); expect(editor.getTextInBufferRange([[2, 0], [11, 5]])).toBe( dedent` @@ -7153,13 +7194,13 @@ describe('TextEditor', () => { .split('\n') .map(l => ` ${l}`) .join('\n') - ) - expect(editor.getSelectedBufferRange()).toEqual([[8, 0], [8, 0]]) - }) + ); + expect(editor.getSelectedBufferRange()).toEqual([[8, 0], [8, 0]]); + }); it('can duplicate the last line of the buffer', () => { - editor.setSelectedBufferRange([[11, 0], [12, 2]]) - editor.duplicateLines() + editor.setSelectedBufferRange([[11, 0], [12, 2]]); + editor.duplicateLines(); expect(editor.getTextInBufferRange([[11, 0], [14, 2]])).toBe( ' ' + dedent` @@ -7168,9 +7209,9 @@ describe('TextEditor', () => { return sort(Array.apply(this, arguments)); }; `.trim() - ) - expect(editor.getSelectedBufferRange()).toEqual([[13, 0], [14, 2]]) - }) + ); + expect(editor.getSelectedBufferRange()).toEqual([[13, 0], [14, 2]]); + }); it('only duplicates lines containing multiple selections once', () => { editor.setText(dedent` @@ -7178,15 +7219,15 @@ describe('TextEditor', () => { bbbbbb cccccc dddddd - `) + `); editor.setSelectedBufferRanges([ [[0, 1], [0, 2]], [[0, 3], [0, 4]], [[2, 1], [2, 2]], [[2, 3], [3, 1]], [[3, 3], [3, 4]] - ]) - editor.duplicateLines() + ]); + editor.duplicateLines(); expect(editor.getText()).toBe(dedent` aaaaaa aaaaaa @@ -7195,158 +7236,160 @@ describe('TextEditor', () => { dddddd cccccc dddddd - `) + `); expect(editor.getSelectedBufferRanges()).toEqual([ [[1, 1], [1, 2]], [[1, 3], [1, 4]], [[5, 1], [5, 2]], [[5, 3], [6, 1]], [[6, 3], [6, 4]] - ]) - }) - }) + ]); + }); + }); describe('when the editor contains surrogate pair characters', () => { it('correctly backspaces over them', () => { - editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97') - editor.moveToBottom() - editor.backspace() - expect(editor.getText()).toBe('\uD835\uDF97\uD835\uDF97') - editor.backspace() - expect(editor.getText()).toBe('\uD835\uDF97') - editor.backspace() - expect(editor.getText()).toBe('') - }) + editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97'); + editor.moveToBottom(); + editor.backspace(); + expect(editor.getText()).toBe('\uD835\uDF97\uD835\uDF97'); + editor.backspace(); + expect(editor.getText()).toBe('\uD835\uDF97'); + editor.backspace(); + expect(editor.getText()).toBe(''); + }); it('correctly deletes over them', () => { - editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97') - editor.moveToTop() - editor.delete() - expect(editor.getText()).toBe('\uD835\uDF97\uD835\uDF97') - editor.delete() - expect(editor.getText()).toBe('\uD835\uDF97') - editor.delete() - expect(editor.getText()).toBe('') - }) + editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97'); + editor.moveToTop(); + editor.delete(); + expect(editor.getText()).toBe('\uD835\uDF97\uD835\uDF97'); + editor.delete(); + expect(editor.getText()).toBe('\uD835\uDF97'); + editor.delete(); + expect(editor.getText()).toBe(''); + }); it('correctly moves over them', () => { - editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97\n') - editor.moveToTop() - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) - }) + editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97\n'); + editor.moveToTop(); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); + }); describe('when the editor contains variation sequence character pairs', () => { it('correctly backspaces over them', () => { - editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E') - editor.moveToBottom() - editor.backspace() - expect(editor.getText()).toBe('\u2714\uFE0E\u2714\uFE0E') - editor.backspace() - expect(editor.getText()).toBe('\u2714\uFE0E') - editor.backspace() - expect(editor.getText()).toBe('') - }) + editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E'); + editor.moveToBottom(); + editor.backspace(); + expect(editor.getText()).toBe('\u2714\uFE0E\u2714\uFE0E'); + editor.backspace(); + expect(editor.getText()).toBe('\u2714\uFE0E'); + editor.backspace(); + expect(editor.getText()).toBe(''); + }); it('correctly deletes over them', () => { - editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E') - editor.moveToTop() - editor.delete() - expect(editor.getText()).toBe('\u2714\uFE0E\u2714\uFE0E') - editor.delete() - expect(editor.getText()).toBe('\u2714\uFE0E') - editor.delete() - expect(editor.getText()).toBe('') - }) + editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E'); + editor.moveToTop(); + editor.delete(); + expect(editor.getText()).toBe('\u2714\uFE0E\u2714\uFE0E'); + editor.delete(); + expect(editor.getText()).toBe('\u2714\uFE0E'); + editor.delete(); + expect(editor.getText()).toBe(''); + }); it('correctly moves over them', () => { - editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E\n') - editor.moveToTop() - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) - editor.moveRight() - expect(editor.getCursorBufferPosition()).toEqual([1, 0]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 6]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 4]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 2]) - editor.moveLeft() - expect(editor.getCursorBufferPosition()).toEqual([0, 0]) - }) - }) + editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E\n'); + editor.moveToTop(); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); + editor.moveRight(); + expect(editor.getCursorBufferPosition()).toEqual([1, 0]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 6]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 4]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 2]); + editor.moveLeft(); + expect(editor.getCursorBufferPosition()).toEqual([0, 0]); + }); + }); describe('.setIndentationForBufferRow', () => { describe('when the editor uses soft tabs but the row has hard tabs', () => { it('only replaces whitespace characters', () => { - editor.setSoftWrapped(true) - editor.setText('\t1\n\t2') - editor.setCursorBufferPosition([0, 0]) - editor.setIndentationForBufferRow(0, 2) - expect(editor.getText()).toBe(' 1\n\t2') - }) - }) + editor.setSoftWrapped(true); + editor.setText('\t1\n\t2'); + editor.setCursorBufferPosition([0, 0]); + editor.setIndentationForBufferRow(0, 2); + expect(editor.getText()).toBe(' 1\n\t2'); + }); + }); describe('when the indentation level is a non-integer', () => { it('does not throw an exception', () => { - editor.setSoftWrapped(true) - editor.setText('\t1\n\t2') - editor.setCursorBufferPosition([0, 0]) - editor.setIndentationForBufferRow(0, 2.1) - expect(editor.getText()).toBe(' 1\n\t2') - }) - }) - }) + editor.setSoftWrapped(true); + editor.setText('\t1\n\t2'); + editor.setCursorBufferPosition([0, 0]); + editor.setIndentationForBufferRow(0, 2.1); + expect(editor.getText()).toBe(' 1\n\t2'); + }); + }); + }); describe("when the editor's grammar has an injection selector", () => { beforeEach(async () => { - atom.config.set('core.useTreeSitterParsers', false) - await atom.packages.activatePackage('language-text') - await atom.packages.activatePackage('language-javascript') - }) + atom.config.set('core.useTreeSitterParsers', false); + await atom.packages.activatePackage('language-text'); + await atom.packages.activatePackage('language-javascript'); + }); it("includes the grammar's patterns when the selector matches the current scope in other grammars", async () => { - await atom.packages.activatePackage('language-hyperlink') + await atom.packages.activatePackage('language-hyperlink'); - const grammar = atom.grammars.selectGrammar('text.js') - const { line, tags } = grammar.tokenizeLine('var i; // http://github.com') + const grammar = atom.grammars.selectGrammar('text.js'); + const { line, tags } = grammar.tokenizeLine( + 'var i; // http://github.com' + ); - const tokens = atom.grammars.decodeTokens(line, tags) - expect(tokens[0].value).toBe('var') - expect(tokens[0].scopes).toEqual(['source.js', 'storage.type.var.js']) - expect(tokens[6].value).toBe('http://github.com') + const tokens = atom.grammars.decodeTokens(line, tags); + expect(tokens[0].value).toBe('var'); + expect(tokens[0].scopes).toEqual(['source.js', 'storage.type.var.js']); + expect(tokens[6].value).toBe('http://github.com'); expect(tokens[6].scopes).toEqual([ 'source.js', 'comment.line.double-slash.js', 'markup.underline.link.http.hyperlink' - ]) - }) + ]); + }); describe('when the grammar is added', () => { it('retokenizes existing buffers that contain tokens that match the injection selector', async () => { - editor = await atom.workspace.open('sample.js') - editor.setText('// http://github.com') - let tokens = editor.tokensForScreenRow(0) + editor = await atom.workspace.open('sample.js'); + editor.setText('// http://github.com'); + let tokens = editor.tokensForScreenRow(0); expect(tokens).toEqual([ { text: '//', @@ -7363,10 +7406,10 @@ describe('TextEditor', () => { 'syntax--comment syntax--line syntax--double-slash syntax--js' ] } - ]) + ]); - await atom.packages.activatePackage('language-hyperlink') - tokens = editor.tokensForScreenRow(0) + await atom.packages.activatePackage('language-hyperlink'); + tokens = editor.tokensForScreenRow(0); expect(tokens).toEqual([ { text: '//', @@ -7391,14 +7434,14 @@ describe('TextEditor', () => { 'syntax--markup syntax--underline syntax--link syntax--http syntax--hyperlink' ] } - ]) - }) + ]); + }); describe('when the grammar is updated', () => { it('retokenizes existing buffers that contain tokens that match the injection selector', async () => { - editor = await atom.workspace.open('sample.js') - editor.setText('// SELECT * FROM OCTOCATS') - let tokens = editor.tokensForScreenRow(0) + editor = await atom.workspace.open('sample.js'); + editor.setText('// SELECT * FROM OCTOCATS'); + let tokens = editor.tokensForScreenRow(0); expect(tokens).toEqual([ { text: '//', @@ -7415,10 +7458,12 @@ describe('TextEditor', () => { 'syntax--comment syntax--line syntax--double-slash syntax--js' ] } - ]) + ]); - await atom.packages.activatePackage('package-with-injection-selector') - tokens = editor.tokensForScreenRow(0) + await atom.packages.activatePackage( + 'package-with-injection-selector' + ); + tokens = editor.tokensForScreenRow(0); expect(tokens).toEqual([ { text: '//', @@ -7435,10 +7480,10 @@ describe('TextEditor', () => { 'syntax--comment syntax--line syntax--double-slash syntax--js' ] } - ]) + ]); - await atom.packages.activatePackage('language-sql') - tokens = editor.tokensForScreenRow(0) + await atom.packages.activatePackage('language-sql'); + tokens = editor.tokensForScreenRow(0); expect(tokens).toEqual([ { text: '//', @@ -7500,337 +7545,337 @@ describe('TextEditor', () => { 'syntax--comment syntax--line syntax--double-slash syntax--js' ] } - ]) - }) - }) - }) - }) + ]); + }); + }); + }); + }); describe('.normalizeTabsInBufferRange()', () => { it("normalizes tabs depending on the editor's soft tab/tab length settings", () => { - editor.setTabLength(1) - editor.setSoftTabs(true) - editor.setText('\t\t\t') - editor.normalizeTabsInBufferRange([[0, 0], [0, 1]]) - expect(editor.getText()).toBe(' \t\t') + editor.setTabLength(1); + editor.setSoftTabs(true); + editor.setText('\t\t\t'); + editor.normalizeTabsInBufferRange([[0, 0], [0, 1]]); + expect(editor.getText()).toBe(' \t\t'); - editor.setTabLength(2) - editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]]) - expect(editor.getText()).toBe(' ') + editor.setTabLength(2); + editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]]); + expect(editor.getText()).toBe(' '); - editor.setSoftTabs(false) - editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]]) - expect(editor.getText()).toBe(' ') - }) - }) + editor.setSoftTabs(false); + editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]]); + expect(editor.getText()).toBe(' '); + }); + }); describe('.pageUp/Down()', () => { it('moves the cursor down one page length', () => { - editor.update({ autoHeight: false }) - const element = editor.getElement() - jasmine.attachToDOM(element) - element.style.height = element.component.getLineHeight() * 5 + 'px' - element.measureDimensions() + editor.update({ autoHeight: false }); + const element = editor.getElement(); + jasmine.attachToDOM(element); + element.style.height = element.component.getLineHeight() * 5 + 'px'; + element.measureDimensions(); - expect(editor.getCursorBufferPosition().row).toBe(0) + expect(editor.getCursorBufferPosition().row).toBe(0); - editor.pageDown() - expect(editor.getCursorBufferPosition().row).toBe(5) + editor.pageDown(); + expect(editor.getCursorBufferPosition().row).toBe(5); - editor.pageDown() - expect(editor.getCursorBufferPosition().row).toBe(10) + editor.pageDown(); + expect(editor.getCursorBufferPosition().row).toBe(10); - editor.pageUp() - expect(editor.getCursorBufferPosition().row).toBe(5) + editor.pageUp(); + expect(editor.getCursorBufferPosition().row).toBe(5); - editor.pageUp() - expect(editor.getCursorBufferPosition().row).toBe(0) - }) - }) + editor.pageUp(); + expect(editor.getCursorBufferPosition().row).toBe(0); + }); + }); describe('.selectPageUp/Down()', () => { it('selects one screen height of text up or down', () => { - editor.update({ autoHeight: false }) - const element = editor.getElement() - jasmine.attachToDOM(element) - element.style.height = element.component.getLineHeight() * 5 + 'px' - element.measureDimensions() + editor.update({ autoHeight: false }); + const element = editor.getElement(); + jasmine.attachToDOM(element); + element.style.height = element.component.getLineHeight() * 5 + 'px'; + element.measureDimensions(); - expect(editor.getCursorBufferPosition().row).toBe(0) + expect(editor.getCursorBufferPosition().row).toBe(0); - editor.selectPageDown() - expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [5, 0]]]) + editor.selectPageDown(); + expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [5, 0]]]); - editor.selectPageDown() - expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [10, 0]]]) + editor.selectPageDown(); + expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [10, 0]]]); - editor.selectPageDown() - expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [12, 2]]]) + editor.selectPageDown(); + expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [12, 2]]]); - editor.moveToBottom() - editor.selectPageUp() - expect(editor.getSelectedBufferRanges()).toEqual([[[7, 0], [12, 2]]]) + editor.moveToBottom(); + editor.selectPageUp(); + expect(editor.getSelectedBufferRanges()).toEqual([[[7, 0], [12, 2]]]); - editor.selectPageUp() - expect(editor.getSelectedBufferRanges()).toEqual([[[2, 0], [12, 2]]]) + editor.selectPageUp(); + expect(editor.getSelectedBufferRanges()).toEqual([[[2, 0], [12, 2]]]); - editor.selectPageUp() - expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [12, 2]]]) - }) - }) + editor.selectPageUp(); + expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [12, 2]]]); + }); + }); describe('::scrollToScreenPosition(position, [options])', () => { it('triggers ::onDidRequestAutoscroll with the logical coordinates along with the options', () => { - const scrollSpy = jasmine.createSpy('::onDidRequestAutoscroll') - editor.onDidRequestAutoscroll(scrollSpy) + const scrollSpy = jasmine.createSpy('::onDidRequestAutoscroll'); + editor.onDidRequestAutoscroll(scrollSpy); - editor.scrollToScreenPosition([8, 20]) - editor.scrollToScreenPosition([8, 20], { center: true }) - editor.scrollToScreenPosition([8, 20], { center: false, reversed: true }) + editor.scrollToScreenPosition([8, 20]); + editor.scrollToScreenPosition([8, 20], { center: true }); + editor.scrollToScreenPosition([8, 20], { center: false, reversed: true }); expect(scrollSpy).toHaveBeenCalledWith({ screenRange: [[8, 20], [8, 20]], options: {} - }) + }); expect(scrollSpy).toHaveBeenCalledWith({ screenRange: [[8, 20], [8, 20]], options: { center: true } - }) + }); expect(scrollSpy).toHaveBeenCalledWith({ screenRange: [[8, 20], [8, 20]], options: { center: false, reversed: true } - }) - }) - }) + }); + }); + }); describe('scroll past end', () => { it('returns false by default but can be customized', () => { - expect(editor.getScrollPastEnd()).toBe(false) - editor.update({ scrollPastEnd: true }) - expect(editor.getScrollPastEnd()).toBe(true) - editor.update({ scrollPastEnd: false }) - expect(editor.getScrollPastEnd()).toBe(false) - }) + expect(editor.getScrollPastEnd()).toBe(false); + editor.update({ scrollPastEnd: true }); + expect(editor.getScrollPastEnd()).toBe(true); + editor.update({ scrollPastEnd: false }); + expect(editor.getScrollPastEnd()).toBe(false); + }); it('always returns false when autoHeight is on', () => { - editor.update({ autoHeight: true, scrollPastEnd: true }) - expect(editor.getScrollPastEnd()).toBe(false) - editor.update({ autoHeight: false }) - expect(editor.getScrollPastEnd()).toBe(true) - }) - }) + editor.update({ autoHeight: true, scrollPastEnd: true }); + expect(editor.getScrollPastEnd()).toBe(false); + editor.update({ autoHeight: false }); + expect(editor.getScrollPastEnd()).toBe(true); + }); + }); describe('auto height', () => { it('returns true by default but can be customized', () => { - editor = new TextEditor() - expect(editor.getAutoHeight()).toBe(true) - editor.update({ autoHeight: false }) - expect(editor.getAutoHeight()).toBe(false) - editor.update({ autoHeight: true }) - expect(editor.getAutoHeight()).toBe(true) - editor.destroy() - }) - }) + editor = new TextEditor(); + expect(editor.getAutoHeight()).toBe(true); + editor.update({ autoHeight: false }); + expect(editor.getAutoHeight()).toBe(false); + editor.update({ autoHeight: true }); + expect(editor.getAutoHeight()).toBe(true); + editor.destroy(); + }); + }); describe('auto width', () => { it('returns false by default but can be customized', () => { - expect(editor.getAutoWidth()).toBe(false) - editor.update({ autoWidth: true }) - expect(editor.getAutoWidth()).toBe(true) - editor.update({ autoWidth: false }) - expect(editor.getAutoWidth()).toBe(false) - }) - }) + expect(editor.getAutoWidth()).toBe(false); + editor.update({ autoWidth: true }); + expect(editor.getAutoWidth()).toBe(true); + editor.update({ autoWidth: false }); + expect(editor.getAutoWidth()).toBe(false); + }); + }); describe('.get/setPlaceholderText()', () => { it('can be created with placeholderText', () => { const newEditor = new TextEditor({ mini: true, placeholderText: 'yep' - }) - expect(newEditor.getPlaceholderText()).toBe('yep') - }) + }); + expect(newEditor.getPlaceholderText()).toBe('yep'); + }); it('models placeholderText and emits an event when changed', () => { - let handler - editor.onDidChangePlaceholderText((handler = jasmine.createSpy())) + let handler; + editor.onDidChangePlaceholderText((handler = jasmine.createSpy())); - expect(editor.getPlaceholderText()).toBeUndefined() + expect(editor.getPlaceholderText()).toBeUndefined(); - editor.setPlaceholderText('OK') - expect(handler).toHaveBeenCalledWith('OK') - expect(editor.getPlaceholderText()).toBe('OK') - }) - }) + editor.setPlaceholderText('OK'); + expect(handler).toHaveBeenCalledWith('OK'); + expect(editor.getPlaceholderText()).toBe('OK'); + }); + }); describe('gutters', () => { describe('the TextEditor constructor', () => { it('creates a line-number gutter', () => { - expect(editor.getGutters().length).toBe(1) - const lineNumberGutter = editor.gutterWithName('line-number') - expect(lineNumberGutter.name).toBe('line-number') - expect(lineNumberGutter.priority).toBe(0) - }) - }) + expect(editor.getGutters().length).toBe(1); + const lineNumberGutter = editor.gutterWithName('line-number'); + expect(lineNumberGutter.name).toBe('line-number'); + expect(lineNumberGutter.priority).toBe(0); + }); + }); describe('::addGutter', () => { it('can add a gutter', () => { - expect(editor.getGutters().length).toBe(1) // line-number gutter + expect(editor.getGutters().length).toBe(1); // line-number gutter const options = { name: 'test-gutter', priority: 1 - } - const gutter = editor.addGutter(options) - expect(editor.getGutters().length).toBe(2) - expect(editor.getGutters()[1]).toBe(gutter) - expect(gutter.type).toBe('decorated') - }) + }; + const gutter = editor.addGutter(options); + expect(editor.getGutters().length).toBe(2); + expect(editor.getGutters()[1]).toBe(gutter); + expect(gutter.type).toBe('decorated'); + }); it('can add a custom line-number gutter', () => { - expect(editor.getGutters().length).toBe(1) + expect(editor.getGutters().length).toBe(1); const options = { name: 'another-gutter', priority: 2, type: 'line-number' - } - const gutter = editor.addGutter(options) - expect(editor.getGutters().length).toBe(2) - expect(editor.getGutters()[1]).toBe(gutter) - expect(gutter.type).toBe('line-number') - }) + }; + const gutter = editor.addGutter(options); + expect(editor.getGutters().length).toBe(2); + expect(editor.getGutters()[1]).toBe(gutter); + expect(gutter.type).toBe('line-number'); + }); it("does not allow a custom gutter with the 'line-number' name.", () => expect( editor.addGutter.bind(editor, { name: 'line-number' }) - ).toThrow()) - }) + ).toThrow()); + }); describe('::decorateMarker', () => { - let marker + let marker; - beforeEach(() => (marker = editor.markBufferRange([[1, 0], [1, 0]]))) + beforeEach(() => (marker = editor.markBufferRange([[1, 0], [1, 0]]))); it('reflects an added decoration when one of its custom gutters is decorated.', () => { - const gutter = editor.addGutter({ name: 'custom-gutter' }) + const gutter = editor.addGutter({ name: 'custom-gutter' }); const decoration = gutter.decorateMarker(marker, { class: 'custom-class' - }) + }); const gutterDecorations = editor.getDecorations({ type: 'gutter', gutterName: 'custom-gutter', class: 'custom-class' - }) - expect(gutterDecorations.length).toBe(1) - expect(gutterDecorations[0]).toBe(decoration) - }) + }); + expect(gutterDecorations.length).toBe(1); + expect(gutterDecorations[0]).toBe(decoration); + }); it('reflects an added decoration when its line-number gutter is decorated.', () => { const decoration = editor .gutterWithName('line-number') - .decorateMarker(marker, { class: 'test-class' }) + .decorateMarker(marker, { class: 'test-class' }); const gutterDecorations = editor.getDecorations({ type: 'line-number', gutterName: 'line-number', class: 'test-class' - }) - expect(gutterDecorations.length).toBe(1) - expect(gutterDecorations[0]).toBe(decoration) - }) - }) + }); + expect(gutterDecorations.length).toBe(1); + expect(gutterDecorations[0]).toBe(decoration); + }); + }); describe('::observeGutters', () => { - let payloads, callback + let payloads, callback; beforeEach(() => { - payloads = [] - callback = payload => payloads.push(payload) - }) + payloads = []; + callback = payload => payloads.push(payload); + }); it('calls the callback immediately with each existing gutter, and with each added gutter after that.', () => { - const lineNumberGutter = editor.gutterWithName('line-number') - editor.observeGutters(callback) - expect(payloads).toEqual([lineNumberGutter]) - const gutter1 = editor.addGutter({ name: 'test-gutter-1' }) - expect(payloads).toEqual([lineNumberGutter, gutter1]) - const gutter2 = editor.addGutter({ name: 'test-gutter-2' }) - expect(payloads).toEqual([lineNumberGutter, gutter1, gutter2]) - }) + const lineNumberGutter = editor.gutterWithName('line-number'); + editor.observeGutters(callback); + expect(payloads).toEqual([lineNumberGutter]); + const gutter1 = editor.addGutter({ name: 'test-gutter-1' }); + expect(payloads).toEqual([lineNumberGutter, gutter1]); + const gutter2 = editor.addGutter({ name: 'test-gutter-2' }); + expect(payloads).toEqual([lineNumberGutter, gutter1, gutter2]); + }); it('does not call the callback when a gutter is removed.', () => { - const gutter = editor.addGutter({ name: 'test-gutter' }) - editor.observeGutters(callback) - payloads = [] - gutter.destroy() - expect(payloads).toEqual([]) - }) + const gutter = editor.addGutter({ name: 'test-gutter' }); + editor.observeGutters(callback); + payloads = []; + gutter.destroy(); + expect(payloads).toEqual([]); + }); it('does not call the callback after the subscription has been disposed.', () => { - const subscription = editor.observeGutters(callback) - payloads = [] - subscription.dispose() - editor.addGutter({ name: 'test-gutter' }) - expect(payloads).toEqual([]) - }) - }) + const subscription = editor.observeGutters(callback); + payloads = []; + subscription.dispose(); + editor.addGutter({ name: 'test-gutter' }); + expect(payloads).toEqual([]); + }); + }); describe('::onDidAddGutter', () => { - let payloads, callback + let payloads, callback; beforeEach(() => { - payloads = [] - callback = payload => payloads.push(payload) - }) + payloads = []; + callback = payload => payloads.push(payload); + }); it('calls the callback with each newly-added gutter, but not with existing gutters.', () => { - editor.onDidAddGutter(callback) - expect(payloads).toEqual([]) - const gutter = editor.addGutter({ name: 'test-gutter' }) - expect(payloads).toEqual([gutter]) - }) + editor.onDidAddGutter(callback); + expect(payloads).toEqual([]); + const gutter = editor.addGutter({ name: 'test-gutter' }); + expect(payloads).toEqual([gutter]); + }); it('does not call the callback after the subscription has been disposed.', () => { - const subscription = editor.onDidAddGutter(callback) - payloads = [] - subscription.dispose() - editor.addGutter({ name: 'test-gutter' }) - expect(payloads).toEqual([]) - }) - }) + const subscription = editor.onDidAddGutter(callback); + payloads = []; + subscription.dispose(); + editor.addGutter({ name: 'test-gutter' }); + expect(payloads).toEqual([]); + }); + }); describe('::onDidRemoveGutter', () => { - let payloads, callback + let payloads, callback; beforeEach(() => { - payloads = [] - callback = payload => payloads.push(payload) - }) + payloads = []; + callback = payload => payloads.push(payload); + }); it('calls the callback when a gutter is removed.', () => { - const gutter = editor.addGutter({ name: 'test-gutter' }) - editor.onDidRemoveGutter(callback) - expect(payloads).toEqual([]) - gutter.destroy() - expect(payloads).toEqual(['test-gutter']) - }) + const gutter = editor.addGutter({ name: 'test-gutter' }); + editor.onDidRemoveGutter(callback); + expect(payloads).toEqual([]); + gutter.destroy(); + expect(payloads).toEqual(['test-gutter']); + }); it('does not call the callback after the subscription has been disposed.', () => { - const gutter = editor.addGutter({ name: 'test-gutter' }) - const subscription = editor.onDidRemoveGutter(callback) - subscription.dispose() - gutter.destroy() - expect(payloads).toEqual([]) - }) - }) - }) + const gutter = editor.addGutter({ name: 'test-gutter' }); + const subscription = editor.onDidRemoveGutter(callback); + subscription.dispose(); + gutter.destroy(); + expect(payloads).toEqual([]); + }); + }); + }); describe('decorations', () => { describe('::decorateMarker', () => { it('includes the decoration in the object returned from ::decorationsStateForScreenRowRange', () => { - const marker = editor.markBufferRange([[2, 4], [6, 8]]) + const marker = editor.markBufferRange([[2, 4], [6, 8]]); const decoration = editor.decorateMarker(marker, { type: 'highlight', class: 'foo' - }) + }); expect( editor.decorationsStateForScreenRowRange(0, 5)[decoration.id] ).toEqual({ @@ -7843,40 +7888,40 @@ describe('TextEditor', () => { screenRange: marker.getScreenRange(), bufferRange: marker.getBufferRange(), rangeIsReversed: false - }) - }) + }); + }); it("does not throw errors after the marker's containing layer is destroyed", () => { - const layer = editor.addMarkerLayer() - layer.markBufferRange([[2, 4], [6, 8]]) + const layer = editor.addMarkerLayer(); + layer.markBufferRange([[2, 4], [6, 8]]); - layer.destroy() - editor.decorationsStateForScreenRowRange(0, 5) - }) - }) + layer.destroy(); + editor.decorationsStateForScreenRowRange(0, 5); + }); + }); describe('::decorateMarkerLayer', () => { it('based on the markers in the layer, includes multiple decoration objects with the same properties and different ranges in the object returned from ::decorationsStateForScreenRowRange', () => { - const layer1 = editor.getBuffer().addMarkerLayer() - const marker1 = layer1.markRange([[2, 4], [6, 8]]) - const marker2 = layer1.markRange([[11, 0], [11, 12]]) - const layer2 = editor.getBuffer().addMarkerLayer() - const marker3 = layer2.markRange([[8, 0], [9, 0]]) + const layer1 = editor.getBuffer().addMarkerLayer(); + const marker1 = layer1.markRange([[2, 4], [6, 8]]); + const marker2 = layer1.markRange([[11, 0], [11, 12]]); + const layer2 = editor.getBuffer().addMarkerLayer(); + const marker3 = layer2.markRange([[8, 0], [9, 0]]); const layer1Decoration1 = editor.decorateMarkerLayer(layer1, { type: 'highlight', class: 'foo' - }) + }); const layer1Decoration2 = editor.decorateMarkerLayer(layer1, { type: 'highlight', class: 'bar' - }) + }); const layer2Decoration = editor.decorateMarkerLayer(layer2, { type: 'highlight', class: 'baz' - }) + }); - let decorationState = editor.decorationsStateForScreenRowRange(0, 13) + let decorationState = editor.decorationsStateForScreenRowRange(0, 13); expect( decorationState[`${layer1Decoration1.id}-${marker1.id}`] @@ -7885,7 +7930,7 @@ describe('TextEditor', () => { screenRange: marker1.getRange(), bufferRange: marker1.getRange(), rangeIsReversed: false - }) + }); expect( decorationState[`${layer1Decoration1.id}-${marker2.id}`] ).toEqual({ @@ -7893,7 +7938,7 @@ describe('TextEditor', () => { screenRange: marker2.getRange(), bufferRange: marker2.getRange(), rangeIsReversed: false - }) + }); expect( decorationState[`${layer1Decoration2.id}-${marker1.id}`] ).toEqual({ @@ -7901,7 +7946,7 @@ describe('TextEditor', () => { screenRange: marker1.getRange(), bufferRange: marker1.getRange(), rangeIsReversed: false - }) + }); expect( decorationState[`${layer1Decoration2.id}-${marker2.id}`] ).toEqual({ @@ -7909,7 +7954,7 @@ describe('TextEditor', () => { screenRange: marker2.getRange(), bufferRange: marker2.getRange(), rangeIsReversed: false - }) + }); expect(decorationState[`${layer2Decoration.id}-${marker3.id}`]).toEqual( { properties: { type: 'highlight', class: 'baz' }, @@ -7917,17 +7962,17 @@ describe('TextEditor', () => { bufferRange: marker3.getRange(), rangeIsReversed: false } - ) + ); - layer1Decoration1.destroy() + layer1Decoration1.destroy(); - decorationState = editor.decorationsStateForScreenRowRange(0, 12) + decorationState = editor.decorationsStateForScreenRowRange(0, 12); expect( decorationState[`${layer1Decoration1.id}-${marker1.id}`] - ).toBeUndefined() + ).toBeUndefined(); expect( decorationState[`${layer1Decoration1.id}-${marker2.id}`] - ).toBeUndefined() + ).toBeUndefined(); expect( decorationState[`${layer1Decoration2.id}-${marker1.id}`] ).toEqual({ @@ -7935,7 +7980,7 @@ describe('TextEditor', () => { screenRange: marker1.getRange(), bufferRange: marker1.getRange(), rangeIsReversed: false - }) + }); expect( decorationState[`${layer1Decoration2.id}-${marker2.id}`] ).toEqual({ @@ -7943,7 +7988,7 @@ describe('TextEditor', () => { screenRange: marker2.getRange(), bufferRange: marker2.getRange(), rangeIsReversed: false - }) + }); expect(decorationState[`${layer2Decoration.id}-${marker3.id}`]).toEqual( { properties: { type: 'highlight', class: 'baz' }, @@ -7951,13 +7996,13 @@ describe('TextEditor', () => { bufferRange: marker3.getRange(), rangeIsReversed: false } - ) + ); layer1Decoration2.setPropertiesForMarker(marker1, { type: 'highlight', class: 'quux' - }) - decorationState = editor.decorationsStateForScreenRowRange(0, 12) + }); + decorationState = editor.decorationsStateForScreenRowRange(0, 12); expect( decorationState[`${layer1Decoration2.id}-${marker1.id}`] ).toEqual({ @@ -7965,10 +8010,10 @@ describe('TextEditor', () => { screenRange: marker1.getRange(), bufferRange: marker1.getRange(), rangeIsReversed: false - }) + }); - layer1Decoration2.setPropertiesForMarker(marker1, null) - decorationState = editor.decorationsStateForScreenRowRange(0, 12) + layer1Decoration2.setPropertiesForMarker(marker1, null); + decorationState = editor.decorationsStateForScreenRowRange(0, 12); expect( decorationState[`${layer1Decoration2.id}-${marker1.id}`] ).toEqual({ @@ -7976,36 +8021,36 @@ describe('TextEditor', () => { screenRange: marker1.getRange(), bufferRange: marker1.getRange(), rangeIsReversed: false - }) - }) - }) - }) + }); + }); + }); + }); describe('invisibles', () => { beforeEach(() => { - editor.update({ showInvisibles: true }) - }) + editor.update({ showInvisibles: true }); + }); it('substitutes invisible characters according to the given rules', () => { - const previousLineText = editor.lineTextForScreenRow(0) - editor.update({ invisibles: { eol: '?' } }) - expect(editor.lineTextForScreenRow(0)).not.toBe(previousLineText) - expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(true) - expect(editor.getInvisibles()).toEqual({ eol: '?' }) - }) + const previousLineText = editor.lineTextForScreenRow(0); + editor.update({ invisibles: { eol: '?' } }); + expect(editor.lineTextForScreenRow(0)).not.toBe(previousLineText); + expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(true); + expect(editor.getInvisibles()).toEqual({ eol: '?' }); + }); it('does not use invisibles if showInvisibles is set to false', () => { - editor.update({ invisibles: { eol: '?' } }) - expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(true) + editor.update({ invisibles: { eol: '?' } }); + expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(true); - editor.update({ showInvisibles: false }) - expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(false) - }) - }) + editor.update({ showInvisibles: false }); + expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(false); + }); + }); describe('indent guides', () => { it('shows indent guides when `editor.showIndentGuide` is set to true and the editor is not mini', () => { - editor.update({ showIndentGuide: false }) + editor.update({ showIndentGuide: false }); expect(editor.tokensForScreenRow(1).slice(0, 3)).toEqual([ { text: ' ', @@ -8016,9 +8061,9 @@ describe('TextEditor', () => { scopes: ['syntax--source syntax--js', 'syntax--storage syntax--type'] }, { text: ' sort ', scopes: ['syntax--source syntax--js'] } - ]) + ]); - editor.update({ showIndentGuide: true }) + editor.update({ showIndentGuide: true }); expect(editor.tokensForScreenRow(1).slice(0, 3)).toEqual([ { text: ' ', @@ -8032,9 +8077,9 @@ describe('TextEditor', () => { scopes: ['syntax--source syntax--js', 'syntax--storage syntax--type'] }, { text: ' sort ', scopes: ['syntax--source syntax--js'] } - ]) + ]); - editor.setMini(true) + editor.setMini(true); expect(editor.tokensForScreenRow(1).slice(0, 3)).toEqual([ { text: ' ', @@ -8045,9 +8090,9 @@ describe('TextEditor', () => { scopes: ['syntax--source syntax--js', 'syntax--storage syntax--type'] }, { text: ' sort ', scopes: ['syntax--source syntax--js'] } - ]) - }) - }) + ]); + }); + }); describe('softWrapAtPreferredLineLength', () => { it('soft wraps the editor at the preferred line length unless the editor is narrower or the editor is mini', () => { @@ -8056,111 +8101,111 @@ describe('TextEditor', () => { softWrapped: true, softWrapAtPreferredLineLength: true, preferredLineLength: 20 - }) + }); - expect(editor.lineTextForScreenRow(0)).toBe('var quicksort = ') + expect(editor.lineTextForScreenRow(0)).toBe('var quicksort = '); - editor.update({ editorWidthInChars: 10 }) - expect(editor.lineTextForScreenRow(0)).toBe('var ') + editor.update({ editorWidthInChars: 10 }); + expect(editor.lineTextForScreenRow(0)).toBe('var '); - editor.update({ mini: true }) + editor.update({ mini: true }); expect(editor.lineTextForScreenRow(0)).toBe( 'var quicksort = function () {' - ) - }) - }) + ); + }); + }); describe('softWrapHangingIndentLength', () => { it('controls how much extra indentation is applied to soft-wrapped lines', () => { - editor.setText('123456789') + editor.setText('123456789'); editor.update({ editorWidthInChars: 8, softWrapped: true, softWrapHangingIndentLength: 2 - }) - expect(editor.lineTextForScreenRow(1)).toEqual(' 9') + }); + expect(editor.lineTextForScreenRow(1)).toEqual(' 9'); - editor.update({ softWrapHangingIndentLength: 4 }) - expect(editor.lineTextForScreenRow(1)).toEqual(' 9') - }) - }) + editor.update({ softWrapHangingIndentLength: 4 }); + expect(editor.lineTextForScreenRow(1)).toEqual(' 9'); + }); + }); describe('::getElement', () => { it('returns an element', () => - expect(editor.getElement() instanceof HTMLElement).toBe(true)) - }) + expect(editor.getElement() instanceof HTMLElement).toBe(true)); + }); describe('setMaxScreenLineLength', () => { it('sets the maximum line length in the editor before soft wrapping is forced', () => { - expect(editor.getSoftWrapColumn()).toBe(500) + expect(editor.getSoftWrapColumn()).toBe(500); editor.update({ maxScreenLineLength: 1500 - }) - expect(editor.getSoftWrapColumn()).toBe(1500) - }) - }) -}) + }); + expect(editor.getSoftWrapColumn()).toBe(1500); + }); + }); +}); describe('TextEditor', () => { - let editor + let editor; afterEach(() => { - editor.destroy() - }) + editor.destroy(); + }); describe('.scopeDescriptorForBufferPosition(position)', () => { it('returns a default scope descriptor when no language mode is assigned', () => { - editor = new TextEditor({ buffer: new TextBuffer() }) - const scopeDescriptor = editor.scopeDescriptorForBufferPosition([0, 0]) - expect(scopeDescriptor.getScopesArray()).toEqual(['text']) - }) - }) + editor = new TextEditor({ buffer: new TextBuffer() }); + const scopeDescriptor = editor.scopeDescriptorForBufferPosition([0, 0]); + expect(scopeDescriptor.getScopesArray()).toEqual(['text']); + }); + }); describe('.syntaxTreeScopeDescriptorForBufferPosition(position)', () => { it('returns the result of scopeDescriptorForBufferPosition() when textmate language mode is used', async () => { - atom.config.set('core.useTreeSitterParsers', false) - editor = await atom.workspace.open('sample.js', { autoIndent: false }) - await atom.packages.activatePackage('language-javascript') + atom.config.set('core.useTreeSitterParsers', false); + editor = await atom.workspace.open('sample.js', { autoIndent: false }); + await atom.packages.activatePackage('language-javascript'); - let buffer = editor.getBuffer() + let buffer = editor.getBuffer(); let languageMode = new TextMateLanguageMode({ buffer, grammar: atom.grammars.grammarForScopeName('source.js') - }) + }); - buffer.setLanguageMode(languageMode) + buffer.setLanguageMode(languageMode); - languageMode.startTokenizing() + languageMode.startTokenizing(); while (languageMode.firstInvalidRow() != null) { - advanceClock() + advanceClock(); } const syntaxTreeeScopeDescriptor = editor.syntaxTreeScopeDescriptorForBufferPosition( [4, 17] - ) + ); expect(syntaxTreeeScopeDescriptor.getScopesArray()).toEqual([ 'source.js', 'support.variable.property.js' - ]) - }) + ]); + }); it('returns the result of syntaxTreeScopeDescriptorForBufferPosition() when tree-sitter language mode is used', async () => { - editor = await atom.workspace.open('sample.js', { autoIndent: false }) - await atom.packages.activatePackage('language-javascript') + editor = await atom.workspace.open('sample.js', { autoIndent: false }); + await atom.packages.activatePackage('language-javascript'); - let buffer = editor.getBuffer() + let buffer = editor.getBuffer(); buffer.setLanguageMode( new TreeSitterLanguageMode({ buffer, grammar: atom.grammars.grammarForScopeName('source.js') }) - ) + ); const syntaxTreeeScopeDescriptor = editor.syntaxTreeScopeDescriptorForBufferPosition( [4, 17] - ) + ); expect(syntaxTreeeScopeDescriptor.getScopesArray()).toEqual([ 'source.js', 'program', @@ -8177,644 +8222,658 @@ describe('TextEditor', () => { 'binary_expression', 'member_expression', 'property_identifier' - ]) - }) - }) + ]); + }); + }); describe('.shouldPromptToSave()', () => { beforeEach(async () => { - editor = await atom.workspace.open('sample.js') - jasmine.unspy(editor, 'shouldPromptToSave') - spyOn(atom.stateStore, 'isConnected').andReturn(true) - }) + editor = await atom.workspace.open('sample.js'); + jasmine.unspy(editor, 'shouldPromptToSave'); + spyOn(atom.stateStore, 'isConnected').andReturn(true); + }); it('returns true when buffer has unsaved changes', () => { - expect(editor.shouldPromptToSave()).toBeFalsy() - editor.setText('changed') - expect(editor.shouldPromptToSave()).toBeTruthy() - }) + expect(editor.shouldPromptToSave()).toBeFalsy(); + editor.setText('changed'); + expect(editor.shouldPromptToSave()).toBeTruthy(); + }); it("returns false when an editor's buffer is in use by more than one buffer", async () => { - editor.setText('changed') + editor.setText('changed'); - atom.workspace.getActivePane().splitRight() + atom.workspace.getActivePane().splitRight(); const editor2 = await atom.workspace.open('sample.js', { autoIndent: false - }) - expect(editor.shouldPromptToSave()).toBeFalsy() + }); + expect(editor.shouldPromptToSave()).toBeFalsy(); - editor2.destroy() - expect(editor.shouldPromptToSave()).toBeTruthy() - }) + editor2.destroy(); + expect(editor.shouldPromptToSave()).toBeTruthy(); + }); it('returns true when the window is closing if the file has changed on disk', async () => { - jasmine.useRealClock() + jasmine.useRealClock(); - editor.setText('initial stuff') - await editor.saveAs(temp.openSync('test-file').path) + editor.setText('initial stuff'); + await editor.saveAs(temp.openSync('test-file').path); - editor.setText('other stuff') - fs.writeFileSync(editor.getPath(), 'new stuff') + editor.setText('other stuff'); + fs.writeFileSync(editor.getPath(), 'new stuff'); expect( editor.shouldPromptToSave({ windowCloseRequested: true, projectHasPaths: true }) - ).toBeFalsy() + ).toBeFalsy(); - await new Promise(resolve => editor.onDidConflict(resolve)) + await new Promise(resolve => editor.onDidConflict(resolve)); expect( editor.shouldPromptToSave({ windowCloseRequested: true, projectHasPaths: true }) - ).toBeTruthy() - }) + ).toBeTruthy(); + }); it('returns false when the window is closing and the project has one or more directory paths', () => { - editor.setText('changed') + editor.setText('changed'); expect( editor.shouldPromptToSave({ windowCloseRequested: true, projectHasPaths: true }) - ).toBeFalsy() - }) + ).toBeFalsy(); + }); it('returns false when the window is closing and the project has no directory paths', () => { - editor.setText('changed') + editor.setText('changed'); expect( editor.shouldPromptToSave({ windowCloseRequested: true, projectHasPaths: false }) - ).toBeTruthy() - }) - }) + ).toBeTruthy(); + }); + }); describe('.toggleLineCommentsInSelection()', () => { beforeEach(async () => { - await atom.packages.activatePackage('language-javascript') - editor = await atom.workspace.open('sample.js') - }) + await atom.packages.activatePackage('language-javascript'); + editor = await atom.workspace.open('sample.js'); + }); it('toggles comments on the selected lines', () => { - editor.setSelectedBufferRange([[4, 5], [7, 5]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[4, 5], [7, 5]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(4)).toBe( ' // while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' // current = items.shift();' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' // current < pivot ? left.push(current) : right.push(current);' - ) - expect(editor.lineTextForBufferRow(7)).toBe(' // }') - expect(editor.getSelectedBufferRange()).toEqual([[4, 8], [7, 8]]) + ); + expect(editor.lineTextForBufferRow(7)).toBe(' // }'); + expect(editor.getSelectedBufferRange()).toEqual([[4, 8], [7, 8]]); - editor.toggleLineCommentsInSelection() + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(4)).toBe( ' while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' current = items.shift();' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' current < pivot ? left.push(current) : right.push(current);' - ) - expect(editor.lineTextForBufferRow(7)).toBe(' }') - }) + ); + expect(editor.lineTextForBufferRow(7)).toBe(' }'); + }); it('does not comment the last line of a non-empty selection if it ends at column 0', () => { - editor.setSelectedBufferRange([[4, 5], [7, 0]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[4, 5], [7, 0]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(4)).toBe( ' // while(items.length > 0) {' - ) + ); expect(editor.lineTextForBufferRow(5)).toBe( ' // current = items.shift();' - ) + ); expect(editor.lineTextForBufferRow(6)).toBe( ' // current < pivot ? left.push(current) : right.push(current);' - ) - expect(editor.lineTextForBufferRow(7)).toBe(' }') - }) + ); + expect(editor.lineTextForBufferRow(7)).toBe(' }'); + }); it('uncomments lines if all lines match the comment regex', () => { - editor.setSelectedBufferRange([[0, 0], [0, 1]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[0, 0], [0, 1]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(0)).toBe( '// var quicksort = function () {' - ) + ); - editor.setSelectedBufferRange([[0, 0], [2, Infinity]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[0, 0], [2, Infinity]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(0)).toBe( '// // var quicksort = function () {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( '// var sort = function(items) {' - ) + ); expect(editor.lineTextForBufferRow(2)).toBe( '// if (items.length <= 1) return items;' - ) + ); - editor.setSelectedBufferRange([[0, 0], [2, Infinity]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[0, 0], [2, Infinity]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(0)).toBe( '// var quicksort = function () {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( ' var sort = function(items) {' - ) + ); expect(editor.lineTextForBufferRow(2)).toBe( ' if (items.length <= 1) return items;' - ) + ); - editor.setSelectedBufferRange([[0, 0], [0, Infinity]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[0, 0], [0, Infinity]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(0)).toBe( 'var quicksort = function () {' - ) - }) + ); + }); it('uncomments commented lines separated by an empty line', () => { - editor.setSelectedBufferRange([[0, 0], [1, Infinity]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[0, 0], [1, Infinity]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(0)).toBe( '// var quicksort = function () {' - ) + ); expect(editor.lineTextForBufferRow(1)).toBe( '// var sort = function(items) {' - ) + ); - editor.getBuffer().insert([0, Infinity], '\n') + editor.getBuffer().insert([0, Infinity], '\n'); - editor.setSelectedBufferRange([[0, 0], [2, Infinity]]) - editor.toggleLineCommentsInSelection() + editor.setSelectedBufferRange([[0, 0], [2, Infinity]]); + editor.toggleLineCommentsInSelection(); expect(editor.lineTextForBufferRow(0)).toBe( 'var quicksort = function () {' - ) - expect(editor.lineTextForBufferRow(1)).toBe('') + ); + expect(editor.lineTextForBufferRow(1)).toBe(''); expect(editor.lineTextForBufferRow(2)).toBe( ' var sort = function(items) {' - ) - }) + ); + }); it('preserves selection emptiness', () => { - editor.setCursorBufferPosition([4, 0]) - editor.toggleLineCommentsInSelection() - expect(editor.getLastSelection().isEmpty()).toBeTruthy() - }) + editor.setCursorBufferPosition([4, 0]); + editor.toggleLineCommentsInSelection(); + expect(editor.getLastSelection().isEmpty()).toBeTruthy(); + }); it('does not explode if the current language mode has no comment regex', () => { const editor = new TextEditor({ buffer: new TextBuffer({ text: 'hello' }) - }) - editor.setSelectedBufferRange([[0, 0], [0, 5]]) - editor.toggleLineCommentsInSelection() - expect(editor.lineTextForBufferRow(0)).toBe('hello') - }) + }); + editor.setSelectedBufferRange([[0, 0], [0, 5]]); + editor.toggleLineCommentsInSelection(); + expect(editor.lineTextForBufferRow(0)).toBe('hello'); + }); it('does nothing for empty lines and null grammar', () => { - atom.grammars.assignLanguageMode(editor, null) - editor.setCursorBufferPosition([10, 0]) - editor.toggleLineCommentsInSelection() - expect(editor.lineTextForBufferRow(10)).toBe('') - }) + atom.grammars.assignLanguageMode(editor, null); + editor.setCursorBufferPosition([10, 0]); + editor.toggleLineCommentsInSelection(); + expect(editor.lineTextForBufferRow(10)).toBe(''); + }); it('uncomments when the line lacks the trailing whitespace in the comment regex', () => { - editor.setCursorBufferPosition([10, 0]) - editor.toggleLineCommentsInSelection() + editor.setCursorBufferPosition([10, 0]); + editor.toggleLineCommentsInSelection(); - expect(editor.lineTextForBufferRow(10)).toBe('// ') - expect(editor.getSelectedBufferRange()).toEqual([[10, 3], [10, 3]]) - editor.backspace() - expect(editor.lineTextForBufferRow(10)).toBe('//') + expect(editor.lineTextForBufferRow(10)).toBe('// '); + expect(editor.getSelectedBufferRange()).toEqual([[10, 3], [10, 3]]); + editor.backspace(); + expect(editor.lineTextForBufferRow(10)).toBe('//'); - editor.toggleLineCommentsInSelection() - expect(editor.lineTextForBufferRow(10)).toBe('') - expect(editor.getSelectedBufferRange()).toEqual([[10, 0], [10, 0]]) - }) + editor.toggleLineCommentsInSelection(); + expect(editor.lineTextForBufferRow(10)).toBe(''); + expect(editor.getSelectedBufferRange()).toEqual([[10, 0], [10, 0]]); + }); it('uncomments when the line has leading whitespace', () => { - editor.setCursorBufferPosition([10, 0]) - editor.toggleLineCommentsInSelection() + editor.setCursorBufferPosition([10, 0]); + editor.toggleLineCommentsInSelection(); - expect(editor.lineTextForBufferRow(10)).toBe('// ') - editor.moveToBeginningOfLine() - editor.insertText(' ') - editor.setSelectedBufferRange([[10, 0], [10, 0]]) - editor.toggleLineCommentsInSelection() - expect(editor.lineTextForBufferRow(10)).toBe(' ') - }) - }) + expect(editor.lineTextForBufferRow(10)).toBe('// '); + editor.moveToBeginningOfLine(); + editor.insertText(' '); + editor.setSelectedBufferRange([[10, 0], [10, 0]]); + editor.toggleLineCommentsInSelection(); + expect(editor.lineTextForBufferRow(10)).toBe(' '); + }); + }); describe('.toggleLineCommentsForBufferRows', () => { describe('xml', () => { beforeEach(async () => { - await atom.packages.activatePackage('language-xml') - editor = await atom.workspace.open('test.xml') - editor.setText('') - }) + await atom.packages.activatePackage('language-xml'); + editor = await atom.workspace.open('test.xml'); + editor.setText(''); + }); it('removes the leading whitespace from the comment end pattern match when uncommenting lines', () => { - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe('test') - }) + editor.toggleLineCommentsForBufferRows(0, 0); + expect(editor.lineTextForBufferRow(0)).toBe('test'); + }); it('does not select the new delimiters', () => { - editor.setText('') - let delimLength = ''); + let delimLength = '' - } + }; const jsCommentStrings = { commentStartString: '//', commentEndString: undefined - } + }; expect(languageMode.commentStringsForPosition(new Point(0, 0))).toEqual( htmlCommentStrings - ) + ); expect(languageMode.commentStringsForPosition(new Point(1, 0))).toEqual( htmlCommentStrings - ) + ); expect(languageMode.commentStringsForPosition(new Point(2, 0))).toEqual( jsCommentStrings - ) + ); expect(languageMode.commentStringsForPosition(new Point(3, 0))).toEqual( jsCommentStrings - ) + ); expect(languageMode.commentStringsForPosition(new Point(4, 0))).toEqual( htmlCommentStrings - ) + ); expect(languageMode.commentStringsForPosition(new Point(5, 0))).toEqual( jsCommentStrings - ) + ); expect(languageMode.commentStringsForPosition(new Point(6, 0))).toEqual( htmlCommentStrings - ) - }) - }) + ); + }); + }); describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => { it('expands and contracts the selection based on the syntax tree', async () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { program: 'source' } - }) + }); buffer.setText(dedent` function a (b, c, d) { eee.f() g() } - `) + `); - buffer.setLanguageMode(new TreeSitterLanguageMode({ buffer, grammar })) + buffer.setLanguageMode(new TreeSitterLanguageMode({ buffer, grammar })); - editor.setCursorBufferPosition([1, 3]) - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('eee') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('eee.f') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('eee.f()') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}') - editor.selectLargerSyntaxNode() + editor.setCursorBufferPosition([1, 3]); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('eee'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('eee.f'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('eee.f()'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}'); + editor.selectLargerSyntaxNode(); expect(editor.getSelectedText()).toBe( 'function a (b, c, d) {\n eee.f()\n g()\n}' - ) + ); - editor.selectSmallerSyntaxNode() - expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}') - editor.selectSmallerSyntaxNode() - expect(editor.getSelectedText()).toBe('eee.f()') - editor.selectSmallerSyntaxNode() - expect(editor.getSelectedText()).toBe('eee.f') - editor.selectSmallerSyntaxNode() - expect(editor.getSelectedText()).toBe('eee') - editor.selectSmallerSyntaxNode() - expect(editor.getSelectedBufferRange()).toEqual([[1, 3], [1, 3]]) - }) + editor.selectSmallerSyntaxNode(); + expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}'); + editor.selectSmallerSyntaxNode(); + expect(editor.getSelectedText()).toBe('eee.f()'); + editor.selectSmallerSyntaxNode(); + expect(editor.getSelectedText()).toBe('eee.f'); + editor.selectSmallerSyntaxNode(); + expect(editor.getSelectedText()).toBe('eee'); + editor.selectSmallerSyntaxNode(); + expect(editor.getSelectedBufferRange()).toEqual([[1, 3], [1, 3]]); + }); it('handles injected languages', async () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { @@ -2153,7 +2161,7 @@ describe('TreeSitterLanguageMode', () => { }, injectionRegExp: 'javascript', injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT] - }) + }); const htmlGrammar = new TreeSitterGrammar( atom.grammars, @@ -2168,57 +2176,57 @@ describe('TreeSitterLanguageMode', () => { }, injectionRegExp: 'html' } - ) + ); - atom.grammars.addGrammar(htmlGrammar) + atom.grammars.addGrammar(htmlGrammar); - buffer.setText('a = html ` c${def()}e${f}g `') + buffer.setText('a = html ` c${def()}e${f}g `'); const languageMode = new TreeSitterLanguageMode({ buffer, grammar: jsGrammar, grammars: atom.grammars - }) - buffer.setLanguageMode(languageMode) + }); + buffer.setLanguageMode(languageMode); editor.setCursorBufferPosition({ row: 0, column: buffer.getText().indexOf('ef()') - }) - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('def') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('def()') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('${def()}') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('c${def()}e${f}g') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('c${def()}e${f}g') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe(' c${def()}e${f}g ') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('` c${def()}e${f}g `') - editor.selectLargerSyntaxNode() - expect(editor.getSelectedText()).toBe('html ` c${def()}e${f}g `') - }) - }) -}) + }); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('def'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('def()'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('${def()}'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('c${def()}e${f}g'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('c${def()}e${f}g'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe(' c${def()}e${f}g '); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('` c${def()}e${f}g `'); + editor.selectLargerSyntaxNode(); + expect(editor.getSelectedText()).toBe('html ` c${def()}e${f}g `'); + }); + }); +}); -function nextHighlightingUpdate (languageMode) { +function nextHighlightingUpdate(languageMode) { return new Promise(resolve => { const subscription = languageMode.onDidChangeHighlighting(() => { - subscription.dispose() - resolve() - }) - }) + subscription.dispose(); + resolve(); + }); + }); } -function getDisplayText (editor) { - return editor.displayLayer.getText() +function getDisplayText(editor) { + return editor.displayLayer.getText(); } -function expectTokensToEqual (editor, expectedTokenLines) { - const lastRow = editor.getLastScreenRow() +function expectTokensToEqual(editor, expectedTokenLines) { + const lastRow = editor.getLastScreenRow(); // Assert that the correct tokens are returned regardless of which row // the highlighting iterator starts on. @@ -2227,12 +2235,12 @@ function expectTokensToEqual (editor, expectedTokenLines) { // iteration, so that the first iteration tests that the cache has been // correctly invalidated by any changes. if (startRow > 0) { - editor.displayLayer.clearSpatialIndex() + editor.displayLayer.clearSpatialIndex(); } - editor.displayLayer.getScreenLines(startRow, Infinity) + editor.displayLayer.getScreenLines(startRow, Infinity); - const tokenLines = [] + const tokenLines = []; for (let row = startRow; row <= lastRow; row++) { tokenLines[row] = editor .tokensForScreenRow(row) @@ -2244,49 +2252,49 @@ function expectTokensToEqual (editor, expectedTokenLines) { .map(className => className.replace('syntax--', '')) .join(' ') ) - })) + })); } for (let row = startRow; row <= lastRow; row++) { - const tokenLine = tokenLines[row] - const expectedTokenLine = expectedTokenLines[row] + const tokenLine = tokenLines[row]; + const expectedTokenLine = expectedTokenLines[row]; - expect(tokenLine.length).toEqual(expectedTokenLine.length) + expect(tokenLine.length).toEqual(expectedTokenLine.length); for (let i = 0; i < tokenLine.length; i++) { expect(tokenLine[i]).toEqual( expectedTokenLine[i], `Token ${i}, startRow: ${startRow}` - ) + ); } } } // Fully populate the screen line cache again so that cache invalidation // due to subsequent edits can be tested. - editor.displayLayer.getScreenLines(0, Infinity) + editor.displayLayer.getScreenLines(0, Infinity); } const HTML_TEMPLATE_LITERAL_INJECTION_POINT = { type: 'call_expression', - language (node) { + language(node) { if ( node.lastChild.type === 'template_string' && node.firstChild.type === 'identifier' ) { - return node.firstChild.text + return node.firstChild.text; } }, - content (node) { - return node.lastChild + content(node) { + return node.lastChild; } -} +}; const SCRIPT_TAG_INJECTION_POINT = { type: 'raw_element', - language () { - return 'javascript' + language() { + return 'javascript'; }, - content (node) { - return node.child(1) + content(node) { + return node.child(1); } -} +}; diff --git a/spec/update-process-env-spec.js b/spec/update-process-env-spec.js index 5185f0198..7eef2235a 100644 --- a/spec/update-process-env-spec.js +++ b/spec/update-process-env-spec.js @@ -1,50 +1,50 @@ /** @babel */ -import path from 'path' -import childProcess from 'child_process' +import path from 'path'; +import childProcess from 'child_process'; import { updateProcessEnv, shouldGetEnvFromShell -} from '../src/update-process-env' -import dedent from 'dedent' -import mockSpawn from 'mock-spawn' -const temp = require('temp').track() +} from '../src/update-process-env'; +import dedent from 'dedent'; +import mockSpawn from 'mock-spawn'; +const temp = require('temp').track(); -describe('updateProcessEnv(launchEnv)', function () { - let originalProcessEnv, originalProcessPlatform, originalSpawn, spawn +describe('updateProcessEnv(launchEnv)', function() { + let originalProcessEnv, originalProcessPlatform, originalSpawn, spawn; - beforeEach(function () { - originalSpawn = childProcess.spawn - spawn = mockSpawn() - childProcess.spawn = spawn - originalProcessEnv = process.env - originalProcessPlatform = process.platform - process.env = {} - }) + beforeEach(function() { + originalSpawn = childProcess.spawn; + spawn = mockSpawn(); + childProcess.spawn = spawn; + originalProcessEnv = process.env; + originalProcessPlatform = process.platform; + process.env = {}; + }); - afterEach(function () { + afterEach(function() { if (originalSpawn) { - childProcess.spawn = originalSpawn + childProcess.spawn = originalSpawn; } - process.env = originalProcessEnv - process.platform = originalProcessPlatform + process.env = originalProcessEnv; + process.platform = originalProcessPlatform; try { - temp.cleanupSync() + temp.cleanupSync(); } catch (e) { // Do nothing } - }) + }); - describe('when the launch environment appears to come from a shell', function () { - it('updates process.env to match the launch environment because PWD is set', async function () { + describe('when the launch environment appears to come from a shell', function() { + it('updates process.env to match the launch environment because PWD is set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - } + }; - const initialProcessEnv = process.env + const initialProcessEnv = process.env; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', @@ -52,7 +52,7 @@ describe('updateProcessEnv(launchEnv)', function () { TERM: 'xterm-something', KEY1: 'value1', KEY2: 'value2' - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', @@ -62,30 +62,30 @@ describe('updateProcessEnv(launchEnv)', function () { NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); // See #11302. On Windows, `process.env` is a magic object that offers // case-insensitive environment variable matching, so we cannot replace it // with another object. - expect(process.env).toBe(initialProcessEnv) - }) + expect(process.env).toBe(initialProcessEnv); + }); - it('updates process.env to match the launch environment because PROMPT is set', async function () { + it('updates process.env to match the launch environment because PROMPT is set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - } + }; - const initialProcessEnv = process.env + const initialProcessEnv = process.env; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PROMPT: '$P$G', KEY1: 'value1', KEY2: 'value2' - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PROMPT: '$P$G', @@ -94,23 +94,23 @@ describe('updateProcessEnv(launchEnv)', function () { NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); // See #11302. On Windows, `process.env` is a magic object that offers // case-insensitive environment variable matching, so we cannot replace it // with another object. - expect(process.env).toBe(initialProcessEnv) - }) + expect(process.env).toBe(initialProcessEnv); + }); - it('updates process.env to match the launch environment because PSModulePath is set', async function () { + it('updates process.env to match the launch environment because PSModulePath is set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - } + }; - const initialProcessEnv = process.env + const initialProcessEnv = process.env; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', @@ -118,7 +118,7 @@ describe('updateProcessEnv(launchEnv)', function () { 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\', KEY1: 'value1', KEY2: 'value2' - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PSModulePath: @@ -128,70 +128,70 @@ describe('updateProcessEnv(launchEnv)', function () { NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); // See #11302. On Windows, `process.env` is a magic object that offers // case-insensitive environment variable matching, so we cannot replace it // with another object. - expect(process.env).toBe(initialProcessEnv) - }) + expect(process.env).toBe(initialProcessEnv); + }); - it('allows ATOM_HOME to be overwritten only if the new value is a valid path', async function () { - let newAtomHomePath = temp.mkdirSync('atom-home') + it('allows ATOM_HOME to be overwritten only if the new value is a valid path', async function() { + let newAtomHomePath = temp.mkdirSync('atom-home'); process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - } + }; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir' - }) + }); expect(process.env).toEqual({ PWD: '/the/dir', ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', ATOM_HOME: path.join(newAtomHomePath, 'non-existent') - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', ATOM_HOME: newAtomHomePath - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: newAtomHomePath - }) - }) + }); + }); - it('allows ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT to be preserved if set', async function () { + it('allows ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT to be preserved if set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - } + }; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', @@ -199,40 +199,40 @@ describe('updateProcessEnv(launchEnv)', function () { NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); await updateProcessEnv({ PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) + }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - }) - }) + }); + }); - it('allows an existing env variable to be updated', async function () { + it('allows an existing env variable to be updated', async function() { process.env = { WILL_BE_UPDATED: 'old-value', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' - } + }; - await updateProcessEnv(process.env) - expect(process.env).toEqual(process.env) + await updateProcessEnv(process.env); + expect(process.env).toEqual(process.env); let updatedEnv = { ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', @@ -241,20 +241,20 @@ describe('updateProcessEnv(launchEnv)', function () { NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home', PWD: '/the/dir' - } + }; - await updateProcessEnv(updatedEnv) - expect(process.env).toEqual(updatedEnv) - }) - }) + await updateProcessEnv(updatedEnv); + expect(process.env).toEqual(updatedEnv); + }); + }); - describe('when the launch environment does not come from a shell', function () { - describe('on macOS', function () { - it("updates process.env to match the environment in the user's login shell", async function () { - if (process.platform === 'win32') return // TestsThatFailOnWin32 + describe('when the launch environment does not come from a shell', function() { + describe('on macOS', function() { + it("updates process.env to match the environment in the user's login shell", async function() { + if (process.platform === 'win32') return; // TestsThatFailOnWin32 - process.platform = 'darwin' - process.env.SHELL = '/my/custom/bash' + process.platform = 'darwin'; + process.env.SHELL = '/my/custom/bash'; spawn.setDefault( spawn.simple( 0, @@ -264,28 +264,28 @@ describe('updateProcessEnv(launchEnv)', function () { PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path ` ) - ) - await updateProcessEnv(process.env) - expect(spawn.calls.length).toBe(1) - expect(spawn.calls[0].command).toBe('/my/custom/bash') - expect(spawn.calls[0].args).toEqual(['-ilc', 'command env']) + ); + await updateProcessEnv(process.env); + expect(spawn.calls.length).toBe(1); + expect(spawn.calls[0].command).toBe('/my/custom/bash'); + expect(spawn.calls[0].args).toEqual(['-ilc', 'command env']); expect(process.env).toEqual({ FOO: 'BAR=BAZ=QUUX', TERM: 'xterm-something', PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path' - }) + }); // Doesn't error - await updateProcessEnv(null) - }) - }) + await updateProcessEnv(null); + }); + }); - describe('on linux', function () { - it("updates process.env to match the environment in the user's login shell", async function () { - if (process.platform === 'win32') return // TestsThatFailOnWin32 + describe('on linux', function() { + it("updates process.env to match the environment in the user's login shell", async function() { + if (process.platform === 'win32') return; // TestsThatFailOnWin32 - process.platform = 'linux' - process.env.SHELL = '/my/custom/bash' + process.platform = 'linux'; + process.env.SHELL = '/my/custom/bash'; spawn.setDefault( spawn.simple( 0, @@ -295,96 +295,100 @@ describe('updateProcessEnv(launchEnv)', function () { PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path ` ) - ) - await updateProcessEnv(process.env) - expect(spawn.calls.length).toBe(1) - expect(spawn.calls[0].command).toBe('/my/custom/bash') - expect(spawn.calls[0].args).toEqual(['-ilc', 'command env']) + ); + await updateProcessEnv(process.env); + expect(spawn.calls.length).toBe(1); + expect(spawn.calls[0].command).toBe('/my/custom/bash'); + expect(spawn.calls[0].args).toEqual(['-ilc', 'command env']); expect(process.env).toEqual({ FOO: 'BAR=BAZ=QUUX', TERM: 'xterm-something', PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path' - }) + }); // Doesn't error - await updateProcessEnv(null) - }) - }) + await updateProcessEnv(null); + }); + }); - describe('on windows', function () { - it('does not update process.env', async function () { - process.platform = 'win32' - spyOn(childProcess, 'spawn') - process.env = { FOO: 'bar' } + describe('on windows', function() { + it('does not update process.env', async function() { + process.platform = 'win32'; + spyOn(childProcess, 'spawn'); + process.env = { FOO: 'bar' }; - await updateProcessEnv(process.env) - expect(childProcess.spawn).not.toHaveBeenCalled() - expect(process.env).toEqual({ FOO: 'bar' }) - }) - }) + await updateProcessEnv(process.env); + expect(childProcess.spawn).not.toHaveBeenCalled(); + expect(process.env).toEqual({ FOO: 'bar' }); + }); + }); - describe('shouldGetEnvFromShell()', function () { - it('indicates when the environment should be fetched from the shell', function () { - if (process.platform === 'win32') return // TestsThatFailOnWin32 + describe('shouldGetEnvFromShell()', function() { + it('indicates when the environment should be fetched from the shell', function() { + if (process.platform === 'win32') return; // TestsThatFailOnWin32 - process.platform = 'darwin' - expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true) - expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe(true) - expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true) + process.platform = 'darwin'; + expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true); + expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe( + true + ); + expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/bash' })).toBe( true - ) - expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true) + ); + expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/zsh' })).toBe( true - ) - expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true) + ); + expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe( true - ) - process.platform = 'linux' - expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true) - expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe(true) - expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true) + ); + process.platform = 'linux'; + expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true); + expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe( + true + ); + expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/bash' })).toBe( true - ) - expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true) + ); + expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/zsh' })).toBe( true - ) - expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true) + ); + expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe( true - ) - }) + ); + }); - it('returns false when the environment indicates that Atom was launched from a shell', function () { - process.platform = 'darwin' + it('returns false when the environment indicates that Atom was launched from a shell', function() { + process.platform = 'darwin'; expect( shouldGetEnvFromShell({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', SHELL: '/bin/sh' }) - ).toBe(false) - process.platform = 'linux' + ).toBe(false); + process.platform = 'linux'; expect( shouldGetEnvFromShell({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', SHELL: '/bin/sh' }) - ).toBe(false) - }) + ).toBe(false); + }); - it('returns false when the shell is undefined or empty', function () { - process.platform = 'darwin' - expect(shouldGetEnvFromShell(undefined)).toBe(false) - expect(shouldGetEnvFromShell({})).toBe(false) + it('returns false when the shell is undefined or empty', function() { + process.platform = 'darwin'; + expect(shouldGetEnvFromShell(undefined)).toBe(false); + expect(shouldGetEnvFromShell({})).toBe(false); - process.platform = 'linux' - expect(shouldGetEnvFromShell(undefined)).toBe(false) - expect(shouldGetEnvFromShell({})).toBe(false) - }) - }) - }) -}) + process.platform = 'linux'; + expect(shouldGetEnvFromShell(undefined)).toBe(false); + expect(shouldGetEnvFromShell({})).toBe(false); + }); + }); + }); +}); diff --git a/spec/uri-handler-registry-spec.js b/spec/uri-handler-registry-spec.js index 6cdc170a9..390cfd903 100644 --- a/spec/uri-handler-registry-spec.js +++ b/spec/uri-handler-registry-spec.js @@ -1,47 +1,47 @@ /** @babel */ -import url from 'url' +import url from 'url'; -import URIHandlerRegistry from '../src/uri-handler-registry' +import URIHandlerRegistry from '../src/uri-handler-registry'; describe('URIHandlerRegistry', () => { - let registry + let registry; beforeEach(() => { - registry = new URIHandlerRegistry(5) - }) + registry = new URIHandlerRegistry(5); + }); it('handles URIs on a per-host basis', async () => { - const testPackageSpy = jasmine.createSpy() - const otherPackageSpy = jasmine.createSpy() - registry.registerHostHandler('test-package', testPackageSpy) - registry.registerHostHandler('other-package', otherPackageSpy) + const testPackageSpy = jasmine.createSpy(); + const otherPackageSpy = jasmine.createSpy(); + registry.registerHostHandler('test-package', testPackageSpy); + registry.registerHostHandler('other-package', otherPackageSpy); - await registry.handleURI('atom://yet-another-package/path') - expect(testPackageSpy).not.toHaveBeenCalled() - expect(otherPackageSpy).not.toHaveBeenCalled() + await registry.handleURI('atom://yet-another-package/path'); + expect(testPackageSpy).not.toHaveBeenCalled(); + expect(otherPackageSpy).not.toHaveBeenCalled(); - await registry.handleURI('atom://test-package/path') + await registry.handleURI('atom://test-package/path'); expect(testPackageSpy).toHaveBeenCalledWith( url.parse('atom://test-package/path', true), 'atom://test-package/path' - ) - expect(otherPackageSpy).not.toHaveBeenCalled() + ); + expect(otherPackageSpy).not.toHaveBeenCalled(); - await registry.handleURI('atom://other-package/path') + await registry.handleURI('atom://other-package/path'); expect(otherPackageSpy).toHaveBeenCalledWith( url.parse('atom://other-package/path', true), 'atom://other-package/path' - ) - }) + ); + }); it('keeps track of the most recent URIs', async () => { - const spy1 = jasmine.createSpy() - const spy2 = jasmine.createSpy() - const changeSpy = jasmine.createSpy() - registry.registerHostHandler('one', spy1) - registry.registerHostHandler('two', spy2) - registry.onHistoryChange(changeSpy) + const spy1 = jasmine.createSpy(); + const spy2 = jasmine.createSpy(); + const changeSpy = jasmine.createSpy(); + registry.registerHostHandler('one', spy1); + registry.registerHostHandler('two', spy2); + registry.onHistoryChange(changeSpy); const uris = [ 'atom://one/something?asdf=1', @@ -49,13 +49,13 @@ describe('URIHandlerRegistry', () => { 'atom://two/other/stuff', 'atom://one/more/thing', 'atom://two/more/stuff' - ] + ]; for (const u of uris) { - await registry.handleURI(u) + await registry.handleURI(u); } - expect(changeSpy.callCount).toBe(5) + expect(changeSpy.callCount).toBe(5); expect(registry.getRecentlyHandledURIs()).toEqual( uris .map((u, idx) => { @@ -64,18 +64,18 @@ describe('URIHandlerRegistry', () => { uri: u, handled: !u.match(/fake/), host: url.parse(u).host - } + }; }) .reverse() - ) + ); - await registry.handleURI('atom://another/url') - expect(changeSpy.callCount).toBe(6) - const history = registry.getRecentlyHandledURIs() - expect(history.length).toBe(5) - expect(history[0].uri).toBe('atom://another/url') - expect(history[4].uri).toBe(uris[1]) - }) + await registry.handleURI('atom://another/url'); + expect(changeSpy.callCount).toBe(6); + const history = registry.getRecentlyHandledURIs(); + expect(history.length).toBe(5); + expect(history[0].uri).toBe('atom://another/url'); + expect(history[4].uri).toBe(uris[1]); + }); it('refuses to handle bad URLs', async () => { const invalidUris = [ @@ -83,18 +83,18 @@ describe('URIHandlerRegistry', () => { 'atom:8080://package/path', 'user:pass@atom://package/path', 'smth://package/path' - ] + ]; - let numErrors = 0 + let numErrors = 0; for (const uri of invalidUris) { try { - await registry.handleURI(uri) - expect(uri).toBe('throwing an error') + await registry.handleURI(uri); + expect(uri).toBe('throwing an error'); } catch (ex) { - numErrors++ + numErrors++; } } - expect(numErrors).toBe(invalidUris.length) - }) -}) + expect(numErrors).toBe(invalidUris.length); + }); +}); diff --git a/spec/view-registry-spec.js b/spec/view-registry-spec.js index b38ffa5f3..192d1b347 100644 --- a/spec/view-registry-spec.js +++ b/spec/view-registry-spec.js @@ -4,52 +4,52 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const ViewRegistry = require('../src/view-registry') +const ViewRegistry = require('../src/view-registry'); describe('ViewRegistry', () => { - let registry = null + let registry = null; beforeEach(() => { - registry = new ViewRegistry() - }) + registry = new ViewRegistry(); + }); afterEach(() => { - registry.clearDocumentRequests() - }) + registry.clearDocumentRequests(); + }); describe('::getView(object)', () => { describe('when passed a DOM node', () => it('returns the given DOM node', () => { - const node = document.createElement('div') - expect(registry.getView(node)).toBe(node) - })) + const node = document.createElement('div'); + expect(registry.getView(node)).toBe(node); + })); describe('when passed an object with an element property', () => it("returns the element property if it's an instance of HTMLElement", () => { class TestComponent { - constructor () { - this.element = document.createElement('div') + constructor() { + this.element = document.createElement('div'); } } - const component = new TestComponent() - expect(registry.getView(component)).toBe(component.element) - })) + const component = new TestComponent(); + expect(registry.getView(component)).toBe(component.element); + })); describe('when passed an object with a getElement function', () => it("returns the return value of getElement if it's an instance of HTMLElement", () => { class TestComponent { - getElement () { + getElement() { if (this.myElement == null) { - this.myElement = document.createElement('div') + this.myElement = document.createElement('div'); } - return this.myElement + return this.myElement; } } - const component = new TestComponent() - expect(registry.getView(component)).toBe(component.myElement) - })) + const component = new TestComponent(); + expect(registry.getView(component)).toBe(component.myElement); + })); describe('when passed a model object', () => { describe("when a view provider is registered matching the object's constructor", () => @@ -59,126 +59,126 @@ describe('ViewRegistry', () => { class TestModelSubclass extends TestModel {} class TestView { - initialize (model) { - this.model = model - return this + initialize(model) { + this.model = model; + return this; } } - const model = new TestModel() + const model = new TestModel(); registry.addViewProvider(TestModel, model => new TestView().initialize(model) - ) + ); - const view = registry.getView(model) - expect(view instanceof TestView).toBe(true) - expect(view.model).toBe(model) + const view = registry.getView(model); + expect(view instanceof TestView).toBe(true); + expect(view.model).toBe(model); - const subclassModel = new TestModelSubclass() - const view2 = registry.getView(subclassModel) - expect(view2 instanceof TestView).toBe(true) - expect(view2.model).toBe(subclassModel) - })) + const subclassModel = new TestModelSubclass(); + const view2 = registry.getView(subclassModel); + expect(view2 instanceof TestView).toBe(true); + expect(view2.model).toBe(subclassModel); + })); describe('when a view provider is registered generically, and works with the object', () => it('constructs a view element and assigns the model on it', () => { registry.addViewProvider(model => { if (model.a === 'b') { - const element = document.createElement('div') - element.className = 'test-element' - return element + const element = document.createElement('div'); + element.className = 'test-element'; + return element; } - }) + }); - const view = registry.getView({ a: 'b' }) - expect(view.className).toBe('test-element') + const view = registry.getView({ a: 'b' }); + expect(view.className).toBe('test-element'); - expect(() => registry.getView({ a: 'c' })).toThrow() - })) + expect(() => registry.getView({ a: 'c' })).toThrow(); + })); describe("when no view provider is registered for the object's constructor", () => it('throws an exception', () => { - expect(() => registry.getView({})).toThrow() - })) - }) - }) + expect(() => registry.getView({})).toThrow(); + })); + }); + }); describe('::addViewProvider(providerSpec)', () => it('returns a disposable that can be used to remove the provider', () => { class TestModel {} class TestView { - initialize (model) { - this.model = model - return this + initialize(model) { + this.model = model; + return this; } } const disposable = registry.addViewProvider(TestModel, model => new TestView().initialize(model) - ) + ); - expect(registry.getView(new TestModel()) instanceof TestView).toBe(true) - disposable.dispose() - expect(() => registry.getView(new TestModel())).toThrow() - })) + expect(registry.getView(new TestModel()) instanceof TestView).toBe(true); + disposable.dispose(); + expect(() => registry.getView(new TestModel())).toThrow(); + })); describe('::updateDocument(fn) and ::readDocument(fn)', () => { - let frameRequests = null + let frameRequests = null; beforeEach(() => { - frameRequests = [] + frameRequests = []; spyOn(window, 'requestAnimationFrame').andCallFake(fn => frameRequests.push(fn) - ) - }) + ); + }); it('performs all pending writes before all pending reads on the next animation frame', () => { - let events = [] + let events = []; - registry.updateDocument(() => events.push('write 1')) - registry.readDocument(() => events.push('read 1')) - registry.readDocument(() => events.push('read 2')) - registry.updateDocument(() => events.push('write 2')) + registry.updateDocument(() => events.push('write 1')); + registry.readDocument(() => events.push('read 1')); + registry.readDocument(() => events.push('read 2')); + registry.updateDocument(() => events.push('write 2')); - expect(events).toEqual([]) + expect(events).toEqual([]); - expect(frameRequests.length).toBe(1) - frameRequests[0]() - expect(events).toEqual(['write 1', 'write 2', 'read 1', 'read 2']) + expect(frameRequests.length).toBe(1); + frameRequests[0](); + expect(events).toEqual(['write 1', 'write 2', 'read 1', 'read 2']); - frameRequests = [] - events = [] - const disposable = registry.updateDocument(() => events.push('write 3')) - registry.updateDocument(() => events.push('write 4')) - registry.readDocument(() => events.push('read 3')) + frameRequests = []; + events = []; + const disposable = registry.updateDocument(() => events.push('write 3')); + registry.updateDocument(() => events.push('write 4')); + registry.readDocument(() => events.push('read 3')); - disposable.dispose() + disposable.dispose(); - expect(frameRequests.length).toBe(1) - frameRequests[0]() - expect(events).toEqual(['write 4', 'read 3']) - }) + expect(frameRequests.length).toBe(1); + frameRequests[0](); + expect(events).toEqual(['write 4', 'read 3']); + }); it('performs writes requested from read callbacks in the same animation frame', () => { - spyOn(window, 'setInterval').andCallFake(fakeSetInterval) - spyOn(window, 'clearInterval').andCallFake(fakeClearInterval) - const events = [] + spyOn(window, 'setInterval').andCallFake(fakeSetInterval); + spyOn(window, 'clearInterval').andCallFake(fakeClearInterval); + const events = []; - registry.updateDocument(() => events.push('write 1')) + registry.updateDocument(() => events.push('write 1')); registry.readDocument(() => { - registry.updateDocument(() => events.push('write from read 1')) - events.push('read 1') - }) + registry.updateDocument(() => events.push('write from read 1')); + events.push('read 1'); + }); registry.readDocument(() => { - registry.updateDocument(() => events.push('write from read 2')) - events.push('read 2') - }) - registry.updateDocument(() => events.push('write 2')) + registry.updateDocument(() => events.push('write from read 2')); + events.push('read 2'); + }); + registry.updateDocument(() => events.push('write 2')); - expect(frameRequests.length).toBe(1) - frameRequests[0]() - expect(frameRequests.length).toBe(1) + expect(frameRequests.length).toBe(1); + frameRequests[0](); + expect(frameRequests.length).toBe(1); expect(events).toEqual([ 'write 1', @@ -187,28 +187,28 @@ describe('ViewRegistry', () => { 'read 2', 'write from read 1', 'write from read 2' - ]) - }) - }) + ]); + }); + }); describe('::getNextUpdatePromise()', () => it('returns a promise that resolves at the end of the next update cycle', () => { - let updateCalled = false - let readCalled = false + let updateCalled = false; + let readCalled = false; waitsFor('getNextUpdatePromise to resolve', done => { registry.getNextUpdatePromise().then(() => { - expect(updateCalled).toBe(true) - expect(readCalled).toBe(true) - done() - }) + expect(updateCalled).toBe(true); + expect(readCalled).toBe(true); + done(); + }); registry.updateDocument(() => { - updateCalled = true - }) + updateCalled = true; + }); registry.readDocument(() => { - readCalled = true - }) - }) - })) -}) + readCalled = true; + }); + }); + })); +}); diff --git a/spec/window-event-handler-spec.js b/spec/window-event-handler-spec.js index 6f840f3bf..62f0fe3b7 100644 --- a/spec/window-event-handler-spec.js +++ b/spec/window-event-handler-spec.js @@ -1,169 +1,169 @@ -const KeymapManager = require('atom-keymap') -const WindowEventHandler = require('../src/window-event-handler') +const KeymapManager = require('atom-keymap'); +const WindowEventHandler = require('../src/window-event-handler'); describe('WindowEventHandler', () => { - let windowEventHandler + let windowEventHandler; beforeEach(() => { - atom.uninstallWindowEventHandler() - spyOn(atom, 'hide') - const initialPath = atom.project.getPaths()[0] + atom.uninstallWindowEventHandler(); + spyOn(atom, 'hide'); + const initialPath = atom.project.getPaths()[0]; spyOn(atom, 'getLoadSettings').andCallFake(() => { - const loadSettings = atom.getLoadSettings.originalValue.call(atom) - loadSettings.initialPath = initialPath - return loadSettings - }) - atom.project.destroy() + const loadSettings = atom.getLoadSettings.originalValue.call(atom); + loadSettings.initialPath = initialPath; + return loadSettings; + }); + atom.project.destroy(); windowEventHandler = new WindowEventHandler({ atomEnvironment: atom, applicationDelegate: atom.applicationDelegate - }) - windowEventHandler.initialize(window, document) - }) + }); + windowEventHandler.initialize(window, document); + }); afterEach(() => { - windowEventHandler.unsubscribe() - atom.installWindowEventHandler() - }) + windowEventHandler.unsubscribe(); + atom.installWindowEventHandler(); + }); describe('when the window is loaded', () => it("doesn't have .is-blurred on the body tag", () => { if (process.platform === 'win32') { - return + return; } // Win32TestFailures - can not steal focus - expect(document.body.className).not.toMatch('is-blurred') - })) + expect(document.body.className).not.toMatch('is-blurred'); + })); describe('when the window is blurred', () => { - beforeEach(() => window.dispatchEvent(new CustomEvent('blur'))) + beforeEach(() => window.dispatchEvent(new CustomEvent('blur'))); - afterEach(() => document.body.classList.remove('is-blurred')) + afterEach(() => document.body.classList.remove('is-blurred')); it('adds the .is-blurred class on the body', () => - expect(document.body.className).toMatch('is-blurred')) + expect(document.body.className).toMatch('is-blurred')); describe('when the window is focused again', () => it('removes the .is-blurred class from the body', () => { - window.dispatchEvent(new CustomEvent('focus')) - expect(document.body.className).not.toMatch('is-blurred') - })) - }) + window.dispatchEvent(new CustomEvent('focus')); + expect(document.body.className).not.toMatch('is-blurred'); + })); + }); describe('resize event', () => it('calls storeWindowDimensions', () => { - spyOn(atom, 'storeWindowDimensions') - window.dispatchEvent(new CustomEvent('resize')) - expect(atom.storeWindowDimensions).toHaveBeenCalled() - })) + spyOn(atom, 'storeWindowDimensions'); + window.dispatchEvent(new CustomEvent('resize')); + expect(atom.storeWindowDimensions).toHaveBeenCalled(); + })); describe('window:close event', () => it('closes the window', () => { - spyOn(atom, 'close') - window.dispatchEvent(new CustomEvent('window:close')) - expect(atom.close).toHaveBeenCalled() - })) + spyOn(atom, 'close'); + window.dispatchEvent(new CustomEvent('window:close')); + expect(atom.close).toHaveBeenCalled(); + })); describe('when a link is clicked', () => { it('opens the http/https links in an external application', () => { - const { shell } = require('electron') - spyOn(shell, 'openExternal') + const { shell } = require('electron'); + spyOn(shell, 'openExternal'); - const link = document.createElement('a') - const linkChild = document.createElement('span') - link.appendChild(linkChild) - link.href = 'http://github.com' - jasmine.attachToDOM(link) + const link = document.createElement('a'); + const linkChild = document.createElement('span'); + link.appendChild(linkChild); + link.href = 'http://github.com'; + jasmine.attachToDOM(link); const fakeEvent = { target: linkChild, currentTarget: link, preventDefault: () => {} - } + }; - windowEventHandler.handleLinkClick(fakeEvent) - expect(shell.openExternal).toHaveBeenCalled() - expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com') - shell.openExternal.reset() + windowEventHandler.handleLinkClick(fakeEvent); + expect(shell.openExternal).toHaveBeenCalled(); + expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com'); + shell.openExternal.reset(); - link.href = 'https://github.com' - windowEventHandler.handleLinkClick(fakeEvent) - expect(shell.openExternal).toHaveBeenCalled() - expect(shell.openExternal.argsForCall[0][0]).toBe('https://github.com') - shell.openExternal.reset() + link.href = 'https://github.com'; + windowEventHandler.handleLinkClick(fakeEvent); + expect(shell.openExternal).toHaveBeenCalled(); + expect(shell.openExternal.argsForCall[0][0]).toBe('https://github.com'); + shell.openExternal.reset(); - link.href = '' - windowEventHandler.handleLinkClick(fakeEvent) - expect(shell.openExternal).not.toHaveBeenCalled() - shell.openExternal.reset() + link.href = ''; + windowEventHandler.handleLinkClick(fakeEvent); + expect(shell.openExternal).not.toHaveBeenCalled(); + shell.openExternal.reset(); - link.href = '#scroll-me' - windowEventHandler.handleLinkClick(fakeEvent) - expect(shell.openExternal).not.toHaveBeenCalled() - }) + link.href = '#scroll-me'; + windowEventHandler.handleLinkClick(fakeEvent); + expect(shell.openExternal).not.toHaveBeenCalled(); + }); it('opens the "atom://" links with URL handler', () => { - const uriHandler = windowEventHandler.atomEnvironment.uriHandlerRegistry - expect(uriHandler).toBeDefined() - spyOn(uriHandler, 'handleURI') + const uriHandler = windowEventHandler.atomEnvironment.uriHandlerRegistry; + expect(uriHandler).toBeDefined(); + spyOn(uriHandler, 'handleURI'); - const link = document.createElement('a') - const linkChild = document.createElement('span') - link.appendChild(linkChild) - link.href = 'atom://github.com' - jasmine.attachToDOM(link) + const link = document.createElement('a'); + const linkChild = document.createElement('span'); + link.appendChild(linkChild); + link.href = 'atom://github.com'; + jasmine.attachToDOM(link); const fakeEvent = { target: linkChild, currentTarget: link, preventDefault: () => {} - } + }; - windowEventHandler.handleLinkClick(fakeEvent) - expect(uriHandler.handleURI).toHaveBeenCalled() - expect(uriHandler.handleURI.argsForCall[0][0]).toBe('atom://github.com') - }) - }) + windowEventHandler.handleLinkClick(fakeEvent); + expect(uriHandler.handleURI).toHaveBeenCalled(); + expect(uriHandler.handleURI.argsForCall[0][0]).toBe('atom://github.com'); + }); + }); describe('when a form is submitted', () => it("prevents the default so that the window's URL isn't changed", () => { - const form = document.createElement('form') - jasmine.attachToDOM(form) + const form = document.createElement('form'); + jasmine.attachToDOM(form); - let defaultPrevented = false - const event = new CustomEvent('submit', { bubbles: true }) + let defaultPrevented = false; + const event = new CustomEvent('submit', { bubbles: true }); event.preventDefault = () => { - defaultPrevented = true - } - form.dispatchEvent(event) - expect(defaultPrevented).toBe(true) - })) + defaultPrevented = true; + }; + form.dispatchEvent(event); + expect(defaultPrevented).toBe(true); + })); describe('core:focus-next and core:focus-previous', () => { describe('when there is no currently focused element', () => it('focuses the element with the lowest/highest tabindex', () => { - const wrapperDiv = document.createElement('div') + const wrapperDiv = document.createElement('div'); wrapperDiv.innerHTML = `
          - `.trim() - const elements = wrapperDiv.firstChild - jasmine.attachToDOM(elements) + `.trim(); + const elements = wrapperDiv.firstChild; + jasmine.attachToDOM(elements); elements.dispatchEvent( new CustomEvent('core:focus-next', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(1) + ); + expect(document.activeElement.tabIndex).toBe(1); - document.body.focus() + document.body.focus(); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(2) - })) + ); + expect(document.activeElement.tabIndex).toBe(2); + })); describe('when a tabindex is set on the currently focused element', () => it('focuses the element with the next highest/lowest tabindex, skipping disabled elements', () => { - const wrapperDiv = document.createElement('div') + const wrapperDiv = document.createElement('div'); wrapperDiv.innerHTML = `
          @@ -174,118 +174,118 @@ describe('WindowEventHandler', () => {
          - `.trim() - const elements = wrapperDiv.firstChild - jasmine.attachToDOM(elements) + `.trim(); + const elements = wrapperDiv.firstChild; + jasmine.attachToDOM(elements); - elements.querySelector('[tabindex="1"]').focus() + elements.querySelector('[tabindex="1"]').focus(); elements.dispatchEvent( new CustomEvent('core:focus-next', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(2) + ); + expect(document.activeElement.tabIndex).toBe(2); elements.dispatchEvent( new CustomEvent('core:focus-next', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(3) + ); + expect(document.activeElement.tabIndex).toBe(3); elements.dispatchEvent( new CustomEvent('core:focus-next', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(5) + ); + expect(document.activeElement.tabIndex).toBe(5); elements.dispatchEvent( new CustomEvent('core:focus-next', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(7) + ); + expect(document.activeElement.tabIndex).toBe(7); elements.dispatchEvent( new CustomEvent('core:focus-next', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(1) + ); + expect(document.activeElement.tabIndex).toBe(1); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(7) + ); + expect(document.activeElement.tabIndex).toBe(7); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(5) + ); + expect(document.activeElement.tabIndex).toBe(5); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(3) + ); + expect(document.activeElement.tabIndex).toBe(3); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(2) + ); + expect(document.activeElement.tabIndex).toBe(2); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(1) + ); + expect(document.activeElement.tabIndex).toBe(1); elements.dispatchEvent( new CustomEvent('core:focus-previous', { bubbles: true }) - ) - expect(document.activeElement.tabIndex).toBe(7) - })) - }) + ); + expect(document.activeElement.tabIndex).toBe(7); + })); + }); describe('when keydown events occur on the document', () => it('dispatches the event via the KeymapManager and CommandRegistry', () => { - const dispatchedCommands = [] - atom.commands.onWillDispatch(command => dispatchedCommands.push(command)) - atom.commands.add('*', { 'foo-command': () => {} }) - atom.keymaps.add('source-name', { '*': { x: 'foo-command' } }) + const dispatchedCommands = []; + atom.commands.onWillDispatch(command => dispatchedCommands.push(command)); + atom.commands.add('*', { 'foo-command': () => {} }); + atom.keymaps.add('source-name', { '*': { x: 'foo-command' } }); const event = KeymapManager.buildKeydownEvent('x', { target: document.createElement('div') - }) - document.dispatchEvent(event) + }); + document.dispatchEvent(event); - expect(dispatchedCommands.length).toBe(1) - expect(dispatchedCommands[0].type).toBe('foo-command') - })) + expect(dispatchedCommands.length).toBe(1); + expect(dispatchedCommands[0].type).toBe('foo-command'); + })); describe('native key bindings', () => it("correctly dispatches them to active elements with the '.native-key-bindings' class", () => { const webContentsSpy = jasmine.createSpyObj('webContents', [ 'copy', 'paste' - ]) + ]); spyOn(atom.applicationDelegate, 'getCurrentWindow').andReturn({ webContents: webContentsSpy, on: () => {} - }) + }); - const nativeKeyBindingsInput = document.createElement('input') - nativeKeyBindingsInput.classList.add('native-key-bindings') - jasmine.attachToDOM(nativeKeyBindingsInput) - nativeKeyBindingsInput.focus() + const nativeKeyBindingsInput = document.createElement('input'); + nativeKeyBindingsInput.classList.add('native-key-bindings'); + jasmine.attachToDOM(nativeKeyBindingsInput); + nativeKeyBindingsInput.focus(); - atom.dispatchApplicationMenuCommand('core:copy') - atom.dispatchApplicationMenuCommand('core:paste') + atom.dispatchApplicationMenuCommand('core:copy'); + atom.dispatchApplicationMenuCommand('core:paste'); - expect(webContentsSpy.copy).toHaveBeenCalled() - expect(webContentsSpy.paste).toHaveBeenCalled() + expect(webContentsSpy.copy).toHaveBeenCalled(); + expect(webContentsSpy.paste).toHaveBeenCalled(); - webContentsSpy.copy.reset() - webContentsSpy.paste.reset() + webContentsSpy.copy.reset(); + webContentsSpy.paste.reset(); - const normalInput = document.createElement('input') - jasmine.attachToDOM(normalInput) - normalInput.focus() + const normalInput = document.createElement('input'); + jasmine.attachToDOM(normalInput); + normalInput.focus(); - atom.dispatchApplicationMenuCommand('core:copy') - atom.dispatchApplicationMenuCommand('core:paste') + atom.dispatchApplicationMenuCommand('core:copy'); + atom.dispatchApplicationMenuCommand('core:paste'); - expect(webContentsSpy.copy).not.toHaveBeenCalled() - expect(webContentsSpy.paste).not.toHaveBeenCalled() - })) -}) + expect(webContentsSpy.copy).not.toHaveBeenCalled(); + expect(webContentsSpy.paste).not.toHaveBeenCalled(); + })); +}); diff --git a/spec/workspace-center-spec.js b/spec/workspace-center-spec.js index b5bf6ba91..eeed80b66 100644 --- a/spec/workspace-center-spec.js +++ b/spec/workspace-center-spec.js @@ -1,34 +1,34 @@ /** @babel */ -const TextEditor = require('../src/text-editor') +const TextEditor = require('../src/text-editor'); describe('WorkspaceCenter', () => { describe('.observeTextEditors()', () => { it('invokes the observer with current and future text editors', () => { - const workspaceCenter = atom.workspace.getCenter() - const pane = workspaceCenter.getActivePane() - const observed = [] + const workspaceCenter = atom.workspace.getCenter(); + const pane = workspaceCenter.getActivePane(); + const observed = []; - const editorAddedBeforeRegisteringObserver = new TextEditor() + const editorAddedBeforeRegisteringObserver = new TextEditor(); const nonEditorItemAddedBeforeRegisteringObserver = document.createElement( 'div' - ) - pane.activateItem(editorAddedBeforeRegisteringObserver) - pane.activateItem(nonEditorItemAddedBeforeRegisteringObserver) + ); + pane.activateItem(editorAddedBeforeRegisteringObserver); + pane.activateItem(nonEditorItemAddedBeforeRegisteringObserver); - workspaceCenter.observeTextEditors(editor => observed.push(editor)) + workspaceCenter.observeTextEditors(editor => observed.push(editor)); - const editorAddedAfterRegisteringObserver = new TextEditor() + const editorAddedAfterRegisteringObserver = new TextEditor(); const nonEditorItemAddedAfterRegisteringObserver = document.createElement( 'div' - ) - pane.activateItem(editorAddedAfterRegisteringObserver) - pane.activateItem(nonEditorItemAddedAfterRegisteringObserver) + ); + pane.activateItem(editorAddedAfterRegisteringObserver); + pane.activateItem(nonEditorItemAddedAfterRegisteringObserver); expect(observed).toEqual([ editorAddedBeforeRegisteringObserver, editorAddedAfterRegisteringObserver - ]) - }) - }) -}) + ]); + }); + }); +}); diff --git a/spec/workspace-element-spec.js b/spec/workspace-element-spec.js index 67ac6efa0..001f31936 100644 --- a/spec/workspace-element-spec.js +++ b/spec/workspace-element-spec.js @@ -1,49 +1,49 @@ /** @babel */ -const { ipcRenderer } = require('electron') -const etch = require('etch') -const path = require('path') -const temp = require('temp').track() -const { Disposable } = require('event-kit') +const { ipcRenderer } = require('electron'); +const etch = require('etch'); +const path = require('path'); +const temp = require('temp').track(); +const { Disposable } = require('event-kit'); -const getNextUpdatePromise = () => etch.getScheduler().nextUpdatePromise +const getNextUpdatePromise = () => etch.getScheduler().nextUpdatePromise; describe('WorkspaceElement', () => { afterEach(() => { try { - temp.cleanupSync() + temp.cleanupSync(); } catch (e) { // Do nothing } - }) + }); describe('when the workspace element is focused', () => { it('transfers focus to the active pane', () => { - const workspaceElement = atom.workspace.getElement() - jasmine.attachToDOM(workspaceElement) - const activePaneElement = atom.workspace.getActivePane().getElement() - document.body.focus() - expect(document.activeElement).not.toBe(activePaneElement) - workspaceElement.focus() - expect(document.activeElement).toBe(activePaneElement) - }) - }) + const workspaceElement = atom.workspace.getElement(); + jasmine.attachToDOM(workspaceElement); + const activePaneElement = atom.workspace.getActivePane().getElement(); + document.body.focus(); + expect(document.activeElement).not.toBe(activePaneElement); + workspaceElement.focus(); + expect(document.activeElement).toBe(activePaneElement); + }); + }); describe('when the active pane of an inactive pane container is focused', () => { it('changes the active pane container', () => { - const dock = atom.workspace.getLeftDock() - dock.show() - jasmine.attachToDOM(atom.workspace.getElement()) + const dock = atom.workspace.getLeftDock(); + dock.show(); + jasmine.attachToDOM(atom.workspace.getElement()); expect(atom.workspace.getActivePaneContainer()).toBe( atom.workspace.getCenter() - ) + ); dock .getActivePane() .getElement() - .focus() - expect(atom.workspace.getActivePaneContainer()).toBe(dock) - }) - }) + .focus(); + expect(atom.workspace.getActivePaneContainer()).toBe(dock); + }); + }); describe('finding the nearest visible pane in a specific direction', () => { let nearestPaneElement, @@ -59,16 +59,16 @@ describe('WorkspaceElement', () => { rightDockPane, bottomDockPane, workspace, - workspaceElement + workspaceElement; - beforeEach(function () { - atom.config.set('core.destroyEmptyPanes', false) + beforeEach(function() { + atom.config.set('core.destroyEmptyPanes', false); expect(document.hasFocus()).toBe( true, 'Document needs to be focused to run this test' - ) + ); - workspace = atom.workspace + workspace = atom.workspace; // Set up a workspace center with a grid of 9 panes, in the following // arrangement, where the numbers correspond to the variable names below. @@ -81,44 +81,44 @@ describe('WorkspaceElement', () => { // |7|8|9| // ------- - const container = workspace.getActivePaneContainer() - expect(container.getLocation()).toEqual('center') - expect(container.getPanes().length).toEqual(1) + const container = workspace.getActivePaneContainer(); + expect(container.getLocation()).toEqual('center'); + expect(container.getPanes().length).toEqual(1); - pane1 = container.getActivePane() - pane4 = pane1.splitDown() - pane7 = pane4.splitDown() + pane1 = container.getActivePane(); + pane4 = pane1.splitDown(); + pane7 = pane4.splitDown(); - pane2 = pane1.splitRight() - pane3 = pane2.splitRight() + pane2 = pane1.splitRight(); + pane3 = pane2.splitRight(); - pane5 = pane4.splitRight() - pane6 = pane5.splitRight() + pane5 = pane4.splitRight(); + pane6 = pane5.splitRight(); - pane8 = pane7.splitRight() - pane8.splitRight() + pane8 = pane7.splitRight(); + pane8.splitRight(); - const leftDock = workspace.getLeftDock() - const rightDock = workspace.getRightDock() - const bottomDock = workspace.getBottomDock() + const leftDock = workspace.getLeftDock(); + const rightDock = workspace.getRightDock(); + const bottomDock = workspace.getBottomDock(); - expect(leftDock.isVisible()).toBe(false) - expect(rightDock.isVisible()).toBe(false) - expect(bottomDock.isVisible()).toBe(false) + expect(leftDock.isVisible()).toBe(false); + expect(rightDock.isVisible()).toBe(false); + expect(bottomDock.isVisible()).toBe(false); - expect(leftDock.getPanes().length).toBe(1) - expect(rightDock.getPanes().length).toBe(1) - expect(bottomDock.getPanes().length).toBe(1) + expect(leftDock.getPanes().length).toBe(1); + expect(rightDock.getPanes().length).toBe(1); + expect(bottomDock.getPanes().length).toBe(1); - leftDockPane = leftDock.getPanes()[0] - rightDockPane = rightDock.getPanes()[0] - bottomDockPane = bottomDock.getPanes()[0] + leftDockPane = leftDock.getPanes()[0]; + rightDockPane = rightDock.getPanes()[0]; + bottomDockPane = bottomDock.getPanes()[0]; - workspaceElement = atom.workspace.getElement() - workspaceElement.style.height = '400px' - workspaceElement.style.width = '400px' - jasmine.attachToDOM(workspaceElement) - }) + workspaceElement = atom.workspace.getElement(); + workspaceElement.style.height = '400px'; + workspaceElement.style.width = '400px'; + jasmine.attachToDOM(workspaceElement); + }); describe('finding the nearest pane above', () => { describe('when there are multiple rows above the pane', () => { @@ -126,32 +126,32 @@ describe('WorkspaceElement', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'above', pane8 - ) - expect(nearestPaneElement).toBe(pane5.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(pane5.getElement()); + }); + }); describe('when there are no rows above the pane', () => { it('returns null', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'above', pane2 - ) - expect(nearestPaneElement).toBeUndefined() // TODO Expect toBeNull() - }) - }) + ); + expect(nearestPaneElement).toBeUndefined(); // TODO Expect toBeNull() + }); + }); describe('when the bottom dock contains the pane', () => { it('returns the pane in the adjacent row above', () => { - workspace.getBottomDock().show() + workspace.getBottomDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'above', bottomDockPane - ) - expect(nearestPaneElement).toBe(pane7.getElement()) - }) - }) - }) + ); + expect(nearestPaneElement).toBe(pane7.getElement()); + }); + }); + }); describe('finding the nearest pane below', () => { describe('when there are multiple rows below the pane', () => { @@ -159,34 +159,34 @@ describe('WorkspaceElement', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'below', pane2 - ) - expect(nearestPaneElement).toBe(pane5.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(pane5.getElement()); + }); + }); describe('when there are no rows below the pane', () => { it('returns null', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'below', pane8 - ) - expect(nearestPaneElement).toBeUndefined() // TODO Expect toBeNull() - }) - }) + ); + expect(nearestPaneElement).toBeUndefined(); // TODO Expect toBeNull() + }); + }); describe('when the bottom dock is visible', () => { describe("when the workspace center's bottommost row contains the pane", () => { it("returns the pane in the bottom dock's adjacent row below", () => { - workspace.getBottomDock().show() + workspace.getBottomDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'below', pane8 - ) - expect(nearestPaneElement).toBe(bottomDockPane.getElement()) - }) - }) - }) - }) + ); + expect(nearestPaneElement).toBe(bottomDockPane.getElement()); + }); + }); + }); + }); describe('finding the nearest pane to the left', () => { describe('when there are multiple columns to the left of the pane', () => { @@ -194,57 +194,57 @@ describe('WorkspaceElement', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'left', pane6 - ) - expect(nearestPaneElement).toBe(pane5.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(pane5.getElement()); + }); + }); describe('when there are no columns to the left of the pane', () => { it('returns null', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'left', pane4 - ) - expect(nearestPaneElement).toBeUndefined() // TODO Expect toBeNull() - }) - }) + ); + expect(nearestPaneElement).toBeUndefined(); // TODO Expect toBeNull() + }); + }); describe('when the right dock contains the pane', () => { it('returns the pane in the adjacent column to the left', () => { - workspace.getRightDock().show() + workspace.getRightDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'left', rightDockPane - ) - expect(nearestPaneElement).toBe(pane3.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(pane3.getElement()); + }); + }); describe('when the left dock is visible', () => { describe("when the workspace center's leftmost column contains the pane", () => { it("returns the pane in the left dock's adjacent column to the left", () => { - workspace.getLeftDock().show() + workspace.getLeftDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'left', pane4 - ) - expect(nearestPaneElement).toBe(leftDockPane.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(leftDockPane.getElement()); + }); + }); describe('when the bottom dock contains the pane', () => { it("returns the pane in the left dock's adjacent column to the left", () => { - workspace.getLeftDock().show() - workspace.getBottomDock().show() + workspace.getLeftDock().show(); + workspace.getBottomDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'left', bottomDockPane - ) - expect(nearestPaneElement).toBe(leftDockPane.getElement()) - }) - }) - }) - }) + ); + expect(nearestPaneElement).toBe(leftDockPane.getElement()); + }); + }); + }); + }); describe('finding the nearest pane to the right', () => { describe('when there are multiple columns to the right of the pane', () => { @@ -252,634 +252,640 @@ describe('WorkspaceElement', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'right', pane4 - ) - expect(nearestPaneElement).toBe(pane5.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(pane5.getElement()); + }); + }); describe('when there are no columns to the right of the pane', () => { it('returns null', () => { nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'right', pane6 - ) - expect(nearestPaneElement).toBeUndefined() // TODO Expect toBeNull() - }) - }) + ); + expect(nearestPaneElement).toBeUndefined(); // TODO Expect toBeNull() + }); + }); describe('when the left dock contains the pane', () => { it('returns the pane in the adjacent column to the right', () => { - workspace.getLeftDock().show() + workspace.getLeftDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'right', leftDockPane - ) - expect(nearestPaneElement).toBe(pane1.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(pane1.getElement()); + }); + }); describe('when the right dock is visible', () => { describe("when the workspace center's rightmost column contains the pane", () => { it("returns the pane in the right dock's adjacent column to the right", () => { - workspace.getRightDock().show() + workspace.getRightDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'right', pane6 - ) - expect(nearestPaneElement).toBe(rightDockPane.getElement()) - }) - }) + ); + expect(nearestPaneElement).toBe(rightDockPane.getElement()); + }); + }); describe('when the bottom dock contains the pane', () => { it("returns the pane in the right dock's adjacent column to the right", () => { - workspace.getRightDock().show() - workspace.getBottomDock().show() + workspace.getRightDock().show(); + workspace.getBottomDock().show(); nearestPaneElement = workspaceElement.nearestVisiblePaneInDirection( 'right', bottomDockPane - ) - expect(nearestPaneElement).toBe(rightDockPane.getElement()) - }) - }) - }) - }) - }) + ); + expect(nearestPaneElement).toBe(rightDockPane.getElement()); + }); + }); + }); + }); + }); - describe('changing focus, copying, and moving items directionally between panes', function () { - let workspace, workspaceElement, startingPane + describe('changing focus, copying, and moving items directionally between panes', function() { + let workspace, workspaceElement, startingPane; - beforeEach(function () { - atom.config.set('core.destroyEmptyPanes', false) + beforeEach(function() { + atom.config.set('core.destroyEmptyPanes', false); expect(document.hasFocus()).toBe( true, 'Document needs to be focused to run this test' - ) + ); - workspace = atom.workspace - expect(workspace.getLeftDock().isVisible()).toBe(false) - expect(workspace.getRightDock().isVisible()).toBe(false) - expect(workspace.getBottomDock().isVisible()).toBe(false) + workspace = atom.workspace; + expect(workspace.getLeftDock().isVisible()).toBe(false); + expect(workspace.getRightDock().isVisible()).toBe(false); + expect(workspace.getBottomDock().isVisible()).toBe(false); - const panes = workspace.getCenter().getPanes() - expect(panes.length).toEqual(1) - startingPane = panes[0] + const panes = workspace.getCenter().getPanes(); + expect(panes.length).toEqual(1); + startingPane = panes[0]; - workspaceElement = atom.workspace.getElement() - workspaceElement.style.height = '400px' - workspaceElement.style.width = '400px' - jasmine.attachToDOM(workspaceElement) - }) + workspaceElement = atom.workspace.getElement(); + workspaceElement.style.height = '400px'; + workspaceElement.style.width = '400px'; + jasmine.attachToDOM(workspaceElement); + }); - describe('::focusPaneViewAbove()', function () { + describe('::focusPaneViewAbove()', function() { describe('when there is a row above the focused pane', () => - it('focuses up to the adjacent row', function () { - const paneAbove = startingPane.splitUp() - startingPane.activate() - workspaceElement.focusPaneViewAbove() - expect(document.activeElement).toBe(paneAbove.getElement()) - })) + it('focuses up to the adjacent row', function() { + const paneAbove = startingPane.splitUp(); + startingPane.activate(); + workspaceElement.focusPaneViewAbove(); + expect(document.activeElement).toBe(paneAbove.getElement()); + })); describe('when there are no rows above the focused pane', () => - it('keeps the current pane focused', function () { - startingPane.activate() - workspaceElement.focusPaneViewAbove() - expect(document.activeElement).toBe(startingPane.getElement()) - })) - }) + it('keeps the current pane focused', function() { + startingPane.activate(); + workspaceElement.focusPaneViewAbove(); + expect(document.activeElement).toBe(startingPane.getElement()); + })); + }); - describe('::focusPaneViewBelow()', function () { + describe('::focusPaneViewBelow()', function() { describe('when there is a row below the focused pane', () => - it('focuses down to the adjacent row', function () { - const paneBelow = startingPane.splitDown() - startingPane.activate() - workspaceElement.focusPaneViewBelow() - expect(document.activeElement).toBe(paneBelow.getElement()) - })) + it('focuses down to the adjacent row', function() { + const paneBelow = startingPane.splitDown(); + startingPane.activate(); + workspaceElement.focusPaneViewBelow(); + expect(document.activeElement).toBe(paneBelow.getElement()); + })); describe('when there are no rows below the focused pane', () => - it('keeps the current pane focused', function () { - startingPane.activate() - workspaceElement.focusPaneViewBelow() - expect(document.activeElement).toBe(startingPane.getElement()) - })) - }) + it('keeps the current pane focused', function() { + startingPane.activate(); + workspaceElement.focusPaneViewBelow(); + expect(document.activeElement).toBe(startingPane.getElement()); + })); + }); - describe('::focusPaneViewOnLeft()', function () { + describe('::focusPaneViewOnLeft()', function() { describe('when there is a column to the left of the focused pane', () => - it('focuses left to the adjacent column', function () { - const paneOnLeft = startingPane.splitLeft() - startingPane.activate() - workspaceElement.focusPaneViewOnLeft() - expect(document.activeElement).toBe(paneOnLeft.getElement()) - })) + it('focuses left to the adjacent column', function() { + const paneOnLeft = startingPane.splitLeft(); + startingPane.activate(); + workspaceElement.focusPaneViewOnLeft(); + expect(document.activeElement).toBe(paneOnLeft.getElement()); + })); describe('when there are no columns to the left of the focused pane', () => - it('keeps the current pane focused', function () { - startingPane.activate() - workspaceElement.focusPaneViewOnLeft() - expect(document.activeElement).toBe(startingPane.getElement()) - })) - }) + it('keeps the current pane focused', function() { + startingPane.activate(); + workspaceElement.focusPaneViewOnLeft(); + expect(document.activeElement).toBe(startingPane.getElement()); + })); + }); - describe('::focusPaneViewOnRight()', function () { + describe('::focusPaneViewOnRight()', function() { describe('when there is a column to the right of the focused pane', () => - it('focuses right to the adjacent column', function () { - const paneOnRight = startingPane.splitRight() - startingPane.activate() - workspaceElement.focusPaneViewOnRight() - expect(document.activeElement).toBe(paneOnRight.getElement()) - })) + it('focuses right to the adjacent column', function() { + const paneOnRight = startingPane.splitRight(); + startingPane.activate(); + workspaceElement.focusPaneViewOnRight(); + expect(document.activeElement).toBe(paneOnRight.getElement()); + })); describe('when there are no columns to the right of the focused pane', () => - it('keeps the current pane focused', function () { - startingPane.activate() - workspaceElement.focusPaneViewOnRight() - expect(document.activeElement).toBe(startingPane.getElement()) - })) - }) + it('keeps the current pane focused', function() { + startingPane.activate(); + workspaceElement.focusPaneViewOnRight(); + expect(document.activeElement).toBe(startingPane.getElement()); + })); + }); - describe('::moveActiveItemToPaneAbove(keepOriginal)', function () { + describe('::moveActiveItemToPaneAbove(keepOriginal)', function() { describe('when there is a row above the focused pane', () => - it('moves the active item up to the adjacent row', function () { - const item = document.createElement('div') - const paneAbove = startingPane.splitUp() - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneAbove() - expect(workspace.paneForItem(item)).toBe(paneAbove) - expect(paneAbove.getActiveItem()).toBe(item) - })) + it('moves the active item up to the adjacent row', function() { + const item = document.createElement('div'); + const paneAbove = startingPane.splitUp(); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneAbove(); + expect(workspace.paneForItem(item)).toBe(paneAbove); + expect(paneAbove.getActiveItem()).toBe(item); + })); describe('when there are no rows above the focused pane', () => - it('keeps the active pane focused', function () { - const item = document.createElement('div') - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneAbove() - expect(workspace.paneForItem(item)).toBe(startingPane) - })) + it('keeps the active pane focused', function() { + const item = document.createElement('div'); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneAbove(); + expect(workspace.paneForItem(item)).toBe(startingPane); + })); describe('when `keepOriginal: true` is passed in the params', () => - it('keeps the item and adds a copy of it to the adjacent pane', function () { - const itemA = document.createElement('div') - const itemB = document.createElement('div') - itemA.copy = () => itemB - const paneAbove = startingPane.splitUp() - startingPane.activate() - startingPane.activateItem(itemA) - workspaceElement.moveActiveItemToPaneAbove({ keepOriginal: true }) - expect(workspace.paneForItem(itemA)).toBe(startingPane) - expect(paneAbove.getActiveItem()).toBe(itemB) - })) - }) + it('keeps the item and adds a copy of it to the adjacent pane', function() { + const itemA = document.createElement('div'); + const itemB = document.createElement('div'); + itemA.copy = () => itemB; + const paneAbove = startingPane.splitUp(); + startingPane.activate(); + startingPane.activateItem(itemA); + workspaceElement.moveActiveItemToPaneAbove({ keepOriginal: true }); + expect(workspace.paneForItem(itemA)).toBe(startingPane); + expect(paneAbove.getActiveItem()).toBe(itemB); + })); + }); - describe('::moveActiveItemToPaneBelow(keepOriginal)', function () { + describe('::moveActiveItemToPaneBelow(keepOriginal)', function() { describe('when there is a row below the focused pane', () => - it('moves the active item down to the adjacent row', function () { - const item = document.createElement('div') - const paneBelow = startingPane.splitDown() - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneBelow() - expect(workspace.paneForItem(item)).toBe(paneBelow) - expect(paneBelow.getActiveItem()).toBe(item) - })) + it('moves the active item down to the adjacent row', function() { + const item = document.createElement('div'); + const paneBelow = startingPane.splitDown(); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneBelow(); + expect(workspace.paneForItem(item)).toBe(paneBelow); + expect(paneBelow.getActiveItem()).toBe(item); + })); describe('when there are no rows below the focused pane', () => - it('keeps the active item in the focused pane', function () { - const item = document.createElement('div') - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneBelow() - expect(workspace.paneForItem(item)).toBe(startingPane) - })) + it('keeps the active item in the focused pane', function() { + const item = document.createElement('div'); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneBelow(); + expect(workspace.paneForItem(item)).toBe(startingPane); + })); describe('when `keepOriginal: true` is passed in the params', () => - it('keeps the item and adds a copy of it to the adjacent pane', function () { - const itemA = document.createElement('div') - const itemB = document.createElement('div') - itemA.copy = () => itemB - const paneBelow = startingPane.splitDown() - startingPane.activate() - startingPane.activateItem(itemA) - workspaceElement.moveActiveItemToPaneBelow({ keepOriginal: true }) - expect(workspace.paneForItem(itemA)).toBe(startingPane) - expect(paneBelow.getActiveItem()).toBe(itemB) - })) - }) + it('keeps the item and adds a copy of it to the adjacent pane', function() { + const itemA = document.createElement('div'); + const itemB = document.createElement('div'); + itemA.copy = () => itemB; + const paneBelow = startingPane.splitDown(); + startingPane.activate(); + startingPane.activateItem(itemA); + workspaceElement.moveActiveItemToPaneBelow({ keepOriginal: true }); + expect(workspace.paneForItem(itemA)).toBe(startingPane); + expect(paneBelow.getActiveItem()).toBe(itemB); + })); + }); - describe('::moveActiveItemToPaneOnLeft(keepOriginal)', function () { + describe('::moveActiveItemToPaneOnLeft(keepOriginal)', function() { describe('when there is a column to the left of the focused pane', () => - it('moves the active item left to the adjacent column', function () { - const item = document.createElement('div') - const paneOnLeft = startingPane.splitLeft() - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneOnLeft() - expect(workspace.paneForItem(item)).toBe(paneOnLeft) - expect(paneOnLeft.getActiveItem()).toBe(item) - })) + it('moves the active item left to the adjacent column', function() { + const item = document.createElement('div'); + const paneOnLeft = startingPane.splitLeft(); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneOnLeft(); + expect(workspace.paneForItem(item)).toBe(paneOnLeft); + expect(paneOnLeft.getActiveItem()).toBe(item); + })); describe('when there are no columns to the left of the focused pane', () => - it('keeps the active item in the focused pane', function () { - const item = document.createElement('div') - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneOnLeft() - expect(workspace.paneForItem(item)).toBe(startingPane) - })) + it('keeps the active item in the focused pane', function() { + const item = document.createElement('div'); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneOnLeft(); + expect(workspace.paneForItem(item)).toBe(startingPane); + })); describe('when `keepOriginal: true` is passed in the params', () => - it('keeps the item and adds a copy of it to the adjacent pane', function () { - const itemA = document.createElement('div') - const itemB = document.createElement('div') - itemA.copy = () => itemB - const paneOnLeft = startingPane.splitLeft() - startingPane.activate() - startingPane.activateItem(itemA) - workspaceElement.moveActiveItemToPaneOnLeft({ keepOriginal: true }) - expect(workspace.paneForItem(itemA)).toBe(startingPane) - expect(paneOnLeft.getActiveItem()).toBe(itemB) - })) - }) + it('keeps the item and adds a copy of it to the adjacent pane', function() { + const itemA = document.createElement('div'); + const itemB = document.createElement('div'); + itemA.copy = () => itemB; + const paneOnLeft = startingPane.splitLeft(); + startingPane.activate(); + startingPane.activateItem(itemA); + workspaceElement.moveActiveItemToPaneOnLeft({ keepOriginal: true }); + expect(workspace.paneForItem(itemA)).toBe(startingPane); + expect(paneOnLeft.getActiveItem()).toBe(itemB); + })); + }); - describe('::moveActiveItemToPaneOnRight(keepOriginal)', function () { + describe('::moveActiveItemToPaneOnRight(keepOriginal)', function() { describe('when there is a column to the right of the focused pane', () => - it('moves the active item right to the adjacent column', function () { - const item = document.createElement('div') - const paneOnRight = startingPane.splitRight() - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneOnRight() - expect(workspace.paneForItem(item)).toBe(paneOnRight) - expect(paneOnRight.getActiveItem()).toBe(item) - })) + it('moves the active item right to the adjacent column', function() { + const item = document.createElement('div'); + const paneOnRight = startingPane.splitRight(); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneOnRight(); + expect(workspace.paneForItem(item)).toBe(paneOnRight); + expect(paneOnRight.getActiveItem()).toBe(item); + })); describe('when there are no columns to the right of the focused pane', () => - it('keeps the active item in the focused pane', function () { - const item = document.createElement('div') - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.moveActiveItemToPaneOnRight() - expect(workspace.paneForItem(item)).toBe(startingPane) - })) + it('keeps the active item in the focused pane', function() { + const item = document.createElement('div'); + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.moveActiveItemToPaneOnRight(); + expect(workspace.paneForItem(item)).toBe(startingPane); + })); describe('when `keepOriginal: true` is passed in the params', () => - it('keeps the item and adds a copy of it to the adjacent pane', function () { - const itemA = document.createElement('div') - const itemB = document.createElement('div') - itemA.copy = () => itemB - const paneOnRight = startingPane.splitRight() - startingPane.activate() - startingPane.activateItem(itemA) - workspaceElement.moveActiveItemToPaneOnRight({ keepOriginal: true }) - expect(workspace.paneForItem(itemA)).toBe(startingPane) - expect(paneOnRight.getActiveItem()).toBe(itemB) - })) - }) + it('keeps the item and adds a copy of it to the adjacent pane', function() { + const itemA = document.createElement('div'); + const itemB = document.createElement('div'); + itemA.copy = () => itemB; + const paneOnRight = startingPane.splitRight(); + startingPane.activate(); + startingPane.activateItem(itemA); + workspaceElement.moveActiveItemToPaneOnRight({ keepOriginal: true }); + expect(workspace.paneForItem(itemA)).toBe(startingPane); + expect(paneOnRight.getActiveItem()).toBe(itemB); + })); + }); describe('::moveActiveItemToNearestPaneInDirection(direction, params)', () => { describe('when the item is not allowed in nearest pane in the given direction', () => { - it('does not move or copy the active item', function () { + it('does not move or copy the active item', function() { const item = { element: document.createElement('div'), getAllowedLocations: () => ['left', 'right'] - } + }; - workspace.getBottomDock().show() - startingPane.activate() - startingPane.activateItem(item) + workspace.getBottomDock().show(); + startingPane.activate(); + startingPane.activateItem(item); workspaceElement.moveActiveItemToNearestPaneInDirection('below', { keepOriginal: false - }) - expect(workspace.paneForItem(item)).toBe(startingPane) + }); + expect(workspace.paneForItem(item)).toBe(startingPane); workspaceElement.moveActiveItemToNearestPaneInDirection('below', { keepOriginal: true - }) - expect(workspace.paneForItem(item)).toBe(startingPane) - }) - }) + }); + expect(workspace.paneForItem(item)).toBe(startingPane); + }); + }); describe("when the item doesn't implement a `copy` function", () => { - it('does not copy the active item', function () { - const item = document.createElement('div') - const paneBelow = startingPane.splitDown() - expect(paneBelow.getItems().length).toEqual(0) + it('does not copy the active item', function() { + const item = document.createElement('div'); + const paneBelow = startingPane.splitDown(); + expect(paneBelow.getItems().length).toEqual(0); - startingPane.activate() - startingPane.activateItem(item) - workspaceElement.focusPaneViewAbove() + startingPane.activate(); + startingPane.activateItem(item); + workspaceElement.focusPaneViewAbove(); workspaceElement.moveActiveItemToNearestPaneInDirection('below', { keepOriginal: true - }) - expect(workspace.paneForItem(item)).toBe(startingPane) - expect(paneBelow.getItems().length).toEqual(0) - }) - }) - }) - }) + }); + expect(workspace.paneForItem(item)).toBe(startingPane); + expect(paneBelow.getItems().length).toEqual(0); + }); + }); + }); + }); describe('mousing over docks', () => { - let workspaceElement - let originalTimeout = jasmine.getEnv().defaultTimeoutInterval + let workspaceElement; + let originalTimeout = jasmine.getEnv().defaultTimeoutInterval; beforeEach(() => { - workspaceElement = atom.workspace.getElement() - workspaceElement.style.width = '600px' - workspaceElement.style.height = '300px' - jasmine.attachToDOM(workspaceElement) + workspaceElement = atom.workspace.getElement(); + workspaceElement.style.width = '600px'; + workspaceElement.style.height = '300px'; + jasmine.attachToDOM(workspaceElement); // To isolate this test from unintended events happening on the host machine, // we remove any listener that could cause interferences. window.removeEventListener( 'mousemove', workspaceElement.handleEdgesMouseMove - ) + ); workspaceElement.htmlElement.removeEventListener( 'mouseleave', workspaceElement.handleCenterLeave - ) + ); - jasmine.getEnv().defaultTimeoutInterval = 10000 - }) + jasmine.getEnv().defaultTimeoutInterval = 10000; + }); afterEach(() => { - jasmine.getEnv().defaultTimeoutInterval = originalTimeout + jasmine.getEnv().defaultTimeoutInterval = originalTimeout; window.addEventListener( 'mousemove', workspaceElement.handleEdgesMouseMove - ) + ); workspaceElement.htmlElement.addEventListener( 'mouseleave', workspaceElement.handleCenterLeave - ) - }) + ); + }); it('shows the toggle button when the dock is open', async () => { await Promise.all([ atom.workspace.open({ element: document.createElement('div'), - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getPreferredWidth () { - return 150 + getPreferredWidth() { + return 150; } }), atom.workspace.open({ element: document.createElement('div'), - getDefaultLocation () { - return 'right' + getDefaultLocation() { + return 'right'; }, - getPreferredWidth () { - return 150 + getPreferredWidth() { + return 150; } }), atom.workspace.open({ element: document.createElement('div'), - getDefaultLocation () { - return 'bottom' + getDefaultLocation() { + return 'bottom'; }, - getPreferredHeight () { - return 100 + getPreferredHeight() { + return 100; } }) - ]) + ]); - const leftDock = atom.workspace.getLeftDock() - const rightDock = atom.workspace.getRightDock() - const bottomDock = atom.workspace.getBottomDock() + const leftDock = atom.workspace.getLeftDock(); + const rightDock = atom.workspace.getRightDock(); + const bottomDock = atom.workspace.getBottomDock(); - expect(leftDock.isVisible()).toBe(true) - expect(rightDock.isVisible()).toBe(true) - expect(bottomDock.isVisible()).toBe(true) - expectToggleButtonHidden(leftDock) - expectToggleButtonHidden(rightDock) - expectToggleButtonHidden(bottomDock) + expect(leftDock.isVisible()).toBe(true); + expect(rightDock.isVisible()).toBe(true); + expect(bottomDock.isVisible()).toBe(true); + expectToggleButtonHidden(leftDock); + expectToggleButtonHidden(rightDock); + expectToggleButtonHidden(bottomDock); // --- Right Dock --- // Mouse over where the toggle button would be if the dock were hovered - moveMouse({ clientX: 440, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonHidden(rightDock) - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 440, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonHidden(rightDock); + expectToggleButtonHidden(bottomDock); // Mouse over the dock - moveMouse({ clientX: 460, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonVisible(rightDock, 'icon-chevron-right') - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 460, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonVisible(rightDock, 'icon-chevron-right'); + expectToggleButtonHidden(bottomDock); // Mouse over the toggle button - moveMouse({ clientX: 440, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonVisible(rightDock, 'icon-chevron-right') - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 440, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonVisible(rightDock, 'icon-chevron-right'); + expectToggleButtonHidden(bottomDock); // Click the toggle button - rightDock.refs.toggleButton.refs.innerElement.click() - await getNextUpdatePromise() - expect(rightDock.isVisible()).toBe(false) - expectToggleButtonHidden(rightDock) + rightDock.refs.toggleButton.refs.innerElement.click(); + await getNextUpdatePromise(); + expect(rightDock.isVisible()).toBe(false); + expectToggleButtonHidden(rightDock); // Mouse to edge of the window - moveMouse({ clientX: 575, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonHidden(rightDock) - moveMouse({ clientX: 598, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonVisible(rightDock, 'icon-chevron-left') + moveMouse({ clientX: 575, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(rightDock); + moveMouse({ clientX: 598, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonVisible(rightDock, 'icon-chevron-left'); // Click the toggle button again - rightDock.refs.toggleButton.refs.innerElement.click() - await getNextUpdatePromise() - expect(rightDock.isVisible()).toBe(true) - expectToggleButtonVisible(rightDock, 'icon-chevron-right') + rightDock.refs.toggleButton.refs.innerElement.click(); + await getNextUpdatePromise(); + expect(rightDock.isVisible()).toBe(true); + expectToggleButtonVisible(rightDock, 'icon-chevron-right'); // --- Left Dock --- // Mouse over where the toggle button would be if the dock were hovered - moveMouse({ clientX: 160, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonHidden(rightDock) - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 160, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonHidden(rightDock); + expectToggleButtonHidden(bottomDock); // Mouse over the dock - moveMouse({ clientX: 140, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonVisible(leftDock, 'icon-chevron-left') - expectToggleButtonHidden(rightDock) - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 140, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonVisible(leftDock, 'icon-chevron-left'); + expectToggleButtonHidden(rightDock); + expectToggleButtonHidden(bottomDock); // Mouse over the toggle button - moveMouse({ clientX: 160, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonVisible(leftDock, 'icon-chevron-left') - expectToggleButtonHidden(rightDock) - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 160, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonVisible(leftDock, 'icon-chevron-left'); + expectToggleButtonHidden(rightDock); + expectToggleButtonHidden(bottomDock); // Click the toggle button - leftDock.refs.toggleButton.refs.innerElement.click() - await getNextUpdatePromise() - expect(leftDock.isVisible()).toBe(false) - expectToggleButtonHidden(leftDock) + leftDock.refs.toggleButton.refs.innerElement.click(); + await getNextUpdatePromise(); + expect(leftDock.isVisible()).toBe(false); + expectToggleButtonHidden(leftDock); // Mouse to edge of the window - moveMouse({ clientX: 25, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - moveMouse({ clientX: 2, clientY: 150 }) - await getNextUpdatePromise() - expectToggleButtonVisible(leftDock, 'icon-chevron-right') + moveMouse({ clientX: 25, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + moveMouse({ clientX: 2, clientY: 150 }); + await getNextUpdatePromise(); + expectToggleButtonVisible(leftDock, 'icon-chevron-right'); // Click the toggle button again - leftDock.refs.toggleButton.refs.innerElement.click() - await getNextUpdatePromise() - expect(leftDock.isVisible()).toBe(true) - expectToggleButtonVisible(leftDock, 'icon-chevron-left') + leftDock.refs.toggleButton.refs.innerElement.click(); + await getNextUpdatePromise(); + expect(leftDock.isVisible()).toBe(true); + expectToggleButtonVisible(leftDock, 'icon-chevron-left'); // --- Bottom Dock --- // Mouse over where the toggle button would be if the dock were hovered - moveMouse({ clientX: 300, clientY: 190 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonHidden(rightDock) - expectToggleButtonHidden(bottomDock) + moveMouse({ clientX: 300, clientY: 190 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonHidden(rightDock); + expectToggleButtonHidden(bottomDock); // Mouse over the dock - moveMouse({ clientX: 300, clientY: 210 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonHidden(rightDock) - expectToggleButtonVisible(bottomDock, 'icon-chevron-down') + moveMouse({ clientX: 300, clientY: 210 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonHidden(rightDock); + expectToggleButtonVisible(bottomDock, 'icon-chevron-down'); // Mouse over the toggle button - moveMouse({ clientX: 300, clientY: 195 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - expectToggleButtonHidden(rightDock) - expectToggleButtonVisible(bottomDock, 'icon-chevron-down') + moveMouse({ clientX: 300, clientY: 195 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + expectToggleButtonHidden(rightDock); + expectToggleButtonVisible(bottomDock, 'icon-chevron-down'); // Click the toggle button - bottomDock.refs.toggleButton.refs.innerElement.click() - await getNextUpdatePromise() - expect(bottomDock.isVisible()).toBe(false) - expectToggleButtonHidden(bottomDock) + bottomDock.refs.toggleButton.refs.innerElement.click(); + await getNextUpdatePromise(); + expect(bottomDock.isVisible()).toBe(false); + expectToggleButtonHidden(bottomDock); // Mouse to edge of the window - moveMouse({ clientX: 300, clientY: 290 }) - await getNextUpdatePromise() - expectToggleButtonHidden(leftDock) - moveMouse({ clientX: 300, clientY: 299 }) - await getNextUpdatePromise() - expectToggleButtonVisible(bottomDock, 'icon-chevron-up') + moveMouse({ clientX: 300, clientY: 290 }); + await getNextUpdatePromise(); + expectToggleButtonHidden(leftDock); + moveMouse({ clientX: 300, clientY: 299 }); + await getNextUpdatePromise(); + expectToggleButtonVisible(bottomDock, 'icon-chevron-up'); // Click the toggle button again - bottomDock.refs.toggleButton.refs.innerElement.click() - await getNextUpdatePromise() - expect(bottomDock.isVisible()).toBe(true) - expectToggleButtonVisible(bottomDock, 'icon-chevron-down') - }) + bottomDock.refs.toggleButton.refs.innerElement.click(); + await getNextUpdatePromise(); + expect(bottomDock.isVisible()).toBe(true); + expectToggleButtonVisible(bottomDock, 'icon-chevron-down'); + }); - function moveMouse (coordinates) { + function moveMouse(coordinates) { // Simulate a mouse move event by calling the method that handles that event. - workspaceElement.updateHoveredDock({x: coordinates.clientX, y: coordinates.clientY}) - advanceClock(100) + workspaceElement.updateHoveredDock({ + x: coordinates.clientX, + y: coordinates.clientY + }); + advanceClock(100); } - function expectToggleButtonHidden (dock) { + function expectToggleButtonHidden(dock) { expect(dock.refs.toggleButton.element).not.toHaveClass( 'atom-dock-toggle-button-visible' - ) + ); } - function expectToggleButtonVisible (dock, iconClass) { + function expectToggleButtonVisible(dock, iconClass) { expect(dock.refs.toggleButton.element).toHaveClass( 'atom-dock-toggle-button-visible' - ) - expect(dock.refs.toggleButton.refs.iconElement).toHaveClass(iconClass) + ); + expect(dock.refs.toggleButton.refs.iconElement).toHaveClass(iconClass); } - }) + }); describe('the scrollbar visibility class', () => { it('has a class based on the style of the scrollbar', () => { - let observeCallback - const scrollbarStyle = require('scrollbar-style') + let observeCallback; + const scrollbarStyle = require('scrollbar-style'); spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake( cb => { - observeCallback = cb - return new Disposable(() => {}) + observeCallback = cb; + return new Disposable(() => {}); } - ) + ); - const workspaceElement = atom.workspace.getElement() - observeCallback('legacy') - expect(workspaceElement.className).toMatch('scrollbars-visible-always') + const workspaceElement = atom.workspace.getElement(); + observeCallback('legacy'); + expect(workspaceElement.className).toMatch('scrollbars-visible-always'); - observeCallback('overlay') - expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling') - }) - }) + observeCallback('overlay'); + expect(workspaceElement).toHaveClass('scrollbars-visible-when-scrolling'); + }); + }); describe('editor font styling', () => { - let editor, editorElement, workspaceElement + let editor, editorElement, workspaceElement; beforeEach(async () => { - await atom.workspace.open('sample.js') + await atom.workspace.open('sample.js'); - workspaceElement = atom.workspace.getElement() - jasmine.attachToDOM(workspaceElement) - editor = atom.workspace.getActiveTextEditor() - editorElement = editor.getElement() - }) + workspaceElement = atom.workspace.getElement(); + jasmine.attachToDOM(workspaceElement); + editor = atom.workspace.getActiveTextEditor(); + editorElement = editor.getElement(); + }); it("updates the font-size based on the 'editor.fontSize' config value", async () => { - const initialCharWidth = editor.getDefaultCharWidth() + 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) - await editorElement.component.getNextUpdatePromise() + atom.config.set( + 'editor.fontSize', + atom.config.get('editor.fontSize') + 5 + ); + await editorElement.component.getNextUpdatePromise(); expect(getComputedStyle(editorElement).fontSize).toBe( atom.config.get('editor.fontSize') + 'px' - ) - expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth) - }) + ); + expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth); + }); it("updates the font-family based on the 'editor.fontFamily' config value", async () => { - const initialCharWidth = editor.getDefaultCharWidth() - let fontFamily = atom.config.get('editor.fontFamily') - expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily) + 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') - await editorElement.component.getNextUpdatePromise() - expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily) - expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth) - }) + atom.config.set('editor.fontFamily', 'sans-serif'); + fontFamily = atom.config.get('editor.fontFamily'); + await editorElement.component.getNextUpdatePromise(); + expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); + expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth); + }); it("updates the line-height based on the 'editor.lineHeight' config value", async () => { - const initialLineHeight = editor.getLineHeightInPixels() - atom.config.set('editor.lineHeight', '30px') - await editorElement.component.getNextUpdatePromise() + const initialLineHeight = editor.getLineHeightInPixels(); + atom.config.set('editor.lineHeight', '30px'); + await editorElement.component.getNextUpdatePromise(); expect(getComputedStyle(editorElement).lineHeight).toBe( atom.config.get('editor.lineHeight') - ) - expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight) - }) + ); + 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) + atom.config.set('editor.zoomFontWhenCtrlScrolling', true); + atom.config.set('editor.fontSize', 12); // Zoom out editorElement.querySelector('span').dispatchEvent( @@ -887,8 +893,8 @@ describe('WorkspaceElement', () => { 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( @@ -896,8 +902,8 @@ describe('WorkspaceElement', () => { 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( @@ -905,192 +911,194 @@ describe('WorkspaceElement', () => { 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) + ); + 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 }) - ) - expect(atom.config.get('editor.fontSize')).toBe(12) - }) - }) + ); + expect(atom.config.get('editor.fontSize')).toBe(12); + }); + }); describe('panel containers', () => { it('inserts panel container elements in the correct places in the DOM', () => { - const workspaceElement = atom.workspace.getElement() + const workspaceElement = atom.workspace.getElement(); 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) + ); + 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(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(headerContainer.nextSibling).toBe(workspaceElement.horizontalAxis); expect(footerContainer.previousSibling).toBe( workspaceElement.horizontalAxis - ) + ); const modalContainer = workspaceElement.querySelector( 'atom-panel-container.modal' - ) - expect(modalContainer.parentNode).toBe(workspaceElement) - }) + ); + expect(modalContainer.parentNode).toBe(workspaceElement); + }); it('stretches header/footer panels to the workspace width', () => { - const workspaceElement = atom.workspace.getElement() - jasmine.attachToDOM(workspaceElement) - expect(workspaceElement.offsetWidth).toBeGreaterThan(0) + const workspaceElement = atom.workspace.getElement(); + 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 }) - expect(footerItem.offsetWidth).toEqual(workspaceElement.offsetWidth) - }) + const 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', () => { - const workspaceElement = atom.workspace.getElement() - workspaceElement.style.height = '100px' + const workspaceElement = atom.workspace.getElement(); + workspaceElement.style.height = '100px'; const horizontalAxisElement = workspaceElement.querySelector( 'atom-workspace-axis.horizontal' - ) - jasmine.attachToDOM(workspaceElement) + ); + 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); 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', () => { - const workspaceElement = atom.workspace.getElement() - 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) - }) - }) + const workspaceElement = atom.workspace.getElement(); + 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", () => { - const workspaceElement = atom.workspace.getElement() - spyOn(ipcRenderer, 'send') + const workspaceElement = atom.workspace.getElement(); + 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') + atom.commands.dispatch(workspaceElement, 'window:run-package-specs'); expect(ipcRenderer.send).toHaveBeenCalledWith( 'run-package-specs', path.join(projectPaths[0], 'spec'), {} - ) - ipcRenderer.send.reset() + ); + 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') + 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() + ); + ipcRenderer.send.reset(); // Active item has no path. Use first project directory. - item.getPath = () => null - atom.commands.dispatch(workspaceElement, 'window:run-package-specs') + 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() + ); + 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') + 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() - }) + ); + ipcRenderer.send.reset(); + }); it('passes additional options to the spec window', () => { - const workspaceElement = atom.workspace.getElement() - spyOn(ipcRenderer, 'send') + const workspaceElement = atom.workspace.getElement(); + spyOn(ipcRenderer, 'send'); - const projectPath = temp.mkdirSync('dir1-') - atom.project.setPaths([projectPath]) + const projectPath = temp.mkdirSync('dir1-'); + atom.project.setPaths([projectPath]); workspaceElement.runPackageSpecs({ env: { ATOM_GITHUB_BABEL_ENV: 'coverage' } - }) + }); expect(ipcRenderer.send).toHaveBeenCalledWith( 'run-package-specs', path.join(projectPath, 'spec'), { env: { ATOM_GITHUB_BABEL_ENV: 'coverage' } } - ) - }) - }) -}) + ); + }); + }); +}); diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 1b84553b1..d080f5fc8 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1,56 +1,56 @@ -const path = require('path') -const temp = require('temp').track() -const dedent = require('dedent') -const TextBuffer = require('text-buffer') -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') -const { conditionPromise } = require('./async-spec-helpers') +const path = require('path'); +const temp = require('temp').track(); +const dedent = require('dedent'); +const TextBuffer = require('text-buffer'); +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'); +const { conditionPromise } = require('./async-spec-helpers'); describe('Workspace', () => { - let workspace - let setDocumentEdited + let workspace; + let setDocumentEdited; beforeEach(() => { - workspace = atom.workspace - workspace.resetFontSize() - spyOn(atom.applicationDelegate, 'confirm') + 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) + ); + atom.project.setPaths([atom.project.getDirectories()[0].resolve('dir')]); + waits(1); - waitsForPromise(() => atom.workspace.itemLocationStore.clear()) - }) + waitsForPromise(() => atom.workspace.itemLocationStore.clear()); + }); afterEach(() => { try { - temp.cleanupSync() + temp.cleanupSync(); } catch (e) { // Do nothing } - }) + }); - function simulateReload () { + function simulateReload() { waitsForPromise(() => { - const workspaceState = workspace.serialize() - const projectState = atom.project.serialize({ isUnloading: true }) - workspace.destroy() - atom.project.destroy() + const workspaceState = workspace.serialize(); + const projectState = atom.project.serialize({ isUnloading: true }); + workspace.destroy(); + atom.project.destroy(); atom.project = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom), applicationDelegate: atom.applicationDelegate, grammarRegistry: atom.grammars - }) + }); return atom.project.deserialize(projectState).then(() => { workspace = atom.workspace = new Workspace({ config: atom.config, @@ -64,132 +64,134 @@ describe('Workspace', () => { viewRegistry: atom.views, assert: atom.assert.bind(atom), textEditorRegistry: atom.textEditors - }) - workspace.deserialize(workspaceState, atom.deserializers) - }) - }) + }); + workspace.deserialize(workspaceState, atom.deserializers); + }); + }); } describe('serialization', () => { 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 }) - let pane4 = null + 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(() => { - pane3.activeItem.setCursorScreenPosition([2, 4]) - pane4 = pane2.splitDown() - }) + 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() - }) + pane4.getActiveItem().setCursorScreenPosition([0, 2]); + pane2.activate(); + }); - simulateReload() + simulateReload(); runs(() => { - expect(atom.workspace.getTextEditors().length).toBe(5) + expect(atom.workspace.getTextEditors().length).toBe(5); 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')) + ] = 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(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(editor2.getCursorScreenPosition()).toEqual([0, 2]); + 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]) - ) + ); expect(document.title).toMatch( new RegExp( `^${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() + atom.workspace.getActivePane().destroy(); + expect(atom.workspace.getTextEditors().length).toBe(0); + simulateReload(); runs(() => { - expect(atom.workspace.getTextEditors().length).toBe(0) - }) - }) - }) - }) + expect(atom.workspace.getTextEditors().length).toBe(0); + }); + }); + }); + }); describe('::open(itemOrURI, options)', () => { - let openEvents = null + let openEvents = null; beforeEach(() => { - openEvents = [] - workspace.onDidOpen(event => openEvents.push(event)) - spyOn(workspace.getActivePane(), 'activate').andCallThrough() - }) + 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 or item', () => { it('adds and activates an empty editor on the active pane', () => { - let editor1 - let editor2 + let editor1; + let editor2; waitsForPromise(() => workspace.open().then(editor => { - editor1 = 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(editor1.getPath()).toBeUndefined(); + expect(workspace.getActivePane().items).toEqual([editor1]); + expect(workspace.getActivePaneItem()).toBe(editor1); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); expect(openEvents).toEqual([ { uri: undefined, @@ -197,21 +199,21 @@ describe('Workspace', () => { item: editor1, index: 0 } - ]) - openEvents = [] - }) + ]); + openEvents = []; + }); waitsForPromise(() => workspace.open().then(editor => { - editor2 = 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(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, @@ -219,36 +221,36 @@ describe('Workspace', () => { 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', () => { - let editor = null - let editor1 = null - let editor2 = null + let editor = null; + let editor1 = null; + let editor2 = null; waitsForPromise(() => workspace.open('a').then(o => { - editor1 = o + editor1 = o; return workspace.open('b').then(o => { - editor2 = o + editor2 = o; return workspace.open('a').then(o => { - editor = o - }) - }) + editor = o; + }); + }); }) - ) + ); runs(() => { - expect(editor).toBe(editor1) - expect(workspace.getActivePaneItem()).toBe(editor) - expect(workspace.getActivePane().activate).toHaveBeenCalled() - const firstDirectory = atom.project.getDirectories()[0] - expect(firstDirectory).toBeDefined() + expect(editor).toBe(editor1); + expect(workspace.getActivePaneItem()).toBe(editor); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + const firstDirectory = atom.project.getDirectories()[0]; + expect(firstDirectory).toBeDefined(); expect(openEvents).toEqual([ { uri: firstDirectory.resolve('a'), @@ -268,1387 +270,1391 @@ describe('Workspace', () => { pane: atom.workspace.getActivePane(), index: 0 } - ]) - }) - }) + ]); + }); + }); it('finds items in docks', () => { - const dock = atom.workspace.getRightDock() - const ITEM_URI = 'atom://test' + const dock = atom.workspace.getRightDock(); + const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, getDefaultLocation: () => 'left', getElement: () => document.createElement('div') - } - dock.getActivePane().addItem(item) - expect(dock.getPaneItems()).toHaveLength(1) + }; + dock.getActivePane().addItem(item); + expect(dock.getPaneItems()).toHaveLength(1); waitsForPromise(() => atom.workspace.open(ITEM_URI, { searchAllPanes: true }) - ) + ); runs(() => { - expect(atom.workspace.getPaneItems()).toHaveLength(1) - expect(dock.getPaneItems()).toHaveLength(1) - expect(dock.getPaneItems()[0]).toBe(item) - }) - }) - }) + expect(atom.workspace.getPaneItems()).toHaveLength(1); + expect(dock.getPaneItems()).toHaveLength(1); + expect(dock.getPaneItems()[0]).toBe(item); + }); + }); + }); describe("when the 'activateItem' option is false", () => { it('adds the item to the workspace', () => { - let editor - waitsForPromise(() => workspace.open('a')) + let editor; + waitsForPromise(() => workspace.open('a')); waitsForPromise(() => workspace.open('b', { activateItem: false }).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - expect(workspace.getPaneItems()).toContain(editor) - expect(workspace.getActivePaneItem()).not.toBe(editor) - }) - }) - }) + expect(workspace.getPaneItems()).toContain(editor); + expect(workspace.getActivePaneItem()).not.toBe(editor); + }); + }); + }); describe('when the active pane does not have an editor for the given uri', () => { beforeEach(() => { - atom.workspace.enablePersistence = true - }) + atom.workspace.enablePersistence = true; + }); afterEach(async () => { - await atom.workspace.itemLocationStore.clear() - atom.workspace.enablePersistence = false - }) + await atom.workspace.itemLocationStore.clear(); + atom.workspace.enablePersistence = false; + }); it('adds and activates a new editor for the given path on the active pane', () => { - let editor = null + let editor = null; waitsForPromise(() => workspace.open('a').then(o => { - editor = o + editor = o; }) - ) + ); 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]) - expect(workspace.getActivePane().activate).toHaveBeenCalled() - }) - }) + 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]); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + }); + }); it('discovers existing editors that are still opening', () => { - let editor0 = null - let editor1 = null + let editor0 = null; + let editor1 = null; waitsForPromise(() => Promise.all([ workspace.open('spartacus.txt').then(o0 => { - editor0 = o0 + editor0 = o0; }), workspace.open('spartacus.txt').then(o1 => { - editor1 = o1 + editor1 = o1; }) ]) - ) + ); runs(() => { - expect(editor0).toEqual(editor1) - expect(workspace.getActivePane().items).toEqual([editor0]) - }) - }) + expect(editor0).toEqual(editor1); + expect(workspace.getActivePane().items).toEqual([editor0]); + }); + }); it("uses the location specified by the model's `getDefaultLocation()` method", () => { const item = { getDefaultLocation: jasmine.createSpy().andReturn('right'), getElement: () => document.createElement('div') - } - const opener = jasmine.createSpy().andReturn(item) - const dock = atom.workspace.getRightDock() + }; + const opener = jasmine.createSpy().andReturn(item); + const dock = atom.workspace.getRightDock(); spyOn(atom.workspace.itemLocationStore, 'load').andReturn( Promise.resolve() - ) - spyOn(atom.workspace, 'getOpeners').andReturn([opener]) - expect(dock.getPaneItems()).toHaveLength(0) - waitsForPromise(() => atom.workspace.open('a')) + ); + spyOn(atom.workspace, 'getOpeners').andReturn([opener]); + expect(dock.getPaneItems()).toHaveLength(0); + waitsForPromise(() => atom.workspace.open('a')); runs(() => { - expect(dock.getPaneItems()).toHaveLength(1) - expect(opener).toHaveBeenCalled() - expect(item.getDefaultLocation).toHaveBeenCalled() - }) - }) + expect(dock.getPaneItems()).toHaveLength(1); + expect(opener).toHaveBeenCalled(); + expect(item.getDefaultLocation).toHaveBeenCalled(); + }); + }); it('prefers the last location the user used for that item', () => { - const ITEM_URI = 'atom://test' + const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, getDefaultLocation: () => 'left', getElement: () => document.createElement('div') - } - const opener = uri => (uri === ITEM_URI ? item : null) - const dock = atom.workspace.getRightDock() + }; + const opener = uri => (uri === ITEM_URI ? item : null); + const dock = atom.workspace.getRightDock(); spyOn(atom.workspace.itemLocationStore, 'load').andCallFake(uri => uri === 'atom://test' ? Promise.resolve('right') : Promise.resolve() - ) - spyOn(atom.workspace, 'getOpeners').andReturn([opener]) - expect(dock.getPaneItems()).toHaveLength(0) - waitsForPromise(() => atom.workspace.open(ITEM_URI)) + ); + spyOn(atom.workspace, 'getOpeners').andReturn([opener]); + expect(dock.getPaneItems()).toHaveLength(0); + waitsForPromise(() => atom.workspace.open(ITEM_URI)); runs(() => { - expect(dock.getPaneItems()).toHaveLength(1) - expect(dock.getPaneItems()[0]).toBe(item) - }) - }) - }) - }) + expect(dock.getPaneItems()).toHaveLength(1); + expect(dock.getPaneItems()[0]).toBe(item); + }); + }); + }); + }); describe('when an item with the given uri exists in an inactive pane container', () => { it("activates that item if it is in that container's active pane", async () => { - const item = await atom.workspace.open('a') - atom.workspace.getLeftDock().activate() + const item = await atom.workspace.open('a'); + atom.workspace.getLeftDock().activate(); expect( await atom.workspace.open('a', { searchAllPanes: false }) - ).toBe(item) + ).toBe(item); expect(atom.workspace.getActivePaneContainer().getLocation()).toBe( 'center' - ) - expect(atom.workspace.getPaneItems()).toEqual([item]) + ); + expect(atom.workspace.getPaneItems()).toEqual([item]); - atom.workspace.getActivePane().splitRight() - atom.workspace.getLeftDock().activate() + atom.workspace.getActivePane().splitRight(); + atom.workspace.getLeftDock().activate(); const item2 = await atom.workspace.open('a', { searchAllPanes: false - }) - expect(item2).not.toBe(item) + }); + expect(item2).not.toBe(item); expect(atom.workspace.getActivePaneContainer().getLocation()).toBe( 'center' - ) - expect(atom.workspace.getPaneItems()).toEqual([item, item2]) - }) - }) - }) + ); + expect(atom.workspace.getPaneItems()).toEqual([item, item2]); + }); + }); + }); 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() + let editor1 = null; + let editor2 = null; + const pane1 = workspace.getActivePane(); + const pane2 = workspace.getActivePane().splitRight(); waitsForPromise(() => { - pane1.activate() + pane1.activate(); return workspace.open('a').then(o => { - editor1 = o - }) - }) + editor1 = o; + }); + }); waitsForPromise(() => { - pane2.activate() + pane2.activate(); return workspace.open('b').then(o => { - editor2 = o - }) - }) + editor2 = o; + }); + }); - runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)) + runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)); - waitsForPromise(() => workspace.open('a', { searchAllPanes: true })) + waitsForPromise(() => workspace.open('a', { searchAllPanes: true })); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(workspace.getActivePaneItem()).toBe(editor1) - }) - }) + expect(workspace.getActivePane()).toBe(pane1); + expect(workspace.getActivePaneItem()).toBe(editor1); + }); + }); it('discovers existing editors that are still opening in an inactive pane', () => { - let editor0 = null - let editor1 = null - const pane0 = workspace.getActivePane() - const pane1 = workspace.getActivePane().splitRight() + let editor0 = null; + let editor1 = null; + const pane0 = workspace.getActivePane(); + const pane1 = workspace.getActivePane().splitRight(); - pane0.activate() + pane0.activate(); const promise0 = workspace .open('spartacus.txt', { searchAllPanes: true }) .then(o0 => { - editor0 = o0 - }) - pane1.activate() + editor0 = o0; + }); + pane1.activate(); const promise1 = workspace .open('spartacus.txt', { searchAllPanes: true }) .then(o1 => { - editor1 = o1 - }) + editor1 = o1; + }); - waitsForPromise(() => Promise.all([promise0, promise1])) + waitsForPromise(() => Promise.all([promise0, promise1])); runs(() => { - expect(editor0).toBeDefined() - expect(editor1).toBeDefined() + expect(editor0).toBeDefined(); + expect(editor1).toBeDefined(); - expect(editor0).toEqual(editor1) - expect(workspace.getActivePane().items).toEqual([editor0]) - }) - }) + expect(editor0).toEqual(editor1); + expect(workspace.getActivePane().items).toEqual([editor0]); + }); + }); it('activates the pane in the dock with the matching item', () => { - const dock = atom.workspace.getRightDock() - const ITEM_URI = 'atom://test' + const dock = atom.workspace.getRightDock(); + const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, getDefaultLocation: jasmine.createSpy().andReturn('left'), getElement: () => document.createElement('div') - } - dock.getActivePane().addItem(item) - spyOn(dock.paneForItem(item), 'activate') + }; + dock.getActivePane().addItem(item); + spyOn(dock.paneForItem(item), 'activate'); waitsForPromise(() => atom.workspace.open(ITEM_URI, { searchAllPanes: true }) - ) - runs(() => expect(dock.paneForItem(item).activate).toHaveBeenCalled()) - }) - }) + ); + runs(() => + expect(dock.paneForItem(item).activate).toHaveBeenCalled() + ); + }); + }); 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 + let editor = null; waitsForPromise(() => workspace.open('a', { searchAllPanes: true }).then(o => { - editor = o + editor = o; }) - ) + ); - runs(() => expect(workspace.getActivePaneItem()).toBe(editor)) - }) - }) - }) + runs(() => expect(workspace.getActivePaneItem()).toBe(editor)); + }); + }); + }); describe('when attempting to open an editor in a dock', () => { it('opens the editor in the workspace center', async () => { - await atom.workspace.open('sample.txt', { location: 'right' }) + await atom.workspace.open('sample.txt', { location: 'right' }); expect( atom.workspace .getCenter() .getActivePaneItem() .getFileName() - ).toEqual('sample.txt') - }) - }) + ).toEqual('sample.txt'); + }); + }); describe('when called with an item rather than a URI', () => { it('adds the item itself to the workspace', async () => { - const item = document.createElement('div') - await atom.workspace.open(item) - expect(atom.workspace.getActivePaneItem()).toBe(item) - }) + const item = document.createElement('div'); + await atom.workspace.open(item); + expect(atom.workspace.getActivePaneItem()).toBe(item); + }); describe('when the active pane already contains the item', () => { it('activates the item', async () => { - const item = document.createElement('div') + const item = document.createElement('div'); - await atom.workspace.open(item) - await atom.workspace.open() - expect(atom.workspace.getActivePaneItem()).not.toBe(item) - expect(atom.workspace.getActivePane().getItems().length).toBe(2) + await atom.workspace.open(item); + await atom.workspace.open(); + expect(atom.workspace.getActivePaneItem()).not.toBe(item); + expect(atom.workspace.getActivePane().getItems().length).toBe(2); - await atom.workspace.open(item) - expect(atom.workspace.getActivePaneItem()).toBe(item) - expect(atom.workspace.getActivePane().getItems().length).toBe(2) - }) - }) + await atom.workspace.open(item); + expect(atom.workspace.getActivePaneItem()).toBe(item); + expect(atom.workspace.getActivePane().getItems().length).toBe(2); + }); + }); describe('when the item already exists in another pane', () => { it('rejects the promise', async () => { - const item = document.createElement('div') + const item = document.createElement('div'); - await atom.workspace.open(item) - await atom.workspace.open(null, { split: 'right' }) - expect(atom.workspace.getActivePaneItem()).not.toBe(item) - expect(atom.workspace.getActivePane().getItems().length).toBe(1) + await atom.workspace.open(item); + await atom.workspace.open(null, { split: 'right' }); + expect(atom.workspace.getActivePaneItem()).not.toBe(item); + expect(atom.workspace.getActivePane().getItems().length).toBe(1); - let rejection + let rejection; try { - await atom.workspace.open(item) + await atom.workspace.open(item); } catch (error) { - rejection = error + rejection = error; } expect(rejection.message).toMatch( /The workspace can only contain one instance of item/ - ) - }) - }) - }) + ); + }); + }); + }); 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) + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitRight(); + expect(workspace.getActivePane()).toBe(pane2); - let editor = null + let editor = null; waitsForPromise(() => workspace.open('a', { split: 'left' }).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(pane1.items).toEqual([editor]) - expect(pane2.items).toEqual([]) - }) + 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() + pane2.focus(); return workspace.open('a', { split: 'left' }).then(o => { - editor = o - }) - }) + editor = o; + }); + }); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(pane1.items).toEqual([editor]) - expect(pane2.items).toEqual([]) - }) - }) - }) + 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', () => { - let editor = null - const pane1 = workspace.getActivePane() - const pane2 = pane1.splitLeft() - pane2.splitDown() - pane1.activate() - expect(workspace.getActivePane()).toBe(pane1) + 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 + editor = o; }) - ) + ); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(pane1.items).toEqual([editor]) - }) - }) - }) + 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', () => { - let editor = null - const pane1 = workspace.getActivePane() - let pane2 = null + let editor = null; + const pane1 = workspace.getActivePane(); + let pane2 = null; waitsForPromise(() => workspace.open('a', { split: 'right' }).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - pane2 = workspace.getPanes().filter(p => p !== pane1)[0] - expect(workspace.getActivePane()).toBe(pane2) - expect(pane1.items).toEqual([]) - expect(pane2.items).toEqual([editor]) - }) + pane2 = workspace.getPanes().filter(p => p !== 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() + pane1.focus(); return workspace.open('a', { split: 'right' }).then(o => { - editor = o - }) - }) + editor = o; + }); + }); runs(() => { - expect(workspace.getActivePane()).toBe(pane2) - expect(pane1.items).toEqual([]) - expect(pane2.items).toEqual([editor]) - }) - }) + 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', () => { - let editor = null - const pane1 = workspace.getActivePane() - const pane2 = pane1.splitRight() - pane2.splitDown() - pane1.activate() - expect(workspace.getActivePane()).toBe(pane1) - let pane4 = null + 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 + editor = o; }) - ) + ); runs(() => { - pane4 = workspace.getPanes().filter(p => p !== pane1)[0] - expect(workspace.getActivePane()).toBe(pane4) - expect(pane4.items).toEqual([editor]) + pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane4); + expect(pane4.items).toEqual([editor]); expect(workspace.getCenter().paneContainer.root.children[0]).toBe( pane1 - ) + ); expect(workspace.getCenter().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', () => { - const pane1 = workspace.getActivePane() - const pane2 = pane1.splitDown() - expect(workspace.getActivePane()).toBe(pane2) + const pane1 = workspace.getActivePane(); + const pane2 = pane1.splitDown(); + expect(workspace.getActivePane()).toBe(pane2); - let editor = null + let editor = null; waitsForPromise(() => workspace.open('a', { split: 'up' }).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(pane1.items).toEqual([editor]) - expect(pane2.items).toEqual([]) - }) + 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() + pane2.focus(); return workspace.open('a', { split: 'up' }).then(o => { - editor = o - }) - }) + editor = o; + }); + }); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(pane1.items).toEqual([editor]) - expect(pane2.items).toEqual([]) - }) - }) - }) + 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', () => { - let editor = null - const pane1 = workspace.getActivePane() - const pane2 = pane1.splitUp() - pane2.splitRight() - pane1.activate() - expect(workspace.getActivePane()).toBe(pane1) + 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 + editor = o; }) - ) + ); runs(() => { - expect(workspace.getActivePane()).toBe(pane1) - expect(pane1.items).toEqual([editor]) - }) - }) - }) + 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', () => { - let editor = null - const pane1 = workspace.getActivePane() - let pane2 = null + let editor = null; + const pane1 = workspace.getActivePane(); + let pane2 = null; waitsForPromise(() => workspace.open('a', { split: 'down' }).then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - pane2 = workspace.getPanes().filter(p => p !== pane1)[0] - expect(workspace.getActivePane()).toBe(pane2) - expect(pane1.items).toEqual([]) - expect(pane2.items).toEqual([editor]) - }) + pane2 = workspace.getPanes().filter(p => p !== 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() + pane1.focus(); return workspace.open('a', { split: 'down' }).then(o => { - editor = o - }) - }) + editor = o; + }); + }); runs(() => { - expect(workspace.getActivePane()).toBe(pane2) - expect(pane1.items).toEqual([]) - expect(pane2.items).toEqual([editor]) - }) - }) + 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', () => { - let editor = null - const pane1 = workspace.getActivePane() - const pane2 = pane1.splitDown() - pane1.activate() - expect(workspace.getActivePane()).toBe(pane1) - let pane4 = null + 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 + editor = o; }) - ) + ); runs(() => { - pane4 = workspace.getPanes().filter(p => p !== pane1)[0] - expect(workspace.getActivePane()).toBe(pane4) - expect(pane4.items).toEqual([editor]) + pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane4); + expect(pane4.items).toEqual([editor]); expect(workspace.getCenter().paneContainer.root.children[0]).toBe( pane1 - ) + ); expect(workspace.getCenter().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]) - ) - }) + ); + }); it('unfolds the fold containing the line', async () => { - let editor + let editor; - await workspace.open('../sample-with-many-folds.js') - editor = workspace.getActiveTextEditor() - editor.foldBufferRow(2) - expect(editor.isFoldedAtBufferRow(2)).toBe(true) - expect(editor.isFoldedAtBufferRow(3)).toBe(true) + await workspace.open('../sample-with-many-folds.js'); + editor = workspace.getActiveTextEditor(); + editor.foldBufferRow(2); + expect(editor.isFoldedAtBufferRow(2)).toBe(true); + expect(editor.isFoldedAtBufferRow(3)).toBe(true); - await workspace.open('../sample-with-many-folds.js', { initialLine: 2 }) - expect(editor.isFoldedAtBufferRow(2)).toBe(false) - expect(editor.isFoldedAtBufferRow(3)).toBe(false) - }) - }) + await workspace.open('../sample-with-many-folds.js', { + initialLine: 2 + }); + expect(editor.isFoldedAtBufferRow(2)).toBe(false); + expect(editor.isFoldedAtBufferRow(3)).toBe(false); + }); + }); describe('when the file size is over the limit defined in `core.warnOnLargeFileLimit`', () => { const shouldPromptForFileOfSize = async (size, shouldPrompt) => { - spyOn(fs, 'getSizeSync').andReturn(size * 1048577) + spyOn(fs, 'getSizeSync').andReturn(size * 1048577); - let selectedButtonIndex = 1 // cancel + let selectedButtonIndex = 1; // cancel atom.applicationDelegate.confirm.andCallFake((options, callback) => callback(selectedButtonIndex) - ) + ); - let editor = await workspace.open('sample.js') + let editor = await workspace.open('sample.js'); if (shouldPrompt) { - expect(editor).toBeUndefined() - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() + expect(editor).toBeUndefined(); + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); - atom.applicationDelegate.confirm.reset() - selectedButtonIndex = 0 // open the file + atom.applicationDelegate.confirm.reset(); + selectedButtonIndex = 0; // open the file - editor = await workspace.open('sample.js') + editor = await workspace.open('sample.js'); - expect(atom.applicationDelegate.confirm).toHaveBeenCalled() + expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); } else { - expect(editor).not.toBeUndefined() + expect(editor).not.toBeUndefined(); } - } + }; it('prompts before opening the file', async () => { - atom.config.set('core.warnOnLargeFileLimit', 20) - await shouldPromptForFileOfSize(20, true) - }) + atom.config.set('core.warnOnLargeFileLimit', 20); + await shouldPromptForFileOfSize(20, true); + }); it("doesn't prompt on files below the limit", async () => { - atom.config.set('core.warnOnLargeFileLimit', 30) - await shouldPromptForFileOfSize(20, false) - }) + atom.config.set('core.warnOnLargeFileLimit', 30); + await shouldPromptForFileOfSize(20, false); + }); it('prompts for smaller files with a lower limit', async () => { - atom.config.set('core.warnOnLargeFileLimit', 5) - await shouldPromptForFileOfSize(10, true) - }) - }) + atom.config.set('core.warnOnLargeFileLimit', 5); + await shouldPromptForFileOfSize(10, true); + }); + }); 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 } + return { foo: pathToOpen, options }; } - } + }; const barOpener = pathToOpen => { if (pathToOpen != null ? pathToOpen.match(/^bar:\/\//) : undefined) { - return { bar: pathToOpen } + return { bar: pathToOpen }; } - } - workspace.addOpener(fooOpener) - workspace.addOpener(barOpener) + }; + workspace.addOpener(fooOpener); + workspace.addOpener(barOpener); waitsForPromise(() => { - const pathToOpen = atom.project.getDirectories()[0].resolve('a.foo') + 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' } }) - ) - }) + ); + }); waitsForPromise(() => workspace .open('bar://baz') .then(item => expect(item).toEqual({ bar: 'bar://baz' })) - ) - }) - }) + ); + }); + }); it("adds the file to the application's recent documents list", () => { if (process.platform !== 'darwin') { - return + return; } // Feature only supported on macOS - spyOn(atom.applicationDelegate, 'addRecentDocument') + spyOn(atom.applicationDelegate, 'addRecentDocument'); - waitsForPromise(() => workspace.open()) + waitsForPromise(() => workspace.open()); 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() - ) + ); - waitsForPromise(() => workspace.open(__filename)) + waitsForPromise(() => workspace.open(__filename)); runs(() => expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith( __filename ) - ) - }) + ); + }); it('notifies ::onDidAddTextEditor observers', () => { - const absolutePath = require.resolve('./fixtures/dir/a') - const newEditorHandler = jasmine.createSpy('newEditorHandler') - workspace.onDidAddTextEditor(newEditorHandler) + const absolutePath = require.resolve('./fixtures/dir/a'); + const newEditorHandler = jasmine.createSpy('newEditorHandler'); + workspace.onDidAddTextEditor(newEditorHandler); - let editor = null + let editor = null; waitsForPromise(() => workspace.open(absolutePath).then(e => { - editor = e + editor = e; }) - ) + ); runs(() => expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor) - ) - }) + ); + }); describe('when there is an error opening the file', () => { - let notificationSpy = null + 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', () => { - waitsForPromise(() => workspace.open('not-a-file.md')) + waitsForPromise(() => workspace.open('not-a-file.md')); runs(() => { - const editor = workspace.getActiveTextEditor() - expect(notificationSpy).not.toHaveBeenCalled() - expect(editor.getPath()).toContain('not-a-file.md') - }) - }) - }) + const 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 => { - const error = new Error(`EACCES, permission denied '${path}'`) - error.path = path - error.code = 'EACCES' - throw error + const error = new Error(`EACCES, permission denied '${path}'`); + error.path = path; + error.code = 'EACCES'; + throw error; }) - ) + ); it('creates a notification', () => { - waitsForPromise(() => workspace.open('file1')) + waitsForPromise(() => workspace.open('file1')); runs(() => { - expect(notificationSpy).toHaveBeenCalled() - const notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('warning') - expect(notification.getMessage()).toContain('Permission denied') - expect(notification.getMessage()).toContain('file1') - }) - }) - }) + expect(notificationSpy).toHaveBeenCalled(); + const 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 => { - const error = new Error(`EPERM, operation not permitted '${path}'`) - error.path = path - error.code = 'EPERM' - throw error + const error = new Error(`EPERM, operation not permitted '${path}'`); + error.path = path; + error.code = 'EPERM'; + throw error; }) - ) + ); it('creates a notification', () => { - waitsForPromise(() => workspace.open('file1')) + waitsForPromise(() => workspace.open('file1')); runs(() => { - expect(notificationSpy).toHaveBeenCalled() - const notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('warning') - expect(notification.getMessage()).toContain('Unable to open') - expect(notification.getMessage()).toContain('file1') - }) - }) - }) + expect(notificationSpy).toHaveBeenCalled(); + const 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 => { - const error = new Error(`EBUSY, resource busy or locked '${path}'`) - error.path = path - error.code = 'EBUSY' - throw error + const 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')) + waitsForPromise(() => workspace.open('file1')); runs(() => { - expect(notificationSpy).toHaveBeenCalled() - const notification = notificationSpy.mostRecentCall.args[0] - expect(notification.getType()).toBe('warning') - expect(notification.getMessage()).toContain('Unable to open') - expect(notification.getMessage()).toContain('file1') - }) - }) - }) + expect(notificationSpy).toHaveBeenCalled(); + const 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!!') + throw new Error('I dont even know what is happening right now!!'); }) - ) + ); it('rejects the promise', () => { waitsFor(done => { workspace.open('file1').catch(error => { expect(error.message).toBe( 'I dont even know what is happening right now!!' - ) - done() - }) - }) - }) - }) - }) + ); + done(); + }); + }); + }); + }); + }); describe('when the file is already open in pending state', () => { it('should terminate the pending state', () => { - let editor = null - let pane = null + let editor = null; + let pane = null; waitsForPromise(() => atom.workspace.open('sample.js', { pending: true }).then(o => { - editor = o - pane = atom.workspace.getActivePane() + editor = o; + pane = atom.workspace.getActivePane(); }) - ) + ); - runs(() => expect(pane.getPendingItem()).toEqual(editor)) + runs(() => expect(pane.getPendingItem()).toEqual(editor)); - waitsForPromise(() => atom.workspace.open('sample.js')) + waitsForPromise(() => atom.workspace.open('sample.js')); - 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', () => { - let editor1 = null - let editor2 = null + let editor1 = null; + let editor2 = null; waitsForPromise(() => atom.workspace.open('sample.txt').then(o => { - editor1 = o + editor1 = o; }) - ) + ); waitsForPromise(() => atom.workspace.open('sample2.txt', { pending: true }).then(o => { - editor2 = o + editor2 = o; }) - ) + ); runs(() => { - const pane = atom.workspace.getActivePane() - pane.activateItem(editor1) - expect(pane.getItems().length).toBe(2) - expect(pane.getItems()).toEqual([editor1, editor2]) - }) - }) - }) + const 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) - let editor1 = null - let editor2 = null - const leftPane = atom.workspace.getActivePane() - let rightPane = null + 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(o => { - editor1 = o - rightPane = atom.workspace.getActivePane() - spyOn(rightPane, 'destroy').andCallThrough() + editor1 = o; + rightPane = atom.workspace.getActivePane(); + spyOn(rightPane, 'destroy').andCallThrough(); }) - ) + ); 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) - }) + 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 + editor2 = o; }) - ) + ); runs(() => { - expect(rightPane.getPendingItem()).toBe(editor2) - expect(rightPane.destroy.callCount).toBe(0) - }) - }) - }) + expect(rightPane.getPendingItem()).toBe(editor2); + expect(rightPane.destroy.callCount).toBe(0); + }); + }); + }); describe("when opening an editor with a buffer that isn't part of the project", () => { it('adds the buffer to the project', async () => { - const buffer = new TextBuffer() - const editor = new TextEditor({ buffer }) + const buffer = new TextBuffer(); + const editor = new TextEditor({ buffer }); - await atom.workspace.open(editor) + await atom.workspace.open(editor); expect(atom.project.getBuffers().map(buffer => buffer.id)).toContain( buffer.id - ) + ); expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' - ) - }) - }) - }) + ); + }); + }); + }); describe('finding items in the workspace', () => { it('can identify the pane and pane container for a given item or URI', () => { - const uri = 'atom://test-pane-for-item' + const uri = 'atom://test-pane-for-item'; const item = { element: document.createElement('div'), - getURI () { - return uri + getURI() { + return uri; } - } + }; - atom.workspace.getActivePane().activateItem(item) + atom.workspace.getActivePane().activateItem(item); expect(atom.workspace.paneForItem(item)).toBe( atom.workspace.getCenter().getActivePane() - ) + ); expect(atom.workspace.paneContainerForItem(item)).toBe( atom.workspace.getCenter() - ) + ); expect(atom.workspace.paneForURI(uri)).toBe( atom.workspace.getCenter().getActivePane() - ) + ); expect(atom.workspace.paneContainerForURI(uri)).toBe( atom.workspace.getCenter() - ) + ); - atom.workspace.getActivePane().destroyActiveItem() + atom.workspace.getActivePane().destroyActiveItem(); atom.workspace .getLeftDock() .getActivePane() - .activateItem(item) + .activateItem(item); expect(atom.workspace.paneForItem(item)).toBe( atom.workspace.getLeftDock().getActivePane() - ) + ); expect(atom.workspace.paneContainerForItem(item)).toBe( atom.workspace.getLeftDock() - ) + ); expect(atom.workspace.paneForURI(uri)).toBe( atom.workspace.getLeftDock().getActivePane() - ) + ); expect(atom.workspace.paneContainerForURI(uri)).toBe( atom.workspace.getLeftDock() - ) - }) - }) + ); + }); + }); describe('::hide(uri)', () => { - let item - const URI = 'atom://hide-test' + let item; + const URI = 'atom://hide-test'; beforeEach(() => { - const el = document.createElement('div') + const el = document.createElement('div'); item = { getTitle: () => 'Item', getElement: () => el, getURI: () => URI - } - }) + }; + }); describe('when called with a URI', () => { it('if the item for the given URI is in the center, removes it', () => { - const pane = atom.workspace.getActivePane() - pane.addItem(item) - atom.workspace.hide(URI) - expect(pane.getItems().length).toBe(0) - }) + const pane = atom.workspace.getActivePane(); + pane.addItem(item); + atom.workspace.hide(URI); + expect(pane.getItems().length).toBe(0); + }); it('if the item for the given URI is in a dock, hides the dock', () => { - const dock = atom.workspace.getLeftDock() - const pane = dock.getActivePane() - pane.addItem(item) - dock.activate() - expect(dock.isVisible()).toBe(true) - const itemFound = atom.workspace.hide(URI) - expect(itemFound).toBe(true) - expect(dock.isVisible()).toBe(false) - }) - }) + const dock = atom.workspace.getLeftDock(); + const pane = dock.getActivePane(); + pane.addItem(item); + dock.activate(); + expect(dock.isVisible()).toBe(true); + const itemFound = atom.workspace.hide(URI); + expect(itemFound).toBe(true); + expect(dock.isVisible()).toBe(false); + }); + }); describe('when called with an item', () => { it('if the item is in the center, removes it', () => { - const pane = atom.workspace.getActivePane() - pane.addItem(item) - atom.workspace.hide(item) - expect(pane.getItems().length).toBe(0) - }) + const pane = atom.workspace.getActivePane(); + pane.addItem(item); + atom.workspace.hide(item); + expect(pane.getItems().length).toBe(0); + }); it('if the item is in a dock, hides the dock', () => { - const dock = atom.workspace.getLeftDock() - const pane = dock.getActivePane() - pane.addItem(item) - dock.activate() - expect(dock.isVisible()).toBe(true) - const itemFound = atom.workspace.hide(item) - expect(itemFound).toBe(true) - expect(dock.isVisible()).toBe(false) - }) - }) - }) + const dock = atom.workspace.getLeftDock(); + const pane = dock.getActivePane(); + pane.addItem(item); + dock.activate(); + expect(dock.isVisible()).toBe(true); + const itemFound = atom.workspace.hide(item); + expect(itemFound).toBe(true); + expect(dock.isVisible()).toBe(false); + }); + }); + }); describe('::toggle(itemOrUri)', () => { describe('when the location resolves to a dock', () => { it('adds or shows the item and its dock if it is not currently visible, and otherwise hides the containing dock', async () => { const item1 = { - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getElement () { - return (this.element = document.createElement('div')) + getElement() { + return (this.element = document.createElement('div')); } - } + }; const item2 = { - getDefaultLocation () { - return 'left' + getDefaultLocation() { + return 'left'; }, - getElement () { - return (this.element = document.createElement('div')) + getElement() { + return (this.element = document.createElement('div')); } - } + }; - const dock = workspace.getLeftDock() - expect(dock.isVisible()).toBe(false) + const dock = workspace.getLeftDock(); + expect(dock.isVisible()).toBe(false); - await workspace.toggle(item1) - expect(dock.isVisible()).toBe(true) - expect(dock.getActivePaneItem()).toBe(item1) + await workspace.toggle(item1); + expect(dock.isVisible()).toBe(true); + expect(dock.getActivePaneItem()).toBe(item1); - await workspace.toggle(item2) - expect(dock.isVisible()).toBe(true) - expect(dock.getActivePaneItem()).toBe(item2) + await workspace.toggle(item2); + expect(dock.isVisible()).toBe(true); + expect(dock.getActivePaneItem()).toBe(item2); - await workspace.toggle(item1) - expect(dock.isVisible()).toBe(true) - expect(dock.getActivePaneItem()).toBe(item1) + await workspace.toggle(item1); + expect(dock.isVisible()).toBe(true); + expect(dock.getActivePaneItem()).toBe(item1); - await workspace.toggle(item1) - expect(dock.isVisible()).toBe(false) - expect(dock.getActivePaneItem()).toBe(item1) + await workspace.toggle(item1); + expect(dock.isVisible()).toBe(false); + expect(dock.getActivePaneItem()).toBe(item1); - await workspace.toggle(item2) - expect(dock.isVisible()).toBe(true) - expect(dock.getActivePaneItem()).toBe(item2) - }) - }) + await workspace.toggle(item2); + expect(dock.isVisible()).toBe(true); + expect(dock.getActivePaneItem()).toBe(item2); + }); + }); describe('when the location resolves to the center', () => { it('adds or shows the item if it is not currently the active pane item, and otherwise removes the item', async () => { const item1 = { - getDefaultLocation () { - return 'center' + getDefaultLocation() { + return 'center'; }, - getElement () { - return (this.element = document.createElement('div')) + getElement() { + return (this.element = document.createElement('div')); } - } + }; const item2 = { - getDefaultLocation () { - return 'center' + getDefaultLocation() { + return 'center'; }, - getElement () { - return (this.element = document.createElement('div')) + getElement() { + return (this.element = document.createElement('div')); } - } + }; - expect(workspace.getActivePaneItem()).toBeUndefined() - await workspace.toggle(item1) - expect(workspace.getActivePaneItem()).toBe(item1) - await workspace.toggle(item2) - expect(workspace.getActivePaneItem()).toBe(item2) - await workspace.toggle(item1) - expect(workspace.getActivePaneItem()).toBe(item1) - await workspace.toggle(item1) - expect(workspace.paneForItem(item1)).toBeUndefined() - expect(workspace.getActivePaneItem()).toBe(item2) - }) - }) - }) + expect(workspace.getActivePaneItem()).toBeUndefined(); + await workspace.toggle(item1); + expect(workspace.getActivePaneItem()).toBe(item1); + await workspace.toggle(item2); + expect(workspace.getActivePaneItem()).toBe(item2); + await workspace.toggle(item1); + expect(workspace.getActivePaneItem()).toBe(item1); + await workspace.toggle(item1); + expect(workspace.paneForItem(item1)).toBeUndefined(); + expect(workspace.getActivePaneItem()).toBe(item2); + }); + }); + }); describe('active pane containers', () => { it('maintains the active pane and item globally across active pane containers', () => { - const leftDock = workspace.getLeftDock() - const leftItem1 = { element: document.createElement('div') } - const leftItem2 = { element: document.createElement('div') } - const leftItem3 = { element: document.createElement('div') } - const leftPane1 = leftDock.getActivePane() - leftPane1.addItems([leftItem1, leftItem2]) - const leftPane2 = leftPane1.splitDown({ items: [leftItem3] }) + const leftDock = workspace.getLeftDock(); + const leftItem1 = { element: document.createElement('div') }; + const leftItem2 = { element: document.createElement('div') }; + const leftItem3 = { element: document.createElement('div') }; + const leftPane1 = leftDock.getActivePane(); + leftPane1.addItems([leftItem1, leftItem2]); + const leftPane2 = leftPane1.splitDown({ items: [leftItem3] }); - const rightDock = workspace.getRightDock() - const rightItem1 = { element: document.createElement('div') } - const rightItem2 = { element: document.createElement('div') } - const rightItem3 = { element: document.createElement('div') } - const rightPane1 = rightDock.getActivePane() - rightPane1.addItems([rightItem1, rightItem2]) - const rightPane2 = rightPane1.splitDown({ items: [rightItem3] }) + const rightDock = workspace.getRightDock(); + const rightItem1 = { element: document.createElement('div') }; + const rightItem2 = { element: document.createElement('div') }; + const rightItem3 = { element: document.createElement('div') }; + const rightPane1 = rightDock.getActivePane(); + rightPane1.addItems([rightItem1, rightItem2]); + const rightPane2 = rightPane1.splitDown({ items: [rightItem3] }); - const bottomDock = workspace.getBottomDock() - const bottomItem1 = { element: document.createElement('div') } - const bottomItem2 = { element: document.createElement('div') } - const bottomItem3 = { element: document.createElement('div') } - const bottomPane1 = bottomDock.getActivePane() - bottomPane1.addItems([bottomItem1, bottomItem2]) - const bottomPane2 = bottomPane1.splitDown({ items: [bottomItem3] }) + const bottomDock = workspace.getBottomDock(); + const bottomItem1 = { element: document.createElement('div') }; + const bottomItem2 = { element: document.createElement('div') }; + const bottomItem3 = { element: document.createElement('div') }; + const bottomPane1 = bottomDock.getActivePane(); + bottomPane1.addItems([bottomItem1, bottomItem2]); + const bottomPane2 = bottomPane1.splitDown({ items: [bottomItem3] }); - const center = workspace.getCenter() - const centerItem1 = { element: document.createElement('div') } - const centerItem2 = { element: document.createElement('div') } - const centerItem3 = { element: document.createElement('div') } - const centerPane1 = center.getActivePane() - centerPane1.addItems([centerItem1, centerItem2]) - const centerPane2 = centerPane1.splitDown({ items: [centerItem3] }) + const center = workspace.getCenter(); + const centerItem1 = { element: document.createElement('div') }; + const centerItem2 = { element: document.createElement('div') }; + const centerItem3 = { element: document.createElement('div') }; + const centerPane1 = center.getActivePane(); + centerPane1.addItems([centerItem1, centerItem2]); + const centerPane2 = centerPane1.splitDown({ items: [centerItem3] }); - const activePaneContainers = [] - const activePanes = [] - const activeItems = [] + const activePaneContainers = []; + const activePanes = []; + const activeItems = []; workspace.onDidChangeActivePaneContainer(container => activePaneContainers.push(container) - ) - workspace.onDidChangeActivePane(pane => activePanes.push(pane)) - workspace.onDidChangeActivePaneItem(item => activeItems.push(item)) - function clearEvents () { - activePaneContainers.length = 0 - activePanes.length = 0 - activeItems.length = 0 + ); + workspace.onDidChangeActivePane(pane => activePanes.push(pane)); + workspace.onDidChangeActivePaneItem(item => activeItems.push(item)); + function clearEvents() { + activePaneContainers.length = 0; + activePanes.length = 0; + activeItems.length = 0; } - expect(workspace.getActivePaneContainer()).toBe(center) - expect(workspace.getActivePane()).toBe(centerPane2) - expect(workspace.getActivePaneItem()).toBe(centerItem3) + expect(workspace.getActivePaneContainer()).toBe(center); + expect(workspace.getActivePane()).toBe(centerPane2); + expect(workspace.getActivePaneItem()).toBe(centerItem3); - leftDock.activate() - expect(workspace.getActivePaneContainer()).toBe(leftDock) - expect(workspace.getActivePane()).toBe(leftPane2) - expect(workspace.getActivePaneItem()).toBe(leftItem3) - expect(activePaneContainers).toEqual([leftDock]) - expect(activePanes).toEqual([leftPane2]) - expect(activeItems).toEqual([leftItem3]) + leftDock.activate(); + expect(workspace.getActivePaneContainer()).toBe(leftDock); + expect(workspace.getActivePane()).toBe(leftPane2); + expect(workspace.getActivePaneItem()).toBe(leftItem3); + expect(activePaneContainers).toEqual([leftDock]); + expect(activePanes).toEqual([leftPane2]); + expect(activeItems).toEqual([leftItem3]); - clearEvents() - leftPane1.activate() - leftPane1.activate() - expect(workspace.getActivePaneContainer()).toBe(leftDock) - expect(workspace.getActivePane()).toBe(leftPane1) - expect(workspace.getActivePaneItem()).toBe(leftItem1) - expect(activePaneContainers).toEqual([]) - expect(activePanes).toEqual([leftPane1]) - expect(activeItems).toEqual([leftItem1]) + clearEvents(); + leftPane1.activate(); + leftPane1.activate(); + expect(workspace.getActivePaneContainer()).toBe(leftDock); + expect(workspace.getActivePane()).toBe(leftPane1); + expect(workspace.getActivePaneItem()).toBe(leftItem1); + expect(activePaneContainers).toEqual([]); + expect(activePanes).toEqual([leftPane1]); + expect(activeItems).toEqual([leftItem1]); - clearEvents() - leftPane1.activateItem(leftItem2) - leftPane1.activateItem(leftItem2) - expect(workspace.getActivePaneContainer()).toBe(leftDock) - expect(workspace.getActivePane()).toBe(leftPane1) - expect(workspace.getActivePaneItem()).toBe(leftItem2) - expect(activePaneContainers).toEqual([]) - expect(activePanes).toEqual([]) - expect(activeItems).toEqual([leftItem2]) + clearEvents(); + leftPane1.activateItem(leftItem2); + leftPane1.activateItem(leftItem2); + expect(workspace.getActivePaneContainer()).toBe(leftDock); + expect(workspace.getActivePane()).toBe(leftPane1); + expect(workspace.getActivePaneItem()).toBe(leftItem2); + expect(activePaneContainers).toEqual([]); + expect(activePanes).toEqual([]); + expect(activeItems).toEqual([leftItem2]); - clearEvents() - expect(rightDock.getActivePane()).toBe(rightPane2) - rightPane1.activate() - rightPane1.activate() - expect(workspace.getActivePaneContainer()).toBe(rightDock) - expect(workspace.getActivePane()).toBe(rightPane1) - expect(workspace.getActivePaneItem()).toBe(rightItem1) - expect(activePaneContainers).toEqual([rightDock]) - expect(activePanes).toEqual([rightPane1]) - expect(activeItems).toEqual([rightItem1]) + clearEvents(); + expect(rightDock.getActivePane()).toBe(rightPane2); + rightPane1.activate(); + rightPane1.activate(); + expect(workspace.getActivePaneContainer()).toBe(rightDock); + expect(workspace.getActivePane()).toBe(rightPane1); + expect(workspace.getActivePaneItem()).toBe(rightItem1); + expect(activePaneContainers).toEqual([rightDock]); + expect(activePanes).toEqual([rightPane1]); + expect(activeItems).toEqual([rightItem1]); - clearEvents() - rightPane1.activateItem(rightItem2) - expect(workspace.getActivePaneContainer()).toBe(rightDock) - expect(workspace.getActivePane()).toBe(rightPane1) - expect(workspace.getActivePaneItem()).toBe(rightItem2) - expect(activePaneContainers).toEqual([]) - expect(activePanes).toEqual([]) - expect(activeItems).toEqual([rightItem2]) + clearEvents(); + rightPane1.activateItem(rightItem2); + expect(workspace.getActivePaneContainer()).toBe(rightDock); + expect(workspace.getActivePane()).toBe(rightPane1); + expect(workspace.getActivePaneItem()).toBe(rightItem2); + expect(activePaneContainers).toEqual([]); + expect(activePanes).toEqual([]); + expect(activeItems).toEqual([rightItem2]); - clearEvents() - expect(bottomDock.getActivePane()).toBe(bottomPane2) - bottomPane2.activate() - bottomPane2.activate() - expect(workspace.getActivePaneContainer()).toBe(bottomDock) - expect(workspace.getActivePane()).toBe(bottomPane2) - expect(workspace.getActivePaneItem()).toBe(bottomItem3) - expect(activePaneContainers).toEqual([bottomDock]) - expect(activePanes).toEqual([bottomPane2]) - expect(activeItems).toEqual([bottomItem3]) + clearEvents(); + expect(bottomDock.getActivePane()).toBe(bottomPane2); + bottomPane2.activate(); + bottomPane2.activate(); + expect(workspace.getActivePaneContainer()).toBe(bottomDock); + expect(workspace.getActivePane()).toBe(bottomPane2); + expect(workspace.getActivePaneItem()).toBe(bottomItem3); + expect(activePaneContainers).toEqual([bottomDock]); + expect(activePanes).toEqual([bottomPane2]); + expect(activeItems).toEqual([bottomItem3]); - clearEvents() - center.activate() - center.activate() - expect(workspace.getActivePaneContainer()).toBe(center) - expect(workspace.getActivePane()).toBe(centerPane2) - expect(workspace.getActivePaneItem()).toBe(centerItem3) - expect(activePaneContainers).toEqual([center]) - expect(activePanes).toEqual([centerPane2]) - expect(activeItems).toEqual([centerItem3]) + clearEvents(); + center.activate(); + center.activate(); + expect(workspace.getActivePaneContainer()).toBe(center); + expect(workspace.getActivePane()).toBe(centerPane2); + expect(workspace.getActivePaneItem()).toBe(centerItem3); + expect(activePaneContainers).toEqual([center]); + expect(activePanes).toEqual([centerPane2]); + expect(activeItems).toEqual([centerItem3]); - clearEvents() - centerPane1.activate() - centerPane1.activate() - expect(workspace.getActivePaneContainer()).toBe(center) - expect(workspace.getActivePane()).toBe(centerPane1) - expect(workspace.getActivePaneItem()).toBe(centerItem1) - expect(activePaneContainers).toEqual([]) - expect(activePanes).toEqual([centerPane1]) - expect(activeItems).toEqual([centerItem1]) - }) - }) + clearEvents(); + centerPane1.activate(); + centerPane1.activate(); + expect(workspace.getActivePaneContainer()).toBe(center); + expect(workspace.getActivePane()).toBe(centerPane1); + expect(workspace.getActivePaneItem()).toBe(centerItem1); + expect(activePaneContainers).toEqual([]); + expect(activePanes).toEqual([centerPane1]); + expect(activeItems).toEqual([centerItem1]); + }); + }); describe('::onDidStopChangingActivePaneItem()', () => { it('invokes observers when the active item of the active pane stops changing', () => { - const pane1 = atom.workspace.getCenter().getActivePane() + const pane1 = atom.workspace.getCenter().getActivePane(); const pane2 = pane1.splitRight({ items: [document.createElement('div'), document.createElement('div')] - }) + }); atom.workspace .getLeftDock() .getActivePane() - .addItem(document.createElement('div')) + .addItem(document.createElement('div')); - const emittedItems = [] + const emittedItems = []; atom.workspace.onDidStopChangingActivePaneItem(item => emittedItems.push(item) - ) + ); - pane2.activateNextItem() - pane2.activateNextItem() - pane1.activate() - atom.workspace.getLeftDock().activate() + pane2.activateNextItem(); + pane2.activateNextItem(); + pane1.activate(); + atom.workspace.getLeftDock().activate(); - advanceClock(100) + advanceClock(100); expect(emittedItems).toEqual([ atom.workspace.getLeftDock().getActivePaneItem() - ]) - }) - }) + ]); + }); + }); describe('the grammar-used hook', () => { it('fires when opening a file or changing the grammar of an open file', async () => { - await atom.packages.activatePackage('language-javascript') - await atom.packages.activatePackage('language-coffee-script') + await atom.packages.activatePackage('language-javascript'); + await atom.packages.activatePackage('language-coffee-script'); - const observeTextEditorsSpy = jasmine.createSpy('observeTextEditors') - const javascriptGrammarUsed = jasmine.createSpy('javascript') - const coffeeScriptGrammarUsed = jasmine.createSpy('coffeescript') + const observeTextEditorsSpy = jasmine.createSpy('observeTextEditors'); + const javascriptGrammarUsed = jasmine.createSpy('javascript'); + const coffeeScriptGrammarUsed = jasmine.createSpy('coffeescript'); - atom.packages.triggerDeferredActivationHooks() + atom.packages.triggerDeferredActivationHooks(); atom.packages.onDidTriggerActivationHook( 'language-javascript:grammar-used', () => { - atom.workspace.observeTextEditors(observeTextEditorsSpy) - javascriptGrammarUsed() + atom.workspace.observeTextEditors(observeTextEditorsSpy); + javascriptGrammarUsed(); } - ) + ); atom.packages.onDidTriggerActivationHook( 'language-coffee-script:grammar-used', coffeeScriptGrammarUsed - ) + ); - expect(javascriptGrammarUsed).not.toHaveBeenCalled() - expect(observeTextEditorsSpy).not.toHaveBeenCalled() + expect(javascriptGrammarUsed).not.toHaveBeenCalled(); + expect(observeTextEditorsSpy).not.toHaveBeenCalled(); const editor = await atom.workspace.open('sample.js', { autoIndent: false - }) - expect(javascriptGrammarUsed).toHaveBeenCalled() - expect(observeTextEditorsSpy.callCount).toBe(1) + }); + expect(javascriptGrammarUsed).toHaveBeenCalled(); + expect(observeTextEditorsSpy.callCount).toBe(1); - expect(coffeeScriptGrammarUsed).not.toHaveBeenCalled() - atom.grammars.assignLanguageMode(editor, 'source.coffee') - expect(coffeeScriptGrammarUsed).toHaveBeenCalled() - }) - }) + expect(coffeeScriptGrammarUsed).not.toHaveBeenCalled(); + atom.grammars.assignLanguageMode(editor, 'source.coffee'); + expect(coffeeScriptGrammarUsed).toHaveBeenCalled(); + }); + }); describe('the root-scope-used hook', () => { it('fires when opening a file or changing the grammar of an open file', async () => { - await atom.packages.activatePackage('language-javascript') - await atom.packages.activatePackage('language-coffee-script') + await atom.packages.activatePackage('language-javascript'); + await atom.packages.activatePackage('language-coffee-script'); - const observeTextEditorsSpy = jasmine.createSpy('observeTextEditors') - const javascriptGrammarUsed = jasmine.createSpy('javascript') - const coffeeScriptGrammarUsed = jasmine.createSpy('coffeescript') + const observeTextEditorsSpy = jasmine.createSpy('observeTextEditors'); + const javascriptGrammarUsed = jasmine.createSpy('javascript'); + const coffeeScriptGrammarUsed = jasmine.createSpy('coffeescript'); - atom.packages.triggerDeferredActivationHooks() + atom.packages.triggerDeferredActivationHooks(); atom.packages.onDidTriggerActivationHook( 'source.js:root-scope-used', () => { - atom.workspace.observeTextEditors(observeTextEditorsSpy) - javascriptGrammarUsed() + atom.workspace.observeTextEditors(observeTextEditorsSpy); + javascriptGrammarUsed(); } - ) + ); atom.packages.onDidTriggerActivationHook( 'source.coffee:root-scope-used', coffeeScriptGrammarUsed - ) + ); - expect(javascriptGrammarUsed).not.toHaveBeenCalled() - expect(observeTextEditorsSpy).not.toHaveBeenCalled() + expect(javascriptGrammarUsed).not.toHaveBeenCalled(); + expect(observeTextEditorsSpy).not.toHaveBeenCalled(); const editor = await atom.workspace.open('sample.js', { autoIndent: false - }) - expect(javascriptGrammarUsed).toHaveBeenCalled() - expect(observeTextEditorsSpy.callCount).toBe(1) + }); + expect(javascriptGrammarUsed).toHaveBeenCalled(); + expect(observeTextEditorsSpy.callCount).toBe(1); - expect(coffeeScriptGrammarUsed).not.toHaveBeenCalled() - atom.grammars.assignLanguageMode(editor, 'source.coffee') - expect(coffeeScriptGrammarUsed).toHaveBeenCalled() - }) - }) + expect(coffeeScriptGrammarUsed).not.toHaveBeenCalled(); + atom.grammars.assignLanguageMode(editor, 'source.coffee'); + expect(coffeeScriptGrammarUsed).toHaveBeenCalled(); + }); + }); describe('::reopenItem()', () => { it("opens the uri associated with the last closed pane that isn't currently open", () => { - const pane = workspace.getActivePane() + const pane = workspace.getActivePane(); waitsForPromise(() => workspace .open('a') @@ -1657,334 +1663,334 @@ describe('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() - }) + expect(workspace.getActivePaneItem().getURI()).toBeUndefined(); + pane.destroyActiveItem(); + }); - waitsForPromise(() => workspace.reopenItem()) + waitsForPromise(() => workspace.reopenItem()); - const firstDirectory = atom.project.getDirectories()[0] - expect(firstDirectory).toBeDefined() + const firstDirectory = atom.project.getDirectories()[0]; + expect(firstDirectory).toBeDefined(); runs(() => { - expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined() + expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined(); // destroy all items expect(workspace.getActivePaneItem().getURI()).toBe( firstDirectory.resolve('file1') - ) - pane.destroyActiveItem() + ); + pane.destroyActiveItem(); expect(workspace.getActivePaneItem().getURI()).toBe( firstDirectory.resolve('b') - ) - pane.destroyActiveItem() + ); + pane.destroyActiveItem(); expect(workspace.getActivePaneItem().getURI()).toBe( firstDirectory.resolve('a') - ) - pane.destroyActiveItem() + ); + pane.destroyActiveItem(); // reopens items with uris - expect(workspace.getActivePaneItem()).toBeUndefined() - }) + expect(workspace.getActivePaneItem()).toBeUndefined(); + }); - waitsForPromise(() => workspace.reopenItem()) + waitsForPromise(() => workspace.reopenItem()); runs(() => expect(workspace.getActivePaneItem().getURI()).toBe( firstDirectory.resolve('a') ) - ) + ); // does not reopen items that are already open - waitsForPromise(() => workspace.open('b')) + waitsForPromise(() => workspace.open('b')); runs(() => expect(workspace.getActivePaneItem().getURI()).toBe( firstDirectory.resolve('b') ) - ) + ); - waitsForPromise(() => workspace.reopenItem()) + waitsForPromise(() => workspace.reopenItem()); runs(() => expect(workspace.getActivePaneItem().getURI()).toBe( firstDirectory.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) - }) - }) + 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", () => { - const originalFontSize = atom.config.get('editor.fontSize') + 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() - expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) - }) + 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', () => { - const originalFontSize = atom.config.get('editor.fontSize') + const originalFontSize = atom.config.get('editor.fontSize'); - workspace.resetFontSize() - expect(atom.config.get('editor.fontSize')).toBe(originalFontSize) - }) + workspace.resetFontSize(); + expect(atom.config.get('editor.fontSize')).toBe(originalFontSize); + }); it("resets the font size when the editor's font size changes", () => { - const originalFontSize = atom.config.get('editor.fontSize') + 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() - 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); + 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()) + 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) - }) - }) + 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('::getActiveTextEditor()', () => { describe("when the workspace center's active pane item is a text editor", () => { describe('when the workspace center has focus', () => { it('returns the text editor', () => { - const workspaceCenter = workspace.getCenter() - const editor = new TextEditor() - workspaceCenter.getActivePane().activateItem(editor) - workspaceCenter.activate() + const workspaceCenter = workspace.getCenter(); + const editor = new TextEditor(); + workspaceCenter.getActivePane().activateItem(editor); + workspaceCenter.activate(); - expect(workspace.getActiveTextEditor()).toBe(editor) - }) - }) + expect(workspace.getActiveTextEditor()).toBe(editor); + }); + }); describe('when a dock has focus', () => { it('returns the text editor', () => { - const workspaceCenter = workspace.getCenter() - const editor = new TextEditor() - workspaceCenter.getActivePane().activateItem(editor) - workspace.getLeftDock().activate() + const workspaceCenter = workspace.getCenter(); + const editor = new TextEditor(); + workspaceCenter.getActivePane().activateItem(editor); + workspace.getLeftDock().activate(); - expect(workspace.getActiveTextEditor()).toBe(editor) - }) - }) - }) + expect(workspace.getActiveTextEditor()).toBe(editor); + }); + }); + }); describe("when the workspace center's active pane item is not a text editor", () => { it('returns undefined', () => { - const workspaceCenter = workspace.getCenter() - const nonEditorItem = document.createElement('div') - workspaceCenter.getActivePane().activateItem(nonEditorItem) + const workspaceCenter = workspace.getCenter(); + const nonEditorItem = document.createElement('div'); + workspaceCenter.getActivePane().activateItem(nonEditorItem); - expect(workspace.getActiveTextEditor()).toBeUndefined() - }) - }) - }) + expect(workspace.getActiveTextEditor()).toBeUndefined(); + }); + }); + }); describe('::observeTextEditors()', () => { it('invokes the observer with current and future text editors', () => { - const observed = [] + const observed = []; - waitsForPromise(() => workspace.open()) - waitsForPromise(() => workspace.open()) - waitsForPromise(() => workspace.openLicense()) + waitsForPromise(() => workspace.open()); + waitsForPromise(() => workspace.open()); + waitsForPromise(() => workspace.openLicense()); - runs(() => workspace.observeTextEditors(editor => observed.push(editor))) + runs(() => workspace.observeTextEditors(editor => observed.push(editor))); - waitsForPromise(() => workspace.open()) + waitsForPromise(() => workspace.open()); - expect(observed).toEqual(workspace.getTextEditors()) - }) - }) + expect(observed).toEqual(workspace.getTextEditors()); + }); + }); describe('::observeActiveTextEditor()', () => { it('invokes the observer with current active text editor and each time a different text editor becomes active', () => { - const pane = workspace.getCenter().getActivePane() - const observed = [] + const pane = workspace.getCenter().getActivePane(); + const observed = []; - const inactiveEditorBeforeRegisteringObserver = new TextEditor() - const activeEditorBeforeRegisteringObserver = new TextEditor() - pane.activateItem(inactiveEditorBeforeRegisteringObserver) - pane.activateItem(activeEditorBeforeRegisteringObserver) + const inactiveEditorBeforeRegisteringObserver = new TextEditor(); + const activeEditorBeforeRegisteringObserver = new TextEditor(); + pane.activateItem(inactiveEditorBeforeRegisteringObserver); + pane.activateItem(activeEditorBeforeRegisteringObserver); - workspace.observeActiveTextEditor(editor => observed.push(editor)) + workspace.observeActiveTextEditor(editor => observed.push(editor)); - const editorAddedAfterRegisteringObserver = new TextEditor() - pane.activateItem(editorAddedAfterRegisteringObserver) + const editorAddedAfterRegisteringObserver = new TextEditor(); + pane.activateItem(editorAddedAfterRegisteringObserver); expect(observed).toEqual([ activeEditorBeforeRegisteringObserver, editorAddedAfterRegisteringObserver - ]) - }) - }) + ]); + }); + }); describe('::onDidChangeActiveTextEditor()', () => { - let center, pane, observed + let center, pane, observed; beforeEach(() => { - center = workspace.getCenter() - pane = center.getActivePane() - observed = [] - }) + center = workspace.getCenter(); + pane = center.getActivePane(); + observed = []; + }); it("invokes the observer when a text editor becomes the workspace center's active pane item while a dock has focus", () => { - workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)) + workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); - const dock = workspace.getLeftDock() - dock.activate() - expect(atom.workspace.getActivePaneContainer()).toBe(dock) + const dock = workspace.getLeftDock(); + dock.activate(); + expect(atom.workspace.getActivePaneContainer()).toBe(dock); - const editor = new TextEditor() - center.getActivePane().activateItem(editor) - expect(atom.workspace.getActivePaneContainer()).toBe(dock) + const editor = new TextEditor(); + center.getActivePane().activateItem(editor); + expect(atom.workspace.getActivePaneContainer()).toBe(dock); - expect(observed).toEqual([editor]) - }) + expect(observed).toEqual([editor]); + }); it('invokes the observer when the last text editor is closed', () => { - const editor = new TextEditor() - pane.activateItem(editor) + const editor = new TextEditor(); + pane.activateItem(editor); - workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)) - pane.destroyItem(editor) - expect(observed).toEqual([undefined]) - }) + workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); + pane.destroyItem(editor); + expect(observed).toEqual([undefined]); + }); it("invokes the observer when the workspace center's active pane item changes from an editor item to a non-editor item", () => { - const editor = new TextEditor() - const nonEditorItem = document.createElement('div') - pane.activateItem(editor) + const editor = new TextEditor(); + const nonEditorItem = document.createElement('div'); + pane.activateItem(editor); - workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)) - pane.activateItem(nonEditorItem) - expect(observed).toEqual([undefined]) - }) + workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); + pane.activateItem(nonEditorItem); + expect(observed).toEqual([undefined]); + }); it("does not invoke the observer when the workspace center's active pane item changes from a non-editor item to another non-editor item", () => { - workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)) + workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); - const nonEditorItem1 = document.createElement('div') - const nonEditorItem2 = document.createElement('div') - pane.activateItem(nonEditorItem1) - pane.activateItem(nonEditorItem2) + const nonEditorItem1 = document.createElement('div'); + const nonEditorItem2 = document.createElement('div'); + pane.activateItem(nonEditorItem1); + pane.activateItem(nonEditorItem2); - expect(observed).toEqual([]) - }) + expect(observed).toEqual([]); + }); it('invokes the observer when closing the one and only text editor after deserialization', async () => { - pane.activateItem(new TextEditor()) + pane.activateItem(new TextEditor()); - simulateReload() + simulateReload(); runs(() => { - workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)) - workspace.closeActivePaneItemOrEmptyPaneOrWindow() - expect(observed).toEqual([undefined]) - }) - }) - }) + workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); + workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + expect(observed).toEqual([undefined]); + }); + }); + }); describe('when an editor is destroyed', () => { it('removes the editor', async () => { - const editor = await workspace.open('a') - expect(workspace.getTextEditors()).toHaveLength(1) - editor.destroy() - expect(workspace.getTextEditors()).toHaveLength(0) - }) - }) + const editor = await workspace.open('a'); + 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', async () => { - await atom.packages.activatePackage('language-javascript') + await atom.packages.activatePackage('language-javascript'); - const editor = await workspace.open('a') + const editor = await workspace.open('a'); - atom.grammars.assignLanguageMode(editor, 'source.js') - expect(editor.getGrammar().name).toBe('JavaScript') + atom.grammars.assignLanguageMode(editor, 'source.js'); + expect(editor.getGrammar().name).toBe('JavaScript'); - workspace.getActivePane().splitRight({ copyActiveItem: true }) - const newEditor = workspace.getActiveTextEditor() - expect(newEditor).not.toBe(editor) - expect(newEditor.getGrammar().name).toBe('JavaScript') - }) - }) + workspace.getActivePane().splitRight({ copyActiveItem: true }); + const 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-javascript')); 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')); runs(() => { atom.workspace.getActiveTextEditor().setText(dedent` i = /test/; #FIXME\ - `) + `); const atom2 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate - }) + }); atom2.initialize({ 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.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 @@ -2000,435 +2006,435 @@ describe('Workspace', () => { 'source.litcoffee', 'text.plain.null-grammar', 'text.todo' - ]) + ]); - atom2.destroy() - }) - }) + 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/) - }) - }) + 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", () => { - const item = atom.workspace.getActivePaneItem() + const item = atom.workspace.getActivePaneItem(); const pathEscaped = fs.tildify( escapeStringRegex(path.dirname(item.getPath())) - ) + ); 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", () => { - const editor = atom.workspace.getActivePaneItem() - editor.buffer.setPath(path.join(temp.dir, 'hi')) + const editor = atom.workspace.getActivePaneItem(); + editor.buffer.setPath(path.join(temp.dir, 'hi')); const pathEscaped = fs.tildify( escapeStringRegex(path.dirname(editor.getPath())) - ) + ); 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", () => { - atom.workspace.getActivePane().activateNextItem() - const item = atom.workspace.getActivePaneItem() + atom.workspace.getActivePane().activateNextItem(); + const item = atom.workspace.getActivePaneItem(); const pathEscaped = fs.tildify( escapeStringRegex(path.dirname(item.getPath())) - ) + ); expect(document.title).toMatch( new RegExp(`^${item.getTitle()} \\u2014 ${pathEscaped}`) - ) - }) - }) + ); + }); + }); 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() - expect(document.title).toBe(initialTitle) - }) - }) - }) + const pane = atom.workspace.getActivePane(); + pane.splitRight(); + const 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'))) + 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", () => { - const item = atom.workspace.getActivePaneItem() + const item = atom.workspace.getActivePaneItem(); const pathEscaped = fs.tildify( escapeStringRegex(atom.project.getPaths()[0]) - ) + ); 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", () => { - const editor = atom.workspace.getActivePaneItem() - editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')) + const editor = atom.workspace.getActivePaneItem(); + editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi')); const pathEscaped = fs.tildify( escapeStringRegex(atom.project.getPaths()[0]) - ) + ); 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", () => { - atom.workspace.getActivePane().activateNextItem() - const item = atom.workspace.getActivePaneItem() + atom.workspace.getActivePane().activateNextItem(); + const item = atom.workspace.getActivePaneItem(); const pathEscaped = fs.tildify( escapeStringRegex(atom.project.getPaths()[0]) - ) + ); 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", () => { - atom.workspace.getActivePane().destroy() - expect(atom.workspace.getActivePaneItem()).toBeUndefined() + 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', () => { - const pane = atom.workspace.getActivePane() - pane.splitRight() - const initialTitle = document.title - pane.activateNextItem() - expect(document.title).toBe(initialTitle) - }) - }) - }) + const pane = atom.workspace.getActivePane(); + pane.splitRight(); + const initialTitle = document.title; + pane.activateNextItem(); + expect(document.title).toBe(initialTitle); + }); + }); + }); describe('when the workspace is deserialized', () => { - beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))) + beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))); it("updates the title to contain the project's path", () => { - document.title = null + document.title = null; const atom2 = new AtomEnvironment({ applicationDelegate: atom.applicationDelegate - }) + }); atom2.initialize({ window: document.createElement('div'), document: Object.assign(document.createElement('div'), { body: document.createElement('div'), head: document.createElement('div') }) - }) + }); waitsForPromise(() => atom2.project.deserialize(atom.project.serialize()) - ) + ); runs(() => { atom2.workspace.deserialize( atom.workspace.serialize(), atom2.deserializers - ) - const item = atom2.workspace.getActivePaneItem() + ); + 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.destroy() - }) - }) - }) - }) + atom2.destroy(); + }); + }); + }); + }); describe('document edited status', () => { - let item1 - let item2 + let item1; + let item2; beforeEach(() => { - waitsForPromise(() => atom.workspace.open('a')) - waitsForPromise(() => atom.workspace.open('b')) + waitsForPromise(() => atom.workspace.open('a')); + waitsForPromise(() => atom.workspace.open('b')); runs(() => { - ;[item1, item2] = atom.workspace.getPaneItems() - }) - }) + [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(atom.workspace.getActivePaneItem()).toBe(item2); + item1.insertText('a'); + expect(item1.isModified()).toBe(true); + atom.workspace.getActivePane().activateNextItem(); - expect(setDocumentEdited).toHaveBeenCalledWith(true) - }) + 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(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) - expect(setDocumentEdited).toHaveBeenCalledWith(false) - }) - }) + expect(item2.isModified()).toBe(false); + expect(setDocumentEdited).toHaveBeenCalledWith(false); + }); + }); describe('adding panels', () => { class TestItem {} // 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 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 + 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', () => { - let addPanelSpy - expect(atom.workspace.getLeftPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::addRightPanel(model)', () => { it('adds a panel to the correct panel container', () => { - let addPanelSpy - expect(atom.workspace.getRightPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::addTopPanel(model)', () => { it('adds a panel to the correct panel container', () => { - let addPanelSpy - expect(atom.workspace.getTopPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::addBottomPanel(model)', () => { it('adds a panel to the correct panel container', () => { - let addPanelSpy - expect(atom.workspace.getBottomPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::addHeaderPanel(model)', () => { it('adds a panel to the correct panel container', () => { - let addPanelSpy - expect(atom.workspace.getHeaderPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::addFooterPanel(model)', () => { it('adds a panel to the correct panel container', () => { - let addPanelSpy - expect(atom.workspace.getFooterPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::addModalPanel(model)', () => { it('adds a panel to the correct panel container', () => { - let addPanelSpy - expect(atom.workspace.getModalPanels().length).toBe(0) + 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) - expect(itemView.getModel()).toBe(model) - }) - }) + ); + expect(itemView instanceof TestItemElement).toBe(true); + expect(itemView.getModel()).toBe(model); + }); + }); describe('::panelForItem(item)', () => { it('returns the panel associated with the item', () => { - const item = new TestItem() - const panel = atom.workspace.addLeftPanel({ item }) + 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) - expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null) - }) - }) - }) + expect(atom.workspace.panelForItem(item)).toBe(panel); + expect(atom.workspace.panelForItem(itemWithNoPanel)).toBe(null); + }); + }); + }); for (const ripgrep of [true, false]) { describe(`::scan(regex, options, callback) { ripgrep: ${ripgrep} }`, () => { - function scan (regex, options, iterator) { - return atom.workspace.scan(regex, { ...options, ripgrep }, iterator) + function scan(regex, options, iterator) { + return atom.workspace.scan(regex, { ...options, ripgrep }, iterator); } describe('when called with a regex', () => { it('calls the callback with all regex results in all files in the project', async () => { - const results = [] + const results = []; await scan( /(a)+/, { leadingContextLineCount: 1, trailingContextLineCount: 1 }, result => results.push(result) - ) + ); - results.sort((a, b) => a.filePath.localeCompare(b.filePath)) + results.sort((a, b) => a.filePath.localeCompare(b.filePath)); - expect(results.length).toBeGreaterThan(0) + expect(results.length).toBeGreaterThan(0); expect(results[0].filePath).toBe( atom.project.getDirectories()[0].resolve('a') - ) - expect(results[0].matches).toHaveLength(3) + ); + expect(results[0].matches).toHaveLength(3); expect(results[0].matches[0]).toEqual({ matchText: 'aaa', lineText: 'aaa bbb', @@ -2436,21 +2442,21 @@ describe('Workspace', () => { range: [[0, 0], [0, 3]], leadingContextLines: [], trailingContextLines: ['cc aa cc'] - }) - }) + }); + }); it('works with with escaped literals (like $ and ^)', async () => { - const results = [] + const results = []; await scan( /\$\w+/, { leadingContextLineCount: 1, trailingContextLineCount: 1 }, result => results.push(result) - ) + ); - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('a')) - expect(matches).toHaveLength(1) + expect(results.length).toBe(1); + const { 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', @@ -2458,63 +2464,65 @@ describe('Workspace', () => { range: [[2, 6], [2, 11]], leadingContextLines: ['cc aa cc'], trailingContextLines: [] - }) - }) + }); + }); it('works on evil filenames', async () => { - atom.config.set('core.excludeVcsIgnoredPaths', false) - platform.generateEvilFiles() - atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')]) - const paths = [] - let matches = [] + atom.config.set('core.excludeVcsIgnoredPaths', false); + platform.generateEvilFiles(); + atom.project.setPaths([ + path.join(__dirname, 'fixtures', 'evil-files') + ]); + const paths = []; + let matches = []; await scan(/evil/, {}, result => { - paths.push(result.filePath) - matches = matches.concat(result.matches) - }) + paths.push(result.filePath); + matches = matches.concat(result.matches); + }); // Sort the paths to make the test deterministic. - paths.sort() + paths.sort(); - _.each(matches, m => expect(m.matchText).toEqual('evil')) + _.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') + 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') + 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', async () => { - const results = [] - await scan(/DOLLAR/i, {}, result => results.push(result)) + const results = []; + await scan(/DOLLAR/i, {}, result => results.push(result)); - expect(results).toHaveLength(1) - }) + expect(results).toHaveLength(1); + }); if (ripgrep) { describe('newlines on regexps', async () => { it('returns multiline results from regexps', async () => { - const results = [] + const results = []; - await scan( - /first\nsecond/, - {}, - result => results.push(result) - ) + await scan(/first\nsecond/, {}, result => results.push(result)); - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('file-with-newline-literal')) - expect(matches).toHaveLength(1) + expect(results.length).toBe(1); + const { filePath, matches } = results[0]; + expect(filePath).toBe( + atom.project + .getDirectories()[0] + .resolve('file-with-newline-literal') + ); + expect(matches).toHaveLength(1); expect(matches[0]).toEqual({ matchText: 'first\nsecond', lineText: 'first\nsecond\\nthird', @@ -2522,11 +2530,11 @@ describe('Workspace', () => { range: [[3, 0], [4, 6]], leadingContextLines: [], trailingContextLines: [] - }) - }) + }); + }); it('returns correctly the context lines', async () => { - const results = [] + const results = []; await scan( /first\nsecond/, @@ -2535,46 +2543,42 @@ describe('Workspace', () => { trailingContextLineCount: 2 }, result => results.push(result) - ) + ); - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('file-with-newline-literal')) - expect(matches).toHaveLength(1) + expect(results.length).toBe(1); + const { filePath, matches } = results[0]; + expect(filePath).toBe( + atom.project + .getDirectories()[0] + .resolve('file-with-newline-literal') + ); + expect(matches).toHaveLength(1); expect(matches[0]).toEqual({ matchText: 'first\nsecond', lineText: 'first\nsecond\\nthird', lineTextOffset: 0, range: [[3, 0], [4, 6]], - leadingContextLines: [ - 'newline2', - 'newline3' - ], - trailingContextLines: [ - 'newline4', - 'newline5' - ] - }) - }) + leadingContextLines: ['newline2', 'newline3'], + trailingContextLines: ['newline4', 'newline5'] + }); + }); it('returns multiple results from the same line', async () => { - const results = [] + const results = []; - await scan( - /line\d\nne/, - {}, - result => results.push(result) - ) + await scan(/line\d\nne/, {}, result => results.push(result)); - results.sort((a, b) => a.filePath.localeCompare(b.filePath)) + results.sort((a, b) => a.filePath.localeCompare(b.filePath)); - expect(results.length).toBe(1) + expect(results.length).toBe(1); - const { filePath, matches } = results[0] + const { filePath, matches } = results[0]; expect(filePath).toBe( - atom.project.getDirectories()[0].resolve('file-with-newline-literal') - ) - expect(matches).toHaveLength(3) + atom.project + .getDirectories()[0] + .resolve('file-with-newline-literal') + ); + expect(matches).toHaveLength(3); expect(matches[0]).toEqual({ matchText: 'line1\nne', lineText: 'newline1\nnewline2', @@ -2582,7 +2586,7 @@ describe('Workspace', () => { range: [[0, 3], [1, 2]], leadingContextLines: [], trailingContextLines: [] - }) + }); expect(matches[1]).toEqual({ matchText: 'line2\nne', lineText: 'newline2\nnewline3', @@ -2590,7 +2594,7 @@ describe('Workspace', () => { range: [[1, 3], [2, 2]], leadingContextLines: [], trailingContextLines: [] - }) + }); expect(matches[2]).toEqual({ matchText: 'line4\nne', lineText: 'newline4\nnewline5', @@ -2598,21 +2602,21 @@ describe('Workspace', () => { range: [[5, 3], [6, 2]], leadingContextLines: [], trailingContextLines: [] - }) - }) + }); + }); it('works with escaped newlines', async () => { - const results = [] + const results = []; - await scan( - /second\\nthird/, - {}, - result => results.push(result) - ) - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('file-with-newline-literal')) - expect(matches).toHaveLength(1) + await scan(/second\\nthird/, {}, result => results.push(result)); + expect(results.length).toBe(1); + const { filePath, matches } = results[0]; + expect(filePath).toBe( + atom.project + .getDirectories()[0] + .resolve('file-with-newline-literal') + ); + expect(matches).toHaveLength(1); expect(matches[0]).toEqual({ matchText: 'second\\nthird', lineText: 'second\\nthird', @@ -2620,21 +2624,21 @@ describe('Workspace', () => { range: [[4, 0], [4, 13]], leadingContextLines: [], trailingContextLines: [] - }) - }) + }); + }); it('matches a regexp ending with a newline', async () => { - const results = [] + const results = []; - await scan( - /newline3\n/, - {}, - result => results.push(result) - ) - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('file-with-newline-literal')) - expect(matches).toHaveLength(1) + await scan(/newline3\n/, {}, result => results.push(result)); + expect(results.length).toBe(1); + const { filePath, matches } = results[0]; + expect(filePath).toBe( + atom.project + .getDirectories()[0] + .resolve('file-with-newline-literal') + ); + expect(matches).toHaveLength(1); expect(matches[0]).toEqual({ matchText: 'newline3\n', lineText: 'newline3', @@ -2642,23 +2646,21 @@ describe('Workspace', () => { range: [[2, 0], [3, 0]], leadingContextLines: [], trailingContextLines: [] - }) - }) - }) + }); + }); + }); } it('returns results on lines with unicode strings', async () => { - const results = [] + const results = []; - await scan( - /line with unico/, - {}, - result => results.push(result) - ) - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('file-with-unicode')) - expect(matches).toHaveLength(1) + await scan(/line with unico/, {}, result => results.push(result)); + expect(results.length).toBe(1); + const { filePath, matches } = results[0]; + expect(filePath).toBe( + atom.project.getDirectories()[0].resolve('file-with-unicode') + ); + expect(matches).toHaveLength(1); expect(matches[0]).toEqual({ matchText: 'line with unico', lineText: 'ДДДДДДДДДДДДДДДДДД line with unicode', @@ -2666,11 +2668,11 @@ describe('Workspace', () => { range: [[0, 19], [0, 34]], leadingContextLines: [], trailingContextLines: [] - }) - }) + }); + }); it('returns results on files detected as binary', async () => { - const results = [] + const results = []; await scan( /asciiProperty=Foo/, @@ -2678,27 +2680,26 @@ describe('Workspace', () => { trailingContextLineCount: 2 }, result => results.push(result) - ) - expect(results.length).toBe(1) - const { filePath, matches } = results[0] - expect(filePath).toBe(atom.project.getDirectories()[0].resolve('file-detected-as-binary')) - expect(matches).toHaveLength(1) + ); + expect(results.length).toBe(1); + const { filePath, matches } = results[0]; + expect(filePath).toBe( + atom.project.getDirectories()[0].resolve('file-detected-as-binary') + ); + expect(matches).toHaveLength(1); expect(matches[0]).toEqual({ matchText: 'asciiProperty=Foo', lineText: 'asciiProperty=Foo', lineTextOffset: 0, range: [[0, 0], [0, 17]], leadingContextLines: [], - trailingContextLines: [ - 'utf8Property=Fòò', - 'latin1Property=F��' - ] - }) - }) + trailingContextLines: ['utf8Property=Fòò', 'latin1Property=F��'] + }); + }); describe('when the core.excludeVcsIgnoredPaths config is truthy', () => { - let projectPath - let ignoredPath + let projectPath; + let ignoredPath; beforeEach(() => { const sourceProjectPath = path.join( @@ -2706,258 +2707,258 @@ describe('Workspace', () => { 'fixtures', 'git', 'working-dir' - ) - projectPath = path.join(temp.mkdirSync('atom')) + ); + 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(done => { - writerStream.on('close', done) - writerStream.on('error', done) - }) + writerStream.on('close', done); + writerStream.on('error', done); + }); runs(() => { fs.renameSync( path.join(projectPath, 'git.git'), path.join(projectPath, '.git') - ) - ignoredPath = path.join(projectPath, 'ignored.txt') - fs.writeFileSync(ignoredPath, 'this match should not be included') - }) - }) + ); + ignoredPath = path.join(projectPath, 'ignored.txt'); + fs.writeFileSync( + ignoredPath, + 'this match should not be included' + ); + }); + }); afterEach(() => { if (fs.existsSync(projectPath)) { - fs.removeSync(projectPath) + fs.removeSync(projectPath); } - }) + }); it('excludes ignored files', async () => { - atom.project.setPaths([projectPath]) - atom.config.set('core.excludeVcsIgnoredPaths', true) - const resultHandler = jasmine.createSpy('result found') + atom.project.setPaths([projectPath]); + atom.config.set('core.excludeVcsIgnoredPaths', true); + const resultHandler = jasmine.createSpy('result found'); - await scan(/match/, {}, () => resultHandler()) + await scan(/match/, {}, () => resultHandler()); - expect(resultHandler).not.toHaveBeenCalled() - }) - }) + expect(resultHandler).not.toHaveBeenCalled(); + }); + }); it('includes only files when a directory filter is specified', async () => { - const projectPath = path.join(path.join(__dirname, 'fixtures', 'dir')) - atom.project.setPaths([projectPath]) + 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 = []; - await scan( - /aaa/, - { paths: [`a-dir${path.sep}`] }, - result => { - paths.push(result.filePath) - matches = matches.concat(result.matches) - } - ) + await scan(/aaa/, { paths: [`a-dir${path.sep}`] }, result => { + paths.push(result.filePath); + matches = matches.concat(result.matches); + }); - expect(paths.length).toBe(1) - expect(paths[0]).toBe(filePath) - expect(matches.length).toBe(1) - }) + expect(paths.length).toBe(1); + expect(paths[0]).toBe(filePath); + expect(matches.length).toBe(1); + }); it("includes files and folders that begin with a '.'", async () => { - 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 = [] + 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 = []; await scan(/match this/, {}, result => { - paths.push(result.filePath) - matches = matches.concat(result.matches) - }) + paths.push(result.filePath); + matches = matches.concat(result.matches); + }); - expect(paths.length).toBe(1) - expect(paths[0]).toBe(filePath) - expect(matches.length).toBe(1) - }) + expect(paths.length).toBe(1); + expect(paths[0]).toBe(filePath); + expect(matches.length).toBe(1); + }); it('excludes values in core.ignoredNames', async () => { - const ignoredNames = atom.config.get('core.ignoredNames') - ignoredNames.push('a') - atom.config.set('core.ignoredNames', ignoredNames) + const ignoredNames = atom.config.get('core.ignoredNames'); + ignoredNames.push('a'); + atom.config.set('core.ignoredNames', ignoredNames); - const resultHandler = jasmine.createSpy('result found') - await scan(/dollar/, {}, () => resultHandler()) + const resultHandler = jasmine.createSpy('result found'); + await scan(/dollar/, {}, () => resultHandler()); - expect(resultHandler).not.toHaveBeenCalled() - }) + expect(resultHandler).not.toHaveBeenCalled(); + }); it('scans buffer contents if the buffer is modified', async () => { - const results = [] - const editor = await atom.workspace.open('a') + const results = []; + const editor = await atom.workspace.open('a'); - editor.setText('Elephant') + editor.setText('Elephant'); - await scan(/a|Elephant/, {}, result => results.push(result)) + await scan(/a|Elephant/, {}, result => results.push(result)); - expect(results.length).toBeGreaterThan(0) + expect(results.length).toBeGreaterThan(0); const resultForA = _.find( results, ({ filePath }) => path.basename(filePath) === 'a' - ) - expect(resultForA.matches).toHaveLength(1) - expect(resultForA.matches[0].matchText).toBe('Elephant') - }) + ); + expect(resultForA.matches).toHaveLength(1); + expect(resultForA.matches[0].matchText).toBe('Elephant'); + }); it('ignores buffers outside the project', async () => { - const results = [] - const editor = await atom.workspace.open(temp.openSync().path) + const results = []; + const editor = await atom.workspace.open(temp.openSync().path); - editor.setText('Elephant') + editor.setText('Elephant'); - await scan(/Elephant/, {}, result => results.push(result)) + await scan(/Elephant/, {}, result => results.push(result)); - expect(results).toHaveLength(0) - }) + expect(results).toHaveLength(0); + }); describe('when the project has multiple root directories', () => { - let dir1 - let dir2 - let file1 - let file2 + let dir1; + let dir2; + let file1; + let file2; beforeEach(() => { - dir1 = atom.project.getPaths()[0] - file1 = path.join(dir1, 'a-dir', 'oh-git') + dir1 = atom.project.getPaths()[0]; + 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'); - atom.project.addPath(dir2) - }) + atom.project.addPath(dir2); + }); it("searches matching files in all of the project's root directories", async () => { - const resultPaths = [] + const resultPaths = []; await scan(/aaaa/, {}, ({ filePath }) => resultPaths.push(filePath) - ) + ); - expect(resultPaths.sort()).toEqual([file1, file2].sort()) - }) + 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', async () => { - - let resultPaths = [] + let resultPaths = []; await scan(/aaaa/, { paths: ['dir'] }, ({ filePath }) => { if (!resultPaths.includes(filePath)) { - resultPaths.push(filePath) + resultPaths.push(filePath); } - }) + }); - expect(resultPaths).toEqual([file1]) + expect(resultPaths).toEqual([file1]); - resultPaths = [] + resultPaths = []; await scan( /aaaa/, { paths: [path.join('dir', 'a-dir')] }, ({ filePath }) => { if (!resultPaths.includes(filePath)) { - resultPaths.push(filePath) + resultPaths.push(filePath); } } - ) + ); - expect(resultPaths).toEqual([file1]) + expect(resultPaths).toEqual([file1]); - resultPaths = [] + resultPaths = []; await scan( /aaaa/, { paths: [path.basename(dir2)] }, ({ filePath }) => { if (!resultPaths.includes(filePath)) { - resultPaths.push(filePath) + resultPaths.push(filePath); } } - ) + ); - expect(resultPaths).toEqual([file2]) + expect(resultPaths).toEqual([file2]); - resultPaths = [] + resultPaths = []; await scan( /aaaa/, { paths: [path.join(path.basename(dir2), 'a-dir')] }, ({ filePath }) => { if (!resultPaths.includes(filePath)) { - resultPaths.push(filePath) + resultPaths.push(filePath); } } - ) + ); - expect(resultPaths).toEqual([file2]) - }) - }) + expect(resultPaths).toEqual([file2]); + }); + }); describe('when a custom directory searcher is registered', () => { - let fakeSearch = null + 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.options = options; this.promise = new Promise((resolve, reject) => { - this.hoistedResolve = resolve - this.hoistedReject = reject + this.hoistedResolve = resolve; + this.hoistedReject = reject; if (typeof onFakeSearchCreated === 'function') { - onFakeSearchCreated(this) + onFakeSearchCreated(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. - this.hoistedResolve() + this.hoistedResolve(); } } beforeEach(() => { - fakeSearch = null - onFakeSearchCreated = null + fakeSearch = null; + onFakeSearchCreated = null; atom.packages.serviceHub.provide( 'atom.directory-searcher', '0.1.0', { - canSearchDirectory (directory) { - return directory.getPath() === dir1 + canSearchDirectory(directory) { + return directory.getPath() === dir1; }, - search (directory, regex, options) { - fakeSearch = new FakeSearch(options) - return fakeSearch + search(directory, regex, options) { + fakeSearch = new FakeSearch(options); + return fakeSearch; } } - ) + ); - waitsFor(() => atom.workspace.directorySearchers.length > 0) - }) + waitsFor(() => atom.workspace.directorySearchers.length > 0); + }); it('can override the DefaultDirectorySearcher on a per-directory basis', async () => { - const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt' - const numPathsSearchedInDir2 = 1 - const numPathsToPretendToSearchInCustomDirectorySearcher = 10 + const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt'; + const numPathsSearchedInDir2 = 1; + const numPathsToPretendToSearchInCustomDirectorySearcher = 10; const searchResult = { filePath: foreignFilePath, matches: [ @@ -2968,113 +2969,117 @@ describe('Workspace', () => { range: [[0, 0], [0, 5]] } ] - } + }; onFakeSearchCreated = fakeSearch => { - fakeSearch.options.didMatch(searchResult) + fakeSearch.options.didMatch(searchResult); fakeSearch.options.didSearchPaths( numPathsToPretendToSearchInCustomDirectorySearcher - ) - fakeSearch.hoistedResolve() - } + ); + fakeSearch.hoistedResolve(); + }; - const resultPaths = [] - const onPathsSearched = jasmine.createSpy('onPathsSearched') + const resultPaths = []; + const onPathsSearched = jasmine.createSpy('onPathsSearched'); await scan(/aaaa/, { onPathsSearched }, ({ filePath }) => resultPaths.push(filePath) - ) + ); 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); expect(onPathsSearched.mostRecentCall.args[0]).toBe( numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2 - ) - }) + ); + }); it('can be cancelled when the object returned by scan() has its cancel() method invoked', async () => { - const thenable = scan(/aaaa/, {}, () => {}) - let resultOfPromiseSearch = null + const thenable = scan(/aaaa/, {}, () => {}); + let resultOfPromiseSearch = null; - waitsFor('fakeSearch to be defined', () => fakeSearch != null) + waitsFor('fakeSearch to be defined', () => fakeSearch != null); runs(() => { - expect(fakeSearch.cancelled).toBe(undefined) - thenable.cancel() - expect(fakeSearch.cancelled).toBe(true) - }) + expect(fakeSearch.cancelled).toBe(undefined); + thenable.cancel(); + expect(fakeSearch.cancelled).toBe(true); + }); waitsForPromise(() => thenable.then(promiseResult => { - resultOfPromiseSearch = promiseResult + resultOfPromiseSearch = promiseResult; }) - ) + ); - runs(() => expect(resultOfPromiseSearch).toBe('cancelled')) - }) + 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 - 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 + canSearchDirectory(directory) { + return directory.getPath() === dir2; }, - search (directory, regex, options) { - fakeSearch2 = new FakeSearch(options) - return fakeSearch2 + search(directory, regex, options) { + fakeSearch2 = new FakeSearch(options); + return fakeSearch2; } } - ) + ); - let didReject = false - const promise = (cancelableSearch = scan( - /aaaa/, - () => {} - )) - waitsFor('fakeSearch to be defined', () => fakeSearch != null) + let didReject = false; + const promise = (cancelableSearch = scan(/aaaa/, () => {})); + waitsFor('fakeSearch to be defined', () => fakeSearch != null); - runs(() => fakeSearch.hoistedReject()) + runs(() => fakeSearch.hoistedReject()); waitsForPromise(() => cancelableSearch.catch(() => { - didReject = true + didReject = true; }) - ) + ); - waitsFor(done => promise.then(null, done)) + waitsFor(done => promise.then(null, done)); runs(() => { - expect(didReject).toBe(true) - expect(fakeSearch2.cancelled).toBe(true) - }) - }) - }) - }) - }) + expect(didReject).toBe(true); + expect(fakeSearch2.cancelled).toBe(true); + }); + }); + }); + }); + }); describe('leadingContextLineCount and trailingContextLineCount options', () => { - async function search ({ leadingContextLineCount, trailingContextLineCount }) { - const results = [] + async function search({ + leadingContextLineCount, + trailingContextLineCount + }) { + const results = []; await scan( /result/, { leadingContextLineCount, trailingContextLineCount }, result => results.push(result) - ) + ); return { - leadingContext: results[0].matches.map(result => result.leadingContextLines), - trailingContext: results[0].matches.map(result => result.trailingContextLines) - } + leadingContext: results[0].matches.map( + result => result.leadingContextLines + ), + trailingContext: results[0].matches.map( + result => result.trailingContextLines + ) + }; } const expectedLeadingContext = [ @@ -3088,62 +3093,88 @@ describe('Workspace', () => { ['result 3', 'line 11', 'line 12', 'result 4', 'line 13'], ['line 11', 'line 12', 'result 4', 'line 13', 'line 14'], ['line 13', 'line 14', 'line 15'] - ] + ]; it('returns valid contexts no matter how many lines are requested', async () => { - expect( - await search({}) - ).toEqual({ + expect(await search({})).toEqual({ leadingContext: [[], [], [], []], trailingContext: [[], [], [], []] - }) + }); expect( - await search({ leadingContextLineCount: 1, trailingContextLineCount: 1 }) + await search({ + leadingContextLineCount: 1, + trailingContextLineCount: 1 + }) ).toEqual({ - leadingContext: expectedLeadingContext.map(result => result.slice(-1)), - trailingContext: expectedTrailingContext.map(result => result.slice(0, 1)) - }) + leadingContext: expectedLeadingContext.map(result => + result.slice(-1) + ), + trailingContext: expectedTrailingContext.map(result => + result.slice(0, 1) + ) + }); expect( - await search({ leadingContextLineCount: 2, trailingContextLineCount: 2 }) + await search({ + leadingContextLineCount: 2, + trailingContextLineCount: 2 + }) ).toEqual({ - leadingContext: expectedLeadingContext.map(result => result.slice(-2)), - trailingContext: expectedTrailingContext.map(result => result.slice(0, 2)) - }) + leadingContext: expectedLeadingContext.map(result => + result.slice(-2) + ), + trailingContext: expectedTrailingContext.map(result => + result.slice(0, 2) + ) + }); expect( - await search({ leadingContextLineCount: 5, trailingContextLineCount: 5 }) + await search({ + leadingContextLineCount: 5, + trailingContextLineCount: 5 + }) ).toEqual({ - leadingContext: expectedLeadingContext.map(result => result.slice(-5)), - trailingContext: expectedTrailingContext.map(result => result.slice(0, 5)) - }) + leadingContext: expectedLeadingContext.map(result => + result.slice(-5) + ), + trailingContext: expectedTrailingContext.map(result => + result.slice(0, 5) + ) + }); expect( - await search({ leadingContextLineCount: 2, trailingContextLineCount: 3 }) + await search({ + leadingContextLineCount: 2, + trailingContextLineCount: 3 + }) ).toEqual({ - leadingContext: expectedLeadingContext.map(result => result.slice(-2)), - trailingContext: expectedTrailingContext.map(result => result.slice(0, 3)) - }) - }) - }) - }) // Cancels other ongoing searches + leadingContext: expectedLeadingContext.map(result => + result.slice(-2) + ), + trailingContext: expectedTrailingContext.map(result => + result.slice(0, 3) + ) + }); + }); + }); + }); // Cancels other ongoing searches } describe('::replace(regex, replacementText, paths, iterator)', () => { - let fixturesDir, projectDir + let fixturesDir, projectDir; beforeEach(() => { - fixturesDir = path.dirname(atom.project.getPaths()[0]) - projectDir = temp.mkdirSync('atom') - atom.project.setPaths([projectDir]) - }) + fixturesDir = path.dirname(atom.project.getPaths()[0]); + projectDir = temp.mkdirSync('atom'); + atom.project.setPaths([projectDir]); + }); 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() + const errors = []; + const missingPath = path.resolve('/not-a-file.js'); + expect(fs.existsSync(missingPath)).toBeFalsy(); waitsForPromise(() => atom.workspace.replace( @@ -3152,98 +3183,101 @@ describe('Workspace', () => { [missingPath], (result, error) => errors.push(error) ) - ) + ); runs(() => { - expect(errors).toHaveLength(1) - expect(errors[0].path).toBe(missingPath) - }) - }) - }) + expect(errors).toHaveLength(1); + expect(errors[0].path).toBe(missingPath); + }); + }); + }); describe('when called with unopened files', () => { it('replaces properly', () => { - const filePath = path.join(projectDir, 'sample.js') - fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath) + const filePath = path.join(projectDir, 'sample.js'); + fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); - const results = [] + const 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) - }) - }) + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); + }); + }); it('does not discard the multiline flag', () => { - const filePath = path.join(projectDir, 'sample.js') - fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath) + const filePath = path.join(projectDir, 'sample.js'); + fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); - const results = [] + const results = []; waitsForPromise(() => atom.workspace.replace(/;$/gim, 'items', [filePath], result => results.push(result) ) - ) + ); runs(() => { - expect(results).toHaveLength(1) - expect(results[0].filePath).toBe(filePath) - expect(results[0].replacements).toBe(8) - }) - }) - }) + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(8); + }); + }); + }); describe('when a buffer is already open', () => { it('replaces properly and saves when not modified', () => { - const filePath = path.join(projectDir, 'sample.js') + const filePath = path.join(projectDir, 'sample.js'); fs.copyFileSync( path.join(fixturesDir, 'sample.js'), path.join(projectDir, 'sample.js') - ) + ); - let editor = null - const results = [] + let editor = null; + const results = []; waitsForPromise(() => atom.workspace.open('sample.js').then(o => { - editor = 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) ) - ) + ); runs(() => { - expect(results).toHaveLength(1) - expect(results[0].filePath).toBe(filePath) - expect(results[0].replacements).toBe(6) + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); - expect(editor.isModified()).toBeFalsy() - }) - }) + expect(editor.isModified()).toBeFalsy(); + }); + }); it('does not replace when the path is not specified', () => { - const filePath = path.join(projectDir, 'sample.js') - const commentFilePath = path.join(projectDir, 'sample-with-comments.js') - fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath) + const filePath = path.join(projectDir, 'sample.js'); + const commentFilePath = path.join( + projectDir, + 'sample-with-comments.js' + ); + fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); fs.copyFileSync( path.join(fixturesDir, 'sample-with-comments.js'), path.join(projectDir, 'sample-with-comments.js') - ) - const results = [] + ); + const results = []; - waitsForPromise(() => atom.workspace.open('sample-with-comments.js')) + waitsForPromise(() => atom.workspace.open('sample-with-comments.js')); waitsForPromise(() => atom.workspace.replace( @@ -3252,232 +3286,232 @@ describe('Workspace', () => { [commentFilePath], result => results.push(result) ) - ) + ); runs(() => { - expect(results).toHaveLength(1) - expect(results[0].filePath).toBe(commentFilePath) - }) - }) + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(commentFilePath); + }); + }); it('does NOT save when modified', () => { - const filePath = path.join(projectDir, 'sample.js') - fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath) + const filePath = path.join(projectDir, 'sample.js'); + fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); - let editor = null - const results = [] + let editor = null; + const results = []; waitsForPromise(() => atom.workspace.open('sample.js').then(o => { - editor = o + editor = o; }) - ) + ); runs(() => { - editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg') - expect(editor.isModified()).toBeTruthy() - }) + 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(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); - expect(editor.isModified()).toBeTruthy() - }) - }) - }) - }) + expect(editor.isModified()).toBeTruthy(); + }); + }); + }); + }); describe('::saveActivePaneItem()', () => { - let editor, notificationSpy + let editor, notificationSpy; beforeEach(() => { waitsForPromise(() => atom.workspace.open('sample.js').then(o => { - editor = o + editor = o; }) - ) + ); - notificationSpy = jasmine.createSpy('did-add-notification') - atom.notifications.onDidAddNotification(notificationSpy) - }) + notificationSpy = jasmine.createSpy('did-add-notification'); + atom.notifications.onDidAddNotification(notificationSpy); + }); 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") - }) + throw new Error("'/some/file' is a directory"); + }); waitsForPromise(() => atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled() + expect(notificationSpy).toHaveBeenCalled(); expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( 'warning' - ) + ); expect( notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save') + ).toContain('Unable to save'); }) - ) - }) + ); + }); 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'") - }) + throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'"); + }); waitsForPromise(() => atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled() + expect(notificationSpy).toHaveBeenCalled(); expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( 'warning' - ) + ); expect( notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save') + ).toContain('Unable to save'); }) - ) - }) + ); + }); it('emits a warning notification when the user does not have permission', () => { 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' - throw error - }) + ); + error.code = 'EACCES'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); waitsForPromise(() => atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled() + expect(notificationSpy).toHaveBeenCalled(); expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( 'warning' - ) + ); expect( notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save') + ).toContain('Unable to save'); }) - ) - }) + ); + }); 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 - }) + ); + error.code = 'EPERM'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); waitsForPromise(() => atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled() + expect(notificationSpy).toHaveBeenCalled(); expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( 'warning' - ) + ); expect( notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save') + ).toContain('Unable to save'); }) - ) - }) + ); + }); it('emits a warning notification when the file is already open by another app', () => { 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' - throw error - }) + ); + error.code = 'EBUSY'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); waitsForPromise(() => atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled() + expect(notificationSpy).toHaveBeenCalled(); expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( 'warning' - ) + ); expect( notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save') + ).toContain('Unable to save'); }) - ) - }) + ); + }); it('emits a warning notification when the file system is read-only', () => { 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' - throw error - }) + ); + error.code = 'EROFS'; + error.path = '/Some/dir/and-a-file.js'; + throw error; + }); waitsForPromise(() => atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled() + expect(notificationSpy).toHaveBeenCalled(); expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( 'warning' - ) + ); expect( notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save') + ).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') - }) + throw new Error('no one knows'); + }); waitsForPromise({ shouldReject: true }, () => atom.workspace.saveActivePaneItem() - ) - }) - }) - }) + ); + }); + }); + }); describe('::closeActivePaneItemOrEmptyPaneOrWindow', () => { beforeEach(() => { - spyOn(atom, 'close') - waitsForPromise(() => atom.workspace.open()) - }) + spyOn(atom, 'close'); + waitsForPromise(() => atom.workspace.open()); + }); it('closes the active center pane item, or the active center pane if it is empty, or the current window if there is only the empty root pane in the center', async () => { - atom.config.set('core.destroyEmptyPanes', false) + atom.config.set('core.destroyEmptyPanes', false); - const pane1 = atom.workspace.getActivePane() - const pane2 = pane1.splitRight({ copyActiveItem: true }) + const pane1 = atom.workspace.getActivePane(); + const pane2 = pane1.splitRight({ copyActiveItem: true }); - expect(atom.workspace.getCenter().getPanes().length).toBe(2) - expect(pane2.getItems().length).toBe(1) - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + expect(atom.workspace.getCenter().getPanes().length).toBe(2); + expect(pane2.getItems().length).toBe(1); + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); - expect(atom.workspace.getCenter().getPanes().length).toBe(2) - expect(pane2.getItems().length).toBe(0) + expect(atom.workspace.getCenter().getPanes().length).toBe(2); + expect(pane2.getItems().length).toBe(0); - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); - expect(atom.workspace.getCenter().getPanes().length).toBe(1) - expect(pane1.getItems().length).toBe(1) + expect(atom.workspace.getCenter().getPanes().length).toBe(1); + expect(pane1.getItems().length).toBe(1); - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - expect(atom.workspace.getCenter().getPanes().length).toBe(1) - expect(pane1.getItems().length).toBe(0) - expect(atom.workspace.getCenter().getPanes().length).toBe(1) + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + expect(atom.workspace.getCenter().getPanes().length).toBe(1); + expect(pane1.getItems().length).toBe(0); + expect(atom.workspace.getCenter().getPanes().length).toBe(1); // The dock items should not be closed await atom.workspace.open({ @@ -3485,288 +3519,288 @@ describe('Workspace', () => { element: document.createElement('div'), getDefaultLocation: () => 'left', isPermanentDockItem: () => true - }) + }); await atom.workspace.open({ getTitle: () => 'Impermanent Dock Item', element: document.createElement('div'), getDefaultLocation: () => 'left' - }) + }); - expect(atom.workspace.getLeftDock().getPaneItems().length).toBe(2) - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() - expect(atom.close).toHaveBeenCalled() - }) - }) + expect(atom.workspace.getLeftDock().getPaneItems().length).toBe(2); + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + expect(atom.close).toHaveBeenCalled(); + }); + }); describe('::activateNextPane', () => { describe('when the active workspace pane is inside a dock', () => { it('activates the next pane in the dock', () => { - const dock = atom.workspace.getLeftDock() - const dockPane1 = dock.getPanes()[0] - const dockPane2 = dockPane1.splitRight() + const dock = atom.workspace.getLeftDock(); + const dockPane1 = dock.getPanes()[0]; + const dockPane2 = dockPane1.splitRight(); - dockPane2.focus() - expect(atom.workspace.getActivePane()).toBe(dockPane2) - atom.workspace.activateNextPane() - expect(atom.workspace.getActivePane()).toBe(dockPane1) - }) - }) + dockPane2.focus(); + expect(atom.workspace.getActivePane()).toBe(dockPane2); + atom.workspace.activateNextPane(); + expect(atom.workspace.getActivePane()).toBe(dockPane1); + }); + }); describe('when the active workspace pane is inside the workspace center', () => { it('activates the next pane in the workspace center', () => { - const center = atom.workspace.getCenter() - const centerPane1 = center.getPanes()[0] - const centerPane2 = centerPane1.splitRight() + const center = atom.workspace.getCenter(); + const centerPane1 = center.getPanes()[0]; + const centerPane2 = centerPane1.splitRight(); - centerPane2.focus() - expect(atom.workspace.getActivePane()).toBe(centerPane2) - atom.workspace.activateNextPane() - expect(atom.workspace.getActivePane()).toBe(centerPane1) - }) - }) - }) + centerPane2.focus(); + expect(atom.workspace.getActivePane()).toBe(centerPane2); + atom.workspace.activateNextPane(); + expect(atom.workspace.getActivePane()).toBe(centerPane1); + }); + }); + }); describe('::activatePreviousPane', () => { describe('when the active workspace pane is inside a dock', () => { it('activates the previous pane in the dock', () => { - const dock = atom.workspace.getLeftDock() - const dockPane1 = dock.getPanes()[0] - const dockPane2 = dockPane1.splitRight() + const dock = atom.workspace.getLeftDock(); + const dockPane1 = dock.getPanes()[0]; + const dockPane2 = dockPane1.splitRight(); - dockPane1.focus() - expect(atom.workspace.getActivePane()).toBe(dockPane1) - atom.workspace.activatePreviousPane() - expect(atom.workspace.getActivePane()).toBe(dockPane2) - }) - }) + dockPane1.focus(); + expect(atom.workspace.getActivePane()).toBe(dockPane1); + atom.workspace.activatePreviousPane(); + expect(atom.workspace.getActivePane()).toBe(dockPane2); + }); + }); describe('when the active workspace pane is inside the workspace center', () => { it('activates the previous pane in the workspace center', () => { - const center = atom.workspace.getCenter() - const centerPane1 = center.getPanes()[0] - const centerPane2 = centerPane1.splitRight() + const center = atom.workspace.getCenter(); + const centerPane1 = center.getPanes()[0]; + const centerPane2 = centerPane1.splitRight(); - centerPane1.focus() - expect(atom.workspace.getActivePane()).toBe(centerPane1) - atom.workspace.activatePreviousPane() - expect(atom.workspace.getActivePane()).toBe(centerPane2) - }) - }) - }) + centerPane1.focus(); + expect(atom.workspace.getActivePane()).toBe(centerPane1); + atom.workspace.activatePreviousPane(); + expect(atom.workspace.getActivePane()).toBe(centerPane2); + }); + }); + }); describe('::getVisiblePanes', () => { it('returns all panes in visible pane containers', () => { - const center = workspace.getCenter() - const leftDock = workspace.getLeftDock() - const rightDock = workspace.getRightDock() - const bottomDock = workspace.getBottomDock() + const center = workspace.getCenter(); + const leftDock = workspace.getLeftDock(); + const rightDock = workspace.getRightDock(); + const bottomDock = workspace.getBottomDock(); - const centerPane = center.getPanes()[0] - const leftDockPane = leftDock.getPanes()[0] - const rightDockPane = rightDock.getPanes()[0] - const bottomDockPane = bottomDock.getPanes()[0] + const centerPane = center.getPanes()[0]; + const leftDockPane = leftDock.getPanes()[0]; + const rightDockPane = rightDock.getPanes()[0]; + const bottomDockPane = bottomDock.getPanes()[0]; - leftDock.hide() - rightDock.hide() - bottomDock.hide() - expect(workspace.getVisiblePanes()).toContain(centerPane) - expect(workspace.getVisiblePanes()).not.toContain(leftDockPane) - expect(workspace.getVisiblePanes()).not.toContain(rightDockPane) - expect(workspace.getVisiblePanes()).not.toContain(bottomDockPane) + leftDock.hide(); + rightDock.hide(); + bottomDock.hide(); + expect(workspace.getVisiblePanes()).toContain(centerPane); + expect(workspace.getVisiblePanes()).not.toContain(leftDockPane); + expect(workspace.getVisiblePanes()).not.toContain(rightDockPane); + expect(workspace.getVisiblePanes()).not.toContain(bottomDockPane); - leftDock.show() - expect(workspace.getVisiblePanes()).toContain(centerPane) - expect(workspace.getVisiblePanes()).toContain(leftDockPane) - expect(workspace.getVisiblePanes()).not.toContain(rightDockPane) - expect(workspace.getVisiblePanes()).not.toContain(bottomDockPane) + leftDock.show(); + expect(workspace.getVisiblePanes()).toContain(centerPane); + expect(workspace.getVisiblePanes()).toContain(leftDockPane); + expect(workspace.getVisiblePanes()).not.toContain(rightDockPane); + expect(workspace.getVisiblePanes()).not.toContain(bottomDockPane); - rightDock.show() - expect(workspace.getVisiblePanes()).toContain(centerPane) - expect(workspace.getVisiblePanes()).toContain(leftDockPane) - expect(workspace.getVisiblePanes()).toContain(rightDockPane) - expect(workspace.getVisiblePanes()).not.toContain(bottomDockPane) + rightDock.show(); + expect(workspace.getVisiblePanes()).toContain(centerPane); + expect(workspace.getVisiblePanes()).toContain(leftDockPane); + expect(workspace.getVisiblePanes()).toContain(rightDockPane); + expect(workspace.getVisiblePanes()).not.toContain(bottomDockPane); - bottomDock.show() - expect(workspace.getVisiblePanes()).toContain(centerPane) - expect(workspace.getVisiblePanes()).toContain(leftDockPane) - expect(workspace.getVisiblePanes()).toContain(rightDockPane) - expect(workspace.getVisiblePanes()).toContain(bottomDockPane) - }) - }) + bottomDock.show(); + expect(workspace.getVisiblePanes()).toContain(centerPane); + expect(workspace.getVisiblePanes()).toContain(leftDockPane); + expect(workspace.getVisiblePanes()).toContain(rightDockPane); + expect(workspace.getVisiblePanes()).toContain(bottomDockPane); + }); + }); describe('::getVisiblePaneContainers', () => { it('returns all visible pane containers', () => { - const center = workspace.getCenter() - const leftDock = workspace.getLeftDock() - const rightDock = workspace.getRightDock() - const bottomDock = workspace.getBottomDock() + const center = workspace.getCenter(); + const leftDock = workspace.getLeftDock(); + const rightDock = workspace.getRightDock(); + const bottomDock = workspace.getBottomDock(); - leftDock.hide() - rightDock.hide() - bottomDock.hide() - expect(workspace.getVisiblePaneContainers()).toEqual([center]) + leftDock.hide(); + rightDock.hide(); + bottomDock.hide(); + expect(workspace.getVisiblePaneContainers()).toEqual([center]); - leftDock.show() + leftDock.show(); expect(workspace.getVisiblePaneContainers().sort()).toEqual([ center, leftDock - ]) + ]); - rightDock.show() + rightDock.show(); expect(workspace.getVisiblePaneContainers().sort()).toEqual([ center, leftDock, rightDock - ]) + ]); - bottomDock.show() + bottomDock.show(); expect(workspace.getVisiblePaneContainers().sort()).toEqual([ center, leftDock, rightDock, bottomDock - ]) - }) - }) + ]); + }); + }); describe('when the core.allowPendingPaneItems option is falsy', () => { it('does not open item with `pending: true` option as pending', () => { - let pane = null - atom.config.set('core.allowPendingPaneItems', false) + let pane = null; + atom.config.set('core.allowPendingPaneItems', false); waitsForPromise(() => atom.workspace.open('sample.js', { pending: true }).then(() => { - pane = atom.workspace.getActivePane() + pane = atom.workspace.getActivePane(); }) - ) + ); - runs(() => expect(pane.getPendingItem()).toBeFalsy()) - }) - }) + runs(() => expect(pane.getPendingItem()).toBeFalsy()); + }); + }); describe('grammar activation', () => { it('notifies the workspace of which grammar is used', async () => { - atom.packages.triggerDeferredActivationHooks() + 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 - ) + ); - await atom.packages.activatePackage('language-ruby') - await atom.packages.activatePackage('language-javascript') - await atom.packages.activatePackage('language-c') - await atom.workspace.open('sample-with-comments.js') + await atom.packages.activatePackage('language-ruby'); + await atom.packages.activatePackage('language-javascript'); + await atom.packages.activatePackage('language-c'); + await atom.workspace.open('sample-with-comments.js'); // Hooks are triggered when opening new editors - expect(javascriptGrammarUsed).toHaveBeenCalled() + expect(javascriptGrammarUsed).toHaveBeenCalled(); // Hooks are triggered when changing existing editors grammars atom.grammars.assignLanguageMode( atom.workspace.getActiveTextEditor(), 'source.c' - ) - expect(cGrammarUsed).toHaveBeenCalled() + ); + expect(cGrammarUsed).toHaveBeenCalled(); // Hooks are triggered when editors are added in other ways. - atom.workspace.getActivePane().splitRight({ copyActiveItem: true }) + atom.workspace.getActivePane().splitRight({ copyActiveItem: true }); atom.grammars.assignLanguageMode( atom.workspace.getActiveTextEditor(), 'source.ruby' - ) - expect(rubyGrammarUsed).toHaveBeenCalled() - }) - }) + ); + expect(rubyGrammarUsed).toHaveBeenCalled(); + }); + }); describe('.checkoutHeadRevision()', () => { - let editor = null + let editor = null; beforeEach(async () => { - jasmine.useRealClock() - atom.config.set('editor.confirmCheckoutHeadRevision', false) + jasmine.useRealClock(); + atom.config.set('editor.confirmCheckoutHeadRevision', false); - editor = await atom.workspace.open('sample-with-comments.js') - }) + editor = await atom.workspace.open('sample-with-comments.js'); + }); it('reverts to the version of its file checked into the project repository', async () => { - editor.setCursorBufferPosition([0, 0]) - editor.insertText('---\n') - expect(editor.lineTextForBufferRow(0)).toBe('---') + editor.setCursorBufferPosition([0, 0]); + editor.insertText('---\n'); + expect(editor.lineTextForBufferRow(0)).toBe('---'); - atom.workspace.checkoutHeadRevision(editor) + atom.workspace.checkoutHeadRevision(editor); - await conditionPromise(() => editor.lineTextForBufferRow(0) === '') - }) + await conditionPromise(() => editor.lineTextForBufferRow(0) === ''); + }); describe("when there's no repository for the editor's file", () => { it("doesn't do anything", async () => { - editor = new TextEditor() - editor.setText('stuff') - atom.workspace.checkoutHeadRevision(editor) + editor = new TextEditor(); + editor.setText('stuff'); + atom.workspace.checkoutHeadRevision(editor); - atom.workspace.checkoutHeadRevision(editor) - }) - }) - }) + atom.workspace.checkoutHeadRevision(editor); + }); + }); + }); describe('when an item is moved', () => { beforeEach(() => { - atom.workspace.enablePersistence = true - }) + atom.workspace.enablePersistence = true; + }); afterEach(async () => { - await atom.workspace.itemLocationStore.clear() - atom.workspace.enablePersistence = false - }) + await atom.workspace.itemLocationStore.clear(); + atom.workspace.enablePersistence = false; + }); it("stores the new location if it's not the default", () => { - const ITEM_URI = 'atom://test' + const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, getDefaultLocation: () => 'left', getElement: () => document.createElement('div') - } - const centerPane = workspace.getActivePane() - centerPane.addItem(item) - const dockPane = atom.workspace.getRightDock().getActivePane() - spyOn(workspace.itemLocationStore, 'save') - centerPane.moveItemToPane(item, dockPane) + }; + const centerPane = workspace.getActivePane(); + centerPane.addItem(item); + const dockPane = atom.workspace.getRightDock().getActivePane(); + spyOn(workspace.itemLocationStore, 'save'); + centerPane.moveItemToPane(item, dockPane); expect(workspace.itemLocationStore.save).toHaveBeenCalledWith( ITEM_URI, 'right' - ) - }) + ); + }); it("clears the location if it's the default", () => { - const ITEM_URI = 'atom://test' + const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, getDefaultLocation: () => 'right', getElement: () => document.createElement('div') - } - const centerPane = workspace.getActivePane() - centerPane.addItem(item) - const dockPane = atom.workspace.getRightDock().getActivePane() - spyOn(workspace.itemLocationStore, 'save') - spyOn(workspace.itemLocationStore, 'delete') - centerPane.moveItemToPane(item, dockPane) - expect(workspace.itemLocationStore.delete).toHaveBeenCalledWith(ITEM_URI) - expect(workspace.itemLocationStore.save).not.toHaveBeenCalled() - }) - }) -}) + }; + const centerPane = workspace.getActivePane(); + centerPane.addItem(item); + const dockPane = atom.workspace.getRightDock().getActivePane(); + spyOn(workspace.itemLocationStore, 'save'); + spyOn(workspace.itemLocationStore, 'delete'); + centerPane.moveItemToPane(item, dockPane); + expect(workspace.itemLocationStore.delete).toHaveBeenCalledWith(ITEM_URI); + expect(workspace.itemLocationStore.save).not.toHaveBeenCalled(); + }); + }); +}); -function escapeStringRegex (string) { - return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') +function escapeStringRegex(string) { + return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); } diff --git a/src/application-delegate.js b/src/application-delegate.js index 34518c40e..cb6dccd32 100644 --- a/src/application-delegate.js +++ b/src/application-delegate.js @@ -1,230 +1,249 @@ -const {ipcRenderer, remote, shell} = require('electron') -const ipcHelpers = require('./ipc-helpers') -const {Emitter, Disposable} = require('event-kit') -const getWindowLoadSettings = require('./get-window-load-settings') +const { ipcRenderer, remote, shell } = require('electron'); +const ipcHelpers = require('./ipc-helpers'); +const { Emitter, Disposable } = require('event-kit'); +const getWindowLoadSettings = require('./get-window-load-settings'); -module.exports = -class ApplicationDelegate { - constructor () { - this.pendingSettingsUpdateCount = 0 - this._ipcMessageEmitter = null +module.exports = class ApplicationDelegate { + constructor() { + this.pendingSettingsUpdateCount = 0; + this._ipcMessageEmitter = null; } - ipcMessageEmitter () { + ipcMessageEmitter() { if (!this._ipcMessageEmitter) { - this._ipcMessageEmitter = new Emitter() + this._ipcMessageEmitter = new Emitter(); ipcRenderer.on('message', (event, message, detail) => { - this._ipcMessageEmitter.emit(message, detail) - }) + this._ipcMessageEmitter.emit(message, detail); + }); } - return this._ipcMessageEmitter + return this._ipcMessageEmitter; } - getWindowLoadSettings () { return getWindowLoadSettings() } - - open (params) { - return ipcRenderer.send('open', params) + getWindowLoadSettings() { + return getWindowLoadSettings(); } - pickFolder (callback) { - const responseChannel = 'atom-pick-folder-response' - ipcRenderer.on(responseChannel, function (event, path) { - ipcRenderer.removeAllListeners(responseChannel) - return callback(path) - }) - return ipcRenderer.send('pick-folder', responseChannel) + open(params) { + return ipcRenderer.send('open', params); } - getCurrentWindow () { - return remote.getCurrentWindow() + pickFolder(callback) { + const responseChannel = 'atom-pick-folder-response'; + ipcRenderer.on(responseChannel, function(event, path) { + ipcRenderer.removeAllListeners(responseChannel); + return callback(path); + }); + return ipcRenderer.send('pick-folder', responseChannel); } - closeWindow () { - return ipcHelpers.call('window-method', 'close') + getCurrentWindow() { + return remote.getCurrentWindow(); } - async getTemporaryWindowState () { - const stateJSON = await ipcHelpers.call('get-temporary-window-state') - return JSON.parse(stateJSON) + closeWindow() { + return ipcHelpers.call('window-method', 'close'); } - setTemporaryWindowState (state) { - return ipcHelpers.call('set-temporary-window-state', JSON.stringify(state)) + async getTemporaryWindowState() { + const stateJSON = await ipcHelpers.call('get-temporary-window-state'); + return JSON.parse(stateJSON); } - getWindowSize () { - const [width, height] = Array.from(remote.getCurrentWindow().getSize()) - return {width, height} + setTemporaryWindowState(state) { + return ipcHelpers.call('set-temporary-window-state', JSON.stringify(state)); } - setWindowSize (width, height) { - return ipcHelpers.call('set-window-size', width, height) + getWindowSize() { + const [width, height] = Array.from(remote.getCurrentWindow().getSize()); + return { width, height }; } - getWindowPosition () { - const [x, y] = Array.from(remote.getCurrentWindow().getPosition()) - return {x, y} + setWindowSize(width, height) { + return ipcHelpers.call('set-window-size', width, height); } - setWindowPosition (x, y) { - return ipcHelpers.call('set-window-position', x, y) + getWindowPosition() { + const [x, y] = Array.from(remote.getCurrentWindow().getPosition()); + return { x, y }; } - centerWindow () { - return ipcHelpers.call('center-window') + setWindowPosition(x, y) { + return ipcHelpers.call('set-window-position', x, y); } - focusWindow () { - return ipcHelpers.call('focus-window') + centerWindow() { + return ipcHelpers.call('center-window'); } - showWindow () { - return ipcHelpers.call('show-window') + focusWindow() { + return ipcHelpers.call('focus-window'); } - hideWindow () { - return ipcHelpers.call('hide-window') + showWindow() { + return ipcHelpers.call('show-window'); } - reloadWindow () { - return ipcHelpers.call('window-method', 'reload') + hideWindow() { + return ipcHelpers.call('hide-window'); } - restartApplication () { - return ipcRenderer.send('restart-application') + reloadWindow() { + return ipcHelpers.call('window-method', 'reload'); } - minimizeWindow () { - return ipcHelpers.call('window-method', 'minimize') + restartApplication() { + return ipcRenderer.send('restart-application'); } - isWindowMaximized () { - return remote.getCurrentWindow().isMaximized() + minimizeWindow() { + return ipcHelpers.call('window-method', 'minimize'); } - maximizeWindow () { - return ipcHelpers.call('window-method', 'maximize') + isWindowMaximized() { + return remote.getCurrentWindow().isMaximized(); } - unmaximizeWindow () { - return ipcHelpers.call('window-method', 'unmaximize') + maximizeWindow() { + return ipcHelpers.call('window-method', 'maximize'); } - isWindowFullScreen () { - return remote.getCurrentWindow().isFullScreen() + unmaximizeWindow() { + return ipcHelpers.call('window-method', 'unmaximize'); } - setWindowFullScreen (fullScreen = false) { - return ipcHelpers.call('window-method', 'setFullScreen', fullScreen) + isWindowFullScreen() { + return remote.getCurrentWindow().isFullScreen(); } - onDidEnterFullScreen (callback) { - return ipcHelpers.on(ipcRenderer, 'did-enter-full-screen', callback) + setWindowFullScreen(fullScreen = false) { + return ipcHelpers.call('window-method', 'setFullScreen', fullScreen); } - onDidLeaveFullScreen (callback) { - return ipcHelpers.on(ipcRenderer, 'did-leave-full-screen', callback) + onDidEnterFullScreen(callback) { + return ipcHelpers.on(ipcRenderer, 'did-enter-full-screen', callback); } - async openWindowDevTools () { + onDidLeaveFullScreen(callback) { + return ipcHelpers.on(ipcRenderer, 'did-leave-full-screen', callback); + } + + async openWindowDevTools() { // Defer DevTools interaction to the next tick, because using them during // event handling causes some wrong input events to be triggered on // `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). - await new Promise(process.nextTick) - return ipcHelpers.call('window-method', 'openDevTools') + await new Promise(process.nextTick); + return ipcHelpers.call('window-method', 'openDevTools'); } - async closeWindowDevTools () { + async closeWindowDevTools() { // Defer DevTools interaction to the next tick, because using them during // event handling causes some wrong input events to be triggered on // `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). - await new Promise(process.nextTick) - return ipcHelpers.call('window-method', 'closeDevTools') + await new Promise(process.nextTick); + return ipcHelpers.call('window-method', 'closeDevTools'); } - async toggleWindowDevTools () { + async toggleWindowDevTools() { // Defer DevTools interaction to the next tick, because using them during // event handling causes some wrong input events to be triggered on // `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). - await new Promise(process.nextTick) - return ipcHelpers.call('window-method', 'toggleDevTools') + await new Promise(process.nextTick); + return ipcHelpers.call('window-method', 'toggleDevTools'); } - executeJavaScriptInWindowDevTools (code) { - return ipcRenderer.send('execute-javascript-in-dev-tools', code) + executeJavaScriptInWindowDevTools(code) { + return ipcRenderer.send('execute-javascript-in-dev-tools', code); } - didClosePathWithWaitSession (path) { - return ipcHelpers.call('window-method', 'didClosePathWithWaitSession', path) + didClosePathWithWaitSession(path) { + return ipcHelpers.call( + 'window-method', + 'didClosePathWithWaitSession', + path + ); } - setWindowDocumentEdited (edited) { - return ipcHelpers.call('window-method', 'setDocumentEdited', edited) + setWindowDocumentEdited(edited) { + return ipcHelpers.call('window-method', 'setDocumentEdited', edited); } - setRepresentedFilename (filename) { - return ipcHelpers.call('window-method', 'setRepresentedFilename', filename) + setRepresentedFilename(filename) { + return ipcHelpers.call('window-method', 'setRepresentedFilename', filename); } - addRecentDocument (filename) { - return ipcRenderer.send('add-recent-document', filename) + addRecentDocument(filename) { + return ipcRenderer.send('add-recent-document', filename); } - setProjectRoots (paths) { - return ipcHelpers.call('window-method', 'setProjectRoots', paths) + setProjectRoots(paths) { + return ipcHelpers.call('window-method', 'setProjectRoots', paths); } - setAutoHideWindowMenuBar (autoHide) { - return ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide) + setAutoHideWindowMenuBar(autoHide) { + return ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide); } - setWindowMenuBarVisibility (visible) { - return remote.getCurrentWindow().setMenuBarVisibility(visible) + setWindowMenuBarVisibility(visible) { + return remote.getCurrentWindow().setMenuBarVisibility(visible); } - getPrimaryDisplayWorkAreaSize () { - return remote.screen.getPrimaryDisplay().workAreaSize + getPrimaryDisplayWorkAreaSize() { + return remote.screen.getPrimaryDisplay().workAreaSize; } - getUserDefault (key, type) { - return remote.systemPreferences.getUserDefault(key, type) + getUserDefault(key, type) { + return remote.systemPreferences.getUserDefault(key, type); } - async setUserSettings (config, configFilePath) { - this.pendingSettingsUpdateCount++ + async setUserSettings(config, configFilePath) { + this.pendingSettingsUpdateCount++; try { - await ipcHelpers.call('set-user-settings', JSON.stringify(config), configFilePath) + await ipcHelpers.call( + 'set-user-settings', + JSON.stringify(config), + configFilePath + ); } finally { - this.pendingSettingsUpdateCount-- + this.pendingSettingsUpdateCount--; } } - onDidChangeUserSettings (callback) { + onDidChangeUserSettings(callback) { return this.ipcMessageEmitter().on('did-change-user-settings', detail => { - if (this.pendingSettingsUpdateCount === 0) callback(detail) - }) + if (this.pendingSettingsUpdateCount === 0) callback(detail); + }); } - onDidFailToReadUserSettings (callback) { - return this.ipcMessageEmitter().on('did-fail-to-read-user-setting', callback) + onDidFailToReadUserSettings(callback) { + return this.ipcMessageEmitter().on( + 'did-fail-to-read-user-setting', + callback + ); } - confirm (options, callback) { + confirm(options, callback) { if (typeof callback === 'function') { // Async version: pass options directly to Electron but set sane defaults - options = Object.assign({type: 'info', normalizeAccessKeys: true}, options) - remote.dialog.showMessageBox(remote.getCurrentWindow(), options, callback) + options = Object.assign( + { type: 'info', normalizeAccessKeys: true }, + options + ); + remote.dialog.showMessageBox( + remote.getCurrentWindow(), + options, + callback + ); } else { // Legacy sync version: options can only have `message`, // `detailedMessage` (optional), and buttons array or object (optional) - let {message, detailedMessage, buttons} = options + let { message, detailedMessage, buttons } = options; - let buttonLabels - if (!buttons) buttons = {} + let buttonLabels; + if (!buttons) buttons = {}; if (Array.isArray(buttons)) { - buttonLabels = buttons + buttonLabels = buttons; } else { - buttonLabels = Object.keys(buttons) + buttonLabels = Object.keys(buttons); } const chosen = remote.dialog.showMessageBox(remote.getCurrentWindow(), { @@ -233,145 +252,161 @@ class ApplicationDelegate { detail: detailedMessage, buttons: buttonLabels, normalizeAccessKeys: true - }) + }); if (Array.isArray(buttons)) { - return chosen + return chosen; } else { - const callback = buttons[buttonLabels[chosen]] - if (typeof callback === 'function') return callback() + const callback = buttons[buttonLabels[chosen]]; + if (typeof callback === 'function') return callback(); } } } - showMessageDialog (params) {} + showMessageDialog(params) {} - showSaveDialog (options, callback) { + showSaveDialog(options, callback) { if (typeof callback === 'function') { // Async - this.getCurrentWindow().showSaveDialog(options, callback) + this.getCurrentWindow().showSaveDialog(options, callback); } else { // Sync if (typeof options === 'string') { - options = {defaultPath: options} + options = { defaultPath: options }; } - return this.getCurrentWindow().showSaveDialog(options) + return this.getCurrentWindow().showSaveDialog(options); } } - playBeepSound () { - return shell.beep() + playBeepSound() { + return shell.beep(); } - onDidOpenLocations (callback) { - return this.ipcMessageEmitter().on('open-locations', callback) + onDidOpenLocations(callback) { + return this.ipcMessageEmitter().on('open-locations', callback); } - onUpdateAvailable (callback) { + onUpdateAvailable(callback) { // TODO: Yes, this is strange that `onUpdateAvailable` is listening for // `did-begin-downloading-update`. We currently have no mechanism to know // if there is an update, so begin of downloading is a good proxy. - return this.ipcMessageEmitter().on('did-begin-downloading-update', callback) + return this.ipcMessageEmitter().on( + 'did-begin-downloading-update', + callback + ); } - onDidBeginDownloadingUpdate (callback) { - return this.onUpdateAvailable(callback) + onDidBeginDownloadingUpdate(callback) { + return this.onUpdateAvailable(callback); } - onDidBeginCheckingForUpdate (callback) { - return this.ipcMessageEmitter().on('checking-for-update', callback) + onDidBeginCheckingForUpdate(callback) { + return this.ipcMessageEmitter().on('checking-for-update', callback); } - onDidCompleteDownloadingUpdate (callback) { - return this.ipcMessageEmitter().on('update-available', callback) + onDidCompleteDownloadingUpdate(callback) { + return this.ipcMessageEmitter().on('update-available', callback); } - onUpdateNotAvailable (callback) { - return this.ipcMessageEmitter().on('update-not-available', callback) + onUpdateNotAvailable(callback) { + return this.ipcMessageEmitter().on('update-not-available', callback); } - onUpdateError (callback) { - return this.ipcMessageEmitter().on('update-error', callback) + onUpdateError(callback) { + return this.ipcMessageEmitter().on('update-error', callback); } - onApplicationMenuCommand (handler) { - const outerCallback = (event, ...args) => handler(...args) + onApplicationMenuCommand(handler) { + const outerCallback = (event, ...args) => handler(...args); - ipcRenderer.on('command', outerCallback) - return new Disposable(() => ipcRenderer.removeListener('command', outerCallback)) + ipcRenderer.on('command', outerCallback); + return new Disposable(() => + ipcRenderer.removeListener('command', outerCallback) + ); } - onContextMenuCommand (handler) { - const outerCallback = (event, ...args) => handler(...args) + onContextMenuCommand(handler) { + const outerCallback = (event, ...args) => handler(...args); - ipcRenderer.on('context-command', outerCallback) - return new Disposable(() => ipcRenderer.removeListener('context-command', outerCallback)) + ipcRenderer.on('context-command', outerCallback); + return new Disposable(() => + ipcRenderer.removeListener('context-command', outerCallback) + ); } - onURIMessage (handler) { - const outerCallback = (event, ...args) => handler(...args) + onURIMessage(handler) { + const outerCallback = (event, ...args) => handler(...args); - ipcRenderer.on('uri-message', outerCallback) - return new Disposable(() => ipcRenderer.removeListener('uri-message', outerCallback)) + ipcRenderer.on('uri-message', outerCallback); + return new Disposable(() => + ipcRenderer.removeListener('uri-message', outerCallback) + ); } - onDidRequestUnload (callback) { + onDidRequestUnload(callback) { const outerCallback = async (event, message) => { - const shouldUnload = await callback(event) - ipcRenderer.send('did-prepare-to-unload', shouldUnload) - } + const shouldUnload = await callback(event); + ipcRenderer.send('did-prepare-to-unload', shouldUnload); + }; - ipcRenderer.on('prepare-to-unload', outerCallback) - return new Disposable(() => ipcRenderer.removeListener('prepare-to-unload', outerCallback)) + ipcRenderer.on('prepare-to-unload', outerCallback); + return new Disposable(() => + ipcRenderer.removeListener('prepare-to-unload', outerCallback) + ); } - onDidChangeHistoryManager (callback) { - const outerCallback = (event, message) => callback(event) + onDidChangeHistoryManager(callback) { + const outerCallback = (event, message) => callback(event); - ipcRenderer.on('did-change-history-manager', outerCallback) - return new Disposable(() => ipcRenderer.removeListener('did-change-history-manager', outerCallback)) + ipcRenderer.on('did-change-history-manager', outerCallback); + return new Disposable(() => + ipcRenderer.removeListener('did-change-history-manager', outerCallback) + ); } - didChangeHistoryManager () { - return ipcRenderer.send('did-change-history-manager') + didChangeHistoryManager() { + return ipcRenderer.send('did-change-history-manager'); } - openExternal (url) { - return shell.openExternal(url) + openExternal(url) { + return shell.openExternal(url); } - checkForUpdate () { - return ipcRenderer.send('command', 'application:check-for-update') + checkForUpdate() { + return ipcRenderer.send('command', 'application:check-for-update'); } - restartAndInstallUpdate () { - return ipcRenderer.send('command', 'application:install-update') + restartAndInstallUpdate() { + return ipcRenderer.send('command', 'application:install-update'); } - getAutoUpdateManagerState () { - return ipcRenderer.sendSync('get-auto-update-manager-state') + getAutoUpdateManagerState() { + return ipcRenderer.sendSync('get-auto-update-manager-state'); } - getAutoUpdateManagerErrorMessage () { - return ipcRenderer.sendSync('get-auto-update-manager-error') + getAutoUpdateManagerErrorMessage() { + return ipcRenderer.sendSync('get-auto-update-manager-error'); } - emitWillSavePath (path) { - return ipcHelpers.call('will-save-path', path) + emitWillSavePath(path) { + return ipcHelpers.call('will-save-path', path); } - emitDidSavePath (path) { - return ipcHelpers.call('did-save-path', path) + emitDidSavePath(path) { + return ipcHelpers.call('did-save-path', path); } - resolveProxy (requestId, url) { - return ipcRenderer.send('resolve-proxy', requestId, url) + resolveProxy(requestId, url) { + return ipcRenderer.send('resolve-proxy', requestId, url); } - onDidResolveProxy (callback) { - const outerCallback = (event, requestId, proxy) => callback(requestId, proxy) + onDidResolveProxy(callback) { + const outerCallback = (event, requestId, proxy) => + callback(requestId, proxy); - ipcRenderer.on('did-resolve-proxy', outerCallback) - return new Disposable(() => ipcRenderer.removeListener('did-resolve-proxy', outerCallback)) + ipcRenderer.on('did-resolve-proxy', outerCallback); + return new Disposable(() => + ipcRenderer.removeListener('did-resolve-proxy', outerCallback) + ); } -} +}; diff --git a/src/atom-environment.js b/src/atom-environment.js index ddc85aaf3..f8257b1c9 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -1,54 +1,54 @@ -const crypto = require('crypto') -const path = require('path') -const util = require('util') -const {ipcRenderer} = require('electron') +const crypto = require('crypto'); +const path = require('path'); +const util = require('util'); +const { ipcRenderer } = require('electron'); -const _ = require('underscore-plus') -const {deprecate} = require('grim') -const {CompositeDisposable, Disposable, Emitter} = require('event-kit') -const fs = require('fs-plus') -const {mapSourcePosition} = require('@atom/source-map-support') -const WindowEventHandler = require('./window-event-handler') -const StateStore = require('./state-store') -const registerDefaultCommands = require('./register-default-commands') -const {updateProcessEnv} = require('./update-process-env') -const ConfigSchema = require('./config-schema') +const _ = require('underscore-plus'); +const { deprecate } = require('grim'); +const { CompositeDisposable, Disposable, Emitter } = require('event-kit'); +const fs = require('fs-plus'); +const { mapSourcePosition } = require('@atom/source-map-support'); +const WindowEventHandler = require('./window-event-handler'); +const StateStore = require('./state-store'); +const registerDefaultCommands = require('./register-default-commands'); +const { updateProcessEnv } = require('./update-process-env'); +const ConfigSchema = require('./config-schema'); -const DeserializerManager = require('./deserializer-manager') -const ViewRegistry = require('./view-registry') -const NotificationManager = require('./notification-manager') -const Config = require('./config') -const KeymapManager = require('./keymap-extensions') -const TooltipManager = require('./tooltip-manager') -const CommandRegistry = require('./command-registry') -const URIHandlerRegistry = require('./uri-handler-registry') -const GrammarRegistry = require('./grammar-registry') -const {HistoryManager} = require('./history-manager') -const ReopenProjectMenuManager = require('./reopen-project-menu-manager') -const StyleManager = require('./style-manager') -const PackageManager = require('./package-manager') -const ThemeManager = require('./theme-manager') -const MenuManager = require('./menu-manager') -const ContextMenuManager = require('./context-menu-manager') -const CommandInstaller = require('./command-installer') -const CoreURIHandlers = require('./core-uri-handlers') -const ProtocolHandlerInstaller = require('./protocol-handler-installer') -const Project = require('./project') -const TitleBar = require('./title-bar') -const Workspace = require('./workspace') -const PaneContainer = require('./pane-container') -const PaneAxis = require('./pane-axis') -const Pane = require('./pane') -const Dock = require('./dock') -const TextEditor = require('./text-editor') -const TextBuffer = require('text-buffer') -const TextEditorRegistry = require('./text-editor-registry') -const AutoUpdateManager = require('./auto-update-manager') -const StartupTime = require('./startup-time') +const DeserializerManager = require('./deserializer-manager'); +const ViewRegistry = require('./view-registry'); +const NotificationManager = require('./notification-manager'); +const Config = require('./config'); +const KeymapManager = require('./keymap-extensions'); +const TooltipManager = require('./tooltip-manager'); +const CommandRegistry = require('./command-registry'); +const URIHandlerRegistry = require('./uri-handler-registry'); +const GrammarRegistry = require('./grammar-registry'); +const { HistoryManager } = require('./history-manager'); +const ReopenProjectMenuManager = require('./reopen-project-menu-manager'); +const StyleManager = require('./style-manager'); +const PackageManager = require('./package-manager'); +const ThemeManager = require('./theme-manager'); +const MenuManager = require('./menu-manager'); +const ContextMenuManager = require('./context-menu-manager'); +const CommandInstaller = require('./command-installer'); +const CoreURIHandlers = require('./core-uri-handlers'); +const ProtocolHandlerInstaller = require('./protocol-handler-installer'); +const Project = require('./project'); +const TitleBar = require('./title-bar'); +const Workspace = require('./workspace'); +const PaneContainer = require('./pane-container'); +const PaneAxis = require('./pane-axis'); +const Pane = require('./pane'); +const Dock = require('./dock'); +const TextEditor = require('./text-editor'); +const TextBuffer = require('text-buffer'); +const TextEditorRegistry = require('./text-editor-registry'); +const AutoUpdateManager = require('./auto-update-manager'); +const StartupTime = require('./startup-time'); -const stat = util.promisify(fs.stat) +const stat = util.promisify(fs.stat); -let nextId = 0 +let nextId = 0; // Essential: Atom global for dealing with packages, themes, menus, and the window. // @@ -58,59 +58,70 @@ class AtomEnvironment { Section: Properties */ - constructor (params = {}) { - this.id = (params.id != null) ? params.id : nextId++ + constructor(params = {}) { + this.id = params.id != null ? params.id : nextId++; // Public: A {Clipboard} instance - this.clipboard = params.clipboard - this.updateProcessEnv = params.updateProcessEnv || updateProcessEnv - this.enablePersistence = params.enablePersistence - this.applicationDelegate = params.applicationDelegate + this.clipboard = params.clipboard; + this.updateProcessEnv = params.updateProcessEnv || updateProcessEnv; + this.enablePersistence = params.enablePersistence; + this.applicationDelegate = params.applicationDelegate; - this.nextProxyRequestId = 0 - this.unloading = false - this.loadTime = null - this.emitter = new Emitter() - this.disposables = new CompositeDisposable() - this.pathsWithWaitSessions = new Set() + this.nextProxyRequestId = 0; + this.unloading = false; + this.loadTime = null; + this.emitter = new Emitter(); + this.disposables = new CompositeDisposable(); + this.pathsWithWaitSessions = new Set(); // Public: A {DeserializerManager} instance - this.deserializers = new DeserializerManager(this) - this.deserializeTimings = {} + this.deserializers = new DeserializerManager(this); + this.deserializeTimings = {}; // Public: A {ViewRegistry} instance - this.views = new ViewRegistry(this) + this.views = new ViewRegistry(this); // Public: A {NotificationManager} instance - this.notifications = new NotificationManager() + this.notifications = new NotificationManager(); - this.stateStore = new StateStore('AtomEnvironments', 1) + this.stateStore = new StateStore('AtomEnvironments', 1); // Public: A {Config} instance this.config = new Config({ saveCallback: settings => { if (this.enablePersistence) { - this.applicationDelegate.setUserSettings(settings, this.config.getUserConfigPath()) + this.applicationDelegate.setUserSettings( + settings, + this.config.getUserConfigPath() + ); } } - }) - this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)}) + }); + this.config.setSchema(null, { + type: 'object', + properties: _.clone(ConfigSchema) + }); // Public: A {KeymapManager} instance - this.keymaps = new KeymapManager({notificationManager: this.notifications}) + this.keymaps = new KeymapManager({ + notificationManager: this.notifications + }); // Public: A {TooltipManager} instance - this.tooltips = new TooltipManager({keymapManager: this.keymaps, viewRegistry: this.views}) + this.tooltips = new TooltipManager({ + keymapManager: this.keymaps, + viewRegistry: this.views + }); // Public: A {CommandRegistry} instance - this.commands = new CommandRegistry() - this.uriHandlerRegistry = new URIHandlerRegistry() + this.commands = new CommandRegistry(); + this.uriHandlerRegistry = new URIHandlerRegistry(); // Public: A {GrammarRegistry} instance - this.grammars = new GrammarRegistry({config: this.config}) + this.grammars = new GrammarRegistry({ config: this.config }); // Public: A {StyleManager} instance - this.styles = new StyleManager() + this.styles = new StyleManager(); // Public: A {PackageManager} instance this.packages = new PackageManager({ @@ -123,7 +134,7 @@ class AtomEnvironment { deserializerManager: this.deserializers, viewRegistry: this.views, uriHandlerRegistry: this.uriHandlerRegistry - }) + }); // Public: A {ThemeManager} instance this.themes = new ThemeManager({ @@ -132,17 +143,20 @@ class AtomEnvironment { styleManager: this.styles, notificationManager: this.notifications, viewRegistry: this.views - }) + }); // Public: A {MenuManager} instance - this.menu = new MenuManager({keymapManager: this.keymaps, packageManager: this.packages}) + this.menu = new MenuManager({ + keymapManager: this.keymaps, + packageManager: this.packages + }); // Public: A {ContextMenuManager} instance - this.contextMenu = new ContextMenuManager({keymapManager: this.keymaps}) + this.contextMenu = new ContextMenuManager({ keymapManager: this.keymaps }); - this.packages.setMenuManager(this.menu) - this.packages.setContextMenuManager(this.contextMenu) - this.packages.setThemeManager(this.themes) + this.packages.setMenuManager(this.menu); + this.packages.setContextMenuManager(this.contextMenu); + this.packages.setThemeManager(this.themes); // Public: A {Project} instance this.project = new Project({ @@ -151,9 +165,9 @@ class AtomEnvironment { grammarRegistry: this.grammars, config: this.config, applicationDelegate: this.applicationDelegate - }) - this.commandInstaller = new CommandInstaller(this.applicationDelegate) - this.protocolHandlerInstaller = new ProtocolHandlerInstaller() + }); + this.commandInstaller = new CommandInstaller(this.applicationDelegate); + this.protocolHandlerInstaller = new ProtocolHandlerInstaller(); // Public: A {TextEditorRegistry} instance this.textEditors = new TextEditorRegistry({ @@ -161,7 +175,7 @@ class AtomEnvironment { grammarRegistry: this.grammars, assert: this.assert.bind(this), packageManager: this.packages - }) + }); // Public: A {Workspace} instance this.workspace = new Workspace({ @@ -177,136 +191,174 @@ class AtomEnvironment { textEditorRegistry: this.textEditors, styleManager: this.styles, enablePersistence: this.enablePersistence - }) + }); - this.themes.workspace = this.workspace + this.themes.workspace = this.workspace; - this.autoUpdater = new AutoUpdateManager({applicationDelegate: this.applicationDelegate}) + this.autoUpdater = new AutoUpdateManager({ + applicationDelegate: this.applicationDelegate + }); if (this.keymaps.canLoadBundledKeymapsFromMemory()) { - this.keymaps.loadBundledKeymaps() + this.keymaps.loadBundledKeymaps(); } - this.registerDefaultCommands() - this.registerDefaultOpeners() - this.registerDefaultDeserializers() + this.registerDefaultCommands(); + this.registerDefaultOpeners(); + this.registerDefaultDeserializers(); - this.windowEventHandler = new WindowEventHandler({atomEnvironment: this, applicationDelegate: this.applicationDelegate}) + this.windowEventHandler = new WindowEventHandler({ + atomEnvironment: this, + applicationDelegate: this.applicationDelegate + }); // Public: A {HistoryManager} instance - this.history = new HistoryManager({project: this.project, commands: this.commands, stateStore: this.stateStore}) + this.history = new HistoryManager({ + project: this.project, + commands: this.commands, + stateStore: this.stateStore + }); // Keep instances of HistoryManager in sync - this.disposables.add(this.history.onDidChangeProjects(event => { - if (!event.reloaded) this.applicationDelegate.didChangeHistoryManager() - })) + this.disposables.add( + this.history.onDidChangeProjects(event => { + if (!event.reloaded) this.applicationDelegate.didChangeHistoryManager(); + }) + ); } - initialize (params = {}) { + initialize(params = {}) { // This will force TextEditorElement to register the custom element, so that // using `document.createElement('atom-text-editor')` works if it's called // before opening a buffer. - require('./text-editor-element') + require('./text-editor-element'); - this.window = params.window - this.document = params.document - this.blobStore = params.blobStore - this.configDirPath = params.configDirPath + this.window = params.window; + this.document = params.document; + this.blobStore = params.blobStore; + this.configDirPath = params.configDirPath; - const {devMode, safeMode, resourcePath, userSettings, projectSpecification} = this.getLoadSettings() + const { + devMode, + safeMode, + resourcePath, + userSettings, + projectSpecification + } = this.getLoadSettings(); 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.' - } + description: + 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' + }; this.config.initialize({ - mainSource: this.enablePersistence && path.join(this.configDirPath, 'config.cson'), + mainSource: + this.enablePersistence && path.join(this.configDirPath, 'config.cson'), projectHomeSchema: ConfigSchema.projectHome - }) - this.config.resetUserSettings(userSettings) + }); + this.config.resetUserSettings(userSettings); if (projectSpecification != null && projectSpecification.config != null) { - this.project.replace(projectSpecification) + this.project.replace(projectSpecification); } - this.menu.initialize({resourcePath}) - this.contextMenu.initialize({resourcePath, devMode}) + this.menu.initialize({ resourcePath }); + this.contextMenu.initialize({ resourcePath, devMode }); - this.keymaps.configDirPath = this.configDirPath - this.keymaps.resourcePath = resourcePath - this.keymaps.devMode = devMode + this.keymaps.configDirPath = this.configDirPath; + this.keymaps.resourcePath = resourcePath; + this.keymaps.devMode = devMode; if (!this.keymaps.canLoadBundledKeymapsFromMemory()) { - this.keymaps.loadBundledKeymaps() + this.keymaps.loadBundledKeymaps(); } - this.commands.attach(this.window) + this.commands.attach(this.window); - this.styles.initialize({configDirPath: this.configDirPath}) - this.packages.initialize({devMode, configDirPath: this.configDirPath, resourcePath, safeMode}) - this.themes.initialize({configDirPath: this.configDirPath, resourcePath, safeMode, devMode}) + this.styles.initialize({ configDirPath: this.configDirPath }); + this.packages.initialize({ + devMode, + configDirPath: this.configDirPath, + resourcePath, + safeMode + }); + this.themes.initialize({ + configDirPath: this.configDirPath, + resourcePath, + safeMode, + devMode + }); - this.commandInstaller.initialize(this.getVersion()) - this.uriHandlerRegistry.registerHostHandler('core', CoreURIHandlers.create(this)) - this.autoUpdater.initialize() + this.commandInstaller.initialize(this.getVersion()); + this.uriHandlerRegistry.registerHostHandler( + 'core', + CoreURIHandlers.create(this) + ); + this.autoUpdater.initialize(); - this.protocolHandlerInstaller.initialize(this.config, this.notifications) + this.protocolHandlerInstaller.initialize(this.config, this.notifications); - this.themes.loadBaseStylesheets() - this.initialStyleElements = this.styles.getSnapshot() - if (params.onlyLoadBaseStyleSheets) this.themes.initialLoadComplete = true - this.setBodyPlatformClass() + this.themes.loadBaseStylesheets(); + this.initialStyleElements = this.styles.getSnapshot(); + if (params.onlyLoadBaseStyleSheets) this.themes.initialLoadComplete = true; + this.setBodyPlatformClass(); - this.stylesElement = this.styles.buildStylesElement() - this.document.head.appendChild(this.stylesElement) + this.stylesElement = this.styles.buildStylesElement(); + this.document.head.appendChild(this.stylesElement); - this.keymaps.subscribeToFileReadFailure() + this.keymaps.subscribeToFileReadFailure(); - this.installUncaughtErrorHandler() - this.attachSaveStateListeners() - this.windowEventHandler.initialize(this.window, this.document) + this.installUncaughtErrorHandler(); + this.attachSaveStateListeners(); + this.windowEventHandler.initialize(this.window, this.document); - const didChangeStyles = this.didChangeStyles.bind(this) - this.disposables.add(this.styles.onDidAddStyleElement(didChangeStyles)) - this.disposables.add(this.styles.onDidUpdateStyleElement(didChangeStyles)) - this.disposables.add(this.styles.onDidRemoveStyleElement(didChangeStyles)) + const didChangeStyles = this.didChangeStyles.bind(this); + this.disposables.add(this.styles.onDidAddStyleElement(didChangeStyles)); + this.disposables.add(this.styles.onDidUpdateStyleElement(didChangeStyles)); + this.disposables.add(this.styles.onDidRemoveStyleElement(didChangeStyles)); - this.observeAutoHideMenuBar() + this.observeAutoHideMenuBar(); - this.disposables.add(this.applicationDelegate.onDidChangeHistoryManager(() => this.history.loadState())) + this.disposables.add( + this.applicationDelegate.onDidChangeHistoryManager(() => + this.history.loadState() + ) + ); } - preloadPackages () { - return this.packages.preloadPackages() + preloadPackages() { + return this.packages.preloadPackages(); } - attachSaveStateListeners () { + attachSaveStateListeners() { const saveState = _.debounce(() => { this.window.requestIdleCallback(() => { - if (!this.unloading) this.saveState({isUnloading: false}) + if (!this.unloading) this.saveState({ isUnloading: false }); + }); + }, this.saveStateDebounceInterval); + this.document.addEventListener('mousedown', saveState, true); + this.document.addEventListener('keydown', saveState, true); + this.disposables.add( + new Disposable(() => { + this.document.removeEventListener('mousedown', saveState, true); + this.document.removeEventListener('keydown', saveState, true); }) - }, this.saveStateDebounceInterval) - this.document.addEventListener('mousedown', saveState, true) - this.document.addEventListener('keydown', saveState, true) - this.disposables.add(new Disposable(() => { - this.document.removeEventListener('mousedown', saveState, true) - this.document.removeEventListener('keydown', saveState, true) - })) + ); } - registerDefaultDeserializers () { - this.deserializers.add(Workspace) - this.deserializers.add(PaneContainer) - this.deserializers.add(PaneAxis) - this.deserializers.add(Pane) - this.deserializers.add(Dock) - this.deserializers.add(Project) - this.deserializers.add(TextEditor) - this.deserializers.add(TextBuffer) + registerDefaultDeserializers() { + this.deserializers.add(Workspace); + this.deserializers.add(PaneContainer); + this.deserializers.add(PaneAxis); + this.deserializers.add(Pane); + this.deserializers.add(Dock); + this.deserializers.add(Project); + this.deserializers.add(TextEditor); + this.deserializers.add(TextBuffer); } - registerDefaultCommands () { + registerDefaultCommands() { registerDefaultCommands({ commandRegistry: this.commands, config: this.config, @@ -314,84 +366,91 @@ class AtomEnvironment { notificationManager: this.notifications, project: this.project, clipboard: this.clipboard - }) + }); } - registerDefaultOpeners () { + registerDefaultOpeners() { this.workspace.addOpener(uri => { switch (uri) { case 'atom://.atom/stylesheet': - return this.workspace.openTextFile(this.styles.getUserStyleSheetPath()) + return this.workspace.openTextFile( + this.styles.getUserStyleSheetPath() + ); case 'atom://.atom/keymap': - return this.workspace.openTextFile(this.keymaps.getUserKeymapPath()) + return this.workspace.openTextFile(this.keymaps.getUserKeymapPath()); case 'atom://.atom/config': - return this.workspace.openTextFile(this.config.getUserConfigPath()) + return this.workspace.openTextFile(this.config.getUserConfigPath()); case 'atom://.atom/init-script': - return this.workspace.openTextFile(this.getUserInitScriptPath()) + return this.workspace.openTextFile(this.getUserInitScriptPath()); } - }) + }); } - registerDefaultTargetForKeymaps () { - this.keymaps.defaultTarget = this.workspace.getElement() + registerDefaultTargetForKeymaps() { + this.keymaps.defaultTarget = this.workspace.getElement(); } - observeAutoHideMenuBar () { - this.disposables.add(this.config.onDidChange('core.autoHideMenuBar', ({newValue}) => { - this.setAutoHideMenuBar(newValue) - })) - if (this.config.get('core.autoHideMenuBar')) this.setAutoHideMenuBar(true) + observeAutoHideMenuBar() { + this.disposables.add( + this.config.onDidChange('core.autoHideMenuBar', ({ newValue }) => { + this.setAutoHideMenuBar(newValue); + }) + ); + if (this.config.get('core.autoHideMenuBar')) this.setAutoHideMenuBar(true); } - async reset () { - this.deserializers.clear() - this.registerDefaultDeserializers() + async reset() { + this.deserializers.clear(); + this.registerDefaultDeserializers(); - this.config.clear() - this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)}) + this.config.clear(); + this.config.setSchema(null, { + type: 'object', + properties: _.clone(ConfigSchema) + }); - this.keymaps.clear() - this.keymaps.loadBundledKeymaps() + this.keymaps.clear(); + this.keymaps.loadBundledKeymaps(); - this.commands.clear() - this.registerDefaultCommands() + this.commands.clear(); + this.registerDefaultCommands(); - this.styles.restoreSnapshot(this.initialStyleElements) + this.styles.restoreSnapshot(this.initialStyleElements); - this.menu.clear() + this.menu.clear(); - this.clipboard.reset() + this.clipboard.reset(); - this.notifications.clear() + this.notifications.clear(); - this.contextMenu.clear() + this.contextMenu.clear(); - await this.packages.reset() - this.workspace.reset(this.packages) - this.registerDefaultOpeners() - this.project.reset(this.packages) - this.workspace.subscribeToEvents() - this.grammars.clear() - this.textEditors.clear() - this.views.clear() - this.pathsWithWaitSessions.clear() + await this.packages.reset(); + this.workspace.reset(this.packages); + this.registerDefaultOpeners(); + this.project.reset(this.packages); + this.workspace.subscribeToEvents(); + this.grammars.clear(); + this.textEditors.clear(); + this.views.clear(); + this.pathsWithWaitSessions.clear(); } - destroy () { - if (!this.project) return + destroy() { + if (!this.project) return; - this.disposables.dispose() - if (this.workspace) this.workspace.destroy() - this.workspace = null - this.themes.workspace = null - if (this.project) this.project.destroy() - this.project = null - this.commands.clear() - if (this.stylesElement) this.stylesElement.remove() - this.autoUpdater.destroy() - this.uriHandlerRegistry.destroy() + this.disposables.dispose(); + if (this.workspace) this.workspace.destroy(); + this.workspace = null; + this.themes.workspace = null; + if (this.project) this.project.destroy(); + this.project = null; + this.commands.clear(); + if (this.stylesElement) this.stylesElement.remove(); + this.autoUpdater.destroy(); + this.uriHandlerRegistry.destroy(); - this.uninstallWindowEventHandler() + this.uninstallWindowEventHandler(); } /* @@ -403,8 +462,8 @@ class AtomEnvironment { // * `callback` {Function} to be called whenever {::beep} is called. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidBeep (callback) { - return this.emitter.on('did-beep', callback) + onDidBeep(callback) { + return this.emitter.on('did-beep', callback); } // Extended: Invoke the given callback when there is an unhandled error, but @@ -420,8 +479,8 @@ class AtomEnvironment { // * `preventDefault` {Function} call this to avoid popping up the dev tools. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillThrowError (callback) { - return this.emitter.on('will-throw-error', callback) + onWillThrowError(callback) { + return this.emitter.on('will-throw-error', callback); } // Extended: Invoke the given callback whenever there is an unhandled error. @@ -435,27 +494,27 @@ class AtomEnvironment { // * `column` {Number} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidThrowError (callback) { - return this.emitter.on('did-throw-error', callback) + onDidThrowError(callback) { + return this.emitter.on('did-throw-error', callback); } // TODO: Make this part of the public API. We should make onDidThrowError // match the interface by only yielding an exception object to the handler // and deprecating the old behavior. - onDidFailAssertion (callback) { - return this.emitter.on('did-fail-assertion', callback) + onDidFailAssertion(callback) { + return this.emitter.on('did-fail-assertion', callback); } // Extended: Invoke the given callback as soon as the shell environment is // loaded (or immediately if it was already loaded). // // * `callback` {Function} to be called whenever there is an unhandled error - whenShellEnvironmentLoaded (callback) { + whenShellEnvironmentLoaded(callback) { if (this.shellEnvironmentLoaded) { - callback() - return new Disposable() + callback(); + return new Disposable(); } else { - return this.emitter.once('loaded-shell-environment', callback) + return this.emitter.once('loaded-shell-environment', callback); } } @@ -464,28 +523,29 @@ class AtomEnvironment { */ // Public: Returns a {Boolean} that is `true` if the current window is in development mode. - inDevMode () { - if (this.devMode == null) this.devMode = this.getLoadSettings().devMode - return this.devMode + inDevMode() { + if (this.devMode == null) this.devMode = this.getLoadSettings().devMode; + return this.devMode; } // Public: Returns a {Boolean} that is `true` if the current window is in safe mode. - inSafeMode () { - if (this.safeMode == null) this.safeMode = this.getLoadSettings().safeMode - return this.safeMode + inSafeMode() { + if (this.safeMode == null) this.safeMode = this.getLoadSettings().safeMode; + return this.safeMode; } // Public: Returns a {Boolean} that is `true` if the current window is running specs. - inSpecMode () { - if (this.specMode == null) this.specMode = this.getLoadSettings().isSpec - return this.specMode + inSpecMode() { + if (this.specMode == null) this.specMode = this.getLoadSettings().isSpec; + return this.specMode; } // Returns a {Boolean} indicating whether this the first time the window's been // loaded. - isFirstLoad () { - if (this.firstLoad == null) this.firstLoad = this.getLoadSettings().firstLoad - return this.firstLoad + isFirstLoad() { + if (this.firstLoad == null) + this.firstLoad = this.getLoadSettings().firstLoad; + return this.firstLoad; } // Public: Get the full name of this Atom release (e.g. "Atom", "Atom Beta") @@ -499,9 +559,10 @@ class AtomEnvironment { // Public: Get the version of the Atom application. // // Returns the version text {String}. - getVersion () { - if (this.appVersion == null) this.appVersion = this.getLoadSettings().appVersion - return this.appVersion + getVersion() { + if (this.appVersion == null) + this.appVersion = this.getLoadSettings().appVersion; + return this.appVersion; } // Public: Gets the release channel of the Atom application. @@ -509,22 +570,24 @@ class AtomEnvironment { // Returns the release channel as a {String}. Will return a specific release channel // name like 'beta' or 'nightly' if one is found in the Atom version or 'stable' // otherwise. - getReleaseChannel () { + getReleaseChannel() { // This matches stable, dev (with or without commit hash) and any other // release channel following the pattern '1.00.0-channel0' - const match = this.getVersion().match(/\d+\.\d+\.\d+(-([a-z]+)(\d+|-\w{4,})?)?$/) + const match = this.getVersion().match( + /\d+\.\d+\.\d+(-([a-z]+)(\d+|-\w{4,})?)?$/ + ); if (!match) { - return 'unrecognized' + return 'unrecognized'; } else if (match[2]) { - return match[2] + return match[2]; } - return 'stable' + return 'stable'; } // Public: Returns a {Boolean} that is `true` if the current version is an official release. - isReleasedVersion () { - return this.getReleaseChannel().match(/stable|beta|nightly/) != null + isReleasedVersion() { + return this.getReleaseChannel().match(/stable|beta|nightly/) != null; } // Public: Get the time taken to completely load the current window. @@ -534,8 +597,8 @@ class AtomEnvironment { // // Returns the {Number} of milliseconds taken to load the window or null // if the window hasn't finished loading yet. - getWindowLoadTime () { - return this.loadTime + getWindowLoadTime() { + return this.loadTime; } // Public: Get the all the markers with the information about startup time. @@ -544,17 +607,17 @@ class AtomEnvironment { // Each timing is an object with two keys: // * `label`: string // * `time`: Time since the `startTime` (in milliseconds). - getStartupMarkers () { - const data = StartupTime.exportData() + getStartupMarkers() { + const data = StartupTime.exportData(); - return data ? data.markers : [] + return data ? data.markers : []; } // Public: Get the load settings for the current window. // // Returns an {Object} containing all the load setting key/value pairs. - getLoadSettings () { - return this.applicationDelegate.getWindowLoadSettings() + getLoadSettings() { + return this.applicationDelegate.getWindowLoadSettings(); } /* @@ -575,8 +638,8 @@ class AtomEnvironment { // repository and also loads all the packages in ~/.atom/dev/packages // * `safeMode` A {Boolean}, true to open the window in safe mode. Safe // mode prevents all packages installed to ~/.atom/packages from loading. - open (params) { - return this.applicationDelegate.open(params) + open(params) { + return this.applicationDelegate.open(params); } // Extended: Prompt the user to select one or more folders. @@ -584,123 +647,123 @@ class AtomEnvironment { // * `callback` A {Function} to call once the user has confirmed the selection. // * `paths` An {Array} of {String} paths that the user selected, or `null` // if the user dismissed the dialog. - pickFolder (callback) { - return this.applicationDelegate.pickFolder(callback) + pickFolder(callback) { + return this.applicationDelegate.pickFolder(callback); } // Essential: Close the current window. - close () { - return this.applicationDelegate.closeWindow() + close() { + return this.applicationDelegate.closeWindow(); } // Essential: Get the size of current window. // // Returns an {Object} in the format `{width: 1000, height: 700}` - getSize () { - return this.applicationDelegate.getWindowSize() + getSize() { + return this.applicationDelegate.getWindowSize(); } // Essential: Set the size of current window. // // * `width` The {Number} of pixels. // * `height` The {Number} of pixels. - setSize (width, height) { - return this.applicationDelegate.setWindowSize(width, height) + setSize(width, height) { + return this.applicationDelegate.setWindowSize(width, height); } // Essential: Get the position of current window. // // Returns an {Object} in the format `{x: 10, y: 20}` - getPosition () { - return this.applicationDelegate.getWindowPosition() + getPosition() { + return this.applicationDelegate.getWindowPosition(); } // Essential: Set the position of current window. // // * `x` The {Number} of pixels. // * `y` The {Number} of pixels. - setPosition (x, y) { - return this.applicationDelegate.setWindowPosition(x, y) + setPosition(x, y) { + return this.applicationDelegate.setWindowPosition(x, y); } // Extended: Get the current window - getCurrentWindow () { - return this.applicationDelegate.getCurrentWindow() + getCurrentWindow() { + return this.applicationDelegate.getCurrentWindow(); } // Extended: Move current window to the center of the screen. - center () { - return this.applicationDelegate.centerWindow() + center() { + return this.applicationDelegate.centerWindow(); } // Extended: Focus the current window. - focus () { - this.applicationDelegate.focusWindow() - return this.window.focus() + focus() { + this.applicationDelegate.focusWindow(); + return this.window.focus(); } // Extended: Show the current window. - show () { - return this.applicationDelegate.showWindow() + show() { + return this.applicationDelegate.showWindow(); } // Extended: Hide the current window. - hide () { - return this.applicationDelegate.hideWindow() + hide() { + return this.applicationDelegate.hideWindow(); } // Extended: Reload the current window. - reload () { - return this.applicationDelegate.reloadWindow() + reload() { + return this.applicationDelegate.reloadWindow(); } // Extended: Relaunch the entire application. - restartApplication () { - return this.applicationDelegate.restartApplication() + restartApplication() { + return this.applicationDelegate.restartApplication(); } // Extended: Returns a {Boolean} that is `true` if the current window is maximized. - isMaximized () { - return this.applicationDelegate.isWindowMaximized() + isMaximized() { + return this.applicationDelegate.isWindowMaximized(); } - maximize () { - return this.applicationDelegate.maximizeWindow() + maximize() { + return this.applicationDelegate.maximizeWindow(); } // Extended: Returns a {Boolean} that is `true` if the current window is in full screen mode. - isFullScreen () { - return this.applicationDelegate.isWindowFullScreen() + isFullScreen() { + return this.applicationDelegate.isWindowFullScreen(); } // Extended: Set the full screen state of the current window. - setFullScreen (fullScreen = false) { - return this.applicationDelegate.setWindowFullScreen(fullScreen) + setFullScreen(fullScreen = false) { + return this.applicationDelegate.setWindowFullScreen(fullScreen); } // Extended: Toggle the full screen state of the current window. - toggleFullScreen () { - return this.setFullScreen(!this.isFullScreen()) + toggleFullScreen() { + return this.setFullScreen(!this.isFullScreen()); } // Restore the window to its previous dimensions and show it. // // Restores the full screen and maximized state after the window has resized to // prevent resize glitches. - async displayWindow () { - await this.restoreWindowDimensions() - const steps = [ - this.restoreWindowBackground(), - this.show(), - this.focus() - ] + async displayWindow() { + await this.restoreWindowDimensions(); + const steps = [this.restoreWindowBackground(), this.show(), this.focus()]; if (this.windowDimensions && this.windowDimensions.fullScreen) { - steps.push(this.setFullScreen(true)) + steps.push(this.setFullScreen(true)); } - if (this.windowDimensions && this.windowDimensions.maximized && process.platform !== 'darwin') { - steps.push(this.maximize()) + if ( + this.windowDimensions && + this.windowDimensions.maximized && + process.platform !== 'darwin' + ) { + steps.push(this.maximize()); } - await Promise.all(steps) + await Promise.all(steps); } // Get the dimensions of this window. @@ -710,12 +773,12 @@ class AtomEnvironment { // * `y` The window's y-position {Number}. // * `width` The window's width {Number}. // * `height` The window's height {Number}. - getWindowDimensions () { - const browserWindow = this.getCurrentWindow() - const [x, y] = browserWindow.getPosition() - const [width, height] = browserWindow.getSize() - const maximized = browserWindow.isMaximized() - return {x, y, width, height, maximized} + getWindowDimensions() { + const browserWindow = this.getCurrentWindow(); + const [x, y] = browserWindow.getPosition(); + const [width, height] = browserWindow.getSize(); + const maximized = browserWindow.isMaximized(); + return { x, y, width, height, maximized }; } // Set the dimensions of the window. @@ -729,169 +792,247 @@ class AtomEnvironment { // * `y` The new y coordinate. // * `width` The new width. // * `height` The new height. - setWindowDimensions ({x, y, width, height}) { - const steps = [] + setWindowDimensions({ x, y, width, height }) { + const steps = []; if (width != null && height != null) { - steps.push(this.setSize(width, height)) + steps.push(this.setSize(width, height)); } if (x != null && y != null) { - steps.push(this.setPosition(x, y)) + steps.push(this.setPosition(x, y)); } else { - steps.push(this.center()) + steps.push(this.center()); } - return Promise.all(steps) + return Promise.all(steps); } // Returns true if the dimensions are useable, false if they should be ignored. // Work around for https://github.com/atom/atom-shell/issues/473 - isValidDimensions ({x, y, width, height} = {}) { - return (width > 0) && (height > 0) && ((x + width) > 0) && ((y + height) > 0) + isValidDimensions({ x, y, width, height } = {}) { + return width > 0 && height > 0 && x + width > 0 && y + height > 0; } - storeWindowDimensions () { - this.windowDimensions = this.getWindowDimensions() + storeWindowDimensions() { + this.windowDimensions = this.getWindowDimensions(); if (this.isValidDimensions(this.windowDimensions)) { - localStorage.setItem('defaultWindowDimensions', JSON.stringify(this.windowDimensions)) + localStorage.setItem( + 'defaultWindowDimensions', + JSON.stringify(this.windowDimensions) + ); } } - getDefaultWindowDimensions () { - const {windowDimensions} = this.getLoadSettings() - if (windowDimensions) return windowDimensions + getDefaultWindowDimensions() { + const { windowDimensions } = this.getLoadSettings(); + if (windowDimensions) return windowDimensions; - let dimensions + let dimensions; try { - dimensions = JSON.parse(localStorage.getItem('defaultWindowDimensions')) + dimensions = JSON.parse(localStorage.getItem('defaultWindowDimensions')); } catch (error) { - console.warn('Error parsing default window dimensions', error) - localStorage.removeItem('defaultWindowDimensions') + console.warn('Error parsing default window dimensions', error); + localStorage.removeItem('defaultWindowDimensions'); } if (dimensions && this.isValidDimensions(dimensions)) { - return dimensions + return dimensions; } else { - const {width, height} = this.applicationDelegate.getPrimaryDisplayWorkAreaSize() - return {x: 0, y: 0, width: Math.min(1024, width), height} + const { + width, + height + } = this.applicationDelegate.getPrimaryDisplayWorkAreaSize(); + return { x: 0, y: 0, width: Math.min(1024, width), height }; } } - async restoreWindowDimensions () { - if (!this.windowDimensions || !this.isValidDimensions(this.windowDimensions)) { - this.windowDimensions = this.getDefaultWindowDimensions() + async restoreWindowDimensions() { + if ( + !this.windowDimensions || + !this.isValidDimensions(this.windowDimensions) + ) { + this.windowDimensions = this.getDefaultWindowDimensions(); } - await this.setWindowDimensions(this.windowDimensions) - return this.windowDimensions + await this.setWindowDimensions(this.windowDimensions); + return this.windowDimensions; } - restoreWindowBackground () { - const backgroundColor = window.localStorage.getItem('atom:window-background-color') + restoreWindowBackground() { + const backgroundColor = window.localStorage.getItem( + 'atom:window-background-color' + ); if (backgroundColor) { - this.backgroundStylesheet = document.createElement('style') - this.backgroundStylesheet.type = 'text/css' - this.backgroundStylesheet.innerText = `html, body { background: ${backgroundColor} !important; }` - document.head.appendChild(this.backgroundStylesheet) + this.backgroundStylesheet = document.createElement('style'); + this.backgroundStylesheet.type = 'text/css'; + this.backgroundStylesheet.innerText = `html, body { background: ${backgroundColor} !important; }`; + document.head.appendChild(this.backgroundStylesheet); } } - storeWindowBackground () { - if (this.inSpecMode()) return + storeWindowBackground() { + if (this.inSpecMode()) return; - const backgroundColor = this.window.getComputedStyle(this.workspace.getElement())['background-color'] - this.window.localStorage.setItem('atom:window-background-color', backgroundColor) + const backgroundColor = this.window.getComputedStyle( + this.workspace.getElement() + )['background-color']; + this.window.localStorage.setItem( + 'atom:window-background-color', + backgroundColor + ); } // Call this method when establishing a real application window. - async startEditorWindow () { - StartupTime.addMarker('window:environment:start-editor-window:start') + async startEditorWindow() { + StartupTime.addMarker('window:environment:start-editor-window:start'); if (this.getLoadSettings().clearWindowState) { - await this.stateStore.clear() + await this.stateStore.clear(); } - this.unloading = false + this.unloading = false; - const updateProcessEnvPromise = this.updateProcessEnvAndTriggerHooks() + const updateProcessEnvPromise = this.updateProcessEnvAndTriggerHooks(); const loadStatePromise = this.loadState().then(async state => { - this.windowDimensions = state && state.windowDimensions + this.windowDimensions = state && state.windowDimensions; if (!this.getLoadSettings().headless) { - StartupTime.addMarker('window:environment:start-editor-window:display-window') - await this.displayWindow() + StartupTime.addMarker( + 'window:environment:start-editor-window:display-window' + ); + await this.displayWindow(); } - this.commandInstaller.installAtomCommand(false, (error) => { - if (error) console.warn(error.message) - }) - this.commandInstaller.installApmCommand(false, (error) => { - if (error) console.warn(error.message) - }) + this.commandInstaller.installAtomCommand(false, error => { + if (error) console.warn(error.message); + }); + this.commandInstaller.installApmCommand(false, error => { + if (error) console.warn(error.message); + }); - this.disposables.add(this.applicationDelegate.onDidChangeUserSettings(settings => - this.config.resetUserSettings(settings) - )) - this.disposables.add(this.applicationDelegate.onDidFailToReadUserSettings(message => - this.notifications.addError(message) - )) + this.disposables.add( + this.applicationDelegate.onDidChangeUserSettings(settings => + this.config.resetUserSettings(settings) + ) + ); + this.disposables.add( + this.applicationDelegate.onDidFailToReadUserSettings(message => + this.notifications.addError(message) + ) + ); - this.disposables.add(this.applicationDelegate.onDidOpenLocations(this.openLocations.bind(this))) - this.disposables.add(this.applicationDelegate.onApplicationMenuCommand(this.dispatchApplicationMenuCommand.bind(this))) - this.disposables.add(this.applicationDelegate.onContextMenuCommand(this.dispatchContextMenuCommand.bind(this))) - this.disposables.add(this.applicationDelegate.onURIMessage(this.dispatchURIMessage.bind(this))) - this.disposables.add(this.applicationDelegate.onDidRequestUnload(this.prepareToUnloadEditorWindow.bind(this))) + this.disposables.add( + this.applicationDelegate.onDidOpenLocations( + this.openLocations.bind(this) + ) + ); + this.disposables.add( + this.applicationDelegate.onApplicationMenuCommand( + this.dispatchApplicationMenuCommand.bind(this) + ) + ); + this.disposables.add( + this.applicationDelegate.onContextMenuCommand( + this.dispatchContextMenuCommand.bind(this) + ) + ); + this.disposables.add( + this.applicationDelegate.onURIMessage( + this.dispatchURIMessage.bind(this) + ) + ); + this.disposables.add( + this.applicationDelegate.onDidRequestUnload( + this.prepareToUnloadEditorWindow.bind(this) + ) + ); - this.listenForUpdates() + this.listenForUpdates(); - this.registerDefaultTargetForKeymaps() + this.registerDefaultTargetForKeymaps(); - StartupTime.addMarker('window:environment:start-editor-window:load-packages') - this.packages.loadPackages() + StartupTime.addMarker( + 'window:environment:start-editor-window:load-packages' + ); + this.packages.loadPackages(); - const startTime = Date.now() - StartupTime.addMarker('window:environment:start-editor-window:deserialize-state') - await this.deserialize(state) - this.deserializeTimings.atom = Date.now() - startTime + const startTime = Date.now(); + StartupTime.addMarker( + 'window:environment:start-editor-window:deserialize-state' + ); + await this.deserialize(state); + this.deserializeTimings.atom = Date.now() - startTime; - if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom') { - this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) - this.document.body.classList.add('custom-title-bar') + if ( + process.platform === 'darwin' && + this.config.get('core.titleBar') === 'custom' + ) { + this.workspace.addHeaderPanel({ + item: new TitleBar({ + workspace: this.workspace, + themes: this.themes, + applicationDelegate: this.applicationDelegate + }) + }); + this.document.body.classList.add('custom-title-bar'); } - if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'custom-inset') { - this.workspace.addHeaderPanel({item: new TitleBar({workspace: this.workspace, themes: this.themes, applicationDelegate: this.applicationDelegate})}) - this.document.body.classList.add('custom-inset-title-bar') + if ( + process.platform === 'darwin' && + this.config.get('core.titleBar') === 'custom-inset' + ) { + this.workspace.addHeaderPanel({ + item: new TitleBar({ + workspace: this.workspace, + themes: this.themes, + applicationDelegate: this.applicationDelegate + }) + }); + this.document.body.classList.add('custom-inset-title-bar'); } - if (process.platform === 'darwin' && this.config.get('core.titleBar') === 'hidden') { - this.document.body.classList.add('hidden-title-bar') + if ( + process.platform === 'darwin' && + this.config.get('core.titleBar') === 'hidden' + ) { + this.document.body.classList.add('hidden-title-bar'); } - this.document.body.appendChild(this.workspace.getElement()) - if (this.backgroundStylesheet) this.backgroundStylesheet.remove() + this.document.body.appendChild(this.workspace.getElement()); + if (this.backgroundStylesheet) this.backgroundStylesheet.remove(); - let previousProjectPaths = this.project.getPaths() - this.disposables.add(this.project.onDidChangePaths(newPaths => { - for (let path of previousProjectPaths) { - if (this.pathsWithWaitSessions.has(path) && !newPaths.includes(path)) { - this.applicationDelegate.didClosePathWithWaitSession(path) + let previousProjectPaths = this.project.getPaths(); + this.disposables.add( + this.project.onDidChangePaths(newPaths => { + for (let path of previousProjectPaths) { + if ( + this.pathsWithWaitSessions.has(path) && + !newPaths.includes(path) + ) { + this.applicationDelegate.didClosePathWithWaitSession(path); + } } - } - previousProjectPaths = newPaths - this.applicationDelegate.setProjectRoots(newPaths) - })) - this.disposables.add(this.workspace.onDidDestroyPaneItem(({item}) => { - const path = item.getPath && item.getPath() - if (this.pathsWithWaitSessions.has(path)) { - this.applicationDelegate.didClosePathWithWaitSession(path) - } - })) + previousProjectPaths = newPaths; + this.applicationDelegate.setProjectRoots(newPaths); + }) + ); + this.disposables.add( + this.workspace.onDidDestroyPaneItem(({ item }) => { + const path = item.getPath && item.getPath(); + if (this.pathsWithWaitSessions.has(path)) { + this.applicationDelegate.didClosePathWithWaitSession(path); + } + }) + ); - StartupTime.addMarker('window:environment:start-editor-window:activate-packages') - this.packages.activate() - this.keymaps.loadUserKeymap() - if (!this.getLoadSettings().safeMode) this.requireUserInitScript() + StartupTime.addMarker( + 'window:environment:start-editor-window:activate-packages' + ); + this.packages.activate(); + this.keymaps.loadUserKeymap(); + if (!this.getLoadSettings().safeMode) this.requireUserInitScript(); - this.menu.update() + this.menu.update(); - StartupTime.addMarker('window:environment:start-editor-window:open-editor') - await this.openInitialEmptyEditorIfNecessary() - }) + StartupTime.addMarker( + 'window:environment:start-editor-window:open-editor' + ); + await this.openInitialEmptyEditorIfNecessary(); + }); const loadHistoryPromise = this.history.loadState().then(() => { this.reopenProjectMenuManager = new ReopenProjectMenuManager({ @@ -899,19 +1040,23 @@ class AtomEnvironment { commands: this.commands, history: this.history, config: this.config, - open: paths => this.open({pathsToOpen: paths}) - }) - this.reopenProjectMenuManager.update() - }) + open: paths => this.open({ pathsToOpen: paths }) + }); + this.reopenProjectMenuManager.update(); + }); - const output = await Promise.all([loadStatePromise, loadHistoryPromise, updateProcessEnvPromise]) + const output = await Promise.all([ + loadStatePromise, + loadHistoryPromise, + updateProcessEnvPromise + ]); - StartupTime.addMarker('window:environment:start-editor-window:end') + StartupTime.addMarker('window:environment:start-editor-window:end'); - return output + return output; } - serialize (options) { + serialize(options) { return { version: this.constructor.version, project: this.project.serialize(options), @@ -920,102 +1065,115 @@ class AtomEnvironment { grammars: this.grammars.serialize(), fullScreen: this.isFullScreen(), windowDimensions: this.windowDimensions - } + }; } - async prepareToUnloadEditorWindow () { + async prepareToUnloadEditorWindow() { try { - await this.saveState({isUnloading: true}) + await this.saveState({ isUnloading: true }); } catch (error) { - console.error(error) + console.error(error); } - const closing = !this.workspace || await this.workspace.confirmClose({ - windowCloseRequested: true, - projectHasPaths: this.project.getPaths().length > 0 - }) + const closing = + !this.workspace || + (await this.workspace.confirmClose({ + windowCloseRequested: true, + projectHasPaths: this.project.getPaths().length > 0 + })); if (closing) { - this.unloading = true - await this.packages.deactivatePackages() + this.unloading = true; + await this.packages.deactivatePackages(); } - return closing + return closing; } - unloadEditorWindow () { - if (!this.project) return + unloadEditorWindow() { + if (!this.project) return; - this.storeWindowBackground() - this.saveBlobStoreSync() + this.storeWindowBackground(); + this.saveBlobStoreSync(); } - saveBlobStoreSync () { + saveBlobStoreSync() { if (this.enablePersistence) { - this.blobStore.save() + this.blobStore.save(); } } - openInitialEmptyEditorIfNecessary () { - if (!this.config.get('core.openEmptyEditorOnStart')) return - const {hasOpenFiles} = this.getLoadSettings() + openInitialEmptyEditorIfNecessary() { + if (!this.config.get('core.openEmptyEditorOnStart')) return; + const { hasOpenFiles } = this.getLoadSettings(); if (!hasOpenFiles && this.workspace.getPaneItems().length === 0) { - return this.workspace.open(null, {pending: true}) + return this.workspace.open(null, { pending: true }); } } - installUncaughtErrorHandler () { - this.previousWindowErrorHandler = this.window.onerror + installUncaughtErrorHandler() { + this.previousWindowErrorHandler = this.window.onerror; this.window.onerror = (message, url, line, column, originalError) => { - const mapping = mapSourcePosition({source: url, line, column}) - line = mapping.line - column = mapping.column - if (url === '') url = mapping.source + const mapping = mapSourcePosition({ source: url, line, column }); + line = mapping.line; + column = mapping.column; + if (url === '') url = mapping.source; - const eventObject = {message, url, line, column, originalError} + const eventObject = { message, url, line, column, originalError }; - let openDevTools = true - eventObject.preventDefault = () => { openDevTools = false } + let openDevTools = true; + eventObject.preventDefault = () => { + openDevTools = false; + }; - this.emitter.emit('will-throw-error', eventObject) + this.emitter.emit('will-throw-error', eventObject); if (openDevTools) { this.openDevTools().then(() => this.executeJavaScriptInDevTools('DevToolsAPI.showPanel("console")') - ) + ); } - this.emitter.emit('did-throw-error', {message, url, line, column, originalError}) - } + this.emitter.emit('did-throw-error', { + message, + url, + line, + column, + originalError + }); + }; } - uninstallUncaughtErrorHandler () { - this.window.onerror = this.previousWindowErrorHandler + uninstallUncaughtErrorHandler() { + this.window.onerror = this.previousWindowErrorHandler; } - installWindowEventHandler () { - this.windowEventHandler = new WindowEventHandler({atomEnvironment: this, applicationDelegate: this.applicationDelegate}) - this.windowEventHandler.initialize(this.window, this.document) + installWindowEventHandler() { + this.windowEventHandler = new WindowEventHandler({ + atomEnvironment: this, + applicationDelegate: this.applicationDelegate + }); + this.windowEventHandler.initialize(this.window, this.document); } - uninstallWindowEventHandler () { + uninstallWindowEventHandler() { if (this.windowEventHandler) { - this.windowEventHandler.unsubscribe() + this.windowEventHandler.unsubscribe(); } - this.windowEventHandler = null + this.windowEventHandler = null; } - didChangeStyles (styleElement) { - TextEditor.didUpdateStyles() + didChangeStyles(styleElement) { + TextEditor.didUpdateStyles(); if (styleElement.textContent.indexOf('scrollbar') >= 0) { - TextEditor.didUpdateScrollbarStyles() + TextEditor.didUpdateScrollbarStyles(); } } - async updateProcessEnvAndTriggerHooks () { - await this.updateProcessEnv(this.getLoadSettings().env) - this.shellEnvironmentLoaded = true - this.emitter.emit('loaded-shell-environment') - this.packages.triggerActivationHook('core:loaded-shell-environment') + async updateProcessEnvAndTriggerHooks() { + await this.updateProcessEnv(this.getLoadSettings().env); + this.shellEnvironmentLoaded = true; + this.emitter.emit('loaded-shell-environment'); + this.packages.triggerActivationHook('core:loaded-shell-environment'); } /* @@ -1023,9 +1181,10 @@ class AtomEnvironment { */ // Essential: Visually and audibly trigger a beep. - beep () { - if (this.config.get('core.audioBeep')) this.applicationDelegate.playBeepSound() - this.emitter.emit('did-beep') + beep() { + if (this.config.get('core.audioBeep')) + this.applicationDelegate.playBeepSound(); + this.emitter.emit('did-beep'); } // Essential: A flexible way to open a dialog akin to an alert dialog. @@ -1081,12 +1240,12 @@ class AtomEnvironment { // Returns the chosen button index {Number} if the buttons option is an array // or the return value of the callback if the buttons option is an object. // If a callback function is supplied, returns `undefined`. - confirm (options = {}, callback) { + confirm(options = {}, callback) { if (callback) { // Async: no return value - this.applicationDelegate.confirm(options, callback) + this.applicationDelegate.confirm(options, callback); } else { - return this.applicationDelegate.confirm(options) + return this.applicationDelegate.confirm(options); } } @@ -1097,440 +1256,509 @@ class AtomEnvironment { // Extended: Open the dev tools for the current window. // // Returns a {Promise} that resolves when the DevTools have been opened. - openDevTools () { - return this.applicationDelegate.openWindowDevTools() + openDevTools() { + return this.applicationDelegate.openWindowDevTools(); } // Extended: Toggle the visibility of the dev tools for the current window. // // Returns a {Promise} that resolves when the DevTools have been opened or // closed. - toggleDevTools () { - return this.applicationDelegate.toggleWindowDevTools() + toggleDevTools() { + return this.applicationDelegate.toggleWindowDevTools(); } // Extended: Execute code in dev tools. - executeJavaScriptInDevTools (code) { - return this.applicationDelegate.executeJavaScriptInWindowDevTools(code) + executeJavaScriptInDevTools(code) { + return this.applicationDelegate.executeJavaScriptInWindowDevTools(code); } /* Section: Private */ - assert (condition, message, callbackOrMetadata) { - if (condition) return true + assert(condition, message, callbackOrMetadata) { + if (condition) return true; - const error = new Error(`Assertion failed: ${message}`) - Error.captureStackTrace(error, this.assert) + const error = new Error(`Assertion failed: ${message}`); + Error.captureStackTrace(error, this.assert); if (callbackOrMetadata) { if (typeof callbackOrMetadata === 'function') { - callbackOrMetadata(error) + callbackOrMetadata(error); } else { - error.metadata = callbackOrMetadata + error.metadata = callbackOrMetadata; } } - this.emitter.emit('did-fail-assertion', error) - if (!this.isReleasedVersion()) throw error + this.emitter.emit('did-fail-assertion', error); + if (!this.isReleasedVersion()) throw error; - return false + return false; } - loadThemes () { - return this.themes.load() + loadThemes() { + return this.themes.load(); } - setDocumentEdited (edited) { - if (typeof this.applicationDelegate.setWindowDocumentEdited === 'function') { - this.applicationDelegate.setWindowDocumentEdited(edited) + setDocumentEdited(edited) { + if ( + typeof this.applicationDelegate.setWindowDocumentEdited === 'function' + ) { + this.applicationDelegate.setWindowDocumentEdited(edited); } } - setRepresentedFilename (filename) { - if (typeof this.applicationDelegate.setWindowRepresentedFilename === 'function') { - this.applicationDelegate.setWindowRepresentedFilename(filename) + setRepresentedFilename(filename) { + if ( + typeof this.applicationDelegate.setWindowRepresentedFilename === + 'function' + ) { + this.applicationDelegate.setWindowRepresentedFilename(filename); } } - addProjectFolder () { - return new Promise((resolve) => { - this.pickFolder((selectedPaths) => { - this.addToProject(selectedPaths || []).then(resolve) - }) - }) + addProjectFolder() { + return new Promise(resolve => { + this.pickFolder(selectedPaths => { + this.addToProject(selectedPaths || []).then(resolve); + }); + }); } - async addToProject (projectPaths) { - const state = await this.loadState(this.getStateKey(projectPaths)) - if (state && (this.project.getPaths().length === 0)) { - this.attemptRestoreProjectStateForPaths(state, projectPaths) + async addToProject(projectPaths) { + const state = await this.loadState(this.getStateKey(projectPaths)); + if (state && this.project.getPaths().length === 0) { + this.attemptRestoreProjectStateForPaths(state, projectPaths); } else { - projectPaths.map((folder) => this.project.addPath(folder)) + projectPaths.map(folder => this.project.addPath(folder)); } } - async attemptRestoreProjectStateForPaths (state, projectPaths, filesToOpen = []) { - const center = this.workspace.getCenter() + async attemptRestoreProjectStateForPaths( + state, + projectPaths, + filesToOpen = [] + ) { + const center = this.workspace.getCenter(); const windowIsUnused = () => { for (let container of this.workspace.getPaneContainers()) { for (let item of container.getPaneItems()) { if (item instanceof TextEditor) { - if (item.getPath() || item.isModified()) return false + if (item.getPath() || item.isModified()) return false; } else { - if (container === center) return false + if (container === center) return false; } } } - return true - } + return true; + }; if (windowIsUnused()) { - await this.restoreStateIntoThisEnvironment(state) - return Promise.all(filesToOpen.map(file => this.workspace.open(file))) + await this.restoreStateIntoThisEnvironment(state); + return Promise.all(filesToOpen.map(file => this.workspace.open(file))); } else { - let resolveDiscardStatePromise = null - const discardStatePromise = new Promise((resolve) => { - resolveDiscardStatePromise = resolve - }) - const nouns = projectPaths.length === 1 ? 'folder' : 'folders' - this.confirm({ - message: 'Previous automatically-saved project state detected', - detail: `There is previously saved state for the selected ${nouns}. ` + - `Would you like to add the ${nouns} to this window, permanently discarding the saved state, ` + - `or open the ${nouns} in a new window, restoring the saved state?`, - buttons: [ - '&Open in new window and recover state', - '&Add to this window and discard state' - ] - }, response => { - if (response === 0) { - this.open({ - pathsToOpen: projectPaths.concat(filesToOpen), - newWindow: true, - devMode: this.inDevMode(), - safeMode: this.inSafeMode() - }) - resolveDiscardStatePromise(Promise.resolve(null)) - } else if (response === 1) { - for (let selectedPath of projectPaths) { - this.project.addPath(selectedPath) + let resolveDiscardStatePromise = null; + const discardStatePromise = new Promise(resolve => { + resolveDiscardStatePromise = resolve; + }); + const nouns = projectPaths.length === 1 ? 'folder' : 'folders'; + this.confirm( + { + message: 'Previous automatically-saved project state detected', + detail: + `There is previously saved state for the selected ${nouns}. ` + + `Would you like to add the ${nouns} to this window, permanently discarding the saved state, ` + + `or open the ${nouns} in a new window, restoring the saved state?`, + buttons: [ + '&Open in new window and recover state', + '&Add to this window and discard state' + ] + }, + response => { + if (response === 0) { + this.open({ + pathsToOpen: projectPaths.concat(filesToOpen), + newWindow: true, + devMode: this.inDevMode(), + safeMode: this.inSafeMode() + }); + resolveDiscardStatePromise(Promise.resolve(null)); + } else if (response === 1) { + for (let selectedPath of projectPaths) { + this.project.addPath(selectedPath); + } + resolveDiscardStatePromise( + Promise.all(filesToOpen.map(file => this.workspace.open(file))) + ); } - resolveDiscardStatePromise(Promise.all(filesToOpen.map(file => this.workspace.open(file)))) } - }) + ); - return discardStatePromise + return discardStatePromise; } } - restoreStateIntoThisEnvironment (state) { - state.fullScreen = this.isFullScreen() + restoreStateIntoThisEnvironment(state) { + state.fullScreen = this.isFullScreen(); for (let pane of this.workspace.getPanes()) { - pane.destroy() + pane.destroy(); } - return this.deserialize(state) + return this.deserialize(state); } - showSaveDialogSync (options = {}) { + showSaveDialogSync(options = {}) { deprecate(`atom.showSaveDialogSync is deprecated and will be removed soon. Please, implement ::saveAs and ::getSaveDialogOptions instead for pane items -or use Pane::saveItemAs for programmatic saving.`) - return this.applicationDelegate.showSaveDialog(options) +or use Pane::saveItemAs for programmatic saving.`); + return this.applicationDelegate.showSaveDialog(options); } - async saveState (options, storageKey) { + async saveState(options, storageKey) { if (this.enablePersistence && this.project) { - const state = this.serialize(options) - if (!storageKey) storageKey = this.getStateKey(this.project && this.project.getPaths()) + const state = this.serialize(options); + if (!storageKey) + storageKey = this.getStateKey(this.project && this.project.getPaths()); if (storageKey) { - await this.stateStore.save(storageKey, state) + await this.stateStore.save(storageKey, state); } else { - await this.applicationDelegate.setTemporaryWindowState(state) + await this.applicationDelegate.setTemporaryWindowState(state); } } } - loadState (stateKey) { + loadState(stateKey) { if (this.enablePersistence) { - if (!stateKey) stateKey = this.getStateKey(this.getLoadSettings().initialProjectRoots) + if (!stateKey) + stateKey = this.getStateKey(this.getLoadSettings().initialProjectRoots); if (stateKey) { - return this.stateStore.load(stateKey) + return this.stateStore.load(stateKey); } else { - return this.applicationDelegate.getTemporaryWindowState() + return this.applicationDelegate.getTemporaryWindowState(); } } else { - return Promise.resolve(null) + return Promise.resolve(null); } } - async deserialize (state) { - if (!state) return Promise.resolve() + async deserialize(state) { + if (!state) return Promise.resolve(); - this.setFullScreen(state.fullScreen) + this.setFullScreen(state.fullScreen); - const missingProjectPaths = [] + const missingProjectPaths = []; - this.packages.packageStates = state.packageStates || {} + this.packages.packageStates = state.packageStates || {}; - let startTime = Date.now() + let startTime = Date.now(); if (state.project) { try { - await this.project.deserialize(state.project, this.deserializers) + await this.project.deserialize(state.project, this.deserializers); } catch (error) { // We handle the missingProjectPaths case in openLocations(). if (!error.missingProjectPaths) { this.notifications.addError('Unable to deserialize project', { description: error.message, stack: error.stack - }) + }); } } } - this.deserializeTimings.project = Date.now() - startTime + this.deserializeTimings.project = Date.now() - startTime; - if (state.grammars) this.grammars.deserialize(state.grammars) + if (state.grammars) this.grammars.deserialize(state.grammars); - startTime = Date.now() - if (state.workspace) this.workspace.deserialize(state.workspace, this.deserializers) - this.deserializeTimings.workspace = Date.now() - startTime + startTime = Date.now(); + if (state.workspace) + this.workspace.deserialize(state.workspace, this.deserializers); + this.deserializeTimings.workspace = Date.now() - startTime; if (missingProjectPaths.length > 0) { - const count = missingProjectPaths.length === 1 ? '' : missingProjectPaths.length + ' ' - const noun = missingProjectPaths.length === 1 ? 'folder' : 'folders' - const toBe = missingProjectPaths.length === 1 ? 'is' : 'are' - const escaped = missingProjectPaths.map(projectPath => `\`${projectPath}\``) - let group + const count = + missingProjectPaths.length === 1 + ? '' + : missingProjectPaths.length + ' '; + const noun = missingProjectPaths.length === 1 ? 'folder' : 'folders'; + const toBe = missingProjectPaths.length === 1 ? 'is' : 'are'; + const escaped = missingProjectPaths.map( + projectPath => `\`${projectPath}\`` + ); + let group; switch (escaped.length) { case 1: - group = escaped[0] - break + group = escaped[0]; + break; case 2: - group = `${escaped[0]} and ${escaped[1]}` - break + group = `${escaped[0]} and ${escaped[1]}`; + break; default: - group = escaped.slice(0, -1).join(', ') + `, and ${escaped[escaped.length - 1]}` + group = + escaped.slice(0, -1).join(', ') + + `, and ${escaped[escaped.length - 1]}`; } this.notifications.addError(`Unable to open ${count}project ${noun}`, { description: `Project ${noun} ${group} ${toBe} no longer on disk.` - }) + }); } } - getStateKey (paths) { + getStateKey(paths) { if (paths && paths.length > 0) { - const sha1 = crypto.createHash('sha1').update(paths.slice().sort().join('\n')).digest('hex') - return `editor-${sha1}` + const sha1 = crypto + .createHash('sha1') + .update( + paths + .slice() + .sort() + .join('\n') + ) + .digest('hex'); + return `editor-${sha1}`; } else { - return null + return null; } } - getConfigDirPath () { - if (!this.configDirPath) this.configDirPath = process.env.ATOM_HOME - return this.configDirPath + getConfigDirPath() { + if (!this.configDirPath) this.configDirPath = process.env.ATOM_HOME; + return this.configDirPath; } - getUserInitScriptPath () { - const initScriptPath = fs.resolve(this.getConfigDirPath(), 'init', ['js', 'coffee']) - return initScriptPath || path.join(this.getConfigDirPath(), 'init.coffee') + getUserInitScriptPath() { + const initScriptPath = fs.resolve(this.getConfigDirPath(), 'init', [ + 'js', + 'coffee' + ]); + return initScriptPath || path.join(this.getConfigDirPath(), 'init.coffee'); } - requireUserInitScript () { - const userInitScriptPath = this.getUserInitScriptPath() + requireUserInitScript() { + const userInitScriptPath = this.getUserInitScriptPath(); if (userInitScriptPath) { try { - if (fs.isFileSync(userInitScriptPath)) require(userInitScriptPath) + if (fs.isFileSync(userInitScriptPath)) require(userInitScriptPath); } catch (error) { - this.notifications.addError(`Failed to load \`${userInitScriptPath}\``, { - detail: error.message, - dismissable: true - }) + this.notifications.addError( + `Failed to load \`${userInitScriptPath}\``, + { + detail: error.message, + dismissable: true + } + ); } } } // TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead - onUpdateAvailable (callback) { - return this.emitter.on('update-available', callback) + onUpdateAvailable(callback) { + return this.emitter.on('update-available', callback); } - updateAvailable (details) { - return this.emitter.emit('update-available', details) + updateAvailable(details) { + return this.emitter.emit('update-available', details); } - listenForUpdates () { + listenForUpdates() { // listen for updates available locally (that have been successfully downloaded) - this.disposables.add(this.autoUpdater.onDidCompleteDownloadingUpdate(this.updateAvailable.bind(this))) + this.disposables.add( + this.autoUpdater.onDidCompleteDownloadingUpdate( + this.updateAvailable.bind(this) + ) + ); } - setBodyPlatformClass () { - this.document.body.classList.add(`platform-${process.platform}`) + setBodyPlatformClass() { + this.document.body.classList.add(`platform-${process.platform}`); } - setAutoHideMenuBar (autoHide) { - this.applicationDelegate.setAutoHideWindowMenuBar(autoHide) - this.applicationDelegate.setWindowMenuBarVisibility(!autoHide) + setAutoHideMenuBar(autoHide) { + this.applicationDelegate.setAutoHideWindowMenuBar(autoHide); + this.applicationDelegate.setWindowMenuBarVisibility(!autoHide); } - dispatchApplicationMenuCommand (command, arg) { - let {activeElement} = this.document + dispatchApplicationMenuCommand(command, arg) { + let { activeElement } = this.document; // Use the workspace element if body has focus if (activeElement === this.document.body) { - activeElement = this.workspace.getElement() + activeElement = this.workspace.getElement(); } - this.commands.dispatch(activeElement, command, arg) + this.commands.dispatch(activeElement, command, arg); } - dispatchContextMenuCommand (command, ...args) { - this.commands.dispatch(this.contextMenu.activeElement, command, args) + dispatchContextMenuCommand(command, ...args) { + this.commands.dispatch(this.contextMenu.activeElement, command, args); } - dispatchURIMessage (uri) { + dispatchURIMessage(uri) { if (this.packages.hasLoadedInitialPackages()) { - this.uriHandlerRegistry.handleURI(uri) + this.uriHandlerRegistry.handleURI(uri); } else { let subscription = this.packages.onDidLoadInitialPackages(() => { - subscription.dispose() - this.uriHandlerRegistry.handleURI(uri) - }) + subscription.dispose(); + this.uriHandlerRegistry.handleURI(uri); + }); } } - async openLocations (locations) { - const needsProjectPaths = this.project && this.project.getPaths().length === 0 - const foldersToAddToProject = new Set() - const fileLocationsToOpen = [] - const missingFolders = [] + async openLocations(locations) { + const needsProjectPaths = + this.project && this.project.getPaths().length === 0; + const foldersToAddToProject = new Set(); + const fileLocationsToOpen = []; + const missingFolders = []; // Asynchronously fetch stat information about each requested path to open. const locationStats = await Promise.all( locations.map(async location => { - const stats = location.pathToOpen ? await stat(location.pathToOpen).catch(() => null) : null - return {location, stats} - }), - ) + const stats = location.pathToOpen + ? await stat(location.pathToOpen).catch(() => null) + : null; + return { location, stats }; + }) + ); - for (const {location, stats} of locationStats) { - const {pathToOpen} = location + for (const { location, stats } of locationStats) { + const { pathToOpen } = location; if (!pathToOpen) { // Untitled buffer - fileLocationsToOpen.push(location) - continue + fileLocationsToOpen.push(location); + continue; } if (stats !== null) { // Path exists if (stats.isDirectory()) { // Directory: add as a project folder - foldersToAddToProject.add(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + foldersToAddToProject.add( + this.project.getDirectoryForProjectPath(pathToOpen).getPath() + ); } else if (stats.isFile()) { if (location.isDirectory) { // File: no longer a directory - missingFolders.push(location) + missingFolders.push(location); } else { // File: add as a file location - fileLocationsToOpen.push(location) + fileLocationsToOpen.push(location); } } } else { // Path does not exist // Attempt to interpret as a URI from a non-default directory provider - const directory = this.project.getProvidedDirectoryForProjectPath(pathToOpen) + const directory = this.project.getProvidedDirectoryForProjectPath( + pathToOpen + ); if (directory) { // Found: add as a project folder - foldersToAddToProject.add(directory.getPath()) + foldersToAddToProject.add(directory.getPath()); } else if (location.isDirectory) { // Not found and must be a directory: add to missing list and use to derive state key - missingFolders.push(location) + missingFolders.push(location); } else { // Not found: open as a new file - fileLocationsToOpen.push(location) + fileLocationsToOpen.push(location); } } - if (location.hasWaitSession) this.pathsWithWaitSessions.add(pathToOpen) + if (location.hasWaitSession) this.pathsWithWaitSessions.add(pathToOpen); } - let restoredState = false + let restoredState = false; if (foldersToAddToProject.size > 0 || missingFolders.length > 0) { // Include missing folders in the state key so that sessions restored with no-longer-present project root folders // don't lose data. - const foldersForStateKey = Array.from(foldersToAddToProject) - .concat(missingFolders.map(location => location.pathToOpen)) - const state = await this.loadState(this.getStateKey(Array.from(foldersForStateKey))) + const foldersForStateKey = Array.from(foldersToAddToProject).concat( + missingFolders.map(location => location.pathToOpen) + ); + const state = await this.loadState( + this.getStateKey(Array.from(foldersForStateKey)) + ); // only restore state if this is the first path added to the project if (state && needsProjectPaths) { - const files = fileLocationsToOpen.map((location) => location.pathToOpen) - await this.attemptRestoreProjectStateForPaths(state, Array.from(foldersToAddToProject), files) - restoredState = true + const files = fileLocationsToOpen.map(location => location.pathToOpen); + await this.attemptRestoreProjectStateForPaths( + state, + Array.from(foldersToAddToProject), + files + ); + restoredState = true; } else { for (let folder of foldersToAddToProject) { - this.project.addPath(folder) + this.project.addPath(folder); } } } if (!restoredState) { - const fileOpenPromises = [] - for (const {pathToOpen, initialLine, initialColumn} of fileLocationsToOpen) { - fileOpenPromises.push(this.workspace && this.workspace.open(pathToOpen, {initialLine, initialColumn})) + const fileOpenPromises = []; + for (const { + pathToOpen, + initialLine, + initialColumn + } of fileLocationsToOpen) { + fileOpenPromises.push( + this.workspace && + this.workspace.open(pathToOpen, { initialLine, initialColumn }) + ); } - await Promise.all(fileOpenPromises) + await Promise.all(fileOpenPromises); } if (missingFolders.length > 0) { - let message = 'Unable to open project folder' + let message = 'Unable to open project folder'; if (missingFolders.length > 1) { - message += 's' + message += 's'; } - let description = 'The ' + let description = 'The '; if (missingFolders.length === 1) { - description += 'directory `' - description += missingFolders[0].pathToOpen - description += '` does not exist.' + description += 'directory `'; + description += missingFolders[0].pathToOpen; + description += '` does not exist.'; } else if (missingFolders.length === 2) { - description += `directories \`${missingFolders[0].pathToOpen}\` ` - description += `and \`${missingFolders[1].pathToOpen}\` do not exist.` + description += `directories \`${missingFolders[0].pathToOpen}\` `; + description += `and \`${missingFolders[1].pathToOpen}\` do not exist.`; } else { - description += 'directories ' - description += (missingFolders + description += 'directories '; + description += missingFolders .slice(0, -1) .map(location => location.pathToOpen) .map(pathToOpen => '`' + pathToOpen + '`, ') - .join('')) - description += 'and `' + missingFolders[missingFolders.length - 1].pathToOpen + '` do not exist.' + .join(''); + description += + 'and `' + + missingFolders[missingFolders.length - 1].pathToOpen + + '` do not exist.'; } - this.notifications.addWarning(message, {description}) + this.notifications.addWarning(message, { description }); } - ipcRenderer.send('window-command', 'window:locations-opened') + ipcRenderer.send('window-command', 'window:locations-opened'); } - resolveProxy (url) { + resolveProxy(url) { return new Promise((resolve, reject) => { - const requestId = this.nextProxyRequestId++ - const disposable = this.applicationDelegate.onDidResolveProxy((id, proxy) => { - if (id === requestId) { - disposable.dispose() - resolve(proxy) + const requestId = this.nextProxyRequestId++; + const disposable = this.applicationDelegate.onDidResolveProxy( + (id, proxy) => { + if (id === requestId) { + disposable.dispose(); + resolve(proxy); + } } - }) + ); - return this.applicationDelegate.resolveProxy(requestId, url) - }) + return this.applicationDelegate.resolveProxy(requestId, url); + }); } } -AtomEnvironment.version = 1 -AtomEnvironment.prototype.saveStateDebounceInterval = 1000 -module.exports = AtomEnvironment +AtomEnvironment.version = 1; +AtomEnvironment.prototype.saveStateDebounceInterval = 1000; +module.exports = AtomEnvironment; /* eslint-disable */ diff --git a/src/atom-paths.js b/src/atom-paths.js index d36ac25f5..8091fc02d 100644 --- a/src/atom-paths.js +++ b/src/atom-paths.js @@ -1,60 +1,70 @@ -const fs = require('fs-plus') -const path = require('path') +const fs = require('fs-plus'); +const path = require('path'); -const hasWriteAccess = (dir) => { - const testFilePath = path.join(dir, 'write.test') +const hasWriteAccess = dir => { + const testFilePath = path.join(dir, 'write.test'); try { - fs.writeFileSync(testFilePath, new Date().toISOString(), { flag: 'w+' }) - fs.unlinkSync(testFilePath) - return true + fs.writeFileSync(testFilePath, new Date().toISOString(), { flag: 'w+' }); + fs.unlinkSync(testFilePath); + return true; } catch (err) { - return false + return false; } -} +}; const getAppDirectory = () => { switch (process.platform) { case 'darwin': - return process.execPath.substring(0, process.execPath.indexOf('.app') + 4) + return process.execPath.substring( + 0, + process.execPath.indexOf('.app') + 4 + ); case 'linux': case 'win32': - return path.join(process.execPath, '..') + return path.join(process.execPath, '..'); } -} +}; module.exports = { - setAtomHome: (homePath) => { + setAtomHome: homePath => { // When a read-writeable .atom folder exists above app use that - const portableHomePath = path.join(getAppDirectory(), '..', '.atom') + const portableHomePath = path.join(getAppDirectory(), '..', '.atom'); if (fs.existsSync(portableHomePath)) { if (hasWriteAccess(portableHomePath)) { - process.env.ATOM_HOME = portableHomePath + process.env.ATOM_HOME = portableHomePath; } else { // A path exists so it was intended to be used but we didn't have rights, so warn. - console.log(`Insufficient permission to portable Atom home "${portableHomePath}".`) + console.log( + `Insufficient permission to portable Atom home "${portableHomePath}".` + ); } } // Check ATOM_HOME environment variable next if (process.env.ATOM_HOME !== undefined) { - return + return; } // Fall back to default .atom folder in users home folder - process.env.ATOM_HOME = path.join(homePath, '.atom') + process.env.ATOM_HOME = path.join(homePath, '.atom'); }, - setUserData: (app) => { - const electronUserDataPath = path.join(process.env.ATOM_HOME, 'electronUserData') + setUserData: app => { + const electronUserDataPath = path.join( + process.env.ATOM_HOME, + 'electronUserData' + ); if (fs.existsSync(electronUserDataPath)) { if (hasWriteAccess(electronUserDataPath)) { - app.setPath('userData', electronUserDataPath) + app.setPath('userData', electronUserDataPath); } else { // A path exists so it was intended to be used but we didn't have rights, so warn. - console.log(`Insufficient permission to Electron user data "${electronUserDataPath}".`) + console.log( + `Insufficient permission to Electron user data "${electronUserDataPath}".` + ); } } }, getAppDirectory: getAppDirectory -} +}; diff --git a/src/auto-update-manager.js b/src/auto-update-manager.js index 5e3a80129..fe1d3ef0e 100644 --- a/src/auto-update-manager.js +++ b/src/auto-update-manager.js @@ -1,68 +1,69 @@ -const {Emitter, CompositeDisposable} = require('event-kit') +const { Emitter, CompositeDisposable } = require('event-kit'); -module.exports = -class AutoUpdateManager { - constructor ({applicationDelegate}) { - this.applicationDelegate = applicationDelegate - this.subscriptions = new CompositeDisposable() - this.emitter = new Emitter() +module.exports = class AutoUpdateManager { + constructor({ applicationDelegate }) { + this.applicationDelegate = applicationDelegate; + this.subscriptions = new CompositeDisposable(); + this.emitter = new Emitter(); } - initialize () { + initialize() { this.subscriptions.add( this.applicationDelegate.onDidBeginCheckingForUpdate(() => { - this.emitter.emit('did-begin-checking-for-update') + this.emitter.emit('did-begin-checking-for-update'); }), this.applicationDelegate.onDidBeginDownloadingUpdate(() => { - this.emitter.emit('did-begin-downloading-update') + this.emitter.emit('did-begin-downloading-update'); }), - this.applicationDelegate.onDidCompleteDownloadingUpdate((details) => { - this.emitter.emit('did-complete-downloading-update', details) + this.applicationDelegate.onDidCompleteDownloadingUpdate(details => { + this.emitter.emit('did-complete-downloading-update', details); }), this.applicationDelegate.onUpdateNotAvailable(() => { - this.emitter.emit('update-not-available') + this.emitter.emit('update-not-available'); }), this.applicationDelegate.onUpdateError(() => { - this.emitter.emit('update-error') + this.emitter.emit('update-error'); }) - ) + ); } - destroy () { - this.subscriptions.dispose() - this.emitter.dispose() + destroy() { + this.subscriptions.dispose(); + this.emitter.dispose(); } - checkForUpdate () { - this.applicationDelegate.checkForUpdate() + checkForUpdate() { + this.applicationDelegate.checkForUpdate(); } - restartAndInstallUpdate () { - this.applicationDelegate.restartAndInstallUpdate() + restartAndInstallUpdate() { + this.applicationDelegate.restartAndInstallUpdate(); } - getState () { - return this.applicationDelegate.getAutoUpdateManagerState() + getState() { + return this.applicationDelegate.getAutoUpdateManagerState(); } - getErrorMessage () { - return this.applicationDelegate.getAutoUpdateManagerErrorMessage() + getErrorMessage() { + return this.applicationDelegate.getAutoUpdateManagerErrorMessage(); } - platformSupportsUpdates () { - return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported' + platformSupportsUpdates() { + return ( + atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported' + ); } - onDidBeginCheckingForUpdate (callback) { - return this.emitter.on('did-begin-checking-for-update', callback) + onDidBeginCheckingForUpdate(callback) { + return this.emitter.on('did-begin-checking-for-update', callback); } - onDidBeginDownloadingUpdate (callback) { - return this.emitter.on('did-begin-downloading-update', callback) + onDidBeginDownloadingUpdate(callback) { + return this.emitter.on('did-begin-downloading-update', callback); } - onDidCompleteDownloadingUpdate (callback) { - return this.emitter.on('did-complete-downloading-update', callback) + onDidCompleteDownloadingUpdate(callback) { + return this.emitter.on('did-complete-downloading-update', callback); } // TODO: When https://github.com/atom/electron/issues/4587 is closed, we can @@ -71,15 +72,15 @@ class AutoUpdateManager { // return this.emitter.on('update-available', callback) // } - onUpdateNotAvailable (callback) { - return this.emitter.on('update-not-available', callback) + onUpdateNotAvailable(callback) { + return this.emitter.on('update-not-available', callback); } - onUpdateError (callback) { - return this.emitter.on('update-error', callback) + onUpdateError(callback) { + return this.emitter.on('update-error', callback); } - getPlatform () { - return process.platform + getPlatform() { + return process.platform; } -} +}; diff --git a/src/babel.js b/src/babel.js index 8476a33c0..622faaad9 100644 --- a/src/babel.js +++ b/src/babel.js @@ -1,35 +1,42 @@ -'use strict' +'use strict'; -var crypto = require('crypto') -var path = require('path') -var defaultOptions = require('../static/babelrc.json') +var crypto = require('crypto'); +var path = require('path'); +var defaultOptions = require('../static/babelrc.json'); -var babel = null -var babelVersionDirectory = null +var babel = null; +var babelVersionDirectory = null; var PREFIXES = [ '/** @babel */', '"use babel"', - '\'use babel\'', + "'use babel'", '/* @flow */', '// @flow' -] +]; -var PREFIX_LENGTH = Math.max.apply(Math, PREFIXES.map(function (prefix) { - return prefix.length -})) - -exports.shouldCompile = function (sourceCode) { - var start = sourceCode.substr(0, PREFIX_LENGTH) - return PREFIXES.some(function (prefix) { - return start.indexOf(prefix) === 0 +var PREFIX_LENGTH = Math.max.apply( + Math, + PREFIXES.map(function(prefix) { + return prefix.length; }) -} +); -exports.getCachePath = function (sourceCode) { +exports.shouldCompile = function(sourceCode) { + var start = sourceCode.substr(0, PREFIX_LENGTH); + return PREFIXES.some(function(prefix) { + return start.indexOf(prefix) === 0; + }); +}; + +exports.getCachePath = function(sourceCode) { if (babelVersionDirectory == null) { - var babelVersion = require('babel-core/package.json').version - babelVersionDirectory = path.join('js', 'babel', createVersionAndOptionsDigest(babelVersion, defaultOptions)) + var babelVersion = require('babel-core/package.json').version; + babelVersionDirectory = path.join( + 'js', + 'babel', + createVersionAndOptionsDigest(babelVersion, defaultOptions) + ); } return path.join( @@ -38,30 +45,30 @@ exports.getCachePath = function (sourceCode) { .createHash('sha1') .update(sourceCode, 'utf8') .digest('hex') + '.js' - ) -} + ); +}; -exports.compile = function (sourceCode, filePath) { +exports.compile = function(sourceCode, filePath) { if (!babel) { - babel = require('babel-core') - var Logger = require('babel-core/lib/transformation/file/logger') - var noop = function () {} - Logger.prototype.debug = noop - Logger.prototype.verbose = noop + babel = require('babel-core'); + var Logger = require('babel-core/lib/transformation/file/logger'); + var noop = function() {}; + Logger.prototype.debug = noop; + Logger.prototype.verbose = noop; } if (process.platform === 'win32') { - filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/') + filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/'); } - var options = {filename: filePath} + var options = { filename: filePath }; for (var key in defaultOptions) { - options[key] = defaultOptions[key] + options[key] = defaultOptions[key]; } - return babel.transform(sourceCode, options).code -} + return babel.transform(sourceCode, options).code; +}; -function createVersionAndOptionsDigest (version, options) { +function createVersionAndOptionsDigest(version, options) { return crypto .createHash('sha1') .update('babel-core', 'utf8') @@ -69,5 +76,5 @@ function createVersionAndOptionsDigest (version, options) { .update(version, 'utf8') .update('\0', 'utf8') .update(JSON.stringify(options), 'utf8') - .digest('hex') + .digest('hex'); } diff --git a/src/buffered-node-process.js b/src/buffered-node-process.js index a33176e51..9f3ca0d64 100644 --- a/src/buffered-node-process.js +++ b/src/buffered-node-process.js @@ -1,4 +1,4 @@ -const BufferedProcess = require('./buffered-process') +const BufferedProcess = require('./buffered-process'); // Extended: Like {BufferedProcess}, but accepts a Node script as the command // to run. @@ -10,9 +10,7 @@ const BufferedProcess = require('./buffered-process') // ```js // const {BufferedNodeProcess} = require('atom') // ``` -module.exports = -class BufferedNodeProcess extends BufferedProcess { - +module.exports = class BufferedNodeProcess extends BufferedProcess { // Public: Runs the given Node script by spawning a new child process. // // * `options` An {Object} with the following keys: @@ -34,14 +32,14 @@ class BufferedNodeProcess extends BufferedProcess { // is sent in a final call (optional). // * `exit` The callback {Function} which receives a single argument // containing the exit status (optional). - constructor ({command, args, options = {}, stdout, stderr, exit}) { - options.env = options.env || Object.create(process.env) - options.env.ELECTRON_RUN_AS_NODE = 1 - options.env.ELECTRON_NO_ATTACH_CONSOLE = 1 + constructor({ command, args, options = {}, stdout, stderr, exit }) { + options.env = options.env || Object.create(process.env); + options.env.ELECTRON_RUN_AS_NODE = 1; + options.env.ELECTRON_NO_ATTACH_CONSOLE = 1; - args = args ? args.slice() : [] - args.unshift(command) - args.unshift('--no-deprecation') + args = args ? args.slice() : []; + args.unshift(command); + args.unshift('--no-deprecation'); super({ command: process.execPath, @@ -50,6 +48,6 @@ class BufferedNodeProcess extends BufferedProcess { stdout, stderr, exit - }) + }); } -} +}; diff --git a/src/buffered-process.js b/src/buffered-process.js index 21872d7d4..2acd8fda7 100644 --- a/src/buffered-process.js +++ b/src/buffered-process.js @@ -1,7 +1,7 @@ -const _ = require('underscore-plus') -const ChildProcess = require('child_process') -const {Emitter} = require('event-kit') -const path = require('path') +const _ = require('underscore-plus'); +const ChildProcess = require('child_process'); +const { Emitter } = require('event-kit'); +const path = require('path'); // Extended: A wrapper which provides standard error/output line buffering for // Node's ChildProcess. @@ -17,8 +17,7 @@ const path = require('path') // const exit = (code) => console.log("ps -ef exited with #{code}") // const process = new BufferedProcess({command, args, stdout, exit}) // ``` -module.exports = -class BufferedProcess { +module.exports = class BufferedProcess { /* Section: Construction */ @@ -48,58 +47,73 @@ class BufferedProcess { // * `autoStart` {Boolean} (optional) Whether the command will automatically start // when this BufferedProcess is created. Defaults to true. When set to false you // must call the `start` method to start the process. - constructor ({command, args, options = {}, stdout, stderr, exit, autoStart = true} = {}) { - this.emitter = new Emitter() - this.command = command - this.args = args - this.options = options - this.stdout = stdout - this.stderr = stderr - this.exit = exit + constructor({ + command, + args, + options = {}, + stdout, + stderr, + exit, + autoStart = true + } = {}) { + this.emitter = new Emitter(); + this.command = command; + this.args = args; + this.options = options; + this.stdout = stdout; + this.stderr = stderr; + this.exit = exit; if (autoStart === true) { - this.start() + this.start(); } - this.killed = false + this.killed = false; } - start () { - if (this.started === true) return + start() { + if (this.started === true) return; - this.started = true + this.started = true; // Related to joyent/node#2318 if (process.platform === 'win32' && this.options.shell === undefined) { - this.spawnWithEscapedWindowsArgs(this.command, this.args, this.options) + this.spawnWithEscapedWindowsArgs(this.command, this.args, this.options); } else { - this.spawn(this.command, this.args, this.options) + this.spawn(this.command, this.args, this.options); } - this.handleEvents(this.stdout, this.stderr, this.exit) + this.handleEvents(this.stdout, this.stderr, this.exit); } // Windows has a bunch of special rules that node still doesn't take care of for you - spawnWithEscapedWindowsArgs (command, args, options) { - let cmdArgs = [] + spawnWithEscapedWindowsArgs(command, args, options) { + let cmdArgs = []; // Quote all arguments and escapes inner quotes if (args) { - cmdArgs = args.filter((arg) => arg != null) - .map((arg) => { + cmdArgs = args + .filter(arg => arg != null) + .map(arg => { if (this.isExplorerCommand(command) && /^\/[a-zA-Z]+,.*$/.test(arg)) { // Don't wrap /root,C:\folder style arguments to explorer calls in // quotes since they will not be interpreted correctly if they are - return arg + return arg; } else { // Escape double quotes by putting a backslash in front of them - return `"${arg.toString().replace(/"/g, '\\"')}"` + return `"${arg.toString().replace(/"/g, '\\"')}"`; } - }) + }); } // The command itself is quoted if it contains spaces, &, ^, | or # chars - cmdArgs.unshift(/\s|&|\^|\(|\)|\||#/.test(command) ? `"${command}"` : command) + cmdArgs.unshift( + /\s|&|\^|\(|\)|\||#/.test(command) ? `"${command}"` : command + ); - const cmdOptions = _.clone(options) - cmdOptions.windowsVerbatimArguments = true + const cmdOptions = _.clone(options); + cmdOptions.windowsVerbatimArguments = true; - this.spawn(this.getCmdPath(), ['/s', '/d', '/c', `"${cmdArgs.join(' ')}"`], cmdOptions) + this.spawn( + this.getCmdPath(), + ['/s', '/d', '/c', `"${cmdArgs.join(' ')}"`], + cmdOptions + ); } /* @@ -118,8 +132,8 @@ class BufferedProcess { // The error will not be thrown if this function is called. // // Returns a {Disposable} - onWillThrowError (callback) { - return this.emitter.on('will-throw-error', callback) + onWillThrowError(callback) { + return this.emitter.on('will-throw-error', callback); } /* @@ -131,183 +145,196 @@ class BufferedProcess { // * `stream` The Stream to read from. // * `onLines` The callback to call with each line of data. // * `onDone` The callback to call when the stream has closed. - bufferStream (stream, onLines, onDone) { - stream.setEncoding('utf8') - let buffered = '' + bufferStream(stream, onLines, onDone) { + stream.setEncoding('utf8'); + let buffered = ''; - stream.on('data', (data) => { - if (this.killed) return + stream.on('data', data => { + if (this.killed) return; - let bufferedLength = buffered.length - buffered += data - let lastNewlineIndex = data.lastIndexOf('\n') + let bufferedLength = buffered.length; + buffered += data; + let lastNewlineIndex = data.lastIndexOf('\n'); if (lastNewlineIndex !== -1) { - let lineLength = lastNewlineIndex + bufferedLength + 1 - onLines(buffered.substring(0, lineLength)) - buffered = buffered.substring(lineLength) + let lineLength = lastNewlineIndex + bufferedLength + 1; + onLines(buffered.substring(0, lineLength)); + buffered = buffered.substring(lineLength); } - }) + }); stream.on('close', () => { - if (this.killed) return - if (buffered.length > 0) onLines(buffered) - onDone() - }) + if (this.killed) return; + if (buffered.length > 0) onLines(buffered); + onDone(); + }); } // Kill all child processes of the spawned cmd.exe process on Windows. // // This is required since killing the cmd.exe does not terminate child // processes. - killOnWindows () { - if (!this.process) return + killOnWindows() { + if (!this.process) return; - const parentPid = this.process.pid - const cmd = 'wmic' + const parentPid = this.process.pid; + const cmd = 'wmic'; const args = [ 'process', 'where', `(ParentProcessId=${parentPid})`, 'get', 'processid' - ] + ]; - let wmicProcess + let wmicProcess; try { - wmicProcess = ChildProcess.spawn(cmd, args) + wmicProcess = ChildProcess.spawn(cmd, args); } catch (spawnError) { - this.killProcess() - return + this.killProcess(); + return; } - wmicProcess.on('error', () => {}) // ignore errors + wmicProcess.on('error', () => {}); // ignore errors - let output = '' - wmicProcess.stdout.on('data', (data) => { - output += data - }) + let output = ''; + wmicProcess.stdout.on('data', data => { + output += data; + }); wmicProcess.stdout.on('close', () => { for (let pid of output.split(/\s+/)) { - if (!/^\d{1,10}$/.test(pid)) continue - pid = parseInt(pid, 10) + if (!/^\d{1,10}$/.test(pid)) continue; + pid = parseInt(pid, 10); - if (!pid || pid === parentPid) continue + if (!pid || pid === parentPid) continue; try { - process.kill(pid) + process.kill(pid); } catch (error) {} } - this.killProcess() - }) + this.killProcess(); + }); } - killProcess () { - if (this.process) this.process.kill() - this.process = null + killProcess() { + if (this.process) this.process.kill(); + this.process = null; } - isExplorerCommand (command) { + isExplorerCommand(command) { if (command === 'explorer.exe' || command === 'explorer') { - return true + return true; } else if (process.env.SystemRoot) { - return command === path.join(process.env.SystemRoot, 'explorer.exe') || command === path.join(process.env.SystemRoot, 'explorer') + return ( + command === path.join(process.env.SystemRoot, 'explorer.exe') || + command === path.join(process.env.SystemRoot, 'explorer') + ); } else { - return false + return false; } } - getCmdPath () { + getCmdPath() { if (process.env.comspec) { - return process.env.comspec + return process.env.comspec; } else if (process.env.SystemRoot) { - return path.join(process.env.SystemRoot, 'System32', 'cmd.exe') + return path.join(process.env.SystemRoot, 'System32', 'cmd.exe'); } else { - return 'cmd.exe' + return 'cmd.exe'; } } // Public: Terminate the process. - kill () { - if (this.killed) return + kill() { + if (this.killed) return; - this.killed = true + this.killed = true; if (process.platform === 'win32') { - this.killOnWindows() + this.killOnWindows(); } else { - this.killProcess() + this.killProcess(); } } - spawn (command, args, options) { + spawn(command, args, options) { try { - this.process = ChildProcess.spawn(command, args, options) + this.process = ChildProcess.spawn(command, args, options); } catch (spawnError) { - process.nextTick(() => this.handleError(spawnError)) + process.nextTick(() => this.handleError(spawnError)); } } - handleEvents (stdout, stderr, exit) { - if (!this.process) return + handleEvents(stdout, stderr, exit) { + if (!this.process) return; const triggerExitCallback = () => { - if (this.killed) return - if (stdoutClosed && stderrClosed && processExited && typeof exit === 'function') { - exit(exitCode) + if (this.killed) return; + if ( + stdoutClosed && + stderrClosed && + processExited && + typeof exit === 'function' + ) { + exit(exitCode); } - } + }; - let stdoutClosed = true - let stderrClosed = true - let processExited = true - let exitCode = 0 + let stdoutClosed = true; + let stderrClosed = true; + let processExited = true; + let exitCode = 0; if (stdout) { - stdoutClosed = false + stdoutClosed = false; this.bufferStream(this.process.stdout, stdout, () => { - stdoutClosed = true - triggerExitCallback() - }) + stdoutClosed = true; + triggerExitCallback(); + }); } if (stderr) { - stderrClosed = false + stderrClosed = false; this.bufferStream(this.process.stderr, stderr, () => { - stderrClosed = true - triggerExitCallback() - }) + stderrClosed = true; + triggerExitCallback(); + }); } if (exit) { - processExited = false - this.process.on('exit', (code) => { - exitCode = code - processExited = true - triggerExitCallback() - }) + processExited = false; + this.process.on('exit', code => { + exitCode = code; + processExited = true; + triggerExitCallback(); + }); } - this.process.on('error', (error) => { - this.handleError(error) - }) + this.process.on('error', error => { + this.handleError(error); + }); } - handleError (error) { - let handled = false + handleError(error) { + let handled = false; const handle = () => { - handled = true - } + handled = true; + }; - this.emitter.emit('will-throw-error', {error, handle}) + this.emitter.emit('will-throw-error', { error, handle }); if (error.code === 'ENOENT' && error.syscall.indexOf('spawn') === 0) { - error = new Error(`Failed to spawn command \`${this.command}\`. Make sure \`${this.command}\` is installed and on your PATH`, error.path) - error.name = 'BufferedProcessError' + error = new Error( + `Failed to spawn command \`${this.command}\`. Make sure \`${ + this.command + }\` is installed and on your PATH`, + error.path + ); + error.name = 'BufferedProcessError'; } - if (!handled) throw error + if (!handled) throw error; } -} +}; diff --git a/src/clipboard.js b/src/clipboard.js index eafc04d6f..db4cc4db2 100644 --- a/src/clipboard.js +++ b/src/clipboard.js @@ -1,5 +1,5 @@ -const crypto = require('crypto') -const {clipboard} = require('electron') +const crypto = require('crypto'); +const { clipboard } = require('electron'); // Extended: Represents the clipboard used for copying and pasting in Atom. // @@ -12,15 +12,14 @@ const {clipboard} = require('electron') // // console.log(atom.clipboard.read()) // 'hello' // ``` -module.exports = -class Clipboard { - constructor () { - this.reset() +module.exports = class Clipboard { + constructor() { + this.reset(); } - reset () { - this.metadata = null - this.signatureForMetadata = null + reset() { + this.metadata = null; + this.signatureForMetadata = null; } // Creates an `md5` hash of some text. @@ -28,8 +27,11 @@ class Clipboard { // * `text` A {String} to hash. // // Returns a hashed {String}. - md5 (text) { - return crypto.createHash('md5').update(text, 'utf8').digest('hex') + md5(text) { + return crypto + .createHash('md5') + .update(text, 'utf8') + .digest('hex'); } // Public: Write the given text to the clipboard. @@ -39,17 +41,17 @@ class Clipboard { // // * `text` The {String} to store. // * `metadata` (optional) The additional info to associate with the text. - write (text, metadata) { - this.signatureForMetadata = this.md5(text) - this.metadata = metadata - clipboard.writeText(text) + write(text, metadata) { + this.signatureForMetadata = this.md5(text); + this.metadata = metadata; + clipboard.writeText(text); } // Public: Read the text from the clipboard. // // Returns a {String}. - read () { - return clipboard.readText() + read() { + return clipboard.readText(); } // Public: Read the text from the clipboard and return both the text and the @@ -58,12 +60,12 @@ class Clipboard { // Returns an {Object} with the following keys: // * `text` The {String} clipboard text. // * `metadata` The metadata stored by an earlier call to {::write}. - readWithMetadata () { - const text = this.read() + readWithMetadata() { + const text = this.read(); if (this.signatureForMetadata === this.md5(text)) { - return {text, metadata: this.metadata} + return { text, metadata: this.metadata }; } else { - return {text} + return { text }; } } -} +}; diff --git a/src/coffee-script.js b/src/coffee-script.js index 0437e787f..389ef5b3b 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -1,45 +1,45 @@ -'use strict' +'use strict'; -var crypto = require('crypto') -var path = require('path') -var CoffeeScript = null +var crypto = require('crypto'); +var path = require('path'); +var CoffeeScript = null; -exports.shouldCompile = function () { - return true -} +exports.shouldCompile = function() { + return true; +}; -exports.getCachePath = function (sourceCode) { +exports.getCachePath = function(sourceCode) { return path.join( 'coffee', crypto .createHash('sha1') .update(sourceCode, 'utf8') .digest('hex') + '.js' - ) -} + ); +}; -exports.compile = function (sourceCode, filePath) { +exports.compile = function(sourceCode, filePath) { if (!CoffeeScript) { - var previousPrepareStackTrace = Error.prepareStackTrace - CoffeeScript = require('coffee-script') + var previousPrepareStackTrace = Error.prepareStackTrace; + CoffeeScript = require('coffee-script'); // When it loads, coffee-script reassigns Error.prepareStackTrace. We have // already reassigned it via the 'source-map-support' module, so we need // to set it back. - Error.prepareStackTrace = previousPrepareStackTrace + Error.prepareStackTrace = previousPrepareStackTrace; } if (process.platform === 'win32') { - filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/') + filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/'); } var output = CoffeeScript.compile(sourceCode, { filename: filePath, sourceFiles: [filePath], inlineMap: true - }) + }); // Strip sourceURL from output so there wouldn't be duplicate entries // in devtools. - return output.replace(/\/\/# sourceURL=[^'"\n]+\s*$/, '') -} + return output.replace(/\/\/# sourceURL=[^'"\n]+\s*$/, ''); +}; diff --git a/src/color.js b/src/color.js index c183fb3e5..cb3f6cba0 100644 --- a/src/color.js +++ b/src/color.js @@ -1,9 +1,8 @@ -let ParsedColor = null +let ParsedColor = null; // Essential: A simple color class returned from {Config::get} when the value // at the key path is of type 'color'. -module.exports = -class Color { +module.exports = class Color { // Essential: Parse a {String} or {Object} into a {Color}. // // * `value` A {String} such as `'white'`, `#ff00ff`, or @@ -11,119 +10,133 @@ class Color { // and `alpha` properties. // // Returns a {Color} or `null` if it cannot be parsed. - static parse (value) { + static parse(value) { switch (typeof value) { case 'string': - break + break; case 'object': - if (Array.isArray(value)) { return null } - break + if (Array.isArray(value)) { + return null; + } + break; default: - return null + return null; } if (!ParsedColor) { - ParsedColor = require('color') + ParsedColor = require('color'); } try { - var parsedColor = new ParsedColor(value) + var parsedColor = new ParsedColor(value); } catch (error) { - return null + return null; } - return new Color(parsedColor.red(), parsedColor.green(), parsedColor.blue(), parsedColor.alpha()) + return new Color( + parsedColor.red(), + parsedColor.green(), + parsedColor.blue(), + parsedColor.alpha() + ); } - constructor (red, green, blue, alpha) { - this.red = red - this.green = green - this.blue = blue - this.alpha = alpha + constructor(red, green, blue, alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; } - set red (red) { - this._red = parseColor(red) + set red(red) { + this._red = parseColor(red); } - set green (green) { - this._green = parseColor(green) + set green(green) { + this._green = parseColor(green); } - set blue (blue) { - this._blue = parseColor(blue) + set blue(blue) { + this._blue = parseColor(blue); } - set alpha (alpha) { - this._alpha = parseAlpha(alpha) + set alpha(alpha) { + this._alpha = parseAlpha(alpha); } - get red () { - return this._red + get red() { + return this._red; } - get green () { - return this._green + get green() { + return this._green; } - get blue () { - return this._blue + get blue() { + return this._blue; } - get alpha () { - return this._alpha + get alpha() { + return this._alpha; } // Essential: Returns a {String} in the form `'#abcdef'`. - toHexString () { - return `#${numberToHexString(this.red)}${numberToHexString(this.green)}${numberToHexString(this.blue)}` + toHexString() { + return `#${numberToHexString(this.red)}${numberToHexString( + this.green + )}${numberToHexString(this.blue)}`; } // Essential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`. - toRGBAString () { - return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})` + toRGBAString() { + return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`; } - toJSON () { - return this.alpha === 1 ? this.toHexString() : this.toRGBAString() + toJSON() { + return this.alpha === 1 ? this.toHexString() : this.toRGBAString(); } - toString () { - return this.toRGBAString() + toString() { + return this.toRGBAString(); } - isEqual (color) { + isEqual(color) { if (this === color) { - return true + return true; } if (!(color instanceof Color)) { - color = Color.parse(color) + color = Color.parse(color); } if (color == null) { - return false + return false; } - return color.red === this.red && color.blue === this.blue && color.green === this.green && color.alpha === this.alpha + return ( + color.red === this.red && + color.blue === this.blue && + color.green === this.green && + color.alpha === this.alpha + ); } - clone () { - return new Color(this.red, this.green, this.blue, this.alpha) + clone() { + return new Color(this.red, this.green, this.blue, this.alpha); } +}; + +function parseColor(colorString) { + const color = parseInt(colorString, 10); + return isNaN(color) ? 0 : Math.min(Math.max(color, 0), 255); } -function parseColor (colorString) { - const color = parseInt(colorString, 10) - return isNaN(color) ? 0 : Math.min(Math.max(color, 0), 255) +function parseAlpha(alphaString) { + const alpha = parseFloat(alphaString); + return isNaN(alpha) ? 1 : Math.min(Math.max(alpha, 0), 1); } -function parseAlpha (alphaString) { - const alpha = parseFloat(alphaString) - return isNaN(alpha) ? 1 : Math.min(Math.max(alpha, 0), 1) -} - -function numberToHexString (number) { - const hex = number.toString(16) - return number < 16 ? `0${hex}` : hex +function numberToHexString(number) { + const hex = number.toString(16); + return number < 16 ? `0${hex}` : hex; } diff --git a/src/command-installer.js b/src/command-installer.js index b432023ba..2bbc70796 100644 --- a/src/command-installer.js +++ b/src/command-installer.js @@ -1,102 +1,116 @@ -const path = require('path') -const fs = require('fs-plus') +const path = require('path'); +const fs = require('fs-plus'); -module.exports = -class CommandInstaller { - constructor (applicationDelegate) { - this.applicationDelegate = applicationDelegate +module.exports = class CommandInstaller { + constructor(applicationDelegate) { + this.applicationDelegate = applicationDelegate; } - initialize (appVersion) { - this.appVersion = appVersion + initialize(appVersion) { + this.appVersion = appVersion; } - getInstallDirectory () { - return '/usr/local/bin' + getInstallDirectory() { + return '/usr/local/bin'; } - getResourcesDirectory () { - return process.resourcesPath + getResourcesDirectory() { + return process.resourcesPath; } - installShellCommandsInteractively () { - const showErrorDialog = (error) => { - this.applicationDelegate.confirm({ - message: 'Failed to install shell commands', - detail: error.message - }, () => {}) - } + installShellCommandsInteractively() { + const showErrorDialog = error => { + this.applicationDelegate.confirm( + { + message: 'Failed to install shell commands', + detail: error.message + }, + () => {} + ); + }; this.installAtomCommand(true, (error, atomCommandName) => { - if (error) return showErrorDialog(error) + if (error) return showErrorDialog(error); this.installApmCommand(true, (error, apmCommandName) => { - if (error) return showErrorDialog(error) - this.applicationDelegate.confirm({ - message: 'Commands installed.', - detail: `The shell commands \`${atomCommandName}\` and \`${apmCommandName}\` are installed.` - }, () => {}) - }) - }) + if (error) return showErrorDialog(error); + this.applicationDelegate.confirm( + { + message: 'Commands installed.', + detail: `The shell commands \`${atomCommandName}\` and \`${apmCommandName}\` are installed.` + }, + () => {} + ); + }); + }); } - getCommandNameForChannel (commandName) { - let channelMatch = this.appVersion.match(/beta|nightly/) - let channel = channelMatch ? channelMatch[0] : '' + getCommandNameForChannel(commandName) { + let channelMatch = this.appVersion.match(/beta|nightly/); + let channel = channelMatch ? channelMatch[0] : ''; switch (channel) { case 'beta': - return `${commandName}-beta` + return `${commandName}-beta`; case 'nightly': - return `${commandName}-nightly` + return `${commandName}-nightly`; default: - return commandName + return commandName; } } - installAtomCommand (askForPrivilege, callback) { + installAtomCommand(askForPrivilege, callback) { this.installCommand( path.join(this.getResourcesDirectory(), 'app', 'atom.sh'), this.getCommandNameForChannel('atom'), askForPrivilege, callback - ) + ); } - installApmCommand (askForPrivilege, callback) { + installApmCommand(askForPrivilege, callback) { this.installCommand( - path.join(this.getResourcesDirectory(), 'app', 'apm', 'node_modules', '.bin', 'apm'), + path.join( + this.getResourcesDirectory(), + 'app', + 'apm', + 'node_modules', + '.bin', + 'apm' + ), this.getCommandNameForChannel('apm'), askForPrivilege, callback - ) + ); } - installCommand (commandPath, commandName, askForPrivilege, callback) { - if (process.platform !== 'darwin') return callback() + installCommand(commandPath, commandName, askForPrivilege, callback) { + if (process.platform !== 'darwin') return callback(); - const destinationPath = path.join(this.getInstallDirectory(), commandName) + const destinationPath = path.join(this.getInstallDirectory(), commandName); fs.readlink(destinationPath, (error, realpath) => { - if (error && error.code !== 'ENOENT') return callback(error) - if (realpath === commandPath) return callback(null, commandName) + if (error && error.code !== 'ENOENT') return callback(error); + if (realpath === commandPath) return callback(null, commandName); this.createSymlink(fs, commandPath, destinationPath, error => { if (error && error.code === 'EACCES' && askForPrivilege) { - const fsAdmin = require('fs-admin') - this.createSymlink(fsAdmin, commandPath, destinationPath, (error) => { callback(error, commandName) }) + const fsAdmin = require('fs-admin'); + this.createSymlink(fsAdmin, commandPath, destinationPath, error => { + callback(error, commandName); + }); } else { - callback(error) + callback(error); } - }) - }) + }); + }); } - createSymlink (fs, sourcePath, destinationPath, callback) { - fs.unlink(destinationPath, (error) => { - if (error && error.code !== 'ENOENT') return callback(error) - fs.makeTree(path.dirname(destinationPath), (error) => { - if (error) return callback(error) - fs.symlink(sourcePath, destinationPath, callback) - }) - }) + createSymlink(fs, sourcePath, destinationPath, callback) { + fs.unlink(destinationPath, error => { + if (error && error.code !== 'ENOENT') return callback(error); + fs.makeTree(path.dirname(destinationPath), error => { + if (error) return callback(error); + fs.symlink(sourcePath, destinationPath, callback); + }); + }); } -} +}; diff --git a/src/command-registry.js b/src/command-registry.js index e503691db..47b4464d4 100644 --- a/src/command-registry.js +++ b/src/command-registry.js @@ -1,10 +1,10 @@ -'use strict' +'use strict'; -const { Emitter, Disposable, CompositeDisposable } = require('event-kit') -const { calculateSpecificity, validateSelector } = require('clear-cut') -const _ = require('underscore-plus') +const { Emitter, Disposable, CompositeDisposable } = require('event-kit'); +const { calculateSpecificity, validateSelector } = require('clear-cut'); +const _ = require('underscore-plus'); -let SequenceCount = 0 +let SequenceCount = 0; // Public: Associates listener functions with commands in a // context-sensitive way using CSS selectors. You can access a global instance of @@ -45,37 +45,37 @@ let SequenceCount = 0 // editor.insertText(new Date().toLocaleString()) // ``` module.exports = class CommandRegistry { - constructor () { - this.handleCommandEvent = this.handleCommandEvent.bind(this) - this.rootNode = null - this.clear() + constructor() { + this.handleCommandEvent = this.handleCommandEvent.bind(this); + this.rootNode = null; + this.clear(); } - clear () { - this.registeredCommands = {} - this.selectorBasedListenersByCommandName = {} - this.inlineListenersByCommandName = {} - this.emitter = new Emitter() + clear() { + this.registeredCommands = {}; + this.selectorBasedListenersByCommandName = {}; + this.inlineListenersByCommandName = {}; + this.emitter = new Emitter(); } - attach (rootNode) { - this.rootNode = rootNode + attach(rootNode) { + this.rootNode = rootNode; for (const command in this.selectorBasedListenersByCommandName) { - this.commandRegistered(command) + this.commandRegistered(command); } for (const command in this.inlineListenersByCommandName) { - this.commandRegistered(command) + this.commandRegistered(command); } } - destroy () { + destroy() { for (const commandName in this.registeredCommands) { this.rootNode.removeEventListener( commandName, this.handleCommandEvent, true - ) + ); } } @@ -127,20 +127,22 @@ module.exports = class CommandRegistry { // // Returns a {Disposable} on which `.dispose()` can be called to remove the // added command handler(s). - add (target, commandName, listener, throwOnInvalidSelector = true) { + add(target, commandName, listener, throwOnInvalidSelector = true) { if (typeof commandName === 'object') { - const commands = commandName - throwOnInvalidSelector = listener - const disposable = new CompositeDisposable() + const commands = commandName; + throwOnInvalidSelector = listener; + const disposable = new CompositeDisposable(); for (commandName in commands) { - listener = commands[commandName] - disposable.add(this.add(target, commandName, listener, throwOnInvalidSelector)) + listener = commands[commandName]; + disposable.add( + this.add(target, commandName, listener, throwOnInvalidSelector) + ); } - return disposable + return disposable; } if (listener == null) { - throw new Error('Cannot register a command with a null listener.') + throw new Error('Cannot register a command with a null listener.'); } // type Listener = ((e: CustomEvent) => void) | { @@ -148,60 +150,77 @@ module.exports = class CommandRegistry { // description?: string, // didDispatch(e: CustomEvent): void, // } - if ((typeof listener !== 'function') && (typeof listener.didDispatch !== 'function')) { - throw new Error('Listener must be a callback function or an object with a didDispatch method.') + if ( + typeof listener !== 'function' && + typeof listener.didDispatch !== 'function' + ) { + throw new Error( + 'Listener must be a callback function or an object with a didDispatch method.' + ); } if (typeof target === 'string') { if (throwOnInvalidSelector) { - validateSelector(target) + validateSelector(target); } - return this.addSelectorBasedListener(target, commandName, listener) + return this.addSelectorBasedListener(target, commandName, listener); } else { - return this.addInlineListener(target, commandName, listener) + return this.addInlineListener(target, commandName, listener); } } - addSelectorBasedListener (selector, commandName, listener) { + addSelectorBasedListener(selector, commandName, listener) { if (this.selectorBasedListenersByCommandName[commandName] == null) { - this.selectorBasedListenersByCommandName[commandName] = [] + this.selectorBasedListenersByCommandName[commandName] = []; } - const listenersForCommand = this.selectorBasedListenersByCommandName[commandName] - const selectorListener = new SelectorBasedListener(selector, commandName, listener) - listenersForCommand.push(selectorListener) + const listenersForCommand = this.selectorBasedListenersByCommandName[ + commandName + ]; + const selectorListener = new SelectorBasedListener( + selector, + commandName, + listener + ); + listenersForCommand.push(selectorListener); - this.commandRegistered(commandName) + this.commandRegistered(commandName); return new Disposable(() => { - listenersForCommand.splice(listenersForCommand.indexOf(selectorListener), 1) + listenersForCommand.splice( + listenersForCommand.indexOf(selectorListener), + 1 + ); if (listenersForCommand.length === 0) { - delete this.selectorBasedListenersByCommandName[commandName] + delete this.selectorBasedListenersByCommandName[commandName]; } - }) + }); } - addInlineListener (element, commandName, listener) { + addInlineListener(element, commandName, listener) { if (this.inlineListenersByCommandName[commandName] == null) { - this.inlineListenersByCommandName[commandName] = new WeakMap() + this.inlineListenersByCommandName[commandName] = new WeakMap(); } - const listenersForCommand = this.inlineListenersByCommandName[commandName] - let listenersForElement = listenersForCommand.get(element) + const listenersForCommand = this.inlineListenersByCommandName[commandName]; + let listenersForElement = listenersForCommand.get(element); if (!listenersForElement) { - listenersForElement = [] - listenersForCommand.set(element, listenersForElement) + listenersForElement = []; + listenersForCommand.set(element, listenersForElement); } - const inlineListener = new InlineListener(commandName, listener) - listenersForElement.push(inlineListener) + const inlineListener = new InlineListener(commandName, listener); + listenersForElement.push(inlineListener); - this.commandRegistered(commandName) + this.commandRegistered(commandName); return new Disposable(() => { - listenersForElement.splice(listenersForElement.indexOf(inlineListener), 1) + listenersForElement.splice( + listenersForElement.indexOf(inlineListener), + 1 + ); if (listenersForElement.length === 0) { - listenersForCommand.delete(element) + listenersForCommand.delete(element); } - }) + }); } // Public: Find all registered commands matching a query. @@ -220,42 +239,42 @@ module.exports = class CommandRegistry { // command // Any additional nonstandard metadata provided when the command was `add`ed // may also be present in the returned descriptor. - findCommands ({ target }) { - const commandNames = new Set() - const commands = [] - let currentTarget = target + findCommands({ target }) { + const commandNames = new Set(); + const commands = []; + let currentTarget = target; while (true) { - let listeners + let listeners; for (const name in this.inlineListenersByCommandName) { - listeners = this.inlineListenersByCommandName[name] + listeners = this.inlineListenersByCommandName[name]; if (listeners.has(currentTarget) && !commandNames.has(name)) { - commandNames.add(name) - const targetListeners = listeners.get(currentTarget) + commandNames.add(name); + const targetListeners = listeners.get(currentTarget); commands.push( ...targetListeners.map(listener => listener.descriptor) - ) + ); } } for (const commandName in this.selectorBasedListenersByCommandName) { - listeners = this.selectorBasedListenersByCommandName[commandName] + listeners = this.selectorBasedListenersByCommandName[commandName]; for (const listener of listeners) { if (listener.matchesTarget(currentTarget)) { if (!commandNames.has(commandName)) { - commandNames.add(commandName) - commands.push(listener.descriptor) + commandNames.add(commandName); + commands.push(listener.descriptor); } } } } if (currentTarget === window) { - break + break; } - currentTarget = currentTarget.parentNode || window + currentTarget = currentTarget.parentNode || window; } - return commands + return commands; } // Public: Simulate the dispatch of a command on a DOM node. @@ -267,174 +286,183 @@ module.exports = class CommandRegistry { // // * `target` The DOM node at which to start bubbling the command event. // * `commandName` {String} indicating the name of the command to dispatch. - dispatch (target, commandName, detail) { - const event = new CustomEvent(commandName, { bubbles: true, detail }) - Object.defineProperty(event, 'target', { value: target }) - return this.handleCommandEvent(event) + dispatch(target, commandName, detail) { + const event = new CustomEvent(commandName, { bubbles: true, detail }); + Object.defineProperty(event, 'target', { value: target }); + return this.handleCommandEvent(event); } // Public: Invoke the given callback before dispatching a command event. // // * `callback` {Function} to be called before dispatching each command // * `event` The Event that will be dispatched - onWillDispatch (callback) { - return this.emitter.on('will-dispatch', callback) + onWillDispatch(callback) { + return this.emitter.on('will-dispatch', callback); } // Public: Invoke the given callback after dispatching a command event. // // * `callback` {Function} to be called after dispatching each command // * `event` The Event that was dispatched - onDidDispatch (callback) { - return this.emitter.on('did-dispatch', callback) + onDidDispatch(callback) { + return this.emitter.on('did-dispatch', callback); } - getSnapshot () { - const snapshot = {} + getSnapshot() { + const snapshot = {}; for (const commandName in this.selectorBasedListenersByCommandName) { - const listeners = this.selectorBasedListenersByCommandName[commandName] - snapshot[commandName] = listeners.slice() + const listeners = this.selectorBasedListenersByCommandName[commandName]; + snapshot[commandName] = listeners.slice(); } - return snapshot + return snapshot; } - restoreSnapshot (snapshot) { - this.selectorBasedListenersByCommandName = {} + restoreSnapshot(snapshot) { + this.selectorBasedListenersByCommandName = {}; for (const commandName in snapshot) { - const listeners = snapshot[commandName] - this.selectorBasedListenersByCommandName[commandName] = listeners.slice() + const listeners = snapshot[commandName]; + this.selectorBasedListenersByCommandName[commandName] = listeners.slice(); } } - handleCommandEvent (event) { - let propagationStopped = false - let immediatePropagationStopped = false - let matched = [] - let currentTarget = event.target + handleCommandEvent(event) { + let propagationStopped = false; + let immediatePropagationStopped = false; + let matched = []; + let currentTarget = event.target; const dispatchedEvent = new CustomEvent(event.type, { bubbles: true, detail: event.detail - }) + }); Object.defineProperty(dispatchedEvent, 'eventPhase', { value: Event.BUBBLING_PHASE - }) + }); Object.defineProperty(dispatchedEvent, 'currentTarget', { - get () { - return currentTarget + get() { + return currentTarget; } - }) - Object.defineProperty(dispatchedEvent, 'target', { value: currentTarget }) + }); + Object.defineProperty(dispatchedEvent, 'target', { value: currentTarget }); Object.defineProperty(dispatchedEvent, 'preventDefault', { - value () { - return event.preventDefault() + value() { + return event.preventDefault(); } - }) + }); Object.defineProperty(dispatchedEvent, 'stopPropagation', { - value () { - event.stopPropagation() - propagationStopped = true + value() { + event.stopPropagation(); + propagationStopped = true; } - }) + }); Object.defineProperty(dispatchedEvent, 'stopImmediatePropagation', { - value () { - event.stopImmediatePropagation() - propagationStopped = true - immediatePropagationStopped = true + value() { + event.stopImmediatePropagation(); + propagationStopped = true; + immediatePropagationStopped = true; } - }) + }); Object.defineProperty(dispatchedEvent, 'abortKeyBinding', { - value () { + value() { if (typeof event.abortKeyBinding === 'function') { - event.abortKeyBinding() + event.abortKeyBinding(); } } - }) + }); for (const key of Object.keys(event)) { if (!(key in dispatchedEvent)) { - dispatchedEvent[key] = event[key] + dispatchedEvent[key] = event[key]; } } - this.emitter.emit('will-dispatch', dispatchedEvent) + this.emitter.emit('will-dispatch', dispatchedEvent); while (true) { - const commandInlineListeners = - this.inlineListenersByCommandName[event.type] + const commandInlineListeners = this.inlineListenersByCommandName[ + event.type + ] ? this.inlineListenersByCommandName[event.type].get(currentTarget) - : null - let listeners = commandInlineListeners || [] + : null; + let listeners = commandInlineListeners || []; if (currentTarget.webkitMatchesSelector != null) { - const selectorBasedListeners = - (this.selectorBasedListenersByCommandName[event.type] || []) - .filter(listener => listener.matchesTarget(currentTarget)) - .sort((a, b) => a.compare(b)) - listeners = selectorBasedListeners.concat(listeners) + const selectorBasedListeners = ( + this.selectorBasedListenersByCommandName[event.type] || [] + ) + .filter(listener => listener.matchesTarget(currentTarget)) + .sort((a, b) => a.compare(b)); + listeners = selectorBasedListeners.concat(listeners); } // Call inline listeners first in reverse registration order, // and selector-based listeners by specificity and reverse // registration order. for (let i = listeners.length - 1; i >= 0; i--) { - const listener = listeners[i] + const listener = listeners[i]; if (immediatePropagationStopped) { - break + break; } - matched.push(listener.didDispatch.call(currentTarget, dispatchedEvent)) + matched.push(listener.didDispatch.call(currentTarget, dispatchedEvent)); } if (currentTarget === window) { - break + break; } if (propagationStopped) { - break + break; } - currentTarget = currentTarget.parentNode || window + currentTarget = currentTarget.parentNode || window; } - this.emitter.emit('did-dispatch', dispatchedEvent) + this.emitter.emit('did-dispatch', dispatchedEvent); - return (matched.length > 0 ? Promise.all(matched) : null) + return matched.length > 0 ? Promise.all(matched) : null; } - commandRegistered (commandName) { + commandRegistered(commandName) { if (this.rootNode != null && !this.registeredCommands[commandName]) { - this.rootNode.addEventListener(commandName, this.handleCommandEvent, true) - return (this.registeredCommands[commandName] = true) + this.rootNode.addEventListener( + commandName, + this.handleCommandEvent, + true + ); + return (this.registeredCommands[commandName] = true); } } -} +}; // type Listener = { // descriptor: CommandDescriptor, // extractDidDispatch: (e: CustomEvent) => void, // }; class SelectorBasedListener { - constructor (selector, commandName, listener) { - this.selector = selector - this.didDispatch = extractDidDispatch(listener) - this.descriptor = extractDescriptor(commandName, listener) - this.specificity = calculateSpecificity(this.selector) - this.sequenceNumber = SequenceCount++ + constructor(selector, commandName, listener) { + this.selector = selector; + this.didDispatch = extractDidDispatch(listener); + this.descriptor = extractDescriptor(commandName, listener); + this.specificity = calculateSpecificity(this.selector); + this.sequenceNumber = SequenceCount++; } - compare (other) { + compare(other) { return ( this.specificity - other.specificity || this.sequenceNumber - other.sequenceNumber - ) + ); } - matchesTarget (target) { - return target.webkitMatchesSelector && target.webkitMatchesSelector(this.selector) + matchesTarget(target) { + return ( + target.webkitMatchesSelector && + target.webkitMatchesSelector(this.selector) + ); } } class InlineListener { - constructor (commandName, listener) { - this.didDispatch = extractDidDispatch(listener) - this.descriptor = extractDescriptor(commandName, listener) + constructor(commandName, listener) { + this.didDispatch = extractDidDispatch(listener); + this.descriptor = extractDescriptor(commandName, listener); } } @@ -442,16 +470,15 @@ class InlineListener { // name: string, // displayName: string, // }; -function extractDescriptor (name, listener) { - return Object.assign( - _.omit(listener, 'didDispatch'), - { - name, - displayName: listener.displayName ? listener.displayName : _.humanizeEventName(name) - } - ) +function extractDescriptor(name, listener) { + return Object.assign(_.omit(listener, 'didDispatch'), { + name, + displayName: listener.displayName + ? listener.displayName + : _.humanizeEventName(name) + }); } -function extractDidDispatch (listener) { - return typeof listener === 'function' ? listener : listener.didDispatch +function extractDidDispatch(listener) { + return typeof listener === 'function' ? listener : listener.didDispatch; } diff --git a/src/compile-cache.js b/src/compile-cache.js index bf9a0771c..2e35760ff 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -1,132 +1,148 @@ -'use strict' +'use strict'; // For now, we're not using babel or ES6 features like `let` and `const` in // this file, because `apm` requires this file directly in order to pre-warm // Atom's compile-cache when installing or updating packages, using an older // version of node.js -var path = require('path') -var fs = require('fs-plus') -var sourceMapSupport = require('@atom/source-map-support') +var path = require('path'); +var fs = require('fs-plus'); +var sourceMapSupport = require('@atom/source-map-support'); -var PackageTranspilationRegistry = require('./package-transpilation-registry') -var CSON = null +var PackageTranspilationRegistry = require('./package-transpilation-registry'); +var CSON = null; -var packageTranspilationRegistry = new PackageTranspilationRegistry() +var packageTranspilationRegistry = new PackageTranspilationRegistry(); var COMPILERS = { '.js': packageTranspilationRegistry.wrapTranspiler(require('./babel')), '.ts': packageTranspilationRegistry.wrapTranspiler(require('./typescript')), '.tsx': packageTranspilationRegistry.wrapTranspiler(require('./typescript')), - '.coffee': packageTranspilationRegistry.wrapTranspiler(require('./coffee-script')) -} + '.coffee': packageTranspilationRegistry.wrapTranspiler( + require('./coffee-script') + ) +}; -exports.addTranspilerConfigForPath = function (packagePath, packageName, packageMeta, config) { - packagePath = fs.realpathSync(packagePath) - packageTranspilationRegistry.addTranspilerConfigForPath(packagePath, packageName, packageMeta, config) -} +exports.addTranspilerConfigForPath = function( + packagePath, + packageName, + packageMeta, + config +) { + packagePath = fs.realpathSync(packagePath); + packageTranspilationRegistry.addTranspilerConfigForPath( + packagePath, + packageName, + packageMeta, + config + ); +}; -exports.removeTranspilerConfigForPath = function (packagePath) { - packagePath = fs.realpathSync(packagePath) - packageTranspilationRegistry.removeTranspilerConfigForPath(packagePath) -} +exports.removeTranspilerConfigForPath = function(packagePath) { + packagePath = fs.realpathSync(packagePath); + packageTranspilationRegistry.removeTranspilerConfigForPath(packagePath); +}; -var cacheStats = {} -var cacheDirectory = null +var cacheStats = {}; +var cacheDirectory = null; -exports.setAtomHomeDirectory = function (atomHome) { - var cacheDir = path.join(atomHome, 'compile-cache') - if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { - cacheDir = path.join(cacheDir, 'root') +exports.setAtomHomeDirectory = function(atomHome) { + var cacheDir = path.join(atomHome, 'compile-cache'); + if ( + process.env.USER === 'root' && + process.env.SUDO_USER && + process.env.SUDO_USER !== process.env.USER + ) { + cacheDir = path.join(cacheDir, 'root'); } - this.setCacheDirectory(cacheDir) -} + this.setCacheDirectory(cacheDir); +}; -exports.setCacheDirectory = function (directory) { - cacheDirectory = directory -} +exports.setCacheDirectory = function(directory) { + cacheDirectory = directory; +}; -exports.getCacheDirectory = function () { - return cacheDirectory -} +exports.getCacheDirectory = function() { + return cacheDirectory; +}; -exports.addPathToCache = function (filePath, atomHome) { - this.setAtomHomeDirectory(atomHome) - var extension = path.extname(filePath) +exports.addPathToCache = function(filePath, atomHome) { + this.setAtomHomeDirectory(atomHome); + var extension = path.extname(filePath); if (extension === '.cson') { if (!CSON) { - CSON = require('season') - CSON.setCacheDir(this.getCacheDirectory()) + CSON = require('season'); + CSON.setCacheDir(this.getCacheDirectory()); } - return CSON.readFileSync(filePath) + return CSON.readFileSync(filePath); } else { - var compiler = COMPILERS[extension] + var compiler = COMPILERS[extension]; if (compiler) { - return compileFileAtPath(compiler, filePath, extension) + return compileFileAtPath(compiler, filePath, extension); } } -} +}; -exports.getCacheStats = function () { - return cacheStats -} +exports.getCacheStats = function() { + return cacheStats; +}; -exports.resetCacheStats = function () { - Object.keys(COMPILERS).forEach(function (extension) { +exports.resetCacheStats = function() { + Object.keys(COMPILERS).forEach(function(extension) { cacheStats[extension] = { hits: 0, misses: 0 - } - }) -} + }; + }); +}; -function compileFileAtPath (compiler, filePath, extension) { - var sourceCode = fs.readFileSync(filePath, 'utf8') +function compileFileAtPath(compiler, filePath, extension) { + var sourceCode = fs.readFileSync(filePath, 'utf8'); if (compiler.shouldCompile(sourceCode, filePath)) { - var cachePath = compiler.getCachePath(sourceCode, filePath) - var compiledCode = readCachedJavaScript(cachePath) + var cachePath = compiler.getCachePath(sourceCode, filePath); + var compiledCode = readCachedJavaScript(cachePath); if (compiledCode != null) { - cacheStats[extension].hits++ + cacheStats[extension].hits++; } else { - cacheStats[extension].misses++ - compiledCode = compiler.compile(sourceCode, filePath) - writeCachedJavaScript(cachePath, compiledCode) + cacheStats[extension].misses++; + compiledCode = compiler.compile(sourceCode, filePath); + writeCachedJavaScript(cachePath, compiledCode); } - return compiledCode + return compiledCode; } - return sourceCode + return sourceCode; } -function readCachedJavaScript (relativeCachePath) { - var cachePath = path.join(cacheDirectory, relativeCachePath) +function readCachedJavaScript(relativeCachePath) { + var cachePath = path.join(cacheDirectory, relativeCachePath); if (fs.isFileSync(cachePath)) { try { - return fs.readFileSync(cachePath, 'utf8') + return fs.readFileSync(cachePath, 'utf8'); } catch (error) {} } - return null + return null; } -function writeCachedJavaScript (relativeCachePath, code) { - var cachePath = path.join(cacheDirectory, relativeCachePath) - fs.writeFileSync(cachePath, code, 'utf8') +function writeCachedJavaScript(relativeCachePath, code) { + var cachePath = path.join(cacheDirectory, relativeCachePath); + fs.writeFileSync(cachePath, code, 'utf8'); } -var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg +var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/gm; -exports.install = function (resourcesPath, nodeRequire) { +exports.install = function(resourcesPath, nodeRequire) { const snapshotSourceMapConsumer = { - originalPositionFor ({line, column}) { - const {relativePath, row} = snapshotResult.translateSnapshotRow(line) + originalPositionFor({ line, column }) { + const { relativePath, row } = snapshotResult.translateSnapshotRow(line); return { column, line: row, source: path.join(resourcesPath, 'app', 'static', relativePath), name: null - } + }; } - } + }; sourceMapSupport.install({ handleUncaughtExceptions: false, @@ -134,110 +150,113 @@ exports.install = function (resourcesPath, nodeRequire) { // 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) { + retrieveSourceMap: function(filePath) { if (filePath === '') { - return {map: snapshotSourceMapConsumer} + return { map: snapshotSourceMapConsumer }; } if (!cacheDirectory || !fs.isFileSync(filePath)) { - return null + return null; } try { - var sourceCode = fs.readFileSync(filePath, 'utf8') + var sourceCode = fs.readFileSync(filePath, 'utf8'); } catch (error) { - console.warn('Error reading source file', error.stack) - return null + 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)) + var fileData = readCachedJavaScript( + compiler.getCachePath(sourceCode, filePath) + ); } catch (error) { - console.warn('Error reading compiled file', error.stack) - return null + console.warn('Error reading compiled file', error.stack); + return null; } if (fileData == null) { - return null + return null; } - var match, lastMatch - INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 + var match, lastMatch; + INLINE_SOURCE_MAP_REGEXP.lastIndex = 0; while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { - lastMatch = match + lastMatch = match; } if (lastMatch == null) { - return 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(Buffer.from(rawData, 'base64')) + var sourceMap = JSON.parse(Buffer.from(rawData, 'base64')); } catch (error) { - console.warn('Error parsing source map', error.stack) - return null + console.warn('Error parsing source map', error.stack); + return null; } return { map: sourceMap, url: null - } + }; } - }) + }); - var prepareStackTraceWithSourceMapping = Error.prepareStackTrace - var prepareStackTrace = prepareStackTraceWithSourceMapping + var prepareStackTraceWithSourceMapping = Error.prepareStackTrace; + var prepareStackTrace = prepareStackTraceWithSourceMapping; - function prepareStackTraceWithRawStackAssignment (error, frames) { - if (error.rawStack) { // avoid infinite recursion - return prepareStackTraceWithSourceMapping(error, frames) + function prepareStackTraceWithRawStackAssignment(error, frames) { + if (error.rawStack) { + // avoid infinite recursion + return prepareStackTraceWithSourceMapping(error, frames); } else { - error.rawStack = frames - return prepareStackTrace(error, frames) + error.rawStack = frames; + return prepareStackTrace(error, frames); } } - Error.stackTraceLimit = 30 + Error.stackTraceLimit = 30; Object.defineProperty(Error, 'prepareStackTrace', { - get: function () { - return prepareStackTraceWithRawStackAssignment + get: function() { + return prepareStackTraceWithRawStackAssignment; }, - set: function (newValue) { - prepareStackTrace = newValue - process.nextTick(function () { - prepareStackTrace = prepareStackTraceWithSourceMapping - }) + set: function(newValue) { + prepareStackTrace = newValue; + process.nextTick(function() { + prepareStackTrace = prepareStackTraceWithSourceMapping; + }); } - }) + }); // eslint-disable-next-line no-extend-native - Error.prototype.getRawStack = function () { + Error.prototype.getRawStack = function() { // Access this.stack to ensure prepareStackTrace has been run on this error // because it assigns this.rawStack as a side-effect - this.stack // eslint-disable-line no-unused-expressions - return this.rawStack - } + this.stack; // eslint-disable-line no-unused-expressions + return this.rawStack; + }; - Object.keys(COMPILERS).forEach(function (extension) { - var compiler = COMPILERS[extension] + 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) + value: function(module, filePath) { + var code = compileFileAtPath(compiler, filePath, extension); + return module._compile(code, filePath); } - }) - }) -} + }); + }); +}; -exports.supportedExtensions = Object.keys(COMPILERS) -exports.resetCacheStats() +exports.supportedExtensions = Object.keys(COMPILERS); +exports.resetCacheStats(); diff --git a/src/config-file.js b/src/config-file.js index 11808d821..798be4f5f 100644 --- a/src/config-file.js +++ b/src/config-file.js @@ -1,84 +1,85 @@ -const _ = require('underscore-plus') -const fs = require('fs-plus') -const dedent = require('dedent') -const {Disposable, Emitter} = require('event-kit') -const {watchPath} = require('./path-watcher') -const CSON = require('season') -const Path = require('path') -const async = require('async') +const _ = require('underscore-plus'); +const fs = require('fs-plus'); +const dedent = require('dedent'); +const { Disposable, Emitter } = require('event-kit'); +const { watchPath } = require('./path-watcher'); +const CSON = require('season'); +const Path = require('path'); +const async = require('async'); -const EVENT_TYPES = new Set([ - 'created', - 'modified', - 'renamed' -]) +const EVENT_TYPES = new Set(['created', 'modified', 'renamed']); -module.exports = -class ConfigFile { - static at (path) { +module.exports = class ConfigFile { + static at(path) { if (!this._known) { - this._known = new Map() + this._known = new Map(); } - const existing = this._known.get(path) + const existing = this._known.get(path); if (existing) { - return existing + return existing; } - const created = new ConfigFile(path) - this._known.set(path, created) - return created + const created = new ConfigFile(path); + this._known.set(path, created); + return created; } - constructor (path) { - this.path = path - this.emitter = new Emitter() - this.value = {} - this.reloadCallbacks = [] + constructor(path) { + this.path = path; + this.emitter = new Emitter(); + this.value = {}; + this.reloadCallbacks = []; // Use a queue to prevent multiple concurrent write to the same file. const writeQueue = async.queue((data, callback) => CSON.writeFile(this.path, data, error => { if (error) { - this.emitter.emit('did-error', dedent ` + this.emitter.emit( + 'did-error', + dedent` Failed to write \`${Path.basename(this.path)}\`. ${error.message} - `) + ` + ); } - callback() + callback(); }) - ) + ); - this.requestLoad = _.debounce(() => this.reload(), 200) - this.requestSave = _.debounce((data) => writeQueue.push(data), 200) + this.requestLoad = _.debounce(() => this.reload(), 200); + this.requestSave = _.debounce(data => writeQueue.push(data), 200); } - get () { - return this.value + get() { + return this.value; } - update (value) { + update(value) { return new Promise(resolve => { - this.requestSave(value) - this.reloadCallbacks.push(resolve) - }) + this.requestSave(value); + this.reloadCallbacks.push(resolve); + }); } - async watch (callback) { + async watch(callback) { if (!fs.existsSync(this.path)) { - fs.makeTreeSync(Path.dirname(this.path)) - CSON.writeFileSync(this.path, {}, {flag: 'wx'}) + fs.makeTreeSync(Path.dirname(this.path)); + CSON.writeFileSync(this.path, {}, { flag: 'wx' }); } - await this.reload() + await this.reload(); try { return await watchPath(this.path, {}, events => { - if (events.some(event => EVENT_TYPES.has(event.action))) this.requestLoad() - }) + if (events.some(event => EVENT_TYPES.has(event.action))) + this.requestLoad(); + }); } catch (error) { - this.emitter.emit('did-error', dedent ` + this.emitter.emit( + 'did-error', + dedent` Unable to watch path: \`${Path.basename(this.path)}\`. Make sure you have permissions to \`${this.path}\`. @@ -86,33 +87,37 @@ class ConfigFile { See [this document][watches] for more info. [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path\ - `) - return new Disposable() + ` + ); + return new Disposable(); } } - onDidChange (callback) { - return this.emitter.on('did-change', callback) + onDidChange(callback) { + return this.emitter.on('did-change', callback); } - onDidError (callback) { - return this.emitter.on('did-error', callback) + onDidError(callback) { + return this.emitter.on('did-error', callback); } - reload () { + reload() { return new Promise(resolve => { CSON.readFile(this.path, (error, data) => { if (error) { - this.emitter.emit('did-error', `Failed to load \`${Path.basename(this.path)}\` - ${error.message}`) + this.emitter.emit( + 'did-error', + `Failed to load \`${Path.basename(this.path)}\` - ${error.message}` + ); } else { - this.value = data || {} - this.emitter.emit('did-change', this.value) + this.value = data || {}; + this.emitter.emit('did-change', this.value); - for (const callback of this.reloadCallbacks) callback() - this.reloadCallbacks.length = 0 + for (const callback of this.reloadCallbacks) callback(); + this.reloadCallbacks.length = 0; } - resolve() - }) - }) + resolve(); + }); + }); } -} +}; diff --git a/src/config-schema.js b/src/config-schema.js index 39831b2b6..fdbaf70c1 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -7,22 +7,33 @@ const configSchema = { properties: { ignoredNames: { type: 'array', - default: ['.git', '.hg', '.svn', '.DS_Store', '._*', 'Thumbs.db', 'desktop.ini'], + default: [ + '.git', + '.hg', + '.svn', + '.DS_Store', + '._*', + 'Thumbs.db', + 'desktop.ini' + ], items: { type: 'string' }, - description: 'List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.' + description: + 'List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.' }, excludeVcsIgnoredPaths: { type: 'boolean', default: true, title: 'Exclude VCS Ignored Paths', - description: 'Files and directories ignored by the current project\'s VCS will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders.' + description: + "Files and directories ignored by the current project's VCS will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders." }, followSymlinks: { type: 'boolean', default: true, - description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.' + description: + 'Follow symbolic links when searching files and when opening files with the fuzzy finder.' }, disabledPackages: { type: 'array', @@ -32,7 +43,8 @@ const configSchema = { type: 'string' }, - description: 'List of names of installed packages which are not loaded at startup.' + description: + 'List of names of installed packages which are not loaded at startup.' }, versionPinnedPackages: { type: 'array', @@ -42,12 +54,14 @@ const configSchema = { type: 'string' }, - description: 'List of names of installed packages which are not automatically updated.' + description: + 'List of names of installed packages which are not automatically updated.' }, customFileTypes: { type: 'object', default: {}, - description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)', + description: + 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)', additionalProperties: { type: 'array', items: { @@ -58,15 +72,18 @@ const configSchema = { uriHandlerRegistration: { type: 'string', default: 'prompt', - description: 'When should Atom register itself as the default handler for atom:// URIs', + description: + 'When should Atom register itself as the default handler for atom:// URIs', enum: [ { value: 'prompt', - description: 'Prompt to register Atom as the default atom:// URI handler' + description: + 'Prompt to register Atom as the default atom:// URI handler' }, { value: 'always', - description: 'Always become the default atom:// URI handler automatically' + description: + 'Always become the default atom:// URI handler automatically' }, { value: 'never', @@ -80,32 +97,38 @@ const configSchema = { items: { type: 'string' }, - description: 'Names of UI and syntax themes which will be used when Atom starts.' + description: + 'Names of UI and syntax themes which will be used when Atom starts.' }, audioBeep: { type: 'boolean', default: true, - description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.' + description: + "Trigger the system's beep sound when certain actions cannot be executed or there are no results." }, closeDeletedFileTabs: { type: 'boolean', default: false, title: 'Close Deleted File Tabs', - description: 'Close corresponding editors when a file is deleted outside Atom.' + description: + 'Close corresponding editors when a file is deleted outside Atom.' }, destroyEmptyPanes: { type: 'boolean', default: true, title: 'Remove Empty Panes', - description: 'When the last tab of a pane is closed, remove that pane as well.' + description: + 'When the last tab of a pane is closed, remove that pane as well.' }, closeEmptyWindows: { type: 'boolean', default: true, - description: 'When a window with no open tabs or panes is given the \'Close Tab\' command, close that window.' + description: + "When a window with no open tabs or panes is given the 'Close Tab' command, close that window." }, fileEncoding: { - description: 'Default character set encoding to use when reading and writing files.', + description: + 'Default character set encoding to use when reading and writing files.', type: 'string', default: 'utf8', enum: [ @@ -272,7 +295,8 @@ const configSchema = { ] }, openEmptyEditorOnStart: { - description: 'When checked opens an untitled editor when loading a blank environment (such as with _File > New Window_ or when "Restore Previous Windows On Start" is unchecked); otherwise no editor is opened when loading a blank environment. This setting has no effect when restoring a previous state.', + description: + 'When checked opens an untitled editor when loading a blank environment (such as with _File > New Window_ or when "Restore Previous Windows On Start" is unchecked); otherwise no editor is opened when loading a blank environment. This setting has no effect when restoring a previous state.', type: 'boolean', default: true }, @@ -280,38 +304,45 @@ const configSchema = { type: 'string', enum: ['no', 'yes', 'always'], default: 'yes', - description: "When selected 'no', a blank environment is loaded. When selected 'yes' and Atom is started from the icon or `atom` by itself from the command line, restores the last state of all Atom windows; otherwise a blank environment is loaded. When selected 'always', restores the last state of all Atom windows always, no matter how Atom is started." + description: + "When selected 'no', a blank environment is loaded. When selected 'yes' and Atom is started from the icon or `atom` by itself from the command line, restores the last state of all Atom windows; otherwise a blank environment is loaded. When selected 'always', restores the last state of all Atom windows always, no matter how Atom is started." }, reopenProjectMenuCount: { - description: 'How many recent projects to show in the Reopen Project menu.', + description: + 'How many recent projects to show in the Reopen Project menu.', type: 'integer', default: 15 }, automaticallyUpdate: { - description: 'Automatically update Atom when a new release is available.', + description: + 'Automatically update Atom when a new release is available.', type: 'boolean', default: true }, useProxySettingsWhenCallingApm: { title: 'Use Proxy Settings When Calling APM', - description: 'Use detected proxy settings when calling the `apm` command-line tool.', + description: + 'Use detected proxy settings when calling the `apm` command-line tool.', type: 'boolean', default: true }, allowPendingPaneItems: { - description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.', + description: + 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.', type: 'boolean', default: true }, telemetryConsent: { - description: 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.', + description: + 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.', title: 'Send Telemetry to the Atom Team', type: 'string', default: 'undecided', enum: [ { value: 'limited', - description: 'Allow limited anonymous usage stats, exception and crash reporting' + description: + 'Allow limited anonymous usage stats, exception and crash reporting' }, { value: 'no', @@ -319,17 +350,20 @@ const configSchema = { }, { value: 'undecided', - description: 'Undecided (Atom will ask again next time it is launched)' + description: + 'Undecided (Atom will ask again next time it is launched)' } ] }, warnOnLargeFileLimit: { - description: 'Warn before opening files larger than this number of megabytes.', + description: + 'Warn before opening files larger than this number of megabytes.', type: 'number', default: 40 }, fileSystemWatcher: { - description: 'Choose the underlying implementation used to watch for filesystem changes. Emulating changes will miss any events caused by applications other than Atom, but may help prevent crashes or freezes.', + description: + 'Choose the underlying implementation used to watch for filesystem changes. Emulating changes will miss any events caused by applications other than Atom, but may help prevent crashes or freezes.', type: 'string', default: 'native', enum: [ @@ -357,7 +391,8 @@ const configSchema = { description: 'Use Tree-sitter parsers for supported languages.' }, colorProfile: { - description: "Specify whether Atom should use the operating system's color profile (recommended) or an alternative color profile.
          Changing this setting will require a relaunch of Atom to take effect.", + description: + "Specify whether Atom should use the operating system's color profile (recommended) or an alternative color profile.
          Changing this setting will require a relaunch of Atom to take effect.", type: 'string', default: 'default', enum: [ @@ -412,13 +447,14 @@ const configSchema = { }, showCursorOnSelection: { type: 'boolean', - 'default': true, + default: true, description: 'Show cursor while there is a selection.' }, showInvisibles: { type: 'boolean', default: false, - description: 'Render placeholders for invisible characters, such as tabs, spaces and newlines.' + description: + 'Render placeholders for invisible characters, such as tabs, spaces and newlines.' }, showIndentGuide: { type: 'boolean', @@ -428,12 +464,13 @@ const configSchema = { showLineNumbers: { type: 'boolean', default: true, - description: 'Show line numbers in the editor\'s gutter.' + description: "Show line numbers in the editor's gutter." }, atomicSoftTabs: { type: 'boolean', default: true, - description: 'Skip over tab-length runs of leading whitespace when moving the cursor.' + description: + 'Skip over tab-length runs of leading whitespace when moving the cursor.' }, autoIndent: { type: 'boolean', @@ -443,24 +480,28 @@ const configSchema = { autoIndentOnPaste: { type: 'boolean', default: true, - description: 'Automatically indent pasted text based on the indentation of the previous line.' + description: + 'Automatically indent pasted text based on the indentation of the previous line.' }, nonWordCharacters: { type: 'string', - default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…", - description: 'A string of non-word characters to define word boundaries.' + default: '/\\()"\':,.;<>~!@#$%^&*|+=[]{}`?-…', + description: + 'A string of non-word characters to define word boundaries.' }, preferredLineLength: { type: 'integer', default: 80, minimum: 1, - description: 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.' + description: + 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.' }, maxScreenLineLength: { type: 'integer', default: 500, minimum: 500, - description: 'Defines the maximum width of the editor window before soft wrapping is enforced, in number of characters.' + description: + 'Defines the maximum width of the editor window before soft wrapping is enforced, in number of characters.' }, tabLength: { type: 'integer', @@ -471,99 +512,115 @@ const configSchema = { softWrap: { type: 'boolean', default: false, - description: 'Wraps lines that exceed the width of the window. When `Soft Wrap At Preferred Line Length` is set, it will wrap to the number of characters defined by the `Preferred Line Length` setting.' + description: + 'Wraps lines that exceed the width of the window. When `Soft Wrap At Preferred Line Length` is set, it will wrap to the number of characters defined by the `Preferred Line Length` setting.' }, softTabs: { type: 'boolean', default: true, - description: 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.' + description: + 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.' }, tabType: { type: 'string', default: 'auto', enum: ['auto', 'soft', 'hard'], - description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.' + description: + 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.' }, softWrapAtPreferredLineLength: { type: 'boolean', default: false, - description: 'Instead of wrapping lines to the window\'s width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language. **Note:** If you want to hide the wrap guide (the vertical line) you can disable the `wrap-guide` package.' + description: + "Instead of wrapping lines to the window's width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language. **Note:** If you want to hide the wrap guide (the vertical line) you can disable the `wrap-guide` package." }, softWrapHangingIndent: { type: 'integer', default: 0, minimum: 0, - description: 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.' + description: + 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.' }, scrollSensitivity: { type: 'integer', default: 40, minimum: 10, maximum: 200, - description: 'Determines how fast the editor scrolls when using a mouse or trackpad.' + description: + 'Determines how fast the editor scrolls when using a mouse or trackpad.' }, scrollPastEnd: { type: 'boolean', default: false, - description: 'Allow the editor to be scrolled past the end of the last line.' + description: + 'Allow the editor to be scrolled past the end of the last line.' }, undoGroupingInterval: { type: 'integer', default: 300, minimum: 0, - description: 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.' + description: + 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.' }, confirmCheckoutHeadRevision: { type: 'boolean', default: true, title: 'Confirm Checkout HEAD Revision', - description: 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.' + description: + 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.' }, invisibles: { type: 'object', - description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).', + description: + 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).', properties: { eol: { type: ['boolean', 'string'], default: '¬', maximumLength: 1, - description: 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. ' + description: + 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. ' }, space: { type: ['boolean', 'string'], default: '·', maximumLength: 1, - description: 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.' + description: + 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.' }, tab: { type: ['boolean', 'string'], default: '»', maximumLength: 1, - description: 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.' + description: + 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.' }, cr: { type: ['boolean', 'string'], default: '¤', maximumLength: 1, - description: 'Character used to render carriage return characters (for Microsoft-style line endings) when the `Show Invisibles` setting is enabled.' + description: + 'Character used to render carriage return characters (for Microsoft-style line endings) when the `Show Invisibles` setting is enabled.' } } }, zoomFontWhenCtrlScrolling: { type: 'boolean', default: process.platform !== 'darwin', - description: 'Change the editor font size when pressing the Ctrl key and scrolling the mouse up/down.' + description: + 'Change the editor font size when pressing the Ctrl key and scrolling the mouse up/down.' } } } -} +}; if (['win32', 'linux'].includes(process.platform)) { configSchema.core.properties.autoHideMenuBar = { type: 'boolean', default: false, - description: 'Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux.' - } + description: + 'Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux.' + }; } if (process.platform === 'darwin') { @@ -571,8 +628,9 @@ if (process.platform === 'darwin') { type: 'string', default: 'native', enum: ['native', 'custom', 'custom-inset', 'hidden'], - description: 'Experimental: A `custom` title bar adapts to theme colors. Choosing `custom-inset` adds a bit more padding. The title bar can also be completely `hidden`.
          Note: Switching to a custom or hidden title bar will compromise some functionality.
          This setting will require a relaunch of Atom to take effect.' - } + description: + 'Experimental: A `custom` title bar adapts to theme colors. Choosing `custom-inset` adds a bit more padding. The title bar can also be completely `hidden`.
          Note: Switching to a custom or hidden title bar will compromise some functionality.
          This setting will require a relaunch of Atom to take effect.' + }; } -module.exports = configSchema +module.exports = configSchema; diff --git a/src/config.js b/src/config.js index 4a7d2eca2..157cc8b69 100644 --- a/src/config.js +++ b/src/config.js @@ -1,14 +1,17 @@ -const _ = require('underscore-plus') -const {Emitter} = require('event-kit') +const _ = require('underscore-plus'); +const { Emitter } = require('event-kit'); const { - getValueAtKeyPath, setValueAtKeyPath, deleteValueAtKeyPath, - pushKeyPath, splitKeyPath -} = require('key-path-helpers') -const Color = require('./color') -const ScopedPropertyStore = require('scoped-property-store') -const ScopeDescriptor = require('./scope-descriptor') + getValueAtKeyPath, + setValueAtKeyPath, + deleteValueAtKeyPath, + pushKeyPath, + splitKeyPath +} = require('key-path-helpers'); +const Color = require('./color'); +const ScopedPropertyStore = require('scoped-property-store'); +const ScopeDescriptor = require('./scope-descriptor'); -const schemaEnforcers = {} +const schemaEnforcers = {}; // Essential: Used to access all of Atom's configuration details. // @@ -378,79 +381,87 @@ const schemaEnforcers = {} // * Don't depend on (or write to) configuration keys outside of your keypath. // class Config { - static addSchemaEnforcer (typeName, enforcerFunction) { - if (schemaEnforcers[typeName] == null) { schemaEnforcers[typeName] = [] } - return schemaEnforcers[typeName].push(enforcerFunction) + static addSchemaEnforcer(typeName, enforcerFunction) { + if (schemaEnforcers[typeName] == null) { + schemaEnforcers[typeName] = []; + } + return schemaEnforcers[typeName].push(enforcerFunction); } - static addSchemaEnforcers (filters) { + static addSchemaEnforcers(filters) { for (let typeName in filters) { - const functions = filters[typeName] + const functions = filters[typeName]; for (let name in functions) { - const enforcerFunction = functions[name] - this.addSchemaEnforcer(typeName, enforcerFunction) + const enforcerFunction = functions[name]; + this.addSchemaEnforcer(typeName, enforcerFunction); } } } - static executeSchemaEnforcers (keyPath, value, schema) { - let error = null - let types = schema.type - if (!Array.isArray(types)) { types = [types] } + static executeSchemaEnforcers(keyPath, value, schema) { + let error = null; + let types = schema.type; + if (!Array.isArray(types)) { + types = [types]; + } for (let type of types) { try { - const enforcerFunctions = schemaEnforcers[type].concat(schemaEnforcers['*']) + const enforcerFunctions = schemaEnforcers[type].concat( + schemaEnforcers['*'] + ); for (let enforcer of enforcerFunctions) { // At some point in one's life, one must call upon an enforcer. - value = enforcer.call(this, keyPath, value, schema) + value = enforcer.call(this, keyPath, value, schema); } - error = null - break + error = null; + break; } catch (e) { - error = e + error = e; } } - if (error != null) { throw error } - return value + if (error != null) { + throw error; + } + return value; } // Created during initialization, available as `atom.config` - constructor (params = {}) { - this.clear() - this.initialize(params) + constructor(params = {}) { + this.clear(); + this.initialize(params); } - initialize ({saveCallback, mainSource, projectHomeSchema}) { + initialize({ saveCallback, mainSource, projectHomeSchema }) { if (saveCallback) { - this.saveCallback = saveCallback + this.saveCallback = saveCallback; } - if (mainSource) this.mainSource = mainSource + if (mainSource) this.mainSource = mainSource; if (projectHomeSchema) { - this.schema.properties.core.properties.projectHome = projectHomeSchema - this.defaultSettings.core.projectHome = projectHomeSchema.default + this.schema.properties.core.properties.projectHome = projectHomeSchema; + this.defaultSettings.core.projectHome = projectHomeSchema.default; } } - clear () { - this.emitter = new Emitter() + clear() { + this.emitter = new Emitter(); this.schema = { type: 'object', properties: {} - } + }; - this.defaultSettings = {} - this.settings = {} - this.projectSettings = {} - this.projectFile = null + this.defaultSettings = {}; + this.settings = {}; + this.projectSettings = {}; + this.projectFile = null; - this.scopedSettingsStore = new ScopedPropertyStore() + this.scopedSettingsStore = new ScopedPropertyStore(); - this.settingsLoaded = false - this.transactDepth = 0 - this.pendingOperations = [] - this.legacyScopeAliases = new Map() - this.requestSave = _.debounce(() => this.save(), 1) + this.settingsLoaded = false; + this.transactDepth = 0; + this.pendingOperations = []; + this.legacyScopeAliases = new Map(); + this.requestSave = _.debounce(() => this.save(), 1); } /* @@ -483,22 +494,31 @@ class Config { // // Returns a {Disposable} with the following keys on which you can call // `.dispose()` to unsubscribe. - observe (...args) { - let callback, keyPath, options, scopeDescriptor + observe(...args) { + let callback, keyPath, options, scopeDescriptor; if (args.length === 2) { - [keyPath, callback] = args - } else if ((args.length === 3) && (_.isString(args[0]) && _.isObject(args[1]))) { - [keyPath, options, callback] = args - scopeDescriptor = options.scope + [keyPath, callback] = args; + } else if ( + args.length === 3 && + (_.isString(args[0]) && _.isObject(args[1])) + ) { + [keyPath, options, callback] = args; + scopeDescriptor = options.scope; } else { - console.error('An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details') - return + console.error( + 'An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details' + ); + return; } if (scopeDescriptor != null) { - return this.observeScopedKeyPath(scopeDescriptor, keyPath, callback) + return this.observeScopedKeyPath(scopeDescriptor, keyPath, callback); } else { - return this.observeKeyPath(keyPath, options != null ? options : {}, callback) + return this.observeKeyPath( + keyPath, + options != null ? options : {}, + callback + ); } } @@ -520,22 +540,22 @@ class Config { // // Returns a {Disposable} with the following keys on which you can call // `.dispose()` to unsubscribe. - onDidChange (...args) { - let callback, keyPath, scopeDescriptor + onDidChange(...args) { + let callback, keyPath, scopeDescriptor; if (args.length === 1) { - [callback] = args + [callback] = args; } else if (args.length === 2) { - [keyPath, callback] = args + [keyPath, callback] = args; } else { let options; - [keyPath, options, callback] = args - scopeDescriptor = options.scope + [keyPath, options, callback] = args; + scopeDescriptor = options.scope; } if (scopeDescriptor != null) { - return this.onDidChangeScopedKeyPath(scopeDescriptor, keyPath, callback) + return this.onDidChangeScopedKeyPath(scopeDescriptor, keyPath, callback); } else { - return this.onDidChangeKeyPath(keyPath, callback) + return this.onDidChangeKeyPath(keyPath, callback); } } @@ -598,22 +618,22 @@ class Config { // // Returns the value from Atom's default settings, the user's configuration // file in the type specified by the configuration schema. - get (...args) { - let keyPath, options, scope + get(...args) { + let keyPath, options, scope; if (args.length > 1) { - if ((typeof args[0] === 'string') || (args[0] == null)) { + if (typeof args[0] === 'string' || args[0] == null) { [keyPath, options] = args; - ({scope} = options) + ({ scope } = options); } } else { - [keyPath] = args + [keyPath] = args; } if (scope != null) { - const value = this.getRawScopedValue(scope, keyPath, options) - return value != null ? value : this.getRawValue(keyPath, options) + const value = this.getRawScopedValue(scope, keyPath, options); + return value != null ? value : this.getRawValue(keyPath, options); } else { - return this.getRawValue(keyPath, options) + return this.getRawValue(keyPath, options); } } @@ -626,36 +646,44 @@ class Config { // Returns an {Array} of {Object}s with the following keys: // * `scopeDescriptor` The {ScopeDescriptor} with which the value is associated // * `value` The value for the key-path - getAll (keyPath, options) { - let globalValue, result, scope - if (options != null) { ({scope} = options) } + getAll(keyPath, options) { + let globalValue, result, scope; + if (options != null) { + ({ scope } = options); + } if (scope != null) { - let legacyScopeDescriptor - const scopeDescriptor = ScopeDescriptor.fromObject(scope) + let legacyScopeDescriptor; + const scopeDescriptor = ScopeDescriptor.fromObject(scope); result = this.scopedSettingsStore.getAll( - scopeDescriptor.getScopeChain(), - keyPath, - options - ) - legacyScopeDescriptor = this.getLegacyScopeDescriptorForNewScopeDescriptor(scopeDescriptor) + scopeDescriptor.getScopeChain(), + keyPath, + options + ); + legacyScopeDescriptor = this.getLegacyScopeDescriptorForNewScopeDescriptor( + scopeDescriptor + ); if (legacyScopeDescriptor) { - result.push(...Array.from(this.scopedSettingsStore.getAll( - legacyScopeDescriptor.getScopeChain(), - keyPath, - options - ) || [])) + result.push( + ...Array.from( + this.scopedSettingsStore.getAll( + legacyScopeDescriptor.getScopeChain(), + keyPath, + options + ) || [] + ) + ); } } else { - result = [] + result = []; } - globalValue = this.getRawValue(keyPath, options) + globalValue = this.getRawValue(keyPath, options); if (globalValue) { - result.push({scopeSelector: '*', value: globalValue}) + result.push({ scopeSelector: '*', value: globalValue }); } - return result + return result; } // Essential: Sets the value for a configuration setting. @@ -700,43 +728,46 @@ class Config { // Returns a {Boolean} // * `true` if the value was set. // * `false` if the value was not able to be coerced to the type specified in the setting's schema. - set (...args) { - let [keyPath, value, options = {}] = args + set(...args) { + let [keyPath, value, options = {}] = args; if (!this.settingsLoaded) { - this.pendingOperations.push(() => this.set(keyPath, value, options)) + this.pendingOperations.push(() => this.set(keyPath, value, options)); } // We should never use the scoped store to set global settings, since they are kept directly // in the config object. - const scopeSelector = options.scopeSelector !== '*' ? options.scopeSelector : undefined - let source = options.source - const shouldSave = options.save != null ? options.save : true + const scopeSelector = + options.scopeSelector !== '*' ? options.scopeSelector : undefined; + let source = options.source; + const shouldSave = options.save != null ? options.save : true; if (source && !scopeSelector && source !== this.projectFile) { - throw new Error("::set with a 'source' and no 'sourceSelector' is not yet implemented!") + throw new Error( + "::set with a 'source' and no 'sourceSelector' is not yet implemented!" + ); } - if (!source) source = this.mainSource + if (!source) source = this.mainSource; if (value !== undefined) { try { - value = this.makeValueConformToSchema(keyPath, value) + value = this.makeValueConformToSchema(keyPath, value); } catch (e) { - return false + return false; } } if (scopeSelector != null) { - this.setRawScopedValue(keyPath, value, source, scopeSelector) + this.setRawScopedValue(keyPath, value, source, scopeSelector); } else { - this.setRawValue(keyPath, value, {source}) + this.setRawValue(keyPath, value, { source }); } if (source === this.mainSource && shouldSave && this.settingsLoaded) { - this.requestSave() + this.requestSave(); } - return true + return true; } // Essential: Restore the setting at `keyPath` to its default value. @@ -745,48 +776,71 @@ class Config { // * `options` (optional) {Object} // * `scopeSelector` (optional) {String}. See {::set} // * `source` (optional) {String}. See {::set} - unset (keyPath, options) { + unset(keyPath, options) { if (!this.settingsLoaded) { - this.pendingOperations.push(() => this.unset(keyPath, options)) + this.pendingOperations.push(() => this.unset(keyPath, options)); } - let {scopeSelector, source} = options != null ? options : {} - if (source == null) { source = this.mainSource } + let { scopeSelector, source } = options != null ? options : {}; + if (source == null) { + source = this.mainSource; + } if (scopeSelector != null) { if (keyPath != null) { - let settings = this.scopedSettingsStore.propertiesForSourceAndSelector(source, scopeSelector) + let settings = this.scopedSettingsStore.propertiesForSourceAndSelector( + source, + scopeSelector + ); if (getValueAtKeyPath(settings, keyPath) != null) { - this.scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector) - setValueAtKeyPath(settings, keyPath, undefined) - settings = withoutEmptyObjects(settings) + this.scopedSettingsStore.removePropertiesForSourceAndSelector( + source, + scopeSelector + ); + setValueAtKeyPath(settings, keyPath, undefined); + settings = withoutEmptyObjects(settings); if (settings != null) { - this.set(null, settings, {scopeSelector, source, priority: this.priorityForSource(source)}) + this.set(null, settings, { + scopeSelector, + source, + priority: this.priorityForSource(source) + }); } - const configIsReady = (source === this.mainSource) && this.settingsLoaded + const configIsReady = + source === this.mainSource && this.settingsLoaded; if (configIsReady) { - return this.requestSave() + return this.requestSave(); } } } else { - this.scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector) - return this.emitChangeEvent() + this.scopedSettingsStore.removePropertiesForSourceAndSelector( + source, + scopeSelector + ); + return this.emitChangeEvent(); } } else { - for (scopeSelector in this.scopedSettingsStore.propertiesForSource(source)) { - this.unset(keyPath, {scopeSelector, source}) + for (scopeSelector in this.scopedSettingsStore.propertiesForSource( + source + )) { + this.unset(keyPath, { scopeSelector, source }); } - if ((keyPath != null) && (source === this.mainSource)) { - return this.set(keyPath, getValueAtKeyPath(this.defaultSettings, keyPath)) + if (keyPath != null && source === this.mainSource) { + return this.set( + keyPath, + getValueAtKeyPath(this.defaultSettings, keyPath) + ); } } } // Extended: Get an {Array} of all of the `source` {String}s with which // settings have been added via {::set}. - getSources () { - return _.uniq(_.pluck(this.scopedSettingsStore.propertySets, 'source')).sort() + getSources() { + return _.uniq( + _.pluck(this.scopedSettingsStore.propertySets, 'source') + ).sort(); } // Extended: Retrieve the schema for a specific key path. The schema will tell @@ -798,32 +852,33 @@ class Config { // Returns an {Object} eg. `{type: 'integer', default: 23, minimum: 1}`. // Returns `null` when the keyPath has no schema specified, but is accessible // from the root schema. - getSchema (keyPath) { - const keys = splitKeyPath(keyPath) - let { schema } = this + getSchema(keyPath) { + const keys = splitKeyPath(keyPath); + let { schema } = this; for (let key of keys) { - let childSchema + let childSchema; if (schema.type === 'object') { - childSchema = schema.properties != null ? schema.properties[key] : undefined + childSchema = + schema.properties != null ? schema.properties[key] : undefined; if (childSchema == null) { if (isPlainObject(schema.additionalProperties)) { - childSchema = schema.additionalProperties + childSchema = schema.additionalProperties; } else if (schema.additionalProperties === false) { - return null + return null; } else { - return {type: 'any'} + return { type: 'any' }; } } } else { - return null + return null; } - schema = childSchema + schema = childSchema; } - return schema + return schema; } - getUserConfigPath () { - return this.mainSource + getUserConfigPath() { + return this.mainSource; } // Extended: Suppress calls to handler functions registered with {::onDidChange} @@ -831,17 +886,17 @@ class Config { // handlers will be called once if the value for their key-path has changed. // // * `callback` {Function} to execute while suppressing calls to handlers. - transact (callback) { - this.beginTransaction() + transact(callback) { + this.beginTransaction(); try { - return callback() + return callback(); } finally { - this.endTransaction() + this.endTransaction(); } } - getLegacyScopeDescriptorForNewScopeDescriptor (scopeDescriptor) { - return null + getLegacyScopeDescriptorForNewScopeDescriptor(scopeDescriptor) { + return null; } /* @@ -859,91 +914,104 @@ class Config { // Returns a {Promise} that is either resolved or rejected according to the // `{Promise}` returned by `callback`. If `callback` throws an error, a // rejected {Promise} will be returned instead. - transactAsync (callback) { - let endTransaction - this.beginTransaction() + transactAsync(callback) { + let endTransaction; + this.beginTransaction(); try { endTransaction = fn => (...args) => { - this.endTransaction() - return fn(...args) - } - const result = callback() + this.endTransaction(); + return fn(...args); + }; + const result = callback(); return new Promise((resolve, reject) => { - return result.then(endTransaction(resolve)).catch(endTransaction(reject)) - }) + return result + .then(endTransaction(resolve)) + .catch(endTransaction(reject)); + }); } catch (error) { - this.endTransaction() - return Promise.reject(error) + this.endTransaction(); + return Promise.reject(error); } } - beginTransaction () { - this.transactDepth++ + beginTransaction() { + this.transactDepth++; } - endTransaction () { - this.transactDepth-- - this.emitChangeEvent() + endTransaction() { + this.transactDepth--; + this.emitChangeEvent(); } - pushAtKeyPath (keyPath, value) { - const left = this.get(keyPath) - const arrayValue = (left == null ? [] : left) - const result = arrayValue.push(value) - this.set(keyPath, arrayValue) - return result + pushAtKeyPath(keyPath, value) { + const left = this.get(keyPath); + const arrayValue = left == null ? [] : left; + const result = arrayValue.push(value); + this.set(keyPath, arrayValue); + return result; } - unshiftAtKeyPath (keyPath, value) { - const left = this.get(keyPath) - const arrayValue = (left == null ? [] : left) - const result = arrayValue.unshift(value) - this.set(keyPath, arrayValue) - return result + unshiftAtKeyPath(keyPath, value) { + const left = this.get(keyPath); + const arrayValue = left == null ? [] : left; + const result = arrayValue.unshift(value); + this.set(keyPath, arrayValue); + return result; } - removeAtKeyPath (keyPath, value) { - const left = this.get(keyPath) - const arrayValue = (left == null ? [] : left) - const result = _.remove(arrayValue, value) - this.set(keyPath, arrayValue) - return result + removeAtKeyPath(keyPath, value) { + const left = this.get(keyPath); + const arrayValue = left == null ? [] : left; + const result = _.remove(arrayValue, value); + this.set(keyPath, arrayValue); + return result; } - setSchema (keyPath, schema) { + setSchema(keyPath, schema) { if (!isPlainObject(schema)) { - throw new Error(`Error loading schema for ${keyPath}: schemas can only be objects!`) + throw new Error( + `Error loading schema for ${keyPath}: schemas can only be objects!` + ); } if (schema.type == null) { - throw new Error(`Error loading schema for ${keyPath}: schema objects must have a type attribute`) + throw new Error( + `Error loading schema for ${keyPath}: schema objects must have a type attribute` + ); } - let rootSchema = this.schema + let rootSchema = this.schema; if (keyPath) { for (let key of splitKeyPath(keyPath)) { - rootSchema.type = 'object' - if (rootSchema.properties == null) { rootSchema.properties = {} } - const { properties } = rootSchema - if (properties[key] == null) { properties[key] = {} } - rootSchema = properties[key] + rootSchema.type = 'object'; + if (rootSchema.properties == null) { + rootSchema.properties = {}; + } + const { properties } = rootSchema; + if (properties[key] == null) { + properties[key] = {}; + } + rootSchema = properties[key]; } } - Object.assign(rootSchema, schema) + Object.assign(rootSchema, schema); this.transact(() => { - this.setDefaults(keyPath, this.extractDefaultsFromSchema(schema)) - this.setScopedDefaultsFromSchema(keyPath, schema) - this.resetSettingsForSchemaChange() - }) + this.setDefaults(keyPath, this.extractDefaultsFromSchema(schema)); + this.setScopedDefaultsFromSchema(keyPath, schema); + this.resetSettingsForSchemaChange(); + }); } - save () { + save() { if (this.saveCallback) { - let allSettings = {'*': this.settings} - allSettings = Object.assign(allSettings, this.scopedSettingsStore.propertiesForSource(this.mainSource)) - allSettings = sortObject(allSettings) - this.saveCallback(allSettings) + let allSettings = { '*': this.settings }; + allSettings = Object.assign( + allSettings, + this.scopedSettingsStore.propertiesForSource(this.mainSource) + ); + allSettings = sortObject(allSettings); + this.saveCallback(allSettings); } } @@ -951,190 +1019,206 @@ class Config { Section: Private methods managing global settings */ - resetUserSettings (newSettings, options = {}) { - this._resetSettings(newSettings, options) + resetUserSettings(newSettings, options = {}) { + this._resetSettings(newSettings, options); } - _resetSettings (newSettings, options = {}) { - const source = options.source - newSettings = Object.assign({}, newSettings) + _resetSettings(newSettings, options = {}) { + const source = options.source; + newSettings = Object.assign({}, newSettings); if (newSettings.global != null) { - newSettings['*'] = newSettings.global - delete newSettings.global + newSettings['*'] = newSettings.global; + delete newSettings.global; } if (newSettings['*'] != null) { - const scopedSettings = newSettings - newSettings = newSettings['*'] - delete scopedSettings['*'] - this.resetScopedSettings(scopedSettings, {source}) + const scopedSettings = newSettings; + newSettings = newSettings['*']; + delete scopedSettings['*']; + this.resetScopedSettings(scopedSettings, { source }); } return this.transact(() => { - this._clearUnscopedSettingsForSource(source) - this.settingsLoaded = true + this._clearUnscopedSettingsForSource(source); + this.settingsLoaded = true; for (let key in newSettings) { - const value = newSettings[key] - this.set(key, value, {save: false, source}) + const value = newSettings[key]; + this.set(key, value, { save: false, source }); } if (this.pendingOperations.length) { - for (let op of this.pendingOperations) { op() } - this.pendingOperations = [] + for (let op of this.pendingOperations) { + op(); + } + this.pendingOperations = []; } - }) + }); } - _clearUnscopedSettingsForSource (source) { + _clearUnscopedSettingsForSource(source) { if (source === this.projectFile) { - this.projectSettings = {} + this.projectSettings = {}; } else { - this.settings = {} + this.settings = {}; } } - resetProjectSettings (newSettings, projectFile) { + resetProjectSettings(newSettings, projectFile) { // Sets the scope and source of all project settings to `path`. - newSettings = Object.assign({}, newSettings) - const oldProjectFile = this.projectFile - this.projectFile = projectFile + newSettings = Object.assign({}, newSettings); + const oldProjectFile = this.projectFile; + this.projectFile = projectFile; if (this.projectFile != null) { - this._resetSettings(newSettings, {source: this.projectFile}) + this._resetSettings(newSettings, { source: this.projectFile }); } else { - this.scopedSettingsStore.removePropertiesForSource(oldProjectFile) - this.projectSettings = {} + this.scopedSettingsStore.removePropertiesForSource(oldProjectFile); + this.projectSettings = {}; } } - clearProjectSettings () { - this.resetProjectSettings({}, null) + clearProjectSettings() { + this.resetProjectSettings({}, null); } - getRawValue (keyPath, options = {}) { - let value - if (!options.excludeSources || !options.excludeSources.includes(this.mainSource)) { - value = getValueAtKeyPath(this.settings, keyPath) + getRawValue(keyPath, options = {}) { + let value; + if ( + !options.excludeSources || + !options.excludeSources.includes(this.mainSource) + ) { + value = getValueAtKeyPath(this.settings, keyPath); if (this.projectFile != null) { - const projectValue = getValueAtKeyPath(this.projectSettings, keyPath) - value = (projectValue === undefined) ? value : projectValue + const projectValue = getValueAtKeyPath(this.projectSettings, keyPath); + value = projectValue === undefined ? value : projectValue; } } - let defaultValue + let defaultValue; if (!options.sources || options.sources.length === 0) { - defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath) + defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath); } if (value != null) { - value = this.deepClone(value) + value = this.deepClone(value); if (isPlainObject(value) && isPlainObject(defaultValue)) { - this.deepDefaults(value, defaultValue) + this.deepDefaults(value, defaultValue); } - return value + return value; } else { - return this.deepClone(defaultValue) + return this.deepClone(defaultValue); } } - setRawValue (keyPath, value, options = {}) { - const source = options.source ? options.source : undefined - const settingsToChange = source === this.projectFile ? 'projectSettings' : 'settings' - const defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath) + setRawValue(keyPath, value, options = {}) { + const source = options.source ? options.source : undefined; + const settingsToChange = + source === this.projectFile ? 'projectSettings' : 'settings'; + const defaultValue = getValueAtKeyPath(this.defaultSettings, keyPath); if (_.isEqual(defaultValue, value)) { if (keyPath != null) { - deleteValueAtKeyPath(this[settingsToChange], keyPath) + deleteValueAtKeyPath(this[settingsToChange], keyPath); } else { - this[settingsToChange] = null + this[settingsToChange] = null; } } else { if (keyPath != null) { - setValueAtKeyPath(this[settingsToChange], keyPath, value) + setValueAtKeyPath(this[settingsToChange], keyPath, value); } else { - this[settingsToChange] = value + this[settingsToChange] = value; } } - return this.emitChangeEvent() + return this.emitChangeEvent(); } - observeKeyPath (keyPath, options, callback) { - callback(this.get(keyPath)) - return this.onDidChangeKeyPath(keyPath, event => callback(event.newValue)) + observeKeyPath(keyPath, options, callback) { + callback(this.get(keyPath)); + return this.onDidChangeKeyPath(keyPath, event => callback(event.newValue)); } - onDidChangeKeyPath (keyPath, callback) { - let oldValue = this.get(keyPath) + onDidChangeKeyPath(keyPath, callback) { + let oldValue = this.get(keyPath); return this.emitter.on('did-change', () => { - const newValue = this.get(keyPath) + const newValue = this.get(keyPath); if (!_.isEqual(oldValue, newValue)) { - const event = {oldValue, newValue} - oldValue = newValue - return callback(event) + const event = { oldValue, newValue }; + oldValue = newValue; + return callback(event); } - }) + }); } - isSubKeyPath (keyPath, subKeyPath) { - if ((keyPath == null) || (subKeyPath == null)) { return false } - const pathSubTokens = splitKeyPath(subKeyPath) - const pathTokens = splitKeyPath(keyPath).slice(0, pathSubTokens.length) - return _.isEqual(pathTokens, pathSubTokens) + isSubKeyPath(keyPath, subKeyPath) { + if (keyPath == null || subKeyPath == null) { + return false; + } + const pathSubTokens = splitKeyPath(subKeyPath); + const pathTokens = splitKeyPath(keyPath).slice(0, pathSubTokens.length); + return _.isEqual(pathTokens, pathSubTokens); } - setRawDefault (keyPath, value) { - setValueAtKeyPath(this.defaultSettings, keyPath, value) - return this.emitChangeEvent() + setRawDefault(keyPath, value) { + setValueAtKeyPath(this.defaultSettings, keyPath, value); + return this.emitChangeEvent(); } - setDefaults (keyPath, defaults) { - if ((defaults != null) && isPlainObject(defaults)) { - const keys = splitKeyPath(keyPath) + setDefaults(keyPath, defaults) { + if (defaults != null && isPlainObject(defaults)) { + const keys = splitKeyPath(keyPath); this.transact(() => { - const result = [] + const result = []; for (let key in defaults) { - const childValue = defaults[key] - if (!defaults.hasOwnProperty(key)) { continue } - result.push(this.setDefaults(keys.concat([key]).join('.'), childValue)) + const childValue = defaults[key]; + if (!defaults.hasOwnProperty(key)) { + continue; + } + result.push( + this.setDefaults(keys.concat([key]).join('.'), childValue) + ); } - return result - }) + return result; + }); } else { try { - defaults = this.makeValueConformToSchema(keyPath, defaults) - this.setRawDefault(keyPath, defaults) + defaults = this.makeValueConformToSchema(keyPath, defaults); + this.setRawDefault(keyPath, defaults); } catch (e) { - console.warn(`'${keyPath}' could not set the default. Attempted default: ${JSON.stringify(defaults)}; Schema: ${JSON.stringify(this.getSchema(keyPath))}`) + console.warn( + `'${keyPath}' could not set the default. Attempted default: ${JSON.stringify( + defaults + )}; Schema: ${JSON.stringify(this.getSchema(keyPath))}` + ); } } } - deepClone (object) { + deepClone(object) { if (object instanceof Color) { - return object.clone() + return object.clone(); } else if (Array.isArray(object)) { - return object.map(value => this.deepClone(value)) + return object.map(value => this.deepClone(value)); } else if (isPlainObject(object)) { - return _.mapObject(object, (key, value) => [key, this.deepClone(value)]) + return _.mapObject(object, (key, value) => [key, this.deepClone(value)]); } else { - return object + return object; } } - deepDefaults (target) { - let result = target - let i = 0 + deepDefaults(target) { + let result = target; + let i = 0; while (++i < arguments.length) { - const object = arguments[i] + const object = arguments[i]; if (isPlainObject(result) && isPlainObject(object)) { for (let key of Object.keys(object)) { - result[key] = this.deepDefaults(result[key], object[key]) + result[key] = this.deepDefaults(result[key], object[key]); } } else { - if ((result == null)) { - result = this.deepClone(object) + if (result == null) { + result = this.deepClone(object); } } } - return result + return result; } // `schema` will look something like this @@ -1146,156 +1230,198 @@ class Config { // '.source.js': // default: 'omg' // ``` - setScopedDefaultsFromSchema (keyPath, schema) { - if ((schema.scopes != null) && isPlainObject(schema.scopes)) { - const scopedDefaults = {} + setScopedDefaultsFromSchema(keyPath, schema) { + if (schema.scopes != null && isPlainObject(schema.scopes)) { + const scopedDefaults = {}; for (let scope in schema.scopes) { - const scopeSchema = schema.scopes[scope] - if (!scopeSchema.hasOwnProperty('default')) { continue } - scopedDefaults[scope] = {} - setValueAtKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default) + const scopeSchema = schema.scopes[scope]; + if (!scopeSchema.hasOwnProperty('default')) { + continue; + } + scopedDefaults[scope] = {}; + setValueAtKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default); } - this.scopedSettingsStore.addProperties('schema-default', scopedDefaults) + this.scopedSettingsStore.addProperties('schema-default', scopedDefaults); } - if ((schema.type === 'object') && (schema.properties != null) && isPlainObject(schema.properties)) { - const keys = splitKeyPath(keyPath) + if ( + schema.type === 'object' && + schema.properties != null && + isPlainObject(schema.properties) + ) { + const keys = splitKeyPath(keyPath); for (let key in schema.properties) { - const childValue = schema.properties[key] - if (!schema.properties.hasOwnProperty(key)) { continue } - this.setScopedDefaultsFromSchema(keys.concat([key]).join('.'), childValue) + const childValue = schema.properties[key]; + if (!schema.properties.hasOwnProperty(key)) { + continue; + } + this.setScopedDefaultsFromSchema( + keys.concat([key]).join('.'), + childValue + ); } } } - extractDefaultsFromSchema (schema) { + extractDefaultsFromSchema(schema) { if (schema.default != null) { - return schema.default - } else if ((schema.type === 'object') && (schema.properties != null) && isPlainObject(schema.properties)) { - const defaults = {} - const properties = schema.properties || {} - for (let key in properties) { const value = properties[key]; defaults[key] = this.extractDefaultsFromSchema(value) } - return defaults + return schema.default; + } else if ( + schema.type === 'object' && + schema.properties != null && + isPlainObject(schema.properties) + ) { + const defaults = {}; + const properties = schema.properties || {}; + for (let key in properties) { + const value = properties[key]; + defaults[key] = this.extractDefaultsFromSchema(value); + } + return defaults; } } - makeValueConformToSchema (keyPath, value, options) { + makeValueConformToSchema(keyPath, value, options) { if (options != null ? options.suppressException : undefined) { try { - return this.makeValueConformToSchema(keyPath, value) + return this.makeValueConformToSchema(keyPath, value); } catch (e) { - return undefined + return undefined; } } else { - let schema + let schema; if ((schema = this.getSchema(keyPath)) == null) { - if (schema === false) { throw new Error(`Illegal key path ${keyPath}`) } + if (schema === false) { + throw new Error(`Illegal key path ${keyPath}`); + } } - return this.constructor.executeSchemaEnforcers(keyPath, value, schema) + return this.constructor.executeSchemaEnforcers(keyPath, value, schema); } } // When the schema is changed / added, there may be values set in the config // that do not conform to the schema. This will reset make them conform. - resetSettingsForSchemaChange (source) { - if (source == null) { source = this.mainSource } + resetSettingsForSchemaChange(source) { + if (source == null) { + source = this.mainSource; + } return this.transact(() => { - this.settings = this.makeValueConformToSchema(null, this.settings, {suppressException: true}) - const selectorsAndSettings = this.scopedSettingsStore.propertiesForSource(source) - this.scopedSettingsStore.removePropertiesForSource(source) + this.settings = this.makeValueConformToSchema(null, this.settings, { + suppressException: true + }); + const selectorsAndSettings = this.scopedSettingsStore.propertiesForSource( + source + ); + this.scopedSettingsStore.removePropertiesForSource(source); for (let scopeSelector in selectorsAndSettings) { - let settings = selectorsAndSettings[scopeSelector] - settings = this.makeValueConformToSchema(null, settings, {suppressException: true}) - this.setRawScopedValue(null, settings, source, scopeSelector) + let settings = selectorsAndSettings[scopeSelector]; + settings = this.makeValueConformToSchema(null, settings, { + suppressException: true + }); + this.setRawScopedValue(null, settings, source, scopeSelector); } - }) + }); } /* Section: Private Scoped Settings */ - priorityForSource (source) { + priorityForSource(source) { switch (source) { case this.mainSource: - return 1000 + return 1000; case this.projectFile: - return 2000 + return 2000; default: - return 0 + return 0; } } - emitChangeEvent () { - if (this.transactDepth <= 0) { return this.emitter.emit('did-change') } + emitChangeEvent() { + if (this.transactDepth <= 0) { + return this.emitter.emit('did-change'); + } } - resetScopedSettings (newScopedSettings, options = {}) { - const source = options.source == null ? this.mainSource : options.source - const priority = this.priorityForSource(source) - this.scopedSettingsStore.removePropertiesForSource(source) + resetScopedSettings(newScopedSettings, options = {}) { + const source = options.source == null ? this.mainSource : options.source; + const priority = this.priorityForSource(source); + this.scopedSettingsStore.removePropertiesForSource(source); for (let scopeSelector in newScopedSettings) { - let settings = newScopedSettings[scopeSelector] - settings = this.makeValueConformToSchema(null, settings, {suppressException: true}) - const validatedSettings = {} - validatedSettings[scopeSelector] = withoutEmptyObjects(settings) - if (validatedSettings[scopeSelector] != null) { this.scopedSettingsStore.addProperties(source, validatedSettings, {priority}) } + let settings = newScopedSettings[scopeSelector]; + settings = this.makeValueConformToSchema(null, settings, { + suppressException: true + }); + const validatedSettings = {}; + validatedSettings[scopeSelector] = withoutEmptyObjects(settings); + if (validatedSettings[scopeSelector] != null) { + this.scopedSettingsStore.addProperties(source, validatedSettings, { + priority + }); + } } - return this.emitChangeEvent() + return this.emitChangeEvent(); } - setRawScopedValue (keyPath, value, source, selector, options) { + setRawScopedValue(keyPath, value, source, selector, options) { if (keyPath != null) { - const newValue = {} - setValueAtKeyPath(newValue, keyPath, value) - value = newValue + const newValue = {}; + setValueAtKeyPath(newValue, keyPath, value); + value = newValue; } - const settingsBySelector = {} - settingsBySelector[selector] = value - this.scopedSettingsStore.addProperties(source, settingsBySelector, {priority: this.priorityForSource(source)}) - return this.emitChangeEvent() + const settingsBySelector = {}; + settingsBySelector[selector] = value; + this.scopedSettingsStore.addProperties(source, settingsBySelector, { + priority: this.priorityForSource(source) + }); + return this.emitChangeEvent(); } - getRawScopedValue (scopeDescriptor, keyPath, options) { - scopeDescriptor = ScopeDescriptor.fromObject(scopeDescriptor) + getRawScopedValue(scopeDescriptor, keyPath, options) { + scopeDescriptor = ScopeDescriptor.fromObject(scopeDescriptor); const result = this.scopedSettingsStore.getPropertyValue( scopeDescriptor.getScopeChain(), keyPath, options - ) + ); - const legacyScopeDescriptor = this.getLegacyScopeDescriptorForNewScopeDescriptor(scopeDescriptor) + const legacyScopeDescriptor = this.getLegacyScopeDescriptorForNewScopeDescriptor( + scopeDescriptor + ); if (result != null) { - return result + return result; } else if (legacyScopeDescriptor) { return this.scopedSettingsStore.getPropertyValue( legacyScopeDescriptor.getScopeChain(), keyPath, options - ) + ); } } - observeScopedKeyPath (scope, keyPath, callback) { - callback(this.get(keyPath, {scope})) - return this.onDidChangeScopedKeyPath(scope, keyPath, event => callback(event.newValue)) + observeScopedKeyPath(scope, keyPath, callback) { + callback(this.get(keyPath, { scope })); + return this.onDidChangeScopedKeyPath(scope, keyPath, event => + callback(event.newValue) + ); } - onDidChangeScopedKeyPath (scope, keyPath, callback) { - let oldValue = this.get(keyPath, {scope}) + onDidChangeScopedKeyPath(scope, keyPath, callback) { + let oldValue = this.get(keyPath, { scope }); return this.emitter.on('did-change', () => { - const newValue = this.get(keyPath, {scope}) + const newValue = this.get(keyPath, { scope }); if (!_.isEqual(oldValue, newValue)) { - const event = {oldValue, newValue} - oldValue = newValue - callback(event) + const event = { oldValue, newValue }; + oldValue = newValue; + callback(event); } - }) + }); } -}; +} // Base schema enforcers. These will coerce raw input into the specified type, // and will throw an error when the value cannot be coerced. Throwing the error @@ -1306,197 +1432,284 @@ class Config { // order of specification. Then the `*` enforcers will be run, in order of // specification. Config.addSchemaEnforcers({ - 'any': { - coerce (keyPath, value, schema) { - return value + any: { + coerce(keyPath, value, schema) { + return value; } }, - 'integer': { - coerce (keyPath, value, schema) { - value = parseInt(value) - if (isNaN(value) || !isFinite(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into an int`) } - return value + integer: { + coerce(keyPath, value, schema) { + value = parseInt(value); + if (isNaN(value) || !isFinite(value)) { + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} cannot be coerced into an int` + ); + } + return value; } }, - 'number': { - coerce (keyPath, value, schema) { - value = parseFloat(value) - if (isNaN(value) || !isFinite(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into a number`) } - return value + number: { + coerce(keyPath, value, schema) { + value = parseFloat(value); + if (isNaN(value) || !isFinite(value)) { + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} cannot be coerced into a number` + ); + } + return value; } }, - 'boolean': { - coerce (keyPath, value, schema) { + boolean: { + coerce(keyPath, value, schema) { switch (typeof value) { case 'string': if (value.toLowerCase() === 'true') { - return true + return true; } else if (value.toLowerCase() === 'false') { - return false + return false; } else { - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a boolean or the string 'true' or 'false'`) + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} must be a boolean or the string 'true' or 'false'` + ); } case 'boolean': - return value + return value; default: - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a boolean or the string 'true' or 'false'`) + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} must be a boolean or the string 'true' or 'false'` + ); } } }, - 'string': { - validate (keyPath, value, schema) { + string: { + validate(keyPath, value, schema) { if (typeof value !== 'string') { - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be a string`) + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} must be a string` + ); } - return value + return value; }, - validateMaximumLength (keyPath, value, schema) { - if ((typeof schema.maximumLength === 'number') && (value.length > schema.maximumLength)) { - return value.slice(0, schema.maximumLength) + validateMaximumLength(keyPath, value, schema) { + if ( + typeof schema.maximumLength === 'number' && + value.length > schema.maximumLength + ) { + return value.slice(0, schema.maximumLength); } else { - return value + return value; } } }, - 'null': { + null: { // null sort of isnt supported. It will just unset in this case - coerce (keyPath, value, schema) { - if (![undefined, null].includes(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be null`) } - return value + coerce(keyPath, value, schema) { + if (![undefined, null].includes(value)) { + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} must be null` + ); + } + return value; } }, - 'object': { - coerce (keyPath, value, schema) { - if (!isPlainObject(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be an object`) } - if (schema.properties == null) { return value } + object: { + coerce(keyPath, value, schema) { + if (!isPlainObject(value)) { + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} must be an object` + ); + } + if (schema.properties == null) { + return value; + } - let defaultChildSchema = null - let allowsAdditionalProperties = true + let defaultChildSchema = null; + let allowsAdditionalProperties = true; if (isPlainObject(schema.additionalProperties)) { - defaultChildSchema = schema.additionalProperties + defaultChildSchema = schema.additionalProperties; } if (schema.additionalProperties === false) { - allowsAdditionalProperties = false + allowsAdditionalProperties = false; } - const newValue = {} + const newValue = {}; for (let prop in value) { - const propValue = value[prop] - const childSchema = schema.properties[prop] != null ? schema.properties[prop] : defaultChildSchema + const propValue = value[prop]; + const childSchema = + schema.properties[prop] != null + ? schema.properties[prop] + : defaultChildSchema; if (childSchema != null) { try { - newValue[prop] = this.executeSchemaEnforcers(pushKeyPath(keyPath, prop), propValue, childSchema) + newValue[prop] = this.executeSchemaEnforcers( + pushKeyPath(keyPath, prop), + propValue, + childSchema + ); } catch (error) { - console.warn(`Error setting item in object: ${error.message}`) + console.warn(`Error setting item in object: ${error.message}`); } } else if (allowsAdditionalProperties) { // Just pass through un-schema'd values - newValue[prop] = propValue + newValue[prop] = propValue; } else { - console.warn(`Illegal object key: ${keyPath}.${prop}`) + console.warn(`Illegal object key: ${keyPath}.${prop}`); } } - return newValue + return newValue; } }, - 'array': { - coerce (keyPath, value, schema) { - if (!Array.isArray(value)) { throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} must be an array`) } - const itemSchema = schema.items + array: { + coerce(keyPath, value, schema) { + if (!Array.isArray(value)) { + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} must be an array` + ); + } + const itemSchema = schema.items; if (itemSchema != null) { - const newValue = [] + const newValue = []; for (let item of value) { try { - newValue.push(this.executeSchemaEnforcers(keyPath, item, itemSchema)) + newValue.push( + this.executeSchemaEnforcers(keyPath, item, itemSchema) + ); } catch (error) { - console.warn(`Error setting item in array: ${error.message}`) + console.warn(`Error setting item in array: ${error.message}`); } } - return newValue + return newValue; } else { - return value + return value; } } }, - 'color': { - coerce (keyPath, value, schema) { - const color = Color.parse(value) + color: { + coerce(keyPath, value, schema) { + const color = Color.parse(value); if (color == null) { - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} cannot be coerced into a color`) + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} cannot be coerced into a color` + ); } - return color + return color; } }, '*': { - coerceMinimumAndMaximum (keyPath, value, schema) { - if (typeof value !== 'number') { return value } - if ((schema.minimum != null) && (typeof schema.minimum === 'number')) { - value = Math.max(value, schema.minimum) + coerceMinimumAndMaximum(keyPath, value, schema) { + if (typeof value !== 'number') { + return value; } - if ((schema.maximum != null) && (typeof schema.maximum === 'number')) { - value = Math.min(value, schema.maximum) + if (schema.minimum != null && typeof schema.minimum === 'number') { + value = Math.max(value, schema.minimum); } - return value + if (schema.maximum != null && typeof schema.maximum === 'number') { + value = Math.min(value, schema.maximum); + } + return value; }, - validateEnum (keyPath, value, schema) { - let possibleValues = schema.enum + validateEnum(keyPath, value, schema) { + let possibleValues = schema.enum; if (Array.isArray(possibleValues)) { possibleValues = possibleValues.map(value => { - if (value.hasOwnProperty('value')) { return value.value } else { return value } - }) + if (value.hasOwnProperty('value')) { + return value.value; + } else { + return value; + } + }); } - if ((possibleValues == null) || !Array.isArray(possibleValues) || !possibleValues.length) { return value } + if ( + possibleValues == null || + !Array.isArray(possibleValues) || + !possibleValues.length + ) { + return value; + } for (let possibleValue of possibleValues) { // Using `isEqual` for possibility of placing enums on array and object schemas - if (_.isEqual(possibleValue, value)) { return value } + if (_.isEqual(possibleValue, value)) { + return value; + } } - throw new Error(`Validation failed at ${keyPath}, ${JSON.stringify(value)} is not one of ${JSON.stringify(possibleValues)}`) + throw new Error( + `Validation failed at ${keyPath}, ${JSON.stringify( + value + )} is not one of ${JSON.stringify(possibleValues)}` + ); } } -}) +}); -let isPlainObject = value => _.isObject(value) && !Array.isArray(value) && !_.isFunction(value) && !_.isString(value) && !(value instanceof Color) +let isPlainObject = value => + _.isObject(value) && + !Array.isArray(value) && + !_.isFunction(value) && + !_.isString(value) && + !(value instanceof Color); let sortObject = value => { - if (!isPlainObject(value)) { return value } - const result = {} - for (let key of Object.keys(value).sort()) { - result[key] = sortObject(value[key]) + if (!isPlainObject(value)) { + return value; } - return result -} + const result = {}; + for (let key of Object.keys(value).sort()) { + result[key] = sortObject(value[key]); + } + return result; +}; -const withoutEmptyObjects = (object) => { - let resultObject +const withoutEmptyObjects = object => { + let resultObject; if (isPlainObject(object)) { for (let key in object) { - const value = object[key] - const newValue = withoutEmptyObjects(value) + const value = object[key]; + const newValue = withoutEmptyObjects(value); if (newValue != null) { - if (resultObject == null) { resultObject = {} } - resultObject[key] = newValue + if (resultObject == null) { + resultObject = {}; + } + resultObject[key] = newValue; } } } else { - resultObject = object + resultObject = object; } - return resultObject -} + return resultObject; +}; -module.exports = Config +module.exports = Config; diff --git a/src/core-uri-handlers.js b/src/core-uri-handlers.js index e7f845609..d09a9af98 100644 --- a/src/core-uri-handlers.js +++ b/src/core-uri-handlers.js @@ -1,54 +1,55 @@ -const fs = require('fs-plus') +const fs = require('fs-plus'); // Converts a query string parameter for a line or column number // to a zero-based line or column number for the Atom API. -function getLineColNumber (numStr) { - const num = parseInt(numStr || 0, 10) - return Math.max(num - 1, 0) +function getLineColNumber(numStr) { + const num = parseInt(numStr || 0, 10); + return Math.max(num - 1, 0); } -function openFile (atom, {query}) { - const {filename, line, column} = query +function openFile(atom, { query }) { + const { filename, line, column } = query; atom.workspace.open(filename, { initialLine: getLineColNumber(line), initialColumn: getLineColNumber(column), searchAllPanes: true - }) + }); } -function windowShouldOpenFile ({query}) { - const {filename} = query - const stat = fs.statSyncNoException(filename) +function windowShouldOpenFile({ query }) { + const { filename } = query; + const stat = fs.statSyncNoException(filename); - return win => win.containsLocation({ - pathToOpen: filename, - exists: Boolean(stat), - isFile: stat.isFile(), - isDirectory: stat.isDirectory() - }) + return win => + win.containsLocation({ + pathToOpen: filename, + exists: Boolean(stat), + isFile: stat.isFile(), + isDirectory: stat.isDirectory() + }); } const ROUTER = { '/open/file': { handler: openFile, getWindowPredicate: windowShouldOpenFile } -} +}; module.exports = { - create (atomEnv) { - return function coreURIHandler (parsed) { - const config = ROUTER[parsed.pathname] + create(atomEnv) { + return function coreURIHandler(parsed) { + const config = ROUTER[parsed.pathname]; if (config) { - config.handler(atomEnv, parsed) + config.handler(atomEnv, parsed); } - } + }; }, - windowPredicate (parsed) { - const config = ROUTER[parsed.pathname] + windowPredicate(parsed) { + const config = ROUTER[parsed.pathname]; if (config && config.getWindowPredicate) { - return config.getWindowPredicate(parsed) + return config.getWindowPredicate(parsed); } else { - return () => true + return () => true; } } -} +}; diff --git a/src/crash-reporter-start.js b/src/crash-reporter-start.js index 035a94e3d..21e68fadc 100644 --- a/src/crash-reporter-start.js +++ b/src/crash-reporter-start.js @@ -1,10 +1,10 @@ -module.exports = function (extra) { - const {crashReporter} = require('electron') +module.exports = function(extra) { + const { crashReporter } = require('electron'); crashReporter.start({ productName: 'Atom', companyName: 'GitHub', submitURL: 'https://crashreporter.atom.io', uploadToServer: false, extra: extra - }) -} + }); +}; diff --git a/src/cursor.js b/src/cursor.js index 8e367849e..334c5b0d8 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -1,27 +1,26 @@ -const {Point, Range} = require('text-buffer') -const {Emitter} = require('event-kit') -const _ = require('underscore-plus') -const Model = require('./model') +const { Point, Range } = require('text-buffer'); +const { Emitter } = require('event-kit'); +const _ = require('underscore-plus'); +const Model = require('./model'); -const EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g +const EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g; // Extended: The `Cursor` class represents the little blinking line identifying // where text can be inserted. // // Cursors belong to {TextEditor}s and have some metadata attached in the form // of a {DisplayMarker}. -module.exports = -class Cursor extends Model { +module.exports = class Cursor extends Model { // Instantiated by a {TextEditor} - constructor (params) { - super(params) - this.editor = params.editor - this.marker = params.marker - this.emitter = new Emitter() + constructor(params) { + super(params); + this.editor = params.editor; + this.marker = params.marker; + this.emitter = new Emitter(); } - destroy () { - this.marker.destroy() + destroy() { + this.marker.destroy(); } /* @@ -40,8 +39,8 @@ class Cursor extends Model { // * `cursor` {Cursor} that triggered the event // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangePosition (callback) { - return this.emitter.on('did-change-position', callback) + onDidChangePosition(callback) { + return this.emitter.on('did-change-position', callback); } // Public: Calls your `callback` when the cursor is destroyed @@ -49,8 +48,8 @@ class Cursor extends Model { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } /* @@ -63,15 +62,15 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `autoscroll` A Boolean which, if `true`, scrolls the {TextEditor} to wherever // the cursor moves to. - setScreenPosition (screenPosition, options = {}) { + setScreenPosition(screenPosition, options = {}) { this.changePosition(options, () => { - this.marker.setHeadScreenPosition(screenPosition, options) - }) + this.marker.setHeadScreenPosition(screenPosition, options); + }); } // Public: Returns the screen position of the cursor as a {Point}. - getScreenPosition () { - return this.marker.getHeadScreenPosition() + getScreenPosition() { + return this.marker.getHeadScreenPosition(); } // Public: Moves a cursor to a given buffer position. @@ -81,51 +80,53 @@ class Cursor extends Model { // * `autoscroll` {Boolean} indicating whether to autoscroll to the new // position. Defaults to `true` if this is the most recently added cursor, // `false` otherwise. - setBufferPosition (bufferPosition, options = {}) { + setBufferPosition(bufferPosition, options = {}) { this.changePosition(options, () => { - this.marker.setHeadBufferPosition(bufferPosition, options) - }) + this.marker.setHeadBufferPosition(bufferPosition, options); + }); } // Public: Returns the current buffer position as an Array. - getBufferPosition () { - return this.marker.getHeadBufferPosition() + getBufferPosition() { + return this.marker.getHeadBufferPosition(); } // Public: Returns the cursor's current screen row. - getScreenRow () { - return this.getScreenPosition().row + getScreenRow() { + return this.getScreenPosition().row; } // Public: Returns the cursor's current screen column. - getScreenColumn () { - return this.getScreenPosition().column + getScreenColumn() { + return this.getScreenPosition().column; } // Public: Retrieves the cursor's current buffer row. - getBufferRow () { - return this.getBufferPosition().row + getBufferRow() { + return this.getBufferPosition().row; } // Public: Returns the cursor's current buffer column. - getBufferColumn () { - return this.getBufferPosition().column + getBufferColumn() { + return this.getBufferPosition().column; } // Public: Returns the cursor's current buffer row of text excluding its line // ending. - getCurrentBufferLine () { - return this.editor.lineTextForBufferRow(this.getBufferRow()) + getCurrentBufferLine() { + return this.editor.lineTextForBufferRow(this.getBufferRow()); } // Public: Returns whether the cursor is at the start of a line. - isAtBeginningOfLine () { - return this.getBufferPosition().column === 0 + isAtBeginningOfLine() { + return this.getBufferPosition().column === 0; } // Public: Returns whether the cursor is on the line return character. - isAtEndOfLine () { - return this.getBufferPosition().isEqual(this.getCurrentLineBufferRange().end) + isAtEndOfLine() { + return this.getBufferPosition().isEqual( + this.getCurrentLineBufferRange().end + ); } /* @@ -134,7 +135,9 @@ class Cursor extends Model { // Public: Returns the underlying {DisplayMarker} for the cursor. // Useful with overlay {Decoration}s. - getMarker () { return this.marker } + getMarker() { + return this.marker; + } // Public: Identifies if the cursor is surrounded by whitespace. // @@ -142,10 +145,10 @@ class Cursor extends Model { // cursor are both whitespace. // // Returns a {Boolean}. - isSurroundedByWhitespace () { - const {row, column} = this.getBufferPosition() - const range = [[row, column - 1], [row, column + 1]] - return /^\s+$/.test(this.editor.getTextInBufferRange(range)) + isSurroundedByWhitespace() { + const { row, column } = this.getBufferPosition(); + const range = [[row, column - 1], [row, column + 1]]; + return /^\s+$/.test(this.editor.getTextInBufferRange(range)); } // Public: Returns whether the cursor is currently between a word and non-word @@ -156,16 +159,19 @@ class Cursor extends Model { // whitespace. // // Returns a Boolean. - isBetweenWordAndNonWord () { - if (this.isAtBeginningOfLine() || this.isAtEndOfLine()) return false + isBetweenWordAndNonWord() { + if (this.isAtBeginningOfLine() || this.isAtEndOfLine()) return false; - const {row, column} = this.getBufferPosition() - const range = [[row, column - 1], [row, column + 1]] - const text = this.editor.getTextInBufferRange(range) - if (/\s/.test(text[0]) || /\s/.test(text[1])) return false + const { row, column } = this.getBufferPosition(); + const range = [[row, column - 1], [row, column + 1]]; + const text = this.editor.getTextInBufferRange(range); + if (/\s/.test(text[0]) || /\s/.test(text[1])) return false; - const nonWordCharacters = this.getNonWordCharacters() - return nonWordCharacters.includes(text[0]) !== nonWordCharacters.includes(text[1]) + const nonWordCharacters = this.getNonWordCharacters(); + return ( + nonWordCharacters.includes(text[0]) !== + nonWordCharacters.includes(text[1]) + ); } // Public: Returns whether this cursor is between a word's start and end. @@ -175,47 +181,53 @@ class Cursor extends Model { // (default: {::wordRegExp}). // // Returns a {Boolean} - isInsideWord (options) { - const {row, column} = this.getBufferPosition() - const range = [[row, column], [row, Infinity]] - const text = this.editor.getTextInBufferRange(range) - return text.search((options && options.wordRegex) || this.wordRegExp()) === 0 + isInsideWord(options) { + const { row, column } = this.getBufferPosition(); + const range = [[row, column], [row, Infinity]]; + const text = this.editor.getTextInBufferRange(range); + return ( + text.search((options && options.wordRegex) || this.wordRegExp()) === 0 + ); } // Public: Returns the indentation level of the current line. - getIndentLevel () { + getIndentLevel() { if (this.editor.getSoftTabs()) { - return this.getBufferColumn() / this.editor.getTabLength() + return this.getBufferColumn() / this.editor.getTabLength(); } else { - return this.getBufferColumn() + return this.getBufferColumn(); } } // Public: Retrieves the scope descriptor for the cursor's current position. // // Returns a {ScopeDescriptor} - getScopeDescriptor () { - return this.editor.scopeDescriptorForBufferPosition(this.getBufferPosition()) + getScopeDescriptor() { + return this.editor.scopeDescriptorForBufferPosition( + this.getBufferPosition() + ); } // Public: Retrieves the syntax tree scope descriptor for the cursor's current position. // // Returns a {ScopeDescriptor} - getSyntaxTreeScopeDescriptor () { - return this.editor.syntaxTreeScopeDescriptorForBufferPosition(this.getBufferPosition()) + getSyntaxTreeScopeDescriptor() { + return this.editor.syntaxTreeScopeDescriptorForBufferPosition( + this.getBufferPosition() + ); } // Public: Returns true if this cursor has no non-whitespace characters before // its current position. - hasPrecedingCharactersOnLine () { - const bufferPosition = this.getBufferPosition() - const line = this.editor.lineTextForBufferRow(bufferPosition.row) - const firstCharacterColumn = line.search(/\S/) + hasPrecedingCharactersOnLine() { + const bufferPosition = this.getBufferPosition(); + const line = this.editor.lineTextForBufferRow(bufferPosition.row); + const firstCharacterColumn = line.search(/\S/); if (firstCharacterColumn === -1) { - return false + return false; } else { - return bufferPosition.column > firstCharacterColumn + return bufferPosition.column > firstCharacterColumn; } } @@ -224,8 +236,8 @@ class Cursor extends Model { // "Last" is defined as the most recently added cursor. // // Returns a {Boolean}. - isLastCursor () { - return this === this.editor.getLastCursor() + isLastCursor() { + return this === this.editor.getLastCursor(); } /* @@ -238,18 +250,21 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `moveToEndOfSelection` if true, move to the left of the selection if a // selection exists. - moveUp (rowCount = 1, {moveToEndOfSelection} = {}) { - let row, column - const range = this.marker.getScreenRange() + moveUp(rowCount = 1, { moveToEndOfSelection } = {}) { + let row, column; + const range = this.marker.getScreenRange(); if (moveToEndOfSelection && !range.isEmpty()) { - ({row, column} = range.start) + ({ row, column } = range.start); } else { - ({row, column} = this.getScreenPosition()) + ({ row, column } = this.getScreenPosition()); } - if (this.goalColumn != null) column = this.goalColumn - this.setScreenPosition({row: row - rowCount, column}, {skipSoftWrapIndentation: true}) - this.goalColumn = column + if (this.goalColumn != null) column = this.goalColumn; + this.setScreenPosition( + { row: row - rowCount, column }, + { skipSoftWrapIndentation: true } + ); + this.goalColumn = column; } // Public: Moves the cursor down one screen row. @@ -258,18 +273,21 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `moveToEndOfSelection` if true, move to the left of the selection if a // selection exists. - moveDown (rowCount = 1, {moveToEndOfSelection} = {}) { - let row, column - const range = this.marker.getScreenRange() + moveDown(rowCount = 1, { moveToEndOfSelection } = {}) { + let row, column; + const range = this.marker.getScreenRange(); if (moveToEndOfSelection && !range.isEmpty()) { - ({row, column} = range.end) + ({ row, column } = range.end); } else { - ({row, column} = this.getScreenPosition()) + ({ row, column } = this.getScreenPosition()); } - if (this.goalColumn != null) column = this.goalColumn - this.setScreenPosition({row: row + rowCount, column}, {skipSoftWrapIndentation: true}) - this.goalColumn = column + if (this.goalColumn != null) column = this.goalColumn; + this.setScreenPosition( + { row: row + rowCount, column }, + { skipSoftWrapIndentation: true } + ); + this.goalColumn = column; } // Public: Moves the cursor left one screen column. @@ -278,21 +296,21 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `moveToEndOfSelection` if true, move to the left of the selection if a // selection exists. - moveLeft (columnCount = 1, {moveToEndOfSelection} = {}) { - const range = this.marker.getScreenRange() + moveLeft(columnCount = 1, { moveToEndOfSelection } = {}) { + const range = this.marker.getScreenRange(); if (moveToEndOfSelection && !range.isEmpty()) { - this.setScreenPosition(range.start) + this.setScreenPosition(range.start); } else { - let {row, column} = this.getScreenPosition() + let { row, column } = this.getScreenPosition(); while (columnCount > column && row > 0) { - columnCount -= column - column = this.editor.lineLengthForScreenRow(--row) - columnCount-- // subtract 1 for the row move + columnCount -= column; + column = this.editor.lineLengthForScreenRow(--row); + columnCount--; // subtract 1 for the row move } - column = column - columnCount - this.setScreenPosition({row, column}, {clipDirection: 'backward'}) + column = column - columnCount; + this.setScreenPosition({ row, column }, { clipDirection: 'backward' }); } } @@ -302,152 +320,168 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `moveToEndOfSelection` if true, move to the right of the selection if a // selection exists. - moveRight (columnCount = 1, {moveToEndOfSelection} = {}) { - const range = this.marker.getScreenRange() + moveRight(columnCount = 1, { moveToEndOfSelection } = {}) { + const range = this.marker.getScreenRange(); if (moveToEndOfSelection && !range.isEmpty()) { - this.setScreenPosition(range.end) + this.setScreenPosition(range.end); } else { - let {row, column} = this.getScreenPosition() - const maxLines = this.editor.getScreenLineCount() - let rowLength = this.editor.lineLengthForScreenRow(row) - let columnsRemainingInLine = rowLength - column + let { row, column } = this.getScreenPosition(); + const maxLines = this.editor.getScreenLineCount(); + let rowLength = this.editor.lineLengthForScreenRow(row); + let columnsRemainingInLine = rowLength - column; while (columnCount > columnsRemainingInLine && row < maxLines - 1) { - columnCount -= columnsRemainingInLine - columnCount-- // subtract 1 for the row move + columnCount -= columnsRemainingInLine; + columnCount--; // subtract 1 for the row move - column = 0 - rowLength = this.editor.lineLengthForScreenRow(++row) - columnsRemainingInLine = rowLength + column = 0; + rowLength = this.editor.lineLengthForScreenRow(++row); + columnsRemainingInLine = rowLength; } - column = column + columnCount - this.setScreenPosition({row, column}, {clipDirection: 'forward'}) + column = column + columnCount; + this.setScreenPosition({ row, column }, { clipDirection: 'forward' }); } } // Public: Moves the cursor to the top of the buffer. - moveToTop () { - this.setBufferPosition([0, 0]) + moveToTop() { + this.setBufferPosition([0, 0]); } // Public: Moves the cursor to the bottom of the buffer. - moveToBottom () { - const column = this.goalColumn - this.setBufferPosition(this.editor.getEofBufferPosition()) - this.goalColumn = column + moveToBottom() { + const column = this.goalColumn; + this.setBufferPosition(this.editor.getEofBufferPosition()); + this.goalColumn = column; } // Public: Moves the cursor to the beginning of the line. - moveToBeginningOfScreenLine () { - this.setScreenPosition([this.getScreenRow(), 0]) + moveToBeginningOfScreenLine() { + this.setScreenPosition([this.getScreenRow(), 0]); } // Public: Moves the cursor to the beginning of the buffer line. - moveToBeginningOfLine () { - this.setBufferPosition([this.getBufferRow(), 0]) + moveToBeginningOfLine() { + this.setBufferPosition([this.getBufferRow(), 0]); } // Public: Moves the cursor to the beginning of the first character in the // line. - moveToFirstCharacterOfLine () { - let targetBufferColumn - const screenRow = this.getScreenRow() - const screenLineStart = this.editor.clipScreenPosition([screenRow, 0], {skipSoftWrapIndentation: true}) - const screenLineEnd = [screenRow, Infinity] - const screenLineBufferRange = this.editor.bufferRangeForScreenRange([screenLineStart, screenLineEnd]) + moveToFirstCharacterOfLine() { + let targetBufferColumn; + const screenRow = this.getScreenRow(); + const screenLineStart = this.editor.clipScreenPosition([screenRow, 0], { + skipSoftWrapIndentation: true + }); + const screenLineEnd = [screenRow, Infinity]; + const screenLineBufferRange = this.editor.bufferRangeForScreenRange([ + screenLineStart, + screenLineEnd + ]); - let firstCharacterColumn = null - this.editor.scanInBufferRange(/\S/, screenLineBufferRange, ({range, stop}) => { - firstCharacterColumn = range.start.column - stop() - }) + let firstCharacterColumn = null; + this.editor.scanInBufferRange( + /\S/, + screenLineBufferRange, + ({ range, stop }) => { + firstCharacterColumn = range.start.column; + stop(); + } + ); - if (firstCharacterColumn != null && firstCharacterColumn !== this.getBufferColumn()) { - targetBufferColumn = firstCharacterColumn + if ( + firstCharacterColumn != null && + firstCharacterColumn !== this.getBufferColumn() + ) { + targetBufferColumn = firstCharacterColumn; } else { - targetBufferColumn = screenLineBufferRange.start.column + targetBufferColumn = screenLineBufferRange.start.column; } - this.setBufferPosition([screenLineBufferRange.start.row, targetBufferColumn]) + this.setBufferPosition([ + screenLineBufferRange.start.row, + targetBufferColumn + ]); } // Public: Moves the cursor to the end of the line. - moveToEndOfScreenLine () { - this.setScreenPosition([this.getScreenRow(), Infinity]) + moveToEndOfScreenLine() { + this.setScreenPosition([this.getScreenRow(), Infinity]); } // Public: Moves the cursor to the end of the buffer line. - moveToEndOfLine () { - this.setBufferPosition([this.getBufferRow(), Infinity]) + moveToEndOfLine() { + this.setBufferPosition([this.getBufferRow(), Infinity]); } // Public: Moves the cursor to the beginning of the word. - moveToBeginningOfWord () { - this.setBufferPosition(this.getBeginningOfCurrentWordBufferPosition()) + moveToBeginningOfWord() { + this.setBufferPosition(this.getBeginningOfCurrentWordBufferPosition()); } // Public: Moves the cursor to the end of the word. - moveToEndOfWord () { - const position = this.getEndOfCurrentWordBufferPosition() - if (position) this.setBufferPosition(position) + moveToEndOfWord() { + const position = this.getEndOfCurrentWordBufferPosition(); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the beginning of the next word. - moveToBeginningOfNextWord () { - const position = this.getBeginningOfNextWordBufferPosition() - if (position) this.setBufferPosition(position) + moveToBeginningOfNextWord() { + const position = this.getBeginningOfNextWordBufferPosition(); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the previous word boundary. - moveToPreviousWordBoundary () { - const position = this.getPreviousWordBoundaryBufferPosition() - if (position) this.setBufferPosition(position) + moveToPreviousWordBoundary() { + const position = this.getPreviousWordBoundaryBufferPosition(); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the next word boundary. - moveToNextWordBoundary () { - const position = this.getNextWordBoundaryBufferPosition() - if (position) this.setBufferPosition(position) + moveToNextWordBoundary() { + const position = this.getNextWordBoundaryBufferPosition(); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the previous subword boundary. - moveToPreviousSubwordBoundary () { - const options = {wordRegex: this.subwordRegExp({backwards: true})} - const position = this.getPreviousWordBoundaryBufferPosition(options) - if (position) this.setBufferPosition(position) + moveToPreviousSubwordBoundary() { + const options = { wordRegex: this.subwordRegExp({ backwards: true }) }; + const position = this.getPreviousWordBoundaryBufferPosition(options); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the next subword boundary. - moveToNextSubwordBoundary () { - const options = {wordRegex: this.subwordRegExp()} - const position = this.getNextWordBoundaryBufferPosition(options) - if (position) this.setBufferPosition(position) + moveToNextSubwordBoundary() { + const options = { wordRegex: this.subwordRegExp() }; + const position = this.getNextWordBoundaryBufferPosition(options); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the beginning of the buffer line, skipping all // whitespace. - skipLeadingWhitespace () { - const position = this.getBufferPosition() - const scanRange = this.getCurrentLineBufferRange() - let endOfLeadingWhitespace = null - this.editor.scanInBufferRange(/^[ \t]*/, scanRange, ({range}) => { - endOfLeadingWhitespace = range.end - }) + skipLeadingWhitespace() { + const position = this.getBufferPosition(); + const scanRange = this.getCurrentLineBufferRange(); + let endOfLeadingWhitespace = null; + this.editor.scanInBufferRange(/^[ \t]*/, scanRange, ({ range }) => { + endOfLeadingWhitespace = range.end; + }); - if (endOfLeadingWhitespace.isGreaterThan(position)) this.setBufferPosition(endOfLeadingWhitespace) + if (endOfLeadingWhitespace.isGreaterThan(position)) + this.setBufferPosition(endOfLeadingWhitespace); } // Public: Moves the cursor to the beginning of the next paragraph - moveToBeginningOfNextParagraph () { - const position = this.getBeginningOfNextParagraphBufferPosition() - if (position) this.setBufferPosition(position) + moveToBeginningOfNextParagraph() { + const position = this.getBeginningOfNextParagraphBufferPosition(); + if (position) this.setBufferPosition(position); } // Public: Moves the cursor to the beginning of the previous paragraph - moveToBeginningOfPreviousParagraph () { - const position = this.getBeginningOfPreviousParagraphBufferPosition() - if (position) this.setBufferPosition(position) + moveToBeginningOfPreviousParagraph() { + const position = this.getBeginningOfPreviousParagraphBufferPosition(); + if (position) this.setBufferPosition(position); } /* @@ -460,27 +494,35 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `wordRegex` A {RegExp} indicating what constitutes a "word" // (default: {::wordRegExp}) - getPreviousWordBoundaryBufferPosition (options = {}) { - const currentBufferPosition = this.getBufferPosition() - const previousNonBlankRow = this.editor.buffer.previousNonBlankRow(currentBufferPosition.row) - const scanRange = Range(Point(previousNonBlankRow || 0, 0), currentBufferPosition) + getPreviousWordBoundaryBufferPosition(options = {}) { + const currentBufferPosition = this.getBufferPosition(); + const previousNonBlankRow = this.editor.buffer.previousNonBlankRow( + currentBufferPosition.row + ); + const scanRange = Range( + Point(previousNonBlankRow || 0, 0), + currentBufferPosition + ); const ranges = this.editor.buffer.findAllInRangeSync( options.wordRegex || this.wordRegExp(), scanRange - ) + ); - const range = ranges[ranges.length - 1] + const range = ranges[ranges.length - 1]; if (range) { - if (range.start.row < currentBufferPosition.row && currentBufferPosition.column > 0) { - return Point(currentBufferPosition.row, 0) + if ( + range.start.row < currentBufferPosition.row && + currentBufferPosition.column > 0 + ) { + return Point(currentBufferPosition.row, 0); } else if (currentBufferPosition.isGreaterThan(range.end)) { - return Point.fromObject(range.end) + return Point.fromObject(range.end); } else { - return Point.fromObject(range.start) + return Point.fromObject(range.start); } } else { - return currentBufferPosition + return currentBufferPosition; } } @@ -490,25 +532,28 @@ class Cursor extends Model { // * `options` (optional) {Object} with the following keys: // * `wordRegex` A {RegExp} indicating what constitutes a "word" // (default: {::wordRegExp}) - getNextWordBoundaryBufferPosition (options = {}) { - const currentBufferPosition = this.getBufferPosition() - const scanRange = Range(currentBufferPosition, this.editor.getEofBufferPosition()) + getNextWordBoundaryBufferPosition(options = {}) { + const currentBufferPosition = this.getBufferPosition(); + const scanRange = Range( + currentBufferPosition, + this.editor.getEofBufferPosition() + ); const range = this.editor.buffer.findInRangeSync( options.wordRegex || this.wordRegExp(), scanRange - ) + ); if (range) { if (range.start.row > currentBufferPosition.row) { - return Point(range.start.row, 0) + return Point(range.start.row, 0); } else if (currentBufferPosition.isLessThan(range.start)) { - return Point.fromObject(range.start) + return Point.fromObject(range.start); } else { - return Point.fromObject(range.end) + return Point.fromObject(range.end); } } else { - return currentBufferPosition + return currentBufferPosition; } } @@ -524,26 +569,27 @@ class Cursor extends Model { // previous word can be returned. // // Returns a {Range}. - getBeginningOfCurrentWordBufferPosition (options = {}) { - const allowPrevious = options.allowPrevious !== false - const position = this.getBufferPosition() + getBeginningOfCurrentWordBufferPosition(options = {}) { + const allowPrevious = options.allowPrevious !== false; + const position = this.getBufferPosition(); const scanRange = allowPrevious ? new Range(new Point(position.row - 1, 0), position) - : new Range(new Point(position.row, 0), position) + : new Range(new Point(position.row, 0), position); const ranges = this.editor.buffer.findAllInRangeSync( options.wordRegex || this.wordRegExp(options), scanRange - ) + ); - let result + let result; for (let range of ranges) { - if (position.isLessThanOrEqual(range.start)) break - if (allowPrevious || position.isLessThanOrEqual(range.end)) result = Point.fromObject(range.start) + if (position.isLessThanOrEqual(range.start)) break; + if (allowPrevious || position.isLessThanOrEqual(range.end)) + result = Point.fromObject(range.start); } - return result || (allowPrevious ? new Point(0, 0) : position) + return result || (allowPrevious ? new Point(0, 0) : position); } // Public: Retrieves the buffer position of where the current word ends. @@ -556,25 +602,25 @@ class Cursor extends Model { // wordRegex is set. // // Returns a {Range}. - getEndOfCurrentWordBufferPosition (options = {}) { - const allowNext = options.allowNext !== false - const position = this.getBufferPosition() + getEndOfCurrentWordBufferPosition(options = {}) { + const allowNext = options.allowNext !== false; + const position = this.getBufferPosition(); const scanRange = allowNext ? new Range(position, new Point(position.row + 2, 0)) - : new Range(position, new Point(position.row, Infinity)) + : new Range(position, new Point(position.row, Infinity)); const ranges = this.editor.buffer.findAllInRangeSync( options.wordRegex || this.wordRegExp(options), scanRange - ) + ); for (let range of ranges) { - if (position.isLessThan(range.start) && !allowNext) break - if (position.isLessThan(range.end)) return Point.fromObject(range.end) + if (position.isLessThan(range.start) && !allowNext) break; + if (position.isLessThan(range.end)) return Point.fromObject(range.end); } - return allowNext ? this.editor.getEofBufferPosition() : position + return allowNext ? this.editor.getEofBufferPosition() : position; } // Public: Retrieves the buffer position of where the next word starts. @@ -584,18 +630,24 @@ class Cursor extends Model { // (default: {::wordRegExp}). // // Returns a {Range} - getBeginningOfNextWordBufferPosition (options = {}) { - const currentBufferPosition = this.getBufferPosition() - const start = this.isInsideWord(options) ? this.getEndOfCurrentWordBufferPosition(options) : currentBufferPosition - const scanRange = [start, this.editor.getEofBufferPosition()] + getBeginningOfNextWordBufferPosition(options = {}) { + const currentBufferPosition = this.getBufferPosition(); + const start = this.isInsideWord(options) + ? this.getEndOfCurrentWordBufferPosition(options) + : currentBufferPosition; + const scanRange = [start, this.editor.getEofBufferPosition()]; - let beginningOfNextWordPosition - this.editor.scanInBufferRange(options.wordRegex || this.wordRegExp(), scanRange, ({range, stop}) => { - beginningOfNextWordPosition = range.start - stop() - }) + let beginningOfNextWordPosition; + this.editor.scanInBufferRange( + options.wordRegex || this.wordRegExp(), + scanRange, + ({ range, stop }) => { + beginningOfNextWordPosition = range.start; + stop(); + } + ); - return beginningOfNextWordPosition || currentBufferPosition + return beginningOfNextWordPosition || currentBufferPosition; } // Public: Returns the buffer Range occupied by the word located under the cursor. @@ -603,16 +655,18 @@ class Cursor extends Model { // * `options` (optional) {Object} // * `wordRegex` A {RegExp} indicating what constitutes a "word" // (default: {::wordRegExp}). - getCurrentWordBufferRange (options = {}) { - const position = this.getBufferPosition() + getCurrentWordBufferRange(options = {}) { + const position = this.getBufferPosition(); const ranges = this.editor.buffer.findAllInRangeSync( options.wordRegex || this.wordRegExp(options), new Range(new Point(position.row, 0), new Point(position.row, Infinity)) - ) - const range = ranges.find(range => - range.end.column >= position.column && range.start.column <= position.column - ) - return range ? Range.fromObject(range) : new Range(position, position) + ); + const range = ranges.find( + range => + range.end.column >= position.column && + range.start.column <= position.column + ); + return range ? Range.fromObject(range) : new Range(position, position); } // Public: Returns the buffer Range for the current line. @@ -620,8 +674,8 @@ class Cursor extends Model { // * `options` (optional) {Object} // * `includeNewline` A {Boolean} which controls whether the Range should // include the newline. - getCurrentLineBufferRange (options) { - return this.editor.bufferRangeForBufferRow(this.getBufferRow(), options) + getCurrentLineBufferRange(options) { + return this.editor.bufferRangeForBufferRow(this.getBufferRow(), options); } // Public: Retrieves the range for the current paragraph. @@ -629,13 +683,16 @@ class Cursor extends Model { // A paragraph is defined as a block of text surrounded by empty lines or comments. // // Returns a {Range}. - getCurrentParagraphBufferRange () { - return this.editor.rowRangeForParagraphAtBufferRow(this.getBufferRow()) + getCurrentParagraphBufferRange() { + return this.editor.rowRangeForParagraphAtBufferRow(this.getBufferRow()); } // Public: Returns the characters preceding the cursor in the current word. - getCurrentWordPrefix () { - return this.editor.getTextInBufferRange([this.getBeginningOfCurrentWordBufferPosition(), this.getBufferPosition()]) + getCurrentWordPrefix() { + return this.editor.getTextInBufferRange([ + this.getBeginningOfCurrentWordBufferPosition(), + this.getBufferPosition() + ]); } /* @@ -651,8 +708,8 @@ class Cursor extends Model { // See {Point::compare} for more details. // // * `otherCursor`{Cursor} to compare against - compare (otherCursor) { - return this.getBufferPosition().compare(otherCursor.getBufferPosition()) + compare(otherCursor) { + return this.getBufferPosition().compare(otherCursor.getBufferPosition()); } /* @@ -660,8 +717,8 @@ class Cursor extends Model { */ // Public: Deselects the current selection. - clearSelection (options) { - if (this.selection) this.selection.clear(options) + clearSelection(options) { + if (this.selection) this.selection.clear(options); } // Public: Get the RegExp used by the cursor to determine what a "word" is. @@ -671,13 +728,13 @@ class Cursor extends Model { // non-word characters in the regex. (default: true) // // Returns a {RegExp}. - wordRegExp (options) { - const nonWordCharacters = _.escapeRegExp(this.getNonWordCharacters()) - let source = `^[\t ]*$|[^\\s${nonWordCharacters}]+` + wordRegExp(options) { + const nonWordCharacters = _.escapeRegExp(this.getNonWordCharacters()); + let source = `^[\t ]*$|[^\\s${nonWordCharacters}]+`; if (!options || options.includeNonWordCharacters !== false) { - source += `|${`[${nonWordCharacters}]+`}` + source += `|${`[${nonWordCharacters}]+`}`; } - return new RegExp(source, 'g') + return new RegExp(source, 'g'); } // Public: Get the RegExp used by the cursor to determine what a "subword" is. @@ -687,81 +744,90 @@ class Cursor extends Model { // for the next subword. (default: false) // // Returns a {RegExp}. - subwordRegExp (options = {}) { - const nonWordCharacters = this.getNonWordCharacters() - const lowercaseLetters = 'a-z\\u00DF-\\u00F6\\u00F8-\\u00FF' - const uppercaseLetters = 'A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE' - const snakeCamelSegment = `[${uppercaseLetters}]?[${lowercaseLetters}]+` + subwordRegExp(options = {}) { + const nonWordCharacters = this.getNonWordCharacters(); + const lowercaseLetters = 'a-z\\u00DF-\\u00F6\\u00F8-\\u00FF'; + const uppercaseLetters = 'A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE'; + const snakeCamelSegment = `[${uppercaseLetters}]?[${lowercaseLetters}]+`; const segments = [ '^[\t ]+', '[\t ]+$', `[${uppercaseLetters}]+(?![${lowercaseLetters}])`, '\\d+' - ] + ]; if (options.backwards) { - segments.push(`${snakeCamelSegment}_*`) - segments.push(`[${_.escapeRegExp(nonWordCharacters)}]+\\s*`) + segments.push(`${snakeCamelSegment}_*`); + segments.push(`[${_.escapeRegExp(nonWordCharacters)}]+\\s*`); } else { - segments.push(`_*${snakeCamelSegment}`) - segments.push(`\\s*[${_.escapeRegExp(nonWordCharacters)}]+`) + segments.push(`_*${snakeCamelSegment}`); + segments.push(`\\s*[${_.escapeRegExp(nonWordCharacters)}]+`); } - segments.push('_+') - return new RegExp(segments.join('|'), 'g') + segments.push('_+'); + return new RegExp(segments.join('|'), 'g'); } /* Section: Private */ - getNonWordCharacters () { - return this.editor.getNonWordCharacters(this.getBufferPosition()) + getNonWordCharacters() { + return this.editor.getNonWordCharacters(this.getBufferPosition()); } - changePosition (options, fn) { - this.clearSelection({autoscroll: false}) - fn() - this.goalColumn = null - const autoscroll = (options && options.autoscroll != null) - ? options.autoscroll - : this.isLastCursor() - if (autoscroll) this.autoscroll() + changePosition(options, fn) { + this.clearSelection({ autoscroll: false }); + fn(); + this.goalColumn = null; + const autoscroll = + options && options.autoscroll != null + ? options.autoscroll + : this.isLastCursor(); + if (autoscroll) this.autoscroll(); } - getScreenRange () { - const {row, column} = this.getScreenPosition() - return new Range(new Point(row, column), new Point(row, column + 1)) + getScreenRange() { + const { row, column } = this.getScreenPosition(); + return new Range(new Point(row, column), new Point(row, column + 1)); } - autoscroll (options = {}) { - options.clip = false - this.editor.scrollToScreenRange(this.getScreenRange(), options) + autoscroll(options = {}) { + options.clip = false; + this.editor.scrollToScreenRange(this.getScreenRange(), options); } - getBeginningOfNextParagraphBufferPosition () { - const start = this.getBufferPosition() - const eof = this.editor.getEofBufferPosition() - const scanRange = [start, eof] + getBeginningOfNextParagraphBufferPosition() { + const start = this.getBufferPosition(); + const eof = this.editor.getEofBufferPosition(); + const scanRange = [start, eof]; - const {row, column} = eof - let position = new Point(row, column - 1) + const { row, column } = eof; + let position = new Point(row, column - 1); - this.editor.scanInBufferRange(EmptyLineRegExp, scanRange, ({range, stop}) => { - position = range.start.traverse(Point(1, 0)) - if (!position.isEqual(start)) stop() - }) - return position + this.editor.scanInBufferRange( + EmptyLineRegExp, + scanRange, + ({ range, stop }) => { + position = range.start.traverse(Point(1, 0)); + if (!position.isEqual(start)) stop(); + } + ); + return position; } - getBeginningOfPreviousParagraphBufferPosition () { - const start = this.getBufferPosition() + getBeginningOfPreviousParagraphBufferPosition() { + const start = this.getBufferPosition(); - const {row, column} = start - const scanRange = [[row - 1, column], [0, 0]] - let position = new Point(0, 0) - this.editor.backwardsScanInBufferRange(EmptyLineRegExp, scanRange, ({range, stop}) => { - position = range.start.traverse(Point(1, 0)) - if (!position.isEqual(start)) stop() - }) - return position + const { row, column } = start; + const scanRange = [[row - 1, column], [0, 0]]; + let position = new Point(0, 0); + this.editor.backwardsScanInBufferRange( + EmptyLineRegExp, + scanRange, + ({ range, stop }) => { + position = range.start.traverse(Point(1, 0)); + if (!position.isEqual(start)) stop(); + } + ); + return position; } -} +}; diff --git a/src/decoration-manager.js b/src/decoration-manager.js index cf3301fd4..5bb8b46fc 100644 --- a/src/decoration-manager.js +++ b/src/decoration-manager.js @@ -1,289 +1,328 @@ -const {Emitter} = require('event-kit') -const Decoration = require('./decoration') -const LayerDecoration = require('./layer-decoration') +const { Emitter } = require('event-kit'); +const Decoration = require('./decoration'); +const LayerDecoration = require('./layer-decoration'); -module.exports = -class DecorationManager { - constructor (editor) { - this.editor = editor - this.displayLayer = this.editor.displayLayer +module.exports = class DecorationManager { + constructor(editor) { + this.editor = editor; + this.displayLayer = this.editor.displayLayer; - this.emitter = new Emitter() - this.decorationCountsByLayer = new Map() - this.markerDecorationCountsByLayer = new Map() - this.decorationsByMarker = new Map() - this.layerDecorationsByMarkerLayer = new Map() - this.overlayDecorations = new Set() - this.layerUpdateDisposablesByLayer = new WeakMap() + this.emitter = new Emitter(); + this.decorationCountsByLayer = new Map(); + this.markerDecorationCountsByLayer = new Map(); + this.decorationsByMarker = new Map(); + this.layerDecorationsByMarkerLayer = new Map(); + this.overlayDecorations = new Set(); + this.layerUpdateDisposablesByLayer = new WeakMap(); } - observeDecorations (callback) { - const decorations = this.getDecorations() + observeDecorations(callback) { + const decorations = this.getDecorations(); for (let i = 0; i < decorations.length; i++) { - callback(decorations[i]) + callback(decorations[i]); } - return this.onDidAddDecoration(callback) + return this.onDidAddDecoration(callback); } - onDidAddDecoration (callback) { - return this.emitter.on('did-add-decoration', callback) + onDidAddDecoration(callback) { + return this.emitter.on('did-add-decoration', callback); } - onDidRemoveDecoration (callback) { - return this.emitter.on('did-remove-decoration', callback) + onDidRemoveDecoration(callback) { + return this.emitter.on('did-remove-decoration', callback); } - onDidUpdateDecorations (callback) { - return this.emitter.on('did-update-decorations', callback) + onDidUpdateDecorations(callback) { + return this.emitter.on('did-update-decorations', callback); } - getDecorations (propertyFilter) { - let allDecorations = [] + getDecorations(propertyFilter) { + let allDecorations = []; - this.decorationsByMarker.forEach((decorations) => { - decorations.forEach((decoration) => allDecorations.push(decoration)) - }) + this.decorationsByMarker.forEach(decorations => { + decorations.forEach(decoration => allDecorations.push(decoration)); + }); if (propertyFilter != null) { - allDecorations = allDecorations.filter(function (decoration) { + allDecorations = allDecorations.filter(function(decoration) { for (let key in propertyFilter) { - const value = propertyFilter[key] - if (decoration.properties[key] !== value) return false + const value = propertyFilter[key]; + if (decoration.properties[key] !== value) return false; } - return true - }) + return true; + }); } - return allDecorations + return allDecorations; } - getLineDecorations (propertyFilter) { - return this.getDecorations(propertyFilter).filter(decoration => decoration.isType('line')) + getLineDecorations(propertyFilter) { + return this.getDecorations(propertyFilter).filter(decoration => + decoration.isType('line') + ); } - getLineNumberDecorations (propertyFilter) { - return this.getDecorations(propertyFilter).filter(decoration => decoration.isType('line-number')) + getLineNumberDecorations(propertyFilter) { + return this.getDecorations(propertyFilter).filter(decoration => + decoration.isType('line-number') + ); } - getHighlightDecorations (propertyFilter) { - return this.getDecorations(propertyFilter).filter(decoration => decoration.isType('highlight')) + getHighlightDecorations(propertyFilter) { + return this.getDecorations(propertyFilter).filter(decoration => + decoration.isType('highlight') + ); } - getOverlayDecorations (propertyFilter) { - const result = [] - result.push(...Array.from(this.overlayDecorations)) + getOverlayDecorations(propertyFilter) { + const result = []; + result.push(...Array.from(this.overlayDecorations)); if (propertyFilter != null) { - return result.filter(function (decoration) { + return result.filter(function(decoration) { for (let key in propertyFilter) { - const value = propertyFilter[key] + const value = propertyFilter[key]; if (decoration.properties[key] !== value) { - return false + return false; } } - return true - }) + return true; + }); } else { - return result + return result; } } - decorationPropertiesByMarkerForScreenRowRange (startScreenRow, endScreenRow) { - const decorationPropertiesByMarker = new Map() + decorationPropertiesByMarkerForScreenRowRange(startScreenRow, endScreenRow) { + const decorationPropertiesByMarker = new Map(); this.decorationCountsByLayer.forEach((count, markerLayer) => { - const markers = markerLayer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow - 1]}) - const layerDecorations = this.layerDecorationsByMarkerLayer.get(markerLayer) - const hasMarkerDecorations = this.markerDecorationCountsByLayer.get(markerLayer) > 0 + const markers = markerLayer.findMarkers({ + intersectsScreenRowRange: [startScreenRow, endScreenRow - 1] + }); + const layerDecorations = this.layerDecorationsByMarkerLayer.get( + markerLayer + ); + const hasMarkerDecorations = + this.markerDecorationCountsByLayer.get(markerLayer) > 0; for (let i = 0; i < markers.length; i++) { - const marker = markers[i] - if (!marker.isValid()) continue + const marker = markers[i]; + if (!marker.isValid()) continue; - let decorationPropertiesForMarker = decorationPropertiesByMarker.get(marker) + let decorationPropertiesForMarker = decorationPropertiesByMarker.get( + marker + ); if (decorationPropertiesForMarker == null) { - decorationPropertiesForMarker = [] - decorationPropertiesByMarker.set(marker, decorationPropertiesForMarker) + decorationPropertiesForMarker = []; + decorationPropertiesByMarker.set( + marker, + decorationPropertiesForMarker + ); } if (layerDecorations) { - layerDecorations.forEach((layerDecoration) => { - const properties = layerDecoration.getPropertiesForMarker(marker) || layerDecoration.getProperties() - decorationPropertiesForMarker.push(properties) - }) + layerDecorations.forEach(layerDecoration => { + const properties = + layerDecoration.getPropertiesForMarker(marker) || + layerDecoration.getProperties(); + decorationPropertiesForMarker.push(properties); + }); } if (hasMarkerDecorations) { - const decorationsForMarker = this.decorationsByMarker.get(marker) + const decorationsForMarker = this.decorationsByMarker.get(marker); if (decorationsForMarker) { - decorationsForMarker.forEach((decoration) => { - decorationPropertiesForMarker.push(decoration.getProperties()) - }) + decorationsForMarker.forEach(decoration => { + decorationPropertiesForMarker.push(decoration.getProperties()); + }); } } } - }) + }); - return decorationPropertiesByMarker + return decorationPropertiesByMarker; } - decorationsForScreenRowRange (startScreenRow, endScreenRow) { - const decorationsByMarkerId = {} + decorationsForScreenRowRange(startScreenRow, endScreenRow) { + const decorationsByMarkerId = {}; for (const layer of this.decorationCountsByLayer.keys()) { - for (const marker of layer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow]})) { - const decorations = this.decorationsByMarker.get(marker) + for (const marker of layer.findMarkers({ + intersectsScreenRowRange: [startScreenRow, endScreenRow] + })) { + const decorations = this.decorationsByMarker.get(marker); if (decorations) { - decorationsByMarkerId[marker.id] = Array.from(decorations) + decorationsByMarkerId[marker.id] = Array.from(decorations); } } } - return decorationsByMarkerId + return decorationsByMarkerId; } - decorationsStateForScreenRowRange (startScreenRow, endScreenRow) { - const decorationsState = {} + decorationsStateForScreenRowRange(startScreenRow, endScreenRow) { + const decorationsState = {}; for (const layer of this.decorationCountsByLayer.keys()) { - for (const marker of layer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow]})) { + for (const marker of layer.findMarkers({ + intersectsScreenRowRange: [startScreenRow, endScreenRow] + })) { if (marker.isValid()) { - const screenRange = marker.getScreenRange() - const bufferRange = marker.getBufferRange() - const rangeIsReversed = marker.isReversed() + const screenRange = marker.getScreenRange(); + const bufferRange = marker.getBufferRange(); + const rangeIsReversed = marker.isReversed(); - const decorations = this.decorationsByMarker.get(marker) + const decorations = this.decorationsByMarker.get(marker); if (decorations) { - decorations.forEach((decoration) => { + decorations.forEach(decoration => { decorationsState[decoration.id] = { properties: decoration.properties, screenRange, bufferRange, rangeIsReversed - } - }) + }; + }); } - const layerDecorations = this.layerDecorationsByMarkerLayer.get(layer) + const layerDecorations = this.layerDecorationsByMarkerLayer.get( + layer + ); if (layerDecorations) { - layerDecorations.forEach((layerDecoration) => { - const properties = layerDecoration.getPropertiesForMarker(marker) || layerDecoration.getProperties() + layerDecorations.forEach(layerDecoration => { + const properties = + layerDecoration.getPropertiesForMarker(marker) || + layerDecoration.getProperties(); decorationsState[`${layerDecoration.id}-${marker.id}`] = { properties, screenRange, bufferRange, rangeIsReversed - } - }) + }; + }); } } } } - return decorationsState + return decorationsState; } - decorateMarker (marker, decorationParams) { + decorateMarker(marker, decorationParams) { if (marker.isDestroyed()) { - const error = new Error('Cannot decorate a destroyed marker') - error.metadata = {markerLayerIsDestroyed: marker.layer.isDestroyed()} + const error = new Error('Cannot decorate a destroyed marker'); + error.metadata = { markerLayerIsDestroyed: marker.layer.isDestroyed() }; if (marker.destroyStackTrace != null) { - error.metadata.destroyStackTrace = marker.destroyStackTrace + error.metadata.destroyStackTrace = marker.destroyStackTrace; } - if (marker.bufferMarker != null && marker.bufferMarker.destroyStackTrace != null) { - error.metadata.destroyStackTrace = marker.bufferMarker.destroyStackTrace + if ( + marker.bufferMarker != null && + marker.bufferMarker.destroyStackTrace != null + ) { + error.metadata.destroyStackTrace = + marker.bufferMarker.destroyStackTrace; } - throw error + throw error; } - marker = this.displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id) - const decoration = new Decoration(marker, this, decorationParams) - let decorationsForMarker = this.decorationsByMarker.get(marker) + marker = this.displayLayer + .getMarkerLayer(marker.layer.id) + .getMarker(marker.id); + const decoration = new Decoration(marker, this, decorationParams); + let decorationsForMarker = this.decorationsByMarker.get(marker); if (!decorationsForMarker) { - decorationsForMarker = new Set() - this.decorationsByMarker.set(marker, decorationsForMarker) + decorationsForMarker = new Set(); + this.decorationsByMarker.set(marker, decorationsForMarker); } - decorationsForMarker.add(decoration) - if (decoration.isType('overlay')) this.overlayDecorations.add(decoration) - this.observeDecoratedLayer(marker.layer, true) - this.editor.didAddDecoration(decoration) - this.emitDidUpdateDecorations() - this.emitter.emit('did-add-decoration', decoration) - return decoration + decorationsForMarker.add(decoration); + if (decoration.isType('overlay')) this.overlayDecorations.add(decoration); + this.observeDecoratedLayer(marker.layer, true); + this.editor.didAddDecoration(decoration); + this.emitDidUpdateDecorations(); + this.emitter.emit('did-add-decoration', decoration); + return decoration; } - decorateMarkerLayer (markerLayer, decorationParams) { + decorateMarkerLayer(markerLayer, decorationParams) { if (markerLayer.isDestroyed()) { - throw new Error('Cannot decorate a destroyed marker layer') + throw new Error('Cannot decorate a destroyed marker layer'); } - markerLayer = this.displayLayer.getMarkerLayer(markerLayer.id) - const decoration = new LayerDecoration(markerLayer, this, decorationParams) - let layerDecorations = this.layerDecorationsByMarkerLayer.get(markerLayer) + markerLayer = this.displayLayer.getMarkerLayer(markerLayer.id); + const decoration = new LayerDecoration(markerLayer, this, decorationParams); + let layerDecorations = this.layerDecorationsByMarkerLayer.get(markerLayer); if (layerDecorations == null) { - layerDecorations = new Set() - this.layerDecorationsByMarkerLayer.set(markerLayer, layerDecorations) + layerDecorations = new Set(); + this.layerDecorationsByMarkerLayer.set(markerLayer, layerDecorations); } - layerDecorations.add(decoration) - this.observeDecoratedLayer(markerLayer, false) - this.emitDidUpdateDecorations() - return decoration + layerDecorations.add(decoration); + this.observeDecoratedLayer(markerLayer, false); + this.emitDidUpdateDecorations(); + return decoration; } - emitDidUpdateDecorations () { - this.editor.scheduleComponentUpdate() - this.emitter.emit('did-update-decorations') + emitDidUpdateDecorations() { + this.editor.scheduleComponentUpdate(); + this.emitter.emit('did-update-decorations'); } - decorationDidChangeType (decoration) { + decorationDidChangeType(decoration) { if (decoration.isType('overlay')) { - this.overlayDecorations.add(decoration) + this.overlayDecorations.add(decoration); } else { - this.overlayDecorations.delete(decoration) + this.overlayDecorations.delete(decoration); } } - didDestroyMarkerDecoration (decoration) { - const {marker} = decoration - const decorations = this.decorationsByMarker.get(marker) + didDestroyMarkerDecoration(decoration) { + const { marker } = decoration; + const decorations = this.decorationsByMarker.get(marker); if (decorations && decorations.has(decoration)) { - decorations.delete(decoration) - if (decorations.size === 0) this.decorationsByMarker.delete(marker) - this.overlayDecorations.delete(decoration) - this.unobserveDecoratedLayer(marker.layer, true) - this.emitter.emit('did-remove-decoration', decoration) - this.emitDidUpdateDecorations() + decorations.delete(decoration); + if (decorations.size === 0) this.decorationsByMarker.delete(marker); + this.overlayDecorations.delete(decoration); + this.unobserveDecoratedLayer(marker.layer, true); + this.emitter.emit('did-remove-decoration', decoration); + this.emitDidUpdateDecorations(); } } - didDestroyLayerDecoration (decoration) { - const {markerLayer} = decoration - const decorations = this.layerDecorationsByMarkerLayer.get(markerLayer) + didDestroyLayerDecoration(decoration) { + const { markerLayer } = decoration; + const decorations = this.layerDecorationsByMarkerLayer.get(markerLayer); if (decorations && decorations.has(decoration)) { - decorations.delete(decoration) + decorations.delete(decoration); if (decorations.size === 0) { - this.layerDecorationsByMarkerLayer.delete(markerLayer) + this.layerDecorationsByMarkerLayer.delete(markerLayer); } - this.unobserveDecoratedLayer(markerLayer, true) - this.emitDidUpdateDecorations() + this.unobserveDecoratedLayer(markerLayer, true); + this.emitDidUpdateDecorations(); } } - observeDecoratedLayer (layer, isMarkerDecoration) { - const newCount = (this.decorationCountsByLayer.get(layer) || 0) + 1 - this.decorationCountsByLayer.set(layer, newCount) + observeDecoratedLayer(layer, isMarkerDecoration) { + const newCount = (this.decorationCountsByLayer.get(layer) || 0) + 1; + this.decorationCountsByLayer.set(layer, newCount); if (newCount === 1) { - this.layerUpdateDisposablesByLayer.set(layer, layer.onDidUpdate(this.emitDidUpdateDecorations.bind(this))) + this.layerUpdateDisposablesByLayer.set( + layer, + layer.onDidUpdate(this.emitDidUpdateDecorations.bind(this)) + ); } if (isMarkerDecoration) { - this.markerDecorationCountsByLayer.set(layer, (this.markerDecorationCountsByLayer.get(layer) || 0) + 1) + this.markerDecorationCountsByLayer.set( + layer, + (this.markerDecorationCountsByLayer.get(layer) || 0) + 1 + ); } } - unobserveDecoratedLayer (layer, isMarkerDecoration) { - const newCount = this.decorationCountsByLayer.get(layer) - 1 + unobserveDecoratedLayer(layer, isMarkerDecoration) { + const newCount = this.decorationCountsByLayer.get(layer) - 1; if (newCount === 0) { - this.layerUpdateDisposablesByLayer.get(layer).dispose() - this.decorationCountsByLayer.delete(layer) + this.layerUpdateDisposablesByLayer.get(layer).dispose(); + this.decorationCountsByLayer.delete(layer); } else { - this.decorationCountsByLayer.set(layer, newCount) + this.decorationCountsByLayer.set(layer, newCount); } if (isMarkerDecoration) { - this.markerDecorationCountsByLayer.set(this.markerDecorationCountsByLayer.get(layer) - 1) + this.markerDecorationCountsByLayer.set( + this.markerDecorationCountsByLayer.get(layer) - 1 + ); } } -} +}; diff --git a/src/decoration.js b/src/decoration.js index 989e48588..1263259c8 100644 --- a/src/decoration.js +++ b/src/decoration.js @@ -1,21 +1,24 @@ -const {Emitter} = require('event-kit') +const { Emitter } = require('event-kit'); -let idCounter = 0 -const nextId = () => idCounter++ +let idCounter = 0; +const nextId = () => idCounter++; -const normalizeDecorationProperties = function (decoration, decorationParams) { - decorationParams.id = decoration.id +const normalizeDecorationProperties = function(decoration, decorationParams) { + decorationParams.id = decoration.id; - if (decorationParams.type === 'line-number' && decorationParams.gutterName == null) { - decorationParams.gutterName = 'line-number' + if ( + decorationParams.type === 'line-number' && + decorationParams.gutterName == null + ) { + decorationParams.gutterName = 'line-number'; } if (decorationParams.order == null) { - decorationParams.order = Infinity + decorationParams.order = Infinity; } - return decorationParams -} + return decorationParams; +}; // Essential: Represents a decoration that follows a {DisplayMarker}. A decoration is // basically a visual representation of a marker. It allows you to add CSS @@ -39,8 +42,7 @@ const normalizeDecorationProperties = function (decoration, decorationParams) { // // You should only use {Decoration::destroy} when you still need or do not own // the marker. -module.exports = -class Decoration { +module.exports = class Decoration { // Private: Check if the `decorationProperties.type` matches `type` // // * `decorationProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}` @@ -51,23 +53,26 @@ class Decoration { // Returns {Boolean} // Note: 'line-number' is a special subtype of the 'gutter' type. I.e., a // 'line-number' is a 'gutter', but a 'gutter' is not a 'line-number'. - static isType (decorationProperties, type) { + static isType(decorationProperties, type) { // 'line-number' is a special case of 'gutter'. if (Array.isArray(decorationProperties.type)) { if (decorationProperties.type.includes(type)) { - return true + return true; } - if (type === 'gutter' && decorationProperties.type.includes('line-number')) { - return true + if ( + type === 'gutter' && + decorationProperties.type.includes('line-number') + ) { + return true; } - return false + return false; } else { if (type === 'gutter') { - return ['gutter', 'line-number'].includes(decorationProperties.type) + return ['gutter', 'line-number'].includes(decorationProperties.type); } else { - return type === decorationProperties.type + return type === decorationProperties.type; } } } @@ -76,31 +81,37 @@ class Decoration { Section: Construction and Destruction */ - constructor (marker, decorationManager, properties) { - this.marker = marker - this.decorationManager = decorationManager - this.emitter = new Emitter() - this.id = nextId() - this.setProperties(properties) - this.destroyed = false - this.markerDestroyDisposable = this.marker.onDidDestroy(() => this.destroy()) + constructor(marker, decorationManager, properties) { + this.marker = marker; + this.decorationManager = decorationManager; + this.emitter = new Emitter(); + this.id = nextId(); + this.setProperties(properties); + this.destroyed = false; + this.markerDestroyDisposable = this.marker.onDidDestroy(() => + this.destroy() + ); } // Essential: Destroy this marker decoration. // // You can also destroy the marker if you own it, which will destroy this // decoration. - destroy () { - if (this.destroyed) { return } - this.markerDestroyDisposable.dispose() - this.markerDestroyDisposable = null - this.destroyed = true - this.decorationManager.didDestroyMarkerDecoration(this) - this.emitter.emit('did-destroy') - return this.emitter.dispose() + destroy() { + if (this.destroyed) { + return; + } + this.markerDestroyDisposable.dispose(); + this.markerDestroyDisposable = null; + this.destroyed = true; + this.decorationManager.didDestroyMarkerDecoration(this); + this.emitter.emit('did-destroy'); + return this.emitter.dispose(); } - isDestroyed () { return this.destroyed } + isDestroyed() { + return this.destroyed; + } /* Section: Event Subscription @@ -114,8 +125,8 @@ class Decoration { // * `newProperties` {Object} the new parameters the decoration now has // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeProperties (callback) { - return this.emitter.on('did-change-properties', callback) + onDidChangeProperties(callback) { + return this.emitter.on('did-change-properties', callback); } // Essential: Invoke the given callback when the {Decoration} is destroyed @@ -123,8 +134,8 @@ class Decoration { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } /* @@ -132,10 +143,14 @@ class Decoration { */ // Essential: An id unique across all {Decoration} objects - getId () { return this.id } + getId() { + return this.id; + } // Essential: Returns the marker associated with this {Decoration} - getMarker () { return this.marker } + getMarker() { + return this.marker; + } // Public: Check if this decoration is of type `type` // @@ -144,8 +159,8 @@ class Decoration { // type matches any in the array. // // Returns {Boolean} - isType (type) { - return Decoration.isType(this.properties, type) + isType(type) { + return Decoration.isType(this.properties, type); } /* @@ -153,8 +168,8 @@ class Decoration { */ // Essential: Returns the {Decoration}'s properties. - getProperties () { - return this.properties + getProperties() { + return this.properties; } // Essential: Update the marker with new Properties. Allows you to change the decoration's class. @@ -166,44 +181,55 @@ class Decoration { // ``` // // * `newProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}` - setProperties (newProperties) { - if (this.destroyed) { return } - const oldProperties = this.properties - this.properties = normalizeDecorationProperties(this, newProperties) - if (newProperties.type != null) { - this.decorationManager.decorationDidChangeType(this) + setProperties(newProperties) { + if (this.destroyed) { + return; } - this.decorationManager.emitDidUpdateDecorations() - return this.emitter.emit('did-change-properties', {oldProperties, newProperties}) + const oldProperties = this.properties; + this.properties = normalizeDecorationProperties(this, newProperties); + if (newProperties.type != null) { + this.decorationManager.decorationDidChangeType(this); + } + this.decorationManager.emitDidUpdateDecorations(); + return this.emitter.emit('did-change-properties', { + oldProperties, + newProperties + }); } /* Section: Utility */ - inspect () { - return `` + inspect() { + return ``; } /* Section: Private methods */ - matchesPattern (decorationPattern) { - if (decorationPattern == null) { return false } - for (let key in decorationPattern) { - const value = decorationPattern[key] - if (this.properties[key] !== value) { return false } + matchesPattern(decorationPattern) { + if (decorationPattern == null) { + return false; } - return true + for (let key in decorationPattern) { + const value = decorationPattern[key]; + if (this.properties[key] !== value) { + return false; + } + } + return true; } - flash (klass, duration) { - if (duration == null) { duration = 500 } - this.properties.flashRequested = true - this.properties.flashClass = klass - this.properties.flashDuration = duration - this.decorationManager.emitDidUpdateDecorations() - return this.emitter.emit('did-flash') + flash(klass, duration) { + if (duration == null) { + duration = 500; + } + this.properties.flashRequested = true; + this.properties.flashClass = klass; + this.properties.flashDuration = duration; + this.decorationManager.emitDidUpdateDecorations(); + return this.emitter.emit('did-flash'); } -} +}; diff --git a/src/default-directory-searcher.js b/src/default-directory-searcher.js index 5dca54a55..982fd5b19 100644 --- a/src/default-directory-searcher.js +++ b/src/default-directory-searcher.js @@ -1,9 +1,9 @@ -const Task = require('./task') +const Task = require('./task'); // Searches local files for lines matching a specified regex. Implements `.then()` // so that it can be used with `Promise.all()`. class DirectorySearch { - constructor (rootPaths, regex, options) { + constructor(rootPaths, regex, options) { const scanHandlerOptions = { ignoreCase: regex.ignoreCase, inclusions: options.inclusions, @@ -11,37 +11,37 @@ class DirectorySearch { excludeVcsIgnores: options.excludeVcsIgnores, globalExclusions: options.exclusions, follow: options.follow - } + }; const searchOptions = { leadingContextLineCount: options.leadingContextLineCount, trailingContextLineCount: options.trailingContextLineCount - } - this.task = new Task(require.resolve('./scan-handler')) - this.task.on('scan:result-found', options.didMatch) - this.task.on('scan:file-error', options.didError) - this.task.on('scan:paths-searched', options.didSearchPaths) + }; + this.task = new Task(require.resolve('./scan-handler')); + this.task.on('scan:result-found', options.didMatch); + this.task.on('scan:file-error', options.didError); + this.task.on('scan:paths-searched', options.didSearchPaths); this.promise = new Promise((resolve, reject) => { - this.task.on('task:cancelled', reject) + this.task.on('task:cancelled', reject); this.task.start( rootPaths, regex.source, scanHandlerOptions, searchOptions, () => { - this.task.terminate() - resolve() + this.task.terminate(); + resolve(); } - ) - }) + ); + }); } - then (...args) { - return this.promise.then.apply(this.promise, args) + then(...args) { + return this.promise.then.apply(this.promise, args); } - cancel () { + cancel() { // This will cause @promise to reject. - this.task.cancel() + this.task.cancel(); } } @@ -52,8 +52,8 @@ module.exports = class DefaultDirectorySearcher { // * `directory` {Directory} whose search needs might be supported by this object. // // Returns a `boolean` indicating whether this object can search this `Directory`. - canSearchDirectory (directory) { - return true + canSearchDirectory(directory) { + return true; } // Performs a text search for files in the specified `Directory`, subject to the @@ -89,26 +89,26 @@ module.exports = class DefaultDirectorySearcher { // // Returns a *thenable* `DirectorySearch` that includes a `cancel()` method. If `cancel()` is // invoked before the `DirectorySearch` is determined, it will resolve the `DirectorySearch`. - search (directories, regex, options) { - const rootPaths = directories.map(directory => directory.getPath()) - let isCancelled = false - const directorySearch = new DirectorySearch(rootPaths, regex, options) - const promise = new Promise(function (resolve, reject) { - directorySearch.then(resolve, function () { + search(directories, regex, options) { + const rootPaths = directories.map(directory => directory.getPath()); + let isCancelled = false; + const directorySearch = new DirectorySearch(rootPaths, regex, options); + const promise = new Promise(function(resolve, reject) { + directorySearch.then(resolve, function() { if (isCancelled) { - resolve() + resolve(); } else { - reject() // eslint-disable-line prefer-promise-reject-errors + reject(); // eslint-disable-line prefer-promise-reject-errors } - }) - }) + }); + }); return { then: promise.then.bind(promise), catch: promise.catch.bind(promise), - cancel () { - isCancelled = true - directorySearch.cancel() + cancel() { + isCancelled = true; + directorySearch.cancel(); } - } + }; } -} +}; diff --git a/src/delegated-listener.js b/src/delegated-listener.js index b24384ba2..c977912e8 100644 --- a/src/delegated-listener.js +++ b/src/delegated-listener.js @@ -1,41 +1,40 @@ -const EventKit = require('event-kit') +const EventKit = require('event-kit'); -module.exports = -function listen (element, eventName, selector, handler) { - var innerHandler = function (event) { +module.exports = function listen(element, eventName, selector, handler) { + var innerHandler = function(event) { if (selector) { - var currentTarget = event.target + var currentTarget = event.target; while (currentTarget) { if (currentTarget.matches && currentTarget.matches(selector)) { handler({ type: event.type, currentTarget: currentTarget, target: event.target, - preventDefault: function () { - event.preventDefault() + preventDefault: function() { + event.preventDefault(); }, originalEvent: event - }) + }); } - if (currentTarget === element) break - currentTarget = currentTarget.parentNode + if (currentTarget === element) break; + currentTarget = currentTarget.parentNode; } } else { handler({ type: event.type, currentTarget: event.currentTarget, target: event.target, - preventDefault: function () { - event.preventDefault() + preventDefault: function() { + event.preventDefault(); }, originalEvent: event - }) + }); } - } + }; - element.addEventListener(eventName, innerHandler) + element.addEventListener(eventName, innerHandler); - return new EventKit.Disposable(function () { - element.removeEventListener(eventName, innerHandler) - }) -} + return new EventKit.Disposable(function() { + element.removeEventListener(eventName, innerHandler); + }); +}; diff --git a/src/deprecated-syntax-selectors.js b/src/deprecated-syntax-selectors.js index 4f8b9cefc..4debbdad0 100644 --- a/src/deprecated-syntax-selectors.js +++ b/src/deprecated-syntax-selectors.js @@ -1,964 +1,5476 @@ module.exports = new Set([ - 'AFDKO', 'AFKDO', 'ASS', 'AVX', 'AVX2', 'AVX512', 'AVX512BW', 'AVX512DQ', - 'Alignment', 'Alpha', 'AlphaLevel', 'Angle', 'Animation', 'AnimationGroup', - 'ArchaeologyDigSiteFrame', 'Arrow__', 'AtLilyPond', 'AttrBaseType', - 'AttrSetVal__', 'BackColour', 'Banner', 'Bold', 'Bonlang', 'BorderStyle', - 'Browser', 'Button', 'C99', 'CALCULATE', 'CharacterSet', 'ChatScript', - 'Chatscript', 'CheckButton', 'ClipboardFormat', 'ClipboardType', - 'Clipboard__', 'CodePage', 'Codepages__', 'Collisions', 'ColorSelect', - 'ColourActual', 'ColourLogical', 'ColourReal', 'ColourScheme', 'ColourSize', - 'Column', 'Comment', 'ConfCachePolicy', 'ControlPoint', 'Cooldown', 'DBE', - 'DDL', 'DML', 'DSC', 'Database__', 'DdcMode', 'Dialogue', - 'DiscussionFilterType', 'DiscussionStatus', 'DisplaySchemes', - 'Document-Structuring-Comment', 'DressUpModel', 'Edit', 'EditBox', 'Effect', - 'Encoding', 'End', 'ExternalLinkBehaviour', 'ExternalLinkDirection', 'F16c', - 'FMA', 'FilterType', 'Font', 'FontInstance', 'FontString', 'Fontname', - 'Fonts__', 'Fontsize', 'Format', 'Frame', 'GameTooltip', 'GroupList', 'HLE', - 'HeaderEvent', 'HistoryType', 'HttpVerb', 'II', 'IO', 'Icon', 'IconID', - 'InPlaceBox__', 'InPlaceEditEvent', 'Info', 'Italic', 'JSXEndTagStart', - 'JSXStartTagEnd', 'KNC', 'KeyModifier', 'Kotlin', 'LUW', 'Language', 'Layer', - 'LayeredRegion', 'LdapItemList', 'LineSpacing', 'LinkFilter', 'LinkLimit', - 'ListView', 'Locales__', 'Lock', 'LoginPolicy', 'MA_End__', 'MA_StdCombo__', - 'MA_StdItem__', 'MA_StdMenu__', 'MISSING', 'Mapping', 'MarginL', 'MarginR', - 'MarginV', 'Marked', 'MessageFrame', 'Minimap', 'MovieFrame', 'Name', - 'Outline', 'OutlineColour', 'ParentedObject', 'Path', 'Permission', 'PlayRes', - 'PlayerModel', 'PrimaryColour', 'Proof', 'QuestPOIFrame', 'RTM', - 'RecentModule__', 'Regexp', 'Region', 'Rotation', 'SCADABasic', 'SSA', - 'Scale', 'ScaleX', 'ScaleY', 'ScaledBorderAndShadow', 'ScenarioPOIFrame', - 'ScriptObject', 'Script__', 'Scroll', 'ScrollEvent', 'ScrollFrame', - 'ScrollSide', 'ScrollingMessageFrame', 'SecondaryColour', 'Sensitivity', - 'Shadow', 'SimpleHTML', 'Slider', 'Spacing', 'Start', 'StatusBar', 'Stream', - 'StrikeOut', 'Style', 'TIS', 'TODO', 'TabardModel', 'Text', 'Texture', - 'Timer', 'ToolType', 'Translation', 'TreeView', 'TriggerStatus', 'UIObject', - 'Underline', 'UserClass', 'UserList', 'UserNotifyList', 'VisibleRegion', - 'Vplus', 'WrapStyle', 'XHPEndTagStart', 'XHPStartTagEnd', 'ZipType', - '__package-name__', '_c', '_function', 'a', 'a10networks', 'aaa', 'abaqus', - 'abbrev', 'abbreviated', 'abbreviation', 'abcnotation', 'abl', 'abnf', 'abp', - 'absolute', 'abstract', 'academic', 'access', 'access-control', - 'access-qualifiers', 'accessed', 'accessor', 'account', 'accumulator', 'ace', - 'ace3', 'acl', 'acos', 'act', 'action', 'action-map', 'actionhandler', - 'actionpack', 'actions', 'actionscript', 'activerecord', 'activesupport', - 'actual', 'acute-accent', 'ada', 'add', 'adddon', 'added', 'addition', - 'additional-character', 'additive', 'addon', 'address', 'address-of', - 'address-space', 'addrfam', 'adjustment', 'admonition', 'adr', 'adverb', - 'adx', 'ael', 'aem', 'aerospace', 'aes', 'aes_functions', 'aesni', - 'aexLightGreen', 'af', 'afii', 'aflex', 'after', 'after-expression', 'agc', - 'agda', 'agentspeak', 'aggregate', 'aggregation', 'ahk', 'ai-connection', - 'ai-player', 'ai-wheeled-vehicle', 'aif', 'alabel', 'alarms', 'alda', 'alert', - 'algebraic-type', 'alias', 'aliases', 'align', 'align-attribute', 'alignment', - 'alignment-cue-setting', 'alignment-mode', 'all', 'all-once', 'all-solutions', - 'allocate', 'alloy', 'alloyglobals', 'alloyxml', 'alog', 'alpha', - 'alphabeticalllt', 'alphabeticallyge', 'alphabeticallygt', 'alphabeticallyle', - 'alt', 'alter', 'alternate-wysiwyg-string', 'alternates', 'alternation', - 'alternatives', 'am', 'ambient-audio-manager', 'ambient-reflectivity', 'amd', - 'amd3DNow', 'amdnops', 'ameter', 'amount', 'amp', 'ampersand', 'ampl', - 'ampscript', 'an', 'analysis', 'analytics', 'anb', 'anchor', 'and', 'andop', - 'angelscript', 'angle', 'angle-brackets', 'angular', 'animation', 'annot', - 'annotated', 'annotation', 'annotation-arguments', 'anon', 'anonymous', - 'another', 'ansi', 'ansi-c', 'ansi-colored', 'ansi-escape-code', - 'ansi-formatted', 'ansi2', 'ansible', 'answer', 'antialiasing', 'antl', - 'antlr', 'antlr4', 'anubis', 'any', 'any-method', 'anyclass', 'aolserver', - 'apa', 'apache', 'apache-config', 'apc', 'apdl', 'apex', 'api', - 'api-notation', 'apiary', 'apib', 'apl', 'apostrophe', 'appcache', - 'applescript', 'application', 'application-name', 'application-process', - 'approx-equal', 'aql', 'aqua', 'ar', 'arbitrary-radix', - 'arbitrary-repetition', 'arbitrary-repitition', 'arch', 'arch_specification', - 'architecture', 'archive', 'archives', 'arduino', 'area-code', 'arendelle', - 'argcount', 'args', 'argument', 'argument-label', 'argument-separator', - 'argument-seperator', 'argument-type', 'arguments', 'arith', 'arithmetic', - 'arithmetical', 'arithmeticcql', 'ark', 'arm', 'arma', 'armaConfig', - 'arnoldc', 'arp', 'arpop', 'arr', 'array', 'array-expression', - 'array-literal', 'arrays', 'arrow', 'articulation', 'artihmetic', 'arvo', - 'aryop', 'as', 'as4', 'ascii', 'asciidoc', 'asdoc', 'ash', 'ashx', 'asl', - 'asm', 'asm-instruction', 'asm-type-prefix', 'asn', 'asp', 'asp-core-2', - 'aspx', 'ass', 'assembly', 'assert', 'assertion', 'assigment', 'assign', - 'assign-class', 'assigned', 'assigned-class', 'assigned-value', 'assignee', - 'assignement', 'assignment', 'assignmentforge-config', 'associate', - 'association', 'associativity', 'assocs', 'asterisk', 'async', 'at-marker', - 'at-root', 'at-rule', 'at-sign', 'atmark', 'atml3', 'atoemp', 'atom', - 'atom-term-processing', 'atomic', 'atomscript', 'att', 'attachment', 'attr', - 'attribute', 'attribute-entry', 'attribute-expression', 'attribute-key-value', - 'attribute-list', 'attribute-lookup', 'attribute-name', 'attribute-reference', - 'attribute-selector', 'attribute-value', 'attribute-values', - 'attribute-with-value', 'attribute_list', 'attribute_value', - 'attribute_value2', 'attributelist', 'attributes', 'attrset', - 'attrset-or-function', 'audio', 'audio-file', 'auditor', 'augmented', 'auth', - 'auth_basic', 'author', 'author-names', 'authorization', 'auto', 'auto-event', - 'autoconf', 'autoindex', 'autoit', 'automake', 'automatic', 'autotools', - 'autovar', 'aux', 'auxiliary', 'avdl', 'avra', 'avrasm', 'avrdisasm', 'avs', - 'avx', 'avx2', 'avx512', 'awk', 'axes_group', 'axis', 'axl', 'b', - 'b-spline-patch', 'babel', 'back', 'back-from', 'back-reference', - 'back-slash', 'backend', 'background', 'backreference', 'backslash', - 'backslash-bar', 'backslash-g', 'backspace', 'backtick', 'bad-ampersand', - 'bad-angle-bracket', 'bad-assignment', 'bad-comments-or-CDATA', 'bad-escape', - 'bad-octal', 'bad-var', 'bang', 'banner', 'bar', 'bareword', 'barline', - 'base', 'base-11', 'base-12', 'base-13', 'base-14', 'base-15', 'base-16', - 'base-17', 'base-18', 'base-19', 'base-20', 'base-21', 'base-22', 'base-23', - 'base-24', 'base-25', 'base-26', 'base-27', 'base-28', 'base-29', 'base-3', - 'base-30', 'base-31', 'base-32', 'base-33', 'base-34', 'base-35', 'base-36', - 'base-4', 'base-5', 'base-6', 'base-7', 'base-9', 'base-call', 'base-integer', - 'base64', 'base85', 'base_pound_number_pound', 'basetype', 'basic', - 'basic-arithmetic', 'basic-type', 'basic_functions', 'basicblock', - 'basis-matrix', 'bat', 'batch', 'batchfile', 'battlesim', 'bb', 'bbcode', - 'bcmath', 'be', 'beam', 'beamer', 'beancount', 'before', 'begin', - 'begin-document', 'begin-emphasis', 'begin-end', 'begin-end-group', - 'begin-literal', 'begin-symbolic', 'begintimeblock', 'behaviour', 'bem', - 'between-tag-pair', 'bevel', 'bezier-patch', 'bfeac', 'bff', 'bg', 'bg-black', - 'bg-blue', 'bg-cyan', 'bg-green', 'bg-normal', 'bg-purple', 'bg-red', - 'bg-white', 'bg-yellow', 'bhtml', 'bhv', 'bibitem', 'bibliography-anchor', - 'biblioref', 'bibpaper', 'bibtex', 'bif', 'big-arrow', 'big-arrow-left', - 'bigdecimal', 'bigint', 'biicode', 'biiconf', 'bin', 'binOp', 'binary', - 'binary-arithmetic', 'bind', 'binder', 'binding', 'binding-prefix', - 'bindings', 'binop', 'bioinformatics', 'biosphere', 'bird-track', 'bis', - 'bison', 'bit', 'bit-and-byte', 'bit-range', 'bit-wise', 'bitarray', 'bitop', - 'bits-mov', 'bitvector', 'bitwise', 'black', 'blade', 'blanks', 'blaze', - 'blenc', 'blend', 'blending', 'blendtype', 'blendu', 'blendv', 'blip', - 'block', 'block-attribute', 'block-dartdoc', 'block-data', 'block-level', - 'blockid', 'blockname', 'blockquote', 'blocktitle', 'blue', 'blueprint', - 'bluespec', 'blur', 'bm', 'bmi', 'bmi1', 'bmi2', 'bnd', 'bnf', 'body', - 'body-statement', 'bold', 'bold-italic-text', 'bold-text', 'bolt', 'bond', - 'bonlang', 'boo', 'boogie', 'bool', 'boolean', 'boolean-test', 'boost', - 'boot', 'bord', 'border', 'botml', 'bottom', 'boundary', 'bounded', 'bounds', - 'bow', 'box', 'bpl', 'bpr', 'bqparam', 'brace', 'braced', 'braces', 'bracket', - 'bracketed', 'brackets', 'brainfuck', 'branch', 'branch-point', 'break', - 'breakpoint', 'breakpoints', 'breaks', 'bridle', 'brightscript', 'bro', - 'broken', 'browser', 'browsers', 'bs', 'bsl', 'btw', 'buffered', 'buffers', - 'bugzilla-number', 'build', 'buildin', 'buildout', 'built-in', - 'built-in-variable', 'built-ins', 'builtin', 'builtin-comparison', 'builtins', - 'bullet', 'bullet-point', 'bump', 'bump-multiplier', 'bundle', 'but', - 'button', 'buttons', 'by', 'by-name', 'by-number', 'byref', 'byte', - 'bytearray', 'bz2', 'bzl', 'c', 'c-style', 'c0', 'c1', 'c2hs', 'ca', 'cabal', - 'cabal-keyword', 'cache', 'cache-management', 'cacheability-control', 'cake', - 'calc', 'calca', 'calendar', 'call', 'callable', 'callback', 'caller', - 'calling', 'callmethod', 'callout', 'callparent', 'camera', 'camlp4', - 'camlp4-stream', 'canonicalized-program-name', 'canopen', 'capability', - 'capnp', 'cappuccino', 'caps', 'caption', 'capture', 'capturename', - 'cardinal-curve', 'cardinal-patch', 'cascade', 'case', 'case-block', - 'case-body', 'case-class', 'case-clause', 'case-clause-body', - 'case-expression', 'case-modifier', 'case-pattern', 'case-statement', - 'case-terminator', 'case-value', 'cassius', 'cast', 'catch', - 'catch-exception', 'catcode', 'categories', 'categort', 'category', 'cba', - 'cbmbasic', 'cbot', 'cbs', 'cc', 'cc65', 'ccml', 'cdata', 'cdef', 'cdtor', - 'ceiling', 'cell', 'cellcontents', 'cellwall', 'ceq', 'ces', 'cet', 'cexpr', - 'cextern', 'ceylon', 'ceylondoc', 'cf', 'cfdg', 'cfengine', 'cfg', 'cfml', - 'cfscript', 'cfunction', 'cg', 'cgi', 'cgx', 'chain', 'chained', 'chaining', - 'chainname', 'changed', 'changelogs', 'changes', 'channel', 'chapel', - 'chapter', 'char', 'characater', 'character', 'character-class', - 'character-data-not-allowed-here', 'character-literal', - 'character-literal-too-long', 'character-not-allowed-here', 'character-range', - 'character-reference', 'character-token', 'character_not_allowed', - 'character_not_allowed_here', 'characters', 'chars', 'chars-and-bytes-io', - 'charset', 'check', 'check-identifier', 'checkboxes', 'checker', 'chef', - 'chem', 'chemical', 'children', 'choice', 'choicescript', 'chord', 'chorus', - 'chuck', 'chunk', 'ciexyz', 'circle', 'circle-jot', 'cirru', 'cisco', - 'cisco-ios-config', 'citation', 'cite', 'citrine', 'cjam', 'cjson', 'clamp', - 'clamping', 'class', 'class-constraint', 'class-constraints', - 'class-declaration', 'class-definition', 'class-fns', 'class-instance', - 'class-list', 'class-struct-block', 'class-type', 'class-type-definition', - 'classcode', 'classes', 'classic', 'classicalb', 'classmethods', 'classobj', - 'classtree', 'clause', 'clause-head-body', 'clauses', 'clear', - 'clear-argument', 'cleared', 'clflushopt', 'click', 'client', 'client-server', - 'clip', 'clipboard', 'clips', 'clmul', 'clock', 'clojure', 'cloned', 'close', - 'closed', 'closing', 'closing-text', 'closure', 'clothes-body', 'cm', 'cmake', - 'cmb', 'cmd', 'cnet', 'cns', 'cobject', 'cocoa', 'cocor', 'cod4mp', 'code', - 'code-example', 'codeblock', 'codepoint', 'codimension', 'codstr', 'coffee', - 'coffeescript', 'coffeescript-preview', 'coil', 'collection', 'collision', - 'colon', 'colons', 'color', 'color-adjustment', 'coloring', 'colour', - 'colour-correction', 'colour-interpolation', 'colour-name', 'colour-scheme', - 'colspan', 'column', 'column-divider', 'column-specials', 'com', - 'combinators', 'comboboxes', 'comma', 'comma-bar', 'comma-parenthesis', - 'command', 'command-name', 'command-synopsis', 'commandline', 'commands', - 'comment', 'comment-ish', 'comment-italic', 'commented-out', 'commit-command', - 'commit-message', 'commodity', 'common', 'commonform', 'communications', - 'community', 'commute', 'comnd', 'compare', 'compareOp', 'comparison', - 'compile', 'compile-only', 'compiled', 'compiled-papyrus', 'compiler', - 'compiler-directive', 'compiletime', 'compiling-and-loading', 'complement', - 'complete', 'completed', 'complex', 'component', 'component-separator', - 'component_instantiation', 'compositor', 'compound', 'compound-assignment', - 'compress', 'computer', 'computercraft', 'concat', 'concatenated-arguments', - 'concatenation', 'concatenator', 'concatination', 'concealed', 'concise', - 'concrete', 'condition', 'conditional', 'conditional-directive', - 'conditional-short', 'conditionals', 'conditions', 'conf', 'config', - 'configuration', 'configure', 'confluence', 'conftype', 'conjunction', - 'conky', 'connect', 'connection-state', 'connectivity', 'connstate', 'cons', - 'consecutive-tags', 'considering', 'console', 'const', 'const-data', - 'constant', 'constants', 'constrained', 'constraint', 'constraints', - 'construct', 'constructor', 'constructor-list', 'constructs', 'consult', - 'contacts', 'container', 'containers-raycast', 'contains', 'content', - 'content-detective', 'contentSupplying', 'contentitem', 'context', - 'context-free', 'context-signature', 'continuation', 'continuations', - 'continue', 'continued', 'continuum', 'contol', 'contract', 'contracts', - 'contrl', 'control', 'control-char', 'control-handlers', 'control-management', - 'control-systems', 'control-transfer', 'controller', 'controlline', - 'controls', 'contstant', 'conventional', 'conversion', 'convert-type', - 'cookie', 'cool', 'coord1', 'coord2', 'coord3', 'coordinates', 'copy', - 'copying', 'coq', 'core', 'core-parse', 'coreutils', 'correct', 'cos', - 'counter', 'counters', 'cover', 'cplkg', 'cplusplus', 'cpm', 'cpp', - 'cpp-include', 'cpp-type', 'cpp_type', 'cpu12', 'cql', 'cram', 'crc32', - 'create', 'creation', 'critic', 'crl', 'crontab', 'crypto', 'crystal', 'cs', - 'csharp', 'cshtml', 'csi', 'csjs', 'csound', 'csound-document', - 'csound-score', 'cspm', 'css', 'csv', 'csx', 'ct', 'ctkey', 'ctor', 'ctxvar', - 'ctxvarbracket', 'ctype', 'cubic-bezier', 'cucumber', 'cuda', - 'cue-identifier', 'cue-timings', 'cuesheet', 'cup', 'cupsym', 'curl', - 'curley', 'curly', 'currency', 'current', 'current-escape-char', - 'curve', 'curve-2d', 'curve-fitting', 'curve-reference', 'curve-technique', - 'custom', 'customevent', 'cut', 'cve-number', 'cvs', 'cw', 'cxx', 'cy-GB', - 'cyan', 'cyc', 'cycle', 'cypher', 'cyrix', 'cython', 'd', 'da', 'daml', - 'dana', 'danger', 'danmakufu', 'dark_aqua', 'dark_blue', 'dark_gray', - 'dark_green', 'dark_purple', 'dark_red', 'dart', 'dartdoc', 'dash', 'dasm', - 'data', 'data-acquisition', 'data-extension', 'data-integrity', 'data-item', - 'data-step', 'data-transfer', 'database', 'database-name', 'datablock', - 'datablocks', 'datafeed', 'datatype', 'datatypes', 'date', 'date-time', - 'datetime', 'dav', 'day', 'dayofmonth', 'dayofweek', 'db', 'dba', 'dbx', 'dc', - 'dcon', 'dd', 'ddp', 'de', 'dealii', 'deallocate', 'deb-control', 'debian', - 'debris', 'debug', 'debug-specification', 'debugger', 'debugging', - 'debugging-comment', 'dec', 'decal', 'decimal', 'decimal-arithmetic', - 'decision', 'decl', 'declaration', 'declaration-expr', 'declaration-prod', - 'declarations', 'declarator', 'declaratyion', 'declare', 'decode', - 'decoration', 'decorator', 'decreasing', 'decrement', 'def', 'default', - 'define', 'define-colour', 'defined', 'definedness', 'definingobj', - 'definition', 'definitions', 'defintions', 'deflate', 'delay', 'delegated', - 'delete', 'deleted', 'deletion', 'delimeter', 'delimited', 'delimiter', - 'delimiter-too-long', 'delimiters', 'dense', 'deprecated', 'depricated', - 'dereference', 'derived-type', 'deriving', 'desc', 'describe', 'description', - 'descriptors', 'design', 'desktop', 'destination', 'destructor', - 'destructured', 'determ', 'developer', 'device', 'device-io', 'dformat', 'dg', - 'dhcp', 'diagnostic', 'dialogue', 'diamond', 'dict', 'dictionary', - 'dictionaryname', 'diff', 'difference', 'different', 'diffuse-reflectivity', - 'digdag', 'digit-width', 'dim', 'dimension', 'dip', 'dir', 'dir-target', - 'dircolors', 'direct', 'direction', 'directive', 'directive-option', - 'directives', 'directory', 'dirjs', 'dirtyblue', 'dirtygreen', 'disable', - 'disable-markdown', 'disable-todo', 'discarded', 'discusson', 'disjunction', - 'disk', 'disk-folder-file', 'dism', 'displacement', 'display', 'dissolve', - 'dissolve-interpolation', 'distribution', 'diverging-function', 'divert', - 'divide', 'divider', 'django', 'dl', 'dlv', 'dm', 'dmf', 'dml', 'do', - 'dobody', 'doc', 'doc-comment', 'docRoot', 'dockerfile', 'dockerignore', - 'doconce', 'docstring', 'doctest', 'doctree-option', 'doctype', 'document', - 'documentation', 'documentroot', 'does', 'dogescript', 'doki', 'dollar', - 'dollar-quote', 'dollar_variable', 'dom', 'domain', 'dontcollect', 'doors', - 'dop', 'dot', 'dot-access', 'dotenv', 'dotfiles', 'dothandout', 'dotnet', - 'dotnote', 'dots', 'dotted', 'dotted-circle', 'dotted-del', 'dotted-greater', - 'dotted-tack-up', 'double', 'double-arrow', 'double-colon', 'double-dash', - 'double-dash-not-allowed', 'double-dot', 'double-number-sign', - 'double-percentage', 'double-qoute', 'double-quote', 'double-quoted', - 'double-quoted-string', 'double-semicolon', 'double-slash', 'doublequote', - 'doubleslash', 'dougle', 'down', 'download', 'downwards', 'doxyfile', - 'doxygen', 'dragdrop', 'drawing', 'drive', 'droiuby', 'drop', 'drop-shadow', - 'droplevel', 'drummode', 'drupal', 'dsl', 'dsv', 'dt', 'dtl', 'due', 'dummy', - 'dummy-variable', 'dump', 'duration', 'dust', 'dust_Conditional', - 'dust_end_section_tag', 'dust_filter', 'dust_partial', - 'dust_partial_not_self_closing', 'dust_ref', 'dust_ref_name', - 'dust_section_context', 'dust_section_name', 'dust_section_params', - 'dust_self_closing_section_tag', 'dust_special', 'dust_start_section_tag', - 'dustjs', 'dut', 'dwscript', 'dxl', 'dylan', 'dynamic', 'dyndoc', 'dyon', 'e', - 'e3globals', 'each', 'eachin', 'earl-grey', 'ebnf', 'ebuild', 'echo', - 'eclass', 'ecmascript', 'eco', 'ecr', 'ect', 'ect2', 'ect3', 'ect4', 'edasm', - 'edge', 'edit-manager', 'editfields', 'editors', 'ee', 'eex', 'effect', - 'effectgroup', 'effective_routine_body', 'effects', 'eiffel', 'eight', 'eio', - 'eiz', 'ejectors', 'el', 'elasticsearch', 'elasticsearch2', 'element', - 'elements', 'elemnt', 'elif', 'elipse', 'elision', 'elixir', 'ellipsis', - 'elm', 'elmx', 'else', 'else-condition', 'else-if', 'elseif', - 'elseif-condition', 'elsewhere', 'eltype', 'elvis', 'em', 'email', 'embed', - 'embed-diversion', 'embedded', 'embedded-c', 'embedded-ruby', 'embedded2', - 'embeded', 'ember', 'emberscript', 'emblem', 'embperl', 'emissive-colour', - 'eml', 'emlist', 'emoji', 'emojicode', 'emp', 'emph', 'emphasis', 'empty', - 'empty-dictionary', 'empty-list', 'empty-parenthesis', 'empty-start', - 'empty-string', 'empty-tag', 'empty-tuple', 'empty-typing-pair', 'empty_gif', - 'emptyelement', 'en', 'en-Scouse', 'en-au', 'en-lol', 'en-old', 'en-pirate', - 'enable', 'enc', 'enchant', 'enclose', 'encode', 'encoding', 'encryption', - 'end', 'end-block-data', 'end-definition', 'end-document', 'end-enum', - 'end-footnote', 'end-of-line', 'end-statement', 'end-value', 'endassociate', - 'endcode', 'enddo', 'endfile', 'endforall', 'endfunction', 'endian', - 'endianness', 'endif', 'endinfo', 'ending', 'ending-space', 'endinterface', - 'endlocaltable', 'endmodule', 'endobject', 'endobjecttable', 'endparamtable', - 'endprogram', 'endproperty', 'endpropertygroup', 'endpropertygrouptable', - 'endpropertytable', 'endselect', 'endstate', 'endstatetable', 'endstruct', - 'endstructtable', 'endsubmodule', 'endsubroutine', 'endtimeblock', 'endtype', - 'enduserflagsref', 'endvariable', 'endvariabletable', 'endwhere', 'engine', - 'enterprise', 'entity', 'entity-creation-and-abolishing', - 'entity_instantiation', 'entry', 'entry-definition', 'entry-key', - 'entry-type', 'entrypoint', 'enum', 'enum-block', 'enum-declaration', - 'enumeration', 'enumerator', 'enumerator-specification', 'env', 'environment', - 'environment-variable', 'eo', 'eof', 'epatch', 'eq', 'eqn', 'eqnarray', - 'equal', 'equal-or-greater', 'equal-or-less', 'equalexpr', 'equality', - 'equals', 'equals-sign', 'equation', 'equation-label', 'erb', 'ereg', - 'erlang', 'error', 'error-control', 'errorfunc', 'errorstop', 'es', 'es6', - 'es6import', 'esc', 'escape', 'escape-char', 'escape-code', 'escape-sequence', - 'escape-unicode', 'escaped', 'escapes', 'escript', 'eso-lua', 'eso-txt', - 'essence', 'et', 'eth', 'ethaddr', 'etml', 'etpl', 'eudoc', 'euler', - 'euphoria', 'european', 'evaled', 'evaluable', 'evaluation', 'even-tab', - 'event', 'event-call', 'event-handler', 'event-handling', 'event-schedulling', - 'eventType', 'eventb', 'eventend', 'events', 'evnd', 'exactly', 'example', - 'exampleText', 'examples', 'exceeding-sections', 'excel-link', 'exception', - 'exceptions', 'exclaimation-point', 'exclamation', 'exec', 'exec-command', - 'execution-context', 'exif', 'existential', 'exit', 'exp', 'expand-register', - 'expanded', 'expansion', 'expected-array-separator', - 'expected-dictionary-separator', 'expected-extends', 'expected-implements', - 'expected-range-separator', 'experimental', 'expires', 'expl3', 'explosion', - 'exponent', 'exponential', 'export', 'exports', 'expr', 'expression', - 'expression-separator', 'expression-seperator', 'expressions', - 'expressions-and-types', 'exprwrap', 'ext', 'extempore', 'extend', 'extended', - 'extends', 'extension', 'extension-specification', 'extensions', 'extern', - 'extern-block', 'external', 'external-call', 'external-signature', 'extersk', - 'extglob', 'extra', 'extra-characters', 'extra-equals-sign', 'extracted', - 'extras', 'extrassk', 'exxample', 'eztpl', 'f', 'f5networks', 'fa', 'face', - 'fact', 'factor', 'factorial', 'fadeawayheight', 'fadeawaywidth', 'fail', - 'fakeroot', 'fallback', 'fallout4', 'false', 'fandoc', 'fann', 'fantom', - 'fastcgi', 'fbaccidental', 'fbfigure', 'fbgroupclose', 'fbgroupopen', 'fbp', - 'fctn', 'fe', 'feature', 'features', 'feedrate', 'fenced', 'fftwfn', 'fhem', - 'fi', 'field', 'field-assignment', 'field-completions', 'field-id', - 'field-level-comment', 'field-name', 'field-tag', 'fields', 'figbassmode', - 'figure', 'figuregroup', 'filder-design-hdl-coder', 'file', 'file-i-o', - 'file-io', 'file-name', 'file-object', 'file-path', 'fileinfo', 'filename', - 'filepath', 'filetest', 'filter', 'filter-pipe', 'filteredtranscludeblock', - 'filters', 'final', 'final-procedure', 'finally', 'financial', - 'financial-derivatives', 'find', 'find-in-files', 'find-m', 'finder', - 'finish', 'finn', 'fire', 'firebug', 'first', 'first-class', 'first-line', - 'fish', 'fitnesse', 'five', 'fix_this_later', 'fixed', 'fixed-income', - 'fixed-point', 'fixme', 'fl', 'flag', 'flag-control', 'flags', 'flash', - 'flatbuffers', 'flex-config', 'fload', 'float', 'float-exponent', 'float_exp', - 'floating-point', 'floating_point', 'floor', 'flow', 'flow-control', - 'flowcontrol', 'flows', 'flowtype', 'flush', 'fma', 'fma4', 'fmod', 'fn', - 'fold', 'folder', 'folder-actions', 'following', 'font', - 'font-cache', 'font-face', 'font-name', 'font-size', 'fontface', 'fontforge', - 'foobar', 'footer', 'footnote', 'for', 'for-in-loop', 'for-loop', - 'for-quantity', 'forall', 'force', 'foreach', 'foreign', 'forever', - 'forge-config', 'forin', 'form', 'form-feed', 'formal', 'format', - 'format-register', 'format-verb', 'formatted', 'formatter', 'formatting', - 'forth', 'fortran', 'forward', 'foundation', 'fountain', 'four', - 'fourd-command', 'fourd-constant', 'fourd-constant-hex', - 'fourd-constant-number', 'fourd-constant-string', 'fourd-control-begin', - 'fourd-control-end', 'fourd-declaration', 'fourd-declaration-array', - 'fourd-local-variable', 'fourd-parameter', 'fourd-table', 'fourd-tag', - 'fourd-variable', 'fpm', 'fpu', 'fpu_x87', 'fr', 'fragment', 'frame', - 'frames', 'frametitle', 'framexml', 'free', 'free-form', 'freebasic', - 'freefem', 'freespace2', 'from', 'from-file', 'front-matter', 'fs', 'fs2', - 'fsc', 'fsgsbase', 'fsharp', 'fsi', 'fsl', 'fsm', 'fsp', 'fsx', 'fth', 'ftl', - 'ftl20n', 'full-line', 'full-stop', 'fun', 'funarg', 'func-tag', 'func_call', - 'funchand', 'function', 'function-arity', 'function-attribute', - 'function-call', 'function-definition', 'function-literal', - 'function-parameter', 'function-recursive', 'function-return', - 'function-type', 'functionDeclaration', 'functionDefinition', - 'function_definition', 'function_prototype', 'functional_test', 'functionend', - 'functions', 'functionstart', 'fundimental', 'funk', 'funtion-definition', - 'fus', 'future', 'futures', 'fuzzy-logic', 'fx', 'fx-foliage-replicator', - 'fx-light', 'fx-shape-replicator', 'fx-sun-light', 'g', 'g-code', 'ga', - 'gain', 'galaxy', 'gallery', 'game-base', 'game-connection', 'game-server', - 'gamebusk', 'gamescript', 'gams', 'gams-lst', 'gap', 'garch', 'gather', - 'gcode', 'gdb', 'gdscript', 'gdx', 'ge', 'geant4-macro', 'geck', - 'geck-keyword', 'general', 'general-purpose', 'generate', 'generator', - 'generic', 'generic-config', 'generic-spec', 'generic-type', 'generic_list', - 'genericcall', 'generics', 'genetic-algorithms', 'geo', 'geometric', - 'geometry', 'geometry-adjustment', 'get', 'getproperty', 'getsec', 'getset', - 'getter', 'gettext', 'getword', 'gfm', 'gfm-todotxt', 'gfx', 'gh-number', - 'gherkin', 'gisdk', 'git', 'git-attributes', 'git-commit', 'git-config', - 'git-rebase', 'gitignore', 'given', 'gj', 'gl', 'glob', 'global', - 'global-functions', 'globals', 'globalsection', 'glsl', 'glue', - 'glyph_class_name', 'glyphname-value', 'gml', 'gmp', 'gmsh', 'gmx', 'gn', - 'gnu', 'gnuplot', 'go', 'goal', 'goatee', 'godmode', 'gohtml', 'gold', 'golo', - 'google', 'gosub', 'gotemplate', 'goto', 'goto-label', 'gpd', 'gpd_note', - 'gpp', 'grace', 'grade-down', 'grade-up', 'gradient', 'gradle', 'grails', - 'grammar', 'grammar-rule', 'grammar_production', 'grap', 'grapahql', 'graph', - 'graphics', 'graphql', 'grave-accent', 'gray', 'greater', 'greater-equal', - 'greater-or-equal', 'greek', 'greek-letter', 'green', 'gremlin', 'grey', - 'grg', 'grid-table', 'gridlists', 'grog', 'groovy', 'groovy-properties', - 'group', 'group-level-comment', 'group-name', 'group-number', - 'group-reference', 'group-title', 'group1', 'group10', 'group11', 'group2', - 'group3', 'group4', 'group5', 'group6', 'group7', 'group8', 'group9', - 'groupend', 'groupflag', 'grouping-statement', 'groupname', 'groupstart', - 'growl', 'grr', 'gs', 'gsc', 'gsp', 'gt', 'guard', 'guards', 'gui', - 'gui-bitmap-ctrl', 'gui-button-base-ctrl', 'gui-canvas', 'gui-control', - 'gui-filter-ctrl', 'gui-frameset-ctrl', 'gui-menu-bar', - 'gui-message-vector-ctrl', 'gui-ml-text-ctrl', 'gui-popup-menu-ctrl', - 'gui-scroll-ctrl', 'gui-slider-ctrl', 'gui-text-ctrl', 'gui-text-edit-ctrl', - 'gui-text-list-ctrl', 'guid', 'guillemot', 'guis', 'gzip', 'gzip_static', 'h', - 'h1', 'hack', 'hackfragment', 'haddock', 'hairpin', 'ham', 'haml', 'hamlbars', - 'hamlc', 'hamlet', 'hamlpy', 'handlebar', 'handlebars', 'handler', - 'hanging-paragraph', 'haproxy-config', 'harbou', 'harbour', 'hard-break', - 'hardlinebreaks', 'hash', 'hash-tick', 'hashbang', 'hashicorp', 'hashkey', - 'haskell', 'haxe', 'hbs', 'hcl', 'hdl', 'hdr', 'he', 'header', - 'header-continuation', 'header-value', 'headername', 'headers', 'heading', - 'heading-0', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', - 'heading-6', 'height', 'helen', 'help', 'helper', 'helpers', 'heredoc', - 'heredoc-token', 'herestring', 'heritage', 'hex', 'hex-ascii', 'hex-byte', - 'hex-literal', 'hex-old', 'hex-string', 'hex-value', 'hex8', 'hexadecimal', - 'hexidecimal', 'hexprefix', 'hg-commit', 'hgignore', 'hi', 'hidden', 'hide', - 'high-minus', 'highlight-end', 'highlight-group', - 'highlight-start', 'hint', 'history', 'hive', 'hive-name', 'hjson', 'hl7', - 'hlsl', 'hn', 'hoa', 'hoc', 'hocharacter', 'hocomment', 'hocon', 'hoconstant', - 'hocontinuation', 'hocontrol', 'hombrew-formula', 'homebrew', 'homematic', - 'hook', 'hoon', 'horizontal-blending', 'horizontal-packed-arithmetic', - 'horizontal-rule', 'hostname', 'hosts', 'hour', 'hours', 'hps', 'hql', 'hr', - 'hrm', 'hs', 'hsc2hs', 'ht', 'htaccess', 'htl', 'html', 'html_entity', - 'htmlbars', 'http', 'hu', 'hungary', 'hxml', 'hy', 'hydrant', 'hydrogen', - 'hyperbolic', 'hyperlink', 'hyphen', 'hyphenation', 'hyphenation-char', 'i', - 'i-beam', 'i18n', 'iRev', 'ice', 'icinga2', 'icmc', 'icmptype', 'icmpv6type', - 'icmpxtype', 'iconv', 'id', 'id-type', 'id-with-protocol', 'idd', 'ideal', - 'identical', 'identifer', 'identified', 'identifier', 'identifier-type', - 'identifiers-and-DTDs', 'identity', 'idf', 'idl', 'idris', 'ieee', 'if', - 'if-block', 'if-branch', 'if-condition', 'if-else', 'if-then', 'ifacespec', - 'ifdef', 'ifname', 'ifndef', 'ignore', 'ignore-eol', 'ignore-errors', - 'ignorebii', 'ignored', 'ignored-binding', 'ignoring', 'iisfunc', 'ijk', - 'ilasm', 'illagal', 'illeagal', 'illegal', 'illumination-model', 'image', - 'image-acquisition', 'image-alignment', 'image-option', 'image-processing', - 'images', 'imap', 'imba', 'imfchan', 'img', 'immediate', - 'immediately-evaluated', 'immutable', 'impex', 'implementation', - 'implementation-defined-hooks', 'implemented', 'implements', 'implicit', - 'import', 'import-all', 'importall', 'important', 'in', 'in-block', - 'in-module', 'in-out', 'inappropriate', 'include', 'include-statement', - 'includefile', 'incomplete', 'incomplete-variable-assignment', 'inconsistent', - 'increment', 'increment-decrement', 'indent', 'indented', - 'indented-paragraph', 'indepimage', 'index', 'index-seperator', 'indexed', - 'indexer', 'indexes', 'indicator', 'indices', 'indirect', 'indirection', - 'individual-enum-definition', 'individual-rpc-call', 'inet', 'inetprototype', - 'inferred', 'infes', 'infinity', 'infix', 'info', 'inform', 'inform6', - 'inform7', 'infotype', 'ingore-eol', 'inherit', 'inheritDoc', 'inheritance', - 'inherited', 'inherited-class', 'inherited-struct', 'inherits', 'ini', 'init', - 'initial-lowercase', 'initial-uppercase', 'initial-value', 'initialization', - 'initialize', 'initializer-list', 'ink', 'inline', 'inline-data', - 'inlineConditionalBranchSeparator', 'inlineConditionalClause', - 'inlineConditionalEnd', 'inlineConditionalStart', 'inlineLogicEnd', - 'inlineLogicStart', 'inlineSequenceEnd', 'inlineSequenceSeparator', - 'inlineSequenceStart', 'inlineSequenceTypeChar', 'inlineblock', 'inlinecode', - 'inlinecomment', 'inlinetag', 'inner', 'inner-class', 'inno', 'ino', 'inout', - 'input', 'inquire', 'inserted', 'insertion', 'insertion-and-extraction', - 'inside', 'install', 'instance', 'instancemethods', 'instanceof', 'instances', - 'instantiation', 'instruction', 'instruction-pointer', 'instructions', - 'instrument', 'instrument-block', 'instrument-control', - 'instrument-declaration', 'int', 'int32', 'int64', 'integer', 'integer-float', - 'intel', 'intel-hex', 'intent', 'intepreted', 'interaction', 'interbase', - 'interface', 'interface-block', 'interface-or-protocol', 'interfaces', - 'interior-instance', 'interiors', 'interlink', 'internal', 'internet', - 'interpolate-argument', 'interpolate-string', 'interpolate-variable', - 'interpolated', 'interpolation', 'interrupt', 'intersection', 'interval', - 'intervalOrList', 'intl', 'intrinsic', 'intuicio4', 'invalid', - 'invalid-character', 'invalid-character-escape', 'invalid-inequality', - 'invalid-quote', 'invalid-variable-name', 'invariant', 'invocation', 'invoke', - 'invokee', 'io', 'ior', 'iota', 'ip', 'ip-port', 'ip6', 'ipkg', 'ipsec', - 'ipv4', 'ipv6', 'ipynb', 'irct', 'irule', 'is', 'isa', 'isc', 'iscexport', - 'isclass', 'isml', 'issue', 'it', 'italic', 'italic-text', 'item', - 'item-access', 'itemlevel', 'items', 'iteration', 'itunes', 'ivar', 'ja', - 'jack', 'jade', 'jakefile', 'jasmin', 'java', 'java-properties', 'java-props', - 'javadoc', 'javascript', 'jbeam', 'jekyll', 'jflex', 'jibo-rule', 'jinja', - 'jison', 'jisonlex', 'jmp', 'joint', 'joker', 'jolie', 'jot', 'journaling', - 'jpl', 'jq', 'jquery', 'js', 'js-label', 'jsdoc', 'jsduck', 'jsim', 'json', - 'json5', 'jsoniq', 'jsonnet', 'jsont', 'jsp', 'jsx', 'julia', 'julius', - 'jump', 'juniper', 'juniper-junos-config', 'junit-test-report', 'junos', - 'juttle', 'jv', 'jxa', 'k', 'kag', 'kagex', 'kb', 'kbd', 'kconfig', - 'kerboscript', 'kernel', 'kevs', 'kevscript', 'kewyword', 'key', - 'key-assignment', 'key-letter', 'key-pair', 'key-path', 'key-value', - 'keyboard', 'keyframe', 'keyframes', 'keygroup', 'keyname', 'keyspace', - 'keyspace-name', 'keyvalue', 'keyword', 'keyword-parameter', 'keyword1', - 'keyword2', 'keyword3', 'keyword4', 'keyword5', 'keyword6', 'keyword7', - 'keyword8', 'keyword_arrays', 'keyword_objects', 'keyword_roots', - 'keyword_string', 'keywords', 'keywork', 'kickstart', 'kind', 'kmd', 'kn', - 'knitr', 'knockout', 'knot', 'ko', 'ko-virtual', 'kos', 'kotlin', 'krl', - 'ksp-cfg', 'kspcfg', 'kurumin', 'kv', 'kxi', 'kxigauge', 'l', 'l20n', - 'l4proto', 'label', 'label-expression', 'labeled', 'labeled-parameter', - 'labelled-thing', 'lagda', 'lambda', 'lambda-function', 'lammps', 'langref', - 'language', 'language-range', 'languagebabel', 'langversion', 'largesk', - 'lasso', 'last', 'last-paren-match', 'latex', 'latex2', 'latino', 'latte', - 'launch', 'layout', 'layoutbii', 'lbsearch', 'lc', 'lc-3', 'lcb', 'ldap', - 'ldif', 'le', 'leader-char', 'leading', 'leading-space', 'leading-tabs', - 'leaf', 'lean', 'ledger', 'left', 'left-margin', 'leftshift', 'lefttoright', - 'legacy', 'legacy-setting', 'lemon', 'len', 'length', 'leopard', 'less', - 'less-equal', 'less-or-equal', 'let', 'letter', 'level', 'level-of-detail', - 'level1', 'level2', 'level3', 'level4', 'level5', 'level6', 'levels', 'lex', - 'lexc', 'lexical', 'lf-in-string', 'lhs', 'li', 'lib', 'libfile', 'library', - 'libs', 'libxml', 'lid', 'lifetime', 'ligature', 'light', 'light_purple', - 'lighting', 'lightning', 'lilypond', 'lilypond-drummode', - 'lilypond-figbassmode', 'lilypond-figuregroup', 'lilypond-internals', - 'lilypond-lyricsmode', 'lilypond-markupmode', 'lilypond-notedrum', - 'lilypond-notemode', 'lilypond-notemode-explicit', 'lilypond-notenames', - 'lilypond-schememode', 'limit_zone', 'line-block', 'line-break', - 'line-continuation', 'line-cue-setting', 'line-statement', - 'line-too-long', 'linebreak', 'linenumber', 'link', 'link-label', - 'link-text', 'link-url', 'linkage', 'linkage-type', 'linkedin', - 'linkedsockets', 'linkplain', 'linkplain-label', 'linq', 'linuxcncgcode', - 'liquid', 'liquidhaskell', 'liquidsoap', 'lisp', 'lisp-repl', 'list', - 'list-done', 'list-separator', 'list-style-type', 'list-today', 'list_item', - 'listing', 'listnum', 'listvalues', 'litaco', 'litcoffee', 'literal', - 'literal-string', 'literate', 'litword', 'livecodescript', 'livescript', - 'livescriptscript', 'll', 'llvm', 'load-constants', 'load-hint', 'loader', - 'local', 'local-variables', 'localhost', 'localizable', 'localized', - 'localname', 'locals', 'localtable', 'location', 'lock', 'log', 'log-debug', - 'log-error', 'log-failed', 'log-info', 'log-patch', 'log-success', - 'log-verbose', 'log-warning', 'logarithm', 'logging', 'logic', 'logicBegin', - 'logical', 'logical-expression', 'logicblox', 'logicode', 'logo', 'logstash', - 'logtalk', 'lol', 'long', 'look-ahead', 'look-behind', 'lookahead', - 'lookaround', 'lookbehind', 'loop', 'loop-control', 'low-high', 'lowercase', - 'lowercase_character_not_allowed_here', 'lozenge', 'lparen', 'lsg', 'lsl', - 'lst', 'lst-cpu12', 'lstdo', 'lt', 'lt-gt', 'lterat', 'lu', 'lua', 'lucee', - 'lucius', 'lury', 'lv', 'lyricsmode', 'm', 'm4', 'm4sh', 'm65816', 'm68k', - 'mac-classic', 'mac-fsaa', 'machine', 'machineclause', 'macro', 'macro-usage', - 'macro11', 'macrocallblock', 'macrocallinline', 'madoko', 'magenta', 'magic', - 'magik', 'mail', 'mailer', 'mailto', 'main', 'makefile', 'makefile2', 'mako', - 'mamba', 'man', 'mantissa', 'manualmelisma', 'map', 'map-library', 'map-name', - 'mapfile', 'mapkey', 'mapping', 'mapping-type', 'maprange', 'marasm', - 'margin', 'marginpar', 'mark', 'mark-input', 'markdown', 'marker', 'marko', - 'marko-attribute', 'marko-tag', 'markup', 'markupmode', 'mas2j', 'mask', - 'mason', 'mat', 'mata', 'match', 'match-bind', 'match-branch', - 'match-condition', 'match-definition', 'match-exception', 'match-option', - 'match-pattern', 'material', 'material-library', 'material-name', 'math', - 'math-symbol', 'math_complex', 'math_real', 'mathematic', 'mathematica', - 'mathematical', 'mathematical-symbols', 'mathematics', 'mathjax', 'mathml', - 'matlab', 'matrix', 'maude', 'maven', 'max', 'max-angle', 'max-distance', - 'max-length', 'maxscript', 'maybe', 'mb', 'mbstring', 'mc', 'mcc', 'mccolor', - 'mch', 'mcn', 'mcode', 'mcq', 'mcr', 'mcrypt', 'mcs', 'md', 'mdash', 'mdoc', - 'mdx', 'me', 'measure', 'media', 'media-feature', 'media-property', - 'media-type', 'mediawiki', 'mei', 'mel', 'memaddress', 'member', - 'member-function-attribute', 'member-of', 'membership', 'memcache', - 'memcached', 'memoir', 'memoir-alltt', 'memoir-fbox', 'memoir-verbatim', - 'memory', 'memory-management', 'memory-protection', 'memos', 'menhir', - 'mention', 'menu', 'mercury', 'merge-group', 'merge-key', 'merlin', - 'mesgTrigger', 'mesgType', 'message', 'message-declaration', - 'message-forwarding-handler', 'message-sending', 'message-vector', 'messages', - 'meta', 'meta-conditional', 'meta-data', 'meta-file', 'meta-info', - 'metaclass', 'metacommand', 'metadata', 'metakey', 'metamodel', 'metapost', - 'metascript', 'meteor', 'method', 'method-call', 'method-definition', - 'method-modification', 'method-mofification', 'method-parameter', - 'method-parameters', 'method-restriction', 'methodcalls', 'methods', - 'metrics', 'mhash', 'microsites', 'microsoft-dynamics', 'middle', - 'midi_processing', 'migration', 'mime', 'min', 'minelua', 'minetweaker', - 'minitemplate', 'minitest', 'minus', 'minute', 'mips', 'mirah', 'misc', - 'miscellaneous', 'mismatched', 'missing', 'missing-asterisk', - 'missing-inheritance', 'missing-parameters', 'missing-section-begin', - 'missingend', 'mission-area', 'mixin', 'mixin-name', 'mjml', 'ml', 'mlab', - 'mls', 'mm', 'mml', 'mmx', 'mmx_instructions', 'mn', 'mnemonic', - 'mobile-messaging', 'mochi', 'mod', 'mod-r', 'mod_perl', 'mod_perl_1', - 'modblock', 'modbus', 'mode', 'model', 'model-based-calibration', - 'model-predictive-control', 'modelica', 'modelicascript', 'modeline', - 'models', 'modern', 'modified', 'modifier', 'modifiers', 'modify', - 'modify-range', 'modifytime', 'modl', 'modr', 'modula-2', 'module', - 'module-alias', 'module-binding', 'module-definition', 'module-expression', - 'module-function', 'module-reference', 'module-rename', 'module-sum', - 'module-type', 'module-type-definition', 'modules', 'modulo', 'modx', - 'mojolicious', 'mojom', 'moment', 'mond', 'money', 'mongo', 'mongodb', - 'monicelli', 'monitor', 'monkberry', 'monkey', 'monospace', 'monospaced', - 'monte', 'month', 'moon', 'moonscript', 'moos', 'moose', 'moosecpp', 'motion', - 'mouse', 'mov', 'movement', 'movie', 'movie-file', 'mozu', 'mpw', 'mpx', - 'mqsc', 'ms', 'mscgen', 'mscript', 'msg', 'msgctxt', 'msgenny', 'msgid', - 'msgstr', 'mson', 'mson-block', 'mss', 'mta', 'mtl', 'mucow', 'mult', 'multi', - 'multi-line', 'multi-symbol', 'multi-threading', 'multiclet', 'multids-file', - 'multiline', 'multiline-cell', 'multiline-text-reference', - 'multiline-tiddler-title', 'multimethod', 'multipart', 'multiplication', - 'multiplicative', 'multiply', 'multiverse', 'mumps', 'mundosk', 'music', - 'must_be', 'mustache', 'mut', 'mutable', 'mutator', 'mx', 'mxml', 'mydsl1', - 'mylanguage', 'mysql', 'mysqli', 'mysqlnd-memcache', 'mysqlnd-ms', - 'mysqlnd-qc', 'mysqlnd-uh', 'mzn', 'nabla', 'nagios', 'name', 'name-list', - 'name-of-parameter', 'named', 'named-char', 'named-key', 'named-tuple', - 'nameless-typed', 'namelist', 'names', 'namespace', 'namespace-block', - 'namespace-definition', 'namespace-language', 'namespace-prefix', - 'namespace-reference', 'namespace-statement', 'namespaces', 'nan', 'nand', - 'nant', 'nant-build', 'narration', 'nas', 'nasal', 'nasl', 'nasm', 'nastran', - 'nat', 'native', 'nativeint', 'natural', 'navigation', 'nbtkey', 'ncf', 'ncl', - 'ndash', 'ne', 'nearley', 'neg-ratio', 'negatable', 'negate', 'negated', - 'negation', 'negative', 'negative-look-ahead', 'negative-look-behind', - 'negativity', 'nesc', 'nessuskb', 'nested', 'nested_braces', - 'nested_brackets', 'nested_ltgt', 'nested_parens', 'nesty', 'net', - 'net-object', 'netbios', 'network', 'network-value', 'networking', - 'neural-network', 'new', 'new-line', 'new-object', 'newline', - 'newline-spacing', 'newlinetext', 'newlisp', 'newobject', 'nez', 'nft', - 'ngdoc', 'nginx', 'nickname', 'nil', 'nim', 'nine', 'ninja', 'ninjaforce', - 'nit', 'nitro', 'nix', 'nl', 'nlf', 'nm', 'nm7', 'no', 'no-capture', - 'no-completions', 'no-content', 'no-default', 'no-indent', - 'no-leading-digits', 'no-trailing-digits', 'no-validate-params', 'node', - 'nogc', 'noindent', 'nokia-sros-config', 'non', 'non-capturing', - 'non-immediate', 'non-null-typehinted', 'non-standard', 'non-terminal', - 'nondir-target', 'none', 'none-parameter', 'nonlocal', 'nonterminal', 'noon', - 'noop', 'nop', 'noparams', 'nor', 'normal', 'normal_numeric', - 'normal_objects', 'normal_text', 'normalised', 'not', 'not-a-number', - 'not-equal', 'not-identical', 'notation', 'note', 'notechord', 'notemode', - 'notequal', 'notequalexpr', 'notes', 'notidentical', 'notification', 'nowdoc', - 'noweb', 'nrtdrv', 'nsapi', 'nscript', 'nse', 'nsis', 'nsl', 'ntriples', - 'nul', 'null', 'nullify', 'nullological', 'nulltype', 'num', 'number', - 'number-sign', 'number-sign-equals', 'numbered', 'numberic', 'numbers', - 'numbersign', 'numeric', 'numeric_std', 'numerical', 'nunjucks', 'nut', - 'nvatom', 'nxc', 'o', 'obj', 'objaggregation', 'objc', 'objcpp', 'objdump', - 'object', 'object-comments', 'object-definition', 'object-level-comment', - 'object-name', 'objects', 'objectset', 'objecttable', 'objectvalues', 'objj', - 'obsolete', 'ocaml', 'ocamllex', 'occam', 'oci8', 'ocmal', 'oct', 'octal', - 'octave', 'octave-change', 'octave-shift', 'octet', 'octo', 'octobercms', - 'octothorpe', 'odd-tab', 'odedsl', 'ods', 'of', 'off', 'offset', 'ofx', - 'ogre', 'ok', 'ol', 'old', 'old-style', 'omap', 'omitted', 'on-background', - 'on-error', 'once', 'one', 'one-sixth-em', 'one-twelfth-em', 'oniguruma', - 'oniguruma-comment', 'only', 'only-in', 'onoff', 'ooc', 'oot', 'op-domain', - 'op-range', 'opa', 'opaque', 'opc', 'opcache', 'opcode', - 'opcode-argument-types', 'opcode-declaration', 'opcode-definition', - 'opcode-details', 'open', 'open-gl', 'openal', 'openbinding', 'opencl', - 'opendss', 'opening', 'opening-text', 'openmp', 'openssl', 'opentype', - 'operand', 'operands', 'operation', 'operator', 'operator2', 'operator3', - 'operators', 'opmask', 'opmaskregs', 'optical-density', 'optimization', - 'option', 'option-description', 'option-toggle', 'optional', - 'optional-parameter', 'optional-parameter-assignment', 'optionals', - 'optionname', 'options', 'optiontype', 'or', 'oracle', 'orbbasic', 'orcam', - 'orchestra', 'order', 'ordered', 'ordered-block', 'ordinal', 'organized', - 'orgtype', 'origin', 'osiris', 'other', 'other-inherited-class', - 'other_buildins', 'other_keywords', 'others', 'otherwise', - 'otherwise-expression', 'out', 'outer', 'output', 'overload', 'override', - 'owner', 'ownership', 'oz', 'p', 'p4', 'p5', 'p8', 'pa', 'package', - 'package-definition', 'package_body', 'packages', 'packed', - 'packed-arithmetic', 'packed-blending', 'packed-comparison', - 'packed-conversion', 'packed-floating-point', 'packed-integer', 'packed-math', - 'packed-mov', 'packed-other', 'packed-shift', 'packed-shuffle', 'packed-test', - 'padlock', 'page', 'page-props', 'pagebreak', 'pair', 'pair-programming', - 'paket', 'pandoc', 'papyrus', 'papyrus-assembly', 'paragraph', 'parallel', - 'param', 'param-list', 'paramater', 'paramerised-type', 'parameter', - 'parameter-entity', 'parameter-space', 'parameterless', 'parameters', - 'paramless', 'params', 'paramtable', 'paramter', 'paren', 'paren-group', - 'parens', 'parent', 'parent-reference', 'parent-selector', - 'parent-selector-suffix', 'parenthases', 'parentheses', 'parenthesis', - 'parenthetical', 'parenthetical_list', 'parenthetical_pair', 'parfor', - 'parfor-quantity', 'parse', 'parsed', 'parser', 'parser-function', - 'parser-token', 'parser3', 'part', 'partial', 'particle', 'pascal', 'pass', - 'pass-through', 'passive', 'passthrough', 'password', 'password-hash', - 'patch', 'path', 'path-camera', 'path-pattern', 'pathoperation', 'paths', - 'pathspec', 'patientId', 'pattern', 'pattern-argument', 'pattern-binding', - 'pattern-definition', 'pattern-match', 'pattern-offset', 'patterns', 'pause', - 'payee', 'payload', 'pbo', 'pbtxt', 'pcdata', 'pcntl', 'pdd', 'pddl', 'ped', - 'pegcoffee', 'pegjs', 'pending', 'percentage', 'percentage-sign', - 'percussionnote', 'period', 'perl', 'perl-section', 'perl6', 'perl6fe', - 'perlfe', 'perlt6e', 'perm', 'permutations', 'personalization', 'pervasive', - 'pf', 'pflotran', 'pfm', 'pfx', 'pgn', 'pgsql', 'phone', 'phone-number', - 'phonix', 'php', 'php-code-in-comment', 'php_apache', 'php_dom', 'php_ftp', - 'php_imap', 'php_mssql', 'php_odbc', 'php_pcre', 'php_spl', 'php_zip', - 'phpdoc', 'phrasemodifiers', 'phraslur', 'physical-zone', 'physics', 'pi', - 'pic', 'pick', 'pickup', 'picture', 'pig', 'pillar', 'pipe', 'pipe-sign', - 'pipeline', 'piratesk', 'pitch', 'pixie', 'pkgbuild', 'pl', 'placeholder', - 'placeholder-parts', 'plain', 'plainsimple-emphasize', 'plainsimple-heading', - 'plainsimple-number', 'plantuml', 'player', 'playerversion', 'pld_modeling', - 'please-build', 'please-build-defs', 'plist', 'plsql', 'plugin', 'plus', - 'plztarget', 'pmc', 'pml', 'pmlPhysics-arrangecharacter', - 'pmlPhysics-emphasisequote', 'pmlPhysics-graphic', 'pmlPhysics-header', - 'pmlPhysics-htmlencoded', 'pmlPhysics-links', 'pmlPhysics-listtable', - 'pmlPhysics-physicalquantity', 'pmlPhysics-relationships', - 'pmlPhysics-slides', 'pmlPhysics-slidestacks', 'pmlPhysics-speech', - 'pmlPhysics-structure', 'pnt', 'po', 'pod', 'poe', 'pogoscript', 'point', - 'point-size', 'pointer', 'pointer-arith', 'pointer-following', 'points', - 'polarcoord', 'policiesbii', 'policy', 'polydelim', 'polygonal', 'polymer', - 'polymorphic', 'polymorphic-variant', 'polynomial-degree', 'polysep', 'pony', - 'port', 'port_list', 'pos-ratio', 'position-cue-setting', 'positional', - 'positive', 'posix', 'posix-reserved', 'post-match', 'postblit', 'postcss', - 'postfix', 'postpone', 'postscript', 'potigol', 'potion', 'pound', - 'pound-sign', 'povray', 'power', 'power_set', 'powershell', 'pp', 'ppc', - 'ppcasm', 'ppd', 'praat', 'pragma', 'pragma-all-once', 'pragma-mark', - 'pragma-message', 'pragma-newline-spacing', 'pragma-newline-spacing-value', - 'pragma-once', 'pragma-stg', 'pragma-stg-value', 'pre', 'pre-defined', - 'pre-match', 'preamble', 'prec', 'precedence', 'precipitation', 'precision', - 'precision-point', 'pred', 'predefined', 'predicate', 'prefetch', - 'prefetchwt', 'prefix', 'prefixed-uri', 'prefixes', 'preinst', 'prelude', - 'prepare', 'prepocessor', 'preposition', 'prepositional', 'preprocessor', - 'prerequisites', 'preset', 'preview', 'previous', 'prg', 'primary', - 'primitive', 'primitive-datatypes', 'primitive-field', 'print', - 'print-argument', 'priority', 'prism', 'private', 'privileged', 'pro', - 'probe', 'proc', 'procedure', 'procedure_definition', 'procedure_prototype', - 'process', 'process-id', 'process-substitution', 'processes', 'processing', - 'proctitle', 'production', 'profile', 'profiling', 'program', 'program-block', - 'program-name', 'progressbars', 'proguard', 'project', 'projectile', 'prolog', - 'prolog-flags', 'prologue', 'promoted', 'prompt', 'prompt-prefix', 'prop', - 'properties', 'properties_literal', 'property', 'property-flag', - 'property-list', 'property-name', 'property-value', - 'property-with-attributes', 'propertydef', 'propertyend', 'propertygroup', - 'propertygrouptable', 'propertyset', 'propertytable', 'proposition', - 'protection', 'protections', 'proto', 'protobuf', 'protobufs', 'protocol', - 'protocol-specification', 'prototype', 'provision', 'proxy', 'psci', 'pseudo', - 'pseudo-class', 'pseudo-element', 'pseudo-method', 'pseudo-mnemonic', - 'pseudo-variable', 'pshdl', 'pspell', 'psql', 'pt', 'ptc-config', - 'ptc-config-modelcheck', 'pthread', 'ptr', 'ptx', 'public', 'pug', - 'punchcard', 'punctual', 'punctuation', 'punctutation', 'puncuation', - 'puncutation', 'puntuation', 'puppet', 'purebasic', 'purescript', 'pweave', - 'pwisa', 'pwn', 'py2pml', 'pyj', 'pyjade', 'pymol', 'pyresttest', 'python', - 'python-function', 'q', 'q-brace', 'q-bracket', 'q-ltgt', 'q-paren', 'qa', - 'qm', 'qml', 'qos', 'qoute', 'qq', 'qq-brace', 'qq-bracket', 'qq-ltgt', - 'qq-paren', 'qry', 'qtpro', 'quad', 'quad-arrow-down', 'quad-arrow-left', - 'quad-arrow-right', 'quad-arrow-up', 'quad-backslash', 'quad-caret-down', - 'quad-caret-up', 'quad-circle', 'quad-colon', 'quad-del-down', 'quad-del-up', - 'quad-diamond', 'quad-divide', 'quad-equal', 'quad-jot', 'quad-less', - 'quad-not-equal', 'quad-question', 'quad-quote', 'quad-slash', 'quadrigraph', - 'qual', 'qualified', 'qualifier', 'quality', 'quant', 'quantifier', - 'quantifiers', 'quartz', 'quasi', 'quasiquote', 'quasiquotes', 'query', - 'query-dsl', 'question', 'questionmark', 'quicel', 'quicktemplate', - 'quicktime-file', 'quotation', 'quote', 'quoted', 'quoted-identifier', - 'quoted-object', 'quoted-or-unquoted', 'quotes', 'qx', 'qx-brace', - 'qx-bracket', 'qx-ltgt', 'qx-paren', 'r', 'r3', 'rabl', 'racket', 'radar', - 'radar-area', 'radiobuttons', 'radix', 'rails', 'rainmeter', 'raml', 'random', - 'random_number', 'randomsk', 'range', 'range-2', 'rank', 'rant', 'rapid', - 'rarity', 'ratio', 'rational-form', 'raw', 'raw-regex', 'raxe', 'rb', 'rd', - 'rdfs-type', 'rdrand', 'rdseed', 'react', 'read', 'readline', 'readonly', - 'readwrite', 'real', 'realip', 'rebeca', 'rebol', 'rec', 'receive', - 'receive-channel', 'recipe', 'recipient-subscriber-list', 'recode', 'record', - 'record-field', 'record-usage', 'recordfield', 'recutils', 'red', - 'redbook-audio', 'redirect', 'redirection', 'redprl', 'redundancy', 'ref', - 'refer', 'reference', 'referer', 'refinement', 'reflection', 'reg', 'regex', - 'regexname', 'regexp', 'regexp-option', 'region-anchor-setting', - 'region-cue-setting', 'region-identifier-setting', 'region-lines-setting', - 'region-scroll-setting', 'region-viewport-anchor-setting', - 'region-width-setting', 'register', 'register-64', 'registers', 'regular', - 'reiny', 'reject', 'rejecttype', 'rel', 'related', 'relation', 'relational', - 'relations', 'relationship', 'relationship-name', 'relationship-pattern', - 'relationship-pattern-end', 'relationship-pattern-start', 'relationship-type', - 'relationship-type-or', 'relationship-type-ored', 'relationship-type-start', - 'relative', 'rem', 'reminder', 'remote', 'removed', 'rename', 'renamed-from', - 'renamed-to', 'renaming', 'render', 'renpy', 'reocrd', 'reparator', 'repeat', - 'repl-prompt', 'replace', 'replaceXXX', 'replaced', 'replacement', 'reply', - 'repo', 'reporter', 'reporting', 'repository', 'request', 'request-type', - 'require', 'required', 'requiredness', 'requirement', 'requirements', - 'rescue', 'reserved', 'reset', 'resolution', 'resource', 'resource-manager', - 'response', 'response-type', 'rest', 'rest-args', 'rester', 'restriced', - 'restructuredtext', 'result', 'result-separator', 'results', 'retro', - 'return', 'return-type', 'return-value', 'returns', 'rev', 'reverse', - 'reversed', 'review', 'rewrite', 'rewrite-condition', 'rewrite-operator', - 'rewrite-pattern', 'rewrite-substitution', 'rewrite-test', 'rewritecond', - 'rewriterule', 'rf', 'rfc', 'rgb', 'rgb-percentage', 'rgb-value', 'rhap', - 'rho', 'rhs', 'rhtml', 'richtext', 'rid', 'right', 'ring', 'riot', - 'rivescript', 'rjs', 'rl', 'rmarkdown', 'rnc', 'rng', 'ro', 'roboconf', - 'robot', 'robotc', 'robust-control', 'rockerfile', 'roff', 'role', - 'rollout-control', 'root', 'rotate', 'rotate-first', 'rotate-last', 'round', - 'round-brackets', 'router', 'routeros', 'routes', 'routine', 'row', 'row2', - 'rowspan', 'roxygen', 'rparent', 'rpc', 'rpc-definition', 'rpe', 'rpm-spec', - 'rpmspec', 'rpt', 'rq', 'rrd', 'rsl', 'rspec', 'rtemplate', 'ru', 'ruby', - 'rubymotion', 'rule', 'rule-identifier', 'rule-name', 'rule-pattern', - 'rule-tag', 'ruleDefinition', 'rules', 'run', 'rune', 'runoff', 'runtime', - 'rust', 'rviz', 'rx', 's', 'safe-call', 'safe-navigation', 'safe-trap', - 'safer', 'safety', 'sage', 'salesforce', 'salt', 'sampler', - 'sampler-comparison', 'samplerarg', 'sampling', 'sas', 'sass', - 'sass-script-maps', 'satcom', 'satisfies', 'sblock', 'scad', 'scala', - 'scaladoc', 'scalar', 'scale', 'scam', 'scan', 'scenario', 'scenario_outline', - 'scene', 'scene-object', 'scheduled', 'schelp', 'schem', 'schema', 'scheme', - 'schememode', 'scientific', 'scilab', 'sck', 'scl', 'scope', 'scope-name', - 'scope-resolution', 'scoping', 'score', 'screen', 'scribble', 'script', - 'script-flag', 'script-metadata', 'script-object', 'script-tag', 'scripting', - 'scriptlet', 'scriptlocal', 'scriptname', 'scriptname-declaration', 'scripts', - 'scroll', 'scrollbars', 'scrollpanes', 'scss', 'scumm', 'sdbl', 'sdl', 'sdo', - 'sealed', 'search', 'seawolf', 'second', 'secondary', 'section', - 'section-attribute', 'sectionname', 'sections', 'see', 'segment', - 'segment-registers', 'segment-resolution', 'select', 'select-block', - 'selector', 'self', 'self-binding', 'self-close', 'sem', 'semantic', - 'semanticmodel', 'semi-colon', 'semicolon', 'semicoron', 'semireserved', - 'send-channel', 'sender', 'senum', 'sep', 'separator', 'separatory', - 'sepatator', 'seperator', 'sequence', 'sequences', 'serial', 'serpent', - 'server', 'service', 'service-declaration', 'service-rpc', 'services', - 'session', 'set', 'set-colour', 'set-size', 'set-variable', 'setbagmix', - 'setname', 'setproperty', 'sets', 'setter', 'setting', 'settings', 'settype', - 'setword', 'seven', 'severity', 'sexpr', 'sfd', 'sfst', 'sgml', 'sgx1', - 'sgx2', 'sha', 'sha256', 'sha512', 'sha_functions', 'shad', 'shade', - 'shaderlab', 'shadow-object', 'shape', 'shape-base', 'shape-base-data', - 'shared', 'shared-static', 'sharp', 'sharpequal', 'sharpge', 'sharpgt', - 'sharple', 'sharplt', 'sharpness', 'shebang', 'shell', 'shell-function', - 'shell-session', 'shift', 'shift-and-rotate', 'shift-left', 'shift-right', - 'shine', 'shinescript', 'shipflow', 'shmop', 'short', 'shortcut', 'shortcuts', - 'shorthand', 'shorthandpropertyname', 'show', 'show-argument', - 'shuffle-and-unpack', 'shutdown', 'shy', 'sidebar', 'sifu', 'sigdec', 'sigil', - 'sign-line', 'signal', 'signal-processing', 'signature', 'signed', - 'signed-int', 'signedness', 'signifier', 'silent', 'sim-group', 'sim-object', - 'sim-set', 'simd', 'simd-horizontal', 'simd-integer', 'simple', - 'simple-delimiter', 'simple-divider', 'simple-element', 'simple_delimiter', - 'simplexml', 'simplez', 'simulate', 'since', 'singe', 'single', 'single-line', - 'single-quote', 'single-quoted', 'single_quote', 'singlequote', 'singleton', - 'singleword', 'sites', 'six', 'size', 'size-cue-setting', 'sized_integer', - 'sizeof', 'sjs', 'sjson', 'sk', 'skaction', 'skdragon', 'skeeland', - 'skellett', 'sketchplugin', 'skevolved', 'skew', 'skill', 'skipped', - 'skmorkaz', 'skquery', 'skrambled', 'skrayfall', 'skript', 'skrpg', 'sksharp', - 'skstuff', 'skutilities', 'skvoice', 'sky', 'skyrim', 'sl', 'slash', - 'slash-bar', 'slash-option', 'slash-sign', 'slashes', 'sleet', 'slice', - 'slim', 'slm', 'sln', 'slot', 'slugignore', 'sma', 'smali', 'smalltalk', - 'smarty', 'smb', 'smbinternal', 'smilebasic', 'sml', 'smoothing-group', - 'smpte', 'smtlib', 'smx', 'snakeskin', 'snapshot', 'snlog', 'snmp', 'so', - 'soap', 'social', 'socketgroup', 'sockets', 'soft', 'solidity', 'solve', - 'soma', 'somearg', 'something', 'soql', 'sort', 'sorting', 'souce', 'sound', - 'sound_processing', 'sound_synthesys', 'source', 'source-constant', 'soy', - 'sp', 'space', 'space-after-command', 'spacebars', 'spaces', 'sparql', - 'spath', 'spec', 'special', 'special-attributes', 'special-character', - 'special-curve', 'special-functions', 'special-hook', 'special-keyword', - 'special-method', 'special-point', 'special-token-sequence', 'special-tokens', - 'special-type', 'specification', 'specifier', 'spectral-curve', - 'specular-exponent', 'specular-reflectivity', 'sphinx', 'sphinx-domain', - 'spice', 'spider', 'spindlespeed', 'splat', 'spline', 'splunk', 'splunk-conf', - 'splus', 'spn', 'spread', 'spread-line', 'spreadmap', 'sprite', 'sproto', - 'sproutcore', 'sqf', 'sql', 'sqlbuiltin', 'sqlite', 'sqlsrv', 'sqr', 'sqsp', - 'squad', 'square', 'squart', 'squirrel', 'sr-Cyrl', 'sr-Latn', 'src', - 'srltext', 'sros', 'srt', 'srv', 'ss', 'ssa', 'sse', 'sse2', 'sse2_simd', - 'sse3', 'sse4', 'sse4_simd', 'sse5', 'sse_avx', 'sse_simd', 'ssh-config', - 'ssi', 'ssl', 'ssn', 'sstemplate', 'st', 'stable', 'stack', 'stack-effect', - 'stackframe', 'stage', 'stan', 'standard', 'standard-key', 'standard-links', - 'standard-suite', 'standardadditions', 'standoc', 'star', 'starline', 'start', - 'start-block', 'start-condition', 'start-symbol', 'start-value', - 'starting-function-params', 'starting-functions', 'starting-functions-point', - 'startshape', 'stata', 'statamic', 'state', 'state-flag', 'state-management', - 'stateend', 'stategrouparg', 'stategroupval', 'statement', - 'statement-separator', 'states', 'statestart', 'statetable', 'static', - 'static-assert', 'static-classes', 'static-if', 'static-shape', - 'staticimages', 'statistics', 'stats', 'std', 'stdWrap', 'std_logic', - 'std_logic_1164', 'stderr-write-file', 'stdint', 'stdlib', 'stdlibcall', - 'stdplugin', 'stem', 'step', 'step-size', 'steps', 'stg', 'stile-shoe-left', - 'stile-shoe-up', 'stile-tilde', 'stitch', 'stk', 'stmt', 'stochastic', 'stop', - 'stopping', 'storage', 'story', 'stp', 'straight-quote', 'stray', - 'stray-comment-end', 'stream', 'stream-selection-and-control', 'streamsfuncs', - 'streem', 'strict', 'strictness', 'strike', 'strikethrough', 'string', - 'string-constant', 'string-format', 'string-interpolation', - 'string-long-quote', 'string-long-single-quote', 'string-single-quote', - 'stringchar', 'stringize', 'strings', 'strong', 'struc', 'struct', - 'struct-union-block', 'structdef', 'structend', 'structs', 'structstart', - 'structtable', 'structure', 'stuff', 'stupid-goddamn-hack', 'style', - 'styleblock', 'styles', 'stylus', 'sub', 'sub-pattern', 'subchord', 'subckt', - 'subcmd', 'subexp', 'subexpression', 'subkey', 'subkeys', 'subl', 'submodule', - 'subnet', 'subnet6', 'subpattern', 'subprogram', 'subroutine', 'subscript', - 'subsection', 'subsections', 'subset', 'subshell', 'subsort', 'substituted', - 'substitution', 'substitution-definition', 'subtitle', 'subtlegradient', - 'subtlegray', 'subtract', 'subtraction', 'subtype', 'suffix', 'sugarml', - 'sugarss', 'sugly', 'sugly-comparison-operators', 'sugly-control-keywords', - 'sugly-declare-function', 'sugly-delcare-operator', 'sugly-delcare-variable', - 'sugly-else-in-invalid-position', 'sugly-encode-clause', - 'sugly-function-groups', 'sugly-function-recursion', - 'sugly-function-variables', 'sugly-general-functions', - 'sugly-general-operators', 'sugly-generic-classes', 'sugly-generic-types', - 'sugly-global-function', 'sugly-int-constants', 'sugly-invoke-function', - 'sugly-json-clause', 'sugly-language-constants', 'sugly-math-clause', - 'sugly-math-constants', 'sugly-multiple-parameter-function', - 'sugly-number-constants', 'sugly-operator-operands', 'sugly-print-clause', - 'sugly-single-parameter-function', 'sugly-subject-or-predicate', - 'sugly-type-function', 'sugly-uri-clause', 'summary', 'super', 'superclass', - 'supercollider', 'superscript', 'superset', 'supervisor', 'supervisord', - 'supplemental', 'supplimental', 'support', 'suppress-image-or-category', - 'suppressed', 'surface', 'surface-technique', 'sv', 'svg', 'svm', 'svn', - 'swift', 'swig', 'switch', 'switch-block', 'switch-expression', - 'switch-statement', 'switchEnd', 'switchStart', 'swizzle', 'sybase', - 'syllableseparator', 'symbol', 'symbol-definition', 'symbol-type', 'symbolic', - 'symbolic-math', 'symbols', 'symmetry', 'sync-match', 'sync-mode', - 'sync-mode-location', 'synchronization', 'synchronize', 'synchronized', - 'synergy', 'synopsis', 'syntax', 'syntax-case', 'syntax-cluster', - 'syntax-conceal', 'syntax-error', 'syntax-include', 'syntax-item', - 'syntax-keywords', 'syntax-match', 'syntax-option', 'syntax-region', - 'syntax-rule', 'syntax-spellcheck', 'syntax-sync', 'sys-types', 'sysj', - 'syslink', 'syslog-ng', 'system', 'system-events', 'system-identification', - 'system-table-pointer', 'systemreference', 'sytem-events', 't', - 't3datastructure', 't4', 't5', 't7', 'ta', 'tab', 'table', 'table-name', - 'tablename', 'tabpanels', 'tabs', 'tabular', 'tacacs', 'tack-down', 'tack-up', - 'taco', 'tads3', 'tag', 'tag-string', 'tag-value', 'tagbraces', 'tagdef', - 'tagged', 'tagger_script', 'taglib', 'tagname', 'tagnamedjango', 'tags', - 'taint', 'take', 'target', 'targetobj', 'targetprop', 'task', 'tasks', - 'tbdfile', 'tbl', 'tbody', 'tcl', 'tcoffee', 'tcp-object', 'td', 'tdl', 'tea', - 'team', 'telegram', 'tell', 'telnet', 'temp', 'template', 'template-call', - 'template-parameter', 'templatetag', 'tempo', 'temporal', 'term', - 'term-comparison', 'term-creation-and-decomposition', 'term-io', - 'term-testing', 'term-unification', 'terminal', 'terminate', 'termination', - 'terminator', 'terms', 'ternary', 'ternary-if', 'terra', 'terraform', - 'terrain-block', 'test', 'testcase', 'testing', 'tests', 'testsuite', 'testx', - 'tex', 'texres', 'texshop', 'text', 'text-reference', 'text-suite', 'textbf', - 'textcolor', 'textile', 'textio', 'textit', 'textlabels', 'textmate', - 'texttt', 'textual', 'texture', 'texture-map', 'texture-option', 'tfoot', - 'th', 'thead', 'then', 'therefore', 'thin', 'thing1', 'third', 'this', - 'thorn', 'thread', 'three', 'thrift', 'throughput', 'throw', 'throwables', - 'throws', 'tick', 'ticket-num', 'ticket-psa', 'tid-file', 'tidal', - 'tidalcycles', 'tiddler', 'tiddler-field', 'tiddler-fields', 'tidy', 'tier', - 'tieslur', 'tikz', 'tilde', 'time', 'timeblock', 'timehrap', 'timeout', - 'timer', 'times', 'timesig', 'timespan', 'timespec', 'timestamp', 'timing', - 'titanium', 'title', 'title-page', 'title-text', 'titled-paragraph', 'tjs', - 'tl', 'tla', 'tlh', 'tmpl', 'tmsim', 'tmux', 'tnote', 'tnsaudit', 'to', - 'to-file', 'to-type', 'toc', 'toc-list', 'todo', 'todo_extra', 'todotxt', - 'token', 'token-def', 'token-paste', 'token-type', 'tokenised', 'tokenizer', - 'toml', 'too-many-tildes', 'tool', 'toolbox', 'tooltip', 'top', 'top-level', - 'top_level', 'topas', 'topic', 'topic-decoration', 'topic-title', 'tornado', - 'torque', 'torquescript', 'tosca', 'total-config', 'totaljs', 'tpye', 'tr', - 'trace', 'trace-argument', 'trace-object', 'traceback', 'tracing', - 'track_processing', 'trader', 'tradersk', 'trail', 'trailing', - 'trailing-array-separator', 'trailing-dictionary-separator', 'trailing-match', - 'trait', 'traits', 'traits-keyword', 'transaction', - 'transcendental', 'transcludeblock', 'transcludeinline', 'transclusion', - 'transform', 'transformation', 'transient', 'transition', - 'transitionable-property-value', 'translation', 'transmission-filter', - 'transparency', 'transparent-line', 'transpose', 'transposed-func', - 'transposed-matrix', 'transposed-parens', 'transposed-variable', 'trap', - 'tree', 'treetop', 'trenni', 'trigEvent_', 'trigLevelMod_', 'trigLevel_', - 'trigger', 'trigger-words', 'triggermodifier', 'trigonometry', - 'trimming-loop', 'triple', 'triple-dash', 'triple-slash', 'triple-star', - 'true', 'truncate', 'truncation', 'truthgreen', 'try', 'try-catch', - 'trycatch', 'ts', 'tsql', 'tss', 'tst', 'tsv', 'tsx', 'tt', 'ttcn3', - 'ttlextension', 'ttpmacro', 'tts', 'tubaina', 'tubaina2', 'tul', 'tup', - 'tuple', 'turbulence', 'turing', 'turquoise', 'turtle', 'tutch', 'tvml', - 'tw5', 'twig', 'twigil', 'twiki', 'two', 'txl', 'txt', 'txt2tags', 'type', - 'type-annotation', 'type-cast', 'type-cheat', 'type-checking', - 'type-constrained', 'type-constraint', 'type-declaration', 'type-def', - 'type-definition', 'type-definition-group', 'type-definitions', - 'type-descriptor', 'type-of', 'type-or', 'type-parameter', 'type-parameters', - 'type-signature', 'type-spec', 'type-specialization', 'type-specifiers', - 'type_2', 'type_trait', 'typeabbrev', 'typeclass', 'typed', 'typed-hole', - 'typedblock', 'typedcoffeescript', 'typedecl', 'typedef', 'typeexp', - 'typehint', 'typehinted', 'typeid', 'typename', 'types', 'typesbii', - 'typescriptish', 'typographic-quotes', 'typoscript', 'typoscript2', 'u', - 'u-degree', 'u-end', 'u-offset', 'u-resolution', 'u-scale', 'u-segments', - 'u-size', 'u-start', 'u-value', 'uc', 'ucicfg', 'ucicmd', 'udaf', 'udf', - 'udl', 'udp', 'udtf', 'ui', 'ui-block', 'ui-group', 'ui-state', 'ui-subgroup', - 'uintptr', 'ujm', 'uk', 'ul', 'umbaska', 'unOp', 'unary', 'unbuffered', - 'unchecked', 'uncleared', 'unclosed', 'unclosed-string', 'unconstrained', - 'undef', 'undefined', 'underbar-circle', 'underbar-diamond', 'underbar-iota', - 'underbar-jot', 'underbar-quote', 'underbar-semicolon', 'underline', - 'underline-text', 'underlined', 'underscore', 'undocumented', - 'unescaped-quote', 'unexpected', 'unexpected-characters', - 'unexpected-extends', 'unexpected-extends-character', 'unfiled', - 'unformatted', 'unicode', 'unicode-16-bit', 'unicode-32-bit', - 'unicode-escape', 'unicode-raw', 'unicode-raw-regex', 'unified', 'unify', - 'unimplemented', 'unimportant', 'union', 'union-declaration', 'unique-id', - 'unit', 'unit-checking', 'unit-test', 'unit_test', 'unittest', 'unity', - 'unityscript', 'universal-match', 'unix', 'unknown', 'unknown-escape', - 'unknown-method', 'unknown-property-name', 'unknown-rune', 'unlabeled', - 'unless', 'unnecessary', 'unnumbered', 'uno', 'unoconfig', 'unop', 'unoproj', - 'unordered', 'unordered-block', 'unosln', 'unpack', 'unpacking', 'unparsed', - 'unqualified', 'unquoted', 'unrecognized', 'unrecognized-character', - 'unrecognized-character-escape', 'unrecognized-string-escape', 'unsafe', - 'unsigned', 'unsigned-int', 'unsized_integer', 'unsupplied', 'until', - 'untitled', 'untyped', 'unused', 'uopz', 'update', 'uppercase', 'upstream', - 'upwards', 'ur', 'uri', 'url', 'usable', 'usage', 'use', 'use-as', 'use-map', - 'use-material', 'usebean', 'usecase', 'usecase-block', 'user', 'user-defined', - 'user-defined-property', 'user-defined-type', 'user-interaction', - 'userflagsref', 'userid', 'username', 'users', 'using', - 'using-namespace-declaration', 'using_animtree', 'util', 'utilities', - 'utility', 'utxt', 'uv-resolution', 'uvu', 'uvw', 'ux', 'uxc', 'uxl', 'uz', - 'v', 'v-degree', 'v-end', 'v-offset', 'v-resolution', 'v-scale', 'v-segments', - 'v-size', 'v-start', 'v-value', 'val', 'vala', 'valgrind', 'valid', - 'valid-ampersand', 'valid-bracket', 'valign', 'value', 'value-pair', - 'value-signature', 'value-size', 'value-type', 'valuepair', 'vamos', 'vamp', - 'vane-down', 'vane-left', 'vane-right', 'vane-up', 'var', - 'var-single-variable', 'var1', 'var2', 'variable', 'variable-access', - 'variable-assignment', 'variable-declaration', 'variable-definition', - 'variable-modifier', 'variable-parameter', 'variable-reference', - 'variable-usage', 'variables', 'variabletable', 'variant', - 'variant-definition', 'varname', 'varnish', 'vars', 'vb', 'vbnet', 'vbs', - 'vc', 'vcard', 'vcd', 'vcl', 'vcs', 'vector', 'vector-load', 'vectors', - 'vehicle', 'velocity', 'vendor-prefix', 'verb', 'verbatim', 'verdict', - 'verilog', 'version', 'version-number', 'version-specification', 'vertex', - 'vertex-reference', 'vertical-blending', 'vertical-span', - 'vertical-text-cue-setting', 'vex', 'vhdl', 'vhost', 'vi', 'via', - 'video-texturing', 'video_processing', 'view', 'viewhelpers', 'vimAugroupKey', - 'vimBehaveModel', 'vimFTCmd', 'vimFTOption', 'vimFuncKey', 'vimGroupSpecial', - 'vimHiAttrib', 'vimHiClear', 'vimMapModKey', 'vimPattern', 'vimSynCase', - 'vimSynType', 'vimSyncC', 'vimSyncLinecont', 'vimSyncMatch', 'vimSyncNone', - 'vimSyncRegion', 'vimUserAttrbCmplt', 'vimUserAttrbKey', 'vimUserCommand', - 'viml', 'virtual', 'virtual-host', 'virtual-reality', 'visibility', - 'visualforce', 'visualization', 'vlanhdr', 'vle', 'vmap', 'vmx', 'voice', - 'void', 'volatile', 'volt', 'volume', 'vpath', 'vplus', 'vrf', 'vtt', 'vue', - 'vue-jade', 'vue-stylus', 'w-offset', 'w-scale', 'w-value', - 'w3c-extended-color-name', 'w3c-non-standard-color-name', - 'w3c-standard-color-name', 'wait', 'waitress', 'waitress-config', - 'waitress-rb', 'warn', 'warning', 'warnings', 'wast', 'water', 'watson-todo', - 'wavefront', 'wavelet', 'wddx', 'wdiff', 'weapon', 'weave', 'weaveBracket', - 'weaveBullet', 'webidl', 'webspeed', 'webvtt', 'weekday', 'weirdland', 'wf', - 'wh', 'whatever', 'wheeled-vehicle', 'when', 'where', 'while', - 'while-condition', 'while-loop', 'whiskey', 'white', 'whitespace', 'widget', - 'width', 'wiki', 'wiki-link', 'wildcard', 'wildsk', 'win', 'window', - 'window-classes', 'windows', 'winered', 'with', 'with-arg', 'with-args', - 'with-arguments', 'with-params', 'with-prefix', 'with-side-effects', - 'with-suffix', 'with-terminator', 'with-value', 'with_colon', 'without-args', - 'without-arguments', 'wla-dx', 'word', 'word-op', 'wordnet', 'wordpress', - 'words', 'workitem', 'world', 'wow', 'wp', 'write', 'wrong', - 'wrong-access-type', 'wrong-division', 'wrong-division-assignment', 'ws', - 'www', 'wxml', 'wysiwyg-string', 'x10', 'x86', 'x86_64', 'x86asm', 'xacro', - 'xbase', 'xchg', 'xhp', 'xhprof', 'xikij', 'xml', 'xml-attr', 'xmlrpc', - 'xmlwriter', 'xop', 'xor', 'xparse', 'xq', 'xquery', 'xref', 'xsave', - 'xsd-all', 'xsd_nillable', 'xsd_optional', 'xsl', 'xslt', 'xsse3_simd', 'xst', - 'xtend', 'xtoy', 'xtpl', 'xu', 'xvc', 'xve', 'xyzw', 'y', 'y1', 'y2', 'yabb', - 'yaml', 'yaml-ext', 'yang', 'yara', 'yate', 'yaws', 'year', 'yellow', 'yield', - 'ykk', 'yorick', 'you-forgot-semicolon', 'z', 'z80', 'zap', 'zapper', 'zep', - 'zepon', 'zepto', 'zero', 'zero-width-marker', 'zero-width-print', 'zeroop', - 'zh-CN', 'zh-TW', 'zig', 'zilde', 'zlib', 'zoomfilter', 'zzz' -]) + 'AFDKO', + 'AFKDO', + 'ASS', + 'AVX', + 'AVX2', + 'AVX512', + 'AVX512BW', + 'AVX512DQ', + 'Alignment', + 'Alpha', + 'AlphaLevel', + 'Angle', + 'Animation', + 'AnimationGroup', + 'ArchaeologyDigSiteFrame', + 'Arrow__', + 'AtLilyPond', + 'AttrBaseType', + 'AttrSetVal__', + 'BackColour', + 'Banner', + 'Bold', + 'Bonlang', + 'BorderStyle', + 'Browser', + 'Button', + 'C99', + 'CALCULATE', + 'CharacterSet', + 'ChatScript', + 'Chatscript', + 'CheckButton', + 'ClipboardFormat', + 'ClipboardType', + 'Clipboard__', + 'CodePage', + 'Codepages__', + 'Collisions', + 'ColorSelect', + 'ColourActual', + 'ColourLogical', + 'ColourReal', + 'ColourScheme', + 'ColourSize', + 'Column', + 'Comment', + 'ConfCachePolicy', + 'ControlPoint', + 'Cooldown', + 'DBE', + 'DDL', + 'DML', + 'DSC', + 'Database__', + 'DdcMode', + 'Dialogue', + 'DiscussionFilterType', + 'DiscussionStatus', + 'DisplaySchemes', + 'Document-Structuring-Comment', + 'DressUpModel', + 'Edit', + 'EditBox', + 'Effect', + 'Encoding', + 'End', + 'ExternalLinkBehaviour', + 'ExternalLinkDirection', + 'F16c', + 'FMA', + 'FilterType', + 'Font', + 'FontInstance', + 'FontString', + 'Fontname', + 'Fonts__', + 'Fontsize', + 'Format', + 'Frame', + 'GameTooltip', + 'GroupList', + 'HLE', + 'HeaderEvent', + 'HistoryType', + 'HttpVerb', + 'II', + 'IO', + 'Icon', + 'IconID', + 'InPlaceBox__', + 'InPlaceEditEvent', + 'Info', + 'Italic', + 'JSXEndTagStart', + 'JSXStartTagEnd', + 'KNC', + 'KeyModifier', + 'Kotlin', + 'LUW', + 'Language', + 'Layer', + 'LayeredRegion', + 'LdapItemList', + 'LineSpacing', + 'LinkFilter', + 'LinkLimit', + 'ListView', + 'Locales__', + 'Lock', + 'LoginPolicy', + 'MA_End__', + 'MA_StdCombo__', + 'MA_StdItem__', + 'MA_StdMenu__', + 'MISSING', + 'Mapping', + 'MarginL', + 'MarginR', + 'MarginV', + 'Marked', + 'MessageFrame', + 'Minimap', + 'MovieFrame', + 'Name', + 'Outline', + 'OutlineColour', + 'ParentedObject', + 'Path', + 'Permission', + 'PlayRes', + 'PlayerModel', + 'PrimaryColour', + 'Proof', + 'QuestPOIFrame', + 'RTM', + 'RecentModule__', + 'Regexp', + 'Region', + 'Rotation', + 'SCADABasic', + 'SSA', + 'Scale', + 'ScaleX', + 'ScaleY', + 'ScaledBorderAndShadow', + 'ScenarioPOIFrame', + 'ScriptObject', + 'Script__', + 'Scroll', + 'ScrollEvent', + 'ScrollFrame', + 'ScrollSide', + 'ScrollingMessageFrame', + 'SecondaryColour', + 'Sensitivity', + 'Shadow', + 'SimpleHTML', + 'Slider', + 'Spacing', + 'Start', + 'StatusBar', + 'Stream', + 'StrikeOut', + 'Style', + 'TIS', + 'TODO', + 'TabardModel', + 'Text', + 'Texture', + 'Timer', + 'ToolType', + 'Translation', + 'TreeView', + 'TriggerStatus', + 'UIObject', + 'Underline', + 'UserClass', + 'UserList', + 'UserNotifyList', + 'VisibleRegion', + 'Vplus', + 'WrapStyle', + 'XHPEndTagStart', + 'XHPStartTagEnd', + 'ZipType', + '__package-name__', + '_c', + '_function', + 'a', + 'a10networks', + 'aaa', + 'abaqus', + 'abbrev', + 'abbreviated', + 'abbreviation', + 'abcnotation', + 'abl', + 'abnf', + 'abp', + 'absolute', + 'abstract', + 'academic', + 'access', + 'access-control', + 'access-qualifiers', + 'accessed', + 'accessor', + 'account', + 'accumulator', + 'ace', + 'ace3', + 'acl', + 'acos', + 'act', + 'action', + 'action-map', + 'actionhandler', + 'actionpack', + 'actions', + 'actionscript', + 'activerecord', + 'activesupport', + 'actual', + 'acute-accent', + 'ada', + 'add', + 'adddon', + 'added', + 'addition', + 'additional-character', + 'additive', + 'addon', + 'address', + 'address-of', + 'address-space', + 'addrfam', + 'adjustment', + 'admonition', + 'adr', + 'adverb', + 'adx', + 'ael', + 'aem', + 'aerospace', + 'aes', + 'aes_functions', + 'aesni', + 'aexLightGreen', + 'af', + 'afii', + 'aflex', + 'after', + 'after-expression', + 'agc', + 'agda', + 'agentspeak', + 'aggregate', + 'aggregation', + 'ahk', + 'ai-connection', + 'ai-player', + 'ai-wheeled-vehicle', + 'aif', + 'alabel', + 'alarms', + 'alda', + 'alert', + 'algebraic-type', + 'alias', + 'aliases', + 'align', + 'align-attribute', + 'alignment', + 'alignment-cue-setting', + 'alignment-mode', + 'all', + 'all-once', + 'all-solutions', + 'allocate', + 'alloy', + 'alloyglobals', + 'alloyxml', + 'alog', + 'alpha', + 'alphabeticalllt', + 'alphabeticallyge', + 'alphabeticallygt', + 'alphabeticallyle', + 'alt', + 'alter', + 'alternate-wysiwyg-string', + 'alternates', + 'alternation', + 'alternatives', + 'am', + 'ambient-audio-manager', + 'ambient-reflectivity', + 'amd', + 'amd3DNow', + 'amdnops', + 'ameter', + 'amount', + 'amp', + 'ampersand', + 'ampl', + 'ampscript', + 'an', + 'analysis', + 'analytics', + 'anb', + 'anchor', + 'and', + 'andop', + 'angelscript', + 'angle', + 'angle-brackets', + 'angular', + 'animation', + 'annot', + 'annotated', + 'annotation', + 'annotation-arguments', + 'anon', + 'anonymous', + 'another', + 'ansi', + 'ansi-c', + 'ansi-colored', + 'ansi-escape-code', + 'ansi-formatted', + 'ansi2', + 'ansible', + 'answer', + 'antialiasing', + 'antl', + 'antlr', + 'antlr4', + 'anubis', + 'any', + 'any-method', + 'anyclass', + 'aolserver', + 'apa', + 'apache', + 'apache-config', + 'apc', + 'apdl', + 'apex', + 'api', + 'api-notation', + 'apiary', + 'apib', + 'apl', + 'apostrophe', + 'appcache', + 'applescript', + 'application', + 'application-name', + 'application-process', + 'approx-equal', + 'aql', + 'aqua', + 'ar', + 'arbitrary-radix', + 'arbitrary-repetition', + 'arbitrary-repitition', + 'arch', + 'arch_specification', + 'architecture', + 'archive', + 'archives', + 'arduino', + 'area-code', + 'arendelle', + 'argcount', + 'args', + 'argument', + 'argument-label', + 'argument-separator', + 'argument-seperator', + 'argument-type', + 'arguments', + 'arith', + 'arithmetic', + 'arithmetical', + 'arithmeticcql', + 'ark', + 'arm', + 'arma', + 'armaConfig', + 'arnoldc', + 'arp', + 'arpop', + 'arr', + 'array', + 'array-expression', + 'array-literal', + 'arrays', + 'arrow', + 'articulation', + 'artihmetic', + 'arvo', + 'aryop', + 'as', + 'as4', + 'ascii', + 'asciidoc', + 'asdoc', + 'ash', + 'ashx', + 'asl', + 'asm', + 'asm-instruction', + 'asm-type-prefix', + 'asn', + 'asp', + 'asp-core-2', + 'aspx', + 'ass', + 'assembly', + 'assert', + 'assertion', + 'assigment', + 'assign', + 'assign-class', + 'assigned', + 'assigned-class', + 'assigned-value', + 'assignee', + 'assignement', + 'assignment', + 'assignmentforge-config', + 'associate', + 'association', + 'associativity', + 'assocs', + 'asterisk', + 'async', + 'at-marker', + 'at-root', + 'at-rule', + 'at-sign', + 'atmark', + 'atml3', + 'atoemp', + 'atom', + 'atom-term-processing', + 'atomic', + 'atomscript', + 'att', + 'attachment', + 'attr', + 'attribute', + 'attribute-entry', + 'attribute-expression', + 'attribute-key-value', + 'attribute-list', + 'attribute-lookup', + 'attribute-name', + 'attribute-reference', + 'attribute-selector', + 'attribute-value', + 'attribute-values', + 'attribute-with-value', + 'attribute_list', + 'attribute_value', + 'attribute_value2', + 'attributelist', + 'attributes', + 'attrset', + 'attrset-or-function', + 'audio', + 'audio-file', + 'auditor', + 'augmented', + 'auth', + 'auth_basic', + 'author', + 'author-names', + 'authorization', + 'auto', + 'auto-event', + 'autoconf', + 'autoindex', + 'autoit', + 'automake', + 'automatic', + 'autotools', + 'autovar', + 'aux', + 'auxiliary', + 'avdl', + 'avra', + 'avrasm', + 'avrdisasm', + 'avs', + 'avx', + 'avx2', + 'avx512', + 'awk', + 'axes_group', + 'axis', + 'axl', + 'b', + 'b-spline-patch', + 'babel', + 'back', + 'back-from', + 'back-reference', + 'back-slash', + 'backend', + 'background', + 'backreference', + 'backslash', + 'backslash-bar', + 'backslash-g', + 'backspace', + 'backtick', + 'bad-ampersand', + 'bad-angle-bracket', + 'bad-assignment', + 'bad-comments-or-CDATA', + 'bad-escape', + 'bad-octal', + 'bad-var', + 'bang', + 'banner', + 'bar', + 'bareword', + 'barline', + 'base', + 'base-11', + 'base-12', + 'base-13', + 'base-14', + 'base-15', + 'base-16', + 'base-17', + 'base-18', + 'base-19', + 'base-20', + 'base-21', + 'base-22', + 'base-23', + 'base-24', + 'base-25', + 'base-26', + 'base-27', + 'base-28', + 'base-29', + 'base-3', + 'base-30', + 'base-31', + 'base-32', + 'base-33', + 'base-34', + 'base-35', + 'base-36', + 'base-4', + 'base-5', + 'base-6', + 'base-7', + 'base-9', + 'base-call', + 'base-integer', + 'base64', + 'base85', + 'base_pound_number_pound', + 'basetype', + 'basic', + 'basic-arithmetic', + 'basic-type', + 'basic_functions', + 'basicblock', + 'basis-matrix', + 'bat', + 'batch', + 'batchfile', + 'battlesim', + 'bb', + 'bbcode', + 'bcmath', + 'be', + 'beam', + 'beamer', + 'beancount', + 'before', + 'begin', + 'begin-document', + 'begin-emphasis', + 'begin-end', + 'begin-end-group', + 'begin-literal', + 'begin-symbolic', + 'begintimeblock', + 'behaviour', + 'bem', + 'between-tag-pair', + 'bevel', + 'bezier-patch', + 'bfeac', + 'bff', + 'bg', + 'bg-black', + 'bg-blue', + 'bg-cyan', + 'bg-green', + 'bg-normal', + 'bg-purple', + 'bg-red', + 'bg-white', + 'bg-yellow', + 'bhtml', + 'bhv', + 'bibitem', + 'bibliography-anchor', + 'biblioref', + 'bibpaper', + 'bibtex', + 'bif', + 'big-arrow', + 'big-arrow-left', + 'bigdecimal', + 'bigint', + 'biicode', + 'biiconf', + 'bin', + 'binOp', + 'binary', + 'binary-arithmetic', + 'bind', + 'binder', + 'binding', + 'binding-prefix', + 'bindings', + 'binop', + 'bioinformatics', + 'biosphere', + 'bird-track', + 'bis', + 'bison', + 'bit', + 'bit-and-byte', + 'bit-range', + 'bit-wise', + 'bitarray', + 'bitop', + 'bits-mov', + 'bitvector', + 'bitwise', + 'black', + 'blade', + 'blanks', + 'blaze', + 'blenc', + 'blend', + 'blending', + 'blendtype', + 'blendu', + 'blendv', + 'blip', + 'block', + 'block-attribute', + 'block-dartdoc', + 'block-data', + 'block-level', + 'blockid', + 'blockname', + 'blockquote', + 'blocktitle', + 'blue', + 'blueprint', + 'bluespec', + 'blur', + 'bm', + 'bmi', + 'bmi1', + 'bmi2', + 'bnd', + 'bnf', + 'body', + 'body-statement', + 'bold', + 'bold-italic-text', + 'bold-text', + 'bolt', + 'bond', + 'bonlang', + 'boo', + 'boogie', + 'bool', + 'boolean', + 'boolean-test', + 'boost', + 'boot', + 'bord', + 'border', + 'botml', + 'bottom', + 'boundary', + 'bounded', + 'bounds', + 'bow', + 'box', + 'bpl', + 'bpr', + 'bqparam', + 'brace', + 'braced', + 'braces', + 'bracket', + 'bracketed', + 'brackets', + 'brainfuck', + 'branch', + 'branch-point', + 'break', + 'breakpoint', + 'breakpoints', + 'breaks', + 'bridle', + 'brightscript', + 'bro', + 'broken', + 'browser', + 'browsers', + 'bs', + 'bsl', + 'btw', + 'buffered', + 'buffers', + 'bugzilla-number', + 'build', + 'buildin', + 'buildout', + 'built-in', + 'built-in-variable', + 'built-ins', + 'builtin', + 'builtin-comparison', + 'builtins', + 'bullet', + 'bullet-point', + 'bump', + 'bump-multiplier', + 'bundle', + 'but', + 'button', + 'buttons', + 'by', + 'by-name', + 'by-number', + 'byref', + 'byte', + 'bytearray', + 'bz2', + 'bzl', + 'c', + 'c-style', + 'c0', + 'c1', + 'c2hs', + 'ca', + 'cabal', + 'cabal-keyword', + 'cache', + 'cache-management', + 'cacheability-control', + 'cake', + 'calc', + 'calca', + 'calendar', + 'call', + 'callable', + 'callback', + 'caller', + 'calling', + 'callmethod', + 'callout', + 'callparent', + 'camera', + 'camlp4', + 'camlp4-stream', + 'canonicalized-program-name', + 'canopen', + 'capability', + 'capnp', + 'cappuccino', + 'caps', + 'caption', + 'capture', + 'capturename', + 'cardinal-curve', + 'cardinal-patch', + 'cascade', + 'case', + 'case-block', + 'case-body', + 'case-class', + 'case-clause', + 'case-clause-body', + 'case-expression', + 'case-modifier', + 'case-pattern', + 'case-statement', + 'case-terminator', + 'case-value', + 'cassius', + 'cast', + 'catch', + 'catch-exception', + 'catcode', + 'categories', + 'categort', + 'category', + 'cba', + 'cbmbasic', + 'cbot', + 'cbs', + 'cc', + 'cc65', + 'ccml', + 'cdata', + 'cdef', + 'cdtor', + 'ceiling', + 'cell', + 'cellcontents', + 'cellwall', + 'ceq', + 'ces', + 'cet', + 'cexpr', + 'cextern', + 'ceylon', + 'ceylondoc', + 'cf', + 'cfdg', + 'cfengine', + 'cfg', + 'cfml', + 'cfscript', + 'cfunction', + 'cg', + 'cgi', + 'cgx', + 'chain', + 'chained', + 'chaining', + 'chainname', + 'changed', + 'changelogs', + 'changes', + 'channel', + 'chapel', + 'chapter', + 'char', + 'characater', + 'character', + 'character-class', + 'character-data-not-allowed-here', + 'character-literal', + 'character-literal-too-long', + 'character-not-allowed-here', + 'character-range', + 'character-reference', + 'character-token', + 'character_not_allowed', + 'character_not_allowed_here', + 'characters', + 'chars', + 'chars-and-bytes-io', + 'charset', + 'check', + 'check-identifier', + 'checkboxes', + 'checker', + 'chef', + 'chem', + 'chemical', + 'children', + 'choice', + 'choicescript', + 'chord', + 'chorus', + 'chuck', + 'chunk', + 'ciexyz', + 'circle', + 'circle-jot', + 'cirru', + 'cisco', + 'cisco-ios-config', + 'citation', + 'cite', + 'citrine', + 'cjam', + 'cjson', + 'clamp', + 'clamping', + 'class', + 'class-constraint', + 'class-constraints', + 'class-declaration', + 'class-definition', + 'class-fns', + 'class-instance', + 'class-list', + 'class-struct-block', + 'class-type', + 'class-type-definition', + 'classcode', + 'classes', + 'classic', + 'classicalb', + 'classmethods', + 'classobj', + 'classtree', + 'clause', + 'clause-head-body', + 'clauses', + 'clear', + 'clear-argument', + 'cleared', + 'clflushopt', + 'click', + 'client', + 'client-server', + 'clip', + 'clipboard', + 'clips', + 'clmul', + 'clock', + 'clojure', + 'cloned', + 'close', + 'closed', + 'closing', + 'closing-text', + 'closure', + 'clothes-body', + 'cm', + 'cmake', + 'cmb', + 'cmd', + 'cnet', + 'cns', + 'cobject', + 'cocoa', + 'cocor', + 'cod4mp', + 'code', + 'code-example', + 'codeblock', + 'codepoint', + 'codimension', + 'codstr', + 'coffee', + 'coffeescript', + 'coffeescript-preview', + 'coil', + 'collection', + 'collision', + 'colon', + 'colons', + 'color', + 'color-adjustment', + 'coloring', + 'colour', + 'colour-correction', + 'colour-interpolation', + 'colour-name', + 'colour-scheme', + 'colspan', + 'column', + 'column-divider', + 'column-specials', + 'com', + 'combinators', + 'comboboxes', + 'comma', + 'comma-bar', + 'comma-parenthesis', + 'command', + 'command-name', + 'command-synopsis', + 'commandline', + 'commands', + 'comment', + 'comment-ish', + 'comment-italic', + 'commented-out', + 'commit-command', + 'commit-message', + 'commodity', + 'common', + 'commonform', + 'communications', + 'community', + 'commute', + 'comnd', + 'compare', + 'compareOp', + 'comparison', + 'compile', + 'compile-only', + 'compiled', + 'compiled-papyrus', + 'compiler', + 'compiler-directive', + 'compiletime', + 'compiling-and-loading', + 'complement', + 'complete', + 'completed', + 'complex', + 'component', + 'component-separator', + 'component_instantiation', + 'compositor', + 'compound', + 'compound-assignment', + 'compress', + 'computer', + 'computercraft', + 'concat', + 'concatenated-arguments', + 'concatenation', + 'concatenator', + 'concatination', + 'concealed', + 'concise', + 'concrete', + 'condition', + 'conditional', + 'conditional-directive', + 'conditional-short', + 'conditionals', + 'conditions', + 'conf', + 'config', + 'configuration', + 'configure', + 'confluence', + 'conftype', + 'conjunction', + 'conky', + 'connect', + 'connection-state', + 'connectivity', + 'connstate', + 'cons', + 'consecutive-tags', + 'considering', + 'console', + 'const', + 'const-data', + 'constant', + 'constants', + 'constrained', + 'constraint', + 'constraints', + 'construct', + 'constructor', + 'constructor-list', + 'constructs', + 'consult', + 'contacts', + 'container', + 'containers-raycast', + 'contains', + 'content', + 'content-detective', + 'contentSupplying', + 'contentitem', + 'context', + 'context-free', + 'context-signature', + 'continuation', + 'continuations', + 'continue', + 'continued', + 'continuum', + 'contol', + 'contract', + 'contracts', + 'contrl', + 'control', + 'control-char', + 'control-handlers', + 'control-management', + 'control-systems', + 'control-transfer', + 'controller', + 'controlline', + 'controls', + 'contstant', + 'conventional', + 'conversion', + 'convert-type', + 'cookie', + 'cool', + 'coord1', + 'coord2', + 'coord3', + 'coordinates', + 'copy', + 'copying', + 'coq', + 'core', + 'core-parse', + 'coreutils', + 'correct', + 'cos', + 'counter', + 'counters', + 'cover', + 'cplkg', + 'cplusplus', + 'cpm', + 'cpp', + 'cpp-include', + 'cpp-type', + 'cpp_type', + 'cpu12', + 'cql', + 'cram', + 'crc32', + 'create', + 'creation', + 'critic', + 'crl', + 'crontab', + 'crypto', + 'crystal', + 'cs', + 'csharp', + 'cshtml', + 'csi', + 'csjs', + 'csound', + 'csound-document', + 'csound-score', + 'cspm', + 'css', + 'csv', + 'csx', + 'ct', + 'ctkey', + 'ctor', + 'ctxvar', + 'ctxvarbracket', + 'ctype', + 'cubic-bezier', + 'cucumber', + 'cuda', + 'cue-identifier', + 'cue-timings', + 'cuesheet', + 'cup', + 'cupsym', + 'curl', + 'curley', + 'curly', + 'currency', + 'current', + 'current-escape-char', + 'curve', + 'curve-2d', + 'curve-fitting', + 'curve-reference', + 'curve-technique', + 'custom', + 'customevent', + 'cut', + 'cve-number', + 'cvs', + 'cw', + 'cxx', + 'cy-GB', + 'cyan', + 'cyc', + 'cycle', + 'cypher', + 'cyrix', + 'cython', + 'd', + 'da', + 'daml', + 'dana', + 'danger', + 'danmakufu', + 'dark_aqua', + 'dark_blue', + 'dark_gray', + 'dark_green', + 'dark_purple', + 'dark_red', + 'dart', + 'dartdoc', + 'dash', + 'dasm', + 'data', + 'data-acquisition', + 'data-extension', + 'data-integrity', + 'data-item', + 'data-step', + 'data-transfer', + 'database', + 'database-name', + 'datablock', + 'datablocks', + 'datafeed', + 'datatype', + 'datatypes', + 'date', + 'date-time', + 'datetime', + 'dav', + 'day', + 'dayofmonth', + 'dayofweek', + 'db', + 'dba', + 'dbx', + 'dc', + 'dcon', + 'dd', + 'ddp', + 'de', + 'dealii', + 'deallocate', + 'deb-control', + 'debian', + 'debris', + 'debug', + 'debug-specification', + 'debugger', + 'debugging', + 'debugging-comment', + 'dec', + 'decal', + 'decimal', + 'decimal-arithmetic', + 'decision', + 'decl', + 'declaration', + 'declaration-expr', + 'declaration-prod', + 'declarations', + 'declarator', + 'declaratyion', + 'declare', + 'decode', + 'decoration', + 'decorator', + 'decreasing', + 'decrement', + 'def', + 'default', + 'define', + 'define-colour', + 'defined', + 'definedness', + 'definingobj', + 'definition', + 'definitions', + 'defintions', + 'deflate', + 'delay', + 'delegated', + 'delete', + 'deleted', + 'deletion', + 'delimeter', + 'delimited', + 'delimiter', + 'delimiter-too-long', + 'delimiters', + 'dense', + 'deprecated', + 'depricated', + 'dereference', + 'derived-type', + 'deriving', + 'desc', + 'describe', + 'description', + 'descriptors', + 'design', + 'desktop', + 'destination', + 'destructor', + 'destructured', + 'determ', + 'developer', + 'device', + 'device-io', + 'dformat', + 'dg', + 'dhcp', + 'diagnostic', + 'dialogue', + 'diamond', + 'dict', + 'dictionary', + 'dictionaryname', + 'diff', + 'difference', + 'different', + 'diffuse-reflectivity', + 'digdag', + 'digit-width', + 'dim', + 'dimension', + 'dip', + 'dir', + 'dir-target', + 'dircolors', + 'direct', + 'direction', + 'directive', + 'directive-option', + 'directives', + 'directory', + 'dirjs', + 'dirtyblue', + 'dirtygreen', + 'disable', + 'disable-markdown', + 'disable-todo', + 'discarded', + 'discusson', + 'disjunction', + 'disk', + 'disk-folder-file', + 'dism', + 'displacement', + 'display', + 'dissolve', + 'dissolve-interpolation', + 'distribution', + 'diverging-function', + 'divert', + 'divide', + 'divider', + 'django', + 'dl', + 'dlv', + 'dm', + 'dmf', + 'dml', + 'do', + 'dobody', + 'doc', + 'doc-comment', + 'docRoot', + 'dockerfile', + 'dockerignore', + 'doconce', + 'docstring', + 'doctest', + 'doctree-option', + 'doctype', + 'document', + 'documentation', + 'documentroot', + 'does', + 'dogescript', + 'doki', + 'dollar', + 'dollar-quote', + 'dollar_variable', + 'dom', + 'domain', + 'dontcollect', + 'doors', + 'dop', + 'dot', + 'dot-access', + 'dotenv', + 'dotfiles', + 'dothandout', + 'dotnet', + 'dotnote', + 'dots', + 'dotted', + 'dotted-circle', + 'dotted-del', + 'dotted-greater', + 'dotted-tack-up', + 'double', + 'double-arrow', + 'double-colon', + 'double-dash', + 'double-dash-not-allowed', + 'double-dot', + 'double-number-sign', + 'double-percentage', + 'double-qoute', + 'double-quote', + 'double-quoted', + 'double-quoted-string', + 'double-semicolon', + 'double-slash', + 'doublequote', + 'doubleslash', + 'dougle', + 'down', + 'download', + 'downwards', + 'doxyfile', + 'doxygen', + 'dragdrop', + 'drawing', + 'drive', + 'droiuby', + 'drop', + 'drop-shadow', + 'droplevel', + 'drummode', + 'drupal', + 'dsl', + 'dsv', + 'dt', + 'dtl', + 'due', + 'dummy', + 'dummy-variable', + 'dump', + 'duration', + 'dust', + 'dust_Conditional', + 'dust_end_section_tag', + 'dust_filter', + 'dust_partial', + 'dust_partial_not_self_closing', + 'dust_ref', + 'dust_ref_name', + 'dust_section_context', + 'dust_section_name', + 'dust_section_params', + 'dust_self_closing_section_tag', + 'dust_special', + 'dust_start_section_tag', + 'dustjs', + 'dut', + 'dwscript', + 'dxl', + 'dylan', + 'dynamic', + 'dyndoc', + 'dyon', + 'e', + 'e3globals', + 'each', + 'eachin', + 'earl-grey', + 'ebnf', + 'ebuild', + 'echo', + 'eclass', + 'ecmascript', + 'eco', + 'ecr', + 'ect', + 'ect2', + 'ect3', + 'ect4', + 'edasm', + 'edge', + 'edit-manager', + 'editfields', + 'editors', + 'ee', + 'eex', + 'effect', + 'effectgroup', + 'effective_routine_body', + 'effects', + 'eiffel', + 'eight', + 'eio', + 'eiz', + 'ejectors', + 'el', + 'elasticsearch', + 'elasticsearch2', + 'element', + 'elements', + 'elemnt', + 'elif', + 'elipse', + 'elision', + 'elixir', + 'ellipsis', + 'elm', + 'elmx', + 'else', + 'else-condition', + 'else-if', + 'elseif', + 'elseif-condition', + 'elsewhere', + 'eltype', + 'elvis', + 'em', + 'email', + 'embed', + 'embed-diversion', + 'embedded', + 'embedded-c', + 'embedded-ruby', + 'embedded2', + 'embeded', + 'ember', + 'emberscript', + 'emblem', + 'embperl', + 'emissive-colour', + 'eml', + 'emlist', + 'emoji', + 'emojicode', + 'emp', + 'emph', + 'emphasis', + 'empty', + 'empty-dictionary', + 'empty-list', + 'empty-parenthesis', + 'empty-start', + 'empty-string', + 'empty-tag', + 'empty-tuple', + 'empty-typing-pair', + 'empty_gif', + 'emptyelement', + 'en', + 'en-Scouse', + 'en-au', + 'en-lol', + 'en-old', + 'en-pirate', + 'enable', + 'enc', + 'enchant', + 'enclose', + 'encode', + 'encoding', + 'encryption', + 'end', + 'end-block-data', + 'end-definition', + 'end-document', + 'end-enum', + 'end-footnote', + 'end-of-line', + 'end-statement', + 'end-value', + 'endassociate', + 'endcode', + 'enddo', + 'endfile', + 'endforall', + 'endfunction', + 'endian', + 'endianness', + 'endif', + 'endinfo', + 'ending', + 'ending-space', + 'endinterface', + 'endlocaltable', + 'endmodule', + 'endobject', + 'endobjecttable', + 'endparamtable', + 'endprogram', + 'endproperty', + 'endpropertygroup', + 'endpropertygrouptable', + 'endpropertytable', + 'endselect', + 'endstate', + 'endstatetable', + 'endstruct', + 'endstructtable', + 'endsubmodule', + 'endsubroutine', + 'endtimeblock', + 'endtype', + 'enduserflagsref', + 'endvariable', + 'endvariabletable', + 'endwhere', + 'engine', + 'enterprise', + 'entity', + 'entity-creation-and-abolishing', + 'entity_instantiation', + 'entry', + 'entry-definition', + 'entry-key', + 'entry-type', + 'entrypoint', + 'enum', + 'enum-block', + 'enum-declaration', + 'enumeration', + 'enumerator', + 'enumerator-specification', + 'env', + 'environment', + 'environment-variable', + 'eo', + 'eof', + 'epatch', + 'eq', + 'eqn', + 'eqnarray', + 'equal', + 'equal-or-greater', + 'equal-or-less', + 'equalexpr', + 'equality', + 'equals', + 'equals-sign', + 'equation', + 'equation-label', + 'erb', + 'ereg', + 'erlang', + 'error', + 'error-control', + 'errorfunc', + 'errorstop', + 'es', + 'es6', + 'es6import', + 'esc', + 'escape', + 'escape-char', + 'escape-code', + 'escape-sequence', + 'escape-unicode', + 'escaped', + 'escapes', + 'escript', + 'eso-lua', + 'eso-txt', + 'essence', + 'et', + 'eth', + 'ethaddr', + 'etml', + 'etpl', + 'eudoc', + 'euler', + 'euphoria', + 'european', + 'evaled', + 'evaluable', + 'evaluation', + 'even-tab', + 'event', + 'event-call', + 'event-handler', + 'event-handling', + 'event-schedulling', + 'eventType', + 'eventb', + 'eventend', + 'events', + 'evnd', + 'exactly', + 'example', + 'exampleText', + 'examples', + 'exceeding-sections', + 'excel-link', + 'exception', + 'exceptions', + 'exclaimation-point', + 'exclamation', + 'exec', + 'exec-command', + 'execution-context', + 'exif', + 'existential', + 'exit', + 'exp', + 'expand-register', + 'expanded', + 'expansion', + 'expected-array-separator', + 'expected-dictionary-separator', + 'expected-extends', + 'expected-implements', + 'expected-range-separator', + 'experimental', + 'expires', + 'expl3', + 'explosion', + 'exponent', + 'exponential', + 'export', + 'exports', + 'expr', + 'expression', + 'expression-separator', + 'expression-seperator', + 'expressions', + 'expressions-and-types', + 'exprwrap', + 'ext', + 'extempore', + 'extend', + 'extended', + 'extends', + 'extension', + 'extension-specification', + 'extensions', + 'extern', + 'extern-block', + 'external', + 'external-call', + 'external-signature', + 'extersk', + 'extglob', + 'extra', + 'extra-characters', + 'extra-equals-sign', + 'extracted', + 'extras', + 'extrassk', + 'exxample', + 'eztpl', + 'f', + 'f5networks', + 'fa', + 'face', + 'fact', + 'factor', + 'factorial', + 'fadeawayheight', + 'fadeawaywidth', + 'fail', + 'fakeroot', + 'fallback', + 'fallout4', + 'false', + 'fandoc', + 'fann', + 'fantom', + 'fastcgi', + 'fbaccidental', + 'fbfigure', + 'fbgroupclose', + 'fbgroupopen', + 'fbp', + 'fctn', + 'fe', + 'feature', + 'features', + 'feedrate', + 'fenced', + 'fftwfn', + 'fhem', + 'fi', + 'field', + 'field-assignment', + 'field-completions', + 'field-id', + 'field-level-comment', + 'field-name', + 'field-tag', + 'fields', + 'figbassmode', + 'figure', + 'figuregroup', + 'filder-design-hdl-coder', + 'file', + 'file-i-o', + 'file-io', + 'file-name', + 'file-object', + 'file-path', + 'fileinfo', + 'filename', + 'filepath', + 'filetest', + 'filter', + 'filter-pipe', + 'filteredtranscludeblock', + 'filters', + 'final', + 'final-procedure', + 'finally', + 'financial', + 'financial-derivatives', + 'find', + 'find-in-files', + 'find-m', + 'finder', + 'finish', + 'finn', + 'fire', + 'firebug', + 'first', + 'first-class', + 'first-line', + 'fish', + 'fitnesse', + 'five', + 'fix_this_later', + 'fixed', + 'fixed-income', + 'fixed-point', + 'fixme', + 'fl', + 'flag', + 'flag-control', + 'flags', + 'flash', + 'flatbuffers', + 'flex-config', + 'fload', + 'float', + 'float-exponent', + 'float_exp', + 'floating-point', + 'floating_point', + 'floor', + 'flow', + 'flow-control', + 'flowcontrol', + 'flows', + 'flowtype', + 'flush', + 'fma', + 'fma4', + 'fmod', + 'fn', + 'fold', + 'folder', + 'folder-actions', + 'following', + 'font', + 'font-cache', + 'font-face', + 'font-name', + 'font-size', + 'fontface', + 'fontforge', + 'foobar', + 'footer', + 'footnote', + 'for', + 'for-in-loop', + 'for-loop', + 'for-quantity', + 'forall', + 'force', + 'foreach', + 'foreign', + 'forever', + 'forge-config', + 'forin', + 'form', + 'form-feed', + 'formal', + 'format', + 'format-register', + 'format-verb', + 'formatted', + 'formatter', + 'formatting', + 'forth', + 'fortran', + 'forward', + 'foundation', + 'fountain', + 'four', + 'fourd-command', + 'fourd-constant', + 'fourd-constant-hex', + 'fourd-constant-number', + 'fourd-constant-string', + 'fourd-control-begin', + 'fourd-control-end', + 'fourd-declaration', + 'fourd-declaration-array', + 'fourd-local-variable', + 'fourd-parameter', + 'fourd-table', + 'fourd-tag', + 'fourd-variable', + 'fpm', + 'fpu', + 'fpu_x87', + 'fr', + 'fragment', + 'frame', + 'frames', + 'frametitle', + 'framexml', + 'free', + 'free-form', + 'freebasic', + 'freefem', + 'freespace2', + 'from', + 'from-file', + 'front-matter', + 'fs', + 'fs2', + 'fsc', + 'fsgsbase', + 'fsharp', + 'fsi', + 'fsl', + 'fsm', + 'fsp', + 'fsx', + 'fth', + 'ftl', + 'ftl20n', + 'full-line', + 'full-stop', + 'fun', + 'funarg', + 'func-tag', + 'func_call', + 'funchand', + 'function', + 'function-arity', + 'function-attribute', + 'function-call', + 'function-definition', + 'function-literal', + 'function-parameter', + 'function-recursive', + 'function-return', + 'function-type', + 'functionDeclaration', + 'functionDefinition', + 'function_definition', + 'function_prototype', + 'functional_test', + 'functionend', + 'functions', + 'functionstart', + 'fundimental', + 'funk', + 'funtion-definition', + 'fus', + 'future', + 'futures', + 'fuzzy-logic', + 'fx', + 'fx-foliage-replicator', + 'fx-light', + 'fx-shape-replicator', + 'fx-sun-light', + 'g', + 'g-code', + 'ga', + 'gain', + 'galaxy', + 'gallery', + 'game-base', + 'game-connection', + 'game-server', + 'gamebusk', + 'gamescript', + 'gams', + 'gams-lst', + 'gap', + 'garch', + 'gather', + 'gcode', + 'gdb', + 'gdscript', + 'gdx', + 'ge', + 'geant4-macro', + 'geck', + 'geck-keyword', + 'general', + 'general-purpose', + 'generate', + 'generator', + 'generic', + 'generic-config', + 'generic-spec', + 'generic-type', + 'generic_list', + 'genericcall', + 'generics', + 'genetic-algorithms', + 'geo', + 'geometric', + 'geometry', + 'geometry-adjustment', + 'get', + 'getproperty', + 'getsec', + 'getset', + 'getter', + 'gettext', + 'getword', + 'gfm', + 'gfm-todotxt', + 'gfx', + 'gh-number', + 'gherkin', + 'gisdk', + 'git', + 'git-attributes', + 'git-commit', + 'git-config', + 'git-rebase', + 'gitignore', + 'given', + 'gj', + 'gl', + 'glob', + 'global', + 'global-functions', + 'globals', + 'globalsection', + 'glsl', + 'glue', + 'glyph_class_name', + 'glyphname-value', + 'gml', + 'gmp', + 'gmsh', + 'gmx', + 'gn', + 'gnu', + 'gnuplot', + 'go', + 'goal', + 'goatee', + 'godmode', + 'gohtml', + 'gold', + 'golo', + 'google', + 'gosub', + 'gotemplate', + 'goto', + 'goto-label', + 'gpd', + 'gpd_note', + 'gpp', + 'grace', + 'grade-down', + 'grade-up', + 'gradient', + 'gradle', + 'grails', + 'grammar', + 'grammar-rule', + 'grammar_production', + 'grap', + 'grapahql', + 'graph', + 'graphics', + 'graphql', + 'grave-accent', + 'gray', + 'greater', + 'greater-equal', + 'greater-or-equal', + 'greek', + 'greek-letter', + 'green', + 'gremlin', + 'grey', + 'grg', + 'grid-table', + 'gridlists', + 'grog', + 'groovy', + 'groovy-properties', + 'group', + 'group-level-comment', + 'group-name', + 'group-number', + 'group-reference', + 'group-title', + 'group1', + 'group10', + 'group11', + 'group2', + 'group3', + 'group4', + 'group5', + 'group6', + 'group7', + 'group8', + 'group9', + 'groupend', + 'groupflag', + 'grouping-statement', + 'groupname', + 'groupstart', + 'growl', + 'grr', + 'gs', + 'gsc', + 'gsp', + 'gt', + 'guard', + 'guards', + 'gui', + 'gui-bitmap-ctrl', + 'gui-button-base-ctrl', + 'gui-canvas', + 'gui-control', + 'gui-filter-ctrl', + 'gui-frameset-ctrl', + 'gui-menu-bar', + 'gui-message-vector-ctrl', + 'gui-ml-text-ctrl', + 'gui-popup-menu-ctrl', + 'gui-scroll-ctrl', + 'gui-slider-ctrl', + 'gui-text-ctrl', + 'gui-text-edit-ctrl', + 'gui-text-list-ctrl', + 'guid', + 'guillemot', + 'guis', + 'gzip', + 'gzip_static', + 'h', + 'h1', + 'hack', + 'hackfragment', + 'haddock', + 'hairpin', + 'ham', + 'haml', + 'hamlbars', + 'hamlc', + 'hamlet', + 'hamlpy', + 'handlebar', + 'handlebars', + 'handler', + 'hanging-paragraph', + 'haproxy-config', + 'harbou', + 'harbour', + 'hard-break', + 'hardlinebreaks', + 'hash', + 'hash-tick', + 'hashbang', + 'hashicorp', + 'hashkey', + 'haskell', + 'haxe', + 'hbs', + 'hcl', + 'hdl', + 'hdr', + 'he', + 'header', + 'header-continuation', + 'header-value', + 'headername', + 'headers', + 'heading', + 'heading-0', + 'heading-1', + 'heading-2', + 'heading-3', + 'heading-4', + 'heading-5', + 'heading-6', + 'height', + 'helen', + 'help', + 'helper', + 'helpers', + 'heredoc', + 'heredoc-token', + 'herestring', + 'heritage', + 'hex', + 'hex-ascii', + 'hex-byte', + 'hex-literal', + 'hex-old', + 'hex-string', + 'hex-value', + 'hex8', + 'hexadecimal', + 'hexidecimal', + 'hexprefix', + 'hg-commit', + 'hgignore', + 'hi', + 'hidden', + 'hide', + 'high-minus', + 'highlight-end', + 'highlight-group', + 'highlight-start', + 'hint', + 'history', + 'hive', + 'hive-name', + 'hjson', + 'hl7', + 'hlsl', + 'hn', + 'hoa', + 'hoc', + 'hocharacter', + 'hocomment', + 'hocon', + 'hoconstant', + 'hocontinuation', + 'hocontrol', + 'hombrew-formula', + 'homebrew', + 'homematic', + 'hook', + 'hoon', + 'horizontal-blending', + 'horizontal-packed-arithmetic', + 'horizontal-rule', + 'hostname', + 'hosts', + 'hour', + 'hours', + 'hps', + 'hql', + 'hr', + 'hrm', + 'hs', + 'hsc2hs', + 'ht', + 'htaccess', + 'htl', + 'html', + 'html_entity', + 'htmlbars', + 'http', + 'hu', + 'hungary', + 'hxml', + 'hy', + 'hydrant', + 'hydrogen', + 'hyperbolic', + 'hyperlink', + 'hyphen', + 'hyphenation', + 'hyphenation-char', + 'i', + 'i-beam', + 'i18n', + 'iRev', + 'ice', + 'icinga2', + 'icmc', + 'icmptype', + 'icmpv6type', + 'icmpxtype', + 'iconv', + 'id', + 'id-type', + 'id-with-protocol', + 'idd', + 'ideal', + 'identical', + 'identifer', + 'identified', + 'identifier', + 'identifier-type', + 'identifiers-and-DTDs', + 'identity', + 'idf', + 'idl', + 'idris', + 'ieee', + 'if', + 'if-block', + 'if-branch', + 'if-condition', + 'if-else', + 'if-then', + 'ifacespec', + 'ifdef', + 'ifname', + 'ifndef', + 'ignore', + 'ignore-eol', + 'ignore-errors', + 'ignorebii', + 'ignored', + 'ignored-binding', + 'ignoring', + 'iisfunc', + 'ijk', + 'ilasm', + 'illagal', + 'illeagal', + 'illegal', + 'illumination-model', + 'image', + 'image-acquisition', + 'image-alignment', + 'image-option', + 'image-processing', + 'images', + 'imap', + 'imba', + 'imfchan', + 'img', + 'immediate', + 'immediately-evaluated', + 'immutable', + 'impex', + 'implementation', + 'implementation-defined-hooks', + 'implemented', + 'implements', + 'implicit', + 'import', + 'import-all', + 'importall', + 'important', + 'in', + 'in-block', + 'in-module', + 'in-out', + 'inappropriate', + 'include', + 'include-statement', + 'includefile', + 'incomplete', + 'incomplete-variable-assignment', + 'inconsistent', + 'increment', + 'increment-decrement', + 'indent', + 'indented', + 'indented-paragraph', + 'indepimage', + 'index', + 'index-seperator', + 'indexed', + 'indexer', + 'indexes', + 'indicator', + 'indices', + 'indirect', + 'indirection', + 'individual-enum-definition', + 'individual-rpc-call', + 'inet', + 'inetprototype', + 'inferred', + 'infes', + 'infinity', + 'infix', + 'info', + 'inform', + 'inform6', + 'inform7', + 'infotype', + 'ingore-eol', + 'inherit', + 'inheritDoc', + 'inheritance', + 'inherited', + 'inherited-class', + 'inherited-struct', + 'inherits', + 'ini', + 'init', + 'initial-lowercase', + 'initial-uppercase', + 'initial-value', + 'initialization', + 'initialize', + 'initializer-list', + 'ink', + 'inline', + 'inline-data', + 'inlineConditionalBranchSeparator', + 'inlineConditionalClause', + 'inlineConditionalEnd', + 'inlineConditionalStart', + 'inlineLogicEnd', + 'inlineLogicStart', + 'inlineSequenceEnd', + 'inlineSequenceSeparator', + 'inlineSequenceStart', + 'inlineSequenceTypeChar', + 'inlineblock', + 'inlinecode', + 'inlinecomment', + 'inlinetag', + 'inner', + 'inner-class', + 'inno', + 'ino', + 'inout', + 'input', + 'inquire', + 'inserted', + 'insertion', + 'insertion-and-extraction', + 'inside', + 'install', + 'instance', + 'instancemethods', + 'instanceof', + 'instances', + 'instantiation', + 'instruction', + 'instruction-pointer', + 'instructions', + 'instrument', + 'instrument-block', + 'instrument-control', + 'instrument-declaration', + 'int', + 'int32', + 'int64', + 'integer', + 'integer-float', + 'intel', + 'intel-hex', + 'intent', + 'intepreted', + 'interaction', + 'interbase', + 'interface', + 'interface-block', + 'interface-or-protocol', + 'interfaces', + 'interior-instance', + 'interiors', + 'interlink', + 'internal', + 'internet', + 'interpolate-argument', + 'interpolate-string', + 'interpolate-variable', + 'interpolated', + 'interpolation', + 'interrupt', + 'intersection', + 'interval', + 'intervalOrList', + 'intl', + 'intrinsic', + 'intuicio4', + 'invalid', + 'invalid-character', + 'invalid-character-escape', + 'invalid-inequality', + 'invalid-quote', + 'invalid-variable-name', + 'invariant', + 'invocation', + 'invoke', + 'invokee', + 'io', + 'ior', + 'iota', + 'ip', + 'ip-port', + 'ip6', + 'ipkg', + 'ipsec', + 'ipv4', + 'ipv6', + 'ipynb', + 'irct', + 'irule', + 'is', + 'isa', + 'isc', + 'iscexport', + 'isclass', + 'isml', + 'issue', + 'it', + 'italic', + 'italic-text', + 'item', + 'item-access', + 'itemlevel', + 'items', + 'iteration', + 'itunes', + 'ivar', + 'ja', + 'jack', + 'jade', + 'jakefile', + 'jasmin', + 'java', + 'java-properties', + 'java-props', + 'javadoc', + 'javascript', + 'jbeam', + 'jekyll', + 'jflex', + 'jibo-rule', + 'jinja', + 'jison', + 'jisonlex', + 'jmp', + 'joint', + 'joker', + 'jolie', + 'jot', + 'journaling', + 'jpl', + 'jq', + 'jquery', + 'js', + 'js-label', + 'jsdoc', + 'jsduck', + 'jsim', + 'json', + 'json5', + 'jsoniq', + 'jsonnet', + 'jsont', + 'jsp', + 'jsx', + 'julia', + 'julius', + 'jump', + 'juniper', + 'juniper-junos-config', + 'junit-test-report', + 'junos', + 'juttle', + 'jv', + 'jxa', + 'k', + 'kag', + 'kagex', + 'kb', + 'kbd', + 'kconfig', + 'kerboscript', + 'kernel', + 'kevs', + 'kevscript', + 'kewyword', + 'key', + 'key-assignment', + 'key-letter', + 'key-pair', + 'key-path', + 'key-value', + 'keyboard', + 'keyframe', + 'keyframes', + 'keygroup', + 'keyname', + 'keyspace', + 'keyspace-name', + 'keyvalue', + 'keyword', + 'keyword-parameter', + 'keyword1', + 'keyword2', + 'keyword3', + 'keyword4', + 'keyword5', + 'keyword6', + 'keyword7', + 'keyword8', + 'keyword_arrays', + 'keyword_objects', + 'keyword_roots', + 'keyword_string', + 'keywords', + 'keywork', + 'kickstart', + 'kind', + 'kmd', + 'kn', + 'knitr', + 'knockout', + 'knot', + 'ko', + 'ko-virtual', + 'kos', + 'kotlin', + 'krl', + 'ksp-cfg', + 'kspcfg', + 'kurumin', + 'kv', + 'kxi', + 'kxigauge', + 'l', + 'l20n', + 'l4proto', + 'label', + 'label-expression', + 'labeled', + 'labeled-parameter', + 'labelled-thing', + 'lagda', + 'lambda', + 'lambda-function', + 'lammps', + 'langref', + 'language', + 'language-range', + 'languagebabel', + 'langversion', + 'largesk', + 'lasso', + 'last', + 'last-paren-match', + 'latex', + 'latex2', + 'latino', + 'latte', + 'launch', + 'layout', + 'layoutbii', + 'lbsearch', + 'lc', + 'lc-3', + 'lcb', + 'ldap', + 'ldif', + 'le', + 'leader-char', + 'leading', + 'leading-space', + 'leading-tabs', + 'leaf', + 'lean', + 'ledger', + 'left', + 'left-margin', + 'leftshift', + 'lefttoright', + 'legacy', + 'legacy-setting', + 'lemon', + 'len', + 'length', + 'leopard', + 'less', + 'less-equal', + 'less-or-equal', + 'let', + 'letter', + 'level', + 'level-of-detail', + 'level1', + 'level2', + 'level3', + 'level4', + 'level5', + 'level6', + 'levels', + 'lex', + 'lexc', + 'lexical', + 'lf-in-string', + 'lhs', + 'li', + 'lib', + 'libfile', + 'library', + 'libs', + 'libxml', + 'lid', + 'lifetime', + 'ligature', + 'light', + 'light_purple', + 'lighting', + 'lightning', + 'lilypond', + 'lilypond-drummode', + 'lilypond-figbassmode', + 'lilypond-figuregroup', + 'lilypond-internals', + 'lilypond-lyricsmode', + 'lilypond-markupmode', + 'lilypond-notedrum', + 'lilypond-notemode', + 'lilypond-notemode-explicit', + 'lilypond-notenames', + 'lilypond-schememode', + 'limit_zone', + 'line-block', + 'line-break', + 'line-continuation', + 'line-cue-setting', + 'line-statement', + 'line-too-long', + 'linebreak', + 'linenumber', + 'link', + 'link-label', + 'link-text', + 'link-url', + 'linkage', + 'linkage-type', + 'linkedin', + 'linkedsockets', + 'linkplain', + 'linkplain-label', + 'linq', + 'linuxcncgcode', + 'liquid', + 'liquidhaskell', + 'liquidsoap', + 'lisp', + 'lisp-repl', + 'list', + 'list-done', + 'list-separator', + 'list-style-type', + 'list-today', + 'list_item', + 'listing', + 'listnum', + 'listvalues', + 'litaco', + 'litcoffee', + 'literal', + 'literal-string', + 'literate', + 'litword', + 'livecodescript', + 'livescript', + 'livescriptscript', + 'll', + 'llvm', + 'load-constants', + 'load-hint', + 'loader', + 'local', + 'local-variables', + 'localhost', + 'localizable', + 'localized', + 'localname', + 'locals', + 'localtable', + 'location', + 'lock', + 'log', + 'log-debug', + 'log-error', + 'log-failed', + 'log-info', + 'log-patch', + 'log-success', + 'log-verbose', + 'log-warning', + 'logarithm', + 'logging', + 'logic', + 'logicBegin', + 'logical', + 'logical-expression', + 'logicblox', + 'logicode', + 'logo', + 'logstash', + 'logtalk', + 'lol', + 'long', + 'look-ahead', + 'look-behind', + 'lookahead', + 'lookaround', + 'lookbehind', + 'loop', + 'loop-control', + 'low-high', + 'lowercase', + 'lowercase_character_not_allowed_here', + 'lozenge', + 'lparen', + 'lsg', + 'lsl', + 'lst', + 'lst-cpu12', + 'lstdo', + 'lt', + 'lt-gt', + 'lterat', + 'lu', + 'lua', + 'lucee', + 'lucius', + 'lury', + 'lv', + 'lyricsmode', + 'm', + 'm4', + 'm4sh', + 'm65816', + 'm68k', + 'mac-classic', + 'mac-fsaa', + 'machine', + 'machineclause', + 'macro', + 'macro-usage', + 'macro11', + 'macrocallblock', + 'macrocallinline', + 'madoko', + 'magenta', + 'magic', + 'magik', + 'mail', + 'mailer', + 'mailto', + 'main', + 'makefile', + 'makefile2', + 'mako', + 'mamba', + 'man', + 'mantissa', + 'manualmelisma', + 'map', + 'map-library', + 'map-name', + 'mapfile', + 'mapkey', + 'mapping', + 'mapping-type', + 'maprange', + 'marasm', + 'margin', + 'marginpar', + 'mark', + 'mark-input', + 'markdown', + 'marker', + 'marko', + 'marko-attribute', + 'marko-tag', + 'markup', + 'markupmode', + 'mas2j', + 'mask', + 'mason', + 'mat', + 'mata', + 'match', + 'match-bind', + 'match-branch', + 'match-condition', + 'match-definition', + 'match-exception', + 'match-option', + 'match-pattern', + 'material', + 'material-library', + 'material-name', + 'math', + 'math-symbol', + 'math_complex', + 'math_real', + 'mathematic', + 'mathematica', + 'mathematical', + 'mathematical-symbols', + 'mathematics', + 'mathjax', + 'mathml', + 'matlab', + 'matrix', + 'maude', + 'maven', + 'max', + 'max-angle', + 'max-distance', + 'max-length', + 'maxscript', + 'maybe', + 'mb', + 'mbstring', + 'mc', + 'mcc', + 'mccolor', + 'mch', + 'mcn', + 'mcode', + 'mcq', + 'mcr', + 'mcrypt', + 'mcs', + 'md', + 'mdash', + 'mdoc', + 'mdx', + 'me', + 'measure', + 'media', + 'media-feature', + 'media-property', + 'media-type', + 'mediawiki', + 'mei', + 'mel', + 'memaddress', + 'member', + 'member-function-attribute', + 'member-of', + 'membership', + 'memcache', + 'memcached', + 'memoir', + 'memoir-alltt', + 'memoir-fbox', + 'memoir-verbatim', + 'memory', + 'memory-management', + 'memory-protection', + 'memos', + 'menhir', + 'mention', + 'menu', + 'mercury', + 'merge-group', + 'merge-key', + 'merlin', + 'mesgTrigger', + 'mesgType', + 'message', + 'message-declaration', + 'message-forwarding-handler', + 'message-sending', + 'message-vector', + 'messages', + 'meta', + 'meta-conditional', + 'meta-data', + 'meta-file', + 'meta-info', + 'metaclass', + 'metacommand', + 'metadata', + 'metakey', + 'metamodel', + 'metapost', + 'metascript', + 'meteor', + 'method', + 'method-call', + 'method-definition', + 'method-modification', + 'method-mofification', + 'method-parameter', + 'method-parameters', + 'method-restriction', + 'methodcalls', + 'methods', + 'metrics', + 'mhash', + 'microsites', + 'microsoft-dynamics', + 'middle', + 'midi_processing', + 'migration', + 'mime', + 'min', + 'minelua', + 'minetweaker', + 'minitemplate', + 'minitest', + 'minus', + 'minute', + 'mips', + 'mirah', + 'misc', + 'miscellaneous', + 'mismatched', + 'missing', + 'missing-asterisk', + 'missing-inheritance', + 'missing-parameters', + 'missing-section-begin', + 'missingend', + 'mission-area', + 'mixin', + 'mixin-name', + 'mjml', + 'ml', + 'mlab', + 'mls', + 'mm', + 'mml', + 'mmx', + 'mmx_instructions', + 'mn', + 'mnemonic', + 'mobile-messaging', + 'mochi', + 'mod', + 'mod-r', + 'mod_perl', + 'mod_perl_1', + 'modblock', + 'modbus', + 'mode', + 'model', + 'model-based-calibration', + 'model-predictive-control', + 'modelica', + 'modelicascript', + 'modeline', + 'models', + 'modern', + 'modified', + 'modifier', + 'modifiers', + 'modify', + 'modify-range', + 'modifytime', + 'modl', + 'modr', + 'modula-2', + 'module', + 'module-alias', + 'module-binding', + 'module-definition', + 'module-expression', + 'module-function', + 'module-reference', + 'module-rename', + 'module-sum', + 'module-type', + 'module-type-definition', + 'modules', + 'modulo', + 'modx', + 'mojolicious', + 'mojom', + 'moment', + 'mond', + 'money', + 'mongo', + 'mongodb', + 'monicelli', + 'monitor', + 'monkberry', + 'monkey', + 'monospace', + 'monospaced', + 'monte', + 'month', + 'moon', + 'moonscript', + 'moos', + 'moose', + 'moosecpp', + 'motion', + 'mouse', + 'mov', + 'movement', + 'movie', + 'movie-file', + 'mozu', + 'mpw', + 'mpx', + 'mqsc', + 'ms', + 'mscgen', + 'mscript', + 'msg', + 'msgctxt', + 'msgenny', + 'msgid', + 'msgstr', + 'mson', + 'mson-block', + 'mss', + 'mta', + 'mtl', + 'mucow', + 'mult', + 'multi', + 'multi-line', + 'multi-symbol', + 'multi-threading', + 'multiclet', + 'multids-file', + 'multiline', + 'multiline-cell', + 'multiline-text-reference', + 'multiline-tiddler-title', + 'multimethod', + 'multipart', + 'multiplication', + 'multiplicative', + 'multiply', + 'multiverse', + 'mumps', + 'mundosk', + 'music', + 'must_be', + 'mustache', + 'mut', + 'mutable', + 'mutator', + 'mx', + 'mxml', + 'mydsl1', + 'mylanguage', + 'mysql', + 'mysqli', + 'mysqlnd-memcache', + 'mysqlnd-ms', + 'mysqlnd-qc', + 'mysqlnd-uh', + 'mzn', + 'nabla', + 'nagios', + 'name', + 'name-list', + 'name-of-parameter', + 'named', + 'named-char', + 'named-key', + 'named-tuple', + 'nameless-typed', + 'namelist', + 'names', + 'namespace', + 'namespace-block', + 'namespace-definition', + 'namespace-language', + 'namespace-prefix', + 'namespace-reference', + 'namespace-statement', + 'namespaces', + 'nan', + 'nand', + 'nant', + 'nant-build', + 'narration', + 'nas', + 'nasal', + 'nasl', + 'nasm', + 'nastran', + 'nat', + 'native', + 'nativeint', + 'natural', + 'navigation', + 'nbtkey', + 'ncf', + 'ncl', + 'ndash', + 'ne', + 'nearley', + 'neg-ratio', + 'negatable', + 'negate', + 'negated', + 'negation', + 'negative', + 'negative-look-ahead', + 'negative-look-behind', + 'negativity', + 'nesc', + 'nessuskb', + 'nested', + 'nested_braces', + 'nested_brackets', + 'nested_ltgt', + 'nested_parens', + 'nesty', + 'net', + 'net-object', + 'netbios', + 'network', + 'network-value', + 'networking', + 'neural-network', + 'new', + 'new-line', + 'new-object', + 'newline', + 'newline-spacing', + 'newlinetext', + 'newlisp', + 'newobject', + 'nez', + 'nft', + 'ngdoc', + 'nginx', + 'nickname', + 'nil', + 'nim', + 'nine', + 'ninja', + 'ninjaforce', + 'nit', + 'nitro', + 'nix', + 'nl', + 'nlf', + 'nm', + 'nm7', + 'no', + 'no-capture', + 'no-completions', + 'no-content', + 'no-default', + 'no-indent', + 'no-leading-digits', + 'no-trailing-digits', + 'no-validate-params', + 'node', + 'nogc', + 'noindent', + 'nokia-sros-config', + 'non', + 'non-capturing', + 'non-immediate', + 'non-null-typehinted', + 'non-standard', + 'non-terminal', + 'nondir-target', + 'none', + 'none-parameter', + 'nonlocal', + 'nonterminal', + 'noon', + 'noop', + 'nop', + 'noparams', + 'nor', + 'normal', + 'normal_numeric', + 'normal_objects', + 'normal_text', + 'normalised', + 'not', + 'not-a-number', + 'not-equal', + 'not-identical', + 'notation', + 'note', + 'notechord', + 'notemode', + 'notequal', + 'notequalexpr', + 'notes', + 'notidentical', + 'notification', + 'nowdoc', + 'noweb', + 'nrtdrv', + 'nsapi', + 'nscript', + 'nse', + 'nsis', + 'nsl', + 'ntriples', + 'nul', + 'null', + 'nullify', + 'nullological', + 'nulltype', + 'num', + 'number', + 'number-sign', + 'number-sign-equals', + 'numbered', + 'numberic', + 'numbers', + 'numbersign', + 'numeric', + 'numeric_std', + 'numerical', + 'nunjucks', + 'nut', + 'nvatom', + 'nxc', + 'o', + 'obj', + 'objaggregation', + 'objc', + 'objcpp', + 'objdump', + 'object', + 'object-comments', + 'object-definition', + 'object-level-comment', + 'object-name', + 'objects', + 'objectset', + 'objecttable', + 'objectvalues', + 'objj', + 'obsolete', + 'ocaml', + 'ocamllex', + 'occam', + 'oci8', + 'ocmal', + 'oct', + 'octal', + 'octave', + 'octave-change', + 'octave-shift', + 'octet', + 'octo', + 'octobercms', + 'octothorpe', + 'odd-tab', + 'odedsl', + 'ods', + 'of', + 'off', + 'offset', + 'ofx', + 'ogre', + 'ok', + 'ol', + 'old', + 'old-style', + 'omap', + 'omitted', + 'on-background', + 'on-error', + 'once', + 'one', + 'one-sixth-em', + 'one-twelfth-em', + 'oniguruma', + 'oniguruma-comment', + 'only', + 'only-in', + 'onoff', + 'ooc', + 'oot', + 'op-domain', + 'op-range', + 'opa', + 'opaque', + 'opc', + 'opcache', + 'opcode', + 'opcode-argument-types', + 'opcode-declaration', + 'opcode-definition', + 'opcode-details', + 'open', + 'open-gl', + 'openal', + 'openbinding', + 'opencl', + 'opendss', + 'opening', + 'opening-text', + 'openmp', + 'openssl', + 'opentype', + 'operand', + 'operands', + 'operation', + 'operator', + 'operator2', + 'operator3', + 'operators', + 'opmask', + 'opmaskregs', + 'optical-density', + 'optimization', + 'option', + 'option-description', + 'option-toggle', + 'optional', + 'optional-parameter', + 'optional-parameter-assignment', + 'optionals', + 'optionname', + 'options', + 'optiontype', + 'or', + 'oracle', + 'orbbasic', + 'orcam', + 'orchestra', + 'order', + 'ordered', + 'ordered-block', + 'ordinal', + 'organized', + 'orgtype', + 'origin', + 'osiris', + 'other', + 'other-inherited-class', + 'other_buildins', + 'other_keywords', + 'others', + 'otherwise', + 'otherwise-expression', + 'out', + 'outer', + 'output', + 'overload', + 'override', + 'owner', + 'ownership', + 'oz', + 'p', + 'p4', + 'p5', + 'p8', + 'pa', + 'package', + 'package-definition', + 'package_body', + 'packages', + 'packed', + 'packed-arithmetic', + 'packed-blending', + 'packed-comparison', + 'packed-conversion', + 'packed-floating-point', + 'packed-integer', + 'packed-math', + 'packed-mov', + 'packed-other', + 'packed-shift', + 'packed-shuffle', + 'packed-test', + 'padlock', + 'page', + 'page-props', + 'pagebreak', + 'pair', + 'pair-programming', + 'paket', + 'pandoc', + 'papyrus', + 'papyrus-assembly', + 'paragraph', + 'parallel', + 'param', + 'param-list', + 'paramater', + 'paramerised-type', + 'parameter', + 'parameter-entity', + 'parameter-space', + 'parameterless', + 'parameters', + 'paramless', + 'params', + 'paramtable', + 'paramter', + 'paren', + 'paren-group', + 'parens', + 'parent', + 'parent-reference', + 'parent-selector', + 'parent-selector-suffix', + 'parenthases', + 'parentheses', + 'parenthesis', + 'parenthetical', + 'parenthetical_list', + 'parenthetical_pair', + 'parfor', + 'parfor-quantity', + 'parse', + 'parsed', + 'parser', + 'parser-function', + 'parser-token', + 'parser3', + 'part', + 'partial', + 'particle', + 'pascal', + 'pass', + 'pass-through', + 'passive', + 'passthrough', + 'password', + 'password-hash', + 'patch', + 'path', + 'path-camera', + 'path-pattern', + 'pathoperation', + 'paths', + 'pathspec', + 'patientId', + 'pattern', + 'pattern-argument', + 'pattern-binding', + 'pattern-definition', + 'pattern-match', + 'pattern-offset', + 'patterns', + 'pause', + 'payee', + 'payload', + 'pbo', + 'pbtxt', + 'pcdata', + 'pcntl', + 'pdd', + 'pddl', + 'ped', + 'pegcoffee', + 'pegjs', + 'pending', + 'percentage', + 'percentage-sign', + 'percussionnote', + 'period', + 'perl', + 'perl-section', + 'perl6', + 'perl6fe', + 'perlfe', + 'perlt6e', + 'perm', + 'permutations', + 'personalization', + 'pervasive', + 'pf', + 'pflotran', + 'pfm', + 'pfx', + 'pgn', + 'pgsql', + 'phone', + 'phone-number', + 'phonix', + 'php', + 'php-code-in-comment', + 'php_apache', + 'php_dom', + 'php_ftp', + 'php_imap', + 'php_mssql', + 'php_odbc', + 'php_pcre', + 'php_spl', + 'php_zip', + 'phpdoc', + 'phrasemodifiers', + 'phraslur', + 'physical-zone', + 'physics', + 'pi', + 'pic', + 'pick', + 'pickup', + 'picture', + 'pig', + 'pillar', + 'pipe', + 'pipe-sign', + 'pipeline', + 'piratesk', + 'pitch', + 'pixie', + 'pkgbuild', + 'pl', + 'placeholder', + 'placeholder-parts', + 'plain', + 'plainsimple-emphasize', + 'plainsimple-heading', + 'plainsimple-number', + 'plantuml', + 'player', + 'playerversion', + 'pld_modeling', + 'please-build', + 'please-build-defs', + 'plist', + 'plsql', + 'plugin', + 'plus', + 'plztarget', + 'pmc', + 'pml', + 'pmlPhysics-arrangecharacter', + 'pmlPhysics-emphasisequote', + 'pmlPhysics-graphic', + 'pmlPhysics-header', + 'pmlPhysics-htmlencoded', + 'pmlPhysics-links', + 'pmlPhysics-listtable', + 'pmlPhysics-physicalquantity', + 'pmlPhysics-relationships', + 'pmlPhysics-slides', + 'pmlPhysics-slidestacks', + 'pmlPhysics-speech', + 'pmlPhysics-structure', + 'pnt', + 'po', + 'pod', + 'poe', + 'pogoscript', + 'point', + 'point-size', + 'pointer', + 'pointer-arith', + 'pointer-following', + 'points', + 'polarcoord', + 'policiesbii', + 'policy', + 'polydelim', + 'polygonal', + 'polymer', + 'polymorphic', + 'polymorphic-variant', + 'polynomial-degree', + 'polysep', + 'pony', + 'port', + 'port_list', + 'pos-ratio', + 'position-cue-setting', + 'positional', + 'positive', + 'posix', + 'posix-reserved', + 'post-match', + 'postblit', + 'postcss', + 'postfix', + 'postpone', + 'postscript', + 'potigol', + 'potion', + 'pound', + 'pound-sign', + 'povray', + 'power', + 'power_set', + 'powershell', + 'pp', + 'ppc', + 'ppcasm', + 'ppd', + 'praat', + 'pragma', + 'pragma-all-once', + 'pragma-mark', + 'pragma-message', + 'pragma-newline-spacing', + 'pragma-newline-spacing-value', + 'pragma-once', + 'pragma-stg', + 'pragma-stg-value', + 'pre', + 'pre-defined', + 'pre-match', + 'preamble', + 'prec', + 'precedence', + 'precipitation', + 'precision', + 'precision-point', + 'pred', + 'predefined', + 'predicate', + 'prefetch', + 'prefetchwt', + 'prefix', + 'prefixed-uri', + 'prefixes', + 'preinst', + 'prelude', + 'prepare', + 'prepocessor', + 'preposition', + 'prepositional', + 'preprocessor', + 'prerequisites', + 'preset', + 'preview', + 'previous', + 'prg', + 'primary', + 'primitive', + 'primitive-datatypes', + 'primitive-field', + 'print', + 'print-argument', + 'priority', + 'prism', + 'private', + 'privileged', + 'pro', + 'probe', + 'proc', + 'procedure', + 'procedure_definition', + 'procedure_prototype', + 'process', + 'process-id', + 'process-substitution', + 'processes', + 'processing', + 'proctitle', + 'production', + 'profile', + 'profiling', + 'program', + 'program-block', + 'program-name', + 'progressbars', + 'proguard', + 'project', + 'projectile', + 'prolog', + 'prolog-flags', + 'prologue', + 'promoted', + 'prompt', + 'prompt-prefix', + 'prop', + 'properties', + 'properties_literal', + 'property', + 'property-flag', + 'property-list', + 'property-name', + 'property-value', + 'property-with-attributes', + 'propertydef', + 'propertyend', + 'propertygroup', + 'propertygrouptable', + 'propertyset', + 'propertytable', + 'proposition', + 'protection', + 'protections', + 'proto', + 'protobuf', + 'protobufs', + 'protocol', + 'protocol-specification', + 'prototype', + 'provision', + 'proxy', + 'psci', + 'pseudo', + 'pseudo-class', + 'pseudo-element', + 'pseudo-method', + 'pseudo-mnemonic', + 'pseudo-variable', + 'pshdl', + 'pspell', + 'psql', + 'pt', + 'ptc-config', + 'ptc-config-modelcheck', + 'pthread', + 'ptr', + 'ptx', + 'public', + 'pug', + 'punchcard', + 'punctual', + 'punctuation', + 'punctutation', + 'puncuation', + 'puncutation', + 'puntuation', + 'puppet', + 'purebasic', + 'purescript', + 'pweave', + 'pwisa', + 'pwn', + 'py2pml', + 'pyj', + 'pyjade', + 'pymol', + 'pyresttest', + 'python', + 'python-function', + 'q', + 'q-brace', + 'q-bracket', + 'q-ltgt', + 'q-paren', + 'qa', + 'qm', + 'qml', + 'qos', + 'qoute', + 'qq', + 'qq-brace', + 'qq-bracket', + 'qq-ltgt', + 'qq-paren', + 'qry', + 'qtpro', + 'quad', + 'quad-arrow-down', + 'quad-arrow-left', + 'quad-arrow-right', + 'quad-arrow-up', + 'quad-backslash', + 'quad-caret-down', + 'quad-caret-up', + 'quad-circle', + 'quad-colon', + 'quad-del-down', + 'quad-del-up', + 'quad-diamond', + 'quad-divide', + 'quad-equal', + 'quad-jot', + 'quad-less', + 'quad-not-equal', + 'quad-question', + 'quad-quote', + 'quad-slash', + 'quadrigraph', + 'qual', + 'qualified', + 'qualifier', + 'quality', + 'quant', + 'quantifier', + 'quantifiers', + 'quartz', + 'quasi', + 'quasiquote', + 'quasiquotes', + 'query', + 'query-dsl', + 'question', + 'questionmark', + 'quicel', + 'quicktemplate', + 'quicktime-file', + 'quotation', + 'quote', + 'quoted', + 'quoted-identifier', + 'quoted-object', + 'quoted-or-unquoted', + 'quotes', + 'qx', + 'qx-brace', + 'qx-bracket', + 'qx-ltgt', + 'qx-paren', + 'r', + 'r3', + 'rabl', + 'racket', + 'radar', + 'radar-area', + 'radiobuttons', + 'radix', + 'rails', + 'rainmeter', + 'raml', + 'random', + 'random_number', + 'randomsk', + 'range', + 'range-2', + 'rank', + 'rant', + 'rapid', + 'rarity', + 'ratio', + 'rational-form', + 'raw', + 'raw-regex', + 'raxe', + 'rb', + 'rd', + 'rdfs-type', + 'rdrand', + 'rdseed', + 'react', + 'read', + 'readline', + 'readonly', + 'readwrite', + 'real', + 'realip', + 'rebeca', + 'rebol', + 'rec', + 'receive', + 'receive-channel', + 'recipe', + 'recipient-subscriber-list', + 'recode', + 'record', + 'record-field', + 'record-usage', + 'recordfield', + 'recutils', + 'red', + 'redbook-audio', + 'redirect', + 'redirection', + 'redprl', + 'redundancy', + 'ref', + 'refer', + 'reference', + 'referer', + 'refinement', + 'reflection', + 'reg', + 'regex', + 'regexname', + 'regexp', + 'regexp-option', + 'region-anchor-setting', + 'region-cue-setting', + 'region-identifier-setting', + 'region-lines-setting', + 'region-scroll-setting', + 'region-viewport-anchor-setting', + 'region-width-setting', + 'register', + 'register-64', + 'registers', + 'regular', + 'reiny', + 'reject', + 'rejecttype', + 'rel', + 'related', + 'relation', + 'relational', + 'relations', + 'relationship', + 'relationship-name', + 'relationship-pattern', + 'relationship-pattern-end', + 'relationship-pattern-start', + 'relationship-type', + 'relationship-type-or', + 'relationship-type-ored', + 'relationship-type-start', + 'relative', + 'rem', + 'reminder', + 'remote', + 'removed', + 'rename', + 'renamed-from', + 'renamed-to', + 'renaming', + 'render', + 'renpy', + 'reocrd', + 'reparator', + 'repeat', + 'repl-prompt', + 'replace', + 'replaceXXX', + 'replaced', + 'replacement', + 'reply', + 'repo', + 'reporter', + 'reporting', + 'repository', + 'request', + 'request-type', + 'require', + 'required', + 'requiredness', + 'requirement', + 'requirements', + 'rescue', + 'reserved', + 'reset', + 'resolution', + 'resource', + 'resource-manager', + 'response', + 'response-type', + 'rest', + 'rest-args', + 'rester', + 'restriced', + 'restructuredtext', + 'result', + 'result-separator', + 'results', + 'retro', + 'return', + 'return-type', + 'return-value', + 'returns', + 'rev', + 'reverse', + 'reversed', + 'review', + 'rewrite', + 'rewrite-condition', + 'rewrite-operator', + 'rewrite-pattern', + 'rewrite-substitution', + 'rewrite-test', + 'rewritecond', + 'rewriterule', + 'rf', + 'rfc', + 'rgb', + 'rgb-percentage', + 'rgb-value', + 'rhap', + 'rho', + 'rhs', + 'rhtml', + 'richtext', + 'rid', + 'right', + 'ring', + 'riot', + 'rivescript', + 'rjs', + 'rl', + 'rmarkdown', + 'rnc', + 'rng', + 'ro', + 'roboconf', + 'robot', + 'robotc', + 'robust-control', + 'rockerfile', + 'roff', + 'role', + 'rollout-control', + 'root', + 'rotate', + 'rotate-first', + 'rotate-last', + 'round', + 'round-brackets', + 'router', + 'routeros', + 'routes', + 'routine', + 'row', + 'row2', + 'rowspan', + 'roxygen', + 'rparent', + 'rpc', + 'rpc-definition', + 'rpe', + 'rpm-spec', + 'rpmspec', + 'rpt', + 'rq', + 'rrd', + 'rsl', + 'rspec', + 'rtemplate', + 'ru', + 'ruby', + 'rubymotion', + 'rule', + 'rule-identifier', + 'rule-name', + 'rule-pattern', + 'rule-tag', + 'ruleDefinition', + 'rules', + 'run', + 'rune', + 'runoff', + 'runtime', + 'rust', + 'rviz', + 'rx', + 's', + 'safe-call', + 'safe-navigation', + 'safe-trap', + 'safer', + 'safety', + 'sage', + 'salesforce', + 'salt', + 'sampler', + 'sampler-comparison', + 'samplerarg', + 'sampling', + 'sas', + 'sass', + 'sass-script-maps', + 'satcom', + 'satisfies', + 'sblock', + 'scad', + 'scala', + 'scaladoc', + 'scalar', + 'scale', + 'scam', + 'scan', + 'scenario', + 'scenario_outline', + 'scene', + 'scene-object', + 'scheduled', + 'schelp', + 'schem', + 'schema', + 'scheme', + 'schememode', + 'scientific', + 'scilab', + 'sck', + 'scl', + 'scope', + 'scope-name', + 'scope-resolution', + 'scoping', + 'score', + 'screen', + 'scribble', + 'script', + 'script-flag', + 'script-metadata', + 'script-object', + 'script-tag', + 'scripting', + 'scriptlet', + 'scriptlocal', + 'scriptname', + 'scriptname-declaration', + 'scripts', + 'scroll', + 'scrollbars', + 'scrollpanes', + 'scss', + 'scumm', + 'sdbl', + 'sdl', + 'sdo', + 'sealed', + 'search', + 'seawolf', + 'second', + 'secondary', + 'section', + 'section-attribute', + 'sectionname', + 'sections', + 'see', + 'segment', + 'segment-registers', + 'segment-resolution', + 'select', + 'select-block', + 'selector', + 'self', + 'self-binding', + 'self-close', + 'sem', + 'semantic', + 'semanticmodel', + 'semi-colon', + 'semicolon', + 'semicoron', + 'semireserved', + 'send-channel', + 'sender', + 'senum', + 'sep', + 'separator', + 'separatory', + 'sepatator', + 'seperator', + 'sequence', + 'sequences', + 'serial', + 'serpent', + 'server', + 'service', + 'service-declaration', + 'service-rpc', + 'services', + 'session', + 'set', + 'set-colour', + 'set-size', + 'set-variable', + 'setbagmix', + 'setname', + 'setproperty', + 'sets', + 'setter', + 'setting', + 'settings', + 'settype', + 'setword', + 'seven', + 'severity', + 'sexpr', + 'sfd', + 'sfst', + 'sgml', + 'sgx1', + 'sgx2', + 'sha', + 'sha256', + 'sha512', + 'sha_functions', + 'shad', + 'shade', + 'shaderlab', + 'shadow-object', + 'shape', + 'shape-base', + 'shape-base-data', + 'shared', + 'shared-static', + 'sharp', + 'sharpequal', + 'sharpge', + 'sharpgt', + 'sharple', + 'sharplt', + 'sharpness', + 'shebang', + 'shell', + 'shell-function', + 'shell-session', + 'shift', + 'shift-and-rotate', + 'shift-left', + 'shift-right', + 'shine', + 'shinescript', + 'shipflow', + 'shmop', + 'short', + 'shortcut', + 'shortcuts', + 'shorthand', + 'shorthandpropertyname', + 'show', + 'show-argument', + 'shuffle-and-unpack', + 'shutdown', + 'shy', + 'sidebar', + 'sifu', + 'sigdec', + 'sigil', + 'sign-line', + 'signal', + 'signal-processing', + 'signature', + 'signed', + 'signed-int', + 'signedness', + 'signifier', + 'silent', + 'sim-group', + 'sim-object', + 'sim-set', + 'simd', + 'simd-horizontal', + 'simd-integer', + 'simple', + 'simple-delimiter', + 'simple-divider', + 'simple-element', + 'simple_delimiter', + 'simplexml', + 'simplez', + 'simulate', + 'since', + 'singe', + 'single', + 'single-line', + 'single-quote', + 'single-quoted', + 'single_quote', + 'singlequote', + 'singleton', + 'singleword', + 'sites', + 'six', + 'size', + 'size-cue-setting', + 'sized_integer', + 'sizeof', + 'sjs', + 'sjson', + 'sk', + 'skaction', + 'skdragon', + 'skeeland', + 'skellett', + 'sketchplugin', + 'skevolved', + 'skew', + 'skill', + 'skipped', + 'skmorkaz', + 'skquery', + 'skrambled', + 'skrayfall', + 'skript', + 'skrpg', + 'sksharp', + 'skstuff', + 'skutilities', + 'skvoice', + 'sky', + 'skyrim', + 'sl', + 'slash', + 'slash-bar', + 'slash-option', + 'slash-sign', + 'slashes', + 'sleet', + 'slice', + 'slim', + 'slm', + 'sln', + 'slot', + 'slugignore', + 'sma', + 'smali', + 'smalltalk', + 'smarty', + 'smb', + 'smbinternal', + 'smilebasic', + 'sml', + 'smoothing-group', + 'smpte', + 'smtlib', + 'smx', + 'snakeskin', + 'snapshot', + 'snlog', + 'snmp', + 'so', + 'soap', + 'social', + 'socketgroup', + 'sockets', + 'soft', + 'solidity', + 'solve', + 'soma', + 'somearg', + 'something', + 'soql', + 'sort', + 'sorting', + 'souce', + 'sound', + 'sound_processing', + 'sound_synthesys', + 'source', + 'source-constant', + 'soy', + 'sp', + 'space', + 'space-after-command', + 'spacebars', + 'spaces', + 'sparql', + 'spath', + 'spec', + 'special', + 'special-attributes', + 'special-character', + 'special-curve', + 'special-functions', + 'special-hook', + 'special-keyword', + 'special-method', + 'special-point', + 'special-token-sequence', + 'special-tokens', + 'special-type', + 'specification', + 'specifier', + 'spectral-curve', + 'specular-exponent', + 'specular-reflectivity', + 'sphinx', + 'sphinx-domain', + 'spice', + 'spider', + 'spindlespeed', + 'splat', + 'spline', + 'splunk', + 'splunk-conf', + 'splus', + 'spn', + 'spread', + 'spread-line', + 'spreadmap', + 'sprite', + 'sproto', + 'sproutcore', + 'sqf', + 'sql', + 'sqlbuiltin', + 'sqlite', + 'sqlsrv', + 'sqr', + 'sqsp', + 'squad', + 'square', + 'squart', + 'squirrel', + 'sr-Cyrl', + 'sr-Latn', + 'src', + 'srltext', + 'sros', + 'srt', + 'srv', + 'ss', + 'ssa', + 'sse', + 'sse2', + 'sse2_simd', + 'sse3', + 'sse4', + 'sse4_simd', + 'sse5', + 'sse_avx', + 'sse_simd', + 'ssh-config', + 'ssi', + 'ssl', + 'ssn', + 'sstemplate', + 'st', + 'stable', + 'stack', + 'stack-effect', + 'stackframe', + 'stage', + 'stan', + 'standard', + 'standard-key', + 'standard-links', + 'standard-suite', + 'standardadditions', + 'standoc', + 'star', + 'starline', + 'start', + 'start-block', + 'start-condition', + 'start-symbol', + 'start-value', + 'starting-function-params', + 'starting-functions', + 'starting-functions-point', + 'startshape', + 'stata', + 'statamic', + 'state', + 'state-flag', + 'state-management', + 'stateend', + 'stategrouparg', + 'stategroupval', + 'statement', + 'statement-separator', + 'states', + 'statestart', + 'statetable', + 'static', + 'static-assert', + 'static-classes', + 'static-if', + 'static-shape', + 'staticimages', + 'statistics', + 'stats', + 'std', + 'stdWrap', + 'std_logic', + 'std_logic_1164', + 'stderr-write-file', + 'stdint', + 'stdlib', + 'stdlibcall', + 'stdplugin', + 'stem', + 'step', + 'step-size', + 'steps', + 'stg', + 'stile-shoe-left', + 'stile-shoe-up', + 'stile-tilde', + 'stitch', + 'stk', + 'stmt', + 'stochastic', + 'stop', + 'stopping', + 'storage', + 'story', + 'stp', + 'straight-quote', + 'stray', + 'stray-comment-end', + 'stream', + 'stream-selection-and-control', + 'streamsfuncs', + 'streem', + 'strict', + 'strictness', + 'strike', + 'strikethrough', + 'string', + 'string-constant', + 'string-format', + 'string-interpolation', + 'string-long-quote', + 'string-long-single-quote', + 'string-single-quote', + 'stringchar', + 'stringize', + 'strings', + 'strong', + 'struc', + 'struct', + 'struct-union-block', + 'structdef', + 'structend', + 'structs', + 'structstart', + 'structtable', + 'structure', + 'stuff', + 'stupid-goddamn-hack', + 'style', + 'styleblock', + 'styles', + 'stylus', + 'sub', + 'sub-pattern', + 'subchord', + 'subckt', + 'subcmd', + 'subexp', + 'subexpression', + 'subkey', + 'subkeys', + 'subl', + 'submodule', + 'subnet', + 'subnet6', + 'subpattern', + 'subprogram', + 'subroutine', + 'subscript', + 'subsection', + 'subsections', + 'subset', + 'subshell', + 'subsort', + 'substituted', + 'substitution', + 'substitution-definition', + 'subtitle', + 'subtlegradient', + 'subtlegray', + 'subtract', + 'subtraction', + 'subtype', + 'suffix', + 'sugarml', + 'sugarss', + 'sugly', + 'sugly-comparison-operators', + 'sugly-control-keywords', + 'sugly-declare-function', + 'sugly-delcare-operator', + 'sugly-delcare-variable', + 'sugly-else-in-invalid-position', + 'sugly-encode-clause', + 'sugly-function-groups', + 'sugly-function-recursion', + 'sugly-function-variables', + 'sugly-general-functions', + 'sugly-general-operators', + 'sugly-generic-classes', + 'sugly-generic-types', + 'sugly-global-function', + 'sugly-int-constants', + 'sugly-invoke-function', + 'sugly-json-clause', + 'sugly-language-constants', + 'sugly-math-clause', + 'sugly-math-constants', + 'sugly-multiple-parameter-function', + 'sugly-number-constants', + 'sugly-operator-operands', + 'sugly-print-clause', + 'sugly-single-parameter-function', + 'sugly-subject-or-predicate', + 'sugly-type-function', + 'sugly-uri-clause', + 'summary', + 'super', + 'superclass', + 'supercollider', + 'superscript', + 'superset', + 'supervisor', + 'supervisord', + 'supplemental', + 'supplimental', + 'support', + 'suppress-image-or-category', + 'suppressed', + 'surface', + 'surface-technique', + 'sv', + 'svg', + 'svm', + 'svn', + 'swift', + 'swig', + 'switch', + 'switch-block', + 'switch-expression', + 'switch-statement', + 'switchEnd', + 'switchStart', + 'swizzle', + 'sybase', + 'syllableseparator', + 'symbol', + 'symbol-definition', + 'symbol-type', + 'symbolic', + 'symbolic-math', + 'symbols', + 'symmetry', + 'sync-match', + 'sync-mode', + 'sync-mode-location', + 'synchronization', + 'synchronize', + 'synchronized', + 'synergy', + 'synopsis', + 'syntax', + 'syntax-case', + 'syntax-cluster', + 'syntax-conceal', + 'syntax-error', + 'syntax-include', + 'syntax-item', + 'syntax-keywords', + 'syntax-match', + 'syntax-option', + 'syntax-region', + 'syntax-rule', + 'syntax-spellcheck', + 'syntax-sync', + 'sys-types', + 'sysj', + 'syslink', + 'syslog-ng', + 'system', + 'system-events', + 'system-identification', + 'system-table-pointer', + 'systemreference', + 'sytem-events', + 't', + 't3datastructure', + 't4', + 't5', + 't7', + 'ta', + 'tab', + 'table', + 'table-name', + 'tablename', + 'tabpanels', + 'tabs', + 'tabular', + 'tacacs', + 'tack-down', + 'tack-up', + 'taco', + 'tads3', + 'tag', + 'tag-string', + 'tag-value', + 'tagbraces', + 'tagdef', + 'tagged', + 'tagger_script', + 'taglib', + 'tagname', + 'tagnamedjango', + 'tags', + 'taint', + 'take', + 'target', + 'targetobj', + 'targetprop', + 'task', + 'tasks', + 'tbdfile', + 'tbl', + 'tbody', + 'tcl', + 'tcoffee', + 'tcp-object', + 'td', + 'tdl', + 'tea', + 'team', + 'telegram', + 'tell', + 'telnet', + 'temp', + 'template', + 'template-call', + 'template-parameter', + 'templatetag', + 'tempo', + 'temporal', + 'term', + 'term-comparison', + 'term-creation-and-decomposition', + 'term-io', + 'term-testing', + 'term-unification', + 'terminal', + 'terminate', + 'termination', + 'terminator', + 'terms', + 'ternary', + 'ternary-if', + 'terra', + 'terraform', + 'terrain-block', + 'test', + 'testcase', + 'testing', + 'tests', + 'testsuite', + 'testx', + 'tex', + 'texres', + 'texshop', + 'text', + 'text-reference', + 'text-suite', + 'textbf', + 'textcolor', + 'textile', + 'textio', + 'textit', + 'textlabels', + 'textmate', + 'texttt', + 'textual', + 'texture', + 'texture-map', + 'texture-option', + 'tfoot', + 'th', + 'thead', + 'then', + 'therefore', + 'thin', + 'thing1', + 'third', + 'this', + 'thorn', + 'thread', + 'three', + 'thrift', + 'throughput', + 'throw', + 'throwables', + 'throws', + 'tick', + 'ticket-num', + 'ticket-psa', + 'tid-file', + 'tidal', + 'tidalcycles', + 'tiddler', + 'tiddler-field', + 'tiddler-fields', + 'tidy', + 'tier', + 'tieslur', + 'tikz', + 'tilde', + 'time', + 'timeblock', + 'timehrap', + 'timeout', + 'timer', + 'times', + 'timesig', + 'timespan', + 'timespec', + 'timestamp', + 'timing', + 'titanium', + 'title', + 'title-page', + 'title-text', + 'titled-paragraph', + 'tjs', + 'tl', + 'tla', + 'tlh', + 'tmpl', + 'tmsim', + 'tmux', + 'tnote', + 'tnsaudit', + 'to', + 'to-file', + 'to-type', + 'toc', + 'toc-list', + 'todo', + 'todo_extra', + 'todotxt', + 'token', + 'token-def', + 'token-paste', + 'token-type', + 'tokenised', + 'tokenizer', + 'toml', + 'too-many-tildes', + 'tool', + 'toolbox', + 'tooltip', + 'top', + 'top-level', + 'top_level', + 'topas', + 'topic', + 'topic-decoration', + 'topic-title', + 'tornado', + 'torque', + 'torquescript', + 'tosca', + 'total-config', + 'totaljs', + 'tpye', + 'tr', + 'trace', + 'trace-argument', + 'trace-object', + 'traceback', + 'tracing', + 'track_processing', + 'trader', + 'tradersk', + 'trail', + 'trailing', + 'trailing-array-separator', + 'trailing-dictionary-separator', + 'trailing-match', + 'trait', + 'traits', + 'traits-keyword', + 'transaction', + 'transcendental', + 'transcludeblock', + 'transcludeinline', + 'transclusion', + 'transform', + 'transformation', + 'transient', + 'transition', + 'transitionable-property-value', + 'translation', + 'transmission-filter', + 'transparency', + 'transparent-line', + 'transpose', + 'transposed-func', + 'transposed-matrix', + 'transposed-parens', + 'transposed-variable', + 'trap', + 'tree', + 'treetop', + 'trenni', + 'trigEvent_', + 'trigLevelMod_', + 'trigLevel_', + 'trigger', + 'trigger-words', + 'triggermodifier', + 'trigonometry', + 'trimming-loop', + 'triple', + 'triple-dash', + 'triple-slash', + 'triple-star', + 'true', + 'truncate', + 'truncation', + 'truthgreen', + 'try', + 'try-catch', + 'trycatch', + 'ts', + 'tsql', + 'tss', + 'tst', + 'tsv', + 'tsx', + 'tt', + 'ttcn3', + 'ttlextension', + 'ttpmacro', + 'tts', + 'tubaina', + 'tubaina2', + 'tul', + 'tup', + 'tuple', + 'turbulence', + 'turing', + 'turquoise', + 'turtle', + 'tutch', + 'tvml', + 'tw5', + 'twig', + 'twigil', + 'twiki', + 'two', + 'txl', + 'txt', + 'txt2tags', + 'type', + 'type-annotation', + 'type-cast', + 'type-cheat', + 'type-checking', + 'type-constrained', + 'type-constraint', + 'type-declaration', + 'type-def', + 'type-definition', + 'type-definition-group', + 'type-definitions', + 'type-descriptor', + 'type-of', + 'type-or', + 'type-parameter', + 'type-parameters', + 'type-signature', + 'type-spec', + 'type-specialization', + 'type-specifiers', + 'type_2', + 'type_trait', + 'typeabbrev', + 'typeclass', + 'typed', + 'typed-hole', + 'typedblock', + 'typedcoffeescript', + 'typedecl', + 'typedef', + 'typeexp', + 'typehint', + 'typehinted', + 'typeid', + 'typename', + 'types', + 'typesbii', + 'typescriptish', + 'typographic-quotes', + 'typoscript', + 'typoscript2', + 'u', + 'u-degree', + 'u-end', + 'u-offset', + 'u-resolution', + 'u-scale', + 'u-segments', + 'u-size', + 'u-start', + 'u-value', + 'uc', + 'ucicfg', + 'ucicmd', + 'udaf', + 'udf', + 'udl', + 'udp', + 'udtf', + 'ui', + 'ui-block', + 'ui-group', + 'ui-state', + 'ui-subgroup', + 'uintptr', + 'ujm', + 'uk', + 'ul', + 'umbaska', + 'unOp', + 'unary', + 'unbuffered', + 'unchecked', + 'uncleared', + 'unclosed', + 'unclosed-string', + 'unconstrained', + 'undef', + 'undefined', + 'underbar-circle', + 'underbar-diamond', + 'underbar-iota', + 'underbar-jot', + 'underbar-quote', + 'underbar-semicolon', + 'underline', + 'underline-text', + 'underlined', + 'underscore', + 'undocumented', + 'unescaped-quote', + 'unexpected', + 'unexpected-characters', + 'unexpected-extends', + 'unexpected-extends-character', + 'unfiled', + 'unformatted', + 'unicode', + 'unicode-16-bit', + 'unicode-32-bit', + 'unicode-escape', + 'unicode-raw', + 'unicode-raw-regex', + 'unified', + 'unify', + 'unimplemented', + 'unimportant', + 'union', + 'union-declaration', + 'unique-id', + 'unit', + 'unit-checking', + 'unit-test', + 'unit_test', + 'unittest', + 'unity', + 'unityscript', + 'universal-match', + 'unix', + 'unknown', + 'unknown-escape', + 'unknown-method', + 'unknown-property-name', + 'unknown-rune', + 'unlabeled', + 'unless', + 'unnecessary', + 'unnumbered', + 'uno', + 'unoconfig', + 'unop', + 'unoproj', + 'unordered', + 'unordered-block', + 'unosln', + 'unpack', + 'unpacking', + 'unparsed', + 'unqualified', + 'unquoted', + 'unrecognized', + 'unrecognized-character', + 'unrecognized-character-escape', + 'unrecognized-string-escape', + 'unsafe', + 'unsigned', + 'unsigned-int', + 'unsized_integer', + 'unsupplied', + 'until', + 'untitled', + 'untyped', + 'unused', + 'uopz', + 'update', + 'uppercase', + 'upstream', + 'upwards', + 'ur', + 'uri', + 'url', + 'usable', + 'usage', + 'use', + 'use-as', + 'use-map', + 'use-material', + 'usebean', + 'usecase', + 'usecase-block', + 'user', + 'user-defined', + 'user-defined-property', + 'user-defined-type', + 'user-interaction', + 'userflagsref', + 'userid', + 'username', + 'users', + 'using', + 'using-namespace-declaration', + 'using_animtree', + 'util', + 'utilities', + 'utility', + 'utxt', + 'uv-resolution', + 'uvu', + 'uvw', + 'ux', + 'uxc', + 'uxl', + 'uz', + 'v', + 'v-degree', + 'v-end', + 'v-offset', + 'v-resolution', + 'v-scale', + 'v-segments', + 'v-size', + 'v-start', + 'v-value', + 'val', + 'vala', + 'valgrind', + 'valid', + 'valid-ampersand', + 'valid-bracket', + 'valign', + 'value', + 'value-pair', + 'value-signature', + 'value-size', + 'value-type', + 'valuepair', + 'vamos', + 'vamp', + 'vane-down', + 'vane-left', + 'vane-right', + 'vane-up', + 'var', + 'var-single-variable', + 'var1', + 'var2', + 'variable', + 'variable-access', + 'variable-assignment', + 'variable-declaration', + 'variable-definition', + 'variable-modifier', + 'variable-parameter', + 'variable-reference', + 'variable-usage', + 'variables', + 'variabletable', + 'variant', + 'variant-definition', + 'varname', + 'varnish', + 'vars', + 'vb', + 'vbnet', + 'vbs', + 'vc', + 'vcard', + 'vcd', + 'vcl', + 'vcs', + 'vector', + 'vector-load', + 'vectors', + 'vehicle', + 'velocity', + 'vendor-prefix', + 'verb', + 'verbatim', + 'verdict', + 'verilog', + 'version', + 'version-number', + 'version-specification', + 'vertex', + 'vertex-reference', + 'vertical-blending', + 'vertical-span', + 'vertical-text-cue-setting', + 'vex', + 'vhdl', + 'vhost', + 'vi', + 'via', + 'video-texturing', + 'video_processing', + 'view', + 'viewhelpers', + 'vimAugroupKey', + 'vimBehaveModel', + 'vimFTCmd', + 'vimFTOption', + 'vimFuncKey', + 'vimGroupSpecial', + 'vimHiAttrib', + 'vimHiClear', + 'vimMapModKey', + 'vimPattern', + 'vimSynCase', + 'vimSynType', + 'vimSyncC', + 'vimSyncLinecont', + 'vimSyncMatch', + 'vimSyncNone', + 'vimSyncRegion', + 'vimUserAttrbCmplt', + 'vimUserAttrbKey', + 'vimUserCommand', + 'viml', + 'virtual', + 'virtual-host', + 'virtual-reality', + 'visibility', + 'visualforce', + 'visualization', + 'vlanhdr', + 'vle', + 'vmap', + 'vmx', + 'voice', + 'void', + 'volatile', + 'volt', + 'volume', + 'vpath', + 'vplus', + 'vrf', + 'vtt', + 'vue', + 'vue-jade', + 'vue-stylus', + 'w-offset', + 'w-scale', + 'w-value', + 'w3c-extended-color-name', + 'w3c-non-standard-color-name', + 'w3c-standard-color-name', + 'wait', + 'waitress', + 'waitress-config', + 'waitress-rb', + 'warn', + 'warning', + 'warnings', + 'wast', + 'water', + 'watson-todo', + 'wavefront', + 'wavelet', + 'wddx', + 'wdiff', + 'weapon', + 'weave', + 'weaveBracket', + 'weaveBullet', + 'webidl', + 'webspeed', + 'webvtt', + 'weekday', + 'weirdland', + 'wf', + 'wh', + 'whatever', + 'wheeled-vehicle', + 'when', + 'where', + 'while', + 'while-condition', + 'while-loop', + 'whiskey', + 'white', + 'whitespace', + 'widget', + 'width', + 'wiki', + 'wiki-link', + 'wildcard', + 'wildsk', + 'win', + 'window', + 'window-classes', + 'windows', + 'winered', + 'with', + 'with-arg', + 'with-args', + 'with-arguments', + 'with-params', + 'with-prefix', + 'with-side-effects', + 'with-suffix', + 'with-terminator', + 'with-value', + 'with_colon', + 'without-args', + 'without-arguments', + 'wla-dx', + 'word', + 'word-op', + 'wordnet', + 'wordpress', + 'words', + 'workitem', + 'world', + 'wow', + 'wp', + 'write', + 'wrong', + 'wrong-access-type', + 'wrong-division', + 'wrong-division-assignment', + 'ws', + 'www', + 'wxml', + 'wysiwyg-string', + 'x10', + 'x86', + 'x86_64', + 'x86asm', + 'xacro', + 'xbase', + 'xchg', + 'xhp', + 'xhprof', + 'xikij', + 'xml', + 'xml-attr', + 'xmlrpc', + 'xmlwriter', + 'xop', + 'xor', + 'xparse', + 'xq', + 'xquery', + 'xref', + 'xsave', + 'xsd-all', + 'xsd_nillable', + 'xsd_optional', + 'xsl', + 'xslt', + 'xsse3_simd', + 'xst', + 'xtend', + 'xtoy', + 'xtpl', + 'xu', + 'xvc', + 'xve', + 'xyzw', + 'y', + 'y1', + 'y2', + 'yabb', + 'yaml', + 'yaml-ext', + 'yang', + 'yara', + 'yate', + 'yaws', + 'year', + 'yellow', + 'yield', + 'ykk', + 'yorick', + 'you-forgot-semicolon', + 'z', + 'z80', + 'zap', + 'zapper', + 'zep', + 'zepon', + 'zepto', + 'zero', + 'zero-width-marker', + 'zero-width-print', + 'zeroop', + 'zh-CN', + 'zh-TW', + 'zig', + 'zilde', + 'zlib', + 'zoomfilter', + 'zzz' +]); diff --git a/src/deserializer-manager.js b/src/deserializer-manager.js index 72ed9485d..0a0c63fcf 100644 --- a/src/deserializer-manager.js +++ b/src/deserializer-manager.js @@ -1,4 +1,4 @@ -const {Disposable} = require('event-kit') +const { Disposable } = require('event-kit'); // Extended: Manages the deserializers used for serialized state // @@ -19,11 +19,10 @@ const {Disposable} = require('event-kit') // serialize: -> // @state // ``` -module.exports = -class DeserializerManager { - constructor (atomEnvironment) { - this.atomEnvironment = atomEnvironment - this.deserializers = {} +module.exports = class DeserializerManager { + constructor(atomEnvironment) { + this.atomEnvironment = atomEnvironment; + this.deserializers = {}; } // Public: Register the given class(es) as deserializers. @@ -35,65 +34,66 @@ class DeserializerManager { // called, it will be passed serialized state as the first argument and the // {AtomEnvironment} object as the second argument, which is useful if you // wish to avoid referencing the `atom` global. - add (...deserializers) { + add(...deserializers) { for (let i = 0; i < deserializers.length; i++) { - let deserializer = deserializers[i] - this.deserializers[deserializer.name] = deserializer + let deserializer = deserializers[i]; + this.deserializers[deserializer.name] = deserializer; } return new Disposable(() => { for (let j = 0; j < deserializers.length; j++) { - let deserializer = deserializers[j] - delete this.deserializers[deserializer.name] + let deserializer = deserializers[j]; + delete this.deserializers[deserializer.name]; } - }) + }); } - getDeserializerCount () { - return Object.keys(this.deserializers).length + getDeserializerCount() { + return Object.keys(this.deserializers).length; } // Public: Deserialize the state and params. // // * `state` The state {Object} to deserialize. - deserialize (state) { + deserialize(state) { if (state == null) { - return + return; } - const deserializer = this.get(state) + const deserializer = this.get(state); if (deserializer) { - let stateVersion = ( - (typeof state.get === 'function') && state.get('version') || - state.version - ) + let stateVersion = + (typeof state.get === 'function' && state.get('version')) || + state.version; - if ((deserializer.version != null) && deserializer.version !== stateVersion) { - return + if ( + deserializer.version != null && + deserializer.version !== stateVersion + ) { + return; } - return deserializer.deserialize(state, this.atomEnvironment) + return deserializer.deserialize(state, this.atomEnvironment); } else { - return console.warn('No deserializer found for', state) + return console.warn('No deserializer found for', state); } } // Get the deserializer for the state. // // * `state` The state {Object} being deserialized. - get (state) { + get(state) { if (state == null) { - return + return; } - let stateDeserializer = ( - (typeof state.get === 'function') && state.get('deserializer') || - state.deserializer - ) + let stateDeserializer = + (typeof state.get === 'function' && state.get('deserializer')) || + state.deserializer; - return this.deserializers[stateDeserializer] + return this.deserializers[stateDeserializer]; } - clear () { - this.deserializers = {} + clear() { + this.deserializers = {}; } -} +}; diff --git a/src/dock.js b/src/dock.js index dc77365fb..5a396d239 100644 --- a/src/dock.js +++ b/src/dock.js @@ -1,44 +1,48 @@ -const etch = require('etch') -const _ = require('underscore-plus') -const {CompositeDisposable, Emitter} = require('event-kit') -const PaneContainer = require('./pane-container') -const TextEditor = require('./text-editor') -const Grim = require('grim') +const etch = require('etch'); +const _ = require('underscore-plus'); +const { CompositeDisposable, Emitter } = require('event-kit'); +const PaneContainer = require('./pane-container'); +const TextEditor = require('./text-editor'); +const Grim = require('grim'); -const $ = etch.dom -const MINIMUM_SIZE = 100 -const DEFAULT_INITIAL_SIZE = 300 -const SHOULD_ANIMATE_CLASS = 'atom-dock-should-animate' -const VISIBLE_CLASS = 'atom-dock-open' -const RESIZE_HANDLE_RESIZABLE_CLASS = 'atom-dock-resize-handle-resizable' -const TOGGLE_BUTTON_VISIBLE_CLASS = 'atom-dock-toggle-button-visible' -const CURSOR_OVERLAY_VISIBLE_CLASS = 'atom-dock-cursor-overlay-visible' +const $ = etch.dom; +const MINIMUM_SIZE = 100; +const DEFAULT_INITIAL_SIZE = 300; +const SHOULD_ANIMATE_CLASS = 'atom-dock-should-animate'; +const VISIBLE_CLASS = 'atom-dock-open'; +const RESIZE_HANDLE_RESIZABLE_CLASS = 'atom-dock-resize-handle-resizable'; +const TOGGLE_BUTTON_VISIBLE_CLASS = 'atom-dock-toggle-button-visible'; +const CURSOR_OVERLAY_VISIBLE_CLASS = 'atom-dock-cursor-overlay-visible'; // Extended: A container at the edges of the editor window capable of holding items. // You should not create a Dock directly. Instead, access one of the three docks of the workspace // via {Workspace::getLeftDock}, {Workspace::getRightDock}, and {Workspace::getBottomDock} // or add an item to a dock via {Workspace::open}. module.exports = class Dock { - constructor (params) { - this.handleResizeHandleDragStart = this.handleResizeHandleDragStart.bind(this) - this.handleResizeToFit = this.handleResizeToFit.bind(this) - this.handleMouseMove = this.handleMouseMove.bind(this) - this.handleMouseUp = this.handleMouseUp.bind(this) - this.handleDrag = _.throttle(this.handleDrag.bind(this), 30) - this.handleDragEnd = this.handleDragEnd.bind(this) - this.handleToggleButtonDragEnter = this.handleToggleButtonDragEnter.bind(this) - this.toggle = this.toggle.bind(this) + constructor(params) { + this.handleResizeHandleDragStart = this.handleResizeHandleDragStart.bind( + this + ); + this.handleResizeToFit = this.handleResizeToFit.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); + this.handleDrag = _.throttle(this.handleDrag.bind(this), 30); + this.handleDragEnd = this.handleDragEnd.bind(this); + this.handleToggleButtonDragEnter = this.handleToggleButtonDragEnter.bind( + this + ); + this.toggle = this.toggle.bind(this); - this.location = params.location - this.widthOrHeight = getWidthOrHeight(this.location) - this.config = params.config - this.applicationDelegate = params.applicationDelegate - this.deserializerManager = params.deserializerManager - this.notificationManager = params.notificationManager - this.viewRegistry = params.viewRegistry - this.didActivate = params.didActivate + this.location = params.location; + this.widthOrHeight = getWidthOrHeight(this.location); + this.config = params.config; + this.applicationDelegate = params.applicationDelegate; + this.deserializerManager = params.deserializerManager; + this.notificationManager = params.notificationManager; + this.viewRegistry = params.viewRegistry; + this.didActivate = params.didActivate; - this.emitter = new Emitter() + this.emitter = new Emitter(); this.paneContainer = new PaneContainer({ location: this.location, @@ -47,103 +51,109 @@ module.exports = class Dock { deserializerManager: this.deserializerManager, notificationManager: this.notificationManager, viewRegistry: this.viewRegistry - }) + }); this.state = { size: null, visible: false, shouldAnimate: false - } + }; this.subscriptions = new CompositeDisposable( this.emitter, this.paneContainer.onDidActivatePane(() => { - this.show() - this.didActivate(this) + this.show(); + this.didActivate(this); }), this.paneContainer.observePanes(pane => { - pane.onDidAddItem(this.handleDidAddPaneItem.bind(this)) - pane.onDidRemoveItem(this.handleDidRemovePaneItem.bind(this)) + pane.onDidAddItem(this.handleDidAddPaneItem.bind(this)); + pane.onDidRemoveItem(this.handleDidRemovePaneItem.bind(this)); }), - this.paneContainer.onDidChangeActivePane((item) => params.didChangeActivePane(this, item)), - this.paneContainer.onDidChangeActivePaneItem((item) => params.didChangeActivePaneItem(this, item)), - this.paneContainer.onDidDestroyPaneItem((item) => params.didDestroyPaneItem(item)) - ) + this.paneContainer.onDidChangeActivePane(item => + params.didChangeActivePane(this, item) + ), + this.paneContainer.onDidChangeActivePaneItem(item => + params.didChangeActivePaneItem(this, item) + ), + this.paneContainer.onDidDestroyPaneItem(item => + params.didDestroyPaneItem(item) + ) + ); } // This method is called explicitly by the object which adds the Dock to the document. - elementAttached () { + elementAttached() { // Re-render when the dock is attached to make sure we remeasure sizes defined in CSS. - etch.updateSync(this) + etch.updateSync(this); } - getElement () { + getElement() { // Because this code is included in the snapshot, we have to make sure we don't touch the DOM // during initialization. Therefore, we defer initialization of the component (which creates a // DOM element) until somebody asks for the element. if (this.element == null) { - etch.initialize(this) + etch.initialize(this); } - return this.element + return this.element; } - getLocation () { - return this.location + getLocation() { + return this.location; } - destroy () { - this.subscriptions.dispose() - this.paneContainer.destroy() - window.removeEventListener('mousemove', this.handleMouseMove) - window.removeEventListener('mouseup', this.handleMouseUp) - window.removeEventListener('drag', this.handleDrag) - window.removeEventListener('dragend', this.handleDragEnd) + destroy() { + this.subscriptions.dispose(); + this.paneContainer.destroy(); + window.removeEventListener('mousemove', this.handleMouseMove); + window.removeEventListener('mouseup', this.handleMouseUp); + window.removeEventListener('drag', this.handleDrag); + window.removeEventListener('dragend', this.handleDragEnd); } - setHovered (hovered) { - if (hovered === this.state.hovered) return - this.setState({hovered}) + setHovered(hovered) { + if (hovered === this.state.hovered) return; + this.setState({ hovered }); } - setDraggingItem (draggingItem) { - if (draggingItem === this.state.draggingItem) return - this.setState({draggingItem}) + setDraggingItem(draggingItem) { + if (draggingItem === this.state.draggingItem) return; + this.setState({ draggingItem }); } // Extended: Show the dock and focus its active {Pane}. - activate () { - this.getActivePane().activate() + activate() { + this.getActivePane().activate(); } // Extended: Show the dock without focusing it. - show () { - this.setState({visible: true}) + show() { + this.setState({ visible: true }); } // Extended: Hide the dock and activate the {WorkspaceCenter} if the dock was // was previously focused. - hide () { - this.setState({visible: false}) + hide() { + this.setState({ visible: false }); } // Extended: Toggle the dock's visibility without changing the {Workspace}'s // active pane container. - toggle () { - const state = {visible: !this.state.visible} - if (!state.visible) state.hovered = false - this.setState(state) + toggle() { + const state = { visible: !this.state.visible }; + if (!state.visible) state.hovered = false; + this.setState(state); } // Extended: Check if the dock is visible. // // Returns a {Boolean}. - isVisible () { - return this.state.visible + isVisible() { + return this.state.visible; } - setState (newState) { - const prevState = this.state - const nextState = Object.assign({}, prevState, newState) + setState(newState) { + const prevState = this.state; + const nextState = Object.assign({}, prevState, newState); // Update the `shouldAnimate` state. This needs to be written to the DOM before updating the // class that changes the animated property. Normally we'd have to defer the class change a @@ -151,58 +161,72 @@ module.exports = class Dock { // case because the drag start always happens before the item is dragged into the toggle button. if (nextState.visible !== prevState.visible) { // Never animate toggling visibility... - nextState.shouldAnimate = false - } else if (!nextState.visible && nextState.draggingItem && !prevState.draggingItem) { + nextState.shouldAnimate = false; + } else if ( + !nextState.visible && + nextState.draggingItem && + !prevState.draggingItem + ) { // ...but do animate if you start dragging while the panel is hidden. - nextState.shouldAnimate = true + nextState.shouldAnimate = true; } - this.state = nextState + this.state = nextState; - const {hovered, visible} = this.state + const { hovered, visible } = this.state; // Render immediately if the dock becomes visible or the size changes in case people are // measuring after opening, for example. if (this.element != null) { - if ((visible && !prevState.visible) || (this.state.size !== prevState.size)) etch.updateSync(this) - else etch.update(this) + if ((visible && !prevState.visible) || this.state.size !== prevState.size) + etch.updateSync(this); + else etch.update(this); } if (hovered !== prevState.hovered) { - this.emitter.emit('did-change-hovered', hovered) + this.emitter.emit('did-change-hovered', hovered); } if (visible !== prevState.visible) { - this.emitter.emit('did-change-visible', visible) + this.emitter.emit('did-change-visible', visible); } } - render () { - const innerElementClassList = ['atom-dock-inner', this.location] - if (this.state.visible) innerElementClassList.push(VISIBLE_CLASS) + render() { + const innerElementClassList = ['atom-dock-inner', this.location]; + if (this.state.visible) innerElementClassList.push(VISIBLE_CLASS); - const maskElementClassList = ['atom-dock-mask'] - if (this.state.shouldAnimate) maskElementClassList.push(SHOULD_ANIMATE_CLASS) + const maskElementClassList = ['atom-dock-mask']; + if (this.state.shouldAnimate) + maskElementClassList.push(SHOULD_ANIMATE_CLASS); - const cursorOverlayElementClassList = ['atom-dock-cursor-overlay', this.location] - if (this.state.resizing) cursorOverlayElementClassList.push(CURSOR_OVERLAY_VISIBLE_CLASS) + const cursorOverlayElementClassList = [ + 'atom-dock-cursor-overlay', + this.location + ]; + if (this.state.resizing) + cursorOverlayElementClassList.push(CURSOR_OVERLAY_VISIBLE_CLASS); - const shouldBeVisible = this.state.visible || this.state.showDropTarget - const size = Math.max(MINIMUM_SIZE, + const shouldBeVisible = this.state.visible || this.state.showDropTarget; + const size = Math.max( + MINIMUM_SIZE, this.state.size || - (this.state.draggingItem && getPreferredSize(this.state.draggingItem, this.location)) || - DEFAULT_INITIAL_SIZE - ) + (this.state.draggingItem && + getPreferredSize(this.state.draggingItem, this.location)) || + DEFAULT_INITIAL_SIZE + ); // We need to change the size of the mask... - const maskStyle = {[this.widthOrHeight]: `${shouldBeVisible ? size : 0}px`} + const maskStyle = { + [this.widthOrHeight]: `${shouldBeVisible ? size : 0}px` + }; // ...but the content needs to maintain a constant size. - const wrapperStyle = {[this.widthOrHeight]: `${size}px`} + const wrapperStyle = { [this.widthOrHeight]: `${size}px` }; return $( 'atom-dock', - {className: this.location}, + { className: this.location }, $.div( - {ref: 'innerElement', className: innerElementClassList.join(' ')}, + { ref: 'innerElement', className: innerElementClassList.join(' ') }, $.div( { className: maskElementClassList.join(' '), @@ -220,13 +244,15 @@ module.exports = class Dock { onResizeToFit: this.handleResizeToFit, dockIsVisible: this.state.visible }), - $(ElementComponent, {element: this.paneContainer.getElement()}), - $.div({className: cursorOverlayElementClassList.join(' ')}) + $(ElementComponent, { element: this.paneContainer.getElement() }), + $.div({ className: cursorOverlayElementClassList.join(' ') }) ) ), $(DockToggleButton, { ref: 'toggleButton', - onDragEnter: this.state.draggingItem ? this.handleToggleButtonDragEnter : null, + onDragEnter: this.state.draggingItem + ? this.handleToggleButtonDragEnter + : null, location: this.location, toggle: this.toggle, dockIsVisible: shouldBeVisible, @@ -240,88 +266,89 @@ module.exports = class Dock { isItemAllowed(this.state.draggingItem, this.location)) }) ) - ) + ); } - update (props) { + update(props) { // Since we're interopping with non-etch stuff, this method's actually never called. - return etch.update(this) + return etch.update(this); } - handleDidAddPaneItem () { + handleDidAddPaneItem() { if (this.state.size == null) { - this.setState({size: this.getInitialSize()}) + this.setState({ size: this.getInitialSize() }); } } - handleDidRemovePaneItem () { + handleDidRemovePaneItem() { // Hide the dock if you remove the last item. if (this.paneContainer.getPaneItems().length === 0) { - this.setState({visible: false, hovered: false, size: null}) + this.setState({ visible: false, hovered: false, size: null }); } } - handleResizeHandleDragStart () { - window.addEventListener('mousemove', this.handleMouseMove) - window.addEventListener('mouseup', this.handleMouseUp) - this.setState({resizing: true}) + handleResizeHandleDragStart() { + window.addEventListener('mousemove', this.handleMouseMove); + window.addEventListener('mouseup', this.handleMouseUp); + this.setState({ resizing: true }); } - handleResizeToFit () { - const item = this.getActivePaneItem() + handleResizeToFit() { + const item = this.getActivePaneItem(); if (item) { - const size = getPreferredSize(item, this.getLocation()) - if (size != null) this.setState({size}) + const size = getPreferredSize(item, this.getLocation()); + if (size != null) this.setState({ size }); } } - handleMouseMove (event) { - if (event.buttons === 0) { // We missed the mouseup event. For some reason it happens on Windows - this.handleMouseUp(event) - return + handleMouseMove(event) { + if (event.buttons === 0) { + // We missed the mouseup event. For some reason it happens on Windows + this.handleMouseUp(event); + return; } - let size = 0 + let size = 0; switch (this.location) { case 'left': - size = event.pageX - this.element.getBoundingClientRect().left - break + size = event.pageX - this.element.getBoundingClientRect().left; + break; case 'bottom': - size = this.element.getBoundingClientRect().bottom - event.pageY - break + size = this.element.getBoundingClientRect().bottom - event.pageY; + break; case 'right': - size = this.element.getBoundingClientRect().right - event.pageX - break + size = this.element.getBoundingClientRect().right - event.pageX; + break; } - this.setState({size}) + this.setState({ size }); } - handleMouseUp (event) { - window.removeEventListener('mousemove', this.handleMouseMove) - window.removeEventListener('mouseup', this.handleMouseUp) - this.setState({resizing: false}) + handleMouseUp(event) { + window.removeEventListener('mousemove', this.handleMouseMove); + window.removeEventListener('mouseup', this.handleMouseUp); + this.setState({ resizing: false }); } - handleToggleButtonDragEnter () { - this.setState({showDropTarget: true}) - window.addEventListener('drag', this.handleDrag) - window.addEventListener('dragend', this.handleDragEnd) + handleToggleButtonDragEnter() { + this.setState({ showDropTarget: true }); + window.addEventListener('drag', this.handleDrag); + window.addEventListener('dragend', this.handleDragEnd); } - handleDrag (event) { - if (!this.pointWithinHoverArea({x: event.pageX, y: event.pageY}, true)) { - this.draggedOut() + handleDrag(event) { + if (!this.pointWithinHoverArea({ x: event.pageX, y: event.pageY }, true)) { + this.draggedOut(); } } - handleDragEnd () { - this.draggedOut() + handleDragEnd() { + this.draggedOut(); } - draggedOut () { - this.setState({showDropTarget: false}) - window.removeEventListener('drag', this.handleDrag) - window.removeEventListener('dragend', this.handleDragEnd) + draggedOut() { + this.setState({ showDropTarget: false }); + window.removeEventListener('drag', this.handleDrag); + window.removeEventListener('dragend', this.handleDragEnd); } // Determine whether the cursor is within the dock hover area. This isn't as simple as just using @@ -330,8 +357,8 @@ module.exports = class Dock { // for detecting entry are different than detecting exit but, in order for us to avoid jitter, the // area considered when detecting exit MUST fully encompass the area considered when detecting // entry. - pointWithinHoverArea (point, detectingExit) { - const dockBounds = this.refs.innerElement.getBoundingClientRect() + pointWithinHoverArea(point, detectingExit) { + const dockBounds = this.refs.innerElement.getBoundingClientRect(); // Copy the bounds object since we can't mutate it. const bounds = { @@ -339,37 +366,37 @@ module.exports = class Dock { right: dockBounds.right, bottom: dockBounds.bottom, left: dockBounds.left - } + }; // To provide a minimum target, expand the area toward the center a bit. switch (this.location) { case 'right': - bounds.left = Math.min(bounds.left, bounds.right - 2) - break + bounds.left = Math.min(bounds.left, bounds.right - 2); + break; case 'bottom': - bounds.top = Math.min(bounds.top, bounds.bottom - 1) - break + bounds.top = Math.min(bounds.top, bounds.bottom - 1); + break; case 'left': - bounds.right = Math.max(bounds.right, bounds.left + 2) - break + bounds.right = Math.max(bounds.right, bounds.left + 2); + break; } // Further expand the area to include all panels that are closer to the edge than the dock. switch (this.location) { case 'right': - bounds.right = Number.POSITIVE_INFINITY - break + bounds.right = Number.POSITIVE_INFINITY; + break; case 'bottom': - bounds.bottom = Number.POSITIVE_INFINITY - break + bounds.bottom = Number.POSITIVE_INFINITY; + break; case 'left': - bounds.left = Number.NEGATIVE_INFINITY - break + bounds.left = Number.NEGATIVE_INFINITY; + break; } // If we're in this area, we know we're within the hover area without having to take further // measurements. - if (rectContainsPoint(bounds, point)) return true + if (rectContainsPoint(bounds, point)) return true; // If we're within the toggle button, we're definitely in the hover area. Unfortunately, we // can't do this measurement conditionally (e.g. only if the toggle button is visible) because @@ -380,55 +407,64 @@ module.exports = class Dock { // remove it as an argument and determine whether we're inside the toggle button using // mouseenter/leave events on it. This class would still need to keep track of the mouse // position (via a mousemove listener) for the other measurements, though. - const toggleButtonBounds = this.refs.toggleButton.getBounds() - if (rectContainsPoint(toggleButtonBounds, point)) return true + const toggleButtonBounds = this.refs.toggleButton.getBounds(); + if (rectContainsPoint(toggleButtonBounds, point)) return true; // The area used when detecting exit is actually larger than when detecting entrances. Expand // our bounds and recheck them. if (detectingExit) { - const hoverMargin = 20 + const hoverMargin = 20; switch (this.location) { case 'right': - bounds.left = Math.min(bounds.left, toggleButtonBounds.left) - hoverMargin - break + bounds.left = + Math.min(bounds.left, toggleButtonBounds.left) - hoverMargin; + break; case 'bottom': - bounds.top = Math.min(bounds.top, toggleButtonBounds.top) - hoverMargin - break + bounds.top = + Math.min(bounds.top, toggleButtonBounds.top) - hoverMargin; + break; case 'left': - bounds.right = Math.max(bounds.right, toggleButtonBounds.right) + hoverMargin - break + bounds.right = + Math.max(bounds.right, toggleButtonBounds.right) + hoverMargin; + break; } - if (rectContainsPoint(bounds, point)) return true + if (rectContainsPoint(bounds, point)) return true; } - return false + return false; } - getInitialSize () { + getInitialSize() { // The item may not have been activated yet. If that's the case, just use the first item. - const activePaneItem = this.paneContainer.getActivePaneItem() || this.paneContainer.getPaneItems()[0] + const activePaneItem = + this.paneContainer.getActivePaneItem() || + this.paneContainer.getPaneItems()[0]; // If there are items, we should have an explicit width; if not, we shouldn't. return activePaneItem ? getPreferredSize(activePaneItem, this.location) || DEFAULT_INITIAL_SIZE - : null + : null; } - serialize () { + serialize() { return { deserializer: 'Dock', size: this.state.size, paneContainer: this.paneContainer.serialize(), visible: this.state.visible - } + }; } - deserialize (serialized, deserializerManager) { - this.paneContainer.deserialize(serialized.paneContainer, deserializerManager) + deserialize(serialized, deserializerManager) { + this.paneContainer.deserialize( + serialized.paneContainer, + deserializerManager + ); this.setState({ size: serialized.size || this.getInitialSize(), // If no items could be deserialized, we don't want to show the dock (even if it was visible last time) - visible: serialized.visible && (this.paneContainer.getPaneItems().length > 0) - }) + visible: + serialized.visible && this.paneContainer.getPaneItems().length > 0 + }); } /* @@ -441,8 +477,8 @@ module.exports = class Dock { // * `visible` {Boolean} Is the dock now visible? // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeVisible (callback) { - return this.emitter.on('did-change-visible', callback) + onDidChangeVisible(callback) { + return this.emitter.on('did-change-visible', callback); } // Essential: Invoke the given callback with the current and all future visibilities of the dock. @@ -451,9 +487,9 @@ module.exports = class Dock { // * `visible` {Boolean} Is the dock now visible? // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeVisible (callback) { - callback(this.isVisible()) - return this.onDidChangeVisible(callback) + observeVisible(callback) { + callback(this.isVisible()); + return this.onDidChangeVisible(callback); } // Essential: Invoke the given callback with all current and future panes items @@ -464,8 +500,8 @@ module.exports = class Dock { // 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. @@ -479,8 +515,8 @@ module.exports = class Dock { // * `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 @@ -497,8 +533,8 @@ module.exports = class Dock { // * `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 @@ -508,8 +544,8 @@ module.exports = class Dock { // * `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); } // Extended: Invoke the given callback when a pane is added to the dock. @@ -519,8 +555,8 @@ module.exports = class Dock { // * `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 @@ -531,8 +567,8 @@ module.exports = class Dock { // * `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 dock. @@ -542,8 +578,8 @@ module.exports = class Dock { // * `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 @@ -554,8 +590,8 @@ module.exports = class Dock { // 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. @@ -564,8 +600,8 @@ module.exports = class Dock { // * `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 @@ -576,8 +612,8 @@ module.exports = class Dock { // * `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 dock. @@ -589,8 +625,8 @@ module.exports = class Dock { // * `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 @@ -604,8 +640,8 @@ module.exports = class Dock { // 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. @@ -618,8 +654,8 @@ module.exports = class Dock { // 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 the hovered state of the dock changes. @@ -628,8 +664,8 @@ module.exports = class Dock { // * `hovered` {Boolean} Is the dock now hovered? // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeHovered (callback) { - return this.emitter.on('did-change-hovered', callback) + onDidChangeHovered(callback) { + return this.emitter.on('did-change-hovered', callback); } /* @@ -639,35 +675,39 @@ module.exports = class Dock { // Essential: Get all pane items in the dock. // // 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(); } // Deprecated: Get the active item if it is a {TextEditor}. // // Returns a {TextEditor} or `undefined` if the current active item is not a // {TextEditor}. - getActiveTextEditor () { - Grim.deprecate('Text editors are not allowed in docks. Use atom.workspace.getActiveTextEditor() instead.') + getActiveTextEditor() { + Grim.deprecate( + 'Text editors are not allowed in docks. Use atom.workspace.getActiveTextEditor() instead.' + ); - const activeItem = this.getActivePaneItem() - if (activeItem instanceof TextEditor) { return activeItem } + const activeItem = this.getActivePaneItem(); + if (activeItem instanceof TextEditor) { + return activeItem; + } } // Save all pane items. - saveAll () { - this.paneContainer.saveAll() + saveAll() { + this.paneContainer.saveAll(); } - confirmClose (options) { - return this.paneContainer.confirmClose(options) + confirmClose(options) { + return this.paneContainer.confirmClose(options); } /* @@ -677,97 +717,99 @@ module.exports = class Dock { // Extended: Get all panes in the dock. // // 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(); } - paneForURI (uri) { - return this.paneContainer.paneForURI(uri) + paneForURI(uri) { + return this.paneContainer.paneForURI(uri); } - paneForItem (item) { - return this.paneContainer.paneForItem(item) + paneForItem(item) { + return this.paneContainer.paneForItem(item); } // Destroy (close) the active pane. - destroyActivePane () { - const activePane = this.getActivePane() + destroyActivePane() { + const activePane = this.getActivePane(); if (activePane != null) { - activePane.destroy() + activePane.destroy(); } } -} +}; class DockResizeHandle { - constructor (props) { - this.props = props - etch.initialize(this) + constructor(props) { + this.props = props; + etch.initialize(this); } - render () { - const classList = ['atom-dock-resize-handle', this.props.location] - if (this.props.dockIsVisible) classList.push(RESIZE_HANDLE_RESIZABLE_CLASS) + render() { + const classList = ['atom-dock-resize-handle', this.props.location]; + if (this.props.dockIsVisible) classList.push(RESIZE_HANDLE_RESIZABLE_CLASS); return $.div({ className: classList.join(' '), - on: {mousedown: this.handleMouseDown} - }) + on: { mousedown: this.handleMouseDown } + }); } - getElement () { - return this.element + getElement() { + return this.element; } - getSize () { + getSize() { if (!this.size) { - this.size = this.element.getBoundingClientRect()[getWidthOrHeight(this.props.location)] + this.size = this.element.getBoundingClientRect()[ + getWidthOrHeight(this.props.location) + ]; } - return this.size + return this.size; } - update (newProps) { - this.props = Object.assign({}, this.props, newProps) - return etch.update(this) + update(newProps) { + this.props = Object.assign({}, this.props, newProps); + return etch.update(this); } - handleMouseDown (event) { + handleMouseDown(event) { if (event.detail === 2) { - this.props.onResizeToFit() + this.props.onResizeToFit(); } else if (this.props.dockIsVisible) { - this.props.onResizeStart() + this.props.onResizeStart(); } } } class DockToggleButton { - constructor (props) { - this.props = props - etch.initialize(this) + constructor(props) { + this.props = props; + etch.initialize(this); } - render () { - const classList = ['atom-dock-toggle-button', this.props.location] - if (this.props.visible) classList.push(TOGGLE_BUTTON_VISIBLE_CLASS) + render() { + const classList = ['atom-dock-toggle-button', this.props.location]; + if (this.props.visible) classList.push(TOGGLE_BUTTON_VISIBLE_CLASS); return $.div( - {className: classList.join(' ')}, + { className: classList.join(' ') }, $.div( { ref: 'innerElement', @@ -785,77 +827,81 @@ class DockToggleButton { )}` }) ) - ) + ); } - getElement () { - return this.element + getElement() { + return this.element; } - getBounds () { - return this.refs.innerElement.getBoundingClientRect() + getBounds() { + return this.refs.innerElement.getBoundingClientRect(); } - update (newProps) { - this.props = Object.assign({}, this.props, newProps) - return etch.update(this) + update(newProps) { + this.props = Object.assign({}, this.props, newProps); + return etch.update(this); } - handleClick () { - this.props.toggle() + handleClick() { + this.props.toggle(); } } // An etch component that doesn't use etch, this component provides a gateway from JSX back into // the mutable DOM world. class ElementComponent { - constructor (props) { - this.element = props.element + constructor(props) { + this.element = props.element; } - update (props) { - this.element = props.element + update(props) { + this.element = props.element; } } -function getWidthOrHeight (location) { - return location === 'left' || location === 'right' ? 'width' : 'height' +function getWidthOrHeight(location) { + return location === 'left' || location === 'right' ? 'width' : 'height'; } -function getPreferredSize (item, location) { +function getPreferredSize(item, location) { switch (location) { case 'left': case 'right': return typeof item.getPreferredWidth === 'function' ? item.getPreferredWidth() - : null + : null; default: return typeof item.getPreferredHeight === 'function' ? item.getPreferredHeight() - : null + : null; } } -function getIconName (location, visible) { +function getIconName(location, visible) { switch (location) { - case 'right': return visible ? 'icon-chevron-right' : 'icon-chevron-left' - case 'bottom': return visible ? 'icon-chevron-down' : 'icon-chevron-up' - case 'left': return visible ? 'icon-chevron-left' : 'icon-chevron-right' - default: throw new Error(`Invalid location: ${location}`) + case 'right': + return visible ? 'icon-chevron-right' : 'icon-chevron-left'; + case 'bottom': + return visible ? 'icon-chevron-down' : 'icon-chevron-up'; + case 'left': + return visible ? 'icon-chevron-left' : 'icon-chevron-right'; + default: + throw new Error(`Invalid location: ${location}`); } } -function rectContainsPoint (rect, point) { +function rectContainsPoint(rect, point) { return ( point.x >= rect.left && point.y >= rect.top && point.x <= rect.right && point.y <= rect.bottom - ) + ); } // Is the item allowed in the given location? -function isItemAllowed (item, location) { - if (typeof item.getAllowedLocations !== 'function') return true - return item.getAllowedLocations().includes(location) +function isItemAllowed(item, location) { + if (typeof item.getAllowedLocations !== 'function') return true; + return item.getAllowedLocations().includes(location); } diff --git a/src/electron-shims.js b/src/electron-shims.js index 3f8e1c215..0c947aa63 100644 --- a/src/electron-shims.js +++ b/src/electron-shims.js @@ -1,80 +1,97 @@ -const path = require('path') -const electron = require('electron') +const path = require('path'); +const electron = require('electron'); -const dirname = path.dirname -path.dirname = function (path) { +const dirname = path.dirname; +path.dirname = function(path) { if (typeof path !== 'string') { - path = '' + path - const Grim = require('grim') - Grim.deprecate('Argument to `path.dirname` must be a string') + path = '' + path; + const Grim = require('grim'); + Grim.deprecate('Argument to `path.dirname` must be a string'); } - return dirname(path) -} + return dirname(path); +}; -const extname = path.extname -path.extname = function (path) { +const extname = path.extname; +path.extname = function(path) { if (typeof path !== 'string') { - path = '' + path - const Grim = require('grim') - Grim.deprecate('Argument to `path.extname` must be a string') + path = '' + path; + const Grim = require('grim'); + Grim.deprecate('Argument to `path.extname` must be a string'); } - return extname(path) -} + return extname(path); +}; -const basename = path.basename -path.basename = function (path, ext) { - if (typeof path !== 'string' || (ext !== undefined && typeof ext !== 'string')) { - path = '' + path - const Grim = require('grim') - Grim.deprecate('Arguments to `path.basename` must be strings') +const basename = path.basename; +path.basename = function(path, ext) { + if ( + typeof path !== 'string' || + (ext !== undefined && typeof ext !== 'string') + ) { + path = '' + path; + const Grim = require('grim'); + Grim.deprecate('Arguments to `path.basename` must be strings'); } - return basename(path, ext) -} + return basename(path, ext); +}; -electron.ipcRenderer.sendChannel = function () { - const Grim = require('grim') - Grim.deprecate('Use `ipcRenderer.send` instead of `ipcRenderer.sendChannel`') - return this.send.apply(this, arguments) -} +electron.ipcRenderer.sendChannel = function() { + const Grim = require('grim'); + Grim.deprecate('Use `ipcRenderer.send` instead of `ipcRenderer.sendChannel`'); + return this.send.apply(this, arguments); +}; -const remoteRequire = electron.remote.require -electron.remote.require = function (moduleName) { - const Grim = require('grim') +const remoteRequire = electron.remote.require; +electron.remote.require = function(moduleName) { + const Grim = require('grim'); switch (moduleName) { case 'menu': - Grim.deprecate('Use `remote.Menu` instead of `remote.require("menu")`') - return this.Menu + Grim.deprecate('Use `remote.Menu` instead of `remote.require("menu")`'); + return this.Menu; case 'menu-item': - Grim.deprecate('Use `remote.MenuItem` instead of `remote.require("menu-item")`') - return this.MenuItem + Grim.deprecate( + 'Use `remote.MenuItem` instead of `remote.require("menu-item")`' + ); + return this.MenuItem; case 'browser-window': - Grim.deprecate('Use `remote.BrowserWindow` instead of `remote.require("browser-window")`') - return this.BrowserWindow + Grim.deprecate( + 'Use `remote.BrowserWindow` instead of `remote.require("browser-window")`' + ); + return this.BrowserWindow; case 'dialog': - Grim.deprecate('Use `remote.Dialog` instead of `remote.require("dialog")`') - return this.Dialog + Grim.deprecate( + 'Use `remote.Dialog` instead of `remote.require("dialog")`' + ); + return this.Dialog; case 'app': - Grim.deprecate('Use `remote.app` instead of `remote.require("app")`') - return this.app + Grim.deprecate('Use `remote.app` instead of `remote.require("app")`'); + return this.app; case 'crash-reporter': - Grim.deprecate('Use `remote.crashReporter` instead of `remote.require("crashReporter")`') - return this.crashReporter + Grim.deprecate( + 'Use `remote.crashReporter` instead of `remote.require("crashReporter")`' + ); + return this.crashReporter; case 'global-shortcut': - Grim.deprecate('Use `remote.globalShortcut` instead of `remote.require("global-shortcut")`') - return this.globalShortcut + Grim.deprecate( + 'Use `remote.globalShortcut` instead of `remote.require("global-shortcut")`' + ); + return this.globalShortcut; case 'clipboard': - Grim.deprecate('Use `remote.clipboard` instead of `remote.require("clipboard")`') - return this.clipboard + Grim.deprecate( + 'Use `remote.clipboard` instead of `remote.require("clipboard")`' + ); + return this.clipboard; case 'native-image': - Grim.deprecate('Use `remote.nativeImage` instead of `remote.require("native-image")`') - return this.nativeImage + Grim.deprecate( + 'Use `remote.nativeImage` instead of `remote.require("native-image")`' + ); + return this.nativeImage; case 'tray': - Grim.deprecate('Use `remote.Tray` instead of `remote.require("tray")`') - return this.Tray + Grim.deprecate('Use `remote.Tray` instead of `remote.require("tray")`'); + return this.Tray; default: - return remoteRequire.call(this, moduleName) + return remoteRequire.call(this, moduleName); } -} +}; diff --git a/src/file-system-blob-store.js b/src/file-system-blob-store.js index 81e4a6f39..6f2aa07c9 100644 --- a/src/file-system-blob-store.js +++ b/src/file-system-blob-store.js @@ -1,127 +1,131 @@ -'use strict' +'use strict'; -const fs = require('fs-plus') -const path = require('path') +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 +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.lockFilename = path.join(directory, 'LOCK') - this.reset() + constructor(directory) { + this.blobFilename = path.join(directory, 'BLOB'); + this.blobMapFilename = path.join(directory, 'MAP'); + this.lockFilename = path.join(directory, 'LOCK'); + this.reset(); } - reset () { - this.inMemoryBlobs = new Map() - this.storedBlob = Buffer.alloc(0) - this.storedBlobMap = {} - this.usedKeys = new Set() + reset() { + this.inMemoryBlobs = new Map(); + this.storedBlob = Buffer.alloc(0); + this.storedBlobMap = {}; + this.usedKeys = new Set(); } - load () { + load() { if (!fs.existsSync(this.blobMapFilename)) { - return + return; } if (!fs.existsSync(this.blobFilename)) { - return + return; } try { - this.storedBlob = fs.readFileSync(this.blobFilename) - this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) + this.storedBlob = fs.readFileSync(this.blobFilename); + this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)); } catch (e) { - this.reset() + this.reset(); } } - save () { - let dump = this.getDump() - let blobToStore = Buffer.concat(dump[0]) - let mapToStore = JSON.stringify(dump[1]) + save() { + let dump = this.getDump(); + let blobToStore = Buffer.concat(dump[0]); + let mapToStore = JSON.stringify(dump[1]); - let acquiredLock = false + let acquiredLock = false; try { - fs.writeFileSync(this.lockFilename, 'LOCK', {flag: 'wx'}) - acquiredLock = true + fs.writeFileSync(this.lockFilename, 'LOCK', { flag: 'wx' }); + acquiredLock = true; - fs.writeFileSync(this.blobFilename, blobToStore) - fs.writeFileSync(this.blobMapFilename, mapToStore) + fs.writeFileSync(this.blobFilename, blobToStore); + fs.writeFileSync(this.blobMapFilename, mapToStore); } catch (error) { // Swallow the exception silently only if we fail to acquire the lock. if (error.code !== 'EEXIST') { - throw error + throw error; } } finally { if (acquiredLock) { - fs.unlinkSync(this.lockFilename) + fs.unlinkSync(this.lockFilename); } } } - has (key) { - return this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) + has(key) { + return ( + this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) + ); } - get (key) { + get(key) { if (this.has(key)) { - this.usedKeys.add(key) - return this.getFromMemory(key) || this.getFromStorage(key) + this.usedKeys.add(key); + return this.getFromMemory(key) || this.getFromStorage(key); } } - set (key, buffer) { - this.usedKeys.add(key) - return this.inMemoryBlobs.set(key, buffer) + set(key, buffer) { + this.usedKeys.add(key); + return this.inMemoryBlobs.set(key, buffer); } - delete (key) { - this.inMemoryBlobs.delete(key) - delete this.storedBlobMap[key] + delete(key) { + this.inMemoryBlobs.delete(key); + delete this.storedBlobMap[key]; } - getFromMemory (key) { - return this.inMemoryBlobs.get(key) + getFromMemory(key) { + return this.inMemoryBlobs.get(key); } - getFromStorage (key) { + getFromStorage(key) { if (!this.storedBlobMap[key]) { - return + return; } - return this.storedBlob.slice.apply(this.storedBlob, this.storedBlobMap[key]) + return this.storedBlob.slice.apply( + this.storedBlob, + this.storedBlobMap[key] + ); } - getDump () { - let buffers = [] - let blobMap = {} - let currentBufferStart = 0 + 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 + 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)) + 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)) + dump(key, this.getFromStorage.bind(this)); } } - return [buffers, blobMap] + return [buffers, blobMap]; } -} +}; diff --git a/src/first-mate-helpers.js b/src/first-mate-helpers.js index 0ca312834..bbc827f68 100644 --- a/src/first-mate-helpers.js +++ b/src/first-mate-helpers.js @@ -1,11 +1,11 @@ module.exports = { - fromFirstMateScopeId (firstMateScopeId) { - let atomScopeId = -firstMateScopeId - if ((atomScopeId & 1) === 0) atomScopeId-- - return atomScopeId + 256 + fromFirstMateScopeId(firstMateScopeId) { + let atomScopeId = -firstMateScopeId; + if ((atomScopeId & 1) === 0) atomScopeId--; + return atomScopeId + 256; }, - toFirstMateScopeId (atomScopeId) { - return -(atomScopeId - 256) + toFirstMateScopeId(atomScopeId) { + return -(atomScopeId - 256); } -} +}; diff --git a/src/get-window-load-settings.js b/src/get-window-load-settings.js index d35b24213..9a0a4b643 100644 --- a/src/get-window-load-settings.js +++ b/src/get-window-load-settings.js @@ -1,10 +1,10 @@ -const {remote} = require('electron') +const { remote } = require('electron'); -let windowLoadSettings = null +let windowLoadSettings = null; module.exports = () => { if (!windowLoadSettings) { - windowLoadSettings = JSON.parse(remote.getCurrentWindow().loadSettingsJSON) + windowLoadSettings = JSON.parse(remote.getCurrentWindow().loadSettingsJSON); } - return windowLoadSettings -} + return windowLoadSettings; +}; diff --git a/src/git-repository-provider.js b/src/git-repository-provider.js index 827523b71..6965024b5 100644 --- a/src/git-repository-provider.js +++ b/src/git-repository-provider.js @@ -1,17 +1,17 @@ -const fs = require('fs') -const { Directory } = require('pathwatcher') -const GitRepository = require('./git-repository') +const fs = require('fs'); +const { Directory } = require('pathwatcher'); +const GitRepository = require('./git-repository'); -const GIT_FILE_REGEX = RegExp('^gitdir: (.+)') +const GIT_FILE_REGEX = RegExp('^gitdir: (.+)'); // Returns the .gitdir path in the agnostic Git symlink .git file given, or // null if the path is not a valid gitfile. // // * `gitFile` {String} path of gitfile to parse -function pathFromGitFileSync (gitFile) { +function pathFromGitFileSync(gitFile) { try { - const gitFileBuff = fs.readFileSync(gitFile, 'utf8') - return gitFileBuff != null ? gitFileBuff.match(GIT_FILE_REGEX)[1] : null + const gitFileBuff = fs.readFileSync(gitFile, 'utf8'); + return gitFileBuff != null ? gitFileBuff.match(GIT_FILE_REGEX)[1] : null; } catch (error) {} } @@ -19,17 +19,17 @@ function pathFromGitFileSync (gitFile) { // Git symlink .git file given, or null if the path is not a valid gitfile. // // * `gitFile` {String} path of gitfile to parse -function pathFromGitFile (gitFile) { +function pathFromGitFile(gitFile) { return new Promise(resolve => { fs.readFile(gitFile, 'utf8', (err, gitFileBuff) => { if (err == null && gitFileBuff != null) { - const result = gitFileBuff.toString().match(GIT_FILE_REGEX) - resolve(result != null ? result[1] : null) + const result = gitFileBuff.toString().match(GIT_FILE_REGEX); + resolve(result != null ? result[1] : null); } else { - resolve(null) + resolve(null); } - }) - }) + }); + }); } // Checks whether a valid `.git` directory is contained within the given @@ -37,15 +37,15 @@ function pathFromGitFile (gitFile) { // `.git` folder will be returned. Otherwise, returns `null`. // // * `directory` {Directory} to explore whether it is part of a Git repository. -function findGitDirectorySync (directory) { +function findGitDirectorySync(directory) { // TODO: Fix node-pathwatcher/src/directory.coffee so the following methods // can return cached values rather than always returning new objects: // getParent(), getFile(), getSubdirectory(). - let gitDir = directory.getSubdirectory('.git') + let gitDir = directory.getSubdirectory('.git'); if (typeof gitDir.getPath === 'function') { - const gitDirPath = pathFromGitFileSync(gitDir.getPath()) + const gitDirPath = pathFromGitFileSync(gitDir.getPath()); if (gitDirPath) { - gitDir = new Directory(directory.resolve(gitDirPath)) + gitDir = new Directory(directory.resolve(gitDirPath)); } } if ( @@ -53,11 +53,11 @@ function findGitDirectorySync (directory) { gitDir.existsSync() && isValidGitDirectorySync(gitDir) ) { - return gitDir + return gitDir; } else if (directory.isRoot()) { - return null + return null; } else { - return findGitDirectorySync(directory.getParent()) + return findGitDirectorySync(directory.getParent()); } } @@ -67,15 +67,15 @@ function findGitDirectorySync (directory) { // // Returns a {Promise} that resolves to // * `directory` {Directory} to explore whether it is part of a Git repository. -async function findGitDirectory (directory) { +async function findGitDirectory(directory) { // TODO: Fix node-pathwatcher/src/directory.coffee so the following methods // can return cached values rather than always returning new objects: // getParent(), getFile(), getSubdirectory(). - let gitDir = directory.getSubdirectory('.git') + let gitDir = directory.getSubdirectory('.git'); if (typeof gitDir.getPath === 'function') { - const gitDirPath = await pathFromGitFile(gitDir.getPath()) + const gitDirPath = await pathFromGitFile(gitDir.getPath()); if (gitDirPath) { - gitDir = new Directory(directory.resolve(gitDirPath)) + gitDir = new Directory(directory.resolve(gitDirPath)); } } if ( @@ -83,11 +83,11 @@ async function findGitDirectory (directory) { (await gitDir.exists()) && isValidGitDirectory(gitDir) ) { - return gitDir + return gitDir; } else if (directory.isRoot()) { - return null + return null; } else { - return findGitDirectory(directory.getParent()) + return findGitDirectory(directory.getParent()); } } @@ -95,7 +95,7 @@ async function findGitDirectory (directory) { // repository. // // * `directory` {Directory} whose base name is `.git`. -function isValidGitDirectorySync (directory) { +function isValidGitDirectorySync(directory) { // To decide whether a directory has a valid .git folder, we use // the heuristic adopted by the valid_repository_path() function defined in // node_modules/git-utils/deps/libgit2/src/repository.c. @@ -103,14 +103,14 @@ function isValidGitDirectorySync (directory) { directory.getSubdirectory('objects').existsSync() && directory.getFile('HEAD').existsSync() && directory.getSubdirectory('refs').existsSync() - ) + ); } // Returns a {Promise} that resolves to a {Boolean} indicating whether the // specified directory represents a Git repository. // // * `directory` {Directory} whose base name is `.git`. -async function isValidGitDirectory (directory) { +async function isValidGitDirectory(directory) { // To decide whether a directory has a valid .git folder, we use // the heuristic adopted by the valid_repository_path() function defined in // node_modules/git-utils/deps/libgit2/src/repository.c. @@ -118,63 +118,66 @@ async function isValidGitDirectory (directory) { (await directory.getSubdirectory('objects').exists()) && (await directory.getFile('HEAD').exists()) && directory.getSubdirectory('refs').exists() - ) + ); } // Provider that conforms to the atom.repository-provider@0.1.0 service. class GitRepositoryProvider { - constructor (project, config) { + constructor(project, config) { // Keys are real paths that end in `.git`. // Values are the corresponding GitRepository objects. - this.project = project - this.config = config - this.pathToRepository = {} + this.project = project; + this.config = config; + this.pathToRepository = {}; } // Returns a {Promise} that resolves with either: // * {GitRepository} if the given directory has a Git repository. // * `null` if the given directory does not have a Git repository. - async repositoryForDirectory (directory) { + async repositoryForDirectory(directory) { // Only one GitRepository should be created for each .git folder. Therefore, // we must check directory and its parent directories to find the nearest // .git folder. - const gitDir = await findGitDirectory(directory) - return this.repositoryForGitDirectory(gitDir) + const gitDir = await findGitDirectory(directory); + return this.repositoryForGitDirectory(gitDir); } // Returns either: // * {GitRepository} if the given directory has a Git repository. // * `null` if the given directory does not have a Git repository. - repositoryForDirectorySync (directory) { + repositoryForDirectorySync(directory) { // Only one GitRepository should be created for each .git folder. Therefore, // we must check directory and its parent directories to find the nearest // .git folder. - const gitDir = findGitDirectorySync(directory) - return this.repositoryForGitDirectory(gitDir) + const gitDir = findGitDirectorySync(directory); + return this.repositoryForGitDirectory(gitDir); } // Returns either: // * {GitRepository} if the given Git directory has a Git repository. // * `null` if the given directory does not have a Git repository. - repositoryForGitDirectory (gitDir) { + repositoryForGitDirectory(gitDir) { if (!gitDir) { - return null + return null; } - const gitDirPath = gitDir.getPath() - let repo = this.pathToRepository[gitDirPath] + const gitDirPath = gitDir.getPath(); + let repo = this.pathToRepository[gitDirPath]; if (!repo) { - repo = GitRepository.open(gitDirPath, { project: this.project, config: this.config }) + repo = GitRepository.open(gitDirPath, { + project: this.project, + config: this.config + }); if (!repo) { - return null + return null; } - repo.onDidDestroy(() => delete this.pathToRepository[gitDirPath]) - this.pathToRepository[gitDirPath] = repo - repo.refreshIndex() - repo.refreshStatus() + repo.onDidDestroy(() => delete this.pathToRepository[gitDirPath]); + this.pathToRepository[gitDirPath] = repo; + repo.refreshIndex(); + repo.refreshStatus(); } - return repo + return repo; } } -module.exports = GitRepositoryProvider +module.exports = GitRepositoryProvider; diff --git a/src/git-repository.js b/src/git-repository.js index 80f76e40a..878fb67e6 100644 --- a/src/git-repository.js +++ b/src/git-repository.js @@ -1,10 +1,10 @@ -const path = require('path') -const fs = require('fs-plus') -const _ = require('underscore-plus') -const {Emitter, Disposable, CompositeDisposable} = require('event-kit') -const GitUtils = require('git-utils') +const path = require('path'); +const fs = require('fs-plus'); +const _ = require('underscore-plus'); +const { Emitter, Disposable, CompositeDisposable } = require('event-kit'); +const GitUtils = require('git-utils'); -let nextId = 0 +let nextId = 0; // Extended: Represents the underlying git operations performed by Atom. // @@ -38,15 +38,14 @@ let nextId = 0 // ```coffee // {GitRepository} = require 'atom' // ``` -module.exports = -class GitRepository { - static exists (path) { - const git = this.open(path) +module.exports = class GitRepository { + static exists(path) { + const git = this.open(path); if (git) { - git.destroy() - return true + git.destroy(); + return true; } else { - return false + return false; } } @@ -62,48 +61,56 @@ class GitRepository { // statuses when the window is focused. // // Returns a {GitRepository} instance or `null` if the repository could not be opened. - static open (path, options) { - if (!path) { return null } + static open(path, options) { + if (!path) { + return null; + } try { - return new GitRepository(path, options) + return new GitRepository(path, options); } catch (error) { - return null + return null; } } - constructor (path, options = {}) { - this.id = nextId++ - this.emitter = new Emitter() - this.subscriptions = new CompositeDisposable() - this.repo = GitUtils.open(path) + constructor(path, options = {}) { + this.id = nextId++; + this.emitter = new Emitter(); + this.subscriptions = new CompositeDisposable(); + this.repo = GitUtils.open(path); if (this.repo == null) { - throw new Error(`No Git repository found searching path: ${path}`) + throw new Error(`No Git repository found searching path: ${path}`); } - this.statusRefreshCount = 0 - this.statuses = {} - this.upstream = {ahead: 0, behind: 0} + this.statusRefreshCount = 0; + this.statuses = {}; + this.upstream = { ahead: 0, behind: 0 }; for (let submodulePath in this.repo.submodules) { - const submoduleRepo = this.repo.submodules[submodulePath] - submoduleRepo.upstream = {ahead: 0, behind: 0} + const submoduleRepo = this.repo.submodules[submodulePath]; + submoduleRepo.upstream = { ahead: 0, behind: 0 }; } - this.project = options.project - this.config = options.config + this.project = options.project; + this.config = options.config; if (options.refreshOnWindowFocus || options.refreshOnWindowFocus == null) { const onWindowFocus = () => { - this.refreshIndex() - this.refreshStatus() - } + this.refreshIndex(); + this.refreshStatus(); + }; - window.addEventListener('focus', onWindowFocus) - this.subscriptions.add(new Disposable(() => window.removeEventListener('focus', onWindowFocus))) + window.addEventListener('focus', onWindowFocus); + this.subscriptions.add( + new Disposable(() => window.removeEventListener('focus', onWindowFocus)) + ); } if (this.project != null) { - this.project.getBuffers().forEach(buffer => this.subscribeToBuffer(buffer)) - this.subscriptions.add(this.project.onDidAddBuffer(buffer => this.subscribeToBuffer(buffer))) + this.project + .getBuffers() + .forEach(buffer => this.subscribeToBuffer(buffer)); + this.subscriptions.add( + this.project.onDidAddBuffer(buffer => this.subscribeToBuffer(buffer)) + ); } } @@ -111,24 +118,24 @@ class GitRepository { // // This destroys any tasks and subscriptions and releases the underlying // libgit2 repository handle. This method is idempotent. - destroy () { - this.repo = null + destroy() { + this.repo = null; if (this.emitter) { - this.emitter.emit('did-destroy') - this.emitter.dispose() - this.emitter = null + this.emitter.emit('did-destroy'); + this.emitter.dispose(); + this.emitter = null; } if (this.subscriptions) { - this.subscriptions.dispose() - this.subscriptions = null + this.subscriptions.dispose(); + this.subscriptions = null; } } // Public: Returns a {Boolean} indicating if this repository has been destroyed. - isDestroyed () { - return this.repo == null + isDestroyed() { + return this.repo == null; } // Public: Invoke the given callback when this GitRepository's destroy() method @@ -137,8 +144,8 @@ class GitRepository { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } /* @@ -156,8 +163,8 @@ class GitRepository { // {::isStatusModified} or {::isStatusNew} to get more information. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeStatus (callback) { - return this.emitter.on('did-change-status', callback) + onDidChangeStatus(callback) { + return this.emitter.on('did-change-status', callback); } // Public: Invoke the given callback when a multiple files' statuses have @@ -168,8 +175,8 @@ class GitRepository { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeStatuses (callback) { - return this.emitter.on('did-change-statuses', callback) + onDidChangeStatuses(callback) { + return this.emitter.on('did-change-statuses', callback); } /* @@ -180,38 +187,42 @@ class GitRepository { // this repository. // // Returns `"git"`. - getType () { return 'git' } + getType() { + return 'git'; + } // Public: Returns the {String} path of the repository. - getPath () { + getPath() { if (this.path == null) { - this.path = fs.absolute(this.getRepo().getPath()) + this.path = fs.absolute(this.getRepo().getPath()); } - return this.path + return this.path; } // Public: Returns the {String} working directory path of the repository. - getWorkingDirectory () { - return this.getRepo().getWorkingDirectory() + getWorkingDirectory() { + return this.getRepo().getWorkingDirectory(); } // Public: Returns true if at the root, false if in a subfolder of the // repository. - isProjectAtRoot () { + isProjectAtRoot() { if (this.projectAtRoot == null) { - this.projectAtRoot = this.project && this.project.relativize(this.getWorkingDirectory()) === '' + this.projectAtRoot = + this.project && + this.project.relativize(this.getWorkingDirectory()) === ''; } - return this.projectAtRoot + return this.projectAtRoot; } // Public: Makes a path relative to the repository's working directory. - relativize (path) { - return this.getRepo().relativize(path) + relativize(path) { + return this.getRepo().relativize(path); } // Public: Returns true if the given branch exists. - hasBranch (branch) { - return this.getReferenceTarget(`refs/heads/${branch}`) != null + hasBranch(branch) { + return this.getReferenceTarget(`refs/heads/${branch}`) != null; } // Public: Retrieves a shortened version of the HEAD reference value. @@ -224,8 +235,8 @@ class GitRepository { // for, only needed if the repository contains submodules. // // Returns a {String}. - getShortHead (path) { - return this.getRepo(path).getShortHead() + getShortHead(path) { + return this.getRepo(path).getShortHead(); } // Public: Is the given path a submodule in the repository? @@ -233,15 +244,18 @@ class GitRepository { // * `path` The {String} path to check. // // Returns a {Boolean}. - isSubmodule (filePath) { - if (!filePath) return false + isSubmodule(filePath) { + if (!filePath) return false; - const repo = this.getRepo(filePath) + const repo = this.getRepo(filePath); if (repo.isSubmodule(repo.relativize(filePath))) { - return true + return true; } else { // Check if the filePath is a working directory in a repo that isn't the root. - return repo !== this.getRepo() && repo.relativize(path.join(filePath, 'dir')) === 'dir' + return ( + repo !== this.getRepo() && + repo.relativize(path.join(filePath, 'dir')) === 'dir' + ); } } @@ -251,8 +265,8 @@ class GitRepository { // * `reference` The {String} branch reference name. // * `path` The {String} path in the repository to get this information for, // only needed if the repository contains submodules. - getAheadBehindCount (reference, path) { - return this.getRepo(path).getAheadBehindCount(reference) + getAheadBehindCount(reference, path) { + return this.getRepo(path).getAheadBehindCount(reference); } // Public: Get the cached ahead/behind commit counts for the current branch's @@ -264,8 +278,8 @@ class GitRepository { // Returns an {Object} with the following keys: // * `ahead` The {Number} of commits ahead. // * `behind` The {Number} of commits behind. - getCachedUpstreamAheadBehindCount (path) { - return this.getRepo(path).upstream || this.upstream + getCachedUpstreamAheadBehindCount(path) { + return this.getRepo(path).upstream || this.upstream; } // Public: Returns the git configuration value specified by the key. @@ -273,16 +287,16 @@ class GitRepository { // * `key` The {String} key for the configuration to lookup. // * `path` An optional {String} path in the repository to get this information // for, only needed if the repository has submodules. - getConfigValue (key, path) { - return this.getRepo(path).getConfigValue(key) + getConfigValue(key, path) { + return this.getRepo(path).getConfigValue(key); } // Public: Returns the origin url of the repository. // // * `path` (optional) {String} path in the repository to get this information // for, only needed if the repository has submodules. - getOriginURL (path) { - return this.getConfigValue('remote.origin.url', path) + getOriginURL(path) { + return this.getConfigValue('remote.origin.url', path); } // Public: Returns the upstream branch for the current HEAD, or null if there @@ -292,8 +306,8 @@ class GitRepository { // only needed if the repository contains submodules. // // Returns a {String} branch name such as `refs/remotes/origin/master`. - getUpstreamBranch (path) { - return this.getRepo(path).getUpstreamBranch() + getUpstreamBranch(path) { + return this.getRepo(path).getUpstreamBranch(); } // Public: Gets all the local and remote references. @@ -305,8 +319,8 @@ class GitRepository { // * `heads` An {Array} of head reference names. // * `remotes` An {Array} of remote reference names. // * `tags` An {Array} of tag reference names. - getReferences (path) { - return this.getRepo(path).getReferences() + getReferences(path) { + return this.getRepo(path).getReferences(); } // Public: Returns the current {String} SHA for the given reference. @@ -314,8 +328,8 @@ class GitRepository { // * `reference` The {String} reference to get the target of. // * `path` An optional {String} path in the repo to get the reference target // for. Only needed if the repository contains submodules. - getReferenceTarget (reference, path) { - return this.getRepo(path).getReferenceTarget(reference) + getReferenceTarget(reference, path) { + return this.getRepo(path).getReferenceTarget(reference); } /* @@ -327,8 +341,8 @@ class GitRepository { // * `path` The {String} path to check. // // Returns a {Boolean} that's true if the `path` is modified. - isPathModified (path) { - return this.isStatusModified(this.getPathStatus(path)) + isPathModified(path) { + return this.isStatusModified(this.getPathStatus(path)); } // Public: Returns true if the given path is new. @@ -336,8 +350,8 @@ class GitRepository { // * `path` The {String} path to check. // // Returns a {Boolean} that's true if the `path` is new. - isPathNew (path) { - return this.isStatusNew(this.getPathStatus(path)) + isPathNew(path) { + return this.isStatusNew(this.getPathStatus(path)); } // Public: Is the given path ignored? @@ -345,8 +359,8 @@ class GitRepository { // * `path` The {String} path to check. // // Returns a {Boolean} that's true if the `path` is ignored. - isPathIgnored (path) { - return this.getRepo().isIgnored(this.relativize(path)) + isPathIgnored(path) { + return this.getRepo().isIgnored(this.relativize(path)); } // Public: Get the status of a directory in the repository's working directory. @@ -355,14 +369,14 @@ class GitRepository { // // Returns a {Number} representing the status. This value can be passed to // {::isStatusModified} or {::isStatusNew} to get more information. - getDirectoryStatus (directoryPath) { - directoryPath = `${this.relativize(directoryPath)}/` - let directoryStatus = 0 + getDirectoryStatus(directoryPath) { + directoryPath = `${this.relativize(directoryPath)}/`; + let directoryStatus = 0; for (let statusPath in this.statuses) { - const status = this.statuses[statusPath] - if (statusPath.startsWith(directoryPath)) directoryStatus |= status + const status = this.statuses[statusPath]; + if (statusPath.startsWith(directoryPath)) directoryStatus |= status; } - return directoryStatus + return directoryStatus; } // Public: Get the status of a single path in the repository. @@ -371,22 +385,22 @@ class GitRepository { // // Returns a {Number} representing the status. This value can be passed to // {::isStatusModified} or {::isStatusNew} to get more information. - getPathStatus (path) { - const repo = this.getRepo(path) - const relativePath = this.relativize(path) - const currentPathStatus = this.statuses[relativePath] || 0 - let pathStatus = repo.getStatus(repo.relativize(path)) || 0 - if (repo.isStatusIgnored(pathStatus)) pathStatus = 0 + getPathStatus(path) { + const repo = this.getRepo(path); + const relativePath = this.relativize(path); + const currentPathStatus = this.statuses[relativePath] || 0; + let pathStatus = repo.getStatus(repo.relativize(path)) || 0; + if (repo.isStatusIgnored(pathStatus)) pathStatus = 0; if (pathStatus > 0) { - this.statuses[relativePath] = pathStatus + this.statuses[relativePath] = pathStatus; } else { - delete this.statuses[relativePath] + delete this.statuses[relativePath]; } if (currentPathStatus !== pathStatus) { - this.emitter.emit('did-change-status', {path, pathStatus}) + this.emitter.emit('did-change-status', { path, pathStatus }); } - return pathStatus + return pathStatus; } // Public: Get the cached status for the given path. @@ -394,8 +408,8 @@ class GitRepository { // * `path` A {String} path in the repository, relative or absolute. // // Returns a status {Number} or null if the path is not in the cache. - getCachedPathStatus (path) { - return this.statuses[this.relativize(path)] + getCachedPathStatus(path) { + return this.statuses[this.relativize(path)]; } // Public: Returns true if the given status indicates modification. @@ -403,15 +417,17 @@ class GitRepository { // * `status` A {Number} representing the status. // // Returns a {Boolean} that's true if the `status` indicates modification. - isStatusModified (status) { return this.getRepo().isStatusModified(status) } + isStatusModified(status) { + return this.getRepo().isStatusModified(status); + } // Public: Returns true if the given status indicates a new path. // // * `status` A {Number} representing the status. // // Returns a {Boolean} that's true if the `status` indicates a new path. - isStatusNew (status) { - return this.getRepo().isStatusNew(status) + isStatusNew(status) { + return this.getRepo().isStatusNew(status); } /* @@ -428,9 +444,9 @@ class GitRepository { // Returns an {Object} with the following keys: // * `added` The {Number} of added lines. // * `deleted` The {Number} of deleted lines. - getDiffStats (path) { - const repo = this.getRepo(path) - return repo.getDiffStats(repo.relativize(path)) + getDiffStats(path) { + const repo = this.getRepo(path); + return repo.getDiffStats(repo.relativize(path)); } // Public: Retrieves the line diffs comparing the `HEAD` version of the given @@ -444,12 +460,12 @@ class GitRepository { // * `newStart` The line {Number} of the new hunk. // * `oldLines` The {Number} of lines in the old hunk. // * `newLines` The {Number} of lines in the new hunk - getLineDiffs (path, text) { + getLineDiffs(path, text) { // Ignore eol of line differences on windows so that files checked in as // LF don't report every line modified when the text contains CRLF endings. - const options = {ignoreEolWhitespace: process.platform === 'win32'} - const repo = this.getRepo(path) - return repo.getLineDiffs(repo.relativize(path), text, options) + const options = { ignoreEolWhitespace: process.platform === 'win32' }; + const repo = this.getRepo(path); + return repo.getLineDiffs(repo.relativize(path), text, options); } /* @@ -469,11 +485,11 @@ class GitRepository { // * `path` The {String} path to checkout. // // Returns a {Boolean} that's true if the method was successful. - checkoutHead (path) { - const repo = this.getRepo(path) - const headCheckedOut = repo.checkoutHead(repo.relativize(path)) - if (headCheckedOut) this.getPathStatus(path) - return headCheckedOut + checkoutHead(path) { + const repo = this.getRepo(path); + const headCheckedOut = repo.checkoutHead(repo.relativize(path)); + if (headCheckedOut) this.getPathStatus(path); + return headCheckedOut; } // Public: Checks out a branch in your repository. @@ -483,8 +499,8 @@ class GitRepository { // it doesn't exist. // // Returns a Boolean that's true if the method was successful. - checkoutReference (reference, create) { - return this.getRepo().checkoutReference(reference, create) + checkoutReference(reference, create) { + return this.getRepo().checkoutReference(reference, create); } /* @@ -492,104 +508,114 @@ class GitRepository { */ // Subscribes to buffer events. - subscribeToBuffer (buffer) { + subscribeToBuffer(buffer) { const getBufferPathStatus = () => { - const bufferPath = buffer.getPath() - if (bufferPath) this.getPathStatus(bufferPath) - } + const bufferPath = buffer.getPath(); + if (bufferPath) this.getPathStatus(bufferPath); + }; - getBufferPathStatus() - const bufferSubscriptions = new CompositeDisposable() - bufferSubscriptions.add(buffer.onDidSave(getBufferPathStatus)) - bufferSubscriptions.add(buffer.onDidReload(getBufferPathStatus)) - bufferSubscriptions.add(buffer.onDidChangePath(getBufferPathStatus)) - bufferSubscriptions.add(buffer.onDidDestroy(() => { - bufferSubscriptions.dispose() - return this.subscriptions.remove(bufferSubscriptions) - })) - this.subscriptions.add(bufferSubscriptions) + getBufferPathStatus(); + const bufferSubscriptions = new CompositeDisposable(); + bufferSubscriptions.add(buffer.onDidSave(getBufferPathStatus)); + bufferSubscriptions.add(buffer.onDidReload(getBufferPathStatus)); + bufferSubscriptions.add(buffer.onDidChangePath(getBufferPathStatus)); + bufferSubscriptions.add( + buffer.onDidDestroy(() => { + bufferSubscriptions.dispose(); + return this.subscriptions.remove(bufferSubscriptions); + }) + ); + this.subscriptions.add(bufferSubscriptions); } // Subscribes to editor view event. - checkoutHeadForEditor (editor) { - const buffer = editor.getBuffer() - const bufferPath = buffer.getPath() + checkoutHeadForEditor(editor) { + const buffer = editor.getBuffer(); + const bufferPath = buffer.getPath(); if (bufferPath) { - this.checkoutHead(bufferPath) - return buffer.reload() + this.checkoutHead(bufferPath); + return buffer.reload(); } } // Returns the corresponding {Repository} - getRepo (path) { + getRepo(path) { if (this.repo) { - return this.repo.submoduleForPath(path) || this.repo + return this.repo.submoduleForPath(path) || this.repo; } else { - throw new Error('Repository has been destroyed') + throw new Error('Repository has been destroyed'); } } // Reread the index to update any values that have changed since the // last time the index was read. - refreshIndex () { - return this.getRepo().refreshIndex() + refreshIndex() { + return this.getRepo().refreshIndex(); } // Refreshes the current git status in an outside process and asynchronously // updates the relevant properties. - async refreshStatus () { - const statusRefreshCount = ++this.statusRefreshCount - const repo = this.getRepo() + async refreshStatus() { + const statusRefreshCount = ++this.statusRefreshCount; + const repo = this.getRepo(); - const relativeProjectPaths = this.project && this.project.getPaths() - .map(projectPath => this.relativize(projectPath)) - .filter(projectPath => (projectPath.length > 0) && !path.isAbsolute(projectPath)) + const relativeProjectPaths = + this.project && + this.project + .getPaths() + .map(projectPath => this.relativize(projectPath)) + .filter( + projectPath => projectPath.length > 0 && !path.isAbsolute(projectPath) + ); - const branch = await repo.getHeadAsync() - const upstream = await repo.getAheadBehindCountAsync() + const branch = await repo.getHeadAsync(); + const upstream = await repo.getAheadBehindCountAsync(); - const statuses = {} - const repoStatus = relativeProjectPaths.length > 0 - ? await repo.getStatusAsync(relativeProjectPaths) - : await repo.getStatusAsync() + const statuses = {}; + const repoStatus = + relativeProjectPaths.length > 0 + ? await repo.getStatusAsync(relativeProjectPaths) + : await repo.getStatusAsync(); for (let filePath in repoStatus) { - statuses[filePath] = repoStatus[filePath] + statuses[filePath] = repoStatus[filePath]; } - const submodules = {} + const submodules = {}; for (let submodulePath in repo.submodules) { - const submoduleRepo = repo.submodules[submodulePath] + const submoduleRepo = repo.submodules[submodulePath]; submodules[submodulePath] = { branch: await submoduleRepo.getHeadAsync(), upstream: await submoduleRepo.getAheadBehindCountAsync() - } + }; - const workingDirectoryPath = submoduleRepo.getWorkingDirectory() - const submoduleStatus = await submoduleRepo.getStatusAsync() + const workingDirectoryPath = submoduleRepo.getWorkingDirectory(); + const submoduleStatus = await submoduleRepo.getStatusAsync(); for (let filePath in submoduleStatus) { - const absolutePath = path.join(workingDirectoryPath, filePath) - const relativizePath = repo.relativize(absolutePath) - statuses[relativizePath] = submoduleStatus[filePath] + const absolutePath = path.join(workingDirectoryPath, filePath); + const relativizePath = repo.relativize(absolutePath); + statuses[relativizePath] = submoduleStatus[filePath]; } } - if (this.statusRefreshCount !== statusRefreshCount || this.isDestroyed()) return + if (this.statusRefreshCount !== statusRefreshCount || this.isDestroyed()) + return; const statusesUnchanged = _.isEqual(branch, this.branch) && _.isEqual(statuses, this.statuses) && _.isEqual(upstream, this.upstream) && - _.isEqual(submodules, this.submodules) + _.isEqual(submodules, this.submodules); - this.branch = branch - this.statuses = statuses - this.upstream = upstream - this.submodules = submodules + this.branch = branch; + this.statuses = statuses; + this.upstream = upstream; + this.submodules = submodules; for (let submodulePath in repo.submodules) { - repo.submodules[submodulePath].upstream = submodules[submodulePath].upstream + repo.submodules[submodulePath].upstream = + submodules[submodulePath].upstream; } - if (!statusesUnchanged) this.emitter.emit('did-change-statuses') + if (!statusesUnchanged) this.emitter.emit('did-change-statuses'); } -} +}; diff --git a/src/grammar-registry.js b/src/grammar-registry.js index 1b033286d..ec678abc5 100644 --- a/src/grammar-registry.js +++ b/src/grammar-registry.js @@ -1,72 +1,76 @@ -const _ = require('underscore-plus') -const Grim = require('grim') -const CSON = require('season') -const FirstMate = require('first-mate') -const {Disposable, CompositeDisposable} = require('event-kit') -const TextMateLanguageMode = require('./text-mate-language-mode') -const TreeSitterLanguageMode = require('./tree-sitter-language-mode') -const TreeSitterGrammar = require('./tree-sitter-grammar') -const ScopeDescriptor = require('./scope-descriptor') -const Token = require('./token') -const fs = require('fs-plus') -const {Point, Range} = require('text-buffer') +const _ = require('underscore-plus'); +const Grim = require('grim'); +const CSON = require('season'); +const FirstMate = require('first-mate'); +const { Disposable, CompositeDisposable } = require('event-kit'); +const TextMateLanguageMode = require('./text-mate-language-mode'); +const TreeSitterLanguageMode = require('./tree-sitter-language-mode'); +const TreeSitterGrammar = require('./tree-sitter-grammar'); +const ScopeDescriptor = require('./scope-descriptor'); +const Token = require('./token'); +const fs = require('fs-plus'); +const { Point, Range } = require('text-buffer'); -const PATH_SPLIT_REGEX = new RegExp('[/.]') +const PATH_SPLIT_REGEX = new RegExp('[/.]'); // Extended: This class holds the grammars used for tokenizing. // // An instance of this class is always available as the `atom.grammars` global. -module.exports = -class GrammarRegistry { - constructor ({config} = {}) { - this.config = config - this.subscriptions = new CompositeDisposable() - this.textmateRegistry = new FirstMate.GrammarRegistry({maxTokensPerLine: 100, maxLineLength: 1000}) - this.clear() +module.exports = class GrammarRegistry { + constructor({ config } = {}) { + this.config = config; + this.subscriptions = new CompositeDisposable(); + this.textmateRegistry = new FirstMate.GrammarRegistry({ + maxTokensPerLine: 100, + maxLineLength: 1000 + }); + this.clear(); } - clear () { - this.textmateRegistry.clear() - this.treeSitterGrammarsById = {} - if (this.subscriptions) this.subscriptions.dispose() - this.subscriptions = new CompositeDisposable() - this.languageOverridesByBufferId = new Map() - this.grammarScoresByBuffer = new Map() - this.textMateScopeNamesByTreeSitterLanguageId = new Map() - this.treeSitterLanguageIdsByTextMateScopeName = new Map() + clear() { + this.textmateRegistry.clear(); + this.treeSitterGrammarsById = {}; + if (this.subscriptions) this.subscriptions.dispose(); + this.subscriptions = new CompositeDisposable(); + this.languageOverridesByBufferId = new Map(); + this.grammarScoresByBuffer = new Map(); + this.textMateScopeNamesByTreeSitterLanguageId = new Map(); + this.treeSitterLanguageIdsByTextMateScopeName = new Map(); - const grammarAddedOrUpdated = this.grammarAddedOrUpdated.bind(this) - this.textmateRegistry.onDidAddGrammar(grammarAddedOrUpdated) - this.textmateRegistry.onDidUpdateGrammar(grammarAddedOrUpdated) + const grammarAddedOrUpdated = this.grammarAddedOrUpdated.bind(this); + this.textmateRegistry.onDidAddGrammar(grammarAddedOrUpdated); + this.textmateRegistry.onDidUpdateGrammar(grammarAddedOrUpdated); - this.subscriptions.add(this.config.onDidChange('core.useTreeSitterParsers', () => { - this.grammarScoresByBuffer.forEach((score, buffer) => { - if (!this.languageOverridesByBufferId.has(buffer.id)) { - this.autoAssignLanguageMode(buffer) - } + this.subscriptions.add( + this.config.onDidChange('core.useTreeSitterParsers', () => { + this.grammarScoresByBuffer.forEach((score, buffer) => { + if (!this.languageOverridesByBufferId.has(buffer.id)) { + this.autoAssignLanguageMode(buffer); + } + }); }) - })) + ); } - serialize () { - const languageOverridesByBufferId = {} + serialize() { + const languageOverridesByBufferId = {}; this.languageOverridesByBufferId.forEach((languageId, bufferId) => { - languageOverridesByBufferId[bufferId] = languageId - }) - return {languageOverridesByBufferId} + languageOverridesByBufferId[bufferId] = languageId; + }); + return { languageOverridesByBufferId }; } - deserialize (params) { + deserialize(params) { for (const bufferId in params.languageOverridesByBufferId || {}) { this.languageOverridesByBufferId.set( bufferId, params.languageOverridesByBufferId[bufferId] - ) + ); } } - createToken (value, scopes) { - return new Token({value, scopes}) + createToken(value, scopes) { + return new Token({ value, scopes }); } // Extended: set a {TextBuffer}'s language mode based on its path and content, @@ -77,40 +81,40 @@ class GrammarRegistry { // // Returns a {Disposable} that can be used to stop updating the buffer's // language mode. - maintainLanguageMode (buffer) { - this.grammarScoresByBuffer.set(buffer, null) + maintainLanguageMode(buffer) { + this.grammarScoresByBuffer.set(buffer, null); - const languageOverride = this.languageOverridesByBufferId.get(buffer.id) + const languageOverride = this.languageOverridesByBufferId.get(buffer.id); if (languageOverride) { - this.assignLanguageMode(buffer, languageOverride) + this.assignLanguageMode(buffer, languageOverride); } else { - this.autoAssignLanguageMode(buffer) + this.autoAssignLanguageMode(buffer); } const pathChangeSubscription = buffer.onDidChangePath(() => { - this.grammarScoresByBuffer.delete(buffer) + this.grammarScoresByBuffer.delete(buffer); if (!this.languageOverridesByBufferId.has(buffer.id)) { - this.autoAssignLanguageMode(buffer) + this.autoAssignLanguageMode(buffer); } - }) + }); const destroySubscription = buffer.onDidDestroy(() => { - this.grammarScoresByBuffer.delete(buffer) - this.languageOverridesByBufferId.delete(buffer.id) - this.subscriptions.remove(destroySubscription) - this.subscriptions.remove(pathChangeSubscription) - }) + this.grammarScoresByBuffer.delete(buffer); + this.languageOverridesByBufferId.delete(buffer.id); + this.subscriptions.remove(destroySubscription); + this.subscriptions.remove(pathChangeSubscription); + }); - this.subscriptions.add(pathChangeSubscription, destroySubscription) + this.subscriptions.add(pathChangeSubscription, destroySubscription); return new Disposable(() => { - destroySubscription.dispose() - pathChangeSubscription.dispose() - this.subscriptions.remove(pathChangeSubscription) - this.subscriptions.remove(destroySubscription) - this.grammarScoresByBuffer.delete(buffer) - this.languageOverridesByBufferId.delete(buffer.id) - }) + destroySubscription.dispose(); + pathChangeSubscription.dispose(); + this.subscriptions.remove(pathChangeSubscription); + this.subscriptions.remove(destroySubscription); + this.grammarScoresByBuffer.delete(buffer); + this.languageOverridesByBufferId.delete(buffer.id); + }); } // Extended: Force a {TextBuffer} to use a different grammar than the @@ -121,33 +125,35 @@ class GrammarRegistry { // // Returns a {Boolean} that indicates whether the language was successfully // found. - assignLanguageMode (buffer, languageId) { - if (buffer.getBuffer) buffer = buffer.getBuffer() + assignLanguageMode(buffer, languageId) { + if (buffer.getBuffer) buffer = buffer.getBuffer(); - let grammar = null + let grammar = null; if (languageId != null) { - grammar = this.grammarForId(languageId) - if (!grammar) return false - this.languageOverridesByBufferId.set(buffer.id, languageId) + grammar = this.grammarForId(languageId); + if (!grammar) return false; + this.languageOverridesByBufferId.set(buffer.id, languageId); } else { - this.languageOverridesByBufferId.set(buffer.id, null) - grammar = this.textmateRegistry.nullGrammar + this.languageOverridesByBufferId.set(buffer.id, null); + grammar = this.textmateRegistry.nullGrammar; } - this.grammarScoresByBuffer.set(buffer, null) + this.grammarScoresByBuffer.set(buffer, null); if (grammar !== buffer.getLanguageMode().grammar) { - buffer.setLanguageMode(this.languageModeForGrammarAndBuffer(grammar, buffer)) + buffer.setLanguageMode( + this.languageModeForGrammarAndBuffer(grammar, buffer) + ); } - return true + return true; } // Extended: Get the `languageId` that has been explicitly assigned to // to the given buffer, if any. // // Returns a {String} id of the language - getAssignedLanguageId (buffer) { - return this.languageOverridesByBufferId.get(buffer.id) + getAssignedLanguageId(buffer) { + return this.languageOverridesByBufferId.get(buffer.id); } // Extended: Remove any language mode override that has been set for the @@ -155,23 +161,30 @@ class GrammarRegistry { // mode available. // // * `buffer` The {TextBuffer}. - autoAssignLanguageMode (buffer) { + autoAssignLanguageMode(buffer) { const result = this.selectGrammarWithScore( buffer.getPath(), getGrammarSelectionContent(buffer) - ) - this.languageOverridesByBufferId.delete(buffer.id) - this.grammarScoresByBuffer.set(buffer, result.score) + ); + this.languageOverridesByBufferId.delete(buffer.id); + this.grammarScoresByBuffer.set(buffer, result.score); if (result.grammar !== buffer.getLanguageMode().grammar) { - buffer.setLanguageMode(this.languageModeForGrammarAndBuffer(result.grammar, buffer)) + buffer.setLanguageMode( + this.languageModeForGrammarAndBuffer(result.grammar, buffer) + ); } } - languageModeForGrammarAndBuffer (grammar, buffer) { + languageModeForGrammarAndBuffer(grammar, buffer) { if (grammar instanceof TreeSitterGrammar) { - return new TreeSitterLanguageMode({grammar, buffer, config: this.config, grammars: this}) + return new TreeSitterLanguageMode({ + grammar, + buffer, + config: this.config, + grammars: this + }); } else { - return new TextMateLanguageMode({grammar, buffer, config: this.config}) + return new TextMateLanguageMode({ grammar, buffer, config: this.config }); } } @@ -184,147 +197,158 @@ class GrammarRegistry { // * `fileContents` A {String} of text for the file path. // // Returns a {Grammar}, never null. - selectGrammar (filePath, fileContents) { - return this.selectGrammarWithScore(filePath, fileContents).grammar + selectGrammar(filePath, fileContents) { + return this.selectGrammarWithScore(filePath, fileContents).grammar; } - selectGrammarWithScore (filePath, fileContents) { - let bestMatch = null - let highestScore = -Infinity + selectGrammarWithScore(filePath, fileContents) { + let bestMatch = null; + let highestScore = -Infinity; this.forEachGrammar(grammar => { - const score = this.getGrammarScore(grammar, filePath, fileContents) + const score = this.getGrammarScore(grammar, filePath, fileContents); if (score > highestScore || bestMatch == null) { - bestMatch = grammar - highestScore = score + bestMatch = grammar; + highestScore = score; } - }) - return {grammar: bestMatch, score: highestScore} + }); + return { grammar: bestMatch, score: highestScore }; } // Extended: Returns a {Number} representing how well the grammar matches the // `filePath` and `contents`. - getGrammarScore (grammar, filePath, contents) { + getGrammarScore(grammar, filePath, contents) { if (contents == null && fs.isFileSync(filePath)) { - contents = fs.readFileSync(filePath, 'utf8') + contents = fs.readFileSync(filePath, 'utf8'); } // Initially identify matching grammars based on the filename and the first // line of the file. - let score = this.getGrammarPathScore(grammar, filePath) - if (this.grammarMatchesPrefix(grammar, contents)) score += 0.5 + let score = this.getGrammarPathScore(grammar, filePath); + if (this.grammarMatchesPrefix(grammar, contents)) score += 0.5; // If multiple grammars match by one of the above criteria, break ties. if (score > 0) { - const isTreeSitter = grammar instanceof TreeSitterGrammar + const isTreeSitter = grammar instanceof TreeSitterGrammar; // Prefer either TextMate or Tree-sitter grammars based on the user's settings. if (isTreeSitter) { if (this.shouldUseTreeSitterParser(grammar.scopeName)) { - score += 0.1 + score += 0.1; } else { - return -Infinity + return -Infinity; } } // Prefer grammars with matching content regexes. Prefer a grammar with no content regex // over one with a non-matching content regex. if (grammar.contentRegex) { - const contentMatch = isTreeSitter ? grammar.contentRegex.test(contents) : grammar.contentRegex.testSync(contents) + const contentMatch = isTreeSitter + ? grammar.contentRegex.test(contents) + : grammar.contentRegex.testSync(contents); if (contentMatch) { - score += 0.05 + score += 0.05; } else { - score -= 0.05 + score -= 0.05; } } // Prefer grammars that the user has manually installed over bundled grammars. - if (!grammar.bundledPackage) score += 0.01 + if (!grammar.bundledPackage) score += 0.01; } - return score + return score; } - getGrammarPathScore (grammar, filePath) { - if (!filePath) return -1 - if (process.platform === 'win32') { filePath = filePath.replace(/\\/g, '/') } - - const pathComponents = filePath.toLowerCase().split(PATH_SPLIT_REGEX) - let pathScore = 0 - - let customFileTypes - if (this.config.get('core.customFileTypes')) { - customFileTypes = this.config.get('core.customFileTypes')[grammar.scopeName] + getGrammarPathScore(grammar, filePath) { + if (!filePath) return -1; + if (process.platform === 'win32') { + filePath = filePath.replace(/\\/g, '/'); } - let { fileTypes } = grammar + const pathComponents = filePath.toLowerCase().split(PATH_SPLIT_REGEX); + let pathScore = 0; + + let customFileTypes; + if (this.config.get('core.customFileTypes')) { + customFileTypes = this.config.get('core.customFileTypes')[ + grammar.scopeName + ]; + } + + let { fileTypes } = grammar; if (customFileTypes) { - fileTypes = fileTypes.concat(customFileTypes) + fileTypes = fileTypes.concat(customFileTypes); } for (let i = 0; i < fileTypes.length; i++) { - const fileType = fileTypes[i] - const fileTypeComponents = fileType.toLowerCase().split(PATH_SPLIT_REGEX) - const pathSuffix = pathComponents.slice(-fileTypeComponents.length) + const fileType = fileTypes[i]; + const fileTypeComponents = fileType.toLowerCase().split(PATH_SPLIT_REGEX); + const pathSuffix = pathComponents.slice(-fileTypeComponents.length); if (_.isEqual(pathSuffix, fileTypeComponents)) { - pathScore = Math.max(pathScore, fileType.length) + pathScore = Math.max(pathScore, fileType.length); if (i >= grammar.fileTypes.length) { - pathScore += 0.5 + pathScore += 0.5; } } } - return pathScore + return pathScore; } - grammarMatchesPrefix (grammar, contents) { + grammarMatchesPrefix(grammar, contents) { if (contents && grammar.firstLineRegex) { - let escaped = false - let numberOfNewlinesInRegex = 0 + let escaped = false; + let numberOfNewlinesInRegex = 0; for (let character of grammar.firstLineRegex.source) { switch (character) { case '\\': - escaped = !escaped - break + escaped = !escaped; + break; case 'n': - if (escaped) { numberOfNewlinesInRegex++ } - escaped = false - break + if (escaped) { + numberOfNewlinesInRegex++; + } + escaped = false; + break; default: - escaped = false + escaped = false; } } - const prefix = contents.split('\n').slice(0, numberOfNewlinesInRegex + 1).join('\n') + const prefix = contents + .split('\n') + .slice(0, numberOfNewlinesInRegex + 1) + .join('\n'); if (grammar.firstLineRegex.testSync) { - return grammar.firstLineRegex.testSync(prefix) + return grammar.firstLineRegex.testSync(prefix); } else { - return grammar.firstLineRegex.test(prefix) + return grammar.firstLineRegex.test(prefix); } } else { - return false + return false; } } - forEachGrammar (callback) { - this.textmateRegistry.grammars.forEach(callback) + forEachGrammar(callback) { + this.textmateRegistry.grammars.forEach(callback); for (const grammarId in this.treeSitterGrammarsById) { - const grammar = this.treeSitterGrammarsById[grammarId] - if (grammar.scopeName) callback(grammar) + const grammar = this.treeSitterGrammarsById[grammarId]; + if (grammar.scopeName) callback(grammar); } } - grammarForId (languageId) { - if (!languageId) return null + grammarForId(languageId) { + if (!languageId) return null; if (this.shouldUseTreeSitterParser(languageId)) { return ( this.treeSitterGrammarsById[languageId] || this.textmateRegistry.grammarForScopeName(languageId) - ) + ); } else { return ( this.textmateRegistry.grammarForScopeName(languageId) || this.treeSitterGrammarsById[languageId] - ) + ); } } @@ -333,10 +357,10 @@ class GrammarRegistry { // * `filePath` A {String} file path. // // Returns a {String} such as `"source.js"`. - grammarOverrideForPath (filePath) { - Grim.deprecate('Use buffer.getLanguageMode().getLanguageId() instead') - const buffer = atom.project.findBufferForPath(filePath) - if (buffer) return this.getAssignedLanguageId(buffer) + grammarOverrideForPath(filePath) { + Grim.deprecate('Use buffer.getLanguageMode().getLanguageId() instead'); + const buffer = atom.project.findBufferForPath(filePath); + if (buffer) return this.getAssignedLanguageId(buffer); } // Deprecated: Set the grammar override for the given file path. @@ -345,12 +369,15 @@ class GrammarRegistry { // * `languageId` A {String} such as `"source.js"`. // // Returns undefined. - setGrammarOverrideForPath (filePath, languageId) { - Grim.deprecate('Use atom.grammars.assignLanguageMode(buffer, languageId) instead') - const buffer = atom.project.findBufferForPath(filePath) + setGrammarOverrideForPath(filePath, languageId) { + Grim.deprecate( + 'Use atom.grammars.assignLanguageMode(buffer, languageId) instead' + ); + const buffer = atom.project.findBufferForPath(filePath); if (buffer) { - const grammar = this.grammarForScopeName(languageId) - if (grammar) this.languageOverridesByBufferId.set(buffer.id, grammar.name) + const grammar = this.grammarForScopeName(languageId); + if (grammar) + this.languageOverridesByBufferId.set(buffer.id, grammar.name); } } @@ -359,35 +386,45 @@ class GrammarRegistry { // * `filePath` A {String} file path. // // Returns undefined. - clearGrammarOverrideForPath (filePath) { - Grim.deprecate('Use atom.grammars.autoAssignLanguageMode(buffer) instead') - const buffer = atom.project.findBufferForPath(filePath) - if (buffer) this.languageOverridesByBufferId.delete(buffer.id) + clearGrammarOverrideForPath(filePath) { + Grim.deprecate('Use atom.grammars.autoAssignLanguageMode(buffer) instead'); + const buffer = atom.project.findBufferForPath(filePath); + if (buffer) this.languageOverridesByBufferId.delete(buffer.id); } - grammarAddedOrUpdated (grammar) { - if (grammar.scopeName && !grammar.id) grammar.id = grammar.scopeName + grammarAddedOrUpdated(grammar) { + if (grammar.scopeName && !grammar.id) grammar.id = grammar.scopeName; this.grammarScoresByBuffer.forEach((score, buffer) => { - const languageMode = buffer.getLanguageMode() - const languageOverride = this.languageOverridesByBufferId.get(buffer.id) + const languageMode = buffer.getLanguageMode(); + const languageOverride = this.languageOverridesByBufferId.get(buffer.id); - if (grammar === buffer.getLanguageMode().grammar || - grammar === this.grammarForId(languageOverride)) { - buffer.setLanguageMode(this.languageModeForGrammarAndBuffer(grammar, buffer)) - return + if ( + grammar === buffer.getLanguageMode().grammar || + grammar === this.grammarForId(languageOverride) + ) { + buffer.setLanguageMode( + this.languageModeForGrammarAndBuffer(grammar, buffer) + ); + return; } else if (!languageOverride) { - const score = this.getGrammarScore(grammar, buffer.getPath(), getGrammarSelectionContent(buffer)) - const currentScore = this.grammarScoresByBuffer.get(buffer) + const score = this.getGrammarScore( + grammar, + buffer.getPath(), + getGrammarSelectionContent(buffer) + ); + const currentScore = this.grammarScoresByBuffer.get(buffer); if (currentScore == null || score > currentScore) { - buffer.setLanguageMode(this.languageModeForGrammarAndBuffer(grammar, buffer)) - this.grammarScoresByBuffer.set(buffer, score) - return + buffer.setLanguageMode( + this.languageModeForGrammarAndBuffer(grammar, buffer) + ); + this.grammarScoresByBuffer.set(buffer, score); + return; } } - languageMode.updateForInjection(grammar) - }) + languageMode.updateForInjection(grammar); + }); } // Extended: Invoke the given callback when a grammar is added to the registry. @@ -396,8 +433,8 @@ class GrammarRegistry { // * `grammar` {Grammar} that was added. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddGrammar (callback) { - return this.textmateRegistry.onDidAddGrammar(callback) + onDidAddGrammar(callback) { + return this.textmateRegistry.onDidAddGrammar(callback); } // Extended: Invoke the given callback when a grammar is updated due to a grammar @@ -407,8 +444,8 @@ class GrammarRegistry { // * `grammar` {Grammar} that was updated. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidUpdateGrammar (callback) { - return this.textmateRegistry.onDidUpdateGrammar(callback) + onDidUpdateGrammar(callback) { + return this.textmateRegistry.onDidUpdateGrammar(callback); } // Experimental: Specify a type of syntax node that may embed other languages. @@ -421,67 +458,72 @@ class GrammarRegistry { // order to determine what language should be embedded. // * `content` A {Function} that is called with syntax nodes of the specified `type` and // returns another syntax node or array of syntax nodes that contain the embedded source code. - addInjectionPoint (grammarId, injectionPoint) { - const grammar = this.treeSitterGrammarsById[grammarId] + addInjectionPoint(grammarId, injectionPoint) { + const grammar = this.treeSitterGrammarsById[grammarId]; if (grammar) { if (grammar.addInjectionPoint) { - grammar.addInjectionPoint(injectionPoint) + grammar.addInjectionPoint(injectionPoint); } else { - grammar.injectionPoints.push(injectionPoint) + grammar.injectionPoints.push(injectionPoint); } } else { this.treeSitterGrammarsById[grammarId] = { injectionPoints: [injectionPoint] - } + }; } return new Disposable(() => { - const grammar = this.treeSitterGrammarsById[grammarId] - grammar.removeInjectionPoint(injectionPoint) - }) + const grammar = this.treeSitterGrammarsById[grammarId]; + grammar.removeInjectionPoint(injectionPoint); + }); } - get nullGrammar () { - return this.textmateRegistry.nullGrammar + get nullGrammar() { + return this.textmateRegistry.nullGrammar; } - get grammars () { - return this.textmateRegistry.grammars + get grammars() { + return this.textmateRegistry.grammars; } - decodeTokens () { - return this.textmateRegistry.decodeTokens.apply(this.textmateRegistry, arguments) + decodeTokens() { + return this.textmateRegistry.decodeTokens.apply( + this.textmateRegistry, + arguments + ); } - grammarForScopeName (scopeName) { - return this.grammarForId(scopeName) + grammarForScopeName(scopeName) { + return this.grammarForId(scopeName); } - addGrammar (grammar) { + addGrammar(grammar) { if (grammar instanceof TreeSitterGrammar) { - const existingParams = this.treeSitterGrammarsById[grammar.scopeName] || {} - if (grammar.scopeName) this.treeSitterGrammarsById[grammar.scopeName] = grammar + const existingParams = + this.treeSitterGrammarsById[grammar.scopeName] || {}; + if (grammar.scopeName) + this.treeSitterGrammarsById[grammar.scopeName] = grammar; if (existingParams.injectionPoints) { for (const injectionPoint of existingParams.injectionPoints) { - grammar.addInjectionPoint(injectionPoint) + grammar.addInjectionPoint(injectionPoint); } } - this.grammarAddedOrUpdated(grammar) - return new Disposable(() => this.removeGrammar(grammar)) + this.grammarAddedOrUpdated(grammar); + return new Disposable(() => this.removeGrammar(grammar)); } else { - return this.textmateRegistry.addGrammar(grammar) + return this.textmateRegistry.addGrammar(grammar); } } - removeGrammar (grammar) { + removeGrammar(grammar) { if (grammar instanceof TreeSitterGrammar) { - delete this.treeSitterGrammarsById[grammar.scopeName] + delete this.treeSitterGrammarsById[grammar.scopeName]; } else { - return this.textmateRegistry.removeGrammar(grammar) + return this.textmateRegistry.removeGrammar(grammar); } } - removeGrammarForScopeName (scopeName) { - return this.textmateRegistry.removeGrammarForScopeName(scopeName) + removeGrammarForScopeName(scopeName) { + return this.textmateRegistry.removeGrammarForScopeName(scopeName); } // Extended: Read a grammar asynchronously and add it to the registry. @@ -490,12 +532,12 @@ class GrammarRegistry { // * `callback` A {Function} to call when loaded with the following arguments: // * `error` An {Error}, may be null. // * `grammar` A {Grammar} or null if an error occured. - loadGrammar (grammarPath, callback) { + loadGrammar(grammarPath, callback) { this.readGrammar(grammarPath, (error, grammar) => { - if (error) return callback(error) - this.addGrammar(grammar) - callback(null, grammar) - }) + if (error) return callback(error); + this.addGrammar(grammar); + callback(null, grammar); + }); } // Extended: Read a grammar synchronously and add it to this registry. @@ -503,10 +545,10 @@ class GrammarRegistry { // * `grammarPath` A {String} absolute file path to a grammar file. // // Returns a {Grammar}. - loadGrammarSync (grammarPath) { - const grammar = this.readGrammarSync(grammarPath) - this.addGrammar(grammar) - return grammar + loadGrammarSync(grammarPath) { + const grammar = this.readGrammarSync(grammarPath); + this.addGrammar(grammar); + return grammar; } // Extended: Read a grammar asynchronously but don't add it to the registry. @@ -517,16 +559,16 @@ class GrammarRegistry { // * `grammar` A {Grammar} or null if an error occured. // // Returns undefined. - readGrammar (grammarPath, callback) { - if (!callback) callback = () => {} + readGrammar(grammarPath, callback) { + if (!callback) callback = () => {}; CSON.readFile(grammarPath, (error, params = {}) => { - if (error) return callback(error) + if (error) return callback(error); try { - callback(null, this.createGrammar(grammarPath, params)) + callback(null, this.createGrammar(grammarPath, params)); } catch (error) { - callback(error) + callback(error); } - }) + }); } // Extended: Read a grammar synchronously but don't add it to the registry. @@ -534,62 +576,68 @@ class GrammarRegistry { // * `grammarPath` A {String} absolute file path to a grammar file. // // Returns a {Grammar}. - readGrammarSync (grammarPath) { - return this.createGrammar(grammarPath, CSON.readFileSync(grammarPath) || {}) + readGrammarSync(grammarPath) { + return this.createGrammar( + grammarPath, + CSON.readFileSync(grammarPath) || {} + ); } - createGrammar (grammarPath, params) { + createGrammar(grammarPath, params) { if (params.type === 'tree-sitter') { - return new TreeSitterGrammar(this, grammarPath, params) + return new TreeSitterGrammar(this, grammarPath, params); } else { - if (typeof params.scopeName !== 'string' || params.scopeName.length === 0) { - throw new Error(`Grammar missing required scopeName property: ${grammarPath}`) + if ( + typeof params.scopeName !== 'string' || + params.scopeName.length === 0 + ) { + throw new Error( + `Grammar missing required scopeName property: ${grammarPath}` + ); } - return this.textmateRegistry.createGrammar(grammarPath, params) + return this.textmateRegistry.createGrammar(grammarPath, params); } } // Extended: Get all the grammars in this registry. // // Returns a non-empty {Array} of {Grammar} instances. - getGrammars () { - return this.textmateRegistry.getGrammars() + getGrammars() { + return this.textmateRegistry.getGrammars(); } - scopeForId (id) { - return this.textmateRegistry.scopeForId(id) + scopeForId(id) { + return this.textmateRegistry.scopeForId(id); } - treeSitterGrammarForLanguageString (languageString) { - let longestMatchLength = 0 - let grammarWithLongestMatch = null + treeSitterGrammarForLanguageString(languageString) { + let longestMatchLength = 0; + let grammarWithLongestMatch = null; for (const id in this.treeSitterGrammarsById) { - const grammar = this.treeSitterGrammarsById[id] + const grammar = this.treeSitterGrammarsById[id]; if (grammar.injectionRegex) { - const match = languageString.match(grammar.injectionRegex) + const match = languageString.match(grammar.injectionRegex); if (match) { - const {length} = match[0] + const { length } = match[0]; if (length > longestMatchLength) { - grammarWithLongestMatch = grammar - longestMatchLength = length + grammarWithLongestMatch = grammar; + longestMatchLength = length; } } } } - return grammarWithLongestMatch + return grammarWithLongestMatch; } - shouldUseTreeSitterParser (languageId) { - return this.config.get( - 'core.useTreeSitterParsers', - {scope: new ScopeDescriptor({scopes: [languageId]})} - ) + shouldUseTreeSitterParser(languageId) { + return this.config.get('core.useTreeSitterParsers', { + scope: new ScopeDescriptor({ scopes: [languageId] }) + }); } -} +}; -function getGrammarSelectionContent (buffer) { - return buffer.getTextInRange(Range( - Point(0, 0), - buffer.positionForCharacterIndex(1024) - )) +function getGrammarSelectionContent(buffer) { + return buffer.getTextInRange( + Range(Point(0, 0), buffer.positionForCharacterIndex(1024)) + ); } diff --git a/src/gutter-container.js b/src/gutter-container.js index cd0c796b2..19d928f10 100644 --- a/src/gutter-container.js +++ b/src/gutter-container.js @@ -1,79 +1,87 @@ -const {Emitter} = require('event-kit') -const Gutter = require('./gutter') +const { Emitter } = require('event-kit'); +const Gutter = require('./gutter'); module.exports = class GutterContainer { - constructor (textEditor) { - this.gutters = [] - this.textEditor = textEditor - this.emitter = new Emitter() + constructor(textEditor) { + this.gutters = []; + this.textEditor = textEditor; + this.emitter = new Emitter(); } - scheduleComponentUpdate () { - this.textEditor.scheduleComponentUpdate() + scheduleComponentUpdate() { + this.textEditor.scheduleComponentUpdate(); } - destroy () { + destroy() { // Create a copy, because `Gutter::destroy` removes the gutter from // GutterContainer's @gutters. - const guttersToDestroy = this.gutters.slice(0) + const guttersToDestroy = this.gutters.slice(0); for (let gutter of guttersToDestroy) { - if (gutter.name !== 'line-number') { gutter.destroy() } + if (gutter.name !== 'line-number') { + gutter.destroy(); + } } - this.gutters = [] - this.emitter.dispose() + this.gutters = []; + this.emitter.dispose(); } - addGutter (options) { - options = options || {} - const gutterName = options.name + addGutter(options) { + options = options || {}; + const gutterName = options.name; if (gutterName === null) { - throw new Error('A name is required to create a gutter.') + throw new Error('A name is required to create a gutter.'); } if (this.gutterWithName(gutterName)) { - throw new Error('Tried to create a gutter with a name that is already in use.') + throw new Error( + 'Tried to create a gutter with a name that is already in use.' + ); } - const newGutter = new Gutter(this, options) + const newGutter = new Gutter(this, options); - let inserted = false + let inserted = false; // Insert the gutter into the gutters array, sorted in ascending order by 'priority'. // This could be optimized, but there are unlikely to be many gutters. for (let i = 0; i < this.gutters.length; i++) { if (this.gutters[i].priority >= newGutter.priority) { - this.gutters.splice(i, 0, newGutter) - inserted = true - break + this.gutters.splice(i, 0, newGutter); + inserted = true; + break; } } if (!inserted) { - this.gutters.push(newGutter) + this.gutters.push(newGutter); } - this.scheduleComponentUpdate() - this.emitter.emit('did-add-gutter', newGutter) - return newGutter + this.scheduleComponentUpdate(); + this.emitter.emit('did-add-gutter', newGutter); + return newGutter; } - getGutters () { - return this.gutters.slice() + getGutters() { + return this.gutters.slice(); } - gutterWithName (name) { + gutterWithName(name) { for (let gutter of this.gutters) { - if (gutter.name === name) { return gutter } + if (gutter.name === name) { + return gutter; + } } - return null + return null; } - observeGutters (callback) { - for (let gutter of this.getGutters()) { callback(gutter) } - return this.onDidAddGutter(callback) + observeGutters(callback) { + for (let gutter of this.getGutters()) { + callback(gutter); + } + return this.onDidAddGutter(callback); } - onDidAddGutter (callback) { - return this.emitter.on('did-add-gutter', callback) + onDidAddGutter(callback) { + return this.emitter.on('did-add-gutter', callback); } - onDidRemoveGutter (callback) { - return this.emitter.on('did-remove-gutter', callback) + onDidRemoveGutter(callback) { + return this.emitter.on('did-remove-gutter', callback); } /* @@ -82,27 +90,28 @@ module.exports = class GutterContainer { // Processes the destruction of the gutter. Throws an error if this gutter is // not within this gutterContainer. - removeGutter (gutter) { - const index = this.gutters.indexOf(gutter) + removeGutter(gutter) { + const index = this.gutters.indexOf(gutter); if (index > -1) { - this.gutters.splice(index, 1) - this.scheduleComponentUpdate() - this.emitter.emit('did-remove-gutter', gutter.name) + this.gutters.splice(index, 1); + this.scheduleComponentUpdate(); + this.emitter.emit('did-remove-gutter', gutter.name); } else { - throw new Error('The given gutter cannot be removed because it is not ' + + throw new Error( + 'The given gutter cannot be removed because it is not ' + 'within this GutterContainer.' - ) + ); } } // The public interface is Gutter::decorateMarker or TextEditor::decorateMarker. - addGutterDecoration (gutter, marker, options) { + addGutterDecoration(gutter, marker, options) { if (gutter.type === 'line-number') { - options.type = 'line-number' + options.type = 'line-number'; } else { - options.type = 'gutter' + options.type = 'gutter'; } - options.gutterName = gutter.name - return this.textEditor.decorateMarker(marker, options) + options.gutterName = gutter.name; + return this.textEditor.decorateMarker(marker, options); } -} +}; diff --git a/src/gutter.js b/src/gutter.js index bd5955b78..c3b6214ee 100644 --- a/src/gutter.js +++ b/src/gutter.js @@ -1,24 +1,25 @@ -const {Emitter} = require('event-kit') +const { Emitter } = require('event-kit'); -const DefaultPriority = -100 +const DefaultPriority = -100; // Extended: Represents a gutter within a {TextEditor}. // // See {TextEditor::addGutter} for information on creating a gutter. module.exports = class Gutter { - constructor (gutterContainer, options) { - this.gutterContainer = gutterContainer - this.name = options && options.name - this.priority = (options && options.priority != null) ? options.priority : DefaultPriority - this.visible = (options && options.visible != null) ? options.visible : true - this.type = (options && options.type != null) ? options.type : 'decorated' - this.labelFn = options && options.labelFn - this.className = options && options.class + constructor(gutterContainer, options) { + this.gutterContainer = gutterContainer; + this.name = options && options.name; + this.priority = + options && options.priority != null ? options.priority : DefaultPriority; + this.visible = options && options.visible != null ? options.visible : true; + this.type = options && options.type != null ? options.type : 'decorated'; + this.labelFn = options && options.labelFn; + this.className = options && options.class; - this.onMouseDown = options && options.onMouseDown - this.onMouseMove = options && options.onMouseMove + this.onMouseDown = options && options.onMouseDown; + this.onMouseMove = options && options.onMouseMove; - this.emitter = new Emitter() + this.emitter = new Emitter(); } /* @@ -26,13 +27,13 @@ module.exports = class Gutter { */ // Essential: Destroys the gutter. - destroy () { + destroy() { if (this.name === 'line-number') { - throw new Error('The line-number gutter cannot be destroyed.') + throw new Error('The line-number gutter cannot be destroyed.'); } else { - this.gutterContainer.removeGutter(this) - this.emitter.emit('did-destroy') - this.emitter.dispose() + this.gutterContainer.removeGutter(this); + this.emitter.emit('did-destroy'); + this.emitter.dispose(); } } @@ -46,8 +47,8 @@ module.exports = class Gutter { // * `gutter` The gutter whose visibility changed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeVisible (callback) { - return this.emitter.on('did-change-visible', callback) + onDidChangeVisible(callback) { + return this.emitter.on('did-change-visible', callback); } // Essential: Calls your `callback` when the gutter is destroyed. @@ -55,8 +56,8 @@ module.exports = class Gutter { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } /* @@ -64,28 +65,28 @@ module.exports = class Gutter { */ // Essential: Hide the gutter. - hide () { + hide() { if (this.visible) { - this.visible = false - this.gutterContainer.scheduleComponentUpdate() - this.emitter.emit('did-change-visible', this) + this.visible = false; + this.gutterContainer.scheduleComponentUpdate(); + this.emitter.emit('did-change-visible', this); } } // Essential: Show the gutter. - show () { + show() { if (!this.visible) { - this.visible = true - this.gutterContainer.scheduleComponentUpdate() - this.emitter.emit('did-change-visible', this) + this.visible = true; + this.gutterContainer.scheduleComponentUpdate(); + this.emitter.emit('did-change-visible', this); } } // Essential: Determine whether the gutter is visible. // // Returns a {Boolean}. - isVisible () { - return this.visible + isVisible() { + return this.visible; } // Essential: Add a decoration that tracks a {DisplayMarker}. When the marker moves, @@ -102,12 +103,12 @@ module.exports = class Gutter { // gutter, `'gutter'` otherwise. This cannot be overridden. // // Returns a {Decoration} object - decorateMarker (marker, options) { - return this.gutterContainer.addGutterDecoration(this, marker, options) + decorateMarker(marker, options) { + return this.gutterContainer.addGutterDecoration(this, marker, options); } - getElement () { - if (this.element == null) this.element = document.createElement('div') - return this.element + getElement() { + if (this.element == null) this.element = document.createElement('div'); + return this.element; } -} +}; diff --git a/src/history-manager.js b/src/history-manager.js index e4651d9d9..ba9f291cc 100644 --- a/src/history-manager.js +++ b/src/history-manager.js @@ -1,4 +1,4 @@ -const {Emitter, CompositeDisposable} = require('event-kit') +const { Emitter, CompositeDisposable } = require('event-kit'); // Extended: History manager for remembering which projects have been opened. // @@ -6,24 +6,32 @@ const {Emitter, CompositeDisposable} = require('event-kit') // // The project history is used to enable the 'Reopen Project' menu. class HistoryManager { - constructor ({project, commands, stateStore}) { - this.stateStore = stateStore - 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)}, false)) - this.disposables.add(project.onDidChangePaths((projectPaths) => this.addProject(projectPaths))) + constructor({ project, commands, stateStore }) { + this.stateStore = stateStore; + 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) }, + false + ) + ); + this.disposables.add( + project.onDidChangePaths(projectPaths => this.addProject(projectPaths)) + ); } - destroy () { - this.disposables.dispose() + destroy() { + this.disposables.dispose(); } // Public: Obtain a list of previously opened projects. // // Returns an {Array} of {HistoryProject} objects, most recent first. - getProjects () { - return this.projects.map(p => new HistoryProject(p.paths, p.lastOpened)) + getProjects() { + return this.projects.map(p => new HistoryProject(p.paths, p.lastOpened)); } // Public: Clear all projects from the history. @@ -33,10 +41,10 @@ class HistoryManager { // // Return a {Promise} that resolves when the history has been successfully // cleared. - async clearProjects () { - this.projects = [] - await this.saveState() - this.didChangeProjects() + async clearProjects() { + this.projects = []; + await this.saveState(); + this.didChangeProjects(); } // Public: Invoke the given callback when the list of projects changes. @@ -44,87 +52,100 @@ class HistoryManager { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeProjects (callback) { - return this.emitter.on('did-change-projects', callback) + onDidChangeProjects(callback) { + return this.emitter.on('did-change-projects', callback); } - didChangeProjects (args = {reloaded: false}) { - this.emitter.emit('did-change-projects', args) + didChangeProjects(args = { reloaded: false }) { + this.emitter.emit('did-change-projects', args); } - async addProject (paths, lastOpened) { - if (paths.length === 0) return + async addProject(paths, lastOpened) { + if (paths.length === 0) return; - let project = this.getProject(paths) + let project = this.getProject(paths); if (!project) { - project = new HistoryProject(paths) - this.projects.push(project) + project = new HistoryProject(paths); + this.projects.push(project); } - project.lastOpened = lastOpened || new Date() - this.projects.sort((a, b) => b.lastOpened - a.lastOpened) + project.lastOpened = lastOpened || new Date(); + this.projects.sort((a, b) => b.lastOpened - a.lastOpened); - await this.saveState() - this.didChangeProjects() + await this.saveState(); + this.didChangeProjects(); } - async removeProject (paths) { - if (paths.length === 0) return + async removeProject(paths) { + if (paths.length === 0) return; - let project = this.getProject(paths) - if (!project) return + let project = this.getProject(paths); + if (!project) return; - let index = this.projects.indexOf(project) - this.projects.splice(index, 1) + let index = this.projects.indexOf(project); + this.projects.splice(index, 1); - await this.saveState() - this.didChangeProjects() + await this.saveState(); + this.didChangeProjects(); } - getProject (paths) { + getProject(paths) { for (var i = 0; i < this.projects.length; i++) { if (arrayEquivalent(paths, this.projects[i].paths)) { - return this.projects[i] + return this.projects[i]; } } - return null + return null; } - async loadState () { - const history = await this.stateStore.load('history-manager') + 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}) + 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 = [] + this.projects = []; } } - async saveState () { - const projects = this.projects.map(p => ({paths: p.paths, lastOpened: p.lastOpened})) - await this.stateStore.save('history-manager', {projects}) + async saveState() { + const projects = this.projects.map(p => ({ + paths: p.paths, + lastOpened: p.lastOpened + })); + await this.stateStore.save('history-manager', { projects }); } } -function arrayEquivalent (a, b) { - if (a.length !== b.length) return false +function arrayEquivalent(a, b) { + if (a.length !== b.length) return false; for (var i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false + if (a[i] !== b[i]) return false; } - return true + return true; } class HistoryProject { - constructor (paths, lastOpened) { - this.paths = paths - this.lastOpened = lastOpened || new Date() + constructor(paths, lastOpened) { + this.paths = paths; + this.lastOpened = lastOpened || new Date(); } - set paths (paths) { this._paths = paths } - get paths () { return this._paths } + set paths(paths) { + this._paths = paths; + } + get paths() { + return this._paths; + } - set lastOpened (lastOpened) { this._lastOpened = lastOpened } - get lastOpened () { return this._lastOpened } + set lastOpened(lastOpened) { + this._lastOpened = lastOpened; + } + get lastOpened() { + return this._lastOpened; + } } -module.exports = {HistoryManager, HistoryProject} +module.exports = { HistoryManager, HistoryProject }; diff --git a/src/initialize-application-window.js b/src/initialize-application-window.js index d34c0a8e6..d8388ab9f 100644 --- a/src/initialize-application-window.js +++ b/src/initialize-application-window.js @@ -1,100 +1,100 @@ -const AtomEnvironment = require('./atom-environment') -const ApplicationDelegate = require('./application-delegate') -const Clipboard = require('./clipboard') -const TextEditor = require('./text-editor') +const AtomEnvironment = require('./atom-environment'); +const ApplicationDelegate = require('./application-delegate'); +const Clipboard = require('./clipboard'); +const TextEditor = require('./text-editor'); -require('./text-editor-component') -require('./file-system-blob-store') -require('./native-compile-cache') -require('./compile-cache') -require('./module-cache') +require('./text-editor-component'); +require('./file-system-blob-store'); +require('./native-compile-cache'); +require('./compile-cache'); +require('./module-cache'); if (global.isGeneratingSnapshot) { - 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('dalek') - require('find-and-replace') - require('fuzzy-finder') - require('github') - require('git-diff') - require('go-to-line') - require('grammar-selector') - require('image-view') - require('incompatible-packages') - require('keybinding-resolver') - require('language-c') - require('language-html') - require('language-javascript') - require('language-ruby') - require('language-typescript') - 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('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('dalek'); + require('find-and-replace'); + require('fuzzy-finder'); + require('github'); + require('git-diff'); + require('go-to-line'); + require('grammar-selector'); + require('image-view'); + require('incompatible-packages'); + require('keybinding-resolver'); + require('language-c'); + require('language-html'); + require('language-javascript'); + require('language-ruby'); + require('language-typescript'); + 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'); } -const clipboard = new Clipboard() -TextEditor.setClipboard(clipboard) -TextEditor.viewForItem = item => atom.views.getView(item) +const clipboard = new Clipboard(); +TextEditor.setClipboard(clipboard); +TextEditor.viewForItem = item => atom.views.getView(item); global.atom = new AtomEnvironment({ clipboard, applicationDelegate: new ApplicationDelegate(), enablePersistence: true -}) +}); -TextEditor.setScheduler(global.atom.views) -global.atom.preloadPackages() +TextEditor.setScheduler(global.atom.views); +global.atom.preloadPackages(); // Like sands through the hourglass, so are the days of our lives. -module.exports = function ({ blobStore }) { - const { updateProcessEnv } = require('./update-process-env') - const path = require('path') - require('./window') - const getWindowLoadSettings = require('./get-window-load-settings') - const { ipcRenderer } = require('electron') - const { resourcePath, devMode } = getWindowLoadSettings() - require('./electron-shims') +module.exports = function({ blobStore }) { + const { updateProcessEnv } = require('./update-process-env'); + const path = require('path'); + require('./window'); + const getWindowLoadSettings = require('./get-window-load-settings'); + const { ipcRenderer } = require('electron'); + const { resourcePath, devMode } = getWindowLoadSettings(); + require('./electron-shims'); // Add application-specific exports to module search path. - const exportsPath = path.join(resourcePath, 'exports') - require('module').globalPaths.push(exportsPath) - process.env.NODE_PATH = exportsPath + const exportsPath = path.join(resourcePath, 'exports'); + require('module').globalPaths.push(exportsPath); + process.env.NODE_PATH = exportsPath; // Make React faster if (!devMode && process.env.NODE_ENV == null) { - process.env.NODE_ENV = 'production' + process.env.NODE_ENV = 'production'; } global.atom.initialize({ @@ -103,16 +103,16 @@ module.exports = function ({ blobStore }) { blobStore, configDirPath: process.env.ATOM_HOME, env: process.env - }) + }); - return global.atom.startEditorWindow().then(function () { + return global.atom.startEditorWindow().then(function() { // Workaround for focus getting cleared upon window creation - const windowFocused = function () { - window.removeEventListener('focus', windowFocused) - setTimeout(() => document.querySelector('atom-workspace').focus(), 0) - } - window.addEventListener('focus', windowFocused) + const windowFocused = function() { + window.removeEventListener('focus', windowFocused); + setTimeout(() => document.querySelector('atom-workspace').focus(), 0); + }; + window.addEventListener('focus', windowFocused); - ipcRenderer.on('environment', (event, env) => updateProcessEnv(env)) - }) -} + ipcRenderer.on('environment', (event, env) => updateProcessEnv(env)); + }); +}; diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index 131785454..cc438137e 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -1,60 +1,76 @@ -const {remote} = require('electron') -const path = require('path') -const ipcHelpers = require('./ipc-helpers') -const util = require('util') +const { remote } = require('electron'); +const path = require('path'); +const ipcHelpers = require('./ipc-helpers'); +const util = require('util'); -module.exports = async function () { - const getWindowLoadSettings = require('./get-window-load-settings') - const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() +module.exports = async function() { + const getWindowLoadSettings = require('./get-window-load-settings'); + const { + test, + headless, + resourcePath, + benchmarkPaths + } = getWindowLoadSettings(); try { - const Clipboard = require('../src/clipboard') - const ApplicationDelegate = require('../src/application-delegate') - const AtomEnvironment = require('../src/atom-environment') - const TextEditor = require('../src/text-editor') - require('./electron-shims') + const Clipboard = require('../src/clipboard'); + const ApplicationDelegate = require('../src/application-delegate'); + const AtomEnvironment = require('../src/atom-environment'); + const TextEditor = require('../src/text-editor'); + require('./electron-shims'); - const exportsPath = path.join(resourcePath, 'exports') - require('module').globalPaths.push(exportsPath) // Add 'exports' to module search path. - process.env.NODE_PATH = exportsPath // Set NODE_PATH env variable since tasks may need it. + const exportsPath = path.join(resourcePath, 'exports'); + require('module').globalPaths.push(exportsPath); // Add 'exports' to module search path. + process.env.NODE_PATH = exportsPath; // Set NODE_PATH env variable since tasks may need it. - document.title = 'Benchmarks' + document.title = 'Benchmarks'; // Allow `document.title` to be assigned in benchmarks without actually changing the window title. - let documentTitle = null + let documentTitle = null; Object.defineProperty(document, 'title', { - get () { return documentTitle }, - set (title) { documentTitle = title } - }) - - window.addEventListener('keydown', (event) => { - // Reload: cmd-r / ctrl-r - if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) { - ipcHelpers.call('window-method', 'reload') + get() { + return documentTitle; + }, + set(title) { + documentTitle = title; } + }); - // Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows) - if (event.keyCode === 73) { - const isDarwin = process.platform === 'darwin' - if ((isDarwin && event.metaKey && event.altKey) || (!isDarwin && event.ctrlKey && event.shiftKey)) { - ipcHelpers.call('window-method', 'toggleDevTools') + window.addEventListener( + 'keydown', + event => { + // Reload: cmd-r / ctrl-r + if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) { + ipcHelpers.call('window-method', 'reload'); } - } - // Close: cmd-w / ctrl-w - if ((event.metaKey || event.ctrlKey) && event.keyCode === 87) { - ipcHelpers.call('window-method', 'close') - } + // Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows) + if (event.keyCode === 73) { + const isDarwin = process.platform === 'darwin'; + if ( + (isDarwin && event.metaKey && event.altKey) || + (!isDarwin && event.ctrlKey && event.shiftKey) + ) { + ipcHelpers.call('window-method', 'toggleDevTools'); + } + } - // Copy: cmd-c / ctrl-c - if ((event.metaKey || event.ctrlKey) && event.keyCode === 67) { - ipcHelpers.call('window-method', 'copy') - } - }, true) + // Close: cmd-w / ctrl-w + if ((event.metaKey || event.ctrlKey) && event.keyCode === 87) { + ipcHelpers.call('window-method', 'close'); + } - const clipboard = new Clipboard() - TextEditor.setClipboard(clipboard) - TextEditor.viewForItem = (item) => atom.views.getView(item) + // Copy: cmd-c / ctrl-c + if ((event.metaKey || event.ctrlKey) && event.keyCode === 67) { + ipcHelpers.call('window-method', 'copy'); + } + }, + true + ); - const applicationDelegate = new ApplicationDelegate() + const clipboard = new Clipboard(); + TextEditor.setClipboard(clipboard); + TextEditor.viewForItem = item => atom.views.getView(item); + + const applicationDelegate = new ApplicationDelegate(); const environmentParams = { applicationDelegate, window, @@ -62,52 +78,52 @@ module.exports = async function () { clipboard, configDirPath: process.env.ATOM_HOME, enablePersistence: false - } - global.atom = new AtomEnvironment(environmentParams) - global.atom.initialize(environmentParams) + }; + global.atom = new AtomEnvironment(environmentParams); + global.atom.initialize(environmentParams); // Prevent benchmarks from modifying application menus - global.atom.menu.sendToBrowserProcess = function () { } + global.atom.menu.sendToBrowserProcess = function() {}; if (headless) { Object.defineProperties(process, { stdout: { value: remote.process.stdout }, stderr: { value: remote.process.stderr } - }) + }); - console.log = function (...args) { - const formatted = util.format(...args) - process.stdout.write(formatted + '\n') - } - console.warn = function (...args) { - const formatted = util.format(...args) - process.stderr.write(formatted + '\n') - } - console.error = function (...args) { - const formatted = util.format(...args) - process.stderr.write(formatted + '\n') - } + console.log = function(...args) { + const formatted = util.format(...args); + process.stdout.write(formatted + '\n'); + }; + console.warn = function(...args) { + const formatted = util.format(...args); + process.stderr.write(formatted + '\n'); + }; + console.error = function(...args) { + const formatted = util.format(...args); + process.stderr.write(formatted + '\n'); + }; } else { - remote.getCurrentWindow().show() + remote.getCurrentWindow().show(); } - const benchmarkRunner = require('../benchmarks/benchmark-runner') - const statusCode = await benchmarkRunner({test, benchmarkPaths}) + const benchmarkRunner = require('../benchmarks/benchmark-runner'); + const statusCode = await benchmarkRunner({ test, benchmarkPaths }); if (headless) { - exitWithStatusCode(statusCode) + exitWithStatusCode(statusCode); } } catch (error) { if (headless) { - console.error(error.stack || error) - exitWithStatusCode(1) + console.error(error.stack || error); + exitWithStatusCode(1); } else { - ipcHelpers.call('window-method', 'openDevTools') - throw error + ipcHelpers.call('window-method', 'openDevTools'); + throw error; } } -} +}; -function exitWithStatusCode (statusCode) { - remote.app.emit('will-quit') - remote.process.exit(statusCode) +function exitWithStatusCode(statusCode) { + remote.app.emit('will-quit'); + remote.process.exit(statusCode); } diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js index bd8c8c933..3627dff43 100644 --- a/src/ipc-helpers.js +++ b/src/ipc-helpers.js @@ -1,45 +1,49 @@ -const Disposable = require('event-kit').Disposable -let ipcRenderer = null -let ipcMain = null -let BrowserWindow = null +const Disposable = require('event-kit').Disposable; +let ipcRenderer = null; +let ipcMain = null; +let BrowserWindow = null; -let nextResponseChannelId = 0 +let nextResponseChannelId = 0; -exports.on = function (emitter, eventName, callback) { - emitter.on(eventName, callback) - return new Disposable(() => emitter.removeListener(eventName, callback)) -} +exports.on = function(emitter, eventName, callback) { + emitter.on(eventName, callback); + return new Disposable(() => emitter.removeListener(eventName, callback)); +}; -exports.call = function (channel, ...args) { +exports.call = function(channel, ...args) { if (!ipcRenderer) { - ipcRenderer = require('electron').ipcRenderer - ipcRenderer.setMaxListeners(20) + ipcRenderer = require('electron').ipcRenderer; + ipcRenderer.setMaxListeners(20); } - const responseChannel = `ipc-helpers-response-${nextResponseChannelId++}` + const responseChannel = `ipc-helpers-response-${nextResponseChannelId++}`; return new Promise(resolve => { ipcRenderer.on(responseChannel, (event, result) => { - ipcRenderer.removeAllListeners(responseChannel) - resolve(result) - }) + ipcRenderer.removeAllListeners(responseChannel); + resolve(result); + }); - ipcRenderer.send(channel, responseChannel, ...args) - }) -} + ipcRenderer.send(channel, responseChannel, ...args); + }); +}; -exports.respondTo = function (channel, callback) { +exports.respondTo = function(channel, callback) { if (!ipcMain) { - const electron = require('electron') - ipcMain = electron.ipcMain - BrowserWindow = electron.BrowserWindow + const electron = require('electron'); + ipcMain = electron.ipcMain; + BrowserWindow = electron.BrowserWindow; } - return exports.on(ipcMain, channel, async (event, responseChannel, ...args) => { - const browserWindow = BrowserWindow.fromWebContents(event.sender) - const result = await callback(browserWindow, ...args) - if (!event.sender.isDestroyed()) { - event.sender.send(responseChannel, result) + return exports.on( + ipcMain, + channel, + async (event, responseChannel, ...args) => { + const browserWindow = BrowserWindow.fromWebContents(event.sender); + const result = await callback(browserWindow, ...args); + if (!event.sender.isDestroyed()) { + event.sender.send(responseChannel, result); + } } - }) -} + ); +}; diff --git a/src/item-registry.js b/src/item-registry.js index 80c83c701..5d69a6405 100644 --- a/src/item-registry.js +++ b/src/item-registry.js @@ -1,21 +1,22 @@ -module.exports = -class ItemRegistry { - constructor () { - this.items = new WeakSet() +module.exports = class ItemRegistry { + constructor() { + this.items = new WeakSet(); } - addItem (item) { + addItem(item) { if (this.hasItem(item)) { - throw new Error(`The workspace can only contain one instance of item ${item}`) + throw new Error( + `The workspace can only contain one instance of item ${item}` + ); } - return this.items.add(item) + return this.items.add(item); } - removeItem (item) { - return this.items.delete(item) + removeItem(item) { + return this.items.delete(item); } - hasItem (item) { - return this.items.has(item) + hasItem(item) { + return this.items.has(item); } -} +}; diff --git a/src/main-process/application-menu.js b/src/main-process/application-menu.js index 2a46f06f4..15834ef2a 100644 --- a/src/main-process/application-menu.js +++ b/src/main-process/application-menu.js @@ -1,19 +1,20 @@ -const {app, Menu} = require('electron') -const _ = require('underscore-plus') -const MenuHelpers = require('../menu-helpers') +const { app, Menu } = require('electron'); +const _ = require('underscore-plus'); +const MenuHelpers = require('../menu-helpers'); // Used to manage the global application menu. // // It's created by {AtomApplication} upon instantiation and used to add, remove // and maintain the state of all menu items. -module.exports = -class ApplicationMenu { - constructor (version, autoUpdateManager) { - this.version = version - this.autoUpdateManager = autoUpdateManager - this.windowTemplates = new WeakMap() - this.setActiveTemplate(this.getDefaultTemplate()) - this.autoUpdateManager.on('state-changed', state => this.showUpdateMenuItem(state)) +module.exports = class ApplicationMenu { + constructor(version, autoUpdateManager) { + this.version = version; + this.autoUpdateManager = autoUpdateManager; + this.windowTemplates = new WeakMap(); + this.setActiveTemplate(this.getDefaultTemplate()); + this.autoUpdateManager.on('state-changed', state => + this.showUpdateMenuItem(state) + ); } // Public: Updates the entire menu with the given keybindings. @@ -22,41 +23,42 @@ class ApplicationMenu { // template - The Object which describes the menu to display. // keystrokesByCommand - An Object where the keys are commands and the values // are Arrays containing the keystroke. - update (window, template, keystrokesByCommand) { - this.translateTemplate(template, keystrokesByCommand) - this.substituteVersion(template) - this.windowTemplates.set(window, template) - if (window === this.lastFocusedWindow) return this.setActiveTemplate(template) + update(window, template, keystrokesByCommand) { + this.translateTemplate(template, keystrokesByCommand); + this.substituteVersion(template); + this.windowTemplates.set(window, template); + if (window === this.lastFocusedWindow) + return this.setActiveTemplate(template); } - setActiveTemplate (template) { + setActiveTemplate(template) { if (!_.isEqual(template, this.activeTemplate)) { - this.activeTemplate = template - this.menu = Menu.buildFromTemplate(_.deepClone(template)) - Menu.setApplicationMenu(this.menu) + this.activeTemplate = template; + this.menu = Menu.buildFromTemplate(_.deepClone(template)); + Menu.setApplicationMenu(this.menu); } - return this.showUpdateMenuItem(this.autoUpdateManager.getState()) + return this.showUpdateMenuItem(this.autoUpdateManager.getState()); } // Register a BrowserWindow with this application menu. - addWindow (window) { - if (this.lastFocusedWindow == null) this.lastFocusedWindow = window + addWindow(window) { + if (this.lastFocusedWindow == null) this.lastFocusedWindow = window; const focusHandler = () => { - this.lastFocusedWindow = window - const template = this.windowTemplates.get(window) - if (template) this.setActiveTemplate(template) - } + this.lastFocusedWindow = window; + const template = this.windowTemplates.get(window); + if (template) this.setActiveTemplate(template); + }; - window.on('focus', focusHandler) + window.on('focus', focusHandler); window.once('closed', () => { - if (window === this.lastFocusedWindow) this.lastFocusedWindow = null - this.windowTemplates.delete(window) - window.removeListener('focus', focusHandler) - }) + if (window === this.lastFocusedWindow) this.lastFocusedWindow = null; + this.windowTemplates.delete(window); + window.removeListener('focus', focusHandler); + }); - this.enableWindowSpecificItems(true) + this.enableWindowSpecificItems(true); } // Flattens the given menu and submenu items into an single Array. @@ -64,15 +66,16 @@ class ApplicationMenu { // menu - A complete menu configuration object for atom-shell's menu API. // // Returns an Array of native menu items. - flattenMenuItems (menu) { - const object = menu.items || {} - let items = [] + flattenMenuItems(menu) { + const object = menu.items || {}; + let items = []; for (let index in object) { - const item = object[index] - items.push(item) - if (item.submenu) items = items.concat(this.flattenMenuItems(item.submenu)) + const item = object[index]; + items.push(item); + if (item.submenu) + items = items.concat(this.flattenMenuItems(item.submenu)); } - return items + return items; } // Flattens the given menu template into an single Array. @@ -80,111 +83,131 @@ class ApplicationMenu { // template - An object describing the menu item. // // Returns an Array of native menu items. - flattenMenuTemplate (template) { - let items = [] + flattenMenuTemplate(template) { + let items = []; for (let item of template) { - items.push(item) - if (item.submenu) items = items.concat(this.flattenMenuTemplate(item.submenu)) + items.push(item); + if (item.submenu) + items = items.concat(this.flattenMenuTemplate(item.submenu)); } - return items + return items; } // Public: Used to make all window related menu items are active. // // enable - If true enables all window specific items, if false disables all // window specific items. - enableWindowSpecificItems (enable) { + enableWindowSpecificItems(enable) { for (let item of this.flattenMenuItems(this.menu)) { - if (item.metadata && item.metadata.windowSpecific) item.enabled = enable + if (item.metadata && item.metadata.windowSpecific) item.enabled = enable; } } // Replaces VERSION with the current version. - substituteVersion (template) { - let item = this.flattenMenuTemplate(template).find(({label}) => label === 'VERSION') - if (item) item.label = `Version ${this.version}` + substituteVersion(template) { + let item = this.flattenMenuTemplate(template).find( + ({ label }) => label === 'VERSION' + ); + if (item) item.label = `Version ${this.version}`; } // Sets the proper visible state the update menu items - showUpdateMenuItem (state) { - const items = this.flattenMenuItems(this.menu) - const checkForUpdateItem = items.find(({label}) => label === 'Check for Update') - const checkingForUpdateItem = items.find(({label}) => label === 'Checking for Update') - const downloadingUpdateItem = items.find(({label}) => label === 'Downloading Update') - const installUpdateItem = items.find(({label}) => label === 'Restart and Install Update') + showUpdateMenuItem(state) { + const items = this.flattenMenuItems(this.menu); + const checkForUpdateItem = items.find( + ({ label }) => label === 'Check for Update' + ); + const checkingForUpdateItem = items.find( + ({ label }) => label === 'Checking for Update' + ); + const downloadingUpdateItem = items.find( + ({ label }) => label === 'Downloading Update' + ); + const installUpdateItem = items.find( + ({ label }) => label === 'Restart and Install Update' + ); - if (!checkForUpdateItem || !checkingForUpdateItem || - !downloadingUpdateItem || !installUpdateItem) return + if ( + !checkForUpdateItem || + !checkingForUpdateItem || + !downloadingUpdateItem || + !installUpdateItem + ) + return; - checkForUpdateItem.visible = false - checkingForUpdateItem.visible = false - downloadingUpdateItem.visible = false - installUpdateItem.visible = false + checkForUpdateItem.visible = false; + checkingForUpdateItem.visible = false; + downloadingUpdateItem.visible = false; + installUpdateItem.visible = false; switch (state) { case 'idle': case 'error': case 'no-update-available': - checkForUpdateItem.visible = true - break + checkForUpdateItem.visible = true; + break; case 'checking': - checkingForUpdateItem.visible = true - break + checkingForUpdateItem.visible = true; + break; case 'downloading': - downloadingUpdateItem.visible = true - break + downloadingUpdateItem.visible = true; + break; case 'update-available': - installUpdateItem.visible = true - break + installUpdateItem.visible = true; + break; } } // Default list of menu items. // // Returns an Array of menu item Objects. - getDefaultTemplate () { - return [{ - label: 'Atom', - submenu: [ - { - label: 'Check for Update', - metadata: {autoUpdate: true} - }, - { - label: 'Reload', - accelerator: 'Command+R', - click: () => { - const window = this.focusedWindow() - if (window) window.reload() + getDefaultTemplate() { + return [ + { + label: 'Atom', + submenu: [ + { + label: 'Check for Update', + metadata: { autoUpdate: true } + }, + { + label: 'Reload', + accelerator: 'Command+R', + click: () => { + const window = this.focusedWindow(); + if (window) window.reload(); + } + }, + { + label: 'Close Window', + accelerator: 'Command+Shift+W', + click: () => { + const window = this.focusedWindow(); + if (window) window.close(); + } + }, + { + label: 'Toggle Dev Tools', + accelerator: 'Command+Alt+I', + click: () => { + const window = this.focusedWindow(); + if (window) window.toggleDevTools(); + } + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: () => app.quit() } - }, - { - label: 'Close Window', - accelerator: 'Command+Shift+W', - click: () => { - const window = this.focusedWindow() - if (window) window.close() - } - }, - { - label: 'Toggle Dev Tools', - accelerator: 'Command+Alt+I', - click: () => { - const window = this.focusedWindow() - if (window) window.toggleDevTools() - } - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: () => app.quit() - } - ] - }] + ] + } + ]; } - focusedWindow () { - return global.atomApplication.getAllWindows().find(window => window.isFocused()) + focusedWindow() { + return global.atomApplication + .getAllWindows() + .find(window => window.isFocused()); } // Combines a menu template with the appropriate keystroke. @@ -195,19 +218,24 @@ class ApplicationMenu { // are Arrays containing the keystroke. // // Returns a complete menu configuration object for atom-shell's menu API. - translateTemplate (template, keystrokesByCommand) { + translateTemplate(template, keystrokesByCommand) { template.forEach(item => { - if (item.metadata == null) item.metadata = {} + if (item.metadata == null) item.metadata = {}; if (item.command) { - item.accelerator = this.acceleratorForCommand(item.command, keystrokesByCommand) - item.click = () => global.atomApplication.sendCommand(item.command, item.commandDetail) + item.accelerator = this.acceleratorForCommand( + item.command, + keystrokesByCommand + ); + item.click = () => + global.atomApplication.sendCommand(item.command, item.commandDetail); if (!/^application:/.test(item.command)) { - item.metadata.windowSpecific = true + item.metadata.windowSpecific = true; } } - if (item.submenu) this.translateTemplate(item.submenu, keystrokesByCommand) - }) - return template + if (item.submenu) + this.translateTemplate(item.submenu, keystrokesByCommand); + }); + return template; } // Determine the accelerator for a given command. @@ -218,8 +246,9 @@ class ApplicationMenu { // // Returns a String containing the keystroke in a format that can be interpreted // by Electron to provide nice icons where available. - acceleratorForCommand (command, keystrokesByCommand) { - const firstKeystroke = keystrokesByCommand[command] && keystrokesByCommand[command][0] - return MenuHelpers.acceleratorForKeystroke(firstKeystroke) + acceleratorForCommand(command, keystrokesByCommand) { + const firstKeystroke = + keystrokesByCommand[command] && keystrokesByCommand[command][0]; + return MenuHelpers.acceleratorForKeystroke(firstKeystroke); } -} +}; diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index e66509e5e..fde5b2498 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -1,56 +1,65 @@ -const AtomWindow = require('./atom-window') -const ApplicationMenu = require('./application-menu') -const AtomProtocolHandler = require('./atom-protocol-handler') -const AutoUpdateManager = require('./auto-update-manager') -const StorageFolder = require('../storage-folder') -const Config = require('../config') -const ConfigFile = require('../config-file') -const FileRecoveryService = require('./file-recovery-service') -const StartupTime = require('../startup-time') -const ipcHelpers = require('../ipc-helpers') -const {BrowserWindow, Menu, app, clipboard, dialog, ipcMain, shell, screen} = require('electron') -const {CompositeDisposable, Disposable} = require('event-kit') -const crypto = require('crypto') -const fs = require('fs-plus') -const path = require('path') -const os = require('os') -const net = require('net') -const url = require('url') -const {promisify} = require('util') -const {EventEmitter} = require('events') -const _ = require('underscore-plus') -let FindParentDir = null -let Resolve = null -const ConfigSchema = require('../config-schema') +const AtomWindow = require('./atom-window'); +const ApplicationMenu = require('./application-menu'); +const AtomProtocolHandler = require('./atom-protocol-handler'); +const AutoUpdateManager = require('./auto-update-manager'); +const StorageFolder = require('../storage-folder'); +const Config = require('../config'); +const ConfigFile = require('../config-file'); +const FileRecoveryService = require('./file-recovery-service'); +const StartupTime = require('../startup-time'); +const ipcHelpers = require('../ipc-helpers'); +const { + BrowserWindow, + Menu, + app, + clipboard, + dialog, + ipcMain, + shell, + screen +} = require('electron'); +const { CompositeDisposable, Disposable } = require('event-kit'); +const crypto = require('crypto'); +const fs = require('fs-plus'); +const path = require('path'); +const os = require('os'); +const net = require('net'); +const url = require('url'); +const { promisify } = require('util'); +const { EventEmitter } = require('events'); +const _ = require('underscore-plus'); +let FindParentDir = null; +let Resolve = null; +const ConfigSchema = require('../config-schema'); -const LocationSuffixRegExp = /(:\d+)(:\d+)?$/ +const LocationSuffixRegExp = /(:\d+)(:\d+)?$/; // Increment this when changing the serialization format of `${ATOM_HOME}/storage/application.json` used by // AtomApplication::saveCurrentWindowOptions() and AtomApplication::loadPreviousWindowOptions() in a backward- // incompatible way. -const APPLICATION_STATE_VERSION = '1' +const APPLICATION_STATE_VERSION = '1'; const getDefaultPath = () => { - const editor = atom.workspace.getActiveTextEditor() + const editor = atom.workspace.getActiveTextEditor(); if (!editor || !editor.getPath()) { - return + return; } - const paths = atom.project.getPaths() + const paths = atom.project.getPaths(); if (paths) { - return paths[0] + return paths[0]; } -} +}; -const getSocketSecretPath = (atomVersion) => { - const {username} = os.userInfo() - const atomHome = path.resolve(process.env.ATOM_HOME) +const getSocketSecretPath = atomVersion => { + const { username } = os.userInfo(); + const atomHome = path.resolve(process.env.ATOM_HOME); - return path.join(atomHome, `.atom-socket-secret-${username}-${atomVersion}`) -} + return path.join(atomHome, `.atom-socket-secret-${username}-${atomVersion}`); +}; -const getSocketPath = (socketSecret) => { +const getSocketPath = socketSecret => { if (!socketSecret) { - return null + return null; } // Hash the secret to create the socket name to not expose it. @@ -58,264 +67,300 @@ const getSocketPath = (socketSecret) => { .createHmac('sha256', socketSecret) .update('socketName') .digest('hex') - .substr(0, 12) + .substr(0, 12); if (process.platform === 'win32') { - return `\\\\.\\pipe\\atom-${socketName}-sock` + return `\\\\.\\pipe\\atom-${socketName}-sock`; } else { - return path.join(os.tmpdir(), `atom-${socketName}.sock`) + return path.join(os.tmpdir(), `atom-${socketName}.sock`); } -} +}; -const getExistingSocketSecret = (atomVersion) => { - const socketSecretPath = getSocketSecretPath(atomVersion) +const getExistingSocketSecret = atomVersion => { + const socketSecretPath = getSocketSecretPath(atomVersion); if (!fs.existsSync(socketSecretPath)) { - return null + return null; } - return fs.readFileSync(socketSecretPath, 'utf8') -} + return fs.readFileSync(socketSecretPath, 'utf8'); +}; -const getRandomBytes = promisify(crypto.randomBytes) -const writeFile = promisify(fs.writeFile) +const getRandomBytes = promisify(crypto.randomBytes); +const writeFile = promisify(fs.writeFile); -const createSocketSecret = async (atomVersion) => { - const socketSecret = (await getRandomBytes(16)).toString('hex') +const createSocketSecret = async atomVersion => { + const socketSecret = (await getRandomBytes(16)).toString('hex'); - await writeFile(getSocketSecretPath(atomVersion), socketSecret, {encoding: 'utf8', mode: 0o600}) + await writeFile(getSocketSecretPath(atomVersion), socketSecret, { + encoding: 'utf8', + mode: 0o600 + }); - return socketSecret -} + return socketSecret; +}; const encryptOptions = (options, secret) => { - const message = JSON.stringify(options) + const message = JSON.stringify(options); // Even if the following IV is not cryptographically secure, there's a really good chance // it's going to be unique between executions which is the requirement for GCM. // We're not using `crypto.randomBytes()` because in electron v2, that API is really slow // on Windows machines, which affects the startup time of Atom. // TodoElectronIssue: Once we upgrade to electron v3 we can use `crypto.randomBytes()` - const initVectorHash = crypto.createHash('sha1') - initVectorHash.update(Date.now() + '') - initVectorHash.update(Math.random() + '') - const initVector = initVectorHash.digest() + const initVectorHash = crypto.createHash('sha1'); + initVectorHash.update(Date.now() + ''); + initVectorHash.update(Math.random() + ''); + const initVector = initVectorHash.digest(); - const cipher = crypto.createCipheriv('aes-256-gcm', secret, initVector) + const cipher = crypto.createCipheriv('aes-256-gcm', secret, initVector); - let content = cipher.update(message, 'utf8', 'hex') - content += cipher.final('hex') + let content = cipher.update(message, 'utf8', 'hex'); + content += cipher.final('hex'); - const authTag = cipher.getAuthTag().toString('hex') + const authTag = cipher.getAuthTag().toString('hex'); return JSON.stringify({ authTag, content, initVector: initVector.toString('hex') - }) -} + }); +}; const decryptOptions = (optionsMessage, secret) => { - const {authTag, content, initVector} = JSON.parse(optionsMessage) + const { authTag, content, initVector } = JSON.parse(optionsMessage); - const decipher = crypto.createDecipheriv('aes-256-gcm', secret, Buffer.from(initVector, 'hex')) - decipher.setAuthTag(Buffer.from(authTag, 'hex')) + const decipher = crypto.createDecipheriv( + 'aes-256-gcm', + secret, + Buffer.from(initVector, 'hex') + ); + decipher.setAuthTag(Buffer.from(authTag, 'hex')); - let message = decipher.update(content, 'hex', 'utf8') - message += decipher.final('utf8') + let message = decipher.update(content, 'hex', 'utf8'); + message += decipher.final('utf8'); - return JSON.parse(message) -} + return JSON.parse(message); +}; // The application's singleton class. // // It's the entry point into the Atom application and maintains the global state // of the application. // -module.exports = -class AtomApplication extends EventEmitter { +module.exports = class AtomApplication extends EventEmitter { // Public: The entry point into the Atom application. - static open (options) { - StartupTime.addMarker('main-process:atom-application:open') + static open(options) { + StartupTime.addMarker('main-process:atom-application:open'); - const socketSecret = getExistingSocketSecret(options.version) - const socketPath = getSocketPath(socketSecret) - const createApplication = options.createApplication || (async () => { - const app = new AtomApplication(options) - await app.initialize(options) - return app - }) + const socketSecret = getExistingSocketSecret(options.version); + const socketPath = getSocketPath(socketSecret); + const createApplication = + options.createApplication || + (async () => { + const app = new AtomApplication(options); + await app.initialize(options); + return app; + }); // FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely // take a few seconds to trigger 'error' event, it could be a bug of node // or electron, before it's fixed we check the existence of socketPath to // speedup startup. if ( - !socketPath || options.test || options.benchmark || options.benchmarkTest || + !socketPath || + options.test || + options.benchmark || + options.benchmarkTest || (process.platform !== 'win32' && !fs.existsSync(socketPath)) ) { - return createApplication(options) + return createApplication(options); } return new Promise(resolve => { - const client = net.connect({path: socketPath}, () => { + const client = net.connect({ path: socketPath }, () => { client.write(encryptOptions(options, socketSecret), () => { - client.end() - app.quit() - resolve(null) - }) - }) + client.end(); + app.quit(); + resolve(null); + }); + }); - client.on('error', () => resolve(createApplication(options))) - }) + client.on('error', () => resolve(createApplication(options))); + }); } - exit (status) { - app.exit(status) + exit(status) { + app.exit(status); } - constructor (options) { - StartupTime.addMarker('main-process:atom-application:constructor:start') + constructor(options) { + StartupTime.addMarker('main-process:atom-application:constructor:start'); - super() - this.quitting = false - this.quittingForUpdate = false - this.getAllWindows = this.getAllWindows.bind(this) - this.getLastFocusedWindow = this.getLastFocusedWindow.bind(this) - this.resourcePath = options.resourcePath - this.devResourcePath = options.devResourcePath - this.version = options.version - this.devMode = options.devMode - this.safeMode = options.safeMode - this.logFile = options.logFile - this.userDataDir = options.userDataDir - this._killProcess = options.killProcess || process.kill.bind(process) - this.waitSessionsByWindow = new Map() - this.windowStack = new WindowStack() + super(); + this.quitting = false; + this.quittingForUpdate = false; + this.getAllWindows = this.getAllWindows.bind(this); + this.getLastFocusedWindow = this.getLastFocusedWindow.bind(this); + this.resourcePath = options.resourcePath; + this.devResourcePath = options.devResourcePath; + this.version = options.version; + this.devMode = options.devMode; + this.safeMode = options.safeMode; + this.logFile = options.logFile; + this.userDataDir = options.userDataDir; + this._killProcess = options.killProcess || process.kill.bind(process); + this.waitSessionsByWindow = new Map(); + this.windowStack = new WindowStack(); - this.initializeAtomHome(process.env.ATOM_HOME) + this.initializeAtomHome(process.env.ATOM_HOME); - const configFilePath = fs.existsSync(path.join(process.env.ATOM_HOME, 'config.json')) + const configFilePath = fs.existsSync( + path.join(process.env.ATOM_HOME, 'config.json') + ) ? path.join(process.env.ATOM_HOME, 'config.json') - : path.join(process.env.ATOM_HOME, 'config.cson') + : path.join(process.env.ATOM_HOME, 'config.cson'); - this.configFile = ConfigFile.at(configFilePath) + this.configFile = ConfigFile.at(configFilePath); this.config = new Config({ saveCallback: settings => { if (!this.quitting) { - return this.configFile.update(settings) + return this.configFile.update(settings); } } - }) - this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)}) + }); + this.config.setSchema(null, { + type: 'object', + properties: _.clone(ConfigSchema) + }); - this.fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, 'recovery')) - this.storageFolder = new StorageFolder(process.env.ATOM_HOME) + this.fileRecoveryService = new FileRecoveryService( + path.join(process.env.ATOM_HOME, 'recovery') + ); + this.storageFolder = new StorageFolder(process.env.ATOM_HOME); this.autoUpdateManager = new AutoUpdateManager( this.version, options.test || options.benchmark || options.benchmarkTest, this.config - ) + ); - this.disposable = new CompositeDisposable() - this.handleEvents() + this.disposable = new CompositeDisposable(); + this.handleEvents(); - StartupTime.addMarker('main-process:atom-application:constructor:end') + StartupTime.addMarker('main-process:atom-application:constructor:end'); } // This stuff was previously done in the constructor, but we want to be able to construct this object // for testing purposes without booting up the world. As you add tests, feel free to move instantiation // of these various sub-objects into the constructor, but you'll need to remove the side-effects they // perform during their construction, adding an initialize method that you call here. - async initialize (options) { - StartupTime.addMarker('main-process:atom-application:initialize:start') + async initialize(options) { + StartupTime.addMarker('main-process:atom-application:initialize:start'); - global.atomApplication = this + global.atomApplication = this; // DEPRECATED: This can be removed at some point (added in 1.13) // It converts `useCustomTitleBar: true` to `titleBar: "custom"` - if (process.platform === 'darwin' && this.config.get('core.useCustomTitleBar')) { - this.config.unset('core.useCustomTitleBar') - this.config.set('core.titleBar', 'custom') + if ( + process.platform === 'darwin' && + this.config.get('core.useCustomTitleBar') + ) { + this.config.unset('core.useCustomTitleBar'); + this.config.set('core.titleBar', 'custom'); } - this.applicationMenu = new ApplicationMenu(this.version, this.autoUpdateManager) - this.atomProtocolHandler = new AtomProtocolHandler(this.resourcePath, this.safeMode) + this.applicationMenu = new ApplicationMenu( + this.version, + this.autoUpdateManager + ); + this.atomProtocolHandler = new AtomProtocolHandler( + this.resourcePath, + this.safeMode + ); // Don't await for the following method to avoid delaying the opening of a new window. // (we await it just after opening it). // We need to do this because `listenForArgumentsFromNewProcess()` calls `crypto.randomBytes`, // which is really slow on Windows machines. // (TodoElectronIssue: This got fixed in electron v3: https://github.com/electron/electron/issues/2073). - const socketServerPromise = this.listenForArgumentsFromNewProcess(options) + const socketServerPromise = this.listenForArgumentsFromNewProcess(options); - this.setupDockMenu() + this.setupDockMenu(); - const result = await this.launch(options) - this.autoUpdateManager.initialize() - await socketServerPromise + const result = await this.launch(options); + this.autoUpdateManager.initialize(); + await socketServerPromise; - StartupTime.addMarker('main-process:atom-application:initialize:end') + StartupTime.addMarker('main-process:atom-application:initialize:end'); - return result + return result; } - async destroy () { + async destroy() { const windowsClosePromises = this.getAllWindows().map(window => { - window.close() - return window.closedPromise - }) - await Promise.all(windowsClosePromises) - this.disposable.dispose() + window.close(); + return window.closedPromise; + }); + await Promise.all(windowsClosePromises); + this.disposable.dispose(); } - async launch (options) { + async launch(options) { if (!this.configFilePromise) { this.configFilePromise = this.configFile.watch().then(disposable => { - this.disposable.add(disposable) - this.config.onDidChange('core.titleBar', () => this.promptForRestart()) - this.config.onDidChange('core.colorProfile', () => this.promptForRestart()) - }) + this.disposable.add(disposable); + this.config.onDidChange('core.titleBar', () => this.promptForRestart()); + this.config.onDidChange('core.colorProfile', () => + this.promptForRestart() + ); + }); // TodoElectronIssue: In electron v2 awaiting the watcher causes some delay // in Windows machines, which affects directly the startup time. if (process.platform !== 'win32') { - await this.configFilePromise + await this.configFilePromise; } } - let optionsForWindowsToOpen = [] - let shouldReopenPreviousWindows = false + let optionsForWindowsToOpen = []; + let shouldReopenPreviousWindows = false; if (options.test || options.benchmark || options.benchmarkTest) { - optionsForWindowsToOpen.push(options) + optionsForWindowsToOpen.push(options); } else if (options.newWindow) { - shouldReopenPreviousWindows = false - } else if ((options.pathsToOpen && options.pathsToOpen.length > 0) || - (options.urlsToOpen && options.urlsToOpen.length > 0)) { - optionsForWindowsToOpen.push(options) - shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') === 'always' + shouldReopenPreviousWindows = false; + } else if ( + (options.pathsToOpen && options.pathsToOpen.length > 0) || + (options.urlsToOpen && options.urlsToOpen.length > 0) + ) { + optionsForWindowsToOpen.push(options); + shouldReopenPreviousWindows = + this.config.get('core.restorePreviousWindowsOnStart') === 'always'; } else { - shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') !== 'no' + shouldReopenPreviousWindows = + this.config.get('core.restorePreviousWindowsOnStart') !== 'no'; } if (shouldReopenPreviousWindows) { - optionsForWindowsToOpen = [...await this.loadPreviousWindowOptions(), ...optionsForWindowsToOpen] + optionsForWindowsToOpen = [ + ...(await this.loadPreviousWindowOptions()), + ...optionsForWindowsToOpen + ]; } if (optionsForWindowsToOpen.length === 0) { - optionsForWindowsToOpen.push(options) + optionsForWindowsToOpen.push(options); } // Preserve window opening order - const windows = [] + const windows = []; for (const options of optionsForWindowsToOpen) { - windows.push(await this.openWithOptions(options)) + windows.push(await this.openWithOptions(options)); } - return windows + return windows; } - openWithOptions (options) { + openWithOptions(options) { const { pathsToOpen, executedFrom, @@ -335,10 +380,10 @@ class AtomApplication extends EventEmitter { addToLastWindow, preserveFocus, env - } = options + } = options; if (!preserveFocus) { - app.focus() + app.focus(); } if (test) { @@ -351,7 +396,7 @@ class AtomApplication extends EventEmitter { logFile, timeout, env - }) + }); } else if (benchmark || benchmarkTest) { return this.runBenchmarks({ headless: true, @@ -361,8 +406,11 @@ class AtomApplication extends EventEmitter { pathsToOpen, timeout, env - }) - } else if ((pathsToOpen && pathsToOpen.length > 0) || (foldersToOpen && foldersToOpen.length > 0)) { + }); + } else if ( + (pathsToOpen && pathsToOpen.length > 0) || + (foldersToOpen && foldersToOpen.length > 0) + ) { return this.openPaths({ pathsToOpen, foldersToOpen, @@ -375,11 +423,13 @@ class AtomApplication extends EventEmitter { clearWindowState, addToLastWindow, env - }) + }); } else if (urlsToOpen && urlsToOpen.length > 0) { return Promise.all( - urlsToOpen.map(urlToOpen => this.openUrl({urlToOpen, devMode, safeMode, env})) - ) + urlsToOpen.map(urlToOpen => + this.openUrl({ urlToOpen, devMode, safeMode, env }) + ) + ); } else { // Always open an editor window if this is the first instance of Atom. return this.openPath({ @@ -392,60 +442,62 @@ class AtomApplication extends EventEmitter { clearWindowState, addToLastWindow, env - }) + }); } } // Public: Create a new {AtomWindow} bound to this application. - createWindow (settings) { - return new AtomWindow(this, this.fileRecoveryService, settings) + createWindow(settings) { + return new AtomWindow(this, this.fileRecoveryService, settings); } // Public: Removes the {AtomWindow} from the global window list. - removeWindow (window) { - this.windowStack.removeWindow(window) + removeWindow(window) { + this.windowStack.removeWindow(window); if (this.getAllWindows().length === 0) { if (this.applicationMenu != null) { - this.applicationMenu.enableWindowSpecificItems(false) + this.applicationMenu.enableWindowSpecificItems(false); } if (['win32', 'linux'].includes(process.platform)) { - app.quit() - return + app.quit(); + return; } } - if (!window.isSpec) this.saveCurrentWindowOptions(true) + if (!window.isSpec) this.saveCurrentWindowOptions(true); } // Public: Adds the {AtomWindow} to the global window list. - addWindow (window) { - this.windowStack.addWindow(window) - if (this.applicationMenu) this.applicationMenu.addWindow(window.browserWindow) + addWindow(window) { + this.windowStack.addWindow(window); + if (this.applicationMenu) + this.applicationMenu.addWindow(window.browserWindow); window.once('window:loaded', () => { - this.autoUpdateManager && this.autoUpdateManager.emitUpdateAvailableEvent(window) - }) + this.autoUpdateManager && + this.autoUpdateManager.emitUpdateAvailableEvent(window); + }); if (!window.isSpec) { - const focusHandler = () => this.windowStack.touch(window) - const blurHandler = () => this.saveCurrentWindowOptions(false) - window.browserWindow.on('focus', focusHandler) - window.browserWindow.on('blur', blurHandler) + const focusHandler = () => this.windowStack.touch(window); + const blurHandler = () => this.saveCurrentWindowOptions(false); + window.browserWindow.on('focus', focusHandler); + window.browserWindow.on('blur', blurHandler); window.browserWindow.once('closed', () => { - this.windowStack.removeWindow(window) - window.browserWindow.removeListener('focus', focusHandler) - window.browserWindow.removeListener('blur', blurHandler) - }) - window.browserWindow.webContents.once('did-finish-load', blurHandler) - this.saveCurrentWindowOptions(false) + this.windowStack.removeWindow(window); + window.browserWindow.removeListener('focus', focusHandler); + window.browserWindow.removeListener('blur', blurHandler); + }); + window.browserWindow.webContents.once('did-finish-load', blurHandler); + this.saveCurrentWindowOptions(false); } } - getAllWindows () { - return this.windowStack.all().slice() + getAllWindows() { + return this.windowStack.all().slice(); } - getLastFocusedWindow (predicate) { - return this.windowStack.getLastFocusedWindow(predicate) + getLastFocusedWindow(predicate) { + return this.windowStack.getLastFocusedWindow(predicate); } // Creates server to listen for additional atom application launches. @@ -453,387 +505,587 @@ class AtomApplication extends EventEmitter { // You can run the atom command multiple times, but after the first launch // the other launches will just pass their information to this server and then // close immediately. - async listenForArgumentsFromNewProcess (options) { + async listenForArgumentsFromNewProcess(options) { if (!options.test && !options.benchmark && !options.benchmarkTest) { - this.socketSecretPromise = createSocketSecret(this.version) - this.socketSecret = await this.socketSecretPromise - this.socketPath = getSocketPath(this.socketSecret) + this.socketSecretPromise = createSocketSecret(this.version); + this.socketSecret = await this.socketSecretPromise; + this.socketPath = getSocketPath(this.socketSecret); } - await this.deleteSocketFile() + await this.deleteSocketFile(); const server = net.createServer(connection => { - let data = '' - connection.on('data', chunk => { data += chunk }) + let data = ''; + connection.on('data', chunk => { + data += chunk; + }); connection.on('end', () => { try { - const options = decryptOptions(data, this.socketSecret) - this.openWithOptions(options) + const options = decryptOptions(data, this.socketSecret); + this.openWithOptions(options); } catch (e) { // Error while parsing/decrypting the options passed by the client. // We cannot trust the client, aborting. } - }) - }) + }); + }); return new Promise(resolve => { - server.listen(this.socketPath, resolve) - server.on('error', error => console.error('Application server failed', error)) - }) + server.listen(this.socketPath, resolve); + server.on('error', error => + console.error('Application server failed', error) + ); + }); } - async deleteSocketFile () { - if (process.platform === 'win32') return + async deleteSocketFile() { + if (process.platform === 'win32') return; if (!this.socketSecretPromise) { - return + return; } - await this.socketSecretPromise + await this.socketSecretPromise; if (fs.existsSync(this.socketPath)) { try { - fs.unlinkSync(this.socketPath) + fs.unlinkSync(this.socketPath); } catch (error) { // Ignore ENOENT errors in case the file was deleted between the exists // check and the call to unlink sync. This occurred occasionally on CI // which is why this check is here. - if (error.code !== 'ENOENT') throw error + if (error.code !== 'ENOENT') throw error; } } } - async deleteSocketSecretFile () { + async deleteSocketSecretFile() { if (!this.socketSecretPromise) { - return + return; } - await this.socketSecretPromise + await this.socketSecretPromise; - const socketSecretPath = getSocketSecretPath(this.version) + const socketSecretPath = getSocketSecretPath(this.version); if (fs.existsSync(socketSecretPath)) { try { - fs.unlinkSync(socketSecretPath) + fs.unlinkSync(socketSecretPath); } catch (error) { // Ignore ENOENT errors in case the file was deleted between the exists // check and the call to unlink sync. - if (error.code !== 'ENOENT') throw error + if (error.code !== 'ENOENT') throw error; } } } // Registers basic application commands, non-idempotent. - handleEvents () { - const createOpenSettings = ({event, sameWindow}) => { - const targetWindow = event ? this.atomWindowForEvent(event) : this.focusedWindow() + handleEvents() { + const createOpenSettings = ({ event, sameWindow }) => { + const targetWindow = event + ? this.atomWindowForEvent(event) + : this.focusedWindow(); return { devMode: targetWindow ? targetWindow.devMode : false, safeMode: targetWindow ? targetWindow.safeMode : false, window: sameWindow && targetWindow ? targetWindow : null - } - } + }; + }; - this.on('application:quit', () => app.quit()) - this.on('application:new-window', () => this.openPath(createOpenSettings({}))) - this.on('application:new-file', () => (this.focusedWindow() || this).openPath()) - this.on('application:open-dev', () => this.promptForPathToOpen('all', {devMode: true})) - this.on('application:open-safe', () => this.promptForPathToOpen('all', {safeMode: true})) - this.on('application:inspect', ({x, y, atomWindow}) => { - if (!atomWindow) atomWindow = this.focusedWindow() - if (atomWindow) atomWindow.browserWindow.inspectElement(x, y) - }) + this.on('application:quit', () => app.quit()); + this.on('application:new-window', () => + this.openPath(createOpenSettings({})) + ); + this.on('application:new-file', () => + (this.focusedWindow() || this).openPath() + ); + this.on('application:open-dev', () => + this.promptForPathToOpen('all', { devMode: true }) + ); + this.on('application:open-safe', () => + this.promptForPathToOpen('all', { safeMode: true }) + ); + this.on('application:inspect', ({ x, y, atomWindow }) => { + if (!atomWindow) atomWindow = this.focusedWindow(); + if (atomWindow) atomWindow.browserWindow.inspectElement(x, y); + }); - this.on('application:open-documentation', () => shell.openExternal('http://flight-manual.atom.io')) - this.on('application:open-discussions', () => shell.openExternal('https://discuss.atom.io')) - this.on('application:open-faq', () => shell.openExternal('https://atom.io/faq')) - this.on('application:open-terms-of-use', () => shell.openExternal('https://atom.io/terms')) - this.on('application:report-issue', () => shell.openExternal('https://github.com/atom/atom/blob/master/CONTRIBUTING.md#reporting-bugs')) - this.on('application:search-issues', () => shell.openExternal('https://github.com/search?q=+is%3Aissue+user%3Aatom')) + this.on('application:open-documentation', () => + shell.openExternal('http://flight-manual.atom.io') + ); + this.on('application:open-discussions', () => + shell.openExternal('https://discuss.atom.io') + ); + this.on('application:open-faq', () => + shell.openExternal('https://atom.io/faq') + ); + this.on('application:open-terms-of-use', () => + shell.openExternal('https://atom.io/terms') + ); + this.on('application:report-issue', () => + shell.openExternal( + 'https://github.com/atom/atom/blob/master/CONTRIBUTING.md#reporting-bugs' + ) + ); + this.on('application:search-issues', () => + shell.openExternal('https://github.com/search?q=+is%3Aissue+user%3Aatom') + ); this.on('application:install-update', () => { - this.quitting = true - this.quittingForUpdate = true - this.autoUpdateManager.install() - }) + this.quitting = true; + this.quittingForUpdate = true; + this.autoUpdateManager.install(); + }); - this.on('application:check-for-update', () => this.autoUpdateManager.check()) + this.on('application:check-for-update', () => + this.autoUpdateManager.check() + ); if (process.platform === 'darwin') { this.on('application:reopen-project', ({ paths }) => { - this.openPaths({ pathsToOpen: paths }) - }) + this.openPaths({ pathsToOpen: paths }); + }); this.on('application:open', () => { - this.promptForPathToOpen('all', createOpenSettings({sameWindow: true}), getDefaultPath()) - }) + this.promptForPathToOpen( + 'all', + createOpenSettings({ sameWindow: true }), + getDefaultPath() + ); + }); this.on('application:open-file', () => { - this.promptForPathToOpen('file', createOpenSettings({sameWindow: true}), getDefaultPath()) - }) + this.promptForPathToOpen( + 'file', + createOpenSettings({ sameWindow: true }), + getDefaultPath() + ); + }); this.on('application:open-folder', () => { - this.promptForPathToOpen('folder', createOpenSettings({sameWindow: true}), getDefaultPath()) - }) + this.promptForPathToOpen( + 'folder', + createOpenSettings({ sameWindow: true }), + getDefaultPath() + ); + }); - this.on('application:bring-all-windows-to-front', () => Menu.sendActionToFirstResponder('arrangeInFront:')) - this.on('application:hide', () => Menu.sendActionToFirstResponder('hide:')) - this.on('application:hide-other-applications', () => Menu.sendActionToFirstResponder('hideOtherApplications:')) - this.on('application:minimize', () => Menu.sendActionToFirstResponder('performMiniaturize:')) - this.on('application:unhide-all-applications', () => Menu.sendActionToFirstResponder('unhideAllApplications:')) - this.on('application:zoom', () => Menu.sendActionToFirstResponder('zoom:')) + this.on('application:bring-all-windows-to-front', () => + Menu.sendActionToFirstResponder('arrangeInFront:') + ); + this.on('application:hide', () => + Menu.sendActionToFirstResponder('hide:') + ); + this.on('application:hide-other-applications', () => + Menu.sendActionToFirstResponder('hideOtherApplications:') + ); + this.on('application:minimize', () => + Menu.sendActionToFirstResponder('performMiniaturize:') + ); + this.on('application:unhide-all-applications', () => + Menu.sendActionToFirstResponder('unhideAllApplications:') + ); + this.on('application:zoom', () => + Menu.sendActionToFirstResponder('zoom:') + ); } else { this.on('application:minimize', () => { - const window = this.focusedWindow() - if (window) window.minimize() - }) - this.on('application:zoom', function () { - const window = this.focusedWindow() - if (window) window.maximize() - }) + const window = this.focusedWindow(); + if (window) window.minimize(); + }); + this.on('application:zoom', function() { + const window = this.focusedWindow(); + if (window) window.maximize(); + }); } - this.openPathOnEvent('application:about', 'atom://about') - this.openPathOnEvent('application:show-settings', 'atom://config') - this.openPathOnEvent('application:open-your-config', 'atom://.atom/config') - this.openPathOnEvent('application:open-your-init-script', 'atom://.atom/init-script') - this.openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap') - this.openPathOnEvent('application:open-your-snippets', 'atom://.atom/snippets') - this.openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet') - this.openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md')) + this.openPathOnEvent('application:about', 'atom://about'); + this.openPathOnEvent('application:show-settings', 'atom://config'); + this.openPathOnEvent('application:open-your-config', 'atom://.atom/config'); + this.openPathOnEvent( + 'application:open-your-init-script', + 'atom://.atom/init-script' + ); + this.openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap'); + this.openPathOnEvent( + 'application:open-your-snippets', + 'atom://.atom/snippets' + ); + this.openPathOnEvent( + 'application:open-your-stylesheet', + 'atom://.atom/stylesheet' + ); + this.openPathOnEvent( + 'application:open-license', + path.join(process.resourcesPath, 'LICENSE.md') + ); this.configFile.onDidChange(settings => { for (let window of this.getAllWindows()) { - window.didChangeUserSettings(settings) + window.didChangeUserSettings(settings); } - this.config.resetUserSettings(settings) - }) + this.config.resetUserSettings(settings); + }); this.configFile.onDidError(message => { - const window = this.focusedWindow() || this.getLastFocusedWindow() + const window = this.focusedWindow() || this.getLastFocusedWindow(); if (window) { - window.didFailToReadUserSettings(message) + window.didFailToReadUserSettings(message); } else { - console.error(message) + console.error(message); } - }) + }); - this.disposable.add(ipcHelpers.on(app, 'before-quit', async event => { - let resolveBeforeQuitPromise - this.lastBeforeQuitPromise = new Promise(resolve => { resolveBeforeQuitPromise = resolve }) + this.disposable.add( + ipcHelpers.on(app, 'before-quit', async event => { + let resolveBeforeQuitPromise; + this.lastBeforeQuitPromise = new Promise(resolve => { + resolveBeforeQuitPromise = resolve; + }); - if (!this.quitting) { - this.quitting = true - event.preventDefault() - const windowUnloadPromises = this.getAllWindows().map(async window => { - const unloaded = await window.prepareToUnload() - if (unloaded) { - window.close() - await window.closedPromise + if (!this.quitting) { + this.quitting = true; + event.preventDefault(); + const windowUnloadPromises = this.getAllWindows().map( + async window => { + const unloaded = await window.prepareToUnload(); + if (unloaded) { + window.close(); + await window.closedPromise; + } + return unloaded; + } + ); + const windowUnloadedResults = await Promise.all(windowUnloadPromises); + if (windowUnloadedResults.every(Boolean)) { + app.quit(); + } else { + this.quitting = false; } - return unloaded - }) - const windowUnloadedResults = await Promise.all(windowUnloadPromises) - if (windowUnloadedResults.every(Boolean)) { - app.quit() - } else { - this.quitting = false } - } - resolveBeforeQuitPromise() - })) + resolveBeforeQuitPromise(); + }) + ); - this.disposable.add(ipcHelpers.on(app, 'will-quit', () => { - this.killAllProcesses() + this.disposable.add( + ipcHelpers.on(app, 'will-quit', () => { + this.killAllProcesses(); - return Promise.all([ - this.deleteSocketFile(), - this.deleteSocketSecretFile() - ]) - })) + return Promise.all([ + this.deleteSocketFile(), + this.deleteSocketSecretFile() + ]); + }) + ); // Triggered by the 'open-file' event from Electron: // https://electronjs.org/docs/api/app#event-open-file-macos // For example, this is fired when a file is dragged and dropped onto the Atom application icon in the dock. - this.disposable.add(ipcHelpers.on(app, 'open-file', (event, pathToOpen) => { - event.preventDefault() - this.openPath({pathToOpen}) - })) - - this.disposable.add(ipcHelpers.on(app, 'open-url', (event, urlToOpen) => { - event.preventDefault() - this.openUrl({urlToOpen, devMode: this.devMode, safeMode: this.safeMode}) - })) - - this.disposable.add(ipcHelpers.on(app, 'activate', (event, hasVisibleWindows) => { - if (hasVisibleWindows) return - if (event) event.preventDefault() - this.emit('application:new-window') - })) - - this.disposable.add(ipcHelpers.on(ipcMain, 'restart-application', () => { - this.restart() - })) - - this.disposable.add(ipcHelpers.on(ipcMain, 'resolve-proxy', (event, requestId, url) => { - event.sender.session.resolveProxy(url, proxy => { - if (!event.sender.isDestroyed()) event.sender.send('did-resolve-proxy', requestId, proxy) + this.disposable.add( + ipcHelpers.on(app, 'open-file', (event, pathToOpen) => { + event.preventDefault(); + this.openPath({ pathToOpen }); }) - })) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'did-change-history-manager', event => { - for (let atomWindow of this.getAllWindows()) { - const {webContents} = atomWindow.browserWindow - if (webContents !== event.sender) webContents.send('did-change-history-manager') - } - })) + this.disposable.add( + ipcHelpers.on(app, 'open-url', (event, urlToOpen) => { + event.preventDefault(); + this.openUrl({ + urlToOpen, + devMode: this.devMode, + safeMode: this.safeMode + }); + }) + ); + + this.disposable.add( + ipcHelpers.on(app, 'activate', (event, hasVisibleWindows) => { + if (hasVisibleWindows) return; + if (event) event.preventDefault(); + this.emit('application:new-window'); + }) + ); + + this.disposable.add( + ipcHelpers.on(ipcMain, 'restart-application', () => { + this.restart(); + }) + ); + + this.disposable.add( + ipcHelpers.on(ipcMain, 'resolve-proxy', (event, requestId, url) => { + event.sender.session.resolveProxy(url, proxy => { + if (!event.sender.isDestroyed()) + event.sender.send('did-resolve-proxy', requestId, proxy); + }); + }) + ); + + this.disposable.add( + ipcHelpers.on(ipcMain, 'did-change-history-manager', event => { + for (let atomWindow of this.getAllWindows()) { + const { webContents } = atomWindow.browserWindow; + if (webContents !== event.sender) + webContents.send('did-change-history-manager'); + } + }) + ); // A request from the associated render process to open a set of paths using the standard window location logic. // Used for application:reopen-project. - this.disposable.add(ipcHelpers.on(ipcMain, 'open', (event, options) => { - if (options) { - if (typeof options.pathsToOpen === 'string') { - options.pathsToOpen = [options.pathsToOpen] - } + this.disposable.add( + ipcHelpers.on(ipcMain, 'open', (event, options) => { + if (options) { + if (typeof options.pathsToOpen === 'string') { + options.pathsToOpen = [options.pathsToOpen]; + } - if (options.here) { - options.window = this.atomWindowForEvent(event) - } + if (options.here) { + options.window = this.atomWindowForEvent(event); + } - if (options.pathsToOpen && options.pathsToOpen.length > 0) { - this.openPaths(options) + if (options.pathsToOpen && options.pathsToOpen.length > 0) { + this.openPaths(options); + } else { + this.addWindow(this.createWindow(options)); + } } else { - this.addWindow(this.createWindow(options)) + this.promptForPathToOpen('all', {}); } - } else { - this.promptForPathToOpen('all', {}) - } - })) + }) + ); // Prompt for a file, folder, or either, then open the chosen paths. Files will be opened in the originating // window; folders will be opened in a new window unless an existing window exactly contains all of them. - this.disposable.add(ipcHelpers.on(ipcMain, 'open-chosen-any', (event, defaultPath) => { - this.promptForPathToOpen('all', createOpenSettings({event, sameWindow: true}), defaultPath) - })) - this.disposable.add(ipcHelpers.on(ipcMain, 'open-chosen-file', (event, defaultPath) => { - this.promptForPathToOpen('file', createOpenSettings({event, sameWindow: true}), defaultPath) - })) - this.disposable.add(ipcHelpers.on(ipcMain, 'open-chosen-folder', (event, defaultPath) => { - this.promptForPathToOpen('folder', createOpenSettings({event}), defaultPath) - })) - - this.disposable.add(ipcHelpers.on(ipcMain, 'update-application-menu', (event, template, menu) => { - const window = BrowserWindow.fromWebContents(event.sender) - if (this.applicationMenu) this.applicationMenu.update(window, template, menu) - })) - - this.disposable.add(ipcHelpers.on(ipcMain, 'run-package-specs', (event, packageSpecPath, options = {}) => { - this.runTests(Object.assign({ - resourcePath: this.devResourcePath, - pathsToOpen: [packageSpecPath], - headless: false - }, options)) - })) - - this.disposable.add(ipcHelpers.on(ipcMain, 'run-benchmarks', (event, benchmarksPath) => { - this.runBenchmarks({ - resourcePath: this.devResourcePath, - pathsToOpen: [benchmarksPath], - headless: false, - test: false + this.disposable.add( + ipcHelpers.on(ipcMain, 'open-chosen-any', (event, defaultPath) => { + this.promptForPathToOpen( + 'all', + createOpenSettings({ event, sameWindow: true }), + defaultPath + ); }) - })) + ); + this.disposable.add( + ipcHelpers.on(ipcMain, 'open-chosen-file', (event, defaultPath) => { + this.promptForPathToOpen( + 'file', + createOpenSettings({ event, sameWindow: true }), + defaultPath + ); + }) + ); + this.disposable.add( + ipcHelpers.on(ipcMain, 'open-chosen-folder', (event, defaultPath) => { + this.promptForPathToOpen( + 'folder', + createOpenSettings({ event }), + defaultPath + ); + }) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'command', (event, command) => { - this.emit(command) - })) + this.disposable.add( + ipcHelpers.on( + ipcMain, + 'update-application-menu', + (event, template, menu) => { + const window = BrowserWindow.fromWebContents(event.sender); + if (this.applicationMenu) + this.applicationMenu.update(window, template, menu); + } + ) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'window-command', (event, command, ...args) => { - const window = BrowserWindow.fromWebContents(event.sender) - return window && window.emit(command, ...args) - })) + this.disposable.add( + ipcHelpers.on( + ipcMain, + 'run-package-specs', + (event, packageSpecPath, options = {}) => { + this.runTests( + Object.assign( + { + resourcePath: this.devResourcePath, + pathsToOpen: [packageSpecPath], + headless: false + }, + options + ) + ); + } + ) + ); - this.disposable.add(ipcHelpers.respondTo('window-method', (browserWindow, method, ...args) => { - const window = this.atomWindowForBrowserWindow(browserWindow) - if (window) window[method](...args) - })) + this.disposable.add( + ipcHelpers.on(ipcMain, 'run-benchmarks', (event, benchmarksPath) => { + this.runBenchmarks({ + resourcePath: this.devResourcePath, + pathsToOpen: [benchmarksPath], + headless: false, + test: false + }); + }) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'pick-folder', (event, responseChannel) => { - this.promptForPath('folder', paths => event.sender.send(responseChannel, paths)) - })) + this.disposable.add( + ipcHelpers.on(ipcMain, 'command', (event, command) => { + this.emit(command); + }) + ); - this.disposable.add(ipcHelpers.respondTo('set-window-size', (window, width, height) => { - window.setSize(width, height) - })) + this.disposable.add( + ipcHelpers.on(ipcMain, 'window-command', (event, command, ...args) => { + const window = BrowserWindow.fromWebContents(event.sender); + return window && window.emit(command, ...args); + }) + ); - this.disposable.add(ipcHelpers.respondTo('set-window-position', (window, x, y) => { - window.setPosition(x, y) - })) + this.disposable.add( + ipcHelpers.respondTo( + 'window-method', + (browserWindow, method, ...args) => { + const window = this.atomWindowForBrowserWindow(browserWindow); + if (window) window[method](...args); + } + ) + ); - this.disposable.add(ipcHelpers.respondTo('set-user-settings', (window, settings, filePath) => { - if (!this.quitting) { - return ConfigFile.at(filePath || this.configFilePath).update(JSON.parse(settings)) - } - })) + this.disposable.add( + ipcHelpers.on(ipcMain, 'pick-folder', (event, responseChannel) => { + this.promptForPath('folder', paths => + event.sender.send(responseChannel, paths) + ); + }) + ); - this.disposable.add(ipcHelpers.respondTo('center-window', window => window.center())) - this.disposable.add(ipcHelpers.respondTo('focus-window', window => window.focus())) - this.disposable.add(ipcHelpers.respondTo('show-window', window => window.show())) - this.disposable.add(ipcHelpers.respondTo('hide-window', window => window.hide())) - this.disposable.add(ipcHelpers.respondTo('get-temporary-window-state', window => window.temporaryState)) + this.disposable.add( + ipcHelpers.respondTo('set-window-size', (window, width, height) => { + window.setSize(width, height); + }) + ); - this.disposable.add(ipcHelpers.respondTo('set-temporary-window-state', (win, state) => { - win.temporaryState = state - })) + this.disposable.add( + ipcHelpers.respondTo('set-window-position', (window, x, y) => { + window.setPosition(x, y); + }) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'write-text-to-selection-clipboard', (event, text) => - clipboard.writeText(text, 'selection') - )) + this.disposable.add( + ipcHelpers.respondTo( + 'set-user-settings', + (window, settings, filePath) => { + if (!this.quitting) { + return ConfigFile.at(filePath || this.configFilePath).update( + JSON.parse(settings) + ); + } + } + ) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'write-to-stdout', (event, output) => - process.stdout.write(output) - )) + this.disposable.add( + ipcHelpers.respondTo('center-window', window => window.center()) + ); + this.disposable.add( + ipcHelpers.respondTo('focus-window', window => window.focus()) + ); + this.disposable.add( + ipcHelpers.respondTo('show-window', window => window.show()) + ); + this.disposable.add( + ipcHelpers.respondTo('hide-window', window => window.hide()) + ); + this.disposable.add( + ipcHelpers.respondTo( + 'get-temporary-window-state', + window => window.temporaryState + ) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'write-to-stderr', (event, output) => - process.stderr.write(output) - )) + this.disposable.add( + ipcHelpers.respondTo('set-temporary-window-state', (win, state) => { + win.temporaryState = state; + }) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'add-recent-document', (event, filename) => - app.addRecentDocument(filename) - )) + this.disposable.add( + ipcHelpers.on( + ipcMain, + 'write-text-to-selection-clipboard', + (event, text) => clipboard.writeText(text, 'selection') + ) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'execute-javascript-in-dev-tools', (event, code) => - event.sender.devToolsWebContents && event.sender.devToolsWebContents.executeJavaScript(code) - )) + this.disposable.add( + ipcHelpers.on(ipcMain, 'write-to-stdout', (event, output) => + process.stdout.write(output) + ) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'get-auto-update-manager-state', event => { - event.returnValue = this.autoUpdateManager.getState() - })) + this.disposable.add( + ipcHelpers.on(ipcMain, 'write-to-stderr', (event, output) => + process.stderr.write(output) + ) + ); - this.disposable.add(ipcHelpers.on(ipcMain, 'get-auto-update-manager-error', event => { - event.returnValue = this.autoUpdateManager.getErrorMessage() - })) + this.disposable.add( + ipcHelpers.on(ipcMain, 'add-recent-document', (event, filename) => + app.addRecentDocument(filename) + ) + ); - this.disposable.add(ipcHelpers.respondTo('will-save-path', (window, path) => - this.fileRecoveryService.willSavePath(window, path) - )) + this.disposable.add( + ipcHelpers.on( + ipcMain, + 'execute-javascript-in-dev-tools', + (event, code) => + event.sender.devToolsWebContents && + event.sender.devToolsWebContents.executeJavaScript(code) + ) + ); - this.disposable.add(ipcHelpers.respondTo('did-save-path', (window, path) => - this.fileRecoveryService.didSavePath(window, path) - )) + this.disposable.add( + ipcHelpers.on(ipcMain, 'get-auto-update-manager-state', event => { + event.returnValue = this.autoUpdateManager.getState(); + }) + ); - this.disposable.add(this.disableZoomOnDisplayChange()) + this.disposable.add( + ipcHelpers.on(ipcMain, 'get-auto-update-manager-error', event => { + event.returnValue = this.autoUpdateManager.getErrorMessage(); + }) + ); + + this.disposable.add( + ipcHelpers.respondTo('will-save-path', (window, path) => + this.fileRecoveryService.willSavePath(window, path) + ) + ); + + this.disposable.add( + ipcHelpers.respondTo('did-save-path', (window, path) => + this.fileRecoveryService.didSavePath(window, path) + ) + ); + + this.disposable.add(this.disableZoomOnDisplayChange()); } - setupDockMenu () { + setupDockMenu() { if (process.platform === 'darwin') { - return app.dock.setMenu(Menu.buildFromTemplate([ - {label: 'New Window', click: () => this.emit('application:new-window')} - ])) + return app.dock.setMenu( + Menu.buildFromTemplate([ + { + label: 'New Window', + click: () => this.emit('application:new-window') + } + ]) + ); } } - initializeAtomHome (configDirPath) { + initializeAtomHome(configDirPath) { if (!fs.existsSync(configDirPath)) { - const templateConfigDirPath = fs.resolve(this.resourcePath, 'dot-atom') - fs.copySync(templateConfigDirPath, configDirPath) + const templateConfigDirPath = fs.resolve(this.resourcePath, 'dot-atom'); + fs.copySync(templateConfigDirPath, configDirPath); } } @@ -843,13 +1095,13 @@ class AtomApplication extends EventEmitter { // // command - The string representing the command. // args - The optional arguments to pass along. - sendCommand (command, ...args) { + sendCommand(command, ...args) { if (!this.emit(command, ...args)) { - const focusedWindow = this.focusedWindow() + const focusedWindow = this.focusedWindow(); if (focusedWindow) { - return focusedWindow.sendCommand(command, ...args) + return focusedWindow.sendCommand(command, ...args); } else { - return this.sendCommandToFirstResponder(command) + return this.sendCommandToFirstResponder(command); } } } @@ -859,44 +1111,44 @@ class AtomApplication extends EventEmitter { // command - The string representing the command. // atomWindow - The {AtomWindow} to send the command to. // args - The optional arguments to pass along. - sendCommandToWindow (command, atomWindow, ...args) { + sendCommandToWindow(command, atomWindow, ...args) { if (!this.emit(command, ...args)) { if (atomWindow) { - return atomWindow.sendCommand(command, ...args) + return atomWindow.sendCommand(command, ...args); } else { - return this.sendCommandToFirstResponder(command) + return this.sendCommandToFirstResponder(command); } } } // Translates the command into macOS action and sends it to application's first // responder. - sendCommandToFirstResponder (command) { - if (process.platform !== 'darwin') return false + sendCommandToFirstResponder(command) { + if (process.platform !== 'darwin') return false; switch (command) { case 'core:undo': - Menu.sendActionToFirstResponder('undo:') - break + Menu.sendActionToFirstResponder('undo:'); + break; case 'core:redo': - Menu.sendActionToFirstResponder('redo:') - break + Menu.sendActionToFirstResponder('redo:'); + break; case 'core:copy': - Menu.sendActionToFirstResponder('copy:') - break + Menu.sendActionToFirstResponder('copy:'); + break; case 'core:cut': - Menu.sendActionToFirstResponder('cut:') - break + Menu.sendActionToFirstResponder('cut:'); + break; case 'core:paste': - Menu.sendActionToFirstResponder('paste:') - break + Menu.sendActionToFirstResponder('paste:'); + break; case 'core:select-all': - Menu.sendActionToFirstResponder('selectAll:') - break + Menu.sendActionToFirstResponder('selectAll:'); + break; default: - return false + return false; } - return true + return true; } // Public: Open the given path in the focused window when the event is @@ -906,61 +1158,66 @@ class AtomApplication extends EventEmitter { // // eventName - The event to listen for. // pathToOpen - The path to open when the event is triggered. - openPathOnEvent (eventName, pathToOpen) { + openPathOnEvent(eventName, pathToOpen) { this.on(eventName, () => { - const window = this.focusedWindow() + const window = this.focusedWindow(); if (window) { - return window.openPath(pathToOpen) + return window.openPath(pathToOpen); } else { - return this.openPath({pathToOpen}) + return this.openPath({ pathToOpen }); } - }) + }); } // Returns the {AtomWindow} for the given locations. - windowForLocations (locationsToOpen, devMode, safeMode) { - return this.getLastFocusedWindow(window => - !window.isSpec && - window.devMode === devMode && - window.safeMode === safeMode && - window.containsLocations(locationsToOpen) - ) + windowForLocations(locationsToOpen, devMode, safeMode) { + return this.getLastFocusedWindow( + window => + !window.isSpec && + window.devMode === devMode && + window.safeMode === safeMode && + window.containsLocations(locationsToOpen) + ); } // Returns the {AtomWindow} for the given ipcMain event. - atomWindowForEvent ({sender}) { - return this.atomWindowForBrowserWindow(BrowserWindow.fromWebContents(sender)) + atomWindowForEvent({ sender }) { + return this.atomWindowForBrowserWindow( + BrowserWindow.fromWebContents(sender) + ); } - atomWindowForBrowserWindow (browserWindow) { - return this.getAllWindows().find(atomWindow => atomWindow.browserWindow === browserWindow) + atomWindowForBrowserWindow(browserWindow) { + return this.getAllWindows().find( + atomWindow => atomWindow.browserWindow === browserWindow + ); } // Public: Returns the currently focused {AtomWindow} or undefined if none. - focusedWindow () { - return this.getAllWindows().find(window => window.isFocused()) + focusedWindow() { + return this.getAllWindows().find(window => window.isFocused()); } // Get the platform-specific window offset for new windows. - getWindowOffsetForCurrentPlatform () { + getWindowOffsetForCurrentPlatform() { const offsetByPlatform = { darwin: 22, win32: 26 - } - return offsetByPlatform[process.platform] || 0 + }; + return offsetByPlatform[process.platform] || 0; } // Get the dimensions for opening a new window by cascading as appropriate to // the platform. - getDimensionsForNewWindow () { - const window = this.focusedWindow() || this.getLastFocusedWindow() - if (!window || window.isMaximized()) return - const dimensions = window.getDimensions() + getDimensionsForNewWindow() { + const window = this.focusedWindow() || this.getLastFocusedWindow(); + if (!window || window.isMaximized()) return; + const dimensions = window.getDimensions(); if (dimensions) { - const offset = this.getWindowOffsetForCurrentPlatform() - dimensions.x += offset - dimensions.y += offset - return dimensions + const offset = this.getWindowOffsetForCurrentPlatform(); + dimensions.x += offset; + dimensions.y += offset; + return dimensions; } } @@ -975,7 +1232,7 @@ class AtomApplication extends EventEmitter { // :profileStartup - Boolean to control creating a profile of the startup time. // :window - {AtomWindow} to open file paths in. // :addToLastWindow - Boolean of whether this should be opened in last focused window. - openPath ({ + openPath({ pathToOpen, pidToKillWhenClosed, newWindow, @@ -998,7 +1255,7 @@ class AtomApplication extends EventEmitter { clearWindowState, addToLastWindow, env - }) + }); } // Public: Opens multiple paths, in existing windows if possible. @@ -1013,7 +1270,7 @@ class AtomApplication extends EventEmitter { // :windowDimensions - Object with height and width keys. // :window - {AtomWindow} to open file paths in. // :addToLastWindow - Boolean of whether this should be opened in last focused window. - async openPaths ({ + async openPaths({ pathsToOpen, foldersToOpen, executedFrom, @@ -1028,19 +1285,21 @@ class AtomApplication extends EventEmitter { addToLastWindow, env } = {}) { - if (!env) env = process.env - if (!pathsToOpen) pathsToOpen = [] - if (!foldersToOpen) foldersToOpen = [] + if (!env) env = process.env; + if (!pathsToOpen) pathsToOpen = []; + if (!foldersToOpen) foldersToOpen = []; - devMode = Boolean(devMode) - safeMode = Boolean(safeMode) - clearWindowState = Boolean(clearWindowState) + devMode = Boolean(devMode); + safeMode = Boolean(safeMode); + clearWindowState = Boolean(clearWindowState); const locationsToOpen = await Promise.all( - pathsToOpen.map(pathToOpen => this.parsePathToOpen(pathToOpen, executedFrom, { - hasWaitSession: pidToKillWhenClosed != null - })) - ) + pathsToOpen.map(pathToOpen => + this.parsePathToOpen(pathToOpen, executedFrom, { + hasWaitSession: pidToKillWhenClosed != null + }) + ) + ); for (const folderToOpen of foldersToOpen) { locationsToOpen.push({ @@ -1050,81 +1309,104 @@ class AtomApplication extends EventEmitter { exists: true, isDirectory: true, hasWaitSession: pidToKillWhenClosed != null - }) + }); } if (locationsToOpen.length === 0) { - return + return; } - const hasNonEmptyPath = locationsToOpen.some(location => location.pathToOpen) - const createNewWindow = newWindow || !hasNonEmptyPath + const hasNonEmptyPath = locationsToOpen.some( + location => location.pathToOpen + ); + const createNewWindow = newWindow || !hasNonEmptyPath; - let existingWindow + let existingWindow; if (!createNewWindow) { // An explicitly provided AtomWindow has precedence. - existingWindow = window + existingWindow = window; // If no window is specified and at least one path is provided, locate an existing window that contains all // provided paths. if (!existingWindow && hasNonEmptyPath) { - existingWindow = this.windowForLocations(locationsToOpen, devMode, safeMode) + existingWindow = this.windowForLocations( + locationsToOpen, + devMode, + safeMode + ); } // No window specified, no existing window found, and addition to the last window requested. Find the last // focused window that matches the requested dev and safe modes. if (!existingWindow && addToLastWindow) { existingWindow = this.getLastFocusedWindow(win => { - return !win.isSpec && win.devMode === devMode && win.safeMode === safeMode - }) + return ( + !win.isSpec && win.devMode === devMode && win.safeMode === safeMode + ); + }); } // Fall back to the last focused window that has no project roots. if (!existingWindow) { - existingWindow = this.getLastFocusedWindow(win => !win.isSpec && !win.hasProjectPaths()) + existingWindow = this.getLastFocusedWindow( + win => !win.isSpec && !win.hasProjectPaths() + ); } // One last case: if *no* paths are directories, add to the last focused window. if (!existingWindow) { - const noDirectories = locationsToOpen.every(location => !location.isDirectory) + const noDirectories = locationsToOpen.every( + location => !location.isDirectory + ); if (noDirectories) { existingWindow = this.getLastFocusedWindow(win => { - return !win.isSpec && win.devMode === devMode && win.safeMode === safeMode - }) + return ( + !win.isSpec && + win.devMode === devMode && + win.safeMode === safeMode + ); + }); } } } - let openedWindow + let openedWindow; if (existingWindow) { - openedWindow = existingWindow - StartupTime.addMarker('main-process:atom-application:open-in-existing') - openedWindow.openLocations(locationsToOpen) + openedWindow = existingWindow; + StartupTime.addMarker('main-process:atom-application:open-in-existing'); + openedWindow.openLocations(locationsToOpen); if (openedWindow.isMinimized()) { - openedWindow.restore() + openedWindow.restore(); } else { - openedWindow.focus() + openedWindow.focus(); } - openedWindow.replaceEnvironment(env) + openedWindow.replaceEnvironment(env); } else { - let resourcePath, windowInitializationScript + let resourcePath, windowInitializationScript; if (devMode) { try { windowInitializationScript = require.resolve( - path.join(this.devResourcePath, 'src', 'initialize-application-window') - ) - resourcePath = this.devResourcePath + path.join( + this.devResourcePath, + 'src', + 'initialize-application-window' + ) + ); + resourcePath = this.devResourcePath; } catch (error) {} } if (!windowInitializationScript) { - windowInitializationScript = require.resolve('../initialize-application-window') + windowInitializationScript = require.resolve( + '../initialize-application-window' + ); } - if (!resourcePath) resourcePath = this.resourcePath - if (!windowDimensions) windowDimensions = this.getDimensionsForNewWindow() + if (!resourcePath) resourcePath = this.resourcePath; + if (!windowDimensions) + windowDimensions = this.getDimensionsForNewWindow(); - StartupTime.addMarker('main-process:atom-application:create-window') + StartupTime.addMarker('main-process:atom-application:create-window'); openedWindow = this.createWindow({ locationsToOpen, windowInitializationScript, @@ -1135,87 +1417,95 @@ class AtomApplication extends EventEmitter { profileStartup, clearWindowState, env - }) - this.addWindow(openedWindow) - openedWindow.focus() + }); + this.addWindow(openedWindow); + openedWindow.focus(); } if (pidToKillWhenClosed != null) { if (!this.waitSessionsByWindow.has(openedWindow)) { - this.waitSessionsByWindow.set(openedWindow, []) + this.waitSessionsByWindow.set(openedWindow, []); } this.waitSessionsByWindow.get(openedWindow).push({ pid: pidToKillWhenClosed, - remainingPaths: new Set(locationsToOpen.map(location => location.pathToOpen).filter(Boolean)) - }) + remainingPaths: new Set( + locationsToOpen.map(location => location.pathToOpen).filter(Boolean) + ) + }); } - openedWindow.browserWindow.once('closed', () => this.killProcessesForWindow(openedWindow)) - return openedWindow + openedWindow.browserWindow.once('closed', () => + this.killProcessesForWindow(openedWindow) + ); + return openedWindow; } // Kill all processes associated with opened windows. - killAllProcesses () { + killAllProcesses() { for (let window of this.waitSessionsByWindow.keys()) { - this.killProcessesForWindow(window) + this.killProcessesForWindow(window); } } - killProcessesForWindow (window) { - const sessions = this.waitSessionsByWindow.get(window) - if (!sessions) return + killProcessesForWindow(window) { + const sessions = this.waitSessionsByWindow.get(window); + if (!sessions) return; for (const session of sessions) { - this.killProcess(session.pid) + this.killProcess(session.pid); } - this.waitSessionsByWindow.delete(window) + this.waitSessionsByWindow.delete(window); } - windowDidClosePathWithWaitSession (window, initialPath) { - const waitSessions = this.waitSessionsByWindow.get(window) - if (!waitSessions) return + windowDidClosePathWithWaitSession(window, initialPath) { + const waitSessions = this.waitSessionsByWindow.get(window); + if (!waitSessions) return; for (let i = waitSessions.length - 1; i >= 0; i--) { - const session = waitSessions[i] - session.remainingPaths.delete(initialPath) + const session = waitSessions[i]; + session.remainingPaths.delete(initialPath); if (session.remainingPaths.size === 0) { - this.killProcess(session.pid) - waitSessions.splice(i, 1) + this.killProcess(session.pid); + waitSessions.splice(i, 1); } } } // Kill the process with the given pid. - killProcess (pid) { + killProcess(pid) { try { - const parsedPid = parseInt(pid) - if (isFinite(parsedPid)) this._killProcess(parsedPid) + const parsedPid = parseInt(pid); + if (isFinite(parsedPid)) this._killProcess(parsedPid); } catch (error) { if (error.code !== 'ESRCH') { - console.log(`Killing process ${pid} failed: ${error.code != null ? error.code : error.message}`) + console.log( + `Killing process ${pid} failed: ${ + error.code != null ? error.code : error.message + }` + ); } } } - async saveCurrentWindowOptions (allowEmpty = false) { - if (this.quitting) return + async saveCurrentWindowOptions(allowEmpty = false) { + if (this.quitting) return; const state = { version: APPLICATION_STATE_VERSION, windows: this.getAllWindows() .filter(window => !window.isSpec) - .map(window => ({projectRoots: window.projectRoots})) - } - state.windows.reverse() + .map(window => ({ projectRoots: window.projectRoots })) + }; + state.windows.reverse(); if (state.windows.length > 0 || allowEmpty) { - await this.storageFolder.store('application.json', state) - this.emit('application:did-save-state') + await this.storageFolder.store('application.json', state); + this.emit('application:did-save-state'); } } - async loadPreviousWindowOptions () { - const state = await this.storageFolder.load('application.json') + async loadPreviousWindowOptions() { + const state = await this.storageFolder.load('application.json'); if (!state) { - return [] + return []; } if (state.version === APPLICATION_STATE_VERSION) { @@ -1226,7 +1516,7 @@ class AtomApplication extends EventEmitter { devMode: this.devMode, safeMode: this.safeMode, newWindow: true - })) + })); } else if (state.version === undefined) { // Atom <= 1.36.0 // Schema: [{initialPaths: ['', ...]}, ...] @@ -1234,23 +1524,30 @@ class AtomApplication extends EventEmitter { state.map(async windowState => { // Classify each window's initialPaths as directories or non-directories const classifiedPaths = await Promise.all( - windowState.initialPaths.map(initialPath => new Promise(resolve => { - fs.isDirectory(initialPath, isDir => resolve({initialPath, isDir})) - })) - ) + windowState.initialPaths.map( + initialPath => + new Promise(resolve => { + fs.isDirectory(initialPath, isDir => + resolve({ initialPath, isDir }) + ); + }) + ) + ); // Only accept initialPaths that are existing directories return { - foldersToOpen: classifiedPaths.filter(({isDir}) => isDir).map(({initialPath}) => initialPath), + foldersToOpen: classifiedPaths + .filter(({ isDir }) => isDir) + .map(({ initialPath }) => initialPath), devMode: this.devMode, safeMode: this.safeMode, newWindow: true - } + }; }) - ) + ); } else { // Unrecognized version (from a newer Atom?) - return [] + return []; } } @@ -1264,11 +1561,11 @@ class AtomApplication extends EventEmitter { // :urlToOpen - The atom:// url to open. // :devMode - Boolean to control the opened window's dev mode. // :safeMode - Boolean to control the opened window's safe mode. - openUrl ({urlToOpen, devMode, safeMode, env}) { - const parsedUrl = url.parse(urlToOpen, true) - if (parsedUrl.protocol !== 'atom:') return + openUrl({ urlToOpen, devMode, safeMode, env }) { + const parsedUrl = url.parse(urlToOpen, true); + if (parsedUrl.protocol !== 'atom:') return; - const pack = this.findPackageWithName(parsedUrl.host, devMode) + const pack = this.findPackageWithName(parsedUrl.host, devMode); if (pack && pack.urlMain) { return this.openPackageUrlMain( parsedUrl.host, @@ -1277,43 +1574,60 @@ class AtomApplication extends EventEmitter { devMode, safeMode, env - ) + ); } else { - return this.openPackageUriHandler(urlToOpen, parsedUrl, devMode, safeMode, env) + return this.openPackageUriHandler( + urlToOpen, + parsedUrl, + devMode, + safeMode, + env + ); } } - openPackageUriHandler (url, parsedUrl, devMode, safeMode, env) { - let bestWindow + openPackageUriHandler(url, parsedUrl, devMode, safeMode, env) { + let bestWindow; if (parsedUrl.host === 'core') { - const predicate = require('../core-uri-handlers').windowPredicate(parsedUrl) - bestWindow = this.getLastFocusedWindow(win => !win.isSpecWindow() && predicate(win)) + const predicate = require('../core-uri-handlers').windowPredicate( + parsedUrl + ); + bestWindow = this.getLastFocusedWindow( + win => !win.isSpecWindow() && predicate(win) + ); } - if (!bestWindow) bestWindow = this.getLastFocusedWindow(win => !win.isSpecWindow()) + if (!bestWindow) + bestWindow = this.getLastFocusedWindow(win => !win.isSpecWindow()); if (bestWindow) { - bestWindow.sendURIMessage(url) - bestWindow.focus() - return bestWindow + bestWindow.sendURIMessage(url); + bestWindow.focus(); + return bestWindow; } else { - let windowInitializationScript - let {resourcePath} = this + let windowInitializationScript; + let { resourcePath } = this; if (devMode) { try { windowInitializationScript = require.resolve( - path.join(this.devResourcePath, 'src', 'initialize-application-window') - ) - resourcePath = this.devResourcePath + path.join( + this.devResourcePath, + 'src', + 'initialize-application-window' + ) + ); + resourcePath = this.devResourcePath; } catch (error) {} } if (!windowInitializationScript) { - windowInitializationScript = require.resolve('../initialize-application-window') + windowInitializationScript = require.resolve( + '../initialize-application-window' + ); } - const windowDimensions = this.getDimensionsForNewWindow() + const windowDimensions = this.getDimensionsForNewWindow(); const window = this.createWindow({ resourcePath, windowInitializationScript, @@ -1321,23 +1635,35 @@ class AtomApplication extends EventEmitter { safeMode, windowDimensions, env - }) - this.addWindow(window) - window.on('window:loaded', () => window.sendURIMessage(url)) - return window + }); + this.addWindow(window); + window.on('window:loaded', () => window.sendURIMessage(url)); + return window; } } - findPackageWithName (packageName, devMode) { - return this.getPackageManager(devMode).getAvailablePackageMetadata().find(({name}) => - name === packageName - ) + findPackageWithName(packageName, devMode) { + return this.getPackageManager(devMode) + .getAvailablePackageMetadata() + .find(({ name }) => name === packageName); } - openPackageUrlMain (packageName, packageUrlMain, urlToOpen, devMode, safeMode, env) { - const packagePath = this.getPackageManager(devMode).resolvePackagePath(packageName) - const windowInitializationScript = path.resolve(packagePath, packageUrlMain) - const windowDimensions = this.getDimensionsForNewWindow() + openPackageUrlMain( + packageName, + packageUrlMain, + urlToOpen, + devMode, + safeMode, + env + ) { + const packagePath = this.getPackageManager(devMode).resolvePackagePath( + packageName + ); + const windowInitializationScript = path.resolve( + packagePath, + packageUrlMain + ); + const windowDimensions = this.getDimensionsForNewWindow(); const window = this.createWindow({ windowInitializationScript, resourcePath: this.resourcePath, @@ -1346,23 +1672,23 @@ class AtomApplication extends EventEmitter { urlToOpen, windowDimensions, env - }) - this.addWindow(window) - return window + }); + this.addWindow(window); + return window; } - getPackageManager (devMode) { + getPackageManager(devMode) { if (this.packages == null) { - const PackageManager = require('../package-manager') - this.packages = new PackageManager({}) + const PackageManager = require('../package-manager'); + this.packages = new PackageManager({}); this.packages.initialize({ configDirPath: process.env.ATOM_HOME, devMode, resourcePath: this.resourcePath - }) + }); } - return this.packages + return this.packages; } // Opens up a new {AtomWindow} to run specs within. @@ -1374,51 +1700,60 @@ class AtomApplication extends EventEmitter { // :specPath - The directory to load specs from. // :safeMode - A Boolean that, if true, won't run specs from ~/.atom/packages // and ~/.atom/dev/packages, defaults to false. - runTests ({headless, resourcePath, executedFrom, pathsToOpen, logFile, safeMode, timeout, env}) { - let windowInitializationScript + runTests({ + headless, + resourcePath, + executedFrom, + pathsToOpen, + logFile, + safeMode, + timeout, + env + }) { + let windowInitializationScript; if (resourcePath !== this.resourcePath && !fs.existsSync(resourcePath)) { - ;({resourcePath} = this) + ({ resourcePath } = this); } - const timeoutInSeconds = Number.parseFloat(timeout) + const timeoutInSeconds = Number.parseFloat(timeout); if (!Number.isNaN(timeoutInSeconds)) { - const timeoutHandler = function () { + const timeoutHandler = function() { console.log( `The test suite has timed out because it has been running for more than ${timeoutInSeconds} seconds.` - ) - return process.exit(124) // Use the same exit code as the UNIX timeout util. - } - setTimeout(timeoutHandler, timeoutInSeconds * 1000) + ); + return process.exit(124); // Use the same exit code as the UNIX timeout util. + }; + setTimeout(timeoutHandler, timeoutInSeconds * 1000); } try { windowInitializationScript = require.resolve( path.resolve(this.devResourcePath, 'src', 'initialize-test-window') - ) + ); } catch (error) { windowInitializationScript = require.resolve( path.resolve(__dirname, '..', '..', 'src', 'initialize-test-window') - ) + ); } - const testPaths = [] + const testPaths = []; if (pathsToOpen != null) { for (let pathToOpen of pathsToOpen) { - testPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen))) + testPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen))); } } if (testPaths.length === 0) { - process.stderr.write('Error: Specify at least one test path\n\n') - process.exit(1) + process.stderr.write('Error: Specify at least one test path\n\n'); + process.exit(1); } - const legacyTestRunnerPath = this.resolveLegacyTestRunnerPath() - const testRunnerPath = this.resolveTestRunnerPath(testPaths[0]) - const devMode = true - const isSpec = true + const legacyTestRunnerPath = this.resolveLegacyTestRunnerPath(); + const testRunnerPath = this.resolveTestRunnerPath(testPaths[0]); + const devMode = true; + const isSpec = true; if (safeMode == null) { - safeMode = false + safeMode = false; } const window = this.createWindow({ windowInitializationScript, @@ -1432,43 +1767,58 @@ class AtomApplication extends EventEmitter { logFile, safeMode, env - }) - this.addWindow(window) - if (env) window.replaceEnvironment(env) - return window + }); + this.addWindow(window); + if (env) window.replaceEnvironment(env); + return window; } - runBenchmarks ({headless, test, resourcePath, executedFrom, pathsToOpen, env}) { - let windowInitializationScript + runBenchmarks({ + headless, + test, + resourcePath, + executedFrom, + pathsToOpen, + env + }) { + let windowInitializationScript; if (resourcePath !== this.resourcePath && !fs.existsSync(resourcePath)) { - ;({resourcePath} = this) + ({ resourcePath } = this); } try { windowInitializationScript = require.resolve( path.resolve(this.devResourcePath, 'src', 'initialize-benchmark-window') - ) + ); } catch (error) { windowInitializationScript = require.resolve( - path.resolve(__dirname, '..', '..', 'src', 'initialize-benchmark-window') - ) + path.resolve( + __dirname, + '..', + '..', + 'src', + 'initialize-benchmark-window' + ) + ); } - const benchmarkPaths = [] + const benchmarkPaths = []; if (pathsToOpen != null) { for (let pathToOpen of pathsToOpen) { - benchmarkPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen))) + benchmarkPaths.push( + path.resolve(executedFrom, fs.normalize(pathToOpen)) + ); } } if (benchmarkPaths.length === 0) { - process.stderr.write('Error: Specify at least one benchmark path.\n\n') - process.exit(1) + process.stderr.write('Error: Specify at least one benchmark path.\n\n'); + process.exit(1); } - const devMode = true - const isSpec = true - const safeMode = false + const devMode = true; + const isSpec = true; + const safeMode = false; const window = this.createWindow({ windowInitializationScript, resourcePath, @@ -1479,23 +1829,23 @@ class AtomApplication extends EventEmitter { benchmarkPaths, safeMode, env - }) - this.addWindow(window) - return window + }); + this.addWindow(window); + return window; } - resolveTestRunnerPath (testPath) { - let packageRoot + resolveTestRunnerPath(testPath) { + let packageRoot; if (FindParentDir == null) { - FindParentDir = require('find-parent-dir') + FindParentDir = require('find-parent-dir'); } if ((packageRoot = FindParentDir.sync(testPath, 'package.json'))) { - const packageMetadata = require(path.join(packageRoot, 'package.json')) + const packageMetadata = require(path.join(packageRoot, 'package.json')); if (packageMetadata.atomTestRunner) { - let testRunnerPath + let testRunnerPath; if (Resolve == null) { - Resolve = require('resolve') + Resolve = require('resolve'); } if ( (testRunnerPath = Resolve.sync(packageMetadata.atomTestRunner, { @@ -1503,79 +1853,90 @@ class AtomApplication extends EventEmitter { extensions: Object.keys(require.extensions) })) ) { - return testRunnerPath + return testRunnerPath; } else { process.stderr.write( - `Error: Could not resolve test runner path '${packageMetadata.atomTestRunner}'` - ) - process.exit(1) + `Error: Could not resolve test runner path '${ + packageMetadata.atomTestRunner + }'` + ); + process.exit(1); } } } - return this.resolveLegacyTestRunnerPath() + return this.resolveLegacyTestRunnerPath(); } - resolveLegacyTestRunnerPath () { + resolveLegacyTestRunnerPath() { try { - return require.resolve(path.resolve(this.devResourcePath, 'spec', 'jasmine-test-runner')) + return require.resolve( + path.resolve(this.devResourcePath, 'spec', 'jasmine-test-runner') + ); } catch (error) { - return require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner')) + return require.resolve( + path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner') + ); } } - async parsePathToOpen (pathToOpen, executedFrom, extra) { - const result = Object.assign({ - pathToOpen, - initialColumn: null, - initialLine: null, - exists: false, - isDirectory: false, - isFile: false - }, extra) + async parsePathToOpen(pathToOpen, executedFrom, extra) { + const result = Object.assign( + { + pathToOpen, + initialColumn: null, + initialLine: null, + exists: false, + isDirectory: false, + isFile: false + }, + extra + ); if (!pathToOpen) { - return result + return result; } - result.pathToOpen = result.pathToOpen.replace(/[:\s]+$/, '') - const match = result.pathToOpen.match(LocationSuffixRegExp) + result.pathToOpen = result.pathToOpen.replace(/[:\s]+$/, ''); + const match = result.pathToOpen.match(LocationSuffixRegExp); if (match != null) { - result.pathToOpen = result.pathToOpen.slice(0, -match[0].length) + result.pathToOpen = result.pathToOpen.slice(0, -match[0].length); if (match[1]) { - result.initialLine = Math.max(0, parseInt(match[1].slice(1), 10) - 1) + result.initialLine = Math.max(0, parseInt(match[1].slice(1), 10) - 1); } if (match[2]) { - result.initialColumn = Math.max(0, parseInt(match[2].slice(1), 10) - 1) + result.initialColumn = Math.max(0, parseInt(match[2].slice(1), 10) - 1); } } - const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(result.pathToOpen))) + const normalizedPath = path.normalize( + path.resolve(executedFrom, fs.normalize(result.pathToOpen)) + ); if (!url.parse(pathToOpen).protocol) { - result.pathToOpen = normalizedPath + result.pathToOpen = normalizedPath; } await new Promise((resolve, reject) => { fs.stat(result.pathToOpen, (err, st) => { if (err) { if (err.code === 'ENOENT' || err.code === 'EACCES') { - result.exists = false - resolve() + result.exists = false; + resolve(); } else { - reject(err) + reject(err); } - return + return; } - result.exists = true - result.isFile = st.isFile() - result.isDirectory = st.isDirectory() - resolve() - }) - }) + result.exists = true; + result.isFile = st.isFile(); + result.isDirectory = st.isDirectory(); + resolve(); + }); + }); - return result + return result; } // Opens a native dialog to prompt the user for a path. @@ -1593,136 +1954,160 @@ class AtomApplication extends EventEmitter { // all are files. // :path - An optional String which controls the default path to which the // file dialog opens. - promptForPathToOpen (type, {devMode, safeMode, window}, path = null) { + promptForPathToOpen(type, { devMode, safeMode, window }, path = null) { return this.promptForPath( type, async pathsToOpen => { - let targetWindow + let targetWindow; // Open in :window as long as no chosen paths are folders. If any chosen path is a folder, open in a // new window instead. if (type === 'folder') { - targetWindow = null + targetWindow = null; } else if (type === 'file') { - targetWindow = window + targetWindow = window; } else if (type === 'all') { const areDirectories = await Promise.all( - pathsToOpen.map(pathToOpen => new Promise(resolve => fs.isDirectory(pathToOpen, resolve))) - ) + pathsToOpen.map( + pathToOpen => + new Promise(resolve => fs.isDirectory(pathToOpen, resolve)) + ) + ); if (!areDirectories.some(Boolean)) { - targetWindow = window + targetWindow = window; } } - return this.openPaths({pathsToOpen, devMode, safeMode, window: targetWindow}) + return this.openPaths({ + pathsToOpen, + devMode, + safeMode, + window: targetWindow + }); }, path - ) + ); } - promptForPath (type, callback, path) { + promptForPath(type, callback, path) { const properties = (() => { switch (type) { - case 'file': return ['openFile'] - case 'folder': return ['openDirectory'] - case 'all': return ['openFile', 'openDirectory'] - default: throw new Error(`${type} is an invalid type for promptForPath`) + case 'file': + return ['openFile']; + case 'folder': + return ['openDirectory']; + case 'all': + return ['openFile', 'openDirectory']; + default: + throw new Error(`${type} is an invalid type for promptForPath`); } - })() + })(); // Show the open dialog as child window on Windows and Linux, and as an independent dialog on macOS. This matches // most native apps. - const parentWindow = process.platform === 'darwin' ? null : BrowserWindow.getFocusedWindow() + const parentWindow = + process.platform === 'darwin' ? null : BrowserWindow.getFocusedWindow(); const openOptions = { properties: properties.concat(['multiSelections', 'createDirectory']), title: (() => { switch (type) { - case 'file': return 'Open File' - case 'folder': return 'Open Folder' - default: return 'Open' + case 'file': + return 'Open File'; + case 'folder': + return 'Open Folder'; + default: + return 'Open'; } })() - } + }; // File dialog defaults to project directory of currently active editor - if (path) openOptions.defaultPath = path - dialog.showOpenDialog(parentWindow, openOptions, callback) + if (path) openOptions.defaultPath = path; + dialog.showOpenDialog(parentWindow, openOptions, callback); } - promptForRestart () { - dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { - type: 'warning', - title: 'Restart required', - message: 'You will need to restart Atom for this change to take effect.', - buttons: ['Restart Atom', 'Cancel'] - }, response => { if (response === 0) this.restart() }) + promptForRestart() { + dialog.showMessageBox( + BrowserWindow.getFocusedWindow(), + { + type: 'warning', + title: 'Restart required', + message: + 'You will need to restart Atom for this change to take effect.', + buttons: ['Restart Atom', 'Cancel'] + }, + response => { + if (response === 0) this.restart(); + } + ); } - restart () { - const args = [] - if (this.safeMode) args.push('--safe') - if (this.logFile != null) args.push(`--log-file=${this.logFile}`) - if (this.userDataDir != null) args.push(`--user-data-dir=${this.userDataDir}`) + restart() { + const args = []; + if (this.safeMode) args.push('--safe'); + if (this.logFile != null) args.push(`--log-file=${this.logFile}`); + if (this.userDataDir != null) + args.push(`--user-data-dir=${this.userDataDir}`); if (this.devMode) { - args.push('--dev') - args.push(`--resource-path=${this.resourcePath}`) + args.push('--dev'); + args.push(`--resource-path=${this.resourcePath}`); } - app.relaunch({args}) - app.quit() + app.relaunch({ args }); + app.quit(); } - disableZoomOnDisplayChange () { + disableZoomOnDisplayChange() { const callback = () => { - this.getAllWindows().map(window => window.disableZoom()) - } + this.getAllWindows().map(window => 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', callback) - screen.on('display-removed', callback) + screen.on('display-added', callback); + screen.on('display-removed', callback); return new Disposable(() => { - screen.removeListener('display-added', callback) - screen.removeListener('display-removed', callback) - }) + screen.removeListener('display-added', callback); + screen.removeListener('display-removed', callback); + }); } -} +}; class WindowStack { - constructor (windows = []) { - this.addWindow = this.addWindow.bind(this) - this.touch = this.touch.bind(this) - this.removeWindow = this.removeWindow.bind(this) - this.getLastFocusedWindow = this.getLastFocusedWindow.bind(this) - this.all = this.all.bind(this) - this.windows = windows + constructor(windows = []) { + this.addWindow = this.addWindow.bind(this); + this.touch = this.touch.bind(this); + this.removeWindow = this.removeWindow.bind(this); + this.getLastFocusedWindow = this.getLastFocusedWindow.bind(this); + this.all = this.all.bind(this); + this.windows = windows; } - addWindow (window) { - this.removeWindow(window) - return this.windows.unshift(window) + addWindow(window) { + this.removeWindow(window); + return this.windows.unshift(window); } - touch (window) { - return this.addWindow(window) + touch(window) { + return this.addWindow(window); } - removeWindow (window) { - const currentIndex = this.windows.indexOf(window) + removeWindow(window) { + const currentIndex = this.windows.indexOf(window); if (currentIndex > -1) { - return this.windows.splice(currentIndex, 1) + return this.windows.splice(currentIndex, 1); } } - getLastFocusedWindow (predicate) { + getLastFocusedWindow(predicate) { if (predicate == null) { - predicate = win => true + predicate = win => true; } - return this.windows.find(predicate) + return this.windows.find(predicate); } - all () { - return this.windows + all() { + return this.windows; } } diff --git a/src/main-process/atom-protocol-handler.js b/src/main-process/atom-protocol-handler.js index 7c870bc1a..173733d5c 100644 --- a/src/main-process/atom-protocol-handler.js +++ b/src/main-process/atom-protocol-handler.js @@ -1,6 +1,6 @@ -const {protocol} = require('electron') -const fs = require('fs-plus') -const path = require('path') +const { protocol } = require('electron'); +const fs = require('fs-plus'); +const path = require('path'); // Handles requests with 'atom' protocol. // @@ -13,43 +13,42 @@ const path = require('path') // * ~/.atom/packages // * RESOURCE_PATH/node_modules // -module.exports = -class AtomProtocolHandler { - constructor (resourcePath, safeMode) { - this.loadPaths = [] +module.exports = class AtomProtocolHandler { + constructor(resourcePath, safeMode) { + this.loadPaths = []; if (!safeMode) { - this.loadPaths.push(path.join(process.env.ATOM_HOME, 'dev', 'packages')) - this.loadPaths.push(path.join(resourcePath, 'packages')) + this.loadPaths.push(path.join(process.env.ATOM_HOME, 'dev', 'packages')); + this.loadPaths.push(path.join(resourcePath, 'packages')); } - this.loadPaths.push(path.join(process.env.ATOM_HOME, 'packages')) - this.loadPaths.push(path.join(resourcePath, 'node_modules')) + this.loadPaths.push(path.join(process.env.ATOM_HOME, 'packages')); + this.loadPaths.push(path.join(resourcePath, 'node_modules')); - this.registerAtomProtocol() + this.registerAtomProtocol(); } // Creates the 'atom' custom protocol handler. - registerAtomProtocol () { + registerAtomProtocol() { protocol.registerFileProtocol('atom', (request, callback) => { - const relativePath = path.normalize(request.url.substr(7)) + const relativePath = path.normalize(request.url.substr(7)); - let filePath + let filePath; if (relativePath.indexOf('assets/') === 0) { - const assetsPath = path.join(process.env.ATOM_HOME, relativePath) - const stat = fs.statSyncNoException(assetsPath) - if (stat && stat.isFile()) filePath = assetsPath + const assetsPath = path.join(process.env.ATOM_HOME, relativePath); + const stat = fs.statSyncNoException(assetsPath); + if (stat && stat.isFile()) filePath = assetsPath; } if (!filePath) { for (let loadPath of this.loadPaths) { - filePath = path.join(loadPath, relativePath) - const stat = fs.statSyncNoException(filePath) - if (stat && stat.isFile()) break + filePath = path.join(loadPath, relativePath); + const stat = fs.statSyncNoException(filePath); + if (stat && stat.isFile()) break; } } - callback(filePath) - }) + callback(filePath); + }); } -} +}; diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index 12f4944f7..330c56c62 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -1,34 +1,37 @@ -const {BrowserWindow, app, dialog, ipcMain} = require('electron') -const path = require('path') -const url = require('url') -const {EventEmitter} = require('events') -const StartupTime = require('../startup-time') +const { BrowserWindow, app, dialog, ipcMain } = require('electron'); +const path = require('path'); +const url = require('url'); +const { EventEmitter } = require('events'); +const StartupTime = require('../startup-time'); -const ICON_PATH = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') +const ICON_PATH = path.resolve(__dirname, '..', '..', 'resources', 'atom.png'); -let includeShellLoadTime = true -let nextId = 0 +let includeShellLoadTime = true; +let nextId = 0; -module.exports = -class AtomWindow extends EventEmitter { - constructor (atomApplication, fileRecoveryService, settings = {}) { - StartupTime.addMarker('main-process:atom-window:start') +module.exports = class AtomWindow extends EventEmitter { + constructor(atomApplication, fileRecoveryService, settings = {}) { + StartupTime.addMarker('main-process:atom-window:start'); - super() + super(); - this.id = nextId++ - this.atomApplication = atomApplication - this.fileRecoveryService = fileRecoveryService - this.isSpec = settings.isSpec - this.headless = settings.headless - this.safeMode = settings.safeMode - this.devMode = settings.devMode - this.resourcePath = settings.resourcePath + this.id = nextId++; + this.atomApplication = atomApplication; + this.fileRecoveryService = fileRecoveryService; + this.isSpec = settings.isSpec; + this.headless = settings.headless; + this.safeMode = settings.safeMode; + this.devMode = settings.devMode; + this.resourcePath = settings.resourcePath; - const locationsToOpen = settings.locationsToOpen || [] + const locationsToOpen = settings.locationsToOpen || []; - this.loadedPromise = new Promise(resolve => { this.resolveLoadedPromise = resolve }) - this.closedPromise = new Promise(resolve => { this.resolveClosedPromise = resolve }) + this.loadedPromise = new Promise(resolve => { + this.resolveLoadedPromise = resolve; + }); + this.closedPromise = new Promise(resolve => { + this.resolveClosedPromise = resolve; + }); const options = { show: false, @@ -45,17 +48,19 @@ class AtomWindow extends EventEmitter { // (Ref: https://github.com/atom/atom/pull/12696#issuecomment-290496960) disableBlinkFeatures: 'Auxclick' } - } + }; // Don't set icon on Windows so the exe's ico will be used as window and // taskbar's icon. See https://github.com/atom/atom/issues/4811 for more. - if (process.platform === 'linux') options.icon = ICON_PATH - if (this.shouldAddCustomTitleBar()) options.titleBarStyle = 'hidden' - if (this.shouldAddCustomInsetTitleBar()) options.titleBarStyle = 'hiddenInset' - if (this.shouldHideTitleBar()) options.frame = false + if (process.platform === 'linux') options.icon = ICON_PATH; + if (this.shouldAddCustomTitleBar()) options.titleBarStyle = 'hidden'; + if (this.shouldAddCustomInsetTitleBar()) + options.titleBarStyle = 'hiddenInset'; + if (this.shouldHideTitleBar()) options.frame = false; - const BrowserWindowConstructor = settings.browserWindowConstructor || BrowserWindow - this.browserWindow = new BrowserWindowConstructor(options) + const BrowserWindowConstructor = + settings.browserWindowConstructor || BrowserWindow; + this.browserWindow = new BrowserWindowConstructor(options); Object.defineProperty(this.browserWindow, 'loadSettingsJSON', { get: () => JSON.stringify(Object.assign({ @@ -91,40 +96,40 @@ class AtomWindow extends EventEmitter { // We only want to make the main process startup data available once, // so if the window is refreshed or a new window is opened, the // renderer process won't use it again. - const timingData = StartupTime.exportData() - StartupTime.deleteData() + const timingData = StartupTime.exportData(); + StartupTime.deleteData(); - return timingData + return timingData; } - }) + }); // Only send to the first non-spec window created if (includeShellLoadTime && !this.isSpec) { - includeShellLoadTime = false + includeShellLoadTime = false; if (!this.loadSettings.shellLoadTime) { - this.loadSettings.shellLoadTime = Date.now() - global.shellStartTime + this.loadSettings.shellLoadTime = Date.now() - global.shellStartTime; } } - if (!this.loadSettings.env) this.env = this.loadSettings.env + if (!this.loadSettings.env) this.env = this.loadSettings.env; this.browserWindow.on('window:loaded', () => { - this.disableZoom() - this.emit('window:loaded') - this.resolveLoadedPromise() - }) + this.disableZoom(); + this.emit('window:loaded'); + this.resolveLoadedPromise(); + }); this.browserWindow.on('window:locations-opened', () => { - this.emit('window:locations-opened') - }) + this.emit('window:locations-opened'); + }); this.browserWindow.on('enter-full-screen', () => { - this.browserWindow.webContents.send('did-enter-full-screen') - }) + this.browserWindow.webContents.send('did-enter-full-screen'); + }); this.browserWindow.on('leave-full-screen', () => { - this.browserWindow.webContents.send('did-leave-full-screen') - }) + this.browserWindow.webContents.send('did-leave-full-screen'); + }); this.browserWindow.loadURL( url.format({ @@ -132,340 +137,370 @@ class AtomWindow extends EventEmitter { pathname: `${this.resourcePath}/static/index.html`, slashes: true }) - ) + ); - this.browserWindow.showSaveDialog = this.showSaveDialog.bind(this) + this.browserWindow.showSaveDialog = this.showSaveDialog.bind(this); - if (this.isSpec) this.browserWindow.focusOnWebView() + if (this.isSpec) this.browserWindow.focusOnWebView(); - const hasPathToOpen = !(locationsToOpen.length === 1 && locationsToOpen[0].pathToOpen == null) - if (hasPathToOpen && !this.isSpecWindow()) this.openLocations(locationsToOpen) + const hasPathToOpen = !( + locationsToOpen.length === 1 && locationsToOpen[0].pathToOpen == null + ); + if (hasPathToOpen && !this.isSpecWindow()) + this.openLocations(locationsToOpen); } - hasProjectPaths () { - return this.projectRoots.length > 0 + hasProjectPaths() { + return this.projectRoots.length > 0; } - setupContextMenu () { - const ContextMenu = require('./context-menu') + setupContextMenu() { + const ContextMenu = require('./context-menu'); this.browserWindow.on('context-menu', menuTemplate => { - return new ContextMenu(menuTemplate, this) - }) + return new ContextMenu(menuTemplate, this); + }); } - containsLocations (locations) { - return locations.every(location => this.containsLocation(location)) + containsLocations(locations) { + return locations.every(location => this.containsLocation(location)); } - containsLocation (location) { - if (!location.pathToOpen) return false + containsLocation(location) { + if (!location.pathToOpen) return false; return this.projectRoots.some(projectPath => { - if (location.pathToOpen === projectPath) return true + if (location.pathToOpen === projectPath) return true; if (location.pathToOpen.startsWith(path.join(projectPath, path.sep))) { - if (!location.exists) return true - if (!location.isDirectory) return true + if (!location.exists) return true; + if (!location.isDirectory) return true; } - return false - }) + return false; + }); } - handleEvents () { + handleEvents() { this.browserWindow.on('close', async event => { - if ((!this.atomApplication.quitting || this.atomApplication.quittingForUpdate) && !this.unloading) { - event.preventDefault() - this.unloading = true - this.atomApplication.saveCurrentWindowOptions(false) - if (await this.prepareToUnload()) this.close() + if ( + (!this.atomApplication.quitting || + this.atomApplication.quittingForUpdate) && + !this.unloading + ) { + event.preventDefault(); + this.unloading = true; + this.atomApplication.saveCurrentWindowOptions(false); + if (await this.prepareToUnload()) this.close(); } - }) + }); this.browserWindow.on('closed', () => { - this.fileRecoveryService.didCloseWindow(this) - this.atomApplication.removeWindow(this) - this.resolveClosedPromise() - }) + this.fileRecoveryService.didCloseWindow(this); + this.atomApplication.removeWindow(this); + this.resolveClosedPromise(); + }); this.browserWindow.on('unresponsive', () => { - if (this.isSpec) return - dialog.showMessageBox(this.browserWindow, { - type: 'warning', - buttons: ['Force Close', 'Keep Waiting'], - cancelId: 1, // Canceling should be the least destructive action - message: 'Editor is not responding', - detail: - 'The editor is not responding. Would you like to force close it or just keep waiting?' - }, response => { if (response === 0) this.browserWindow.destroy() }) - }) + if (this.isSpec) return; + dialog.showMessageBox( + this.browserWindow, + { + type: 'warning', + buttons: ['Force Close', 'Keep Waiting'], + cancelId: 1, // Canceling should be the least destructive action + message: 'Editor is not responding', + detail: + 'The editor is not responding. Would you like to force close it or just keep waiting?' + }, + response => { + if (response === 0) this.browserWindow.destroy(); + } + ); + }); this.browserWindow.webContents.on('crashed', async () => { if (this.headless) { - console.log('Renderer process crashed, exiting') - this.atomApplication.exit(100) - return + console.log('Renderer process crashed, exiting'); + this.atomApplication.exit(100); + return; } - await this.fileRecoveryService.didCrashWindow(this) - dialog.showMessageBox(this.browserWindow, { - type: 'warning', - buttons: ['Close Window', 'Reload', 'Keep It Open'], - cancelId: 2, // Canceling should be the least destructive action - message: 'The editor has crashed', - detail: 'Please report this issue to https://github.com/atom/atom' - }, response => { - switch (response) { - case 0: return this.browserWindow.destroy() - case 1: return this.browserWindow.reload() + await this.fileRecoveryService.didCrashWindow(this); + dialog.showMessageBox( + this.browserWindow, + { + type: 'warning', + buttons: ['Close Window', 'Reload', 'Keep It Open'], + cancelId: 2, // Canceling should be the least destructive action + message: 'The editor has crashed', + detail: 'Please report this issue to https://github.com/atom/atom' + }, + response => { + switch (response) { + case 0: + return this.browserWindow.destroy(); + case 1: + return this.browserWindow.reload(); + } } - }) - }) + ); + }); this.browserWindow.webContents.on('will-navigate', (event, url) => { - if (url !== this.browserWindow.webContents.getURL()) event.preventDefault() - }) + if (url !== this.browserWindow.webContents.getURL()) + event.preventDefault(); + }); - this.setupContextMenu() + this.setupContextMenu(); // Spec window's web view should always have focus - if (this.isSpec) this.browserWindow.on('blur', () => this.browserWindow.focusOnWebView()) + if (this.isSpec) + this.browserWindow.on('blur', () => this.browserWindow.focusOnWebView()); } - async prepareToUnload () { - if (this.isSpecWindow()) return true + async prepareToUnload() { + if (this.isSpecWindow()) return true; this.lastPrepareToUnloadPromise = new Promise(resolve => { const callback = (event, result) => { - if (BrowserWindow.fromWebContents(event.sender) === this.browserWindow) { - ipcMain.removeListener('did-prepare-to-unload', callback) + if ( + BrowserWindow.fromWebContents(event.sender) === this.browserWindow + ) { + ipcMain.removeListener('did-prepare-to-unload', callback); if (!result) { - this.unloading = false - this.atomApplication.quitting = false + this.unloading = false; + this.atomApplication.quitting = false; } - resolve(result) + resolve(result); } - } - ipcMain.on('did-prepare-to-unload', callback) - this.browserWindow.webContents.send('prepare-to-unload') - }) + }; + ipcMain.on('did-prepare-to-unload', callback); + this.browserWindow.webContents.send('prepare-to-unload'); + }); - return this.lastPrepareToUnloadPromise + return this.lastPrepareToUnloadPromise; } - openPath (pathToOpen, initialLine, initialColumn) { - return this.openLocations([{pathToOpen, initialLine, initialColumn}]) + openPath(pathToOpen, initialLine, initialColumn) { + return this.openLocations([{ pathToOpen, initialLine, initialColumn }]); } - async openLocations (locationsToOpen) { - this.addLocationsToOpen(locationsToOpen) - await this.loadedPromise - this.sendMessage('open-locations', locationsToOpen) + async openLocations(locationsToOpen) { + this.addLocationsToOpen(locationsToOpen); + await this.loadedPromise; + this.sendMessage('open-locations', locationsToOpen); } - didChangeUserSettings (settings) { - this.sendMessage('did-change-user-settings', settings) + didChangeUserSettings(settings) { + this.sendMessage('did-change-user-settings', settings); } - didFailToReadUserSettings (message) { - this.sendMessage('did-fail-to-read-user-settings', message) + didFailToReadUserSettings(message) { + this.sendMessage('did-fail-to-read-user-settings', message); } - addLocationsToOpen (locationsToOpen) { - const roots = new Set(this.projectRoots || []) - for (const {pathToOpen, isDirectory} of locationsToOpen) { + addLocationsToOpen(locationsToOpen) { + const roots = new Set(this.projectRoots || []); + for (const { pathToOpen, isDirectory } of locationsToOpen) { if (isDirectory) { - roots.add(pathToOpen) + roots.add(pathToOpen); } } - this.projectRoots = Array.from(roots) - this.projectRoots.sort() + this.projectRoots = Array.from(roots); + this.projectRoots.sort(); } - replaceEnvironment (env) { - this.browserWindow.webContents.send('environment', env) + replaceEnvironment(env) { + this.browserWindow.webContents.send('environment', env); } - sendMessage (message, detail) { - this.browserWindow.webContents.send('message', message, detail) + sendMessage(message, detail) { + this.browserWindow.webContents.send('message', message, detail); } - sendCommand (command, ...args) { + sendCommand(command, ...args) { if (this.isSpecWindow()) { if (!this.atomApplication.sendCommandToFirstResponder(command)) { switch (command) { - case 'window:reload': return this.reload() - case 'window:toggle-dev-tools': return this.toggleDevTools() - case 'window:close': return this.close() + case 'window:reload': + return this.reload(); + case 'window:toggle-dev-tools': + return this.toggleDevTools(); + case 'window:close': + return this.close(); } } } else if (this.isWebViewFocused()) { - this.sendCommandToBrowserWindow(command, ...args) + this.sendCommandToBrowserWindow(command, ...args); } else if (!this.atomApplication.sendCommandToFirstResponder(command)) { - this.sendCommandToBrowserWindow(command, ...args) + this.sendCommandToBrowserWindow(command, ...args); } } - sendURIMessage (uri) { - this.browserWindow.webContents.send('uri-message', uri) + sendURIMessage(uri) { + this.browserWindow.webContents.send('uri-message', uri); } - sendCommandToBrowserWindow (command, ...args) { - const action = args[0] && args[0].contextCommand - ? 'context-command' - : 'command' - this.browserWindow.webContents.send(action, command, ...args) + sendCommandToBrowserWindow(command, ...args) { + const action = + args[0] && args[0].contextCommand ? 'context-command' : 'command'; + this.browserWindow.webContents.send(action, command, ...args); } - getDimensions () { - const [x, y] = Array.from(this.browserWindow.getPosition()) - const [width, height] = Array.from(this.browserWindow.getSize()) - return {x, y, width, height} + getDimensions() { + const [x, y] = Array.from(this.browserWindow.getPosition()); + const [width, height] = Array.from(this.browserWindow.getSize()); + return { x, y, width, height }; } - shouldAddCustomTitleBar () { + shouldAddCustomTitleBar() { return ( !this.isSpec && process.platform === 'darwin' && this.atomApplication.config.get('core.titleBar') === 'custom' - ) + ); } - shouldAddCustomInsetTitleBar () { + shouldAddCustomInsetTitleBar() { return ( !this.isSpec && process.platform === 'darwin' && this.atomApplication.config.get('core.titleBar') === 'custom-inset' - ) + ); } - shouldHideTitleBar () { + shouldHideTitleBar() { return ( !this.isSpec && process.platform === 'darwin' && this.atomApplication.config.get('core.titleBar') === 'hidden' - ) + ); } - close () { - return this.browserWindow.close() + close() { + return this.browserWindow.close(); } - focus () { - return this.browserWindow.focus() + focus() { + return this.browserWindow.focus(); } - minimize () { - return this.browserWindow.minimize() + minimize() { + return this.browserWindow.minimize(); } - maximize () { - return this.browserWindow.maximize() + maximize() { + return this.browserWindow.maximize(); } - unmaximize () { - return this.browserWindow.unmaximize() + unmaximize() { + return this.browserWindow.unmaximize(); } - restore () { - return this.browserWindow.restore() + restore() { + return this.browserWindow.restore(); } - setFullScreen (fullScreen) { - return this.browserWindow.setFullScreen(fullScreen) + setFullScreen(fullScreen) { + return this.browserWindow.setFullScreen(fullScreen); } - setAutoHideMenuBar (autoHideMenuBar) { - return this.browserWindow.setAutoHideMenuBar(autoHideMenuBar) + setAutoHideMenuBar(autoHideMenuBar) { + return this.browserWindow.setAutoHideMenuBar(autoHideMenuBar); } - handlesAtomCommands () { - return !this.isSpecWindow() && this.isWebViewFocused() + handlesAtomCommands() { + return !this.isSpecWindow() && this.isWebViewFocused(); } - isFocused () { - return this.browserWindow.isFocused() + isFocused() { + return this.browserWindow.isFocused(); } - isMaximized () { - return this.browserWindow.isMaximized() + isMaximized() { + return this.browserWindow.isMaximized(); } - isMinimized () { - return this.browserWindow.isMinimized() + isMinimized() { + return this.browserWindow.isMinimized(); } - isWebViewFocused () { - return this.browserWindow.isWebViewFocused() + isWebViewFocused() { + return this.browserWindow.isWebViewFocused(); } - isSpecWindow () { - return this.isSpec + isSpecWindow() { + return this.isSpec; } - reload () { - this.loadedPromise = new Promise(resolve => { this.resolveLoadedPromise = resolve }) + reload() { + this.loadedPromise = new Promise(resolve => { + this.resolveLoadedPromise = resolve; + }); this.prepareToUnload().then(canUnload => { - if (canUnload) this.browserWindow.reload() - }) - return this.loadedPromise + if (canUnload) this.browserWindow.reload(); + }); + return this.loadedPromise; } - showSaveDialog (options, callback) { - options = Object.assign({ - title: 'Save File', - defaultPath: this.projectRoots[0] - }, options) + showSaveDialog(options, callback) { + options = Object.assign( + { + title: 'Save File', + defaultPath: this.projectRoots[0] + }, + options + ); if (typeof callback === 'function') { // Async - dialog.showSaveDialog(this.browserWindow, options, callback) + dialog.showSaveDialog(this.browserWindow, options, callback); } else { // Sync - return dialog.showSaveDialog(this.browserWindow, options) + return dialog.showSaveDialog(this.browserWindow, options); } } - toggleDevTools () { - return this.browserWindow.toggleDevTools() + toggleDevTools() { + return this.browserWindow.toggleDevTools(); } - openDevTools () { - return this.browserWindow.openDevTools() + openDevTools() { + return this.browserWindow.openDevTools(); } - closeDevTools () { - return this.browserWindow.closeDevTools() + closeDevTools() { + return this.browserWindow.closeDevTools(); } - setDocumentEdited (documentEdited) { - return this.browserWindow.setDocumentEdited(documentEdited) + setDocumentEdited(documentEdited) { + return this.browserWindow.setDocumentEdited(documentEdited); } - setRepresentedFilename (representedFilename) { - return this.browserWindow.setRepresentedFilename(representedFilename) + setRepresentedFilename(representedFilename) { + return this.browserWindow.setRepresentedFilename(representedFilename); } - setProjectRoots (projectRootPaths) { - this.projectRoots = projectRootPaths - this.projectRoots.sort() - this.loadSettings.initialProjectRoots = this.projectRoots - return this.atomApplication.saveCurrentWindowOptions() + setProjectRoots(projectRootPaths) { + this.projectRoots = projectRootPaths; + this.projectRoots.sort(); + this.loadSettings.initialProjectRoots = this.projectRoots; + return this.atomApplication.saveCurrentWindowOptions(); } - didClosePathWithWaitSession (path) { - this.atomApplication.windowDidClosePathWithWaitSession(this, path) + didClosePathWithWaitSession(path) { + this.atomApplication.windowDidClosePathWithWaitSession(this, path); } - copy () { - return this.browserWindow.copy() + copy() { + return this.browserWindow.copy(); } - disableZoom () { - return this.browserWindow.webContents.setVisualZoomLevelLimits(1, 1) + disableZoom() { + return this.browserWindow.webContents.setVisualZoomLevelLimits(1, 1); } - getLoadedPromise () { - return this.loadedPromise + getLoadedPromise() { + return this.loadedPromise; } -} +}; diff --git a/src/main-process/auto-update-manager.js b/src/main-process/auto-update-manager.js index bed2e6c2d..4f9446d6c 100644 --- a/src/main-process/auto-update-manager.js +++ b/src/main-process/auto-update-manager.js @@ -1,179 +1,203 @@ -const {EventEmitter} = require('events') -const path = require('path') +const { EventEmitter } = require('events'); +const path = require('path'); -const IdleState = 'idle' -const CheckingState = 'checking' -const DownloadingState = 'downloading' -const UpdateAvailableState = 'update-available' -const NoUpdateAvailableState = 'no-update-available' -const UnsupportedState = 'unsupported' -const ErrorState = 'error' +const IdleState = 'idle'; +const CheckingState = 'checking'; +const DownloadingState = 'downloading'; +const UpdateAvailableState = 'update-available'; +const NoUpdateAvailableState = 'no-update-available'; +const UnsupportedState = 'unsupported'; +const ErrorState = 'error'; -let autoUpdater = null +let autoUpdater = null; -module.exports = -class AutoUpdateManager extends EventEmitter { - constructor (version, testMode, config) { - super() - this.onUpdateNotAvailable = this.onUpdateNotAvailable.bind(this) - this.onUpdateError = this.onUpdateError.bind(this) - this.version = version - this.testMode = testMode - this.config = config - this.state = IdleState - this.iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') - this.updateUrlPrefix = process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io' +module.exports = class AutoUpdateManager extends EventEmitter { + constructor(version, testMode, config) { + super(); + this.onUpdateNotAvailable = this.onUpdateNotAvailable.bind(this); + this.onUpdateError = this.onUpdateError.bind(this); + this.version = version; + this.testMode = testMode; + this.config = config; + this.state = IdleState; + this.iconPath = path.resolve( + __dirname, + '..', + '..', + 'resources', + 'atom.png' + ); + this.updateUrlPrefix = + process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io'; } - initialize () { + initialize() { if (process.platform === 'win32') { - const archSuffix = process.arch === 'ia32' ? '' : `-${process.arch}` - this.feedUrl = `${this.updateUrlPrefix}/api/updates${archSuffix}?version=${this.version}` - autoUpdater = require('./auto-updater-win32') + const archSuffix = process.arch === 'ia32' ? '' : `-${process.arch}`; + this.feedUrl = `${ + this.updateUrlPrefix + }/api/updates${archSuffix}?version=${this.version}`; + autoUpdater = require('./auto-updater-win32'); } else { - this.feedUrl = `${this.updateUrlPrefix}/api/updates?version=${this.version}`; - ({autoUpdater} = require('electron')) + this.feedUrl = `${this.updateUrlPrefix}/api/updates?version=${ + this.version + }`; + ({ autoUpdater } = require('electron')); } autoUpdater.on('error', (event, message) => { - this.setState(ErrorState, message) - this.emitWindowEvent('update-error') - console.error(`Error Downloading Update: ${message}`) - }) + this.setState(ErrorState, message); + this.emitWindowEvent('update-error'); + console.error(`Error Downloading Update: ${message}`); + }); - autoUpdater.setFeedURL(this.feedUrl) + autoUpdater.setFeedURL(this.feedUrl); autoUpdater.on('checking-for-update', () => { - this.setState(CheckingState) - this.emitWindowEvent('checking-for-update') - }) + this.setState(CheckingState); + this.emitWindowEvent('checking-for-update'); + }); autoUpdater.on('update-not-available', () => { - this.setState(NoUpdateAvailableState) - this.emitWindowEvent('update-not-available') - }) + this.setState(NoUpdateAvailableState); + this.emitWindowEvent('update-not-available'); + }); autoUpdater.on('update-available', () => { - this.setState(DownloadingState) + this.setState(DownloadingState); // We use sendMessage to send an event called 'update-available' in 'update-downloaded' // once the update download is complete. This mismatch between the electron // autoUpdater events is unfortunate but in the interest of not changing the // one existing event handled by applicationDelegate - this.emitWindowEvent('did-begin-downloading-update') - this.emit('did-begin-download') - }) + this.emitWindowEvent('did-begin-downloading-update'); + this.emit('did-begin-download'); + }); - autoUpdater.on('update-downloaded', (event, releaseNotes, releaseVersion) => { - this.releaseVersion = releaseVersion - this.setState(UpdateAvailableState) - this.emitUpdateAvailableEvent() - }) - - this.config.onDidChange('core.automaticallyUpdate', ({newValue}) => { - if (newValue) { - this.scheduleUpdateCheck() - } else { - this.cancelScheduledUpdateCheck() + autoUpdater.on( + 'update-downloaded', + (event, releaseNotes, releaseVersion) => { + this.releaseVersion = releaseVersion; + this.setState(UpdateAvailableState); + this.emitUpdateAvailableEvent(); } - }) + ); - if (this.config.get('core.automaticallyUpdate')) this.scheduleUpdateCheck() + this.config.onDidChange('core.automaticallyUpdate', ({ newValue }) => { + if (newValue) { + this.scheduleUpdateCheck(); + } else { + this.cancelScheduledUpdateCheck(); + } + }); + + if (this.config.get('core.automaticallyUpdate')) this.scheduleUpdateCheck(); switch (process.platform) { case 'win32': if (!autoUpdater.supportsUpdates()) { - this.setState(UnsupportedState) + this.setState(UnsupportedState); } - break + break; case 'linux': - this.setState(UnsupportedState) + this.setState(UnsupportedState); } } - emitUpdateAvailableEvent () { - if (this.releaseVersion == null) return - this.emitWindowEvent('update-available', {releaseVersion: this.releaseVersion}) + emitUpdateAvailableEvent() { + if (this.releaseVersion == null) return; + this.emitWindowEvent('update-available', { + releaseVersion: this.releaseVersion + }); } - emitWindowEvent (eventName, payload) { + emitWindowEvent(eventName, payload) { for (let atomWindow of this.getWindows()) { - atomWindow.sendMessage(eventName, payload) + atomWindow.sendMessage(eventName, payload); } } - setState (state, errorMessage) { - if (this.state === state) return - this.state = state - this.errorMessage = errorMessage - this.emit('state-changed', this.state) + setState(state, errorMessage) { + if (this.state === state) return; + this.state = state; + this.errorMessage = errorMessage; + this.emit('state-changed', this.state); } - getState () { - return this.state + getState() { + return this.state; } - getErrorMessage () { - return this.errorMessage + getErrorMessage() { + return this.errorMessage; } - scheduleUpdateCheck () { + scheduleUpdateCheck() { // Only schedule update check periodically if running in release version and // and there is no existing scheduled update check. if (!/-dev/.test(this.version) && !this.checkForUpdatesIntervalID) { - const checkForUpdates = () => this.check({hidePopups: true}) - const fourHours = 1000 * 60 * 60 * 4 - this.checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours) - checkForUpdates() + const checkForUpdates = () => this.check({ hidePopups: true }); + const fourHours = 1000 * 60 * 60 * 4; + this.checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours); + checkForUpdates(); } } - cancelScheduledUpdateCheck () { + cancelScheduledUpdateCheck() { if (this.checkForUpdatesIntervalID) { - clearInterval(this.checkForUpdatesIntervalID) - this.checkForUpdatesIntervalID = null + clearInterval(this.checkForUpdatesIntervalID); + this.checkForUpdatesIntervalID = null; } } - check ({hidePopups} = {}) { + check({ hidePopups } = {}) { if (!hidePopups) { - autoUpdater.once('update-not-available', this.onUpdateNotAvailable) - autoUpdater.once('error', this.onUpdateError) + autoUpdater.once('update-not-available', this.onUpdateNotAvailable); + autoUpdater.once('error', this.onUpdateError); } - if (!this.testMode) autoUpdater.checkForUpdates() + if (!this.testMode) autoUpdater.checkForUpdates(); } - install () { - if (!this.testMode) autoUpdater.quitAndInstall() + install() { + if (!this.testMode) autoUpdater.quitAndInstall(); } - onUpdateNotAvailable () { - autoUpdater.removeListener('error', this.onUpdateError) - const {dialog} = require('electron') - dialog.showMessageBox({ - type: 'info', - buttons: ['OK'], - icon: this.iconPath, - message: 'No update available.', - title: 'No Update Available', - detail: `Version ${this.version} is the latest version.` - }, () => {}) // noop callback to get async behavior + onUpdateNotAvailable() { + autoUpdater.removeListener('error', this.onUpdateError); + const { dialog } = require('electron'); + dialog.showMessageBox( + { + type: 'info', + buttons: ['OK'], + icon: this.iconPath, + message: 'No update available.', + title: 'No Update Available', + detail: `Version ${this.version} is the latest version.` + }, + () => {} + ); // noop callback to get async behavior } - onUpdateError (event, message) { - autoUpdater.removeListener('update-not-available', this.onUpdateNotAvailable) - const {dialog} = require('electron') - dialog.showMessageBox({ - type: 'warning', - buttons: ['OK'], - icon: this.iconPath, - message: 'There was an error checking for updates.', - title: 'Update Error', - detail: message - }, () => {}) // noop callback to get async behavior + onUpdateError(event, message) { + autoUpdater.removeListener( + 'update-not-available', + this.onUpdateNotAvailable + ); + const { dialog } = require('electron'); + dialog.showMessageBox( + { + type: 'warning', + buttons: ['OK'], + icon: this.iconPath, + message: 'There was an error checking for updates.', + title: 'Update Error', + detail: message + }, + () => {} + ); // noop callback to get async behavior } - getWindows () { - return global.atomApplication.getAllWindows() + getWindows() { + return global.atomApplication.getAllWindows(); } -} +}; diff --git a/src/main-process/auto-updater-win32.js b/src/main-process/auto-updater-win32.js index 053ef040d..f0e583a66 100644 --- a/src/main-process/auto-updater-win32.js +++ b/src/main-process/auto-updater-win32.js @@ -1,74 +1,80 @@ -const {EventEmitter} = require('events') -const SquirrelUpdate = require('./squirrel-update') +const { EventEmitter } = require('events'); +const SquirrelUpdate = require('./squirrel-update'); class AutoUpdater extends EventEmitter { - setFeedURL (updateUrl) { - this.updateUrl = updateUrl + setFeedURL(updateUrl) { + this.updateUrl = updateUrl; } - quitAndInstall () { + quitAndInstall() { if (SquirrelUpdate.existsSync()) { - SquirrelUpdate.restartAtom(require('electron').app) + SquirrelUpdate.restartAtom(require('electron').app); } else { - require('electron').autoUpdater.quitAndInstall() + require('electron').autoUpdater.quitAndInstall(); } } - downloadUpdate (callback) { - SquirrelUpdate.spawn(['--download', this.updateUrl], function (error, stdout) { - let update - if (error != null) return callback(error) + downloadUpdate(callback) { + SquirrelUpdate.spawn(['--download', this.updateUrl], function( + error, + stdout + ) { + let update; + if (error != null) return callback(error); try { // Last line of output is the JSON details about the releases - const json = stdout.trim().split('\n').pop() - const data = JSON.parse(json) - const releasesToApply = data && data.releasesToApply - if (releasesToApply.pop) update = releasesToApply.pop() + const json = stdout + .trim() + .split('\n') + .pop(); + const data = JSON.parse(json); + const releasesToApply = data && data.releasesToApply; + if (releasesToApply.pop) update = releasesToApply.pop(); } catch (error) { - error.stdout = stdout - return callback(error) + error.stdout = stdout; + return callback(error); } - callback(null, update) - }) + callback(null, update); + }); } - installUpdate (callback) { - SquirrelUpdate.spawn(['--update', this.updateUrl], callback) + installUpdate(callback) { + SquirrelUpdate.spawn(['--update', this.updateUrl], callback); } - supportsUpdates () { - return SquirrelUpdate.existsSync() + supportsUpdates() { + return SquirrelUpdate.existsSync(); } - checkForUpdates () { - if (!this.updateUrl) throw new Error('Update URL is not set') + checkForUpdates() { + if (!this.updateUrl) throw new Error('Update URL is not set'); - this.emit('checking-for-update') + this.emit('checking-for-update'); if (!SquirrelUpdate.existsSync()) { - this.emit('update-not-available') - return + this.emit('update-not-available'); + return; } this.downloadUpdate((error, update) => { if (error != null) { - this.emit('update-not-available') - return + this.emit('update-not-available'); + return; } if (update == null) { - this.emit('update-not-available') - return + this.emit('update-not-available'); + return; } - this.emit('update-available') + this.emit('update-available'); this.installUpdate(error => { if (error != null) { - this.emit('update-not-available') - return + this.emit('update-not-available'); + return; } this.emit( @@ -79,10 +85,10 @@ class AutoUpdater extends EventEmitter { new Date(), 'https://atom.io', () => this.quitAndInstall() - ) - }) - }) + ); + }); + }); } } -module.exports = new AutoUpdater() +module.exports = new AutoUpdater(); diff --git a/src/main-process/context-menu.js b/src/main-process/context-menu.js index 6726bbe12..b63830574 100644 --- a/src/main-process/context-menu.js +++ b/src/main-process/context-menu.js @@ -1,33 +1,32 @@ -const {Menu} = require('electron') +const { Menu } = require('electron'); -module.exports = -class ContextMenu { - constructor (template, atomWindow) { - this.atomWindow = atomWindow - this.createClickHandlers(template) - const menu = Menu.buildFromTemplate(template) - menu.popup(this.atomWindow.browserWindow, {async: true}) +module.exports = class ContextMenu { + constructor(template, atomWindow) { + this.atomWindow = atomWindow; + this.createClickHandlers(template); + const menu = Menu.buildFromTemplate(template); + menu.popup(this.atomWindow.browserWindow, { async: true }); } // It's necessary to build the event handlers in this process, otherwise // closures are dragged across processes and failed to be garbage collected // appropriately. - createClickHandlers (template) { + createClickHandlers(template) { template.forEach(item => { if (item.command) { - if (!item.commandDetail) item.commandDetail = {} - item.commandDetail.contextCommand = true - item.commandDetail.atomWindow = this.atomWindow + if (!item.commandDetail) item.commandDetail = {}; + item.commandDetail.contextCommand = true; + item.commandDetail.atomWindow = this.atomWindow; item.click = () => { global.atomApplication.sendCommandToWindow( item.command, this.atomWindow, item.commandDetail - ) - } + ); + }; } else if (item.submenu) { - this.createClickHandlers(item.submenu) + this.createClickHandlers(item.submenu); } - }) + }); } -} +}; diff --git a/src/main-process/file-recovery-service.js b/src/main-process/file-recovery-service.js index abe2df84e..96834dd78 100644 --- a/src/main-process/file-recovery-service.js +++ b/src/main-process/file-recovery-service.js @@ -1,165 +1,184 @@ -const {dialog} = require('electron') -const crypto = require('crypto') -const Path = require('path') -const fs = require('fs-plus') -const mkdirp = require('mkdirp') +const { dialog } = require('electron'); +const crypto = require('crypto'); +const Path = require('path'); +const fs = require('fs-plus'); +const mkdirp = require('mkdirp'); -module.exports = -class FileRecoveryService { - constructor (recoveryDirectory) { - this.recoveryDirectory = recoveryDirectory - this.recoveryFilesByFilePath = new Map() - this.recoveryFilesByWindow = new WeakMap() - this.windowsByRecoveryFile = new Map() +module.exports = class FileRecoveryService { + constructor(recoveryDirectory) { + this.recoveryDirectory = recoveryDirectory; + this.recoveryFilesByFilePath = new Map(); + this.recoveryFilesByWindow = new WeakMap(); + this.windowsByRecoveryFile = new Map(); } - async willSavePath (window, path) { - const stats = await tryStatFile(path) - if (!stats) return + async willSavePath(window, path) { + const stats = await tryStatFile(path); + if (!stats) return; - const recoveryPath = Path.join(this.recoveryDirectory, RecoveryFile.fileNameForPath(path)) + const recoveryPath = Path.join( + this.recoveryDirectory, + RecoveryFile.fileNameForPath(path) + ); const recoveryFile = - this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, stats.mode, recoveryPath) + this.recoveryFilesByFilePath.get(path) || + new RecoveryFile(path, stats.mode, recoveryPath); try { - await recoveryFile.retain() + await recoveryFile.retain(); } catch (err) { - console.log(`Couldn't retain ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`) - return + console.log( + `Couldn't retain ${recoveryFile.recoveryPath}. Code: ${ + err.code + }. Message: ${err.message}` + ); + return; } if (!this.recoveryFilesByWindow.has(window)) { - this.recoveryFilesByWindow.set(window, new Set()) + this.recoveryFilesByWindow.set(window, new Set()); } if (!this.windowsByRecoveryFile.has(recoveryFile)) { - this.windowsByRecoveryFile.set(recoveryFile, new Set()) + this.windowsByRecoveryFile.set(recoveryFile, new Set()); } - this.recoveryFilesByWindow.get(window).add(recoveryFile) - this.windowsByRecoveryFile.get(recoveryFile).add(window) - this.recoveryFilesByFilePath.set(path, recoveryFile) + this.recoveryFilesByWindow.get(window).add(recoveryFile); + this.windowsByRecoveryFile.get(recoveryFile).add(window); + this.recoveryFilesByFilePath.set(path, recoveryFile); } - async didSavePath (window, path) { - const recoveryFile = this.recoveryFilesByFilePath.get(path) + async didSavePath(window, path) { + const recoveryFile = this.recoveryFilesByFilePath.get(path); if (recoveryFile != null) { try { - await recoveryFile.release() + await recoveryFile.release(); } catch (err) { - console.log(`Couldn't release ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`) + console.log( + `Couldn't release ${recoveryFile.recoveryPath}. Code: ${ + err.code + }. Message: ${err.message}` + ); } - if (recoveryFile.isReleased()) this.recoveryFilesByFilePath.delete(path) - this.recoveryFilesByWindow.get(window).delete(recoveryFile) - this.windowsByRecoveryFile.get(recoveryFile).delete(window) + if (recoveryFile.isReleased()) this.recoveryFilesByFilePath.delete(path); + this.recoveryFilesByWindow.get(window).delete(recoveryFile); + this.windowsByRecoveryFile.get(recoveryFile).delete(window); } } - async didCrashWindow (window) { - if (!this.recoveryFilesByWindow.has(window)) return + async didCrashWindow(window) { + if (!this.recoveryFilesByWindow.has(window)) return; - const promises = [] + const promises = []; for (const recoveryFile of this.recoveryFilesByWindow.get(window)) { - promises.push(recoveryFile.recover() - .catch(error => { - const message = 'A file that Atom was saving could be corrupted' - const detail = - `Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` + - `Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".` - console.log(detail) - dialog.showMessageBox(window, {type: 'info', buttons: ['OK'], message, detail}, () => { /* noop callback to get async behavior */ }) - }) - .then(() => { - for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { - this.recoveryFilesByWindow.get(window).delete(recoveryFile) - } - this.windowsByRecoveryFile.delete(recoveryFile) - this.recoveryFilesByFilePath.delete(recoveryFile.originalPath) - }) - ) + promises.push( + recoveryFile + .recover() + .catch(error => { + const message = 'A file that Atom was saving could be corrupted'; + const detail = + `Error ${error.code}. There was a crash while saving "${ + recoveryFile.originalPath + }", so this file might be blank or corrupted.\n` + + `Atom couldn't recover it automatically, but a recovery file has been saved at: "${ + recoveryFile.recoveryPath + }".`; + console.log(detail); + dialog.showMessageBox( + window, + { type: 'info', buttons: ['OK'], message, detail }, + () => { + /* noop callback to get async behavior */ + } + ); + }) + .then(() => { + for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { + this.recoveryFilesByWindow.get(window).delete(recoveryFile); + } + this.windowsByRecoveryFile.delete(recoveryFile); + this.recoveryFilesByFilePath.delete(recoveryFile.originalPath); + }) + ); } - await Promise.all(promises) + await Promise.all(promises); } - didCloseWindow (window) { - if (!this.recoveryFilesByWindow.has(window)) return + didCloseWindow(window) { + if (!this.recoveryFilesByWindow.has(window)) return; for (let recoveryFile of this.recoveryFilesByWindow.get(window)) { - this.windowsByRecoveryFile.get(recoveryFile).delete(window) + this.windowsByRecoveryFile.get(recoveryFile).delete(window); } - this.recoveryFilesByWindow.delete(window) + this.recoveryFilesByWindow.delete(window); } -} +}; class RecoveryFile { - static fileNameForPath (path) { - const extension = Path.extname(path) - const basename = Path.basename(path, extension).substring(0, 34) - const randomSuffix = crypto.randomBytes(3).toString('hex') - return `${basename}-${randomSuffix}${extension}` + static fileNameForPath(path) { + const extension = Path.extname(path); + const basename = Path.basename(path, extension).substring(0, 34); + const randomSuffix = crypto.randomBytes(3).toString('hex'); + return `${basename}-${randomSuffix}${extension}`; } - constructor (originalPath, fileMode, recoveryPath) { - this.originalPath = originalPath - this.fileMode = fileMode - this.recoveryPath = recoveryPath - this.refCount = 0 + constructor(originalPath, fileMode, recoveryPath) { + this.originalPath = originalPath; + this.fileMode = fileMode; + this.recoveryPath = recoveryPath; + this.refCount = 0; } - async store () { - await copyFile(this.originalPath, this.recoveryPath, this.fileMode) + async store() { + await copyFile(this.originalPath, this.recoveryPath, this.fileMode); } - async recover () { - await copyFile(this.recoveryPath, this.originalPath, this.fileMode) - await this.remove() + async recover() { + await copyFile(this.recoveryPath, this.originalPath, this.fileMode); + await this.remove(); } - async remove () { + async remove() { return new Promise((resolve, reject) => fs.unlink(this.recoveryPath, error => error && error.code !== 'ENOENT' ? reject(error) : resolve() ) - ) + ); } - async retain () { - if (this.isReleased()) await this.store() - this.refCount++ + async retain() { + if (this.isReleased()) await this.store(); + this.refCount++; } - async release () { - this.refCount-- - if (this.isReleased()) await this.remove() + async release() { + this.refCount--; + if (this.isReleased()) await this.remove(); } - isReleased () { - return this.refCount === 0 + isReleased() { + return this.refCount === 0; } } -async function tryStatFile (path) { +async function tryStatFile(path) { return new Promise((resolve, reject) => - fs.stat(path, (error, result) => - resolve(error == null && result) - ) - ) + fs.stat(path, (error, result) => resolve(error == null && result)) + ); } -async function copyFile (source, destination, mode) { +async function copyFile(source, destination, mode) { return new Promise((resolve, reject) => { - mkdirp(Path.dirname(destination), (error) => { - if (error) return reject(error) - const readStream = fs.createReadStream(source) - readStream - .on('error', reject) - .once('open', () => { - const writeStream = fs.createWriteStream(destination, {mode}) - writeStream - .on('error', reject) - .on('open', () => readStream.pipe(writeStream)) - .once('close', () => resolve()) - }) - }) - }) + mkdirp(Path.dirname(destination), error => { + if (error) return reject(error); + const readStream = fs.createReadStream(source); + readStream.on('error', reject).once('open', () => { + const writeStream = fs.createWriteStream(destination, { mode }); + writeStream + .on('error', reject) + .on('open', () => readStream.pipe(writeStream)) + .once('close', () => resolve()); + }); + }); + }); } diff --git a/src/main-process/main.js b/src/main-process/main.js index fac0c0617..ca5e129c9 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -1,64 +1,66 @@ if (typeof snapshotResult !== 'undefined') { - snapshotResult.setGlobals(global, process, global, {}, console, require) + snapshotResult.setGlobals(global, process, global, {}, console, require); } -const startTime = Date.now() -const StartupTime = require('../startup-time') -StartupTime.setStartTime() +const startTime = Date.now(); +const StartupTime = require('../startup-time'); +StartupTime.setStartTime(); -const path = require('path') -const fs = require('fs-plus') -const CSON = require('season') -const yargs = require('yargs') -const electron = require('electron') +const path = require('path'); +const fs = require('fs-plus'); +const CSON = require('season'); +const yargs = require('yargs'); +const electron = require('electron'); -const args = - yargs(process.argv) - .alias('d', 'dev') - .alias('t', 'test') - .alias('r', 'resource-path') - .argv +const args = yargs(process.argv) + .alias('d', 'dev') + .alias('t', 'test') + .alias('r', 'resource-path').argv; -function isAtomRepoPath (repoPath) { - let packageJsonPath = path.join(repoPath, 'package.json') +function isAtomRepoPath(repoPath) { + let packageJsonPath = path.join(repoPath, 'package.json'); if (fs.statSyncNoException(packageJsonPath)) { try { - let packageJson = CSON.readFileSync(packageJsonPath) - return packageJson.name === 'atom' + let packageJson = CSON.readFileSync(packageJsonPath); + return packageJson.name === 'atom'; } catch (e) { - return false + return false; } } - return false + return false; } -let resourcePath -let devResourcePath +let resourcePath; +let devResourcePath; if (args.resourcePath) { - resourcePath = args.resourcePath - devResourcePath = resourcePath + resourcePath = args.resourcePath; + devResourcePath = resourcePath; } else { - const stableResourcePath = path.dirname(path.dirname(__dirname)) - const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom') + const stableResourcePath = path.dirname(path.dirname(__dirname)); + const defaultRepositoryPath = path.join( + electron.app.getPath('home'), + 'github', + 'atom' + ); if (process.env.ATOM_DEV_RESOURCE_PATH) { - devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH + devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH; } else if (isAtomRepoPath(process.cwd())) { - devResourcePath = process.cwd() + devResourcePath = process.cwd(); } else if (fs.statSyncNoException(defaultRepositoryPath)) { - devResourcePath = defaultRepositoryPath + devResourcePath = defaultRepositoryPath; } else { - devResourcePath = stableResourcePath + devResourcePath = stableResourcePath; } if (args.dev || args.test || args.benchmark || args.benchmarkTest) { - resourcePath = devResourcePath + resourcePath = devResourcePath; } else { - resourcePath = stableResourcePath + resourcePath = stableResourcePath; } } -const start = require(path.join(resourcePath, 'src', 'main-process', 'start')) -start(resourcePath, devResourcePath, startTime) +const start = require(path.join(resourcePath, 'src', 'main-process', 'start')); +start(resourcePath, devResourcePath, startTime); diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js index df8d3b2ae..f35d151a9 100644 --- a/src/main-process/parse-command-line.js +++ b/src/main-process/parse-command-line.js @@ -1,12 +1,12 @@ -'use strict' +'use strict'; -const dedent = require('dedent') -const yargs = require('yargs') -const {app} = require('electron') +const dedent = require('dedent'); +const yargs = require('yargs'); +const { app } = require('electron'); -module.exports = function parseCommandLine (processArgs) { - const options = yargs(processArgs).wrap(yargs.terminalWidth()) - const version = app.getVersion() +module.exports = function parseCommandLine(processArgs) { + const options = yargs(processArgs).wrap(yargs.terminalWidth()); + const version = app.getVersion(); options.usage( dedent`Atom Editor v${version} @@ -32,38 +32,111 @@ module.exports = function parseCommandLine (processArgs) { ATOM_HOME The root path for all configuration files and folders. Defaults to \`~/.atom\`.` - ) + ); // Deprecated 1.0 API preview flag - options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported.') - options.boolean('include-deprecated-apis').describe('include-deprecated-apis', 'This option is not currently supported.') - options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.') - options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the main process in the foreground.') - options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.') - options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.') - options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.') - options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.') - options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.') - options.boolean('safe').describe( - 'safe', - 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.' - ) - options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.') - options.boolean('benchmark-test').describe('benchmark-test', 'Run a faster version of the benchmarks in headless mode.') - options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.') - options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.') - options.string('timeout').describe( - 'timeout', - 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).' - ) - options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.') - options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.') - options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.') - options.string('user-data-dir') - options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.') - options.boolean('enable-electron-logging').describe('enable-electron-logging', 'Enable low-level logging messages from Electron.') - options.boolean('uri-handler') + options + .alias('1', 'one') + .boolean('1') + .describe('1', 'This option is no longer supported.'); + options + .boolean('include-deprecated-apis') + .describe( + 'include-deprecated-apis', + 'This option is not currently supported.' + ); + options + .alias('d', 'dev') + .boolean('d') + .describe('d', 'Run in development mode.'); + options + .alias('f', 'foreground') + .boolean('f') + .describe('f', 'Keep the main process in the foreground.'); + options + .alias('h', 'help') + .boolean('h') + .describe('h', 'Print this usage message.'); + options + .alias('l', 'log-file') + .string('l') + .describe('l', 'Log all output to file.'); + options + .alias('n', 'new-window') + .boolean('n') + .describe('n', 'Open a new window.'); + options + .boolean('profile-startup') + .describe( + 'profile-startup', + 'Create a profile of the startup execution time.' + ); + options + .alias('r', 'resource-path') + .string('r') + .describe( + 'r', + 'Set the path to the Atom source directory and enable dev-mode.' + ); + options + .boolean('safe') + .describe( + 'safe', + 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.' + ); + options + .boolean('benchmark') + .describe( + 'benchmark', + 'Open a new window that runs the specified benchmarks.' + ); + options + .boolean('benchmark-test') + .describe( + 'benchmark-test', + 'Run a faster version of the benchmarks in headless mode.' + ); + options + .alias('t', 'test') + .boolean('t') + .describe( + 't', + 'Run the specified specs and exit with error code on failures.' + ); + options + .alias('m', 'main-process') + .boolean('m') + .describe('m', 'Run the specified specs in the main process.'); + options + .string('timeout') + .describe( + 'timeout', + 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).' + ); + options + .alias('v', 'version') + .boolean('v') + .describe('v', 'Print the version information.'); + options + .alias('w', 'wait') + .boolean('w') + .describe('w', 'Wait for window to be closed before returning.'); + options + .alias('a', 'add') + .boolean('a') + .describe('add', 'Open path as a new project in last used window.'); + options.string('user-data-dir'); + options + .boolean('clear-window-state') + .describe('clear-window-state', 'Delete all Atom environment state.'); + options + .boolean('enable-electron-logging') + .describe( + 'enable-electron-logging', + 'Enable low-level logging messages from Electron.' + ); + options.boolean('uri-handler'); - let args = options.argv + let args = options.argv; // If --uri-handler is set, then we parse NOTHING else if (args.uriHandler) { @@ -71,78 +144,78 @@ module.exports = function parseCommandLine (processArgs) { uriHandler: true, 'uri-handler': true, _: args._.filter(str => str.startsWith('atom://')).slice(0, 1) - } + }; } if (args.help) { - process.stdout.write(options.help()) - process.exit(0) + process.stdout.write(options.help()); + process.exit(0); } if (args.version) { process.stdout.write( `Atom : ${app.getVersion()}\n` + - `Electron: ${process.versions.electron}\n` + - `Chrome : ${process.versions.chrome}\n` + - `Node : ${process.versions.node}\n` - ) - process.exit(0) + `Electron: ${process.versions.electron}\n` + + `Chrome : ${process.versions.chrome}\n` + + `Node : ${process.versions.node}\n` + ); + process.exit(0); } - const addToLastWindow = args['add'] - const safeMode = args['safe'] - const benchmark = args['benchmark'] - const benchmarkTest = args['benchmark-test'] - const test = args['test'] - const mainProcess = args['main-process'] - const timeout = args['timeout'] - const newWindow = args['new-window'] - let executedFrom = null + const addToLastWindow = args['add']; + const safeMode = args['safe']; + const benchmark = args['benchmark']; + const benchmarkTest = args['benchmark-test']; + const test = args['test']; + const mainProcess = args['main-process']; + const timeout = args['timeout']; + const newWindow = args['new-window']; + let executedFrom = null; if (args['executed-from'] && args['executed-from'].toString()) { - executedFrom = args['executed-from'].toString() + executedFrom = args['executed-from'].toString(); } else { - executedFrom = process.cwd() + executedFrom = process.cwd(); } if (newWindow && addToLastWindow) { process.stderr.write( - `Only one of the --add and --new-window options may be specified at the same time.\n\n${options.help()}`, - ) + `Only one of the --add and --new-window options may be specified at the same time.\n\n${options.help()}` + ); // Exiting the main process with a nonzero exit code on MacOS causes the app open to fail with the mysterious // message "LSOpenURLsWithRole() failed for the application /Applications/Atom Dev.app with error -10810." - process.exit(0) + process.exit(0); } - let pidToKillWhenClosed = null + let pidToKillWhenClosed = null; if (args['wait']) { - pidToKillWhenClosed = args['pid'] + pidToKillWhenClosed = args['pid']; } - const logFile = args['log-file'] - const userDataDir = args['user-data-dir'] - const profileStartup = args['profile-startup'] - const clearWindowState = args['clear-window-state'] - let pathsToOpen = [] - let urlsToOpen = [] - let devMode = args['dev'] + const logFile = args['log-file']; + const userDataDir = args['user-data-dir']; + const profileStartup = args['profile-startup']; + const clearWindowState = args['clear-window-state']; + let pathsToOpen = []; + let urlsToOpen = []; + let devMode = args['dev']; for (const path of args._) { if (path.startsWith('atom://')) { - urlsToOpen.push(path) + urlsToOpen.push(path); } else { - pathsToOpen.push(path) + pathsToOpen.push(path); } } if (args.resourcePath || test) { - devMode = true + devMode = true; } if (args['path-environment']) { // On Yosemite the $PATH is not inherited by the "open" command, so we have to // explicitly pass it by command line, see http://git.io/YC8_Ew. - process.env.PATH = args['path-environment'] + process.env.PATH = args['path-environment']; } return { @@ -165,5 +238,5 @@ module.exports = function parseCommandLine (processArgs) { benchmark, benchmarkTest, env: process.env - } -} + }; +}; diff --git a/src/main-process/spawner.js b/src/main-process/spawner.js index e39e0ff4e..21a673205 100644 --- a/src/main-process/spawner.js +++ b/src/main-process/spawner.js @@ -1,4 +1,4 @@ -const ChildProcess = require('child_process') +const ChildProcess = require('child_process'); // Spawn a command and invoke the callback when it completes with an error // and the output from standard out. @@ -10,34 +10,38 @@ const ChildProcess = require('child_process') // * `code` Error code returned by the command. // * `stdout` The {String} output text generated by the command. // * `stdout` The {String} output text generated by the command. -exports.spawn = function (command, args, callback) { - let error - let spawnedProcess - let stdout = '' +exports.spawn = function(command, args, callback) { + let error; + let spawnedProcess; + let stdout = ''; try { - spawnedProcess = ChildProcess.spawn(command, args) + spawnedProcess = ChildProcess.spawn(command, args); } catch (error) { - process.nextTick(() => callback && callback(error, stdout)) - return + process.nextTick(() => callback && callback(error, stdout)); + return; } - spawnedProcess.stdout.on('data', data => { stdout += data }) - spawnedProcess.on('error', processError => { error = processError }) + spawnedProcess.stdout.on('data', data => { + stdout += data; + }); + spawnedProcess.on('error', processError => { + error = processError; + }); spawnedProcess.on('close', (code, signal) => { if (!error && code !== 0) { - error = new Error(`Command failed: ${signal != null ? signal : code}`) + error = new Error(`Command failed: ${signal != null ? signal : code}`); } if (error) { - if (error.code == null) error.code = code - if (error.stdout == null) error.stdout = stdout + if (error.code == null) error.code = code; + if (error.stdout == null) error.stdout = stdout; } - callback && callback(error, stdout) - }) + callback && callback(error, stdout); + }); // This is necessary if using Powershell 2 on Windows 7 to get the events to raise // http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs - return spawnedProcess.stdin.end() -} + return spawnedProcess.stdin.end(); +}; diff --git a/src/main-process/squirrel-update.js b/src/main-process/squirrel-update.js index 6002b177b..bd7ba3421 100644 --- a/src/main-process/squirrel-update.js +++ b/src/main-process/squirrel-update.js @@ -11,18 +11,19 @@ const binFolder = path.join(rootAtomFolder, 'bin') const updateDotExe = path.join(rootAtomFolder, 'Update.exe') if (process.env.SystemRoot) { - const system32Path = path.join(process.env.SystemRoot, 'System32') - setxPath = path.join(system32Path, 'setx.exe') + const system32Path = path.join(process.env.SystemRoot, 'System32'); + setxPath = path.join(system32Path, 'setx.exe'); } else { - setxPath = 'setx.exe' + setxPath = 'setx.exe'; } // Spawn setx.exe and callback when it completes -const spawnSetx = (args, callback) => Spawner.spawn(setxPath, args, callback) +const spawnSetx = (args, callback) => Spawner.spawn(setxPath, args, callback); // Spawn the Update.exe with the given arguments and invoke the callback when // the command completes. -const spawnUpdate = (args, callback) => Spawner.spawn(updateDotExe, args, callback) +const spawnUpdate = (args, callback) => + Spawner.spawn(updateDotExe, args, callback); // Add atom and apm to the PATH // @@ -56,45 +57,51 @@ const addCommandsToPath = (exeName, callback) => { fs.writeFile(apmShCommandPath, apmShCommand, () => callback()) ) ) - ) - } + ); + }; const addBinToPath = (pathSegments, callback) => { - pathSegments.push(binFolder) - const newPathEnv = pathSegments.join(';') - spawnSetx(['Path', newPathEnv], callback) - } + pathSegments.push(binFolder); + const newPathEnv = pathSegments.join(';'); + spawnSetx(['Path', newPathEnv], callback); + }; installCommands(error => { - if (error) return callback(error) + if (error) return callback(error); WinPowerShell.getPath((error, pathEnv) => { - if (error) return callback(error) + if (error) return callback(error); - const pathSegments = pathEnv.split(/;+/).filter(pathSegment => pathSegment) + const pathSegments = pathEnv + .split(/;+/) + .filter(pathSegment => pathSegment); if (pathSegments.indexOf(binFolder) === -1) { - addBinToPath(pathSegments, callback) + addBinToPath(pathSegments, callback); } else { - callback() + callback(); } - }) - }) -} + }); + }); +}; // Remove atom and apm from the PATH const removeCommandsFromPath = callback => WinPowerShell.getPath((error, pathEnv) => { - if (error != null) { return callback(error) } + if (error != null) { + return callback(error); + } - const pathSegments = pathEnv.split(/;+/).filter(pathSegment => pathSegment && (pathSegment !== binFolder)) - const newPathEnv = pathSegments.join(';') + const pathSegments = pathEnv + .split(/;+/) + .filter(pathSegment => pathSegment && pathSegment !== binFolder); + const newPathEnv = pathSegments.join(';'); if (pathEnv !== newPathEnv) { - return spawnSetx(['Path', newPathEnv], callback) + return spawnSetx(['Path', newPathEnv], callback); } else { - return callback() + return callback(); } - }) + }); const getExeName = (app) => path.basename(app.getPath('exe')) @@ -111,25 +118,27 @@ const updateShortcuts = (appName, exeName, callback) => { const desktopShortcutPath = path.join(homeDirectory, 'Desktop', `${appName}.lnk`) // Check if the desktop shortcut has been previously deleted and // and keep it deleted if it was - fs.exists(desktopShortcutPath, (desktopShortcutExists) => { - const locations = ['StartMenu'] - if (desktopShortcutExists) { locations.push('Desktop') } + fs.exists(desktopShortcutPath, desktopShortcutExists => { + const locations = ['StartMenu']; + if (desktopShortcutExists) { + locations.push('Desktop'); + } createShortcuts(exeName, locations, callback) }) } else { createShortcuts(exeName, ['Desktop', 'StartMenu'], callback) } -} +}; // Remove the desktop and start menu shortcuts by using the command line API // provided by Squirrel's Update.exe const removeShortcuts = (exeName, callback) => spawnUpdate(['--removeShortcut', exeName], callback) -exports.spawn = spawnUpdate +exports.spawn = spawnUpdate; // Is the Update.exe installed with Atom? -exports.existsSync = () => fs.existsSync(updateDotExe) +exports.existsSync = () => fs.existsSync(updateDotExe); // Restart Atom using the version pointed to by the atom.cmd shim exports.restartAtom = (app) => { @@ -137,8 +146,8 @@ exports.restartAtom = (app) => { const exeName = getExeName(app) const atomCmdName = exeName.replace('.exe', '.cmd') if (global.atomApplication && global.atomApplication.lastFocusedWindow) { - const {projectPath} = global.atomApplication.lastFocusedWindow - if (projectPath) args = [projectPath] + const { projectPath } = global.atomApplication.lastFocusedWindow; + if (projectPath) args = [projectPath]; } app.once('will-quit', () => Spawner.spawn(path.join(binFolder, atomCmdName), args)) app.quit() @@ -153,26 +162,26 @@ exports.handleStartupEvent = (app, squirrelCommand) => { addCommandsToPath(exeName, () => WinShell.registerShellIntegration(app.getName(), () => app.quit()) ) - ) - return true + ); + return true; case '--squirrel-updated': updateShortcuts(app.getName(), exeName, () => addCommandsToPath(exeName, () => WinShell.updateShellIntegration(app.getName(), () => app.quit()) ) - ) - return true + ); + return true; case '--squirrel-uninstall': removeShortcuts(exeName, () => removeCommandsFromPath(() => WinShell.deregisterShellIntegration(app.getName(), () => app.quit()) ) - ) - return true + ); + return true; case '--squirrel-obsolete': - app.quit() - return true + app.quit(); + return true; default: - return false + return false; } -} +}; diff --git a/src/main-process/start.js b/src/main-process/start.js index d5c1bfd0e..cd312f392 100644 --- a/src/main-process/start.js +++ b/src/main-process/start.js @@ -1,136 +1,150 @@ -const {app} = require('electron') -const nslog = require('nslog') -const path = require('path') -const temp = require('temp').track() -const parseCommandLine = require('./parse-command-line') -const startCrashReporter = require('../crash-reporter-start') -const atomPaths = require('../atom-paths') -const fs = require('fs') -const CSON = require('season') -const Config = require('../config') -const StartupTime = require('../startup-time') +const { app } = require('electron'); +const nslog = require('nslog'); +const path = require('path'); +const temp = require('temp').track(); +const parseCommandLine = require('./parse-command-line'); +const startCrashReporter = require('../crash-reporter-start'); +const atomPaths = require('../atom-paths'); +const fs = require('fs'); +const CSON = require('season'); +const Config = require('../config'); +const StartupTime = require('../startup-time'); -StartupTime.setStartTime() +StartupTime.setStartTime(); -module.exports = function start (resourcePath, devResourcePath, startTime) { - global.shellStartTime = startTime - StartupTime.addMarker('main-process:start') +module.exports = function start(resourcePath, devResourcePath, startTime) { + global.shellStartTime = startTime; + StartupTime.addMarker('main-process:start'); - process.on('uncaughtException', function (error = {}) { + process.on('uncaughtException', function(error = {}) { if (error.message != null) { - console.log(error.message) + console.log(error.message); } if (error.stack != null) { - console.log(error.stack) + console.log(error.stack); } - }) + }); - process.on('unhandledRejection', function (error = {}) { + process.on('unhandledRejection', function(error = {}) { if (error.message != null) { - console.log(error.message) + console.log(error.message); } if (error.stack != null) { - console.log(error.stack) + console.log(error.stack); } - }) + }); - const previousConsoleLog = console.log - console.log = nslog + const previousConsoleLog = console.log; + console.log = nslog; - app.commandLine.appendSwitch('enable-experimental-web-platform-features') + app.commandLine.appendSwitch('enable-experimental-web-platform-features'); - const args = parseCommandLine(process.argv.slice(1)) - args.resourcePath = normalizeDriveLetterName(resourcePath) - args.devResourcePath = normalizeDriveLetterName(devResourcePath) + const args = parseCommandLine(process.argv.slice(1)); + args.resourcePath = normalizeDriveLetterName(resourcePath); + args.devResourcePath = normalizeDriveLetterName(devResourcePath); - atomPaths.setAtomHome(app.getPath('home')) - atomPaths.setUserData(app) + atomPaths.setAtomHome(app.getPath('home')); + atomPaths.setUserData(app); - const config = getConfig() - const colorProfile = config.get('core.colorProfile') + const config = getConfig(); + const colorProfile = config.get('core.colorProfile'); if (colorProfile && colorProfile !== 'default') { - app.commandLine.appendSwitch('force-color-profile', colorProfile) + app.commandLine.appendSwitch('force-color-profile', colorProfile); } if (handleStartupEventWithSquirrel()) { - return + return; } else if (args.test && args.mainProcess) { - app.setPath('userData', temp.mkdirSync('atom-user-data-dir-for-main-process-tests')) - console.log = previousConsoleLog - app.on('ready', function () { - const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner')) - testRunner(args.pathsToOpen) - }) - return + app.setPath( + 'userData', + temp.mkdirSync('atom-user-data-dir-for-main-process-tests') + ); + console.log = previousConsoleLog; + app.on('ready', function() { + const testRunner = require(path.join( + args.resourcePath, + 'spec/main-process/mocha-test-runner' + )); + testRunner(args.pathsToOpen); + }); + return; } // NB: This prevents Win10 from showing dupe items in the taskbar - app.setAppUserModelId('com.squirrel.atom.' + process.arch) + app.setAppUserModelId('com.squirrel.atom.' + process.arch); - function addPathToOpen (event, pathToOpen) { - event.preventDefault() - args.pathsToOpen.push(pathToOpen) + function addPathToOpen(event, pathToOpen) { + event.preventDefault(); + args.pathsToOpen.push(pathToOpen); } - function addUrlToOpen (event, urlToOpen) { - event.preventDefault() - args.urlsToOpen.push(urlToOpen) + function addUrlToOpen(event, urlToOpen) { + event.preventDefault(); + args.urlsToOpen.push(urlToOpen); } - app.on('open-file', addPathToOpen) - app.on('open-url', addUrlToOpen) - app.on('will-finish-launching', startCrashReporter) + app.on('open-file', addPathToOpen); + app.on('open-url', addUrlToOpen); + app.on('will-finish-launching', startCrashReporter); if (args.userDataDir != null) { - app.setPath('userData', args.userDataDir) + app.setPath('userData', args.userDataDir); } else if (args.test || args.benchmark || args.benchmarkTest) { - app.setPath('userData', temp.mkdirSync('atom-test-data')) + app.setPath('userData', temp.mkdirSync('atom-test-data')); } - StartupTime.addMarker('main-process:electron-onready:start') - app.on('ready', function () { - StartupTime.addMarker('main-process:electron-onready:end') - app.removeListener('open-file', addPathToOpen) - app.removeListener('open-url', addUrlToOpen) - const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application')) - AtomApplication.open(args) - }) -} + StartupTime.addMarker('main-process:electron-onready:start'); + app.on('ready', function() { + StartupTime.addMarker('main-process:electron-onready:end'); + app.removeListener('open-file', addPathToOpen); + app.removeListener('open-url', addUrlToOpen); + const AtomApplication = require(path.join( + args.resourcePath, + 'src', + 'main-process', + 'atom-application' + )); + AtomApplication.open(args); + }); +}; -function handleStartupEventWithSquirrel () { +function handleStartupEventWithSquirrel() { if (process.platform !== 'win32') { - return false + return false; } - const SquirrelUpdate = require('./squirrel-update') - const squirrelCommand = process.argv[1] - return SquirrelUpdate.handleStartupEvent(app, squirrelCommand) + const SquirrelUpdate = require('./squirrel-update'); + const squirrelCommand = process.argv[1]; + return SquirrelUpdate.handleStartupEvent(app, squirrelCommand); } -function getConfig () { - const config = new Config() +function getConfig() { + const config = new Config(); - let configFilePath + let configFilePath; if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.json'))) { - configFilePath = path.join(process.env.ATOM_HOME, 'config.json') + configFilePath = path.join(process.env.ATOM_HOME, 'config.json'); } else if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.cson'))) { - configFilePath = path.join(process.env.ATOM_HOME, 'config.cson') + configFilePath = path.join(process.env.ATOM_HOME, 'config.cson'); } if (configFilePath) { - const configFileData = CSON.readFileSync(configFilePath) - config.resetUserSettings(configFileData) + const configFileData = CSON.readFileSync(configFilePath); + config.resetUserSettings(configFileData); } - return config + return config; } -function normalizeDriveLetterName (filePath) { +function normalizeDriveLetterName(filePath) { if (process.platform === 'win32' && filePath) { - return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':') + return filePath.replace( + /^([a-z]):/, + ([driveLetter]) => driveLetter.toUpperCase() + ':' + ); } else { - return filePath + return filePath; } } diff --git a/src/main-process/win-powershell.js b/src/main-process/win-powershell.js index f8e404d07..1c447c935 100644 --- a/src/main-process/win-powershell.js +++ b/src/main-process/win-powershell.js @@ -1,16 +1,21 @@ -let powershellPath -const path = require('path') -const Spawner = require('./spawner') +let powershellPath; +const path = require('path'); +const Spawner = require('./spawner'); if (process.env.SystemRoot) { - const system32Path = path.join(process.env.SystemRoot, 'System32') - powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe') + const system32Path = path.join(process.env.SystemRoot, 'System32'); + powershellPath = path.join( + system32Path, + 'WindowsPowerShell', + 'v1.0', + 'powershell.exe' + ); } else { - powershellPath = 'powershell.exe' + powershellPath = 'powershell.exe'; } // Spawn powershell.exe and callback when it completes -const spawnPowershell = function (args, callback) { +const spawnPowershell = function(args, callback) { // Set encoding and execute the command, capture the output, and return it // via .NET's console in order to have consistent UTF-8 encoding. // See http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell @@ -19,13 +24,13 @@ const spawnPowershell = function (args, callback) { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 $output=${args[0]} [Console]::WriteLine($output)\ -` - args.unshift('-command') - args.unshift('RemoteSigned') - args.unshift('-ExecutionPolicy') - args.unshift('-noprofile') - Spawner.spawn(powershellPath, args, callback) -} +`; + args.unshift('-command'); + args.unshift('RemoteSigned'); + args.unshift('-ExecutionPolicy'); + args.unshift('-noprofile'); + Spawner.spawn(powershellPath, args, callback); +}; // Get the user's PATH environment variable registry value. // @@ -34,11 +39,14 @@ $output=${args[0]} // // Returns the user's path {String}. exports.getPath = callback => - spawnPowershell(['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], function (error, stdout) { - if (error != null) { - return callback(error) - } + spawnPowershell( + ["[environment]::GetEnvironmentVariable('Path','User')"], + function(error, stdout) { + if (error != null) { + return callback(error); + } - const pathOutput = stdout.replace(/^\s+|\s+$/g, '') - return callback(null, pathOutput) - }) + const pathOutput = stdout.replace(/^\s+|\s+$/g, ''); + return callback(null, pathOutput); + } + ); diff --git a/src/main-process/win-shell.js b/src/main-process/win-shell.js index ea4e89540..eb20be5c9 100644 --- a/src/main-process/win-shell.js +++ b/src/main-process/win-shell.js @@ -1,52 +1,67 @@ -const Registry = require('winreg') -const Path = require('path') +const Registry = require('winreg'); +const Path = require('path'); let exeName = Path.basename(process.execPath) let appPath = `"${process.execPath}"` let fileIconPath = `"${Path.join(process.execPath, '..', 'resources', 'cli', 'file.ico')}"` class ShellOption { - constructor (key, parts) { - this.isRegistered = this.isRegistered.bind(this) - this.register = this.register.bind(this) - this.deregister = this.deregister.bind(this) - this.update = this.update.bind(this) - this.key = key - this.parts = parts + constructor(key, parts) { + this.isRegistered = this.isRegistered.bind(this); + this.register = this.register.bind(this); + this.deregister = this.deregister.bind(this); + this.update = this.update.bind(this); + this.key = key; + this.parts = parts; } - isRegistered (callback) { - new Registry({hive: 'HKCU', key: `${this.key}\\${this.parts[0].key}`}) - .get(this.parts[0].name, (err, val) => callback((err == null) && (val != null) && val.value === this.parts[0].value)) + isRegistered(callback) { + new Registry({ + hive: 'HKCU', + key: `${this.key}\\${this.parts[0].key}` + }).get(this.parts[0].name, (err, val) => + callback(err == null && val != null && val.value === this.parts[0].value) + ); } - register (callback) { - let doneCount = this.parts.length + register(callback) { + let doneCount = this.parts.length; this.parts.forEach(part => { - let reg = new Registry({hive: 'HKCU', key: (part.key != null) ? `${this.key}\\${part.key}` : this.key}) - return reg.create(() => reg.set(part.name, Registry.REG_SZ, part.value, () => { if (--doneCount === 0) return callback() })) - }) + let reg = new Registry({ + hive: 'HKCU', + key: part.key != null ? `${this.key}\\${part.key}` : this.key + }); + return reg.create(() => + reg.set(part.name, Registry.REG_SZ, part.value, () => { + if (--doneCount === 0) return callback(); + }) + ); + }); } - deregister (callback) { + deregister(callback) { this.isRegistered(isRegistered => { if (isRegistered) { - new Registry({hive: 'HKCU', key: this.key}).destroy(() => callback(null, true)) + new Registry({ hive: 'HKCU', key: this.key }).destroy(() => + callback(null, true) + ); } else { - callback(null, false) + callback(null, false); } - }) + }); } - update (callback) { - new Registry({hive: 'HKCU', key: `${this.key}\\${this.parts[0].key}`}) - .get(this.parts[0].name, (err, val) => { - if ((err != null) || (val == null)) { - callback(err) - } else { - this.register(callback) - } - }) + update(callback) { + new Registry({ + hive: 'HKCU', + key: `${this.key}\\${this.parts[0].key}` + }).get(this.parts[0].name, (err, val) => { + if (err != null || val == null) { + callback(err); + } else { + this.register(callback); + } + }); } } diff --git a/src/menu-helpers.js b/src/menu-helpers.js index 12598764e..c9d87a625 100644 --- a/src/menu-helpers.js +++ b/src/menu-helpers.js @@ -1,78 +1,82 @@ -const _ = require('underscore-plus') +const _ = require('underscore-plus'); -const ItemSpecificities = new WeakMap() +const ItemSpecificities = new WeakMap(); // Add an item to a menu, ensuring separators are not duplicated. -function addItemToMenu (item, menu) { - const lastMenuItem = _.last(menu) - const lastMenuItemIsSpearator = lastMenuItem && lastMenuItem.type === 'separator' +function addItemToMenu(item, menu) { + const lastMenuItem = _.last(menu); + const lastMenuItemIsSpearator = + lastMenuItem && lastMenuItem.type === 'separator'; if (!(item.type === 'separator' && lastMenuItemIsSpearator)) { - menu.push(item) + menu.push(item); } } -function merge (menu, item, itemSpecificity = Infinity) { - item = cloneMenuItem(item) - ItemSpecificities.set(item, itemSpecificity) - const matchingItemIndex = findMatchingItemIndex(menu, item) +function merge(menu, item, itemSpecificity = Infinity) { + item = cloneMenuItem(item); + ItemSpecificities.set(item, itemSpecificity); + const matchingItemIndex = findMatchingItemIndex(menu, item); if (matchingItemIndex === -1) { - addItemToMenu(item, menu) - return + addItemToMenu(item, menu); + return; } - const matchingItem = menu[matchingItemIndex] + const matchingItem = menu[matchingItemIndex]; if (item.submenu != null) { for (let submenuItem of item.submenu) { - merge(matchingItem.submenu, submenuItem, itemSpecificity) + merge(matchingItem.submenu, submenuItem, itemSpecificity); } - } else if (itemSpecificity && itemSpecificity >= ItemSpecificities.get(matchingItem)) { - menu[matchingItemIndex] = item + } else if ( + itemSpecificity && + itemSpecificity >= ItemSpecificities.get(matchingItem) + ) { + menu[matchingItemIndex] = item; } } -function unmerge (menu, item) { - const matchingItemIndex = findMatchingItemIndex(menu, item) +function unmerge(menu, item) { + const matchingItemIndex = findMatchingItemIndex(menu, item); if (matchingItemIndex === -1) { - return + return; } - const matchingItem = menu[matchingItemIndex] + const matchingItem = menu[matchingItemIndex]; if (item.submenu != null) { for (let submenuItem of item.submenu) { - unmerge(matchingItem.submenu, submenuItem) + unmerge(matchingItem.submenu, submenuItem); } } if (matchingItem.submenu == null || matchingItem.submenu.length === 0) { - menu.splice(matchingItemIndex, 1) + menu.splice(matchingItemIndex, 1); } } -function findMatchingItemIndex (menu, { type, label, submenu }) { +function findMatchingItemIndex(menu, { type, label, submenu }) { if (type === 'separator') { - return -1 + return -1; } for (let index = 0; index < menu.length; index++) { - const item = menu[index] + const item = menu[index]; if ( normalizeLabel(item.label) === normalizeLabel(label) && (item.submenu != null) === (submenu != null) ) { - return index + return index; } } - return -1 + return -1; } -function normalizeLabel (label) { +function normalizeLabel(label) { if (label == null) { - return + return; } - return process.platform === 'darwin' ? label : label.replace(/&/g, '') + return process.platform === 'darwin' ? label : label.replace(/&/g, ''); } -function cloneMenuItem (item) { +function cloneMenuItem(item) { item = _.pick( item, 'type', @@ -88,11 +92,11 @@ function cloneMenuItem (item) { 'after', 'beforeGroupContaining', 'afterGroupContaining' - ) + ); if (item.submenu != null) { - item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem)) + item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem)); } - return item + return item; } // Determine the Electron accelerator for a given Atom keystroke. @@ -101,15 +105,15 @@ function cloneMenuItem (item) { // // Returns a String containing the keystroke in a format that can be interpreted // by Electron to provide nice icons where available. -function acceleratorForKeystroke (keystroke) { +function acceleratorForKeystroke(keystroke) { if (!keystroke) { - return null + return null; } - let modifiers = keystroke.split(/-(?=.)/) + let modifiers = keystroke.split(/-(?=.)/); const key = modifiers .pop() .toUpperCase() - .replace('+', 'Plus') + .replace('+', 'Plus'); modifiers = modifiers.map(modifier => modifier @@ -117,10 +121,10 @@ function acceleratorForKeystroke (keystroke) { .replace(/cmd/gi, 'Command') .replace(/ctrl/gi, 'Ctrl') .replace(/alt/gi, 'Alt') - ) + ); - const keys = [...modifiers, key] - return keys.join('+') + const keys = [...modifiers, key]; + return keys.join('+'); } module.exports = { @@ -129,4 +133,4 @@ module.exports = { normalizeLabel, cloneMenuItem, acceleratorForKeystroke -} +}; diff --git a/src/menu-sort-helpers.js b/src/menu-sort-helpers.js index 259f8321e..9f04d57a6 100644 --- a/src/menu-sort-helpers.js +++ b/src/menu-sort-helpers.js @@ -1,186 +1,184 @@ // UTILS -function splitArray (arr, predicate) { - let lastArr = [] - const multiArr = [lastArr] +function splitArray(arr, predicate) { + let lastArr = []; + const multiArr = [lastArr]; arr.forEach(item => { if (predicate(item)) { if (lastArr.length > 0) { - lastArr = [] - multiArr.push(lastArr) + lastArr = []; + multiArr.push(lastArr); } } else { - lastArr.push(item) + lastArr.push(item); } - }) - return multiArr + }); + return multiArr; } -function joinArrays (arrays, joiner) { - const joinedArr = [] +function joinArrays(arrays, joiner) { + const joinedArr = []; arrays.forEach((arr, i) => { if (i > 0 && arr.length > 0) { - joinedArr.push(joiner) + joinedArr.push(joiner); } - joinedArr.push(...arr) - }) - return joinedArr + joinedArr.push(...arr); + }); + return joinedArr; } const pushOntoMultiMap = (map, key, value) => { if (!map.has(key)) { - map.set(key, []) + map.set(key, []); } - map.get(key).push(value) -} + map.get(key).push(value); +}; -function indexOfGroupContainingCommand (groups, command, ignoreGroup) { +function indexOfGroupContainingCommand(groups, command, ignoreGroup) { return groups.findIndex( candiateGroup => candiateGroup !== ignoreGroup && - candiateGroup.some( - candidateItem => candidateItem.command === command - ) - ) + candiateGroup.some(candidateItem => candidateItem.command === command) + ); } // Sort nodes topologically using a depth-first approach. Encountered cycles // are broken. -function sortTopologically (originalOrder, edgesById) { - const sorted = [] - const marked = new Set() +function sortTopologically(originalOrder, edgesById) { + const sorted = []; + const marked = new Set(); - function visit (id) { + function visit(id) { if (marked.has(id)) { // Either this node has already been placed, or we have encountered a // cycle and need to exit. - return + return; } - marked.add(id) - const edges = edgesById.get(id) + marked.add(id); + const edges = edgesById.get(id); if (edges != null) { - edges.forEach(visit) + edges.forEach(visit); } - sorted.push(id) + sorted.push(id); } - originalOrder.forEach(visit) - return sorted + originalOrder.forEach(visit); + return sorted; } -function attemptToMergeAGroup (groups) { +function attemptToMergeAGroup(groups) { for (let i = 0; i < groups.length; i++) { - const group = groups[i] + const group = groups[i]; for (const item of group) { - const toCommands = [...(item.before || []), ...(item.after || [])] + const toCommands = [...(item.before || []), ...(item.after || [])]; for (const command of toCommands) { - const index = indexOfGroupContainingCommand(groups, command, group) + const index = indexOfGroupContainingCommand(groups, command, group); if (index === -1) { // No valid edge for this command - continue + continue; } - const mergeTarget = groups[index] + const mergeTarget = groups[index]; // Merge with group containing `command` - mergeTarget.push(...group) - groups.splice(i, 1) - return true + mergeTarget.push(...group); + groups.splice(i, 1); + return true; } } } - return false + return false; } // Merge groups based on before/after positions // Mutates both the array of groups, and the individual group arrays. -function mergeGroups (groups) { - let mergedAGroup = true +function mergeGroups(groups) { + let mergedAGroup = true; while (mergedAGroup) { - mergedAGroup = attemptToMergeAGroup(groups) + mergedAGroup = attemptToMergeAGroup(groups); } - return groups + return groups; } -function sortItemsInGroup (group) { - const originalOrder = group.map((node, i) => i) - const edges = new Map() - const commandToIndex = new Map(group.map((item, i) => [item.command, i])) +function sortItemsInGroup(group) { + const originalOrder = group.map((node, i) => i); + const edges = new Map(); + const commandToIndex = new Map(group.map((item, i) => [item.command, i])); group.forEach((item, i) => { if (item.before) { item.before.forEach(toCommand => { - const to = commandToIndex.get(toCommand) + const to = commandToIndex.get(toCommand); if (to != null) { - pushOntoMultiMap(edges, to, i) + pushOntoMultiMap(edges, to, i); } - }) + }); } if (item.after) { item.after.forEach(toCommand => { - const to = commandToIndex.get(toCommand) + const to = commandToIndex.get(toCommand); if (to != null) { - pushOntoMultiMap(edges, i, to) + pushOntoMultiMap(edges, i, to); } - }) + }); } - }) + }); - const sortedNodes = sortTopologically(originalOrder, edges) + const sortedNodes = sortTopologically(originalOrder, edges); - return sortedNodes.map(i => group[i]) + return sortedNodes.map(i => group[i]); } -function findEdgesInGroup (groups, i, edges) { - const group = groups[i] +function findEdgesInGroup(groups, i, edges) { + const group = groups[i]; for (const item of group) { if (item.beforeGroupContaining) { for (const command of item.beforeGroupContaining) { - const to = indexOfGroupContainingCommand(groups, command, group) + const to = indexOfGroupContainingCommand(groups, command, group); if (to !== -1) { - pushOntoMultiMap(edges, to, i) - return + pushOntoMultiMap(edges, to, i); + return; } } } if (item.afterGroupContaining) { for (const command of item.afterGroupContaining) { - const to = indexOfGroupContainingCommand(groups, command, group) + const to = indexOfGroupContainingCommand(groups, command, group); if (to !== -1) { - pushOntoMultiMap(edges, i, to) - return + pushOntoMultiMap(edges, i, to); + return; } } } } } -function sortGroups (groups) { - const originalOrder = groups.map((item, i) => i) - const edges = new Map() +function sortGroups(groups) { + const originalOrder = groups.map((item, i) => i); + const edges = new Map(); for (let i = 0; i < groups.length; i++) { - findEdgesInGroup(groups, i, edges) + findEdgesInGroup(groups, i, edges); } - const sortedGroupIndexes = sortTopologically(originalOrder, edges) - return sortedGroupIndexes.map(i => groups[i]) + const sortedGroupIndexes = sortTopologically(originalOrder, edges); + return sortedGroupIndexes.map(i => groups[i]); } -function isSeparator (item) { - return item.type === 'separator' +function isSeparator(item) { + return item.type === 'separator'; } -function sortMenuItems (menuItems) { +function sortMenuItems(menuItems) { // Split the items into their implicit groups based upon separators. - const groups = splitArray(menuItems, isSeparator) + const groups = splitArray(menuItems, isSeparator); // Merge groups that contain before/after references to eachother. - const mergedGroups = mergeGroups(groups) + const mergedGroups = mergeGroups(groups); // Sort each individual group internally. - const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup) + const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup); // Sort the groups based upon their beforeGroupContaining/afterGroupContaining // references. - const sortedGroups = sortGroups(mergedGroupsWithSortedItems) + const sortedGroups = sortGroups(mergedGroupsWithSortedItems); // Join the groups back - return joinArrays(sortedGroups, { type: 'separator' }) + return joinArrays(sortedGroups, { type: 'separator' }); } -module.exports = {sortMenuItems} +module.exports = { sortMenuItems }; diff --git a/src/module-cache.js b/src/module-cache.js index f83e54a28..a63b3a731 100644 --- a/src/module-cache.js +++ b/src/module-cache.js @@ -1,30 +1,30 @@ -const Module = require('module') -const path = require('path') -const semver = require('semver') +const Module = require('module'); +const path = require('path'); +const semver = require('semver'); // Extend semver.Range to memoize matched versions for speed class Range extends semver.Range { - constructor () { - super(...arguments) - this.matchedVersions = new Set() - this.unmatchedVersions = new Set() + constructor() { + super(...arguments); + this.matchedVersions = new Set(); + this.unmatchedVersions = new Set(); } - test (version) { - if (this.matchedVersions.has(version)) return true - if (this.unmatchedVersions.has(version)) return false + test(version) { + if (this.matchedVersions.has(version)) return true; + if (this.unmatchedVersions.has(version)) return false; - const matches = super.test(...arguments) + const matches = super.test(...arguments); if (matches) { - this.matchedVersions.add(version) + this.matchedVersions.add(version); } else { - this.unmatchedVersions.add(version) + this.unmatchedVersions.add(version); } - return matches + return matches; } } -let nativeModules = null +let nativeModules = null; const cache = { builtins: {}, @@ -36,38 +36,48 @@ const cache = { registered: false, resourcePath: null, resourcePathWithTrailingSlash: null -} +}; // isAbsolute is inlined from fs-plus so that fs-plus itself can be required // from this cache. -let isAbsolute +let isAbsolute; if (process.platform === 'win32') { - isAbsolute = pathToCheck => pathToCheck && ((pathToCheck[1] === ':') || ((pathToCheck[0] === '\\') && (pathToCheck[1] === '\\'))) + isAbsolute = pathToCheck => + pathToCheck && + (pathToCheck[1] === ':' || + (pathToCheck[0] === '\\' && pathToCheck[1] === '\\')); } else { - isAbsolute = pathToCheck => pathToCheck && (pathToCheck[0] === '/') + isAbsolute = pathToCheck => pathToCheck && pathToCheck[0] === '/'; } -const isCorePath = pathToCheck => pathToCheck.startsWith(cache.resourcePathWithTrailingSlash) +const isCorePath = pathToCheck => + pathToCheck.startsWith(cache.resourcePathWithTrailingSlash); -function loadDependencies (modulePath, rootPath, rootMetadata, moduleCache) { - const fs = require('fs-plus') +function loadDependencies(modulePath, rootPath, rootMetadata, moduleCache) { + const fs = require('fs-plus'); for (let childPath of fs.listSync(path.join(modulePath, 'node_modules'))) { - if (path.basename(childPath) === '.bin') continue - if (rootPath === modulePath && (rootMetadata.packageDependencies && rootMetadata.packageDependencies.hasOwnProperty(path.basename(childPath)))) { - continue + if (path.basename(childPath) === '.bin') continue; + if ( + rootPath === modulePath && + (rootMetadata.packageDependencies && + rootMetadata.packageDependencies.hasOwnProperty( + path.basename(childPath) + )) + ) { + continue; } - const childMetadataPath = path.join(childPath, 'package.json') - if (!fs.isFileSync(childMetadataPath)) continue + const childMetadataPath = path.join(childPath, 'package.json'); + if (!fs.isFileSync(childMetadataPath)) continue; - const childMetadata = JSON.parse(fs.readFileSync(childMetadataPath)) + const childMetadata = JSON.parse(fs.readFileSync(childMetadataPath)); if (childMetadata && childMetadata.version) { - var mainPath + var mainPath; try { - mainPath = require.resolve(childPath) + mainPath = require.resolve(childPath); } catch (error) { - mainPath = null + mainPath = null; } if (mainPath) { @@ -75,265 +85,311 @@ function loadDependencies (modulePath, rootPath, rootMetadata, moduleCache) { name: childMetadata.name, version: childMetadata.version, path: path.relative(rootPath, mainPath) - }) + }); } - loadDependencies(childPath, rootPath, rootMetadata, moduleCache) + loadDependencies(childPath, rootPath, rootMetadata, moduleCache); } } } -function loadFolderCompatibility (modulePath, rootPath, rootMetadata, moduleCache) { - const fs = require('fs-plus') +function loadFolderCompatibility( + modulePath, + rootPath, + rootMetadata, + moduleCache +) { + const fs = require('fs-plus'); - const metadataPath = path.join(modulePath, 'package.json') - if (!fs.isFileSync(metadataPath)) return + const metadataPath = path.join(modulePath, 'package.json'); + if (!fs.isFileSync(metadataPath)) return; - const metadata = JSON.parse(fs.readFileSync(metadataPath)) - const dependencies = metadata.dependencies || {} + const metadata = JSON.parse(fs.readFileSync(metadataPath)); + const dependencies = metadata.dependencies || {}; for (let name in dependencies) { if (!semver.validRange(dependencies[name])) { - delete dependencies[name] + delete dependencies[name]; } } - const onDirectory = childPath => path.basename(childPath) !== 'node_modules' + const onDirectory = childPath => path.basename(childPath) !== 'node_modules'; - const extensions = ['.js', '.coffee', '.json', '.node'] - let paths = {} - function onFile (childPath) { - const needle = path.extname(childPath) + const extensions = ['.js', '.coffee', '.json', '.node']; + let paths = {}; + function onFile(childPath) { + const needle = path.extname(childPath); if (extensions.includes(needle)) { - const relativePath = path.relative(rootPath, path.dirname(childPath)) - paths[relativePath] = true + const relativePath = path.relative(rootPath, path.dirname(childPath)); + paths[relativePath] = true; } } - fs.traverseTreeSync(modulePath, onFile, onDirectory) + fs.traverseTreeSync(modulePath, onFile, onDirectory); - paths = Object.keys(paths) + paths = Object.keys(paths); if (paths.length > 0 && Object.keys(dependencies).length > 0) { - moduleCache.folders.push({paths, dependencies}) + moduleCache.folders.push({ paths, dependencies }); } for (let childPath of fs.listSync(path.join(modulePath, 'node_modules'))) { - if (path.basename(childPath) === '.bin') continue - if (rootPath === modulePath && (rootMetadata.packageDependencies && rootMetadata.packageDependencies.hasOwnProperty(path.basename(childPath)))) { - continue + if (path.basename(childPath) === '.bin') continue; + if ( + rootPath === modulePath && + (rootMetadata.packageDependencies && + rootMetadata.packageDependencies.hasOwnProperty( + path.basename(childPath) + )) + ) { + continue; } - loadFolderCompatibility(childPath, rootPath, rootMetadata, moduleCache) + loadFolderCompatibility(childPath, rootPath, rootMetadata, moduleCache); } } -function loadExtensions (modulePath, rootPath, rootMetadata, moduleCache) { - const fs = require('fs-plus') - const extensions = ['.js', '.coffee', '.json', '.node'] - const nodeModulesPath = path.join(rootPath, 'node_modules') +function loadExtensions(modulePath, rootPath, rootMetadata, moduleCache) { + const fs = require('fs-plus'); + const extensions = ['.js', '.coffee', '.json', '.node']; + const nodeModulesPath = path.join(rootPath, 'node_modules'); - function onFile (filePath) { - filePath = path.relative(rootPath, filePath) - const segments = filePath.split(path.sep) - if (segments.includes('test')) return - if (segments.includes('tests')) return - if (segments.includes('spec')) return - if (segments.includes('specs')) return - if (segments.length > 1 && !['exports', 'lib', 'node_modules', 'src', 'static', 'vendor'].includes(segments[0])) return + function onFile(filePath) { + filePath = path.relative(rootPath, filePath); + const segments = filePath.split(path.sep); + if (segments.includes('test')) return; + if (segments.includes('tests')) return; + if (segments.includes('spec')) return; + if (segments.includes('specs')) return; + if ( + segments.length > 1 && + !['exports', 'lib', 'node_modules', 'src', 'static', 'vendor'].includes( + segments[0] + ) + ) + return; - const extension = path.extname(filePath) + const extension = path.extname(filePath); if (extensions.includes(extension)) { - if (moduleCache.extensions[extension] == null) { moduleCache.extensions[extension] = [] } - moduleCache.extensions[extension].push(filePath) + if (moduleCache.extensions[extension] == null) { + moduleCache.extensions[extension] = []; + } + moduleCache.extensions[extension].push(filePath); } } - function onDirectory (childPath) { + function onDirectory(childPath) { // Don't include extensions from bundled packages // These are generated and stored in the package's own metadata cache if (rootMetadata.name === 'atom') { - const parentPath = path.dirname(childPath) + const parentPath = path.dirname(childPath); if (parentPath === nodeModulesPath) { - const packageName = path.basename(childPath) - if (rootMetadata.packageDependencies && rootMetadata.packageDependencies.hasOwnProperty(packageName)) return false + const packageName = path.basename(childPath); + if ( + rootMetadata.packageDependencies && + rootMetadata.packageDependencies.hasOwnProperty(packageName) + ) + return false; } } - return true + return true; } - fs.traverseTreeSync(rootPath, onFile, onDirectory) + fs.traverseTreeSync(rootPath, onFile, onDirectory); } -function satisfies (version, rawRange) { - let parsedRange +function satisfies(version, rawRange) { + let parsedRange; if (!(parsedRange = cache.ranges[rawRange])) { - parsedRange = new Range(rawRange) - cache.ranges[rawRange] = parsedRange + parsedRange = new Range(rawRange); + cache.ranges[rawRange] = parsedRange; } - return parsedRange.test(version) + return parsedRange.test(version); } -function resolveFilePath (relativePath, parentModule) { - if (!relativePath) return - if (!(parentModule && parentModule.filename)) return - if (relativePath[0] !== '.' && !isAbsolute(relativePath)) return +function resolveFilePath(relativePath, parentModule) { + if (!relativePath) return; + if (!(parentModule && parentModule.filename)) return; + if (relativePath[0] !== '.' && !isAbsolute(relativePath)) return; - const resolvedPath = path.resolve(path.dirname(parentModule.filename), relativePath) - if (!isCorePath(resolvedPath)) return + const resolvedPath = path.resolve( + path.dirname(parentModule.filename), + relativePath + ); + if (!isCorePath(resolvedPath)) return; - let extension = path.extname(resolvedPath) + let extension = path.extname(resolvedPath); if (extension) { - if (cache.extensions[extension] && cache.extensions[extension].has(resolvedPath)) return resolvedPath + if ( + cache.extensions[extension] && + cache.extensions[extension].has(resolvedPath) + ) + return resolvedPath; } else { for (extension in cache.extensions) { - const paths = cache.extensions[extension] - const resolvedPathWithExtension = `${resolvedPath}${extension}` + const paths = cache.extensions[extension]; + const resolvedPathWithExtension = `${resolvedPath}${extension}`; if (paths.has(resolvedPathWithExtension)) { - return resolvedPathWithExtension + return resolvedPathWithExtension; } } } } -function resolveModulePath (relativePath, parentModule) { - if (!relativePath) return - if (!(parentModule && parentModule.filename)) return +function resolveModulePath(relativePath, parentModule) { + if (!relativePath) return; + if (!(parentModule && parentModule.filename)) return; - if (!nativeModules) nativeModules = process.binding('natives') - if (nativeModules.hasOwnProperty(relativePath)) return - if (relativePath[0] === '.') return - if (isAbsolute(relativePath)) return + if (!nativeModules) nativeModules = process.binding('natives'); + if (nativeModules.hasOwnProperty(relativePath)) return; + if (relativePath[0] === '.') return; + if (isAbsolute(relativePath)) return; - const folderPath = path.dirname(parentModule.filename) + const folderPath = path.dirname(parentModule.filename); - const range = cache.folders[folderPath] && cache.folders[folderPath][relativePath] + const range = + cache.folders[folderPath] && cache.folders[folderPath][relativePath]; if (!range) { - const builtinPath = cache.builtins[relativePath] + const builtinPath = cache.builtins[relativePath]; if (builtinPath) { - return builtinPath + return builtinPath; } else { - return + return; } } - const candidates = cache.dependencies[relativePath] - if (candidates == null) return + const candidates = cache.dependencies[relativePath]; + if (candidates == null) return; for (let version in candidates) { - const resolvedPath = candidates[version] + const resolvedPath = candidates[version]; if (Module._cache[resolvedPath] || isCorePath(resolvedPath)) { - if (satisfies(version, range)) return resolvedPath + if (satisfies(version, range)) return resolvedPath; } } } -function registerBuiltins (devMode) { - if (devMode || !cache.resourcePath.startsWith(`${process.resourcesPath}${path.sep}`)) { - const fs = require('fs-plus') - const atomJsPath = path.join(cache.resourcePath, 'exports', 'atom.js') - if (fs.isFileSync(atomJsPath)) { cache.builtins.atom = atomJsPath } +function registerBuiltins(devMode) { + if ( + devMode || + !cache.resourcePath.startsWith(`${process.resourcesPath}${path.sep}`) + ) { + const fs = require('fs-plus'); + const atomJsPath = path.join(cache.resourcePath, 'exports', 'atom.js'); + if (fs.isFileSync(atomJsPath)) { + cache.builtins.atom = atomJsPath; + } + } + if (cache.builtins.atom == null) { + cache.builtins.atom = path.join(cache.resourcePath, 'exports', 'atom.js'); } - if (cache.builtins.atom == null) { cache.builtins.atom = path.join(cache.resourcePath, 'exports', 'atom.js') } - const electronAsarRoot = path.join(process.resourcesPath, 'electron.asar') + const electronAsarRoot = path.join(process.resourcesPath, 'electron.asar'); - const commonRoot = path.join(electronAsarRoot, 'common', 'api') - const commonBuiltins = ['callbacks-registry', 'clipboard', 'crash-reporter', 'shell'] + const commonRoot = path.join(electronAsarRoot, 'common', 'api'); + const commonBuiltins = [ + 'callbacks-registry', + 'clipboard', + 'crash-reporter', + 'shell' + ]; for (const builtin of commonBuiltins) { - cache.builtins[builtin] = path.join(commonRoot, `${builtin}.js`) + cache.builtins[builtin] = path.join(commonRoot, `${builtin}.js`); } - const rendererRoot = path.join(electronAsarRoot, 'renderer', 'api') - const rendererBuiltins = ['ipc-renderer', 'remote', 'screen'] + const rendererRoot = path.join(electronAsarRoot, 'renderer', 'api'); + const rendererBuiltins = ['ipc-renderer', 'remote', 'screen']; for (const builtin of rendererBuiltins) { - cache.builtins[builtin] = path.join(rendererRoot, `${builtin}.js`) + cache.builtins[builtin] = path.join(rendererRoot, `${builtin}.js`); } } -exports.create = function (modulePath) { - const fs = require('fs-plus') +exports.create = function(modulePath) { + const fs = require('fs-plus'); - modulePath = fs.realpathSync(modulePath) - const metadataPath = path.join(modulePath, 'package.json') - const metadata = JSON.parse(fs.readFileSync(metadataPath)) + modulePath = fs.realpathSync(modulePath); + const metadataPath = path.join(modulePath, 'package.json'); + const metadata = JSON.parse(fs.readFileSync(metadataPath)); const moduleCache = { version: 1, dependencies: [], extensions: {}, folders: [] - } + }; - loadDependencies(modulePath, modulePath, metadata, moduleCache) - loadFolderCompatibility(modulePath, modulePath, metadata, moduleCache) - loadExtensions(modulePath, modulePath, metadata, moduleCache) + loadDependencies(modulePath, modulePath, metadata, moduleCache); + loadFolderCompatibility(modulePath, modulePath, metadata, moduleCache); + loadExtensions(modulePath, modulePath, metadata, moduleCache); - metadata._atomModuleCache = moduleCache - fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2)) -} + metadata._atomModuleCache = moduleCache; + fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2)); +}; -exports.register = function ({resourcePath, devMode} = {}) { - if (cache.registered) return +exports.register = function({ resourcePath, devMode } = {}) { + if (cache.registered) return; - const originalResolveFilename = Module._resolveFilename - Module._resolveFilename = function (relativePath, parentModule) { - let resolvedPath = resolveModulePath(relativePath, parentModule) + const originalResolveFilename = Module._resolveFilename; + Module._resolveFilename = function(relativePath, parentModule) { + let resolvedPath = resolveModulePath(relativePath, parentModule); if (!resolvedPath) { - resolvedPath = resolveFilePath(relativePath, parentModule) + resolvedPath = resolveFilePath(relativePath, parentModule); } - return resolvedPath || originalResolveFilename(relativePath, parentModule) - } + return resolvedPath || originalResolveFilename(relativePath, parentModule); + }; - cache.registered = true - cache.resourcePath = resourcePath - cache.resourcePathWithTrailingSlash = `${resourcePath}${path.sep}` - registerBuiltins(devMode) -} + cache.registered = true; + cache.resourcePath = resourcePath; + cache.resourcePathWithTrailingSlash = `${resourcePath}${path.sep}`; + registerBuiltins(devMode); +}; -exports.add = function (directoryPath, metadata) { +exports.add = function(directoryPath, metadata) { // path.join isn't used in this function for speed since path.join calls // path.normalize and all the paths are already normalized here. if (metadata == null) { try { - metadata = require(`${directoryPath}${path.sep}package.json`) + metadata = require(`${directoryPath}${path.sep}package.json`); } catch (error) { - return + return; } } - const cacheToAdd = metadata && metadata._atomModuleCache - if (!cacheToAdd) return + const cacheToAdd = metadata && metadata._atomModuleCache; + if (!cacheToAdd) return; for (const dependency of cacheToAdd.dependencies || []) { if (!cache.dependencies[dependency.name]) { - cache.dependencies[dependency.name] = {} + cache.dependencies[dependency.name] = {}; } if (!cache.dependencies[dependency.name][dependency.version]) { - cache.dependencies[dependency.name][dependency.version] = `${directoryPath}${path.sep}${dependency.path}` + cache.dependencies[dependency.name][ + dependency.version + ] = `${directoryPath}${path.sep}${dependency.path}`; } } for (const entry of cacheToAdd.folders || []) { for (const folderPath of entry.paths) { if (folderPath) { - cache.folders[`${directoryPath}${path.sep}${folderPath}`] = entry.dependencies + cache.folders[`${directoryPath}${path.sep}${folderPath}`] = + entry.dependencies; } else { - cache.folders[directoryPath] = entry.dependencies + cache.folders[directoryPath] = entry.dependencies; } } } for (const extension in cacheToAdd.extensions) { - const paths = cacheToAdd.extensions[extension] + const paths = cacheToAdd.extensions[extension]; if (!cache.extensions[extension]) { - cache.extensions[extension] = new Set() + cache.extensions[extension] = new Set(); } for (let filePath of paths) { - cache.extensions[extension].add(`${directoryPath}${path.sep}${filePath}`) + cache.extensions[extension].add(`${directoryPath}${path.sep}${filePath}`); } } -} +}; -exports.cache = cache +exports.cache = cache; -exports.Range = Range +exports.Range = Range; diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 786868355..5b097954c 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -1,114 +1,130 @@ -const Module = require('module') -const path = require('path') -const crypto = require('crypto') -const vm = require('vm') +const Module = require('module'); +const path = require('path'); +const crypto = require('crypto'); +const vm = require('vm'); -function computeHash (contents) { - return crypto.createHash('sha1').update(contents, 'utf8').digest('hex') +function computeHash(contents) { + return crypto + .createHash('sha1') + .update(contents, 'utf8') + .digest('hex'); } class NativeCompileCache { - constructor () { - this.cacheStore = null - this.previousModuleCompile = null + constructor() { + this.cacheStore = null; + this.previousModuleCompile = null; } - setCacheStore (store) { - this.cacheStore = store + setCacheStore(store) { + this.cacheStore = store; } - setV8Version (v8Version) { - this.v8Version = v8Version.toString() + setV8Version(v8Version) { + this.v8Version = v8Version.toString(); } - install () { - this.savePreviousModuleCompile() - this.overrideModuleCompile() + install() { + this.savePreviousModuleCompile(); + this.overrideModuleCompile(); } - uninstall () { - this.restorePreviousModuleCompile() + uninstall() { + this.restorePreviousModuleCompile(); } - savePreviousModuleCompile () { - this.previousModuleCompile = Module.prototype._compile + savePreviousModuleCompile() { + this.previousModuleCompile = Module.prototype._compile; } - runInThisContext (code, filename) { + runInThisContext(code, filename) { // TodoElectronIssue: produceCachedData is deprecated after Node 10.6, so we'll // will need to update this for Electron v4 to use script.createCachedData(). - const script = new vm.Script(code, {filename, produceCachedData: true}) + const script = new vm.Script(code, { filename, produceCachedData: true }); return { result: script.runInThisContext(), cacheBuffer: script.cachedData - } + }; } - runInThisContextCached (code, filename, cachedData) { - const script = new vm.Script(code, {filename, cachedData}) + runInThisContextCached(code, filename, cachedData) { + const script = new vm.Script(code, { filename, cachedData }); return { result: script.runInThisContext(), wasRejected: script.cachedDataRejected - } + }; } - overrideModuleCompile () { - let self = this + overrideModuleCompile() { + let self = this; // 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 + Module.prototype._compile = function(content, filename) { + let moduleSelf = this; // remove shebang - content = content.replace(/^#!.*/, '') - function require (path) { - return moduleSelf.require(path) + content = content.replace(/^#!.*/, ''); + function require(path) { + return moduleSelf.require(path); } - require.resolve = function (request) { - return Module._resolveFilename(request, moduleSelf) - } - require.main = process.mainModule + 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 + require.extensions = Module._extensions; + require.cache = Module._cache; - let dirname = path.dirname(filename) + let dirname = path.dirname(filename); // create wrapper function - let wrapper = Module.wrap(content) + let wrapper = Module.wrap(content); - let cacheKey = computeHash(wrapper + self.v8Version) - let compiledWrapper = null + let cacheKey = computeHash(wrapper + self.v8Version); + let compiledWrapper = null; if (self.cacheStore.has(cacheKey)) { - let buffer = self.cacheStore.get(cacheKey) - let compilationResult = self.runInThisContextCached(wrapper, filename, buffer) - compiledWrapper = compilationResult.result + let buffer = self.cacheStore.get(cacheKey); + let compilationResult = self.runInThisContextCached( + wrapper, + filename, + buffer + ); + compiledWrapper = compilationResult.result; if (compilationResult.wasRejected) { - self.cacheStore.delete(cacheKey) + self.cacheStore.delete(cacheKey); } } else { - let compilationResult + let compilationResult; try { - compilationResult = self.runInThisContext(wrapper, filename) + compilationResult = self.runInThisContext(wrapper, filename); } catch (err) { - console.error(`Error running script ${filename}`) - throw err + console.error(`Error running script ${filename}`); + throw err; } if (compilationResult.cacheBuffer !== null) { - self.cacheStore.set(cacheKey, compilationResult.cacheBuffer) + self.cacheStore.set(cacheKey, compilationResult.cacheBuffer); } - compiledWrapper = compilationResult.result + compiledWrapper = compilationResult.result; } - let args = [moduleSelf.exports, require, moduleSelf, filename, dirname, process, global, Buffer] - return compiledWrapper.apply(moduleSelf.exports, args) - } + let args = [ + moduleSelf.exports, + require, + moduleSelf, + filename, + dirname, + process, + global, + Buffer + ]; + return compiledWrapper.apply(moduleSelf.exports, args); + }; } - restorePreviousModuleCompile () { - Module.prototype._compile = this.previousModuleCompile + restorePreviousModuleCompile() { + Module.prototype._compile = this.previousModuleCompile; } } -module.exports = new NativeCompileCache() +module.exports = new NativeCompileCache(); diff --git a/src/native-watcher-registry.js b/src/native-watcher-registry.js index 97f33e3fb..54f2cdcd4 100644 --- a/src/native-watcher-registry.js +++ b/src/native-watcher-registry.js @@ -1,9 +1,11 @@ -const path = require('path') +const path = require('path'); // Private: re-join the segments split from an absolute path to form another absolute path. -function absolute (...parts) { - const candidate = path.join(...parts) - return path.isAbsolute(candidate) ? candidate : path.join(path.sep, candidate) +function absolute(...parts) { + const candidate = path.join(...parts); + return path.isAbsolute(candidate) + ? candidate + : path.join(path.sep, candidate); } // Private: Map userland filesystem watcher subscriptions efficiently to deliver filesystem change notifications to @@ -16,17 +18,16 @@ function absolute (...parts) { // // Uses a trie whose structure mirrors the directory structure. class RegistryTree { - // Private: Construct a tree with no native watchers. // // * `basePathSegments` the position of this tree's root relative to the filesystem's root as an {Array} of directory // names. // * `createNative` {Function} used to construct new native watchers. It should accept an absolute path as an argument // and return a new {NativeWatcher}. - constructor (basePathSegments, createNative) { - this.basePathSegments = basePathSegments - this.root = new RegistryNode() - this.createNative = createNative + constructor(basePathSegments, createNative) { + this.basePathSegments = basePathSegments; + this.root = new RegistryNode(); + this.createNative = createNative; } // Private: Identify the native watcher that should be used to produce events at a watched path, creating a new one @@ -35,69 +36,73 @@ class RegistryTree { // * `pathSegments` the path to watch represented as an {Array} of directory names relative to this {RegistryTree}'s // root. // * `attachToNative` {Function} invoked with the appropriate native watcher and the absolute path to its watch root. - add (pathSegments, attachToNative) { - const absolutePathSegments = this.basePathSegments.concat(pathSegments) - const absolutePath = absolute(...absolutePathSegments) + add(pathSegments, attachToNative) { + const absolutePathSegments = this.basePathSegments.concat(pathSegments); + const absolutePath = absolute(...absolutePathSegments); - const attachToNew = (childPaths) => { - const native = this.createNative(absolutePath) - const leaf = new RegistryWatcherNode(native, absolutePathSegments, childPaths) - this.root = this.root.insert(pathSegments, leaf) + const attachToNew = childPaths => { + const native = this.createNative(absolutePath); + const leaf = new RegistryWatcherNode( + native, + absolutePathSegments, + childPaths + ); + this.root = this.root.insert(pathSegments, leaf); const sub = native.onWillStop(() => { - sub.dispose() - this.root = this.root.remove(pathSegments, this.createNative) || new RegistryNode() - }) + sub.dispose(); + this.root = + this.root.remove(pathSegments, this.createNative) || + new RegistryNode(); + }); - attachToNative(native, absolutePath) - return native - } + attachToNative(native, absolutePath); + return native; + }; this.root.lookup(pathSegments).when({ parent: (parent, remaining) => { // An existing NativeWatcher is watching the same directory or a parent directory of the requested path. // Attach this Watcher to it as a filtering watcher and record it as a dependent child path. - const native = parent.getNativeWatcher() - parent.addChildPath(remaining) - attachToNative(native, absolute(...parent.getAbsolutePathSegments())) + const native = parent.getNativeWatcher(); + parent.addChildPath(remaining); + attachToNative(native, absolute(...parent.getAbsolutePathSegments())); }, children: children => { // One or more NativeWatchers exist on child directories of the requested path. Create a new native watcher // on the parent directory, note the subscribed child paths, and cleanly stop the child native watchers. - const newNative = attachToNew(children.map(child => child.path)) + const newNative = attachToNew(children.map(child => child.path)); for (let i = 0; i < children.length; i++) { - const childNode = children[i].node - const childNative = childNode.getNativeWatcher() - childNative.reattachTo(newNative, absolutePath) - childNative.dispose() - childNative.stop() + const childNode = children[i].node; + const childNative = childNode.getNativeWatcher(); + childNative.reattachTo(newNative, absolutePath); + childNative.dispose(); + childNative.stop(); } }, missing: () => attachToNew([]) - }) + }); } // Private: Access the root node of the tree. - getRoot () { - return this.root + getRoot() { + return this.root; } // Private: Return a {String} representation of this tree's structure for diagnostics and testing. - print () { - return this.root.print() + print() { + return this.root.print(); } - } // Private: Non-leaf node in a {RegistryTree} used by the {NativeWatcherRegistry} to cover the allocated {Watcher} // instances with the most efficient set of {NativeWatcher} instances possible. Each {RegistryNode} maps to a directory // in the filesystem tree. class RegistryNode { - // Private: Construct a new, empty node representing a node with no watchers. - constructor () { - this.children = {} + constructor() { + this.children = {}; } // Private: Recursively discover any existing watchers corresponding to a path. @@ -107,17 +112,17 @@ class RegistryNode { // Returns: A {ParentResult} if the exact requested directory or a parent directory is being watched, a // {ChildrenResult} if one or more child paths are being watched, or a {MissingResult} if no relevant watchers // exist. - lookup (pathSegments) { + lookup(pathSegments) { if (pathSegments.length === 0) { - return new ChildrenResult(this.leaves([])) + return new ChildrenResult(this.leaves([])); } - const child = this.children[pathSegments[0]] + const child = this.children[pathSegments[0]]; if (child === undefined) { - return new MissingResult(this) + return new MissingResult(this); } - return child.lookup(pathSegments.slice(1)) + return child.lookup(pathSegments.slice(1)); } // Private: Insert a new {RegistryWatcherNode} into the tree, creating new intermediate {RegistryNode} instances as @@ -128,18 +133,18 @@ class RegistryNode { // // Returns: The root of a new tree with the {RegistryWatcherNode} inserted at the correct location. Callers should // replace their node references with the returned value. - insert (pathSegments, leaf) { + insert(pathSegments, leaf) { if (pathSegments.length === 0) { - return leaf + return leaf; } - const pathKey = pathSegments[0] - let child = this.children[pathKey] + const pathKey = pathSegments[0]; + let child = this.children[pathKey]; if (child === undefined) { - child = new RegistryNode() + child = new RegistryNode(); } - this.children[pathKey] = child.insert(pathSegments.slice(1), leaf) - return this + this.children[pathKey] = child.insert(pathSegments.slice(1), leaf); + return this; } // Private: Remove a {RegistryWatcherNode} by its exact watched directory. @@ -150,29 +155,29 @@ class RegistryNode { // // Returns: The root of a new tree with the {RegistryWatcherNode} removed. Callers should replace their node // references with the returned value. - remove (pathSegments, createSplitNative) { + remove(pathSegments, createSplitNative) { if (pathSegments.length === 0) { // Attempt to remove a path with child watchers. Do nothing. - return this + return this; } - const pathKey = pathSegments[0] - const child = this.children[pathKey] + const pathKey = pathSegments[0]; + const child = this.children[pathKey]; if (child === undefined) { // Attempt to remove a path that isn't watched. Do nothing. - return this + return this; } // Recurse - const newChild = child.remove(pathSegments.slice(1), createSplitNative) + const newChild = child.remove(pathSegments.slice(1), createSplitNative); if (newChild === null) { - delete this.children[pathKey] + delete this.children[pathKey]; } else { - this.children[pathKey] = newChild + this.children[pathKey] = newChild; } // Remove this node if all of its children have been removed - return Object.keys(this.children).length === 0 ? null : this + return Object.keys(this.children).length === 0 ? null : this; } // Private: Discover all {RegistryWatcherNode} instances beneath this tree node and the child paths @@ -182,33 +187,32 @@ class RegistryNode { // // Returns: A possibly empty {Array} of `{node, path}` objects describing {RegistryWatcherNode} // instances beneath this node. - leaves (prefix) { - const results = [] + leaves(prefix) { + const results = []; for (const p of Object.keys(this.children)) { - results.push(...this.children[p].leaves(prefix.concat([p]))) + results.push(...this.children[p].leaves(prefix.concat([p]))); } - return results + return results; } // Private: Return a {String} representation of this subtree for diagnostics and testing. - print (indent = 0) { - let spaces = '' + print(indent = 0) { + let spaces = ''; for (let i = 0; i < indent; i++) { - spaces += ' ' + spaces += ' '; } - let result = '' + let result = ''; for (const p of Object.keys(this.children)) { - result += `${spaces}${p}\n${this.children[p].print(indent + 2)}` + result += `${spaces}${p}\n${this.children[p].print(indent + 2)}`; } - return result + return result; } } // Private: Leaf node within a {NativeWatcherRegistry} tree. Represents a directory that is covered by a // {NativeWatcher}. class RegistryWatcherNode { - // Private: Allocate a new node to track a {NativeWatcher}. // // * `nativeWatcher` An existing {NativeWatcher} instance. @@ -217,14 +221,14 @@ class RegistryWatcherNode { // * `childPaths` {Array} of child directories that are currently the responsibility of this // {NativeWatcher}, if any. Directories are represented as arrays of the path segments between this // node's directory and the watched child path. - constructor (nativeWatcher, absolutePathSegments, childPaths) { - this.nativeWatcher = nativeWatcher - this.absolutePathSegments = absolutePathSegments + constructor(nativeWatcher, absolutePathSegments, childPaths) { + this.nativeWatcher = nativeWatcher; + this.absolutePathSegments = absolutePathSegments; // Store child paths as joined strings so they work as Set members. - this.childPaths = new Set() + this.childPaths = new Set(); for (let i = 0; i < childPaths.length; i++) { - this.childPaths.add(path.join(...childPaths[i])) + this.childPaths.add(path.join(...childPaths[i])); } } @@ -233,8 +237,8 @@ class RegistryWatcherNode { // // * `childPathSegments` the {Array} of path segments between this node's directory and the watched // child directory. - addChildPath (childPathSegments) { - this.childPaths.add(path.join(...childPathSegments)) + addChildPath(childPathSegments) { + this.childPaths.add(path.join(...childPathSegments)); } // Private: Stop assuming responsibility for a previously assigned child path. If this node is @@ -242,18 +246,18 @@ class RegistryWatcherNode { // // * `childPathSegments` the {Array} of path segments between this node's directory and the no longer // watched child directory. - removeChildPath (childPathSegments) { - this.childPaths.delete(path.join(...childPathSegments)) + removeChildPath(childPathSegments) { + this.childPaths.delete(path.join(...childPathSegments)); } // Private: Accessor for the {NativeWatcher}. - getNativeWatcher () { - return this.nativeWatcher + getNativeWatcher() { + return this.nativeWatcher; } // Private: Return the absolute path watched by this {NativeWatcher} as an {Array} of directory names. - getAbsolutePathSegments () { - return this.absolutePathSegments + getAbsolutePathSegments() { + return this.absolutePathSegments; } // Private: Identify how this watcher relates to a request to watch a directory tree. @@ -261,8 +265,8 @@ class RegistryWatcherNode { // * `pathSegments` filesystem path of a new {Watcher} already split into an Array of directory names. // // Returns: A {ParentResult} referencing this node. - lookup (pathSegments) { - return new ParentResult(this, pathSegments) + lookup(pathSegments) { + return new ParentResult(this, pathSegments); } // Private: Remove this leaf node if the watcher's exact path matches. If this node is covering additional @@ -276,22 +280,25 @@ class RegistryWatcherNode { // or a new {RegistryNode} on a newly allocated subtree if it did. If `pathSegments` does not match the watcher's // path, it's an attempt to remove a subnode that doesn't exist, so the remove call has no effect and returns // `this` unaltered. - remove (pathSegments, createSplitNative) { + remove(pathSegments, createSplitNative) { if (pathSegments.length !== 0) { - return this + return this; } else if (this.childPaths.size > 0) { - let newSubTree = new RegistryTree(this.absolutePathSegments, createSplitNative) + let newSubTree = new RegistryTree( + this.absolutePathSegments, + createSplitNative + ); for (const childPath of this.childPaths) { - const childPathSegments = childPath.split(path.sep) + const childPathSegments = childPath.split(path.sep); newSubTree.add(childPathSegments, (native, attachmentPath) => { - this.nativeWatcher.reattachTo(native, attachmentPath) - }) + this.nativeWatcher.reattachTo(native, attachmentPath); + }); } - return newSubTree.getRoot() + return newSubTree.getRoot(); } else { - return null + return null; } } @@ -300,36 +307,35 @@ class RegistryWatcherNode { // * `prefix` {Array} of intermediate path segments to prepend to the resulting child paths. // // Returns: An {Array} containing a `{node, path}` object describing this node. - leaves (prefix) { - return [{node: this, path: prefix}] + leaves(prefix) { + return [{ node: this, path: prefix }]; } // Private: Return a {String} representation of this watcher for diagnostics and testing. Indicates the number of // child paths that this node's {NativeWatcher} is responsible for. - print (indent = 0) { - let result = '' + print(indent = 0) { + let result = ''; for (let i = 0; i < indent; i++) { - result += ' ' + result += ' '; } - result += '[watcher' + result += '[watcher'; if (this.childPaths.size > 0) { - result += ` +${this.childPaths.size}` + result += ` +${this.childPaths.size}`; } - result += ']\n' + result += ']\n'; - return result + return result; } } // Private: A {RegistryNode} traversal result that's returned when neither a directory, its children, nor its parents // are present in the tree. class MissingResult { - // Private: Instantiate a new {MissingResult}. // // * `lastParent` the final successfully traversed {RegistryNode}. - constructor (lastParent) { - this.lastParent = lastParent + constructor(lastParent) { + this.lastParent = lastParent; } // Private: Dispatch within a map of callback actions. @@ -339,23 +345,22 @@ class MissingResult { // traversal. // // Returns: the result of the `actions` callback. - when (actions) { - return actions.missing(this.lastParent) + when(actions) { + return actions.missing(this.lastParent); } } // Private: A {RegistryNode.lookup} traversal result that's returned when a parent or an exact match of the requested // directory is being watched by an existing {RegistryWatcherNode}. class ParentResult { - // Private: Instantiate a new {ParentResult}. // // * `parent` the {RegistryWatcherNode} that was discovered. // * `remainingPathSegments` an {Array} of the directories that lie between the leaf node's watched directory and // the requested directory. This will be empty for exact matches. - constructor (parent, remainingPathSegments) { - this.parent = parent - this.remainingPathSegments = remainingPathSegments + constructor(parent, remainingPathSegments) { + this.parent = parent; + this.remainingPathSegments = remainingPathSegments; } // Private: Dispatch within a map of callback actions. @@ -366,20 +371,19 @@ class ParentResult { // and the requested directory. // // Returns: the result of the `actions` callback. - when (actions) { - return actions.parent(this.parent, this.remainingPathSegments) + when(actions) { + return actions.parent(this.parent, this.remainingPathSegments); } } // Private: A {RegistryNode.lookup} traversal result that's returned when one or more children of the requested // directory are already being watched. class ChildrenResult { - // Private: Instantiate a new {ChildrenResult}. // // * `children` {Array} of the {RegistryWatcherNode} instances that were discovered. - constructor (children) { - this.children = children + constructor(children) { + this.children = children; } // Private: Dispatch within a map of callback actions. @@ -389,8 +393,8 @@ class ChildrenResult { // {RegistryWatcherNode} instance. // // Returns: the result of the `actions` callback. - when (actions) { - return actions.children(this.children) + when(actions) { + return actions.children(this.children); } } @@ -402,13 +406,12 @@ class ChildrenResult { // 3. Replacing multiple {NativeWatcher} instances on child directories with a single new {NativeWatcher} on the // parent. class NativeWatcherRegistry { - // Private: Instantiate an empty registry. // // * `createNative` {Function} that will be called with a normalized filesystem path to create a new native // filesystem watcher. - constructor (createNative) { - this.tree = new RegistryTree([], createNative) + constructor(createNative) { + this.tree = new RegistryTree([], createNative); } // Private: Attach a watcher to a directory, assigning it a {NativeWatcher}. If a suitable {NativeWatcher} already @@ -421,22 +424,24 @@ class NativeWatcherRegistry { // the new watcher. // // * `watcher` an unattached {Watcher}. - async attach (watcher) { - const normalizedDirectory = await watcher.getNormalizedPathPromise() - const pathSegments = normalizedDirectory.split(path.sep).filter(segment => segment.length > 0) + async attach(watcher) { + const normalizedDirectory = await watcher.getNormalizedPathPromise(); + const pathSegments = normalizedDirectory + .split(path.sep) + .filter(segment => segment.length > 0); this.tree.add(pathSegments, (native, nativePath) => { - watcher.attachToNative(native, nativePath) - }) + watcher.attachToNative(native, nativePath); + }); } // Private: Generate a visual representation of the currently active watchers managed by this // registry. // // Returns a {String} showing the tree structure. - print () { - return this.tree.print() + print() { + return this.tree.print(); } } -module.exports = {NativeWatcherRegistry} +module.exports = { NativeWatcherRegistry }; diff --git a/src/notification-manager.js b/src/notification-manager.js index a0ae139d3..8ee7ad659 100644 --- a/src/notification-manager.js +++ b/src/notification-manager.js @@ -1,16 +1,15 @@ -const {Emitter} = require('event-kit') -const Notification = require('../src/notification') +const { Emitter } = require('event-kit'); +const Notification = require('../src/notification'); // Public: A notification manager used to create {Notification}s to be shown // to the user. // // An instance of this class is always available as the `atom.notifications` // global. -module.exports = -class NotificationManager { - constructor () { - this.notifications = [] - this.emitter = new Emitter() +module.exports = class NotificationManager { + constructor() { + this.notifications = []; + this.emitter = new Emitter(); } /* @@ -23,8 +22,8 @@ class NotificationManager { // * `notification` The {Notification} that was added. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddNotification (callback) { - return this.emitter.on('did-add-notification', callback) + onDidAddNotification(callback) { + return this.emitter.on('did-add-notification', callback); } // Public: Invoke the given callback after the notifications have been cleared. @@ -32,8 +31,8 @@ class NotificationManager { // * `callback` {Function} to be called after the notifications are cleared. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidClearNotifications (callback) { - return this.emitter.on('did-clear-notifications', callback) + onDidClearNotifications(callback) { + return this.emitter.on('did-clear-notifications', callback); } /* @@ -64,8 +63,8 @@ class NotificationManager { // in the notification header. Defaults to `'check'`. // // Returns the {Notification} that was added. - addSuccess (message, options) { - return this.addNotification(new Notification('success', message, options)) + addSuccess(message, options) { + return this.addNotification(new Notification('success', message, options)); } // Public: Add an informational notification. @@ -92,8 +91,8 @@ class NotificationManager { // in the notification header. Defaults to `'info'`. // // Returns the {Notification} that was added. - addInfo (message, options) { - return this.addNotification(new Notification('info', message, options)) + addInfo(message, options) { + return this.addNotification(new Notification('info', message, options)); } // Public: Add a warning notification. @@ -120,8 +119,8 @@ class NotificationManager { // in the notification header. Defaults to `'alert'`. // // Returns the {Notification} that was added. - addWarning (message, options) { - return this.addNotification(new Notification('warning', message, options)) + addWarning(message, options) { + return this.addNotification(new Notification('warning', message, options)); } // Public: Add an error notification. @@ -150,8 +149,8 @@ class NotificationManager { // information describing the location of the error. // // Returns the {Notification} that was added. - addError (message, options) { - return this.addNotification(new Notification('error', message, options)) + addError(message, options) { + return this.addNotification(new Notification('error', message, options)); } // Public: Add a fatal error notification. @@ -180,18 +179,18 @@ class NotificationManager { // information describing the location of the error. // // Returns the {Notification} that was added. - addFatalError (message, options) { - return this.addNotification(new Notification('fatal', message, options)) + addFatalError(message, options) { + return this.addNotification(new Notification('fatal', message, options)); } - add (type, message, options) { - return this.addNotification(new Notification(type, message, options)) + add(type, message, options) { + return this.addNotification(new Notification(type, message, options)); } - addNotification (notification) { - this.notifications.push(notification) - this.emitter.emit('did-add-notification', notification) - return notification + addNotification(notification) { + this.notifications.push(notification); + this.emitter.emit('did-add-notification', notification); + return notification; } /* @@ -201,8 +200,8 @@ class NotificationManager { // Public: Get all the notifications. // // Returns an {Array} of {Notification}s. - getNotifications () { - return this.notifications.slice() + getNotifications() { + return this.notifications.slice(); } /* @@ -210,8 +209,8 @@ class NotificationManager { */ // Public: Clear all the notifications. - clear () { - this.notifications = [] - this.emitter.emit('did-clear-notifications') + clear() { + this.notifications = []; + this.emitter.emit('did-clear-notifications'); } -} +}; diff --git a/src/notification.js b/src/notification.js index 96fad59e0..88e82d7ec 100644 --- a/src/notification.js +++ b/src/notification.js @@ -1,28 +1,31 @@ -const {Emitter} = require('event-kit') -const _ = require('underscore-plus') +const { Emitter } = require('event-kit'); +const _ = require('underscore-plus'); // Public: A notification to the user containing a message and type. -module.exports = -class Notification { - constructor (type, message, options = {}) { - this.type = type - this.message = message - this.options = options - this.emitter = new Emitter() - this.timestamp = new Date() - this.dismissed = true - if (this.isDismissable()) this.dismissed = false - this.displayed = false - this.validate() +module.exports = class Notification { + constructor(type, message, options = {}) { + this.type = type; + this.message = message; + this.options = options; + this.emitter = new Emitter(); + this.timestamp = new Date(); + this.dismissed = true; + if (this.isDismissable()) this.dismissed = false; + this.displayed = false; + this.validate(); } - validate () { + validate() { if (typeof this.message !== 'string') { - throw new Error(`Notification must be created with string message: ${this.message}`) + throw new Error( + `Notification must be created with string message: ${this.message}` + ); } if (!_.isObject(this.options) || Array.isArray(this.options)) { - throw new Error(`Notification must be created with an options object: ${this.options}`) + throw new Error( + `Notification must be created with an options object: ${this.options}` + ); } } @@ -35,8 +38,8 @@ class Notification { // * `callback` {Function} to be called when the notification is dismissed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDismiss (callback) { - return this.emitter.on('did-dismiss', callback) + onDidDismiss(callback) { + return this.emitter.on('did-dismiss', callback); } // Public: Invoke the given callback when the notification is displayed. @@ -44,12 +47,12 @@ class Notification { // * `callback` {Function} to be called when the notification is displayed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDisplay (callback) { - return this.emitter.on('did-display', callback) + onDidDisplay(callback) { + return this.emitter.on('did-display', callback); } - getOptions () { - return this.options + getOptions() { + return this.options; } /* @@ -57,62 +60,69 @@ class Notification { */ // Public: Returns the {String} type. - getType () { - return this.type + getType() { + return this.type; } // Public: Returns the {String} message. - getMessage () { - return this.message + getMessage() { + return this.message; } - getTimestamp () { - return this.timestamp + getTimestamp() { + return this.timestamp; } - getDetail () { - return this.options.detail + getDetail() { + return this.options.detail; } - isEqual (other) { - return (this.getMessage() === other.getMessage()) && - (this.getType() === other.getType()) && - (this.getDetail() === other.getDetail()) + isEqual(other) { + return ( + this.getMessage() === other.getMessage() && + this.getType() === other.getType() && + this.getDetail() === other.getDetail() + ); } // Extended: Dismisses the notification, removing it from the UI. Calling this // programmatically will call all callbacks added via `onDidDismiss`. - dismiss () { - if (!this.isDismissable() || this.isDismissed()) return - this.dismissed = true - this.emitter.emit('did-dismiss', this) + dismiss() { + if (!this.isDismissable() || this.isDismissed()) return; + this.dismissed = true; + this.emitter.emit('did-dismiss', this); } - isDismissed () { - return this.dismissed + isDismissed() { + return this.dismissed; } - isDismissable () { - return !!this.options.dismissable + isDismissable() { + return !!this.options.dismissable; } - wasDisplayed () { - return this.displayed + wasDisplayed() { + return this.displayed; } - setDisplayed (displayed) { - this.displayed = displayed - this.emitter.emit('did-display', this) + setDisplayed(displayed) { + this.displayed = displayed; + this.emitter.emit('did-display', this); } - getIcon () { - if (this.options.icon != null) return this.options.icon + getIcon() { + if (this.options.icon != null) return this.options.icon; switch (this.type) { - case 'fatal': return 'bug' - case 'error': return 'flame' - case 'warning': return 'alert' - case 'info': return 'info' - case 'success': return 'check' + case 'fatal': + return 'bug'; + case 'error': + return 'flame'; + case 'warning': + return 'alert'; + case 'info': + return 'info'; + case 'success': + return 'check'; } } -} +}; diff --git a/src/null-grammar.js b/src/null-grammar.js index 12cfbbe53..81a7cd68e 100644 --- a/src/null-grammar.js +++ b/src/null-grammar.js @@ -1,38 +1,42 @@ -const {Disposable} = require('event-kit') +const { Disposable } = require('event-kit'); module.exports = { name: 'Null Grammar', scopeName: 'text.plain.null-grammar', - scopeForId (id) { + scopeForId(id) { if (id === -1 || id === -2) { - return this.scopeName + return this.scopeName; } else { - return null + return null; } }, - startIdForScope (scopeName) { + startIdForScope(scopeName) { if (scopeName === this.scopeName) { - return -1 + return -1; } else { - return null + return null; } }, - endIdForScope (scopeName) { + endIdForScope(scopeName) { if (scopeName === this.scopeName) { - return -2 + return -2; } else { - return null + return null; } }, - tokenizeLine (text) { + tokenizeLine(text) { return { - tags: [this.startIdForScope(this.scopeName), text.length, this.endIdForScope(this.scopeName)], + tags: [ + this.startIdForScope(this.scopeName), + text.length, + this.endIdForScope(this.scopeName) + ], ruleStack: null - } + }; }, - onDidUpdate (callback) { - return new Disposable(noop) + onDidUpdate(callback) { + return new Disposable(noop); } -} +}; -function noop () {} +function noop() {} diff --git a/src/package-manager.js b/src/package-manager.js index a9cb0e5b6..eabfbdf7b 100644 --- a/src/package-manager.js +++ b/src/package-manager.js @@ -1,16 +1,16 @@ -const path = require('path') -let normalizePackageData = null +const path = require('path'); +let normalizePackageData = null; -const _ = require('underscore-plus') -const {Emitter} = require('event-kit') -const fs = require('fs-plus') -const CSON = require('season') +const _ = require('underscore-plus'); +const { Emitter } = require('event-kit'); +const fs = require('fs-plus'); +const CSON = require('season'); -const ServiceHub = require('service-hub') -const Package = require('./package') -const ThemePackage = require('./theme-package') -const ModuleCache = require('./module-cache') -const packageJSON = require('../package.json') +const ServiceHub = require('service-hub'); +const Package = require('./package'); +const ThemePackage = require('./theme-package'); +const ModuleCache = require('./module-cache'); +const packageJSON = require('../package.json'); // Extended: Package manager for coordinating the lifecycle of Atom packages. // @@ -28,69 +28,85 @@ const packageJSON = require('../package.json') // Packages can be enabled/disabled via the `core.disabledPackages` config // settings and also by calling `enablePackage()/disablePackage()`. module.exports = class PackageManager { - constructor (params) { + constructor(params) { ({ - config: this.config, styleManager: this.styleManager, notificationManager: this.notificationManager, keymapManager: this.keymapManager, - commandRegistry: this.commandRegistry, grammarRegistry: this.grammarRegistry, deserializerManager: this.deserializerManager, viewRegistry: this.viewRegistry, + config: this.config, + styleManager: this.styleManager, + notificationManager: this.notificationManager, + keymapManager: this.keymapManager, + commandRegistry: this.commandRegistry, + grammarRegistry: this.grammarRegistry, + deserializerManager: this.deserializerManager, + viewRegistry: this.viewRegistry, uriHandlerRegistry: this.uriHandlerRegistry - } = params) + } = params); - this.emitter = new Emitter() - this.activationHookEmitter = new Emitter() - this.packageDirPaths = [] - this.deferredActivationHooks = [] - this.triggeredActivationHooks = new Set() - this.packagesCache = packageJSON._atomPackages != null ? packageJSON._atomPackages : {} - this.packageDependencies = packageJSON.packageDependencies != null ? packageJSON.packageDependencies : {} - this.deprecatedPackages = packageJSON._deprecatedPackages || {} - this.deprecatedPackageRanges = {} - this.initialPackagesLoaded = false - this.initialPackagesActivated = false - this.preloadedPackages = {} - this.loadedPackages = {} - this.activePackages = {} - this.activatingPackages = {} - this.packageStates = {} - this.serviceHub = new ServiceHub() + this.emitter = new Emitter(); + this.activationHookEmitter = new Emitter(); + this.packageDirPaths = []; + this.deferredActivationHooks = []; + this.triggeredActivationHooks = new Set(); + this.packagesCache = + packageJSON._atomPackages != null ? packageJSON._atomPackages : {}; + this.packageDependencies = + packageJSON.packageDependencies != null + ? packageJSON.packageDependencies + : {}; + this.deprecatedPackages = packageJSON._deprecatedPackages || {}; + this.deprecatedPackageRanges = {}; + this.initialPackagesLoaded = false; + this.initialPackagesActivated = false; + this.preloadedPackages = {}; + this.loadedPackages = {}; + this.activePackages = {}; + this.activatingPackages = {}; + this.packageStates = {}; + this.serviceHub = new ServiceHub(); - this.packageActivators = [] - this.registerPackageActivator(this, ['atom', 'textmate']) + this.packageActivators = []; + this.registerPackageActivator(this, ['atom', 'textmate']); } - initialize (params) { - this.devMode = params.devMode - this.resourcePath = params.resourcePath + initialize(params) { + this.devMode = params.devMode; + this.resourcePath = params.resourcePath; if (params.configDirPath != null && !params.safeMode) { if (this.devMode) { - this.packageDirPaths.push(path.join(params.configDirPath, 'dev', 'packages')) - this.packageDirPaths.push(path.join(this.resourcePath, 'packages')) + this.packageDirPaths.push( + path.join(params.configDirPath, 'dev', 'packages') + ); + this.packageDirPaths.push(path.join(this.resourcePath, 'packages')); } - this.packageDirPaths.push(path.join(params.configDirPath, 'packages')) + this.packageDirPaths.push(path.join(params.configDirPath, 'packages')); } } - setContextMenuManager (contextMenuManager) { - this.contextMenuManager = contextMenuManager + setContextMenuManager(contextMenuManager) { + this.contextMenuManager = contextMenuManager; } - setMenuManager (menuManager) { - this.menuManager = menuManager + setMenuManager(menuManager) { + this.menuManager = menuManager; } - setThemeManager (themeManager) { - this.themeManager = themeManager + setThemeManager(themeManager) { + this.themeManager = themeManager; } - async reset () { - this.serviceHub.clear() - await this.deactivatePackages() - this.loadedPackages = {} - this.preloadedPackages = {} - this.packageStates = {} - this.packagesCache = packageJSON._atomPackages != null ? packageJSON._atomPackages : {} - this.packageDependencies = packageJSON.packageDependencies != null ? packageJSON.packageDependencies : {} - this.triggeredActivationHooks.clear() - this.activatePromise = null + async reset() { + this.serviceHub.clear(); + await this.deactivatePackages(); + this.loadedPackages = {}; + this.preloadedPackages = {}; + this.packageStates = {}; + this.packagesCache = + packageJSON._atomPackages != null ? packageJSON._atomPackages : {}; + this.packageDependencies = + packageJSON.packageDependencies != null + ? packageJSON.packageDependencies + : {}; + this.triggeredActivationHooks.clear(); + this.activatePromise = null; } /* @@ -102,8 +118,8 @@ module.exports = class PackageManager { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidLoadInitialPackages (callback) { - return this.emitter.on('did-load-initial-packages', callback) + onDidLoadInitialPackages(callback) { + return this.emitter.on('did-load-initial-packages', callback); } // Public: Invoke the given callback when all packages have been activated. @@ -111,15 +127,15 @@ module.exports = class PackageManager { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidActivateInitialPackages (callback) { - return this.emitter.on('did-activate-initial-packages', callback) + onDidActivateInitialPackages(callback) { + return this.emitter.on('did-activate-initial-packages', callback); } getActivatePromise() { if (this.activatePromise) { - return this.activatePromise + return this.activatePromise; } else { - return Promise.resolve() + return Promise.resolve(); } } @@ -129,8 +145,8 @@ module.exports = class PackageManager { // * `package` The {Package} that was activated. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidActivatePackage (callback) { - return this.emitter.on('did-activate-package', callback) + onDidActivatePackage(callback) { + return this.emitter.on('did-activate-package', callback); } // Public: Invoke the given callback when a package is deactivated. @@ -139,8 +155,8 @@ module.exports = class PackageManager { // * `package` The {Package} that was deactivated. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDeactivatePackage (callback) { - return this.emitter.on('did-deactivate-package', callback) + onDidDeactivatePackage(callback) { + return this.emitter.on('did-deactivate-package', callback); } // Public: Invoke the given callback when a package is loaded. @@ -149,8 +165,8 @@ module.exports = class PackageManager { // * `package` The {Package} that was loaded. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidLoadPackage (callback) { - return this.emitter.on('did-load-package', callback) + onDidLoadPackage(callback) { + return this.emitter.on('did-load-package', callback); } // Public: Invoke the given callback when a package is unloaded. @@ -159,8 +175,8 @@ module.exports = class PackageManager { // * `package` The {Package} that was unloaded. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidUnloadPackage (callback) { - return this.emitter.on('did-unload-package', callback) + onDidUnloadPackage(callback) { + return this.emitter.on('did-unload-package', callback); } /* @@ -172,26 +188,32 @@ module.exports = class PackageManager { // Uses the value of the `core.apmPath` config setting if it exists. // // Return a {String} file path to apm. - getApmPath () { - const configPath = atom.config.get('core.apmPath') + getApmPath() { + const configPath = atom.config.get('core.apmPath'); if (configPath || this.apmPath) { - return configPath || this.apmPath + return configPath || this.apmPath; } - const commandName = process.platform === 'win32' ? 'apm.cmd' : 'apm' - const apmRoot = path.join(process.resourcesPath, 'app', 'apm') - this.apmPath = path.join(apmRoot, 'bin', commandName) + const commandName = process.platform === 'win32' ? 'apm.cmd' : 'apm'; + const apmRoot = path.join(process.resourcesPath, 'app', 'apm'); + this.apmPath = path.join(apmRoot, 'bin', commandName); if (!fs.isFileSync(this.apmPath)) { - this.apmPath = path.join(apmRoot, 'node_modules', 'atom-package-manager', 'bin', commandName) + this.apmPath = path.join( + apmRoot, + 'node_modules', + 'atom-package-manager', + 'bin', + commandName + ); } - return this.apmPath + return this.apmPath; } // Public: Get the paths being used to look for packages. // // Returns an {Array} of {String} directory paths. - getPackageDirPaths () { - return _.clone(this.packageDirPaths) + getPackageDirPaths() { + return _.clone(this.packageDirPaths); } /* @@ -203,22 +225,22 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Return a {String} folder path or undefined if it could not be resolved. - resolvePackagePath (name) { + resolvePackagePath(name) { if (fs.isDirectorySync(name)) { - return name + return name; } - let packagePath = fs.resolve(...this.packageDirPaths, name) + let packagePath = fs.resolve(...this.packageDirPaths, name); if (fs.isDirectorySync(packagePath)) { - return packagePath + return packagePath; } - packagePath = path.join(this.resourcePath, 'node_modules', name) + packagePath = path.join(this.resourcePath, 'node_modules', name); if (this.hasAtomEngine(packagePath)) { - return packagePath + return packagePath; } - return null + return null; } // Public: Is the package with the given name bundled with Atom? @@ -226,31 +248,31 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns a {Boolean}. - isBundledPackage (name) { - return this.getPackageDependencies().hasOwnProperty(name) + isBundledPackage(name) { + return this.getPackageDependencies().hasOwnProperty(name); } - isDeprecatedPackage (name, version) { - const metadata = this.deprecatedPackages[name] - if (!metadata) return false - if (!metadata.version) return true + isDeprecatedPackage(name, version) { + const metadata = this.deprecatedPackages[name]; + if (!metadata) return false; + if (!metadata.version) return true; - let range = this.deprecatedPackageRanges[metadata.version] + let range = this.deprecatedPackageRanges[metadata.version]; if (!range) { try { - range = new ModuleCache.Range(metadata.version) + range = new ModuleCache.Range(metadata.version); } catch (error) { - range = NullVersionRange + range = NullVersionRange; } - this.deprecatedPackageRanges[metadata.version] = range + this.deprecatedPackageRanges[metadata.version] = range; } - return range.test(version) + return range.test(version); } - getDeprecatedPackageMetadata (name) { - const metadata = this.deprecatedPackages[name] - if (metadata) Object.freeze(metadata) - return metadata + getDeprecatedPackageMetadata(name) { + const metadata = this.deprecatedPackages[name]; + if (metadata) Object.freeze(metadata); + return metadata; } /* @@ -262,12 +284,12 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns the {Package} that was enabled or null if it isn't loaded. - enablePackage (name) { - const pack = this.loadPackage(name) + enablePackage(name) { + const pack = this.loadPackage(name); if (pack != null) { - pack.enable() + pack.enable(); } - return pack + return pack; } // Public: Disable the package with the given name. @@ -275,12 +297,12 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns the {Package} that was disabled or null if it isn't loaded. - disablePackage (name) { - const pack = this.loadPackage(name) + disablePackage(name) { + const pack = this.loadPackage(name); if (!this.isPackageDisabled(name) && pack != null) { - pack.disable() + pack.disable(); } - return pack + return pack; } // Public: Is the package with the given name disabled? @@ -288,8 +310,8 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns a {Boolean}. - isPackageDisabled (name) { - return _.include(this.config.get('core.disabledPackages') || [], name) + isPackageDisabled(name) { + return _.include(this.config.get('core.disabledPackages') || [], name); } /* @@ -297,8 +319,8 @@ module.exports = class PackageManager { */ // Public: Get an {Array} of all the active {Package}s. - getActivePackages () { - return _.values(this.activePackages) + getActivePackages() { + return _.values(this.activePackages); } // Public: Get the active {Package} with the given name. @@ -306,8 +328,8 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns a {Package} or undefined. - getActivePackage (name) { - return this.activePackages[name] + getActivePackage(name) { + return this.activePackages[name]; } // Public: Is the {Package} with the given name active? @@ -315,13 +337,13 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns a {Boolean}. - isPackageActive (name) { - return (this.getActivePackage(name) != null) + isPackageActive(name) { + return this.getActivePackage(name) != null; } // Public: Returns a {Boolean} indicating whether package activation has occurred. - hasActivatedInitialPackages () { - return this.initialPackagesActivated + hasActivatedInitialPackages() { + return this.initialPackagesActivated; } /* @@ -329,15 +351,15 @@ module.exports = class PackageManager { */ // Public: Get an {Array} of all the loaded {Package}s - getLoadedPackages () { - return _.values(this.loadedPackages) + getLoadedPackages() { + return _.values(this.loadedPackages); } // Get packages for a certain package type // // * `types` an {Array} of {String}s like ['atom', 'textmate']. - getLoadedPackagesForTypes (types) { - return this.getLoadedPackages().filter(p => types.includes(p.getType())) + getLoadedPackagesForTypes(types) { + return this.getLoadedPackages().filter(p => types.includes(p.getType())); } // Public: Get the loaded {Package} with the given name. @@ -345,8 +367,8 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns a {Package} or undefined. - getLoadedPackage (name) { - return this.loadedPackages[name] + getLoadedPackage(name) { + return this.loadedPackages[name]; } // Public: Is the package with the given name loaded? @@ -354,13 +376,13 @@ module.exports = class PackageManager { // * `name` - The {String} package name. // // Returns a {Boolean}. - isPackageLoaded (name) { - return this.getLoadedPackage(name) != null + isPackageLoaded(name) { + return this.getLoadedPackage(name) != null; } // Public: Returns a {Boolean} indicating whether package loading has occurred. - hasLoadedInitialPackages () { - return this.initialPackagesLoaded + hasLoadedInitialPackages() { + return this.initialPackagesLoaded; } /* @@ -368,42 +390,49 @@ module.exports = class PackageManager { */ // Public: Returns an {Array} of {String}s of all the available package paths. - getAvailablePackagePaths () { - return this.getAvailablePackages().map(a => a.path) + getAvailablePackagePaths() { + return this.getAvailablePackages().map(a => a.path); } // Public: Returns an {Array} of {String}s of all the available package names. - getAvailablePackageNames () { - return this.getAvailablePackages().map(a => a.name) + getAvailablePackageNames() { + return this.getAvailablePackages().map(a => a.name); } // Public: Returns an {Array} of {String}s of all the available package metadata. - getAvailablePackageMetadata () { - const packages = [] + getAvailablePackageMetadata() { + const packages = []; for (const pack of this.getAvailablePackages()) { - const loadedPackage = this.getLoadedPackage(pack.name) - const metadata = loadedPackage != null ? loadedPackage.metadata : this.loadPackageMetadata(pack, true) - packages.push(metadata) + const loadedPackage = this.getLoadedPackage(pack.name); + const metadata = + loadedPackage != null + ? loadedPackage.metadata + : this.loadPackageMetadata(pack, true); + packages.push(metadata); } - return packages + return packages; } - getAvailablePackages () { - const packages = [] - const packagesByName = new Set() + getAvailablePackages() { + const packages = []; + const packagesByName = new Set(); for (const packageDirPath of this.packageDirPaths) { if (fs.isDirectorySync(packageDirPath)) { for (let packagePath of fs.readdirSync(packageDirPath)) { - packagePath = path.join(packageDirPath, packagePath) - const packageName = path.basename(packagePath) - if (!packageName.startsWith('.') && !packagesByName.has(packageName) && fs.isDirectorySync(packagePath)) { + packagePath = path.join(packageDirPath, packagePath); + const packageName = path.basename(packagePath); + if ( + !packageName.startsWith('.') && + !packagesByName.has(packageName) && + fs.isDirectorySync(packagePath) + ) { packages.push({ name: packageName, path: packagePath, isBundled: false - }) - packagesByName.add(packageName) + }); + packagesByName.add(packageName); } } } @@ -415,105 +444,140 @@ module.exports = class PackageManager { name: packageName, path: path.join(this.resourcePath, 'node_modules', packageName), isBundled: true - }) + }); } } - return packages.sort((a, b) => a.name.localeCompare(b.name)) + return packages.sort((a, b) => a.name.localeCompare(b.name)); } /* Section: Private */ - getPackageState (name) { - return this.packageStates[name] + getPackageState(name) { + return this.packageStates[name]; } - setPackageState (name, state) { - this.packageStates[name] = state + setPackageState(name, state) { + this.packageStates[name] = state; } - getPackageDependencies () { - return this.packageDependencies + getPackageDependencies() { + return this.packageDependencies; } - hasAtomEngine (packagePath) { - const metadata = this.loadPackageMetadata(packagePath, true) - return metadata != null && metadata.engines != null && metadata.engines.atom != null + hasAtomEngine(packagePath) { + const metadata = this.loadPackageMetadata(packagePath, true); + return ( + metadata != null && + metadata.engines != null && + metadata.engines.atom != null + ); } - unobserveDisabledPackages () { + unobserveDisabledPackages() { if (this.disabledPackagesSubscription != null) { - this.disabledPackagesSubscription.dispose() + this.disabledPackagesSubscription.dispose(); } - this.disabledPackagesSubscription = null + this.disabledPackagesSubscription = null; } - observeDisabledPackages () { + observeDisabledPackages() { if (this.disabledPackagesSubscription != null) { - return + return; } - this.disabledPackagesSubscription = this.config.onDidChange('core.disabledPackages', ({newValue, oldValue}) => { - const packagesToEnable = _.difference(oldValue, newValue) - const packagesToDisable = _.difference(newValue, oldValue) - packagesToDisable.forEach(name => { if (this.getActivePackage(name)) this.deactivatePackage(name) }) - packagesToEnable.forEach(name => this.activatePackage(name)) - return null - }) + this.disabledPackagesSubscription = this.config.onDidChange( + 'core.disabledPackages', + ({ newValue, oldValue }) => { + const packagesToEnable = _.difference(oldValue, newValue); + const packagesToDisable = _.difference(newValue, oldValue); + packagesToDisable.forEach(name => { + if (this.getActivePackage(name)) this.deactivatePackage(name); + }); + packagesToEnable.forEach(name => this.activatePackage(name)); + return null; + } + ); } - unobservePackagesWithKeymapsDisabled () { + unobservePackagesWithKeymapsDisabled() { if (this.packagesWithKeymapsDisabledSubscription != null) { - this.packagesWithKeymapsDisabledSubscription.dispose() + this.packagesWithKeymapsDisabledSubscription.dispose(); } - this.packagesWithKeymapsDisabledSubscription = null + this.packagesWithKeymapsDisabledSubscription = null; } - observePackagesWithKeymapsDisabled () { + observePackagesWithKeymapsDisabled() { if (this.packagesWithKeymapsDisabledSubscription != null) { - return + return; } - const performOnLoadedActivePackages = (packageNames, disabledPackageNames, action) => { + const performOnLoadedActivePackages = ( + packageNames, + disabledPackageNames, + action + ) => { for (const packageName of packageNames) { if (!disabledPackageNames.has(packageName)) { - var pack = this.getLoadedPackage(packageName) + var pack = this.getLoadedPackage(packageName); if (pack != null) { - action(pack) + action(pack); } } } - } + }; - this.packagesWithKeymapsDisabledSubscription = this.config.onDidChange('core.packagesWithKeymapsDisabled', ({newValue, oldValue}) => { - const keymapsToEnable = _.difference(oldValue, newValue) - const keymapsToDisable = _.difference(newValue, oldValue) + this.packagesWithKeymapsDisabledSubscription = this.config.onDidChange( + 'core.packagesWithKeymapsDisabled', + ({ newValue, oldValue }) => { + const keymapsToEnable = _.difference(oldValue, newValue); + const keymapsToDisable = _.difference(newValue, oldValue); - const disabledPackageNames = new Set(this.config.get('core.disabledPackages')) - performOnLoadedActivePackages(keymapsToDisable, disabledPackageNames, p => p.deactivateKeymaps()) - performOnLoadedActivePackages(keymapsToEnable, disabledPackageNames, p => p.activateKeymaps()) - return null - }) + const disabledPackageNames = new Set( + this.config.get('core.disabledPackages') + ); + performOnLoadedActivePackages( + keymapsToDisable, + disabledPackageNames, + p => p.deactivateKeymaps() + ); + performOnLoadedActivePackages( + keymapsToEnable, + disabledPackageNames, + p => p.activateKeymaps() + ); + return null; + } + ); } - preloadPackages () { - const result = [] + preloadPackages() { + const result = []; for (const packageName in this.packagesCache) { - result.push(this.preloadPackage(packageName, this.packagesCache[packageName])) + result.push( + this.preloadPackage(packageName, this.packagesCache[packageName]) + ); } - return result + return result; } - preloadPackage (packageName, pack) { - const metadata = pack.metadata || {} + preloadPackage(packageName, pack) { + const metadata = pack.metadata || {}; if (typeof metadata.name !== 'string' || metadata.name.length < 1) { - metadata.name = packageName + metadata.name = packageName; } - if (metadata.repository != null && metadata.repository.type === 'git' && typeof metadata.repository.url === 'string') { - metadata.repository.url = metadata.repository.url.replace(/(^git\+)|(\.git$)/g, '') + if ( + metadata.repository != null && + metadata.repository.type === 'git' && + typeof metadata.repository.url === 'string' + ) { + metadata.repository.url = metadata.repository.url.replace( + /(^git\+)|(\.git$)/g, + '' + ); } const options = { @@ -534,87 +598,104 @@ module.exports = class PackageManager { contextMenuManager: this.contextMenuManager, deserializerManager: this.deserializerManager, viewRegistry: this.viewRegistry - } + }; - pack = metadata.theme ? new ThemePackage(options) : new Package(options) - pack.preload() - this.preloadedPackages[packageName] = pack - return pack + pack = metadata.theme ? new ThemePackage(options) : new Package(options); + pack.preload(); + this.preloadedPackages[packageName] = pack; + return pack; } - loadPackages () { + loadPackages() { // Ensure atom exports is already in the require cache so the load time // of the first package isn't skewed by being the first to require atom - require('../exports/atom') + require('../exports/atom'); - const disabledPackageNames = new Set(this.config.get('core.disabledPackages')) + const disabledPackageNames = new Set( + this.config.get('core.disabledPackages') + ); this.config.transact(() => { for (const pack of this.getAvailablePackages()) { - this.loadAvailablePackage(pack, disabledPackageNames) + this.loadAvailablePackage(pack, disabledPackageNames); } - }) - this.initialPackagesLoaded = true - this.emitter.emit('did-load-initial-packages') + }); + this.initialPackagesLoaded = true; + this.emitter.emit('did-load-initial-packages'); } - loadPackage (nameOrPath) { - if (path.basename(nameOrPath)[0].match(/^\./)) { // primarily to skip .git folder - return null + loadPackage(nameOrPath) { + if (path.basename(nameOrPath)[0].match(/^\./)) { + // primarily to skip .git folder + return null; } - const pack = this.getLoadedPackage(nameOrPath) + const pack = this.getLoadedPackage(nameOrPath); if (pack) { - return pack + return pack; } - const packagePath = this.resolvePackagePath(nameOrPath) + const packagePath = this.resolvePackagePath(nameOrPath); if (packagePath) { - const name = path.basename(nameOrPath) - return this.loadAvailablePackage({name, path: packagePath, isBundled: this.isBundledPackagePath(packagePath)}) + const name = path.basename(nameOrPath); + return this.loadAvailablePackage({ + name, + path: packagePath, + isBundled: this.isBundledPackagePath(packagePath) + }); } - console.warn(`Could not resolve '${nameOrPath}' to a package path`) - return null + console.warn(`Could not resolve '${nameOrPath}' to a package path`); + return null; } - loadAvailablePackage (availablePackage, disabledPackageNames) { - const preloadedPackage = this.preloadedPackages[availablePackage.name] + loadAvailablePackage(availablePackage, disabledPackageNames) { + const preloadedPackage = this.preloadedPackages[availablePackage.name]; - if (disabledPackageNames != null && disabledPackageNames.has(availablePackage.name)) { + if ( + disabledPackageNames != null && + disabledPackageNames.has(availablePackage.name) + ) { if (preloadedPackage != null) { - preloadedPackage.deactivate() - delete preloadedPackage[availablePackage.name] + preloadedPackage.deactivate(); + delete preloadedPackage[availablePackage.name]; } - return null + return null; } - const loadedPackage = this.getLoadedPackage(availablePackage.name) + const loadedPackage = this.getLoadedPackage(availablePackage.name); if (loadedPackage != null) { - return loadedPackage + return loadedPackage; } if (preloadedPackage != null) { if (availablePackage.isBundled) { - preloadedPackage.finishLoading() - this.loadedPackages[availablePackage.name] = preloadedPackage - return preloadedPackage + preloadedPackage.finishLoading(); + this.loadedPackages[availablePackage.name] = preloadedPackage; + return preloadedPackage; } else { - preloadedPackage.deactivate() - delete preloadedPackage[availablePackage.name] + preloadedPackage.deactivate(); + delete preloadedPackage[availablePackage.name]; } } - let metadata + let metadata; try { - metadata = this.loadPackageMetadata(availablePackage) || {} + metadata = this.loadPackageMetadata(availablePackage) || {}; } catch (error) { - this.handleMetadataError(error, availablePackage.path) - return null + this.handleMetadataError(error, availablePackage.path); + return null; } - if (!availablePackage.isBundled && this.isDeprecatedPackage(metadata.name, metadata.version)) { - console.warn(`Could not load ${metadata.name}@${metadata.version} because it uses deprecated APIs that have been removed.`) - return null + if ( + !availablePackage.isBundled && + this.isDeprecatedPackage(metadata.name, metadata.version) + ) { + console.warn( + `Could not load ${metadata.name}@${ + metadata.version + } because it uses deprecated APIs that have been removed.` + ); + return null; } const options = { @@ -634,272 +715,310 @@ module.exports = class PackageManager { contextMenuManager: this.contextMenuManager, deserializerManager: this.deserializerManager, viewRegistry: this.viewRegistry - } + }; - const pack = metadata.theme ? new ThemePackage(options) : new Package(options) - pack.load() - this.loadedPackages[pack.name] = pack - this.emitter.emit('did-load-package', pack) - return pack + const pack = metadata.theme + ? new ThemePackage(options) + : new Package(options); + pack.load(); + this.loadedPackages[pack.name] = pack; + this.emitter.emit('did-load-package', pack); + return pack; } - unloadPackages () { - _.keys(this.loadedPackages).forEach(name => this.unloadPackage(name)) + unloadPackages() { + _.keys(this.loadedPackages).forEach(name => this.unloadPackage(name)); } - unloadPackage (name) { + unloadPackage(name) { if (this.isPackageActive(name)) { - throw new Error(`Tried to unload active package '${name}'`) + throw new Error(`Tried to unload active package '${name}'`); } - const pack = this.getLoadedPackage(name) + const pack = this.getLoadedPackage(name); if (pack) { - delete this.loadedPackages[pack.name] - this.emitter.emit('did-unload-package', pack) + delete this.loadedPackages[pack.name]; + this.emitter.emit('did-unload-package', pack); } else { - throw new Error(`No loaded package for name '${name}'`) + throw new Error(`No loaded package for name '${name}'`); } } // Activate all the packages that should be activated. - activate () { - let promises = [] + activate() { + let promises = []; for (let [activator, types] of this.packageActivators) { - const packages = this.getLoadedPackagesForTypes(types) - promises = promises.concat(activator.activatePackages(packages)) + const packages = this.getLoadedPackagesForTypes(types); + promises = promises.concat(activator.activatePackages(packages)); } this.activatePromise = Promise.all(promises).then(() => { - this.triggerDeferredActivationHooks() - this.initialPackagesActivated = true - this.emitter.emit('did-activate-initial-packages') - this.activatePromise = null - }) - return this.activatePromise + this.triggerDeferredActivationHooks(); + this.initialPackagesActivated = true; + this.emitter.emit('did-activate-initial-packages'); + this.activatePromise = null; + }); + return this.activatePromise; } - registerURIHandlerForPackage (packageName, handler) { - return this.uriHandlerRegistry.registerHostHandler(packageName, handler) + registerURIHandlerForPackage(packageName, handler) { + return this.uriHandlerRegistry.registerHostHandler(packageName, handler); } // another type of package manager can handle other package types. // See ThemeManager - registerPackageActivator (activator, types) { - this.packageActivators.push([activator, types]) + registerPackageActivator(activator, types) { + this.packageActivators.push([activator, types]); } - activatePackages (packages) { - const promises = [] + activatePackages(packages) { + const promises = []; this.config.transactAsync(() => { for (const pack of packages) { - const promise = this.activatePackage(pack.name) + const promise = this.activatePackage(pack.name); if (!pack.activationShouldBeDeferred()) { - promises.push(promise) + promises.push(promise); } } - return Promise.all(promises) - }) - this.observeDisabledPackages() - this.observePackagesWithKeymapsDisabled() - return promises + return Promise.all(promises); + }); + this.observeDisabledPackages(); + this.observePackagesWithKeymapsDisabled(); + return promises; } // Activate a single package by name - activatePackage (name) { - let pack = this.getActivePackage(name) + activatePackage(name) { + let pack = this.getActivePackage(name); if (pack) { - return Promise.resolve(pack) + return Promise.resolve(pack); } - pack = this.loadPackage(name) + pack = this.loadPackage(name); if (!pack) { - return Promise.reject(new Error(`Failed to load package '${name}'`)) + return Promise.reject(new Error(`Failed to load package '${name}'`)); } - this.activatingPackages[pack.name] = pack + this.activatingPackages[pack.name] = pack; const activationPromise = pack.activate().then(() => { if (this.activatingPackages[pack.name] != null) { - delete this.activatingPackages[pack.name] - this.activePackages[pack.name] = pack - this.emitter.emit('did-activate-package', pack) + delete this.activatingPackages[pack.name]; + this.activePackages[pack.name] = pack; + this.emitter.emit('did-activate-package', pack); } - return pack - }) + return pack; + }); if (this.deferredActivationHooks == null) { - this.triggeredActivationHooks.forEach(hook => this.activationHookEmitter.emit(hook)) + this.triggeredActivationHooks.forEach(hook => + this.activationHookEmitter.emit(hook) + ); } - return activationPromise + return activationPromise; } - triggerDeferredActivationHooks () { + triggerDeferredActivationHooks() { if (this.deferredActivationHooks == null) { - return + return; } for (const hook of this.deferredActivationHooks) { - this.activationHookEmitter.emit(hook) + this.activationHookEmitter.emit(hook); } - this.deferredActivationHooks = null + this.deferredActivationHooks = null; } - triggerActivationHook (hook) { + triggerActivationHook(hook) { if (hook == null || !_.isString(hook) || hook.length <= 0) { - return new Error('Cannot trigger an empty activation hook') + return new Error('Cannot trigger an empty activation hook'); } - this.triggeredActivationHooks.add(hook) + this.triggeredActivationHooks.add(hook); if (this.deferredActivationHooks != null) { - this.deferredActivationHooks.push(hook) + this.deferredActivationHooks.push(hook); } else { - this.activationHookEmitter.emit(hook) + this.activationHookEmitter.emit(hook); } } - onDidTriggerActivationHook (hook, callback) { + onDidTriggerActivationHook(hook, callback) { if (hook == null || !_.isString(hook) || hook.length <= 0) { - return + return; } - return this.activationHookEmitter.on(hook, callback) + return this.activationHookEmitter.on(hook, callback); } - serialize () { + serialize() { for (const pack of this.getActivePackages()) { - this.serializePackage(pack) + this.serializePackage(pack); } - return this.packageStates + return this.packageStates; } - serializePackage (pack) { + serializePackage(pack) { if (typeof pack.serialize === 'function') { - this.setPackageState(pack.name, pack.serialize()) + this.setPackageState(pack.name, pack.serialize()); } } // Deactivate all packages - async deactivatePackages () { + async deactivatePackages() { await this.config.transactAsync(() => - Promise.all(this.getLoadedPackages().map(pack => this.deactivatePackage(pack.name, true))) - ) - this.unobserveDisabledPackages() - this.unobservePackagesWithKeymapsDisabled() + Promise.all( + this.getLoadedPackages().map(pack => + this.deactivatePackage(pack.name, true) + ) + ) + ); + this.unobserveDisabledPackages(); + this.unobservePackagesWithKeymapsDisabled(); } // Deactivate the package with the given name - async deactivatePackage (name, suppressSerialization) { - const pack = this.getLoadedPackage(name) + async deactivatePackage(name, suppressSerialization) { + const pack = this.getLoadedPackage(name); if (pack == null) { - return + return; } if (!suppressSerialization && this.isPackageActive(pack.name)) { - this.serializePackage(pack) + this.serializePackage(pack); } - const deactivationResult = pack.deactivate() + const deactivationResult = pack.deactivate(); if (deactivationResult && typeof deactivationResult.then === 'function') { - await deactivationResult + await deactivationResult; } - delete this.activePackages[pack.name] - delete this.activatingPackages[pack.name] - this.emitter.emit('did-deactivate-package', pack) + delete this.activePackages[pack.name]; + delete this.activatingPackages[pack.name]; + this.emitter.emit('did-deactivate-package', pack); } - handleMetadataError (error, packagePath) { - const metadataPath = path.join(packagePath, 'package.json') - const detail = `${error.message} in ${metadataPath}` - const stack = `${error.stack}\n at ${metadataPath}:1:1` - const message = `Failed to load the ${path.basename(packagePath)} package` - this.notificationManager.addError(message, {stack, detail, packageName: path.basename(packagePath), dismissable: true}) + handleMetadataError(error, packagePath) { + const metadataPath = path.join(packagePath, 'package.json'); + const detail = `${error.message} in ${metadataPath}`; + const stack = `${error.stack}\n at ${metadataPath}:1:1`; + const message = `Failed to load the ${path.basename(packagePath)} package`; + this.notificationManager.addError(message, { + stack, + detail, + packageName: path.basename(packagePath), + dismissable: true + }); } - uninstallDirectory (directory) { - const symlinkPromise = new Promise(resolve => fs.isSymbolicLink(directory, isSymLink => resolve(isSymLink))) - const dirPromise = new Promise(resolve => fs.isDirectory(directory, isDir => resolve(isDir))) + uninstallDirectory(directory) { + const symlinkPromise = new Promise(resolve => + fs.isSymbolicLink(directory, isSymLink => resolve(isSymLink)) + ); + const dirPromise = new Promise(resolve => + fs.isDirectory(directory, isDir => resolve(isDir)) + ); return Promise.all([symlinkPromise, dirPromise]).then(values => { - const [isSymLink, isDir] = values + const [isSymLink, isDir] = values; if (!isSymLink && isDir) { - return fs.remove(directory, function () {}) + return fs.remove(directory, function() {}); } - }) + }); } - reloadActivePackageStyleSheets () { + reloadActivePackageStyleSheets() { for (const pack of this.getActivePackages()) { - if (pack.getType() !== 'theme' && typeof pack.reloadStylesheets === 'function') { - pack.reloadStylesheets() + if ( + pack.getType() !== 'theme' && + typeof pack.reloadStylesheets === 'function' + ) { + pack.reloadStylesheets(); } } } - isBundledPackagePath (packagePath) { - if (this.devMode && !this.resourcePath.startsWith(`${process.resourcesPath}${path.sep}`)) { - return false + isBundledPackagePath(packagePath) { + if ( + this.devMode && + !this.resourcePath.startsWith(`${process.resourcesPath}${path.sep}`) + ) { + return false; } if (this.resourcePathWithTrailingSlash == null) { - this.resourcePathWithTrailingSlash = `${this.resourcePath}${path.sep}` + this.resourcePathWithTrailingSlash = `${this.resourcePath}${path.sep}`; } - return packagePath != null && packagePath.startsWith(this.resourcePathWithTrailingSlash) + return ( + packagePath != null && + packagePath.startsWith(this.resourcePathWithTrailingSlash) + ); } - loadPackageMetadata (packagePathOrAvailablePackage, ignoreErrors = false) { - let isBundled, packageName, packagePath + loadPackageMetadata(packagePathOrAvailablePackage, ignoreErrors = false) { + let isBundled, packageName, packagePath; if (typeof packagePathOrAvailablePackage === 'object') { - const availablePackage = packagePathOrAvailablePackage - packageName = availablePackage.name - packagePath = availablePackage.path - isBundled = availablePackage.isBundled + const availablePackage = packagePathOrAvailablePackage; + packageName = availablePackage.name; + packagePath = availablePackage.path; + isBundled = availablePackage.isBundled; } else { - packagePath = packagePathOrAvailablePackage - packageName = path.basename(packagePath) - isBundled = this.isBundledPackagePath(packagePath) + packagePath = packagePathOrAvailablePackage; + packageName = path.basename(packagePath); + isBundled = this.isBundledPackagePath(packagePath); } - let metadata + let metadata; if (isBundled && this.packagesCache[packageName] != null) { - metadata = this.packagesCache[packageName].metadata + metadata = this.packagesCache[packageName].metadata; } if (metadata == null) { - const metadataPath = CSON.resolve(path.join(packagePath, 'package')) + const metadataPath = CSON.resolve(path.join(packagePath, 'package')); if (metadataPath) { try { - metadata = CSON.readFileSync(metadataPath) - this.normalizePackageMetadata(metadata) + metadata = CSON.readFileSync(metadataPath); + this.normalizePackageMetadata(metadata); } catch (error) { - if (!ignoreErrors) { throw error } + if (!ignoreErrors) { + throw error; + } } } } if (metadata == null) { - metadata = {} + metadata = {}; } if (typeof metadata.name !== 'string' || metadata.name.length <= 0) { - metadata.name = packageName + metadata.name = packageName; } - if (metadata.repository && metadata.repository.type === 'git' && typeof metadata.repository.url === 'string') { - metadata.repository.url = metadata.repository.url.replace(/(^git\+)|(\.git$)/g, '') + if ( + metadata.repository && + metadata.repository.type === 'git' && + typeof metadata.repository.url === 'string' + ) { + metadata.repository.url = metadata.repository.url.replace( + /(^git\+)|(\.git$)/g, + '' + ); } - return metadata + return metadata; } - normalizePackageMetadata (metadata) { + normalizePackageMetadata(metadata) { if (metadata != null) { - normalizePackageData = normalizePackageData || require('normalize-package-data') - normalizePackageData(metadata) + normalizePackageData = + normalizePackageData || require('normalize-package-data'); + normalizePackageData(metadata); } } -} +}; const NullVersionRange = { - test () { return false } -} + test() { + return false; + } +}; diff --git a/src/package-transpilation-registry.js b/src/package-transpilation-registry.js index 341e2e5ef..9cc189101 100644 --- a/src/package-transpilation-registry.js +++ b/src/package-transpilation-registry.js @@ -1,178 +1,212 @@ -'use strict' +'use strict'; // This file is required by compile-cache, which is required directly from // apm, so it can only use the subset of newer JavaScript features that apm's // version of Node supports. Strict mode is required for block scoped declarations. -const crypto = require('crypto') -const fs = require('fs') -const path = require('path') +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); -const minimatch = require('minimatch') +const minimatch = require('minimatch'); -let Resolve = null +let Resolve = null; class PackageTranspilationRegistry { - constructor () { - this.configByPackagePath = {} - this.specByFilePath = {} - this.transpilerPaths = {} + constructor() { + this.configByPackagePath = {}; + this.specByFilePath = {}; + this.transpilerPaths = {}; } - addTranspilerConfigForPath (packagePath, packageName, packageMeta, config) { + addTranspilerConfigForPath(packagePath, packageName, packageMeta, config) { this.configByPackagePath[packagePath] = { name: packageName, meta: packageMeta, path: packagePath, specs: config.map(spec => Object.assign({}, spec)) - } + }; } - removeTranspilerConfigForPath (packagePath) { - delete this.configByPackagePath[packagePath] + removeTranspilerConfigForPath(packagePath) { + delete this.configByPackagePath[packagePath]; const packagePathWithSep = packagePath.endsWith(path.sep) - ? path.join(packagePath) : path.join(packagePath) + path.sep + ? path.join(packagePath) + : path.join(packagePath) + path.sep; Object.keys(this.specByFilePath).forEach(filePath => { if (path.join(filePath).startsWith(packagePathWithSep)) { - delete this.specByFilePath[filePath] + delete this.specByFilePath[filePath]; } - }) + }); } // Wraps the transpiler in an object with the same interface // that falls back to the original transpiler implementation if and // only if a package hasn't registered its desire to transpile its own source. - wrapTranspiler (transpiler) { + wrapTranspiler(transpiler) { return { getCachePath: (sourceCode, filePath) => { - const spec = this.getPackageTranspilerSpecForFilePath(filePath) + const spec = this.getPackageTranspilerSpecForFilePath(filePath); if (spec) { - return this.getCachePath(sourceCode, filePath, spec) + return this.getCachePath(sourceCode, filePath, spec); } - return transpiler.getCachePath(sourceCode, filePath) + return transpiler.getCachePath(sourceCode, filePath); }, compile: (sourceCode, filePath) => { - const spec = this.getPackageTranspilerSpecForFilePath(filePath) + const spec = this.getPackageTranspilerSpecForFilePath(filePath); if (spec) { - return this.transpileWithPackageTranspiler(sourceCode, filePath, spec) + return this.transpileWithPackageTranspiler( + sourceCode, + filePath, + spec + ); } - return transpiler.compile(sourceCode, filePath) + return transpiler.compile(sourceCode, filePath); }, shouldCompile: (sourceCode, filePath) => { if (this.transpilerPaths[filePath]) { - return false + return false; } - const spec = this.getPackageTranspilerSpecForFilePath(filePath) + const spec = this.getPackageTranspilerSpecForFilePath(filePath); if (spec) { - return true + return true; } - return transpiler.shouldCompile(sourceCode, filePath) + return transpiler.shouldCompile(sourceCode, filePath); } - } + }; } - getPackageTranspilerSpecForFilePath (filePath) { - if (this.specByFilePath[filePath] !== undefined) return this.specByFilePath[filePath] + getPackageTranspilerSpecForFilePath(filePath) { + if (this.specByFilePath[filePath] !== undefined) + return this.specByFilePath[filePath]; - let thisPath = filePath - let lastPath = null + let thisPath = filePath; + let lastPath = null; // Iterate parents from the file path to the root, checking at each level // to see if a package manages transpilation for that directory. // This means searching for a config for `/path/to/file/here.js` only // only iterates four times, even if there are hundreds of configs registered. - while (thisPath !== lastPath) { // until we reach the root - let config = this.configByPackagePath[thisPath] + while (thisPath !== lastPath) { + // until we reach the root + let config = this.configByPackagePath[thisPath]; if (config) { - const relativePath = path.relative(thisPath, filePath) - if (relativePath.startsWith(`node_modules${path.sep}`) || relativePath.indexOf(`${path.sep}node_modules${path.sep}`) > -1) { - return false + const relativePath = path.relative(thisPath, filePath); + if ( + relativePath.startsWith(`node_modules${path.sep}`) || + relativePath.indexOf(`${path.sep}node_modules${path.sep}`) > -1 + ) { + return false; } for (let i = 0; i < config.specs.length; i++) { - const spec = config.specs[i] + const spec = config.specs[i]; if (minimatch(filePath, path.join(config.path, spec.glob))) { - spec._config = config - this.specByFilePath[filePath] = spec - return spec + spec._config = config; + this.specByFilePath[filePath] = spec; + return spec; } } } - lastPath = thisPath - thisPath = path.join(thisPath, '..') + lastPath = thisPath; + thisPath = path.join(thisPath, '..'); } - this.specByFilePath[filePath] = null - return null + this.specByFilePath[filePath] = null; + return null; } - getCachePath (sourceCode, filePath, spec) { - const transpilerPath = this.getTranspilerPath(spec) - const transpilerSource = spec._transpilerSource || fs.readFileSync(transpilerPath, 'utf8') - spec._transpilerSource = transpilerSource - const transpiler = this.getTranspiler(spec) + getCachePath(sourceCode, filePath, spec) { + const transpilerPath = this.getTranspilerPath(spec); + const transpilerSource = + spec._transpilerSource || fs.readFileSync(transpilerPath, 'utf8'); + spec._transpilerSource = transpilerSource; + const transpiler = this.getTranspiler(spec); let hash = crypto .createHash('sha1') .update(JSON.stringify(spec.options || {})) .update(transpilerSource, 'utf8') - .update(sourceCode, 'utf8') + .update(sourceCode, 'utf8'); if (transpiler && transpiler.getCacheKeyData) { - const meta = this.getMetadata(spec) - const additionalCacheData = transpiler.getCacheKeyData(sourceCode, filePath, spec.options || {}, meta) - hash.update(additionalCacheData, 'utf8') + const meta = this.getMetadata(spec); + const additionalCacheData = transpiler.getCacheKeyData( + sourceCode, + filePath, + spec.options || {}, + meta + ); + hash.update(additionalCacheData, 'utf8'); } - return path.join('package-transpile', spec._config.name, hash.digest('hex')) + return path.join( + 'package-transpile', + spec._config.name, + hash.digest('hex') + ); } - transpileWithPackageTranspiler (sourceCode, filePath, spec) { - const transpiler = this.getTranspiler(spec) + transpileWithPackageTranspiler(sourceCode, filePath, spec) { + const transpiler = this.getTranspiler(spec); if (transpiler) { - const meta = this.getMetadata(spec) - const result = transpiler.transpile(sourceCode, filePath, spec.options || {}, meta) + const meta = this.getMetadata(spec); + const result = transpiler.transpile( + sourceCode, + filePath, + spec.options || {}, + meta + ); if (result === undefined || (result && result.code === undefined)) { - return sourceCode + return sourceCode; } else if (result.code) { - return result.code.toString() + return result.code.toString(); } else { - throw new Error('Could not find a property `.code` on the transpilation results of ' + filePath) + throw new Error( + 'Could not find a property `.code` on the transpilation results of ' + + filePath + ); } } else { - const err = new Error("Could not resolve transpiler '" + spec.transpiler + "' from '" + spec._config.path + "'") - throw err + const err = new Error( + "Could not resolve transpiler '" + + spec.transpiler + + "' from '" + + spec._config.path + + "'" + ); + throw err; } } - getMetadata (spec) { + getMetadata(spec) { return { name: spec._config.name, path: spec._config.path, meta: spec._config.meta - } + }; } - getTranspilerPath (spec) { - Resolve = Resolve || require('resolve') + getTranspilerPath(spec) { + Resolve = Resolve || require('resolve'); return Resolve.sync(spec.transpiler, { basedir: spec._config.path, extensions: Object.keys(require.extensions) - }) + }); } - getTranspiler (spec) { - const transpilerPath = this.getTranspilerPath(spec) + getTranspiler(spec) { + const transpilerPath = this.getTranspilerPath(spec); if (transpilerPath) { - const transpiler = require(transpilerPath) - this.transpilerPaths[transpilerPath] = true - return transpiler + const transpiler = require(transpilerPath); + this.transpilerPaths[transpilerPath] = true; + return transpiler; } } } -module.exports = PackageTranspilationRegistry +module.exports = PackageTranspilationRegistry; diff --git a/src/package.js b/src/package.js index 37afb30b5..019a8d179 100644 --- a/src/package.js +++ b/src/package.js @@ -1,51 +1,50 @@ -const path = require('path') -const async = require('async') -const CSON = require('season') -const fs = require('fs-plus') -const {Emitter, CompositeDisposable} = require('event-kit') -const dedent = require('dedent') +const path = require('path'); +const async = require('async'); +const CSON = require('season'); +const fs = require('fs-plus'); +const { Emitter, CompositeDisposable } = require('event-kit'); +const dedent = require('dedent'); -const CompileCache = require('./compile-cache') -const ModuleCache = require('./module-cache') -const BufferedProcess = require('./buffered-process') +const CompileCache = require('./compile-cache'); +const ModuleCache = require('./module-cache'); +const BufferedProcess = require('./buffered-process'); // Extended: Loads and activates a package's main module and resources such as // stylesheets, keymaps, grammar, editor properties, and menus. -module.exports = -class Package { +module.exports = class Package { /* Section: Construction */ - constructor (params) { - this.config = params.config - this.packageManager = params.packageManager - this.styleManager = params.styleManager - this.commandRegistry = params.commandRegistry - this.keymapManager = params.keymapManager - this.notificationManager = params.notificationManager - this.grammarRegistry = params.grammarRegistry - this.themeManager = params.themeManager - this.menuManager = params.menuManager - this.contextMenuManager = params.contextMenuManager - this.deserializerManager = params.deserializerManager - this.viewRegistry = params.viewRegistry - this.emitter = new Emitter() + constructor(params) { + this.config = params.config; + this.packageManager = params.packageManager; + this.styleManager = params.styleManager; + this.commandRegistry = params.commandRegistry; + this.keymapManager = params.keymapManager; + this.notificationManager = params.notificationManager; + this.grammarRegistry = params.grammarRegistry; + this.themeManager = params.themeManager; + this.menuManager = params.menuManager; + this.contextMenuManager = params.contextMenuManager; + this.deserializerManager = params.deserializerManager; + this.viewRegistry = params.viewRegistry; + this.emitter = new Emitter(); - this.mainModule = null - this.path = params.path - this.preloadedPackage = params.preloadedPackage + this.mainModule = null; + this.path = params.path; + this.preloadedPackage = params.preloadedPackage; this.metadata = - params.metadata || - this.packageManager.loadPackageMetadata(this.path) - this.bundledPackage = params.bundledPackage != null - ? params.bundledPackage - : this.packageManager.isBundledPackagePath(this.path) + params.metadata || this.packageManager.loadPackageMetadata(this.path); + this.bundledPackage = + params.bundledPackage != null + ? params.bundledPackage + : this.packageManager.isBundledPackagePath(this.path); this.name = (this.metadata && this.metadata.name) || params.name || - path.basename(this.path) - this.reset() + path.basename(this.path); + this.reset(); } /* @@ -57,874 +56,1021 @@ class Package { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDeactivate (callback) { - return this.emitter.on('did-deactivate', callback) + onDidDeactivate(callback) { + return this.emitter.on('did-deactivate', callback); } /* Section: Instance Methods */ - enable () { - return this.config.removeAtKeyPath('core.disabledPackages', this.name) + enable() { + return this.config.removeAtKeyPath('core.disabledPackages', this.name); } - disable () { - return this.config.pushAtKeyPath('core.disabledPackages', this.name) + disable() { + return this.config.pushAtKeyPath('core.disabledPackages', this.name); } - isTheme () { - return this.metadata && this.metadata.theme + isTheme() { + return this.metadata && this.metadata.theme; } - measure (key, fn) { - const startTime = Date.now() - const value = fn() - this[key] = Date.now() - startTime - return value + measure(key, fn) { + const startTime = Date.now(); + const value = fn(); + this[key] = Date.now() - startTime; + return value; } - getType () { return 'atom' } + getType() { + return 'atom'; + } - getStyleSheetPriority () { return 0 } + getStyleSheetPriority() { + return 0; + } - preload () { - this.loadKeymaps() - this.loadMenus() - this.registerDeserializerMethods() - this.activateCoreStartupServices() - this.registerURIHandler() - this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata() - this.requireMainModule() - this.settingsPromise = this.loadSettings() + preload() { + this.loadKeymaps(); + this.loadMenus(); + this.registerDeserializerMethods(); + this.activateCoreStartupServices(); + this.registerURIHandler(); + this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata(); + this.requireMainModule(); + this.settingsPromise = this.loadSettings(); - this.activationDisposables = new CompositeDisposable() - this.activateKeymaps() - this.activateMenus() + this.activationDisposables = new CompositeDisposable(); + this.activateKeymaps(); + this.activateMenus(); for (let settings of this.settings) { - settings.activate(this.config) + settings.activate(this.config); } - this.settingsActivated = true + this.settingsActivated = true; } - finishLoading () { + finishLoading() { this.measure('loadTime', () => { - this.path = path.join(this.packageManager.resourcePath, this.path) - ModuleCache.add(this.path, this.metadata) + this.path = path.join(this.packageManager.resourcePath, this.path); + ModuleCache.add(this.path, this.metadata); - this.loadStylesheets() + this.loadStylesheets(); // Unfortunately some packages are accessing `@mainModulePath`, so we need // to compute that variable eagerly also for preloaded packages. - this.getMainModulePath() - }) + this.getMainModulePath(); + }); } - load () { + load() { this.measure('loadTime', () => { try { - ModuleCache.add(this.path, this.metadata) + ModuleCache.add(this.path, this.metadata); - this.loadKeymaps() - this.loadMenus() - this.loadStylesheets() - this.registerDeserializerMethods() - this.activateCoreStartupServices() - this.registerURIHandler() - this.registerTranspilerConfig() - this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata() - this.settingsPromise = this.loadSettings() - if (this.shouldRequireMainModuleOnLoad() && (this.mainModule == null)) { - this.requireMainModule() + this.loadKeymaps(); + this.loadMenus(); + this.loadStylesheets(); + this.registerDeserializerMethods(); + this.activateCoreStartupServices(); + this.registerURIHandler(); + this.registerTranspilerConfig(); + this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata(); + this.settingsPromise = this.loadSettings(); + if (this.shouldRequireMainModuleOnLoad() && this.mainModule == null) { + this.requireMainModule(); } } catch (error) { - this.handleError(`Failed to load the ${this.name} package`, error) + this.handleError(`Failed to load the ${this.name} package`, error); } - }) - return this + }); + return this; } - unload () { - this.unregisterTranspilerConfig() + unload() { + this.unregisterTranspilerConfig(); } - shouldRequireMainModuleOnLoad () { + shouldRequireMainModuleOnLoad() { return !( this.metadata.deserializers || this.metadata.viewProviders || this.metadata.configSchema || this.activationShouldBeDeferred() || - localStorage.getItem(this.getCanDeferMainModuleRequireStorageKey()) === 'true' - ) + localStorage.getItem(this.getCanDeferMainModuleRequireStorageKey()) === + 'true' + ); } - reset () { - this.stylesheets = [] - this.keymaps = [] - this.menus = [] - this.grammars = [] - this.settings = [] - this.mainInitialized = false - this.mainActivated = false + reset() { + this.stylesheets = []; + this.keymaps = []; + this.menus = []; + this.grammars = []; + this.settings = []; + this.mainInitialized = false; + this.mainActivated = false; } - initializeIfNeeded () { - if (this.mainInitialized) return + initializeIfNeeded() { + if (this.mainInitialized) return; this.measure('initializeTime', () => { try { // The main module's `initialize()` method is guaranteed to be called // before its `activate()`. This gives you a chance to handle the // serialized package state before the package's derserializers and view // providers are used. - if (!this.mainModule) this.requireMainModule() + if (!this.mainModule) this.requireMainModule(); if (typeof this.mainModule.initialize === 'function') { - this.mainModule.initialize(this.packageManager.getPackageState(this.name) || {}) + this.mainModule.initialize( + this.packageManager.getPackageState(this.name) || {} + ); } - this.mainInitialized = true + this.mainInitialized = true; } catch (error) { - this.handleError(`Failed to initialize the ${this.name} package`, error) + this.handleError( + `Failed to initialize the ${this.name} package`, + error + ); } - }) + }); } - activate () { - if (!this.grammarsPromise) this.grammarsPromise = this.loadGrammars() + activate() { + if (!this.grammarsPromise) this.grammarsPromise = this.loadGrammars(); if (!this.activationPromise) { this.activationPromise = new Promise((resolve, reject) => { - this.resolveActivationPromise = resolve + this.resolveActivationPromise = resolve; this.measure('activateTime', () => { try { - this.activateResources() + this.activateResources(); if (this.activationShouldBeDeferred()) { - return this.subscribeToDeferredActivation() + return this.subscribeToDeferredActivation(); } else { - return this.activateNow() + return this.activateNow(); } } catch (error) { - return this.handleError(`Failed to activate the ${this.name} package`, error) + return this.handleError( + `Failed to activate the ${this.name} package`, + error + ); } - }) - }) + }); + }); } - return Promise.all([this.grammarsPromise, this.settingsPromise, this.activationPromise]) + return Promise.all([ + this.grammarsPromise, + this.settingsPromise, + this.activationPromise + ]); } - activateNow () { + activateNow() { try { - if (!this.mainModule) this.requireMainModule() - this.configSchemaRegisteredOnActivate = this.registerConfigSchemaFromMainModule() - this.registerViewProviders() - this.activateStylesheets() + if (!this.mainModule) this.requireMainModule(); + this.configSchemaRegisteredOnActivate = this.registerConfigSchemaFromMainModule(); + this.registerViewProviders(); + this.activateStylesheets(); if (this.mainModule && !this.mainActivated) { - this.initializeIfNeeded() + this.initializeIfNeeded(); if (typeof this.mainModule.activateConfig === 'function') { - this.mainModule.activateConfig() + this.mainModule.activateConfig(); } if (typeof this.mainModule.activate === 'function') { - this.mainModule.activate(this.packageManager.getPackageState(this.name) || {}) + this.mainModule.activate( + this.packageManager.getPackageState(this.name) || {} + ); } - this.mainActivated = true - this.activateServices() + this.mainActivated = true; + this.activateServices(); } - if (this.activationCommandSubscriptions) this.activationCommandSubscriptions.dispose() - if (this.activationHookSubscriptions) this.activationHookSubscriptions.dispose() + if (this.activationCommandSubscriptions) + this.activationCommandSubscriptions.dispose(); + if (this.activationHookSubscriptions) + this.activationHookSubscriptions.dispose(); } catch (error) { - this.handleError(`Failed to activate the ${this.name} package`, error) + this.handleError(`Failed to activate the ${this.name} package`, error); } - if (typeof this.resolveActivationPromise === 'function') this.resolveActivationPromise() + if (typeof this.resolveActivationPromise === 'function') + this.resolveActivationPromise(); } - registerConfigSchemaFromMetadata () { - const configSchema = this.metadata.configSchema + registerConfigSchemaFromMetadata() { + const configSchema = this.metadata.configSchema; if (configSchema) { - this.config.setSchema(this.name, {type: 'object', properties: configSchema}) - return true + this.config.setSchema(this.name, { + type: 'object', + properties: configSchema + }); + return true; } else { - return false + return false; } } - registerConfigSchemaFromMainModule () { + registerConfigSchemaFromMainModule() { if (this.mainModule && !this.configSchemaRegisteredOnLoad) { if (typeof this.mainModule.config === 'object') { - this.config.setSchema(this.name, {type: 'object', properties: this.mainModule.config}) - return true + this.config.setSchema(this.name, { + type: 'object', + properties: this.mainModule.config + }); + return true; } } - return false + return false; } // TODO: Remove. Settings view calls this method currently. - activateConfig () { - if (this.configSchemaRegisteredOnLoad) return - this.requireMainModule() - this.registerConfigSchemaFromMainModule() + activateConfig() { + if (this.configSchemaRegisteredOnLoad) return; + this.requireMainModule(); + this.registerConfigSchemaFromMainModule(); } - activateStylesheets () { - if (this.stylesheetsActivated) return + activateStylesheets() { + if (this.stylesheetsActivated) return; - this.stylesheetDisposables = new CompositeDisposable() + this.stylesheetDisposables = new CompositeDisposable(); - const priority = this.getStyleSheetPriority() + const priority = this.getStyleSheetPriority(); for (let [sourcePath, source] of this.stylesheets) { - const match = path.basename(sourcePath).match(/[^.]*\.([^.]*)\./) + const match = path.basename(sourcePath).match(/[^.]*\.([^.]*)\./); - let context + let context; if (match) { - context = match[1] + context = match[1]; } else if (this.metadata.theme === 'syntax') { - context = 'atom-text-editor' + context = 'atom-text-editor'; } this.stylesheetDisposables.add( - this.styleManager.addStyleSheet( - source, - { - sourcePath, - priority, - context, - skipDeprecatedSelectorsTransformation: this.bundledPackage - } - ) - ) + this.styleManager.addStyleSheet(source, { + sourcePath, + priority, + context, + skipDeprecatedSelectorsTransformation: this.bundledPackage + }) + ); } - this.stylesheetsActivated = true + this.stylesheetsActivated = true; } - activateResources () { - if (!this.activationDisposables) this.activationDisposables = new CompositeDisposable() + activateResources() { + if (!this.activationDisposables) + this.activationDisposables = new CompositeDisposable(); - const packagesWithKeymapsDisabled = this.config.get('core.packagesWithKeymapsDisabled') - if (packagesWithKeymapsDisabled && packagesWithKeymapsDisabled.includes(this.name)) { - this.deactivateKeymaps() + const packagesWithKeymapsDisabled = this.config.get( + 'core.packagesWithKeymapsDisabled' + ); + if ( + packagesWithKeymapsDisabled && + packagesWithKeymapsDisabled.includes(this.name) + ) { + this.deactivateKeymaps(); } else if (!this.keymapActivated) { - this.activateKeymaps() + this.activateKeymaps(); } if (!this.menusActivated) { - this.activateMenus() + this.activateMenus(); } if (!this.grammarsActivated) { for (let grammar of this.grammars) { - grammar.activate() + grammar.activate(); } - this.grammarsActivated = true + this.grammarsActivated = true; } if (!this.settingsActivated) { for (let settings of this.settings) { - settings.activate(this.config) + settings.activate(this.config); } - this.settingsActivated = true + this.settingsActivated = true; } } - activateKeymaps () { - if (this.keymapActivated) return + activateKeymaps() { + if (this.keymapActivated) return; - this.keymapDisposables = new CompositeDisposable() + this.keymapDisposables = new CompositeDisposable(); - const validateSelectors = !this.preloadedPackage + const validateSelectors = !this.preloadedPackage; for (let [keymapPath, map] of this.keymaps) { - this.keymapDisposables.add(this.keymapManager.add(keymapPath, map, 0, validateSelectors)) + this.keymapDisposables.add( + this.keymapManager.add(keymapPath, map, 0, validateSelectors) + ); } - this.menuManager.update() + this.menuManager.update(); - this.keymapActivated = true + this.keymapActivated = true; } - deactivateKeymaps () { - if (!this.keymapActivated) return + deactivateKeymaps() { + if (!this.keymapActivated) return; if (this.keymapDisposables) { - this.keymapDisposables.dispose() + this.keymapDisposables.dispose(); } - this.menuManager.update() - this.keymapActivated = false + this.menuManager.update(); + this.keymapActivated = false; } - hasKeymaps () { + hasKeymaps() { for (let [, map] of this.keymaps) { - if (map.length > 0) return true + if (map.length > 0) return true; } - return false + return false; } - activateMenus () { - const validateSelectors = !this.preloadedPackage + activateMenus() { + const validateSelectors = !this.preloadedPackage; for (const [menuPath, map] of this.menus) { if (map['context-menu']) { try { - const itemsBySelector = map['context-menu'] - this.activationDisposables.add(this.contextMenuManager.add(itemsBySelector, validateSelectors)) + const itemsBySelector = map['context-menu']; + this.activationDisposables.add( + this.contextMenuManager.add(itemsBySelector, validateSelectors) + ); } catch (error) { if (error.code === 'EBADSELECTOR') { - error.message += ` in ${menuPath}` - error.stack += `\n at ${menuPath}:1:1` + error.message += ` in ${menuPath}`; + error.stack += `\n at ${menuPath}:1:1`; } - throw error + throw error; } } } for (const [, map] of this.menus) { - if (map.menu) this.activationDisposables.add(this.menuManager.add(map.menu)) + if (map.menu) + this.activationDisposables.add(this.menuManager.add(map.menu)); } - this.menusActivated = true + this.menusActivated = true; } - activateServices () { - let methodName, version, versions + activateServices() { + let methodName, version, versions; for (var name in this.metadata.providedServices) { - ({versions} = this.metadata.providedServices[name]) - const servicesByVersion = {} + ({ versions } = this.metadata.providedServices[name]); + const servicesByVersion = {}; for (version in versions) { - methodName = versions[version] + methodName = versions[version]; if (typeof this.mainModule[methodName] === 'function') { - servicesByVersion[version] = this.mainModule[methodName]() + servicesByVersion[version] = this.mainModule[methodName](); } } - this.activationDisposables.add(this.packageManager.serviceHub.provide(name, servicesByVersion)) + this.activationDisposables.add( + this.packageManager.serviceHub.provide(name, servicesByVersion) + ); } for (name in this.metadata.consumedServices) { - ({versions} = this.metadata.consumedServices[name]) + ({ versions } = this.metadata.consumedServices[name]); for (version in versions) { - methodName = versions[version] + methodName = versions[version]; if (typeof this.mainModule[methodName] === 'function') { - this.activationDisposables.add(this.packageManager.serviceHub.consume(name, version, this.mainModule[methodName].bind(this.mainModule))) + this.activationDisposables.add( + this.packageManager.serviceHub.consume( + name, + version, + this.mainModule[methodName].bind(this.mainModule) + ) + ); } } } } - registerURIHandler () { - const handlerConfig = this.getURIHandler() - const methodName = handlerConfig && handlerConfig.method + registerURIHandler() { + const handlerConfig = this.getURIHandler(); + const methodName = handlerConfig && handlerConfig.method; if (methodName) { - this.uriHandlerSubscription = this.packageManager.registerURIHandlerForPackage(this.name, (...args) => - this.handleURI(methodName, args) - ) + this.uriHandlerSubscription = this.packageManager.registerURIHandlerForPackage( + this.name, + (...args) => this.handleURI(methodName, args) + ); } } - unregisterURIHandler () { - if (this.uriHandlerSubscription) this.uriHandlerSubscription.dispose() + unregisterURIHandler() { + if (this.uriHandlerSubscription) this.uriHandlerSubscription.dispose(); } - handleURI (methodName, args) { + handleURI(methodName, args) { this.activate().then(() => { - if (this.mainModule[methodName]) this.mainModule[methodName].apply(this.mainModule, args) - }) - if (!this.mainActivated) this.activateNow() + if (this.mainModule[methodName]) + this.mainModule[methodName].apply(this.mainModule, args); + }); + if (!this.mainActivated) this.activateNow(); } - registerTranspilerConfig () { + registerTranspilerConfig() { if (this.metadata.atomTranspilers) { - CompileCache.addTranspilerConfigForPath(this.path, this.name, this.metadata, this.metadata.atomTranspilers) + CompileCache.addTranspilerConfigForPath( + this.path, + this.name, + this.metadata, + this.metadata.atomTranspilers + ); } } - unregisterTranspilerConfig () { + unregisterTranspilerConfig() { if (this.metadata.atomTranspilers) { - CompileCache.removeTranspilerConfigForPath(this.path) + CompileCache.removeTranspilerConfigForPath(this.path); } } - loadKeymaps () { + loadKeymaps() { if (this.bundledPackage && this.packageManager.packagesCache[this.name]) { - this.keymaps = [] - for (const keymapPath in this.packageManager.packagesCache[this.name].keymaps) { - const keymapObject = this.packageManager.packagesCache[this.name].keymaps[keymapPath] - this.keymaps.push([`core:${keymapPath}`, keymapObject]) + this.keymaps = []; + for (const keymapPath in this.packageManager.packagesCache[this.name] + .keymaps) { + const keymapObject = this.packageManager.packagesCache[this.name] + .keymaps[keymapPath]; + this.keymaps.push([`core:${keymapPath}`, keymapObject]); } } else { - this.keymaps = this.getKeymapPaths().map((keymapPath) => [ + this.keymaps = this.getKeymapPaths().map(keymapPath => [ keymapPath, - CSON.readFileSync(keymapPath, {allowDuplicateKeys: false}) || {} - ]) + CSON.readFileSync(keymapPath, { allowDuplicateKeys: false }) || {} + ]); } } - loadMenus () { + loadMenus() { if (this.bundledPackage && this.packageManager.packagesCache[this.name]) { - this.menus = [] - for (const menuPath in this.packageManager.packagesCache[this.name].menus) { - const menuObject = this.packageManager.packagesCache[this.name].menus[menuPath] - this.menus.push([`core:${menuPath}`, menuObject]) + this.menus = []; + for (const menuPath in this.packageManager.packagesCache[this.name] + .menus) { + const menuObject = this.packageManager.packagesCache[this.name].menus[ + menuPath + ]; + this.menus.push([`core:${menuPath}`, menuObject]); } } else { - this.menus = this.getMenuPaths().map((menuPath) => [ + this.menus = this.getMenuPaths().map(menuPath => [ menuPath, CSON.readFileSync(menuPath) || {} - ]) + ]); } } - getKeymapPaths () { - const keymapsDirPath = path.join(this.path, 'keymaps') + getKeymapPaths() { + const keymapsDirPath = path.join(this.path, 'keymaps'); if (this.metadata.keymaps) { - return this.metadata.keymaps.map(name => fs.resolve(keymapsDirPath, name, ['json', 'cson', ''])) + return this.metadata.keymaps.map(name => + fs.resolve(keymapsDirPath, name, ['json', 'cson', '']) + ); } else { - return fs.listSync(keymapsDirPath, ['cson', 'json']) + return fs.listSync(keymapsDirPath, ['cson', 'json']); } } - getMenuPaths () { - const menusDirPath = path.join(this.path, 'menus') + getMenuPaths() { + const menusDirPath = path.join(this.path, 'menus'); if (this.metadata.menus) { - return this.metadata.menus.map(name => fs.resolve(menusDirPath, name, ['json', 'cson', ''])) + return this.metadata.menus.map(name => + fs.resolve(menusDirPath, name, ['json', 'cson', '']) + ); } else { - return fs.listSync(menusDirPath, ['cson', 'json']) + return fs.listSync(menusDirPath, ['cson', 'json']); } } - loadStylesheets () { - this.stylesheets = this.getStylesheetPaths().map(stylesheetPath => - [stylesheetPath, this.themeManager.loadStylesheet(stylesheetPath, true)] - ) + loadStylesheets() { + this.stylesheets = this.getStylesheetPaths().map(stylesheetPath => [ + stylesheetPath, + this.themeManager.loadStylesheet(stylesheetPath, true) + ]); } - registerDeserializerMethods () { + registerDeserializerMethods() { if (this.metadata.deserializers) { Object.keys(this.metadata.deserializers).forEach(deserializerName => { - const methodName = this.metadata.deserializers[deserializerName] + const methodName = this.metadata.deserializers[deserializerName]; this.deserializerManager.add({ name: deserializerName, deserialize: (state, atomEnvironment) => { - this.registerViewProviders() - this.requireMainModule() - this.initializeIfNeeded() - return this.mainModule[methodName](state, atomEnvironment) + this.registerViewProviders(); + this.requireMainModule(); + this.initializeIfNeeded(); + return this.mainModule[methodName](state, atomEnvironment); } - }) - }) + }); + }); } } - activateCoreStartupServices () { + activateCoreStartupServices() { const directoryProviderService = this.metadata.providedServices && - this.metadata.providedServices['atom.directory-provider'] + this.metadata.providedServices['atom.directory-provider']; if (directoryProviderService) { - this.requireMainModule() - const servicesByVersion = {} + this.requireMainModule(); + const servicesByVersion = {}; for (let version in directoryProviderService.versions) { - const methodName = directoryProviderService.versions[version] + const methodName = directoryProviderService.versions[version]; if (typeof this.mainModule[methodName] === 'function') { - servicesByVersion[version] = this.mainModule[methodName]() + servicesByVersion[version] = this.mainModule[methodName](); } } - this.packageManager.serviceHub.provide('atom.directory-provider', servicesByVersion) + this.packageManager.serviceHub.provide( + 'atom.directory-provider', + servicesByVersion + ); } } - registerViewProviders () { + registerViewProviders() { if (this.metadata.viewProviders && !this.registeredViewProviders) { - this.requireMainModule() + this.requireMainModule(); this.metadata.viewProviders.forEach(methodName => { this.viewRegistry.addViewProvider(model => { - this.initializeIfNeeded() - return this.mainModule[methodName](model) - }) - }) - this.registeredViewProviders = true + this.initializeIfNeeded(); + return this.mainModule[methodName](model); + }); + }); + this.registeredViewProviders = true; } } - getStylesheetsPath () { - return path.join(this.path, 'styles') + getStylesheetsPath() { + return path.join(this.path, 'styles'); } - getStylesheetPaths () { - if (this.bundledPackage && - this.packageManager.packagesCache[this.name] && - this.packageManager.packagesCache[this.name].styleSheetPaths) { - const {styleSheetPaths} = this.packageManager.packagesCache[this.name] - return styleSheetPaths.map(styleSheetPath => path.join(this.path, styleSheetPath)) + getStylesheetPaths() { + if ( + this.bundledPackage && + this.packageManager.packagesCache[this.name] && + this.packageManager.packagesCache[this.name].styleSheetPaths + ) { + const { styleSheetPaths } = this.packageManager.packagesCache[this.name]; + return styleSheetPaths.map(styleSheetPath => + path.join(this.path, styleSheetPath) + ); } else { - let indexStylesheet - const stylesheetDirPath = this.getStylesheetsPath() + let indexStylesheet; + const stylesheetDirPath = this.getStylesheetsPath(); if (this.metadata.mainStyleSheet) { - return [fs.resolve(this.path, this.metadata.mainStyleSheet)] + return [fs.resolve(this.path, this.metadata.mainStyleSheet)]; } else if (this.metadata.styleSheets) { - return this.metadata.styleSheets.map(name => fs.resolve(stylesheetDirPath, name, ['css', 'less', ''])) - } else if ((indexStylesheet = fs.resolve(this.path, 'index', ['css', 'less']))) { - return [indexStylesheet] + return this.metadata.styleSheets.map(name => + fs.resolve(stylesheetDirPath, name, ['css', 'less', '']) + ); + } else if ( + (indexStylesheet = fs.resolve(this.path, 'index', ['css', 'less'])) + ) { + return [indexStylesheet]; } else { - return fs.listSync(stylesheetDirPath, ['css', 'less']) + return fs.listSync(stylesheetDirPath, ['css', 'less']); } } } - loadGrammarsSync () { - if (this.grammarsLoaded) return + loadGrammarsSync() { + if (this.grammarsLoaded) return; - let grammarPaths + let grammarPaths; if (this.preloadedPackage && this.packageManager.packagesCache[this.name]) { - ({grammarPaths} = this.packageManager.packagesCache[this.name]) + ({ grammarPaths } = this.packageManager.packagesCache[this.name]); } else { - grammarPaths = fs.listSync(path.join(this.path, 'grammars'), ['json', 'cson']) + grammarPaths = fs.listSync(path.join(this.path, 'grammars'), [ + 'json', + 'cson' + ]); } for (let grammarPath of grammarPaths) { - if (this.preloadedPackage && this.packageManager.packagesCache[this.name]) { - grammarPath = path.resolve(this.packageManager.resourcePath, grammarPath) + if ( + this.preloadedPackage && + this.packageManager.packagesCache[this.name] + ) { + grammarPath = path.resolve( + this.packageManager.resourcePath, + grammarPath + ); } try { - const grammar = this.grammarRegistry.readGrammarSync(grammarPath) - grammar.packageName = this.name - grammar.bundledPackage = this.bundledPackage - this.grammars.push(grammar) - grammar.activate() + const grammar = this.grammarRegistry.readGrammarSync(grammarPath); + grammar.packageName = this.name; + grammar.bundledPackage = this.bundledPackage; + this.grammars.push(grammar); + grammar.activate(); } catch (error) { - console.warn(`Failed to load grammar: ${grammarPath}`, error.stack || error) + console.warn( + `Failed to load grammar: ${grammarPath}`, + error.stack || error + ); } } - this.grammarsLoaded = true - this.grammarsActivated = true + this.grammarsLoaded = true; + this.grammarsActivated = true; } - loadGrammars () { - if (this.grammarsLoaded) return Promise.resolve() + loadGrammars() { + if (this.grammarsLoaded) return Promise.resolve(); const loadGrammar = (grammarPath, callback) => { if (this.preloadedPackage) { - grammarPath = path.resolve(this.packageManager.resourcePath, grammarPath) + grammarPath = path.resolve( + this.packageManager.resourcePath, + grammarPath + ); } return this.grammarRegistry.readGrammar(grammarPath, (error, grammar) => { if (error) { - const detail = `${error.message} in ${grammarPath}` - const stack = `${error.stack}\n at ${grammarPath}:1:1` - this.notificationManager.addFatalError(`Failed to load a ${this.name} package grammar`, {stack, detail, packageName: this.name, dismissable: true}) + const detail = `${error.message} in ${grammarPath}`; + const stack = `${error.stack}\n at ${grammarPath}:1:1`; + this.notificationManager.addFatalError( + `Failed to load a ${this.name} package grammar`, + { stack, detail, packageName: this.name, dismissable: true } + ); } else { - grammar.packageName = this.name - grammar.bundledPackage = this.bundledPackage - this.grammars.push(grammar) - if (this.grammarsActivated) grammar.activate() + grammar.packageName = this.name; + grammar.bundledPackage = this.bundledPackage; + this.grammars.push(grammar); + if (this.grammarsActivated) grammar.activate(); } - return callback() - }) - } + return callback(); + }); + }; return new Promise(resolve => { - if (this.preloadedPackage && this.packageManager.packagesCache[this.name]) { - const { grammarPaths } = this.packageManager.packagesCache[this.name] - return async.each(grammarPaths, loadGrammar, () => resolve()) + if ( + this.preloadedPackage && + this.packageManager.packagesCache[this.name] + ) { + const { grammarPaths } = this.packageManager.packagesCache[this.name]; + return async.each(grammarPaths, loadGrammar, () => resolve()); } else { - const grammarsDirPath = path.join(this.path, 'grammars') - fs.exists(grammarsDirPath, (grammarsDirExists) => { - if (!grammarsDirExists) return resolve() + const grammarsDirPath = path.join(this.path, 'grammars'); + fs.exists(grammarsDirPath, grammarsDirExists => { + if (!grammarsDirExists) return resolve(); fs.list(grammarsDirPath, ['json', 'cson'], (error, grammarPaths) => { - if (error || !grammarPaths) return resolve() - async.each(grammarPaths, loadGrammar, () => resolve()) - }) - }) + if (error || !grammarPaths) return resolve(); + async.each(grammarPaths, loadGrammar, () => resolve()); + }); + }); } - }) + }); } - loadSettings () { - this.settings = [] + loadSettings() { + this.settings = []; const loadSettingsFile = (settingsPath, callback) => { return SettingsFile.load(settingsPath, (error, settingsFile) => { if (error) { - const detail = `${error.message} in ${settingsPath}` - const stack = `${error.stack}\n at ${settingsPath}:1:1` - this.notificationManager.addFatalError(`Failed to load the ${this.name} package settings`, {stack, detail, packageName: this.name, dismissable: true}) + const detail = `${error.message} in ${settingsPath}`; + const stack = `${error.stack}\n at ${settingsPath}:1:1`; + this.notificationManager.addFatalError( + `Failed to load the ${this.name} package settings`, + { stack, detail, packageName: this.name, dismissable: true } + ); } else { - this.settings.push(settingsFile) - if (this.settingsActivated) settingsFile.activate(this.config) + this.settings.push(settingsFile); + if (this.settingsActivated) settingsFile.activate(this.config); } - return callback() - }) - } + return callback(); + }); + }; if (this.preloadedPackage && this.packageManager.packagesCache[this.name]) { - for (let settingsPath in this.packageManager.packagesCache[this.name].settings) { - const properties = this.packageManager.packagesCache[this.name].settings[settingsPath] - const settingsFile = new SettingsFile(`core:${settingsPath}`, properties || {}) - this.settings.push(settingsFile) - if (this.settingsActivated) settingsFile.activate(this.config) + for (let settingsPath in this.packageManager.packagesCache[this.name] + .settings) { + const properties = this.packageManager.packagesCache[this.name] + .settings[settingsPath]; + const settingsFile = new SettingsFile( + `core:${settingsPath}`, + properties || {} + ); + this.settings.push(settingsFile); + if (this.settingsActivated) settingsFile.activate(this.config); } } else { return new Promise(resolve => { - const settingsDirPath = path.join(this.path, 'settings') - fs.exists(settingsDirPath, (settingsDirExists) => { - if (!settingsDirExists) return resolve() + const settingsDirPath = path.join(this.path, 'settings'); + fs.exists(settingsDirPath, settingsDirExists => { + if (!settingsDirExists) return resolve(); fs.list(settingsDirPath, ['json', 'cson'], (error, settingsPaths) => { - if (error || !settingsPaths) return resolve() - async.each(settingsPaths, loadSettingsFile, () => resolve()) - }) - }) - }) + if (error || !settingsPaths) return resolve(); + async.each(settingsPaths, loadSettingsFile, () => resolve()); + }); + }); + }); } } - serialize () { + serialize() { if (this.mainActivated) { if (typeof this.mainModule.serialize === 'function') { try { - return this.mainModule.serialize() + return this.mainModule.serialize(); } catch (error) { - console.error(`Error serializing package '${this.name}'`, error.stack) + console.error( + `Error serializing package '${this.name}'`, + error.stack + ); } } } } - async deactivate () { - this.activationPromise = null - this.resolveActivationPromise = null - if (this.activationCommandSubscriptions) this.activationCommandSubscriptions.dispose() - if (this.activationHookSubscriptions) this.activationHookSubscriptions.dispose() - this.configSchemaRegisteredOnActivate = false - this.unregisterURIHandler() - this.deactivateResources() - this.deactivateKeymaps() + async deactivate() { + this.activationPromise = null; + this.resolveActivationPromise = null; + if (this.activationCommandSubscriptions) + this.activationCommandSubscriptions.dispose(); + if (this.activationHookSubscriptions) + this.activationHookSubscriptions.dispose(); + this.configSchemaRegisteredOnActivate = false; + this.unregisterURIHandler(); + this.deactivateResources(); + this.deactivateKeymaps(); if (!this.mainActivated) { - this.emitter.emit('did-deactivate') - return + this.emitter.emit('did-deactivate'); + return; } if (typeof this.mainModule.deactivate === 'function') { try { - const deactivationResult = this.mainModule.deactivate() - if (deactivationResult && typeof deactivationResult.then === 'function') { - await deactivationResult + const deactivationResult = this.mainModule.deactivate(); + if ( + deactivationResult && + typeof deactivationResult.then === 'function' + ) { + await deactivationResult; } } catch (error) { - console.error(`Error deactivating package '${this.name}'`, error.stack) + console.error(`Error deactivating package '${this.name}'`, error.stack); } } if (typeof this.mainModule.deactivateConfig === 'function') { try { - await this.mainModule.deactivateConfig() + await this.mainModule.deactivateConfig(); } catch (error) { - console.error(`Error deactivating package '${this.name}'`, error.stack) + console.error(`Error deactivating package '${this.name}'`, error.stack); } } - this.mainActivated = false - this.mainInitialized = false - this.emitter.emit('did-deactivate') + this.mainActivated = false; + this.mainInitialized = false; + this.emitter.emit('did-deactivate'); } - deactivateResources () { + deactivateResources() { for (let grammar of this.grammars) { - grammar.deactivate() + grammar.deactivate(); } for (let settings of this.settings) { - settings.deactivate(this.config) + settings.deactivate(this.config); } - if (this.stylesheetDisposables) this.stylesheetDisposables.dispose() - if (this.activationDisposables) this.activationDisposables.dispose() - if (this.keymapDisposables) this.keymapDisposables.dispose() + if (this.stylesheetDisposables) this.stylesheetDisposables.dispose(); + if (this.activationDisposables) this.activationDisposables.dispose(); + if (this.keymapDisposables) this.keymapDisposables.dispose(); - this.stylesheetsActivated = false - this.grammarsActivated = false - this.settingsActivated = false - this.menusActivated = false + this.stylesheetsActivated = false; + this.grammarsActivated = false; + this.settingsActivated = false; + this.menusActivated = false; } - reloadStylesheets () { + reloadStylesheets() { try { - this.loadStylesheets() + this.loadStylesheets(); } catch (error) { - this.handleError(`Failed to reload the ${this.name} package stylesheets`, error) + this.handleError( + `Failed to reload the ${this.name} package stylesheets`, + error + ); } - if (this.stylesheetDisposables) this.stylesheetDisposables.dispose() - this.stylesheetDisposables = new CompositeDisposable() - this.stylesheetsActivated = false - this.activateStylesheets() + if (this.stylesheetDisposables) this.stylesheetDisposables.dispose(); + this.stylesheetDisposables = new CompositeDisposable(); + this.stylesheetsActivated = false; + this.activateStylesheets(); } - requireMainModule () { + requireMainModule() { if (this.bundledPackage && this.packageManager.packagesCache[this.name]) { if (this.packageManager.packagesCache[this.name].main) { - this.mainModule = require(this.packageManager.packagesCache[this.name].main) - return this.mainModule + this.mainModule = require(this.packageManager.packagesCache[this.name] + .main); + return this.mainModule; } } else if (this.mainModuleRequired) { - return this.mainModule + return this.mainModule; } else if (!this.isCompatible()) { - const nativeModuleNames = this.incompatibleModules.map(m => m.name).join(', ') - console.warn(dedent ` - Failed to require the main module of '${this.name}' because it requires one or more incompatible native modules (${nativeModuleNames}). + const nativeModuleNames = this.incompatibleModules + .map(m => m.name) + .join(', '); + console.warn(dedent` + Failed to require the main module of '${ + this.name + }' because it requires one or more incompatible native modules (${nativeModuleNames}). Run \`apm rebuild\` in the package directory and restart Atom to resolve.\ - `) + `); } else { - const mainModulePath = this.getMainModulePath() + const mainModulePath = this.getMainModulePath(); if (fs.isFileSync(mainModulePath)) { - this.mainModuleRequired = true + this.mainModuleRequired = true; - const previousViewProviderCount = this.viewRegistry.getViewProviderCount() - const previousDeserializerCount = this.deserializerManager.getDeserializerCount() - this.mainModule = require(mainModulePath) - if ((this.viewRegistry.getViewProviderCount() === previousViewProviderCount) && - (this.deserializerManager.getDeserializerCount() === previousDeserializerCount)) { - localStorage.setItem(this.getCanDeferMainModuleRequireStorageKey(), 'true') + const previousViewProviderCount = this.viewRegistry.getViewProviderCount(); + const previousDeserializerCount = this.deserializerManager.getDeserializerCount(); + this.mainModule = require(mainModulePath); + if ( + this.viewRegistry.getViewProviderCount() === + previousViewProviderCount && + this.deserializerManager.getDeserializerCount() === + previousDeserializerCount + ) { + localStorage.setItem( + this.getCanDeferMainModuleRequireStorageKey(), + 'true' + ); } - return this.mainModule + return this.mainModule; } } } - getMainModulePath () { - if (this.resolvedMainModulePath) return this.mainModulePath - this.resolvedMainModulePath = true + getMainModulePath() { + if (this.resolvedMainModulePath) return this.mainModulePath; + this.resolvedMainModulePath = true; if (this.bundledPackage && this.packageManager.packagesCache[this.name]) { if (this.packageManager.packagesCache[this.name].main) { - this.mainModulePath = path.resolve(this.packageManager.resourcePath, 'static', this.packageManager.packagesCache[this.name].main) + this.mainModulePath = path.resolve( + this.packageManager.resourcePath, + 'static', + this.packageManager.packagesCache[this.name].main + ); } else { - this.mainModulePath = null + this.mainModulePath = null; } } else { const mainModulePath = this.metadata.main ? path.join(this.path, this.metadata.main) - : path.join(this.path, 'index') - this.mainModulePath = fs.resolveExtension(mainModulePath, ['', ...CompileCache.supportedExtensions]) + : path.join(this.path, 'index'); + this.mainModulePath = fs.resolveExtension(mainModulePath, [ + '', + ...CompileCache.supportedExtensions + ]); } - return this.mainModulePath + return this.mainModulePath; } - activationShouldBeDeferred () { - return this.hasActivationCommands() || this.hasActivationHooks() || this.hasDeferredURIHandler() + activationShouldBeDeferred() { + return ( + this.hasActivationCommands() || + this.hasActivationHooks() || + this.hasDeferredURIHandler() + ); } - hasActivationHooks () { - const hooks = this.getActivationHooks() - return hooks && hooks.length > 0 + hasActivationHooks() { + const hooks = this.getActivationHooks(); + return hooks && hooks.length > 0; } - hasActivationCommands () { - const object = this.getActivationCommands() + hasActivationCommands() { + const object = this.getActivationCommands(); for (let selector in object) { - const commands = object[selector] - if (commands.length > 0) return true + const commands = object[selector]; + if (commands.length > 0) return true; } - return false + return false; } - hasDeferredURIHandler () { - const handler = this.getURIHandler() - return handler && handler.deferActivation !== false + hasDeferredURIHandler() { + const handler = this.getURIHandler(); + return handler && handler.deferActivation !== false; } - subscribeToDeferredActivation () { - this.subscribeToActivationCommands() - this.subscribeToActivationHooks() + subscribeToDeferredActivation() { + this.subscribeToActivationCommands(); + this.subscribeToActivationHooks(); } - subscribeToActivationCommands () { - this.activationCommandSubscriptions = new CompositeDisposable() - const object = this.getActivationCommands() + subscribeToActivationCommands() { + this.activationCommandSubscriptions = new CompositeDisposable(); + const object = this.getActivationCommands(); for (let selector in object) { - const commands = object[selector] + const commands = object[selector]; for (let command of commands) { ((selector, command) => { // Add dummy command so it appears in menu. // The real command will be registered on package activation try { - this.activationCommandSubscriptions.add(this.commandRegistry.add(selector, command, function () {})) + this.activationCommandSubscriptions.add( + this.commandRegistry.add(selector, command, function() {}) + ); } catch (error) { if (error.code === 'EBADSELECTOR') { - const metadataPath = path.join(this.path, 'package.json') - error.message += ` in ${metadataPath}` - error.stack += `\n at ${metadataPath}:1:1` + const metadataPath = path.join(this.path, 'package.json'); + error.message += ` in ${metadataPath}`; + error.stack += `\n at ${metadataPath}:1:1`; } - throw error + throw error; } - this.activationCommandSubscriptions.add(this.commandRegistry.onWillDispatch(event => { - if (event.type !== command) return - let currentTarget = event.target - while (currentTarget) { - if (currentTarget.webkitMatchesSelector(selector)) { - this.activationCommandSubscriptions.dispose() - this.activateNow() - break + this.activationCommandSubscriptions.add( + this.commandRegistry.onWillDispatch(event => { + if (event.type !== command) return; + let currentTarget = event.target; + while (currentTarget) { + if (currentTarget.webkitMatchesSelector(selector)) { + this.activationCommandSubscriptions.dispose(); + this.activateNow(); + break; + } + currentTarget = currentTarget.parentElement; } - currentTarget = currentTarget.parentElement - } - })) - })(selector, command) + }) + ); + })(selector, command); } } } - getActivationCommands () { - if (this.activationCommands) return this.activationCommands + getActivationCommands() { + if (this.activationCommands) return this.activationCommands; - this.activationCommands = {} + this.activationCommands = {}; if (this.metadata.activationCommands) { for (let selector in this.metadata.activationCommands) { - const commands = this.metadata.activationCommands[selector] - if (!this.activationCommands[selector]) this.activationCommands[selector] = [] + const commands = this.metadata.activationCommands[selector]; + if (!this.activationCommands[selector]) + this.activationCommands[selector] = []; if (typeof commands === 'string') { - this.activationCommands[selector].push(commands) + this.activationCommands[selector].push(commands); } else if (Array.isArray(commands)) { - this.activationCommands[selector].push(...commands) + this.activationCommands[selector].push(...commands); } } } - return this.activationCommands + return this.activationCommands; } - subscribeToActivationHooks () { - this.activationHookSubscriptions = new CompositeDisposable() + subscribeToActivationHooks() { + this.activationHookSubscriptions = new CompositeDisposable(); for (let hook of this.getActivationHooks()) { if (typeof hook === 'string' && hook.trim().length > 0) { this.activationHookSubscriptions.add( - this.packageManager.onDidTriggerActivationHook(hook, () => this.activateNow()) - ) + this.packageManager.onDidTriggerActivationHook(hook, () => + this.activateNow() + ) + ); } } } - getActivationHooks () { - if (this.metadata && this.activationHooks) return this.activationHooks + getActivationHooks() { + if (this.metadata && this.activationHooks) return this.activationHooks; if (this.metadata.activationHooks) { if (Array.isArray(this.metadata.activationHooks)) { - this.activationHooks = Array.from(new Set(this.metadata.activationHooks)) + this.activationHooks = Array.from( + new Set(this.metadata.activationHooks) + ); } else if (typeof this.metadata.activationHooks === 'string') { - this.activationHooks = [this.metadata.activationHooks] + this.activationHooks = [this.metadata.activationHooks]; } else { - this.activationHooks = [] + this.activationHooks = []; } } else { - this.activationHooks = [] + this.activationHooks = []; } - return this.activationHooks + return this.activationHooks; } - getURIHandler () { - return this.metadata && this.metadata.uriHandler + getURIHandler() { + return this.metadata && this.metadata.uriHandler; } // Does the given module path contain native code? - isNativeModule (modulePath) { + isNativeModule(modulePath) { try { - return fs.listSync(path.join(modulePath, 'build', 'Release'), ['.node']).length > 0 + return ( + fs.listSync(path.join(modulePath, 'build', 'Release'), ['.node']) + .length > 0 + ); } catch (error) { - return false + return false; } } @@ -933,32 +1079,40 @@ class Package { // First try to get this information from // @metadata._atomModuleCache.extensions. If @metadata._atomModuleCache doesn't // exist, recurse through all dependencies. - getNativeModuleDependencyPaths () { - const nativeModulePaths = [] + getNativeModuleDependencyPaths() { + const nativeModulePaths = []; if (this.metadata._atomModuleCache) { const relativeNativeModuleBindingPaths = - (this.metadata._atomModuleCache.extensions && this.metadata._atomModuleCache.extensions['.node']) || - [] + (this.metadata._atomModuleCache.extensions && + this.metadata._atomModuleCache.extensions['.node']) || + []; for (let relativeNativeModuleBindingPath of relativeNativeModuleBindingPaths) { - const nativeModulePath = path.join(this.path, relativeNativeModuleBindingPath, '..', '..', '..') - nativeModulePaths.push(nativeModulePath) + const nativeModulePath = path.join( + this.path, + relativeNativeModuleBindingPath, + '..', + '..', + '..' + ); + nativeModulePaths.push(nativeModulePath); } - return nativeModulePaths + return nativeModulePaths; } var traversePath = nodeModulesPath => { try { for (let modulePath of fs.listSync(nodeModulesPath)) { - if (this.isNativeModule(modulePath)) nativeModulePaths.push(modulePath) - traversePath(path.join(modulePath, 'node_modules')) + if (this.isNativeModule(modulePath)) + nativeModulePaths.push(modulePath); + traversePath(path.join(modulePath, 'node_modules')); } } catch (error) {} - } + }; - traversePath(path.join(this.path, 'node_modules')) + traversePath(path.join(this.path, 'node_modules')); - return nativeModulePaths + return nativeModulePaths; } /* @@ -971,20 +1125,20 @@ class Package { // Incompatible packages cannot be activated. // // Returns a {Boolean}, true if compatible, false if incompatible. - isCompatible () { + isCompatible() { if (this.compatible == null) { if (this.preloadedPackage) { - this.compatible = true + this.compatible = true; } else if (this.getMainModulePath()) { - this.incompatibleModules = this.getIncompatibleNativeModules() + this.incompatibleModules = this.getIncompatibleNativeModules(); this.compatible = this.incompatibleModules.length === 0 && - this.getBuildFailureOutput() == null + this.getBuildFailureOutput() == null; } else { - this.compatible = true + this.compatible = true; } } - return this.compatible + return this.compatible; } // Extended: Rebuild native modules in this package's dependencies for the @@ -993,52 +1147,72 @@ class Package { // Returns a {Promise} that resolves with an object containing `code`, // `stdout`, and `stderr` properties based on the results of running // `apm rebuild` on the package. - rebuild () { + rebuild() { return new Promise(resolve => this.runRebuildProcess(result => { if (result.code === 0) { - global.localStorage.removeItem(this.getBuildFailureOutputStorageKey()) + global.localStorage.removeItem( + this.getBuildFailureOutputStorageKey() + ); } else { - this.compatible = false - global.localStorage.setItem(this.getBuildFailureOutputStorageKey(), result.stderr) + this.compatible = false; + global.localStorage.setItem( + this.getBuildFailureOutputStorageKey(), + result.stderr + ); } - global.localStorage.setItem(this.getIncompatibleNativeModulesStorageKey(), '[]') - resolve(result) + global.localStorage.setItem( + this.getIncompatibleNativeModulesStorageKey(), + '[]' + ); + resolve(result); }) - ) + ); } // Extended: If a previous rebuild failed, get the contents of stderr. // // Returns a {String} or null if no previous build failure occurred. - getBuildFailureOutput () { - return global.localStorage.getItem(this.getBuildFailureOutputStorageKey()) + getBuildFailureOutput() { + return global.localStorage.getItem(this.getBuildFailureOutputStorageKey()); } - runRebuildProcess (done) { - let stderr = '' - let stdout = '' + runRebuildProcess(done) { + let stderr = ''; + let stdout = ''; return new BufferedProcess({ command: this.packageManager.getApmPath(), args: ['rebuild', '--no-color'], - options: {cwd: this.path}, - stderr (output) { stderr += output }, - stdout (output) { stdout += output }, - exit (code) { done({code, stdout, stderr}) } - }) + options: { cwd: this.path }, + stderr(output) { + stderr += output; + }, + stdout(output) { + stdout += output; + }, + exit(code) { + done({ code, stdout, stderr }); + } + }); } - getBuildFailureOutputStorageKey () { - return `installed-packages:${this.name}:${this.metadata.version}:build-error` + getBuildFailureOutputStorageKey() { + return `installed-packages:${this.name}:${ + this.metadata.version + }:build-error`; } - getIncompatibleNativeModulesStorageKey () { - const electronVersion = process.versions.electron - return `installed-packages:${this.name}:${this.metadata.version}:electron-${electronVersion}:incompatible-native-modules` + getIncompatibleNativeModulesStorageKey() { + const electronVersion = process.versions.electron; + return `installed-packages:${this.name}:${ + this.metadata.version + }:electron-${electronVersion}:incompatible-native-modules`; } - getCanDeferMainModuleRequireStorageKey () { - return `installed-packages:${this.name}:${this.metadata.version}:can-defer-main-module-require` + getCanDeferMainModuleRequireStorageKey() { + return `installed-packages:${this.name}:${ + this.metadata.version + }:can-defer-main-module-require`; } // Get the incompatible native modules that this package depends on. @@ -1047,88 +1221,102 @@ class Package { // // This information is cached in local storage on a per package/version basis // to minimize the impact on startup time. - getIncompatibleNativeModules () { + getIncompatibleNativeModules() { if (!this.packageManager.devMode) { try { - const arrayAsString = global.localStorage.getItem(this.getIncompatibleNativeModulesStorageKey()) - if (arrayAsString) return JSON.parse(arrayAsString) + const arrayAsString = global.localStorage.getItem( + this.getIncompatibleNativeModulesStorageKey() + ); + if (arrayAsString) return JSON.parse(arrayAsString); } catch (error1) {} } - const incompatibleNativeModules = [] + const incompatibleNativeModules = []; for (let nativeModulePath of this.getNativeModuleDependencyPaths()) { try { - require(nativeModulePath) + require(nativeModulePath); } catch (error) { - let version + let version; try { - ({version} = require(`${nativeModulePath}/package.json`)) + ({ version } = require(`${nativeModulePath}/package.json`)); } catch (error2) {} incompatibleNativeModules.push({ path: nativeModulePath, name: path.basename(nativeModulePath), version, error: error.message - }) + }); } } global.localStorage.setItem( this.getIncompatibleNativeModulesStorageKey(), JSON.stringify(incompatibleNativeModules) - ) + ); - return incompatibleNativeModules + return incompatibleNativeModules; } - handleError (message, error) { - if (atom.inSpecMode()) throw error + handleError(message, error) { + if (atom.inSpecMode()) throw error; - let detail, location, stack + let detail, location, stack; if (error.filename && error.location && error instanceof SyntaxError) { - location = `${error.filename}:${error.location.first_line + 1}:${error.location.first_column + 1}` - detail = `${error.message} in ${location}` - stack = 'SyntaxError: ' + error.message + '\n' + 'at ' + location - } else if (error.less && error.filename && error.column != null && error.line != null) { - location = `${error.filename}:${error.line}:${error.column}` - detail = `${error.message} in ${location}` - stack = 'LessError: ' + error.message + '\n' + 'at ' + location + location = `${error.filename}:${error.location.first_line + 1}:${error + .location.first_column + 1}`; + detail = `${error.message} in ${location}`; + stack = 'SyntaxError: ' + error.message + '\n' + 'at ' + location; + } else if ( + error.less && + error.filename && + error.column != null && + error.line != null + ) { + location = `${error.filename}:${error.line}:${error.column}`; + detail = `${error.message} in ${location}`; + stack = 'LessError: ' + error.message + '\n' + 'at ' + location; } else { - detail = error.message - stack = error.stack || error + detail = error.message; + stack = error.stack || error; } this.notificationManager.addFatalError(message, { - stack, detail, packageName: this.name, dismissable: true - }) + stack, + detail, + packageName: this.name, + dismissable: true + }); } -} +}; class SettingsFile { - static load (path, callback) { + static load(path, callback) { CSON.readFile(path, (error, properties = {}) => { if (error) { - callback(error) + callback(error); } else { - callback(null, new SettingsFile(path, properties)) + callback(null, new SettingsFile(path, properties)); } - }) + }); } - constructor (path, properties) { - this.path = path - this.properties = properties + constructor(path, properties) { + this.path = path; + this.properties = properties; } - activate (config) { + activate(config) { for (let selector in this.properties) { - config.set(null, this.properties[selector], {scopeSelector: selector, source: this.path}) + config.set(null, this.properties[selector], { + scopeSelector: selector, + source: this.path + }); } } - deactivate (config) { + deactivate(config) { for (let selector in this.properties) { - config.unset(null, {scopeSelector: selector, source: this.path}) + config.unset(null, { scopeSelector: selector, source: this.path }); } } } diff --git a/src/pane-axis.js b/src/pane-axis.js index 23c87f928..8abe6641f 100644 --- a/src/pane-axis.js +++ b/src/pane-axis.js @@ -1,199 +1,205 @@ -const {Emitter, CompositeDisposable} = require('event-kit') -const {flatten} = require('underscore-plus') -const Model = require('./model') -const PaneAxisElement = require('./pane-axis-element') +const { Emitter, CompositeDisposable } = require('event-kit'); +const { flatten } = require('underscore-plus'); +const Model = require('./model'); +const PaneAxisElement = require('./pane-axis-element'); class PaneAxis extends Model { - static deserialize (state, {deserializers, views}) { - state.children = state.children.map(childState => deserializers.deserialize(childState)) - return new PaneAxis(state, views) + static deserialize(state, { deserializers, views }) { + state.children = state.children.map(childState => + deserializers.deserialize(childState) + ); + return new PaneAxis(state, views); } - constructor ({orientation, children, flexScale}, viewRegistry) { - super() - this.parent = null - this.container = null - this.orientation = orientation - this.viewRegistry = viewRegistry - this.emitter = new Emitter() - this.subscriptionsByChild = new WeakMap() - this.subscriptions = new CompositeDisposable() - this.flexScale = flexScale != null ? flexScale : 1 - this.children = [] + constructor({ orientation, children, flexScale }, viewRegistry) { + super(); + this.parent = null; + this.container = null; + this.orientation = orientation; + this.viewRegistry = viewRegistry; + this.emitter = new Emitter(); + this.subscriptionsByChild = new WeakMap(); + this.subscriptions = new CompositeDisposable(); + this.flexScale = flexScale != null ? flexScale : 1; + this.children = []; if (children) { for (let child of children) { - this.addChild(child) + this.addChild(child); } } } - serialize () { + serialize() { return { deserializer: 'PaneAxis', children: this.children.map(child => child.serialize()), orientation: this.orientation, flexScale: this.flexScale - } + }; } - getElement () { + getElement() { if (!this.element) { - this.element = new PaneAxisElement().initialize(this, this.viewRegistry) + this.element = new PaneAxisElement().initialize(this, this.viewRegistry); } - return this.element + return this.element; } - getFlexScale () { - return this.flexScale + getFlexScale() { + return this.flexScale; } - setFlexScale (flexScale) { - this.flexScale = flexScale - this.emitter.emit('did-change-flex-scale', this.flexScale) - return this.flexScale + setFlexScale(flexScale) { + this.flexScale = flexScale; + this.emitter.emit('did-change-flex-scale', this.flexScale); + return this.flexScale; } - getParent () { - return this.parent + getParent() { + return this.parent; } - setParent (parent) { - this.parent = parent - return this.parent + setParent(parent) { + this.parent = parent; + return this.parent; } - getContainer () { - return this.container + getContainer() { + return this.container; } - setContainer (container) { - if (container && (container !== this.container)) { - this.container = container - this.children.forEach(child => child.setContainer(container)) + setContainer(container) { + if (container && container !== this.container) { + this.container = container; + this.children.forEach(child => child.setContainer(container)); } } - getOrientation () { - return this.orientation + getOrientation() { + return this.orientation; } - getChildren () { - return this.children.slice() + getChildren() { + return this.children.slice(); } - getPanes () { - return flatten(this.children.map(child => child.getPanes())) + getPanes() { + return flatten(this.children.map(child => child.getPanes())); } - getItems () { - return flatten(this.children.map(child => child.getItems())) + getItems() { + return flatten(this.children.map(child => child.getItems())); } - onDidAddChild (fn) { - return this.emitter.on('did-add-child', fn) + onDidAddChild(fn) { + return this.emitter.on('did-add-child', fn); } - onDidRemoveChild (fn) { - return this.emitter.on('did-remove-child', fn) + onDidRemoveChild(fn) { + return this.emitter.on('did-remove-child', fn); } - onDidReplaceChild (fn) { - return this.emitter.on('did-replace-child', fn) + onDidReplaceChild(fn) { + return this.emitter.on('did-replace-child', fn); } - onDidDestroy (fn) { - return this.emitter.once('did-destroy', fn) + onDidDestroy(fn) { + return this.emitter.once('did-destroy', fn); } - onDidChangeFlexScale (fn) { - return this.emitter.on('did-change-flex-scale', fn) + onDidChangeFlexScale(fn) { + return this.emitter.on('did-change-flex-scale', fn); } - observeFlexScale (fn) { - fn(this.flexScale) - return this.onDidChangeFlexScale(fn) + observeFlexScale(fn) { + fn(this.flexScale); + return this.onDidChangeFlexScale(fn); } - addChild (child, index = this.children.length) { - this.children.splice(index, 0, child) - child.setParent(this) - child.setContainer(this.container) - this.subscribeToChild(child) - return this.emitter.emit('did-add-child', {child, index}) + addChild(child, index = this.children.length) { + this.children.splice(index, 0, child); + child.setParent(this); + child.setContainer(this.container); + this.subscribeToChild(child); + return this.emitter.emit('did-add-child', { child, index }); } - adjustFlexScale () { + adjustFlexScale() { // get current total flex scale of children - let total = 0 - for (var child of this.children) { total += child.getFlexScale() } + let total = 0; + for (var child of this.children) { + total += child.getFlexScale(); + } - const needTotal = this.children.length + const needTotal = this.children.length; // set every child's flex scale by the ratio for (child of this.children) { - child.setFlexScale((needTotal * child.getFlexScale()) / total) + child.setFlexScale((needTotal * child.getFlexScale()) / total); } } - removeChild (child, replacing = false) { - const index = this.children.indexOf(child) - if (index === -1) { throw new Error('Removing non-existent child') } + removeChild(child, replacing = false) { + const index = this.children.indexOf(child); + if (index === -1) { + throw new Error('Removing non-existent child'); + } - this.unsubscribeFromChild(child) + this.unsubscribeFromChild(child); - this.children.splice(index, 1) - this.adjustFlexScale() - this.emitter.emit('did-remove-child', {child, index}) + this.children.splice(index, 1); + this.adjustFlexScale(); + this.emitter.emit('did-remove-child', { child, index }); if (!replacing && this.children.length < 2) { - this.reparentLastChild() + this.reparentLastChild(); } } - replaceChild (oldChild, newChild) { - this.unsubscribeFromChild(oldChild) - this.subscribeToChild(newChild) + replaceChild(oldChild, newChild) { + this.unsubscribeFromChild(oldChild); + this.subscribeToChild(newChild); - newChild.setParent(this) - newChild.setContainer(this.container) + newChild.setParent(this); + newChild.setContainer(this.container); - const index = this.children.indexOf(oldChild) - this.children.splice(index, 1, newChild) - this.emitter.emit('did-replace-child', {oldChild, newChild, index}) + const index = this.children.indexOf(oldChild); + this.children.splice(index, 1, newChild); + this.emitter.emit('did-replace-child', { oldChild, newChild, index }); } - insertChildBefore (currentChild, newChild) { - const index = this.children.indexOf(currentChild) - return this.addChild(newChild, index) + insertChildBefore(currentChild, newChild) { + const index = this.children.indexOf(currentChild); + return this.addChild(newChild, index); } - insertChildAfter (currentChild, newChild) { - const index = this.children.indexOf(currentChild) - return this.addChild(newChild, index + 1) + insertChildAfter(currentChild, newChild) { + const index = this.children.indexOf(currentChild); + return this.addChild(newChild, index + 1); } - reparentLastChild () { - const lastChild = this.children[0] - lastChild.setFlexScale(this.flexScale) - this.parent.replaceChild(this, lastChild) - this.destroy() + reparentLastChild() { + const lastChild = this.children[0]; + lastChild.setFlexScale(this.flexScale); + this.parent.replaceChild(this, lastChild); + this.destroy(); } - subscribeToChild (child) { - const subscription = child.onDidDestroy(() => this.removeChild(child)) - this.subscriptionsByChild.set(child, subscription) - this.subscriptions.add(subscription) + subscribeToChild(child) { + const subscription = child.onDidDestroy(() => this.removeChild(child)); + this.subscriptionsByChild.set(child, subscription); + this.subscriptions.add(subscription); } - unsubscribeFromChild (child) { - const subscription = this.subscriptionsByChild.get(child) - this.subscriptions.remove(subscription) - subscription.dispose() + unsubscribeFromChild(child) { + const subscription = this.subscriptionsByChild.get(child); + this.subscriptions.remove(subscription); + subscription.dispose(); } - destroyed () { - this.subscriptions.dispose() - this.emitter.emit('did-destroy') - this.emitter.dispose() + destroyed() { + this.subscriptions.dispose(); + this.emitter.emit('did-destroy'); + this.emitter.dispose(); } } -module.exports = PaneAxis +module.exports = PaneAxis; diff --git a/src/pane-container-element.js b/src/pane-container-element.js index 7a2a88463..bff5b29c7 100644 --- a/src/pane-container-element.js +++ b/src/pane-container-element.js @@ -1,40 +1,44 @@ -const {CompositeDisposable} = require('event-kit') +const { CompositeDisposable } = require('event-kit'); class PaneContainerElement extends HTMLElement { - createdCallback () { - this.subscriptions = new CompositeDisposable() - this.classList.add('panes') + createdCallback() { + this.subscriptions = new CompositeDisposable(); + this.classList.add('panes'); } - initialize (model, {views}) { - this.model = model - this.views = views + initialize(model, { views }) { + this.model = model; + this.views = views; if (this.views == null) { - throw new Error('Must pass a views parameter when initializing PaneContainerElements') + throw new Error( + 'Must pass a views parameter when initializing PaneContainerElements' + ); } - this.subscriptions.add(this.model.observeRoot(this.rootChanged.bind(this))) - return this + this.subscriptions.add(this.model.observeRoot(this.rootChanged.bind(this))); + return this; } - rootChanged (root) { - const focusedElement = this.hasFocus() ? document.activeElement : null + rootChanged(root) { + const focusedElement = this.hasFocus() ? document.activeElement : null; if (this.firstChild != null) { - this.firstChild.remove() + this.firstChild.remove(); } if (root != null) { - const view = this.views.getView(root) - this.appendChild(view) + const view = this.views.getView(root); + this.appendChild(view); if (focusedElement != null) { - focusedElement.focus() + focusedElement.focus(); } } } - hasFocus () { - return this === document.activeElement || this.contains(document.activeElement) + hasFocus() { + return ( + this === document.activeElement || this.contains(document.activeElement) + ); } } module.exports = document.registerElement('atom-pane-container', { prototype: PaneContainerElement.prototype -}) +}); diff --git a/src/pane-container.js b/src/pane-container.js index 1185d7a9c..4cd76dcda 100644 --- a/src/pane-container.js +++ b/src/pane-container.js @@ -1,299 +1,351 @@ -const {find} = require('underscore-plus') -const {Emitter, CompositeDisposable} = require('event-kit') -const Pane = require('./pane') -const ItemRegistry = require('./item-registry') -const PaneContainerElement = require('./pane-container-element') +const { find } = require('underscore-plus'); +const { Emitter, CompositeDisposable } = require('event-kit'); +const Pane = require('./pane'); +const ItemRegistry = require('./item-registry'); +const PaneContainerElement = require('./pane-container-element'); -const SERIALIZATION_VERSION = 1 -const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100 +const SERIALIZATION_VERSION = 1; +const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100; -module.exports = -class PaneContainer { - constructor (params) { +module.exports = class PaneContainer { + constructor(params) { let applicationDelegate, deserializerManager, notificationManager; - ({config: this.config, applicationDelegate, notificationManager, deserializerManager, viewRegistry: this.viewRegistry, location: this.location} = params) - this.emitter = new Emitter() - this.subscriptions = new CompositeDisposable() - this.itemRegistry = new ItemRegistry() - this.alive = true - this.stoppedChangingActivePaneItemTimeout = null + ({ + config: this.config, + applicationDelegate, + notificationManager, + deserializerManager, + viewRegistry: this.viewRegistry, + location: this.location + } = params); + this.emitter = new Emitter(); + this.subscriptions = new CompositeDisposable(); + this.itemRegistry = new ItemRegistry(); + this.alive = true; + this.stoppedChangingActivePaneItemTimeout = null; - this.setRoot(new Pane({container: this, config: this.config, applicationDelegate, notificationManager, deserializerManager, viewRegistry: this.viewRegistry})) - this.didActivatePane(this.getRoot()) + this.setRoot( + new Pane({ + container: this, + config: this.config, + applicationDelegate, + notificationManager, + deserializerManager, + viewRegistry: this.viewRegistry + }) + ); + this.didActivatePane(this.getRoot()); } - getLocation () { return this.location } - - getElement () { - return this.element != null ? this.element : (this.element = new PaneContainerElement().initialize(this, {views: this.viewRegistry})) + getLocation() { + return this.location; } - destroy () { - this.alive = false - for (let pane of this.getRoot().getPanes()) { pane.destroy() } - this.cancelStoppedChangingActivePaneItemTimeout() - this.subscriptions.dispose() - this.emitter.dispose() + getElement() { + return this.element != null + ? this.element + : (this.element = new PaneContainerElement().initialize(this, { + views: this.viewRegistry + })); } - isAlive () { return this.alive } + destroy() { + this.alive = false; + for (let pane of this.getRoot().getPanes()) { + pane.destroy(); + } + this.cancelStoppedChangingActivePaneItemTimeout(); + this.subscriptions.dispose(); + this.emitter.dispose(); + } - isDestroyed () { return !this.isAlive() } + isAlive() { + return this.alive; + } - serialize (params) { + isDestroyed() { + return !this.isAlive(); + } + + serialize(params) { return { deserializer: 'PaneContainer', version: SERIALIZATION_VERSION, root: this.root ? this.root.serialize() : null, activePaneId: this.activePane.id + }; + } + + deserialize(state, deserializerManager) { + if (state.version !== SERIALIZATION_VERSION) return; + this.itemRegistry = new ItemRegistry(); + this.setRoot(deserializerManager.deserialize(state.root)); + this.activePane = + find(this.getRoot().getPanes(), pane => pane.id === state.activePaneId) || + this.getPanes()[0]; + if (this.config.get('core.destroyEmptyPanes')) this.destroyEmptyPanes(); + } + + onDidChangeRoot(fn) { + return this.emitter.on('did-change-root', fn); + } + + observeRoot(fn) { + fn(this.getRoot()); + return this.onDidChangeRoot(fn); + } + + onDidAddPane(fn) { + return this.emitter.on('did-add-pane', fn); + } + + observePanes(fn) { + for (let pane of this.getPanes()) { + fn(pane); + } + return this.onDidAddPane(({ pane }) => fn(pane)); + } + + onDidDestroyPane(fn) { + return this.emitter.on('did-destroy-pane', fn); + } + + onWillDestroyPane(fn) { + return this.emitter.on('will-destroy-pane', fn); + } + + onDidChangeActivePane(fn) { + return this.emitter.on('did-change-active-pane', fn); + } + + onDidActivatePane(fn) { + return this.emitter.on('did-activate-pane', fn); + } + + observeActivePane(fn) { + fn(this.getActivePane()); + return this.onDidChangeActivePane(fn); + } + + onDidAddPaneItem(fn) { + return this.emitter.on('did-add-pane-item', fn); + } + + observePaneItems(fn) { + for (let item of this.getPaneItems()) { + fn(item); + } + return this.onDidAddPaneItem(({ item }) => fn(item)); + } + + onDidChangeActivePaneItem(fn) { + return this.emitter.on('did-change-active-pane-item', fn); + } + + onDidStopChangingActivePaneItem(fn) { + return this.emitter.on('did-stop-changing-active-pane-item', fn); + } + + observeActivePaneItem(fn) { + fn(this.getActivePaneItem()); + return this.onDidChangeActivePaneItem(fn); + } + + onWillDestroyPaneItem(fn) { + return this.emitter.on('will-destroy-pane-item', fn); + } + + onDidDestroyPaneItem(fn) { + return this.emitter.on('did-destroy-pane-item', fn); + } + + getRoot() { + return this.root; + } + + setRoot(root) { + this.root = root; + this.root.setParent(this); + this.root.setContainer(this); + this.emitter.emit('did-change-root', this.root); + if (this.getActivePane() == null && this.root instanceof Pane) { + this.didActivatePane(this.root); } } - deserialize (state, deserializerManager) { - if (state.version !== SERIALIZATION_VERSION) return - this.itemRegistry = new ItemRegistry() - this.setRoot(deserializerManager.deserialize(state.root)) - this.activePane = find(this.getRoot().getPanes(), pane => pane.id === state.activePaneId) || this.getPanes()[0] - if (this.config.get('core.destroyEmptyPanes')) this.destroyEmptyPanes() - } - - onDidChangeRoot (fn) { - return this.emitter.on('did-change-root', fn) - } - - observeRoot (fn) { - fn(this.getRoot()) - return this.onDidChangeRoot(fn) - } - - onDidAddPane (fn) { - return this.emitter.on('did-add-pane', fn) - } - - observePanes (fn) { - for (let pane of this.getPanes()) { fn(pane) } - return this.onDidAddPane(({pane}) => fn(pane)) - } - - onDidDestroyPane (fn) { - return this.emitter.on('did-destroy-pane', fn) - } - - onWillDestroyPane (fn) { - return this.emitter.on('will-destroy-pane', fn) - } - - onDidChangeActivePane (fn) { - return this.emitter.on('did-change-active-pane', fn) - } - - onDidActivatePane (fn) { - return this.emitter.on('did-activate-pane', fn) - } - - observeActivePane (fn) { - fn(this.getActivePane()) - return this.onDidChangeActivePane(fn) - } - - onDidAddPaneItem (fn) { - return this.emitter.on('did-add-pane-item', fn) - } - - observePaneItems (fn) { - for (let item of this.getPaneItems()) { fn(item) } - return this.onDidAddPaneItem(({item}) => fn(item)) - } - - onDidChangeActivePaneItem (fn) { - return this.emitter.on('did-change-active-pane-item', fn) - } - - onDidStopChangingActivePaneItem (fn) { - return this.emitter.on('did-stop-changing-active-pane-item', fn) - } - - observeActivePaneItem (fn) { - fn(this.getActivePaneItem()) - return this.onDidChangeActivePaneItem(fn) - } - - onWillDestroyPaneItem (fn) { - return this.emitter.on('will-destroy-pane-item', fn) - } - - onDidDestroyPaneItem (fn) { - return this.emitter.on('did-destroy-pane-item', fn) - } - - getRoot () { return this.root } - - setRoot (root) { - this.root = root - this.root.setParent(this) - this.root.setContainer(this) - this.emitter.emit('did-change-root', this.root) - if ((this.getActivePane() == null) && this.root instanceof Pane) { - this.didActivatePane(this.root) + replaceChild(oldChild, newChild) { + if (oldChild !== this.root) { + throw new Error('Replacing non-existent child'); } + this.setRoot(newChild); } - replaceChild (oldChild, newChild) { - if (oldChild !== this.root) { throw new Error('Replacing non-existent child') } - this.setRoot(newChild) - } - - getPanes () { + getPanes() { if (this.alive) { - return this.getRoot().getPanes() + return this.getRoot().getPanes(); } else { - return [] + return []; } } - getPaneItems () { - return this.getRoot().getItems() + getPaneItems() { + return this.getRoot().getItems(); } - getActivePane () { - return this.activePane + getActivePane() { + return this.activePane; } - getActivePaneItem () { - return this.getActivePane().getActiveItem() + getActivePaneItem() { + return this.getActivePane().getActiveItem(); } - paneForURI (uri) { - return find(this.getPanes(), pane => pane.itemForURI(uri) != null) + paneForURI(uri) { + return find(this.getPanes(), pane => pane.itemForURI(uri) != null); } - paneForItem (item) { - return find(this.getPanes(), pane => pane.getItems().includes(item)) + paneForItem(item) { + return find(this.getPanes(), pane => pane.getItems().includes(item)); } - saveAll () { - for (let pane of this.getPanes()) { pane.saveItems() } + saveAll() { + for (let pane of this.getPanes()) { + pane.saveItems(); + } } - confirmClose (options) { - const promises = [] + confirmClose(options) { + const promises = []; for (const pane of this.getPanes()) { for (const item of pane.getItems()) { - promises.push(pane.promptToSaveItem(item, options)) + promises.push(pane.promptToSaveItem(item, options)); } } - return Promise.all(promises).then((results) => !results.includes(false)) + return Promise.all(promises).then(results => !results.includes(false)); } - activateNextPane () { - const panes = this.getPanes() + activateNextPane() { + const panes = this.getPanes(); if (panes.length > 1) { - const currentIndex = panes.indexOf(this.activePane) - const nextIndex = (currentIndex + 1) % panes.length - panes[nextIndex].activate() - return true + const currentIndex = panes.indexOf(this.activePane); + const nextIndex = (currentIndex + 1) % panes.length; + panes[nextIndex].activate(); + return true; } else { - return false + return false; } } - activatePreviousPane () { - const panes = this.getPanes() + activatePreviousPane() { + const panes = this.getPanes(); if (panes.length > 1) { - const currentIndex = panes.indexOf(this.activePane) - let previousIndex = currentIndex - 1 - if (previousIndex < 0) { previousIndex = panes.length - 1 } - panes[previousIndex].activate() - return true + const currentIndex = panes.indexOf(this.activePane); + let previousIndex = currentIndex - 1; + if (previousIndex < 0) { + previousIndex = panes.length - 1; + } + panes[previousIndex].activate(); + return true; } else { - return false + return false; } } - moveActiveItemToPane (destPane) { - const item = this.activePane.getActiveItem() + moveActiveItemToPane(destPane) { + const item = this.activePane.getActiveItem(); - if (!destPane.isItemAllowed(item)) { return } + if (!destPane.isItemAllowed(item)) { + return; + } - this.activePane.moveItemToPane(item, destPane) - destPane.setActiveItem(item) + this.activePane.moveItemToPane(item, destPane); + destPane.setActiveItem(item); } - copyActiveItemToPane (destPane) { - const item = this.activePane.copyActiveItem() + copyActiveItemToPane(destPane) { + const item = this.activePane.copyActiveItem(); if (item && destPane.isItemAllowed(item)) { - destPane.activateItem(item) + destPane.activateItem(item); } } - destroyEmptyPanes () { - for (let pane of this.getPanes()) { if (pane.items.length === 0) { pane.destroy() } } + destroyEmptyPanes() { + for (let pane of this.getPanes()) { + if (pane.items.length === 0) { + pane.destroy(); + } + } } - didAddPane (event) { - this.emitter.emit('did-add-pane', event) - const items = event.pane.getItems() + didAddPane(event) { + this.emitter.emit('did-add-pane', event); + const items = event.pane.getItems(); for (let i = 0, length = items.length; i < length; i++) { - const item = items[i] - this.didAddPaneItem(item, event.pane, i) + const item = items[i]; + this.didAddPaneItem(item, event.pane, i); } } - willDestroyPane (event) { - this.emitter.emit('will-destroy-pane', event) + willDestroyPane(event) { + this.emitter.emit('will-destroy-pane', event); } - didDestroyPane (event) { - this.emitter.emit('did-destroy-pane', event) + didDestroyPane(event) { + this.emitter.emit('did-destroy-pane', event); } - didActivatePane (activePane) { + didActivatePane(activePane) { if (activePane !== this.activePane) { if (!this.getPanes().includes(activePane)) { - throw new Error('Setting active pane that is not present in pane container') + throw new Error( + 'Setting active pane that is not present in pane container' + ); } - this.activePane = activePane - this.emitter.emit('did-change-active-pane', this.activePane) - this.didChangeActiveItemOnPane(this.activePane, this.activePane.getActiveItem()) + this.activePane = activePane; + this.emitter.emit('did-change-active-pane', this.activePane); + this.didChangeActiveItemOnPane( + this.activePane, + this.activePane.getActiveItem() + ); } - this.emitter.emit('did-activate-pane', this.activePane) - return this.activePane + this.emitter.emit('did-activate-pane', this.activePane); + return this.activePane; } - didAddPaneItem (item, pane, index) { - this.itemRegistry.addItem(item) - this.emitter.emit('did-add-pane-item', {item, pane, index}) + didAddPaneItem(item, pane, index) { + this.itemRegistry.addItem(item); + this.emitter.emit('did-add-pane-item', { item, pane, index }); } - willDestroyPaneItem (event) { - return this.emitter.emitAsync('will-destroy-pane-item', event) + willDestroyPaneItem(event) { + return this.emitter.emitAsync('will-destroy-pane-item', event); } - didDestroyPaneItem (event) { - this.itemRegistry.removeItem(event.item) - this.emitter.emit('did-destroy-pane-item', event) + didDestroyPaneItem(event) { + this.itemRegistry.removeItem(event.item); + this.emitter.emit('did-destroy-pane-item', event); } - didChangeActiveItemOnPane (pane, activeItem) { + didChangeActiveItemOnPane(pane, activeItem) { if (pane === this.getActivePane()) { - this.emitter.emit('did-change-active-pane-item', activeItem) + this.emitter.emit('did-change-active-pane-item', activeItem); - this.cancelStoppedChangingActivePaneItemTimeout() + this.cancelStoppedChangingActivePaneItemTimeout(); // `setTimeout()` isn't available during the snapshotting phase, but that's okay. if (!global.isGeneratingSnapshot) { this.stoppedChangingActivePaneItemTimeout = setTimeout(() => { - this.stoppedChangingActivePaneItemTimeout = null - this.emitter.emit('did-stop-changing-active-pane-item', activeItem) - }, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY) + this.stoppedChangingActivePaneItemTimeout = null; + this.emitter.emit('did-stop-changing-active-pane-item', activeItem); + }, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY); } } } - cancelStoppedChangingActivePaneItemTimeout () { + cancelStoppedChangingActivePaneItemTimeout() { if (this.stoppedChangingActivePaneItemTimeout != null) { - clearTimeout(this.stoppedChangingActivePaneItemTimeout) + clearTimeout(this.stoppedChangingActivePaneItemTimeout); } } -} +}; diff --git a/src/pane-element.js b/src/pane-element.js index 1ef9ce17e..07bf5a85b 100644 --- a/src/pane-element.js +++ b/src/pane-element.js @@ -1,35 +1,35 @@ -const path = require('path') -const {CompositeDisposable} = require('event-kit') +const path = require('path'); +const { CompositeDisposable } = require('event-kit'); class PaneElement extends HTMLElement { - createdCallback () { - this.attached = false - this.subscriptions = new CompositeDisposable() - this.inlineDisplayStyles = new WeakMap() - this.initializeContent() - this.subscribeToDOMEvents() + createdCallback() { + this.attached = false; + this.subscriptions = new CompositeDisposable(); + this.inlineDisplayStyles = new WeakMap(); + this.initializeContent(); + this.subscribeToDOMEvents(); } - attachedCallback () { - this.attached = true + attachedCallback() { + this.attached = true; if (this.model.isFocused()) { - this.focus() + this.focus(); } } - detachedCallback () { - this.attached = false + detachedCallback() { + this.attached = false; } - initializeContent () { - this.setAttribute('class', 'pane') - this.setAttribute('tabindex', -1) - this.itemViews = document.createElement('div') - this.appendChild(this.itemViews) - this.itemViews.setAttribute('class', 'item-views') + initializeContent() { + this.setAttribute('class', 'pane'); + this.setAttribute('tabindex', -1); + this.itemViews = document.createElement('div'); + this.appendChild(this.itemViews); + this.itemViews.setAttribute('class', 'item-views'); } - subscribeToDOMEvents () { + subscribeToDOMEvents() { const handleFocus = event => { if ( !( @@ -38,181 +38,181 @@ class PaneElement extends HTMLElement { this.contains(event.relatedTarget) ) ) { - this.model.focus() + this.model.focus(); } - if (event.target !== this) return - const view = this.getActiveView() + if (event.target !== this) return; + const view = this.getActiveView(); if (view) { - view.focus() - event.stopPropagation() + view.focus(); + event.stopPropagation(); } - } + }; const handleBlur = event => { if (!this.contains(event.relatedTarget)) { - this.model.blur() + this.model.blur(); } - } + }; const handleDragOver = event => { - event.preventDefault() - event.stopPropagation() - } + event.preventDefault(); + event.stopPropagation(); + }; const handleDrop = event => { - event.preventDefault() - event.stopPropagation() - this.getModel().activate() - const pathsToOpen = [...event.dataTransfer.files].map(file => file.path) + event.preventDefault(); + event.stopPropagation(); + this.getModel().activate(); + const pathsToOpen = [...event.dataTransfer.files].map(file => file.path); if (pathsToOpen.length > 0) { - this.applicationDelegate.open({pathsToOpen, here: true}) + this.applicationDelegate.open({ pathsToOpen, here: true }); } - } - this.addEventListener('focus', handleFocus, true) - this.addEventListener('blur', handleBlur, true) - this.addEventListener('dragover', handleDragOver) - this.addEventListener('drop', handleDrop) + }; + this.addEventListener('focus', handleFocus, true); + this.addEventListener('blur', handleBlur, true); + this.addEventListener('dragover', handleDragOver); + this.addEventListener('drop', handleDrop); } - initialize (model, {views, applicationDelegate}) { - this.model = model - this.views = views - this.applicationDelegate = applicationDelegate + initialize(model, { views, applicationDelegate }) { + this.model = model; + this.views = views; + this.applicationDelegate = applicationDelegate; if (this.views == null) { throw new Error( 'Must pass a views parameter when initializing PaneElements' - ) + ); } if (this.applicationDelegate == null) { throw new Error( 'Must pass an applicationDelegate parameter when initializing PaneElements' - ) + ); } - this.subscriptions.add(this.model.onDidActivate(this.activated.bind(this))) + this.subscriptions.add(this.model.onDidActivate(this.activated.bind(this))); this.subscriptions.add( this.model.observeActive(this.activeStatusChanged.bind(this)) - ) + ); this.subscriptions.add( this.model.observeActiveItem(this.activeItemChanged.bind(this)) - ) + ); this.subscriptions.add( this.model.onDidRemoveItem(this.itemRemoved.bind(this)) - ) + ); this.subscriptions.add( this.model.onDidDestroy(this.paneDestroyed.bind(this)) - ) + ); this.subscriptions.add( this.model.observeFlexScale(this.flexScaleChanged.bind(this)) - ) - return this + ); + return this; } - getModel () { - return this.model + getModel() { + return this.model; } - activated () { - this.isActivating = true + activated() { + this.isActivating = true; if (!this.hasFocus()) { // Don't steal focus from children. - this.focus() + this.focus(); } - this.isActivating = false + this.isActivating = false; } - activeStatusChanged (active) { + activeStatusChanged(active) { if (active) { - this.classList.add('active') + this.classList.add('active'); } else { - this.classList.remove('active') + this.classList.remove('active'); } } - activeItemChanged (item) { - delete this.dataset.activeItemName - delete this.dataset.activeItemPath + activeItemChanged(item) { + delete this.dataset.activeItemName; + delete this.dataset.activeItemPath; if (this.changePathDisposable != null) { - this.changePathDisposable.dispose() + this.changePathDisposable.dispose(); } if (item == null) { - return + return; } - const hasFocus = this.hasFocus() - const itemView = this.views.getView(item) - const itemPath = typeof item.getPath === 'function' ? item.getPath() : null + const hasFocus = this.hasFocus(); + const itemView = this.views.getView(item); + const itemPath = typeof item.getPath === 'function' ? item.getPath() : null; if (itemPath) { - this.dataset.activeItemName = path.basename(itemPath) - this.dataset.activeItemPath = itemPath + this.dataset.activeItemName = path.basename(itemPath); + this.dataset.activeItemPath = itemPath; if (item.onDidChangePath != null) { this.changePathDisposable = item.onDidChangePath(() => { - const itemPath = item.getPath() - this.dataset.activeItemName = path.basename(itemPath) - this.dataset.activeItemPath = itemPath - }) + const itemPath = item.getPath(); + this.dataset.activeItemName = path.basename(itemPath); + this.dataset.activeItemPath = itemPath; + }); } } if (!this.itemViews.contains(itemView)) { - this.itemViews.appendChild(itemView) + this.itemViews.appendChild(itemView); } for (const child of this.itemViews.children) { if (child === itemView) { if (this.attached) { - this.showItemView(child) + this.showItemView(child); } } else { - this.hideItemView(child) + this.hideItemView(child); } } if (hasFocus) { - itemView.focus() + itemView.focus(); } } - showItemView (itemView) { - const inlineDisplayStyle = this.inlineDisplayStyles.get(itemView) + showItemView(itemView) { + const inlineDisplayStyle = this.inlineDisplayStyles.get(itemView); if (inlineDisplayStyle != null) { - itemView.style.display = inlineDisplayStyle + itemView.style.display = inlineDisplayStyle; } else { - itemView.style.display = '' + itemView.style.display = ''; } } - hideItemView (itemView) { - const inlineDisplayStyle = itemView.style.display + hideItemView(itemView) { + const inlineDisplayStyle = itemView.style.display; if (inlineDisplayStyle !== 'none') { if (inlineDisplayStyle != null) { - this.inlineDisplayStyles.set(itemView, inlineDisplayStyle) + this.inlineDisplayStyles.set(itemView, inlineDisplayStyle); } - itemView.style.display = 'none' + itemView.style.display = 'none'; } } - itemRemoved ({item, index, destroyed}) { - const viewToRemove = this.views.getView(item) + itemRemoved({ item, index, destroyed }) { + const viewToRemove = this.views.getView(item); if (viewToRemove) { - viewToRemove.remove() + viewToRemove.remove(); } } - paneDestroyed () { - this.subscriptions.dispose() + paneDestroyed() { + this.subscriptions.dispose(); if (this.changePathDisposable != null) { - this.changePathDisposable.dispose() + this.changePathDisposable.dispose(); } } - flexScaleChanged (flexScale) { - this.style.flexGrow = flexScale + flexScaleChanged(flexScale) { + this.style.flexGrow = flexScale; } - getActiveView () { - return this.views.getView(this.model.getActiveItem()) + getActiveView() { + return this.views.getView(this.model.getActiveItem()); } - hasFocus () { + hasFocus() { return ( this === document.activeElement || this.contains(document.activeElement) - ) + ); } } module.exports = document.registerElement('atom-pane', { prototype: PaneElement.prototype -}) +}); diff --git a/src/pane.js b/src/pane.js index ac36f39f7..431ab91c4 100644 --- a/src/pane.js +++ b/src/pane.js @@ -1,10 +1,10 @@ -const Grim = require('grim') -const {CompositeDisposable, Emitter} = require('event-kit') -const PaneAxis = require('./pane-axis') -const TextEditor = require('./text-editor') -const PaneElement = require('./pane-element') +const Grim = require('grim'); +const { CompositeDisposable, Emitter } = require('event-kit'); +const PaneAxis = require('./pane-axis'); +const TextEditor = require('./text-editor'); +const PaneElement = require('./pane-element'); -let nextInstanceId = 1 +let nextInstanceId = 1; class SaveCancelledError extends Error {} @@ -17,96 +17,108 @@ class SaveCancelledError extends Error {} // to a pane, it will replace the currently pending item, if any, instead of // simply being added. In the default configuration, the text in the tab for // pending items is shown in italics. -module.exports = -class Pane { - inspect () { - return `Pane ${this.id}` +module.exports = class Pane { + inspect() { + return `Pane ${this.id}`; } - static deserialize (state, {deserializers, applicationDelegate, config, notifications, views}) { - const {activeItemIndex} = state - const activeItemURI = state.activeItemURI || state.activeItemUri + static deserialize( + state, + { deserializers, applicationDelegate, config, notifications, views } + ) { + const { activeItemIndex } = state; + const activeItemURI = state.activeItemURI || state.activeItemUri; - const items = [] + const items = []; for (const itemState of state.items) { - const item = deserializers.deserialize(itemState) - if (item) items.push(item) + const item = deserializers.deserialize(itemState); + if (item) items.push(item); } - state.items = items + state.items = items; - state.activeItem = items[activeItemIndex] + state.activeItem = items[activeItemIndex]; if (!state.activeItem && activeItemURI) { - state.activeItem = state.items.find((item) => - typeof item.getURI === 'function' && item.getURI() === activeItemURI - ) + state.activeItem = state.items.find( + item => + typeof item.getURI === 'function' && item.getURI() === activeItemURI + ); } - return new Pane(Object.assign({ - deserializerManager: deserializers, - notificationManager: notifications, - viewRegistry: views, - config, - applicationDelegate - }, state)) + return new Pane( + Object.assign( + { + deserializerManager: deserializers, + notificationManager: notifications, + viewRegistry: views, + config, + applicationDelegate + }, + state + ) + ); } - constructor (params = {}) { - this.setPendingItem = this.setPendingItem.bind(this) - this.getPendingItem = this.getPendingItem.bind(this) - this.clearPendingItem = this.clearPendingItem.bind(this) - this.onItemDidTerminatePendingState = this.onItemDidTerminatePendingState.bind(this) - this.saveItem = this.saveItem.bind(this) - this.saveItemAs = this.saveItemAs.bind(this) + constructor(params = {}) { + this.setPendingItem = this.setPendingItem.bind(this); + this.getPendingItem = this.getPendingItem.bind(this); + this.clearPendingItem = this.clearPendingItem.bind(this); + this.onItemDidTerminatePendingState = this.onItemDidTerminatePendingState.bind( + this + ); + this.saveItem = this.saveItem.bind(this); + this.saveItemAs = this.saveItemAs.bind(this); - this.id = params.id + this.id = params.id; if (this.id != null) { - nextInstanceId = Math.max(nextInstanceId, this.id + 1) + nextInstanceId = Math.max(nextInstanceId, this.id + 1); } else { - this.id = nextInstanceId++ + this.id = nextInstanceId++; } - this.activeItem = params.activeItem - this.focused = params.focused != null ? params.focused : false - this.applicationDelegate = params.applicationDelegate - this.notificationManager = params.notificationManager - this.config = params.config - this.deserializerManager = params.deserializerManager - this.viewRegistry = params.viewRegistry + this.activeItem = params.activeItem; + this.focused = params.focused != null ? params.focused : false; + this.applicationDelegate = params.applicationDelegate; + this.notificationManager = params.notificationManager; + this.config = params.config; + this.deserializerManager = params.deserializerManager; + this.viewRegistry = params.viewRegistry; - this.emitter = new Emitter() - this.alive = true - this.subscriptionsPerItem = new WeakMap() - this.items = [] - this.itemStack = [] - this.container = null + this.emitter = new Emitter(); + this.alive = true; + this.subscriptionsPerItem = new WeakMap(); + this.items = []; + this.itemStack = []; + this.container = null; - this.addItems((params.items || []).filter(item => item)) - if (!this.getActiveItem()) this.setActiveItem(this.items[0]) - this.addItemsToStack(params.itemStackIndices || []) - this.setFlexScale(params.flexScale || 1) + this.addItems((params.items || []).filter(item => item)); + if (!this.getActiveItem()) this.setActiveItem(this.items[0]); + this.addItemsToStack(params.itemStackIndices || []); + this.setFlexScale(params.flexScale || 1); } - getElement () { + getElement() { if (!this.element) { - this.element = new PaneElement().initialize( - this, - {views: this.viewRegistry, applicationDelegate: this.applicationDelegate} - ) + this.element = new PaneElement().initialize(this, { + views: this.viewRegistry, + applicationDelegate: this.applicationDelegate + }); } - return this.element + return this.element; } - serialize () { - const itemsToBeSerialized = this.items.filter(item => item && typeof item.serialize === 'function') + serialize() { + const itemsToBeSerialized = this.items.filter( + item => item && typeof item.serialize === 'function' + ); - const itemStackIndices = [] + const itemStackIndices = []; for (const item of this.itemStack) { if (typeof item.serialize === 'function') { - itemStackIndices.push(itemsToBeSerialized.indexOf(item)) + itemStackIndices.push(itemsToBeSerialized.indexOf(item)); } } - const activeItemIndex = itemsToBeSerialized.indexOf(this.activeItem) + const activeItemIndex = itemsToBeSerialized.indexOf(this.activeItem); return { deserializer: 'Pane', @@ -116,21 +128,25 @@ class Pane { activeItemIndex, focused: this.focused, flexScale: this.flexScale - } + }; } - getParent () { return this.parent } - - setParent (parent) { - this.parent = parent + getParent() { + return this.parent; } - getContainer () { return this.container } + setParent(parent) { + this.parent = parent; + } - setContainer (container) { + getContainer() { + return this.container; + } + + setContainer(container) { if (container && container !== this.container) { - this.container = container - container.didAddPane({pane: this}) + this.container = container; + container.didAddPane({ pane: this }); } } @@ -139,31 +155,35 @@ class Pane { // * `item` the Item // // Returns a {Boolean}. - isItemAllowed (item) { + isItemAllowed(item) { if (typeof item.getAllowedLocations !== 'function') { - return true + return true; } else { - return item.getAllowedLocations().includes(this.getContainer().getLocation()) + return item + .getAllowedLocations() + .includes(this.getContainer().getLocation()); } } - setFlexScale (flexScale) { - this.flexScale = flexScale - this.emitter.emit('did-change-flex-scale', this.flexScale) - return this.flexScale + setFlexScale(flexScale) { + this.flexScale = flexScale; + this.emitter.emit('did-change-flex-scale', this.flexScale); + return this.flexScale; } - getFlexScale () { return this.flexScale } + getFlexScale() { + return this.flexScale; + } - increaseSize () { + increaseSize() { if (this.getContainer().getPanes().length > 1) { - this.setFlexScale(this.getFlexScale() * 1.1) + this.setFlexScale(this.getFlexScale() * 1.1); } } - decreaseSize () { + decreaseSize() { if (this.getContainer().getPanes().length > 1) { - this.setFlexScale(this.getFlexScale() / 1.1) + this.setFlexScale(this.getFlexScale() / 1.1); } } @@ -181,8 +201,8 @@ class Pane { // flex item to grow if necessary. // // Returns a {Disposable} on which '.dispose()' can be called to unsubscribe. - onDidChangeFlexScale (callback) { - return this.emitter.on('did-change-flex-scale', callback) + onDidChangeFlexScale(callback) { + return this.emitter.on('did-change-flex-scale', callback); } // Public: Invoke the given callback with the current and future values of @@ -194,9 +214,9 @@ class Pane { // flex item to grow if necessary. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeFlexScale (callback) { - callback(this.flexScale) - return this.onDidChangeFlexScale(callback) + observeFlexScale(callback) { + callback(this.flexScale); + return this.onDidChangeFlexScale(callback); } // Public: Invoke the given callback when the pane is activated. @@ -207,8 +227,8 @@ class Pane { // * `callback` {Function} to be called when the pane is activated. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidActivate (callback) { - return this.emitter.on('did-activate', callback) + onDidActivate(callback) { + return this.emitter.on('did-activate', callback); } // Public: Invoke the given callback before the pane is destroyed. @@ -216,8 +236,8 @@ class Pane { // * `callback` {Function} to be called before the pane is destroyed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillDestroy (callback) { - return this.emitter.on('will-destroy', callback) + onWillDestroy(callback) { + return this.emitter.on('will-destroy', callback); } // Public: Invoke the given callback when the pane is destroyed. @@ -225,8 +245,8 @@ class Pane { // * `callback` {Function} to be called when the pane is destroyed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } // Public: Invoke the given callback when the value of the {::isActive} @@ -237,11 +257,11 @@ class Pane { // * `active` {Boolean} indicating whether the pane is active. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActive (callback) { + onDidChangeActive(callback) { return this.container.onDidChangeActivePane(activePane => { - const isActive = this === activePane - callback(isActive) - }) + const isActive = this === activePane; + callback(isActive); + }); } // Public: Invoke the given callback with the current and future values of the @@ -252,9 +272,9 @@ class Pane { // * `active` {Boolean} indicating whether the pane is active. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActive (callback) { - callback(this.isActive()) - return this.onDidChangeActive(callback) + observeActive(callback) { + callback(this.isActive()); + return this.onDidChangeActive(callback); } // Public: Invoke the given callback when an item is added to the pane. @@ -265,8 +285,8 @@ class Pane { // * `index` {Number} indicating where the item is located. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddItem (callback) { - return this.emitter.on('did-add-item', callback) + onDidAddItem(callback) { + return this.emitter.on('did-add-item', callback); } // Public: Invoke the given callback when an item is removed from the pane. @@ -277,8 +297,8 @@ class Pane { // * `index` {Number} indicating where the item was located. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidRemoveItem (callback) { - return this.emitter.on('did-remove-item', callback) + onDidRemoveItem(callback) { + return this.emitter.on('did-remove-item', callback); } // Public: Invoke the given callback before an item is removed from the pane. @@ -287,8 +307,8 @@ class Pane { // * `event` {Object} with the following keys: // * `item` The pane item to be removed. // * `index` {Number} indicating where the item is located. - onWillRemoveItem (callback) { - return this.emitter.on('will-remove-item', callback) + onWillRemoveItem(callback) { + return this.emitter.on('will-remove-item', callback); } // Public: Invoke the given callback when an item is moved within the pane. @@ -300,8 +320,8 @@ class Pane { // * `newIndex` {Number} indicating where the item is now located. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidMoveItem (callback) { - return this.emitter.on('did-move-item', callback) + onDidMoveItem(callback) { + return this.emitter.on('did-move-item', callback); } // Public: Invoke the given callback with all current and future items. @@ -311,11 +331,11 @@ class Pane { // subscription or that is added at some later time. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeItems (callback) { + observeItems(callback) { for (let item of this.getItems()) { - callback(item) + callback(item); } - return this.onDidAddItem(({item}) => callback(item)) + return this.onDidAddItem(({ item }) => callback(item)); } // Public: Invoke the given callback when the value of {::getActiveItem} @@ -325,8 +345,8 @@ class Pane { // * `activeItem` The current active item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActiveItem (callback) { - return this.emitter.on('did-change-active-item', callback) + onDidChangeActiveItem(callback) { + return this.emitter.on('did-change-active-item', callback); } // Public: Invoke the given callback when {::activateNextRecentlyUsedItem} @@ -337,8 +357,8 @@ class Pane { // * `nextRecentlyUsedItem` The next MRU item, now being set active // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onChooseNextMRUItem (callback) { - return this.emitter.on('choose-next-mru-item', callback) + onChooseNextMRUItem(callback) { + return this.emitter.on('choose-next-mru-item', callback); } // Public: Invoke the given callback when {::activatePreviousRecentlyUsedItem} @@ -349,8 +369,8 @@ class Pane { // * `previousRecentlyUsedItem` The previous MRU item, now being set active // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onChooseLastMRUItem (callback) { - return this.emitter.on('choose-last-mru-item', callback) + onChooseLastMRUItem(callback) { + return this.emitter.on('choose-last-mru-item', callback); } // Public: Invoke the given callback when {::moveActiveItemToTopOfStack} @@ -361,8 +381,8 @@ class Pane { // * `callback` {Function} to be called with when the MRU traversal is done. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDoneChoosingMRUItem (callback) { - return this.emitter.on('done-choosing-mru-item', callback) + onDoneChoosingMRUItem(callback) { + return this.emitter.on('done-choosing-mru-item', callback); } // Public: Invoke the given callback with the current and future values of @@ -373,9 +393,9 @@ class Pane { // * `activeItem` The current active item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActiveItem (callback) { - callback(this.getActiveItem()) - return this.onDidChangeActiveItem(callback) + observeActiveItem(callback) { + callback(this.getActiveItem()); + return this.onDidChangeActiveItem(callback); } // Public: Invoke the given callback before items are destroyed. @@ -387,30 +407,34 @@ class Pane { // // Returns a {Disposable} on which `.dispose()` can be called to // unsubscribe. - onWillDestroyItem (callback) { - return this.emitter.on('will-destroy-item', callback) + onWillDestroyItem(callback) { + return this.emitter.on('will-destroy-item', callback); } // Called by the view layer to indicate that the pane has gained focus. - focus () { - return this.activate() + focus() { + return this.activate(); } // Called by the view layer to indicate that the pane has lost focus. - blur () { - this.focused = false - return true // if this is called from an event handler, don't cancel it + blur() { + this.focused = false; + return true; // if this is called from an event handler, don't cancel it } - isFocused () { return this.focused } + isFocused() { + return this.focused; + } - getPanes () { return [this] } + getPanes() { + return [this]; + } - unsubscribeFromItem (item) { - const subscription = this.subscriptionsPerItem.get(item) + unsubscribeFromItem(item) { + const subscription = this.subscriptionsPerItem.get(item); if (subscription) { - subscription.dispose() - this.subscriptionsPerItem.delete(item) + subscription.dispose(); + this.subscriptionsPerItem.delete(item); } } @@ -421,50 +445,58 @@ class Pane { // Public: Get the items in this pane. // // Returns an {Array} of items. - getItems () { - return this.items.slice() + getItems() { + return this.items.slice(); } // Public: Get the active pane item in this pane. // // Returns a pane item. - getActiveItem () { return this.activeItem } + getActiveItem() { + return this.activeItem; + } - setActiveItem (activeItem, options) { - const modifyStack = options && options.modifyStack + setActiveItem(activeItem, options) { + const modifyStack = options && options.modifyStack; if (activeItem !== this.activeItem) { - if (modifyStack !== false) this.addItemToStack(activeItem) - this.activeItem = activeItem - this.emitter.emit('did-change-active-item', this.activeItem) - if (this.container) this.container.didChangeActiveItemOnPane(this, this.activeItem) + if (modifyStack !== false) this.addItemToStack(activeItem); + this.activeItem = activeItem; + this.emitter.emit('did-change-active-item', this.activeItem); + if (this.container) + this.container.didChangeActiveItemOnPane(this, this.activeItem); } - return this.activeItem + return this.activeItem; } // Build the itemStack after deserializing - addItemsToStack (itemStackIndices) { + addItemsToStack(itemStackIndices) { if (this.items.length > 0) { - if (itemStackIndices.length !== this.items.length || itemStackIndices.includes(-1)) { - itemStackIndices = this.items.map((item, i) => i) + if ( + itemStackIndices.length !== this.items.length || + itemStackIndices.includes(-1) + ) { + itemStackIndices = this.items.map((item, i) => i); } for (let itemIndex of itemStackIndices) { - this.addItemToStack(this.items[itemIndex]) + this.addItemToStack(this.items[itemIndex]); } } } // Add item (or move item) to the end of the itemStack - addItemToStack (newItem) { - if (newItem == null) { return } - const index = this.itemStack.indexOf(newItem) - if (index !== -1) this.itemStack.splice(index, 1) - return this.itemStack.push(newItem) + addItemToStack(newItem) { + if (newItem == null) { + return; + } + const index = this.itemStack.indexOf(newItem); + if (index !== -1) this.itemStack.splice(index, 1); + return this.itemStack.push(newItem); } // Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise. - getActiveEditor () { - if (this.activeItem instanceof TextEditor) return this.activeItem + getActiveEditor() { + if (this.activeItem instanceof TextEditor) return this.activeItem; } // Public: Return the item at the given index. @@ -472,93 +504,100 @@ class Pane { // * `index` {Number} // // Returns an item or `null` if no item exists at the given index. - itemAtIndex (index) { - return this.items[index] + itemAtIndex(index) { + return this.items[index]; } // Makes the next item in the itemStack active. - activateNextRecentlyUsedItem () { + activateNextRecentlyUsedItem() { if (this.items.length > 1) { - if (this.itemStackIndex == null) this.itemStackIndex = this.itemStack.length - 1 - if (this.itemStackIndex === 0) this.itemStackIndex = this.itemStack.length - this.itemStackIndex-- - const nextRecentlyUsedItem = this.itemStack[this.itemStackIndex] - this.emitter.emit('choose-next-mru-item', nextRecentlyUsedItem) - this.setActiveItem(nextRecentlyUsedItem, {modifyStack: false}) + if (this.itemStackIndex == null) + this.itemStackIndex = this.itemStack.length - 1; + if (this.itemStackIndex === 0) + this.itemStackIndex = this.itemStack.length; + this.itemStackIndex--; + const nextRecentlyUsedItem = this.itemStack[this.itemStackIndex]; + this.emitter.emit('choose-next-mru-item', nextRecentlyUsedItem); + this.setActiveItem(nextRecentlyUsedItem, { modifyStack: false }); } } // Makes the previous item in the itemStack active. - activatePreviousRecentlyUsedItem () { + activatePreviousRecentlyUsedItem() { if (this.items.length > 1) { - if (this.itemStackIndex + 1 === this.itemStack.length || this.itemStackIndex == null) { - this.itemStackIndex = -1 + if ( + this.itemStackIndex + 1 === this.itemStack.length || + this.itemStackIndex == null + ) { + this.itemStackIndex = -1; } - this.itemStackIndex++ - const previousRecentlyUsedItem = this.itemStack[this.itemStackIndex] - this.emitter.emit('choose-last-mru-item', previousRecentlyUsedItem) - this.setActiveItem(previousRecentlyUsedItem, {modifyStack: false}) + this.itemStackIndex++; + const previousRecentlyUsedItem = this.itemStack[this.itemStackIndex]; + this.emitter.emit('choose-last-mru-item', previousRecentlyUsedItem); + this.setActiveItem(previousRecentlyUsedItem, { modifyStack: false }); } } // Moves the active item to the end of the itemStack once the ctrl key is lifted - moveActiveItemToTopOfStack () { - delete this.itemStackIndex - this.addItemToStack(this.activeItem) - this.emitter.emit('done-choosing-mru-item') + moveActiveItemToTopOfStack() { + delete this.itemStackIndex; + this.addItemToStack(this.activeItem); + this.emitter.emit('done-choosing-mru-item'); } // Public: Makes the next item active. - activateNextItem () { - const index = this.getActiveItemIndex() - if (index < (this.items.length - 1)) { - this.activateItemAtIndex(index + 1) + activateNextItem() { + const index = this.getActiveItemIndex(); + if (index < this.items.length - 1) { + this.activateItemAtIndex(index + 1); } else { - this.activateItemAtIndex(0) + this.activateItemAtIndex(0); } } // Public: Makes the previous item active. - activatePreviousItem () { - const index = this.getActiveItemIndex() + activatePreviousItem() { + const index = this.getActiveItemIndex(); if (index > 0) { - this.activateItemAtIndex(index - 1) + this.activateItemAtIndex(index - 1); } else { - this.activateItemAtIndex(this.items.length - 1) + this.activateItemAtIndex(this.items.length - 1); } } - activateLastItem () { - this.activateItemAtIndex(this.items.length - 1) + activateLastItem() { + this.activateItemAtIndex(this.items.length - 1); } // Public: Move the active tab to the right. - moveItemRight () { - const index = this.getActiveItemIndex() - const rightItemIndex = index + 1 - if (rightItemIndex <= this.items.length - 1) this.moveItem(this.getActiveItem(), rightItemIndex) + moveItemRight() { + const index = this.getActiveItemIndex(); + const rightItemIndex = index + 1; + if (rightItemIndex <= this.items.length - 1) + this.moveItem(this.getActiveItem(), rightItemIndex); } // Public: Move the active tab to the left - moveItemLeft () { - const index = this.getActiveItemIndex() - const leftItemIndex = index - 1 - if (leftItemIndex >= 0) return this.moveItem(this.getActiveItem(), leftItemIndex) + moveItemLeft() { + const index = this.getActiveItemIndex(); + const leftItemIndex = index - 1; + if (leftItemIndex >= 0) + return this.moveItem(this.getActiveItem(), leftItemIndex); } // Public: Get the index of the active item. // // Returns a {Number}. - getActiveItemIndex () { - return this.items.indexOf(this.activeItem) + getActiveItemIndex() { + return this.items.indexOf(this.activeItem); } // Public: Activate the item at the given index. // // * `index` {Number} - activateItemAtIndex (index) { - const item = this.itemAtIndex(index) || this.getActiveItem() - return this.setActiveItem(item) + activateItemAtIndex(index) { + const item = this.itemAtIndex(index) || this.getActiveItem(); + return this.setActiveItem(item); } // Public: Make the given item *active*, causing it to be displayed by @@ -569,13 +608,14 @@ class Pane { // * `pending` (optional) {Boolean} indicating that the item should be added // in a pending state if it does not yet exist in the pane. Existing pending // items in a pane are replaced with new pending items when they are opened. - activateItem (item, options = {}) { + activateItem(item, options = {}) { if (item) { - const index = (this.getPendingItem() === this.activeItem) - ? this.getActiveItemIndex() - : this.getActiveItemIndex() + 1 - this.addItem(item, Object.assign({}, options, {index})) - this.setActiveItem(item) + const index = + this.getPendingItem() === this.activeItem + ? this.getActiveItemIndex() + : this.getActiveItemIndex() + 1; + this.addItem(item, Object.assign({}, options, { index })); + this.setActiveItem(item); } } @@ -591,75 +631,90 @@ class Pane { // new pending items when they are opened. // // Returns the added item. - addItem (item, options = {}) { + addItem(item, options = {}) { // Backward compat with old API: // addItem(item, index=@getActiveItemIndex() + 1) if (typeof options === 'number') { - Grim.deprecate(`Pane::addItem(item, ${options}) is deprecated in favor of Pane::addItem(item, {index: ${options}})`) - options = {index: options} + Grim.deprecate( + `Pane::addItem(item, ${options}) is deprecated in favor of Pane::addItem(item, {index: ${options}})` + ); + options = { index: options }; } - const index = options.index != null ? options.index : this.getActiveItemIndex() + 1 - const moved = options.moved != null ? options.moved : false - const pending = options.pending != null ? options.pending : false + const index = + options.index != null ? options.index : this.getActiveItemIndex() + 1; + const moved = options.moved != null ? options.moved : false; + const pending = options.pending != null ? options.pending : false; if (!item || typeof item !== 'object') { - throw new Error(`Pane items must be objects. Attempted to add item ${item}.`) + throw new Error( + `Pane items must be objects. Attempted to add item ${item}.` + ); } if (typeof item.isDestroyed === 'function' && item.isDestroyed()) { - throw new Error(`Adding a pane item with URI '${typeof item.getURI === 'function' && item.getURI()}' that has already been destroyed`) + throw new Error( + `Adding a pane item with URI '${typeof item.getURI === 'function' && + item.getURI()}' that has already been destroyed` + ); } - if (this.items.includes(item)) return + if (this.items.includes(item)) return; - const itemSubscriptions = new CompositeDisposable() - this.subscriptionsPerItem.set(item, itemSubscriptions) + const itemSubscriptions = new CompositeDisposable(); + this.subscriptionsPerItem.set(item, itemSubscriptions); if (typeof item.onDidDestroy === 'function') { - itemSubscriptions.add(item.onDidDestroy(() => this.removeItem(item, false))) + itemSubscriptions.add( + item.onDidDestroy(() => this.removeItem(item, false)) + ); } if (typeof item.onDidTerminatePendingState === 'function') { - itemSubscriptions.add(item.onDidTerminatePendingState(() => { - if (this.getPendingItem() === item) this.clearPendingItem() - })) + itemSubscriptions.add( + item.onDidTerminatePendingState(() => { + if (this.getPendingItem() === item) this.clearPendingItem(); + }) + ); } - this.items.splice(index, 0, item) - const lastPendingItem = this.getPendingItem() - const replacingPendingItem = lastPendingItem != null && !moved - if (replacingPendingItem) this.pendingItem = null - if (pending) this.setPendingItem(item) + this.items.splice(index, 0, item); + const lastPendingItem = this.getPendingItem(); + const replacingPendingItem = lastPendingItem != null && !moved; + if (replacingPendingItem) this.pendingItem = null; + if (pending) this.setPendingItem(item); - this.emitter.emit('did-add-item', {item, index, moved}) + this.emitter.emit('did-add-item', { item, index, moved }); if (!moved) { - if (this.container) this.container.didAddPaneItem(item, this, index) + if (this.container) this.container.didAddPaneItem(item, this, index); } - if (replacingPendingItem) this.destroyItem(lastPendingItem) - if (!this.getActiveItem()) this.setActiveItem(item) - return item + if (replacingPendingItem) this.destroyItem(lastPendingItem); + if (!this.getActiveItem()) this.setActiveItem(item); + return item; } - setPendingItem (item) { + setPendingItem(item) { if (this.pendingItem !== item) { - const mostRecentPendingItem = this.pendingItem - this.pendingItem = item + const mostRecentPendingItem = this.pendingItem; + this.pendingItem = item; if (mostRecentPendingItem) { - this.emitter.emit('item-did-terminate-pending-state', mostRecentPendingItem) + this.emitter.emit( + 'item-did-terminate-pending-state', + mostRecentPendingItem + ); } } } - getPendingItem () { - return this.pendingItem || null + getPendingItem() { + return this.pendingItem || null; } - clearPendingItem () { - this.setPendingItem(null) + clearPendingItem() { + this.setPendingItem(null); } - onItemDidTerminatePendingState (callback) { - return this.emitter.on('item-did-terminate-pending-state', callback) + onItemDidTerminatePendingState(callback) { + return this.emitter.on('item-did-terminate-pending-state', callback); } // Public: Add the given items to the pane. @@ -671,56 +726,68 @@ class Pane { // the item is # added after the current active item. // // Returns an {Array} of added items. - addItems (items, index = this.getActiveItemIndex() + 1) { - items = items.filter(item => !this.items.includes(item)) + addItems(items, index = this.getActiveItemIndex() + 1) { + items = items.filter(item => !this.items.includes(item)); for (let i = 0; i < items.length; i++) { - const item = items[i] - this.addItem(item, {index: index + i}) + const item = items[i]; + this.addItem(item, { index: index + i }); } - return items + return items; } - removeItem (item, moved) { - const index = this.items.indexOf(item) - if (index === -1) return - if (this.getPendingItem() === item) this.pendingItem = null - this.removeItemFromStack(item) - this.emitter.emit('will-remove-item', {item, index, destroyed: !moved, moved}) - this.unsubscribeFromItem(item) + removeItem(item, moved) { + const index = this.items.indexOf(item); + if (index === -1) return; + if (this.getPendingItem() === item) this.pendingItem = null; + this.removeItemFromStack(item); + this.emitter.emit('will-remove-item', { + item, + index, + destroyed: !moved, + moved + }); + this.unsubscribeFromItem(item); if (item === this.activeItem) { if (this.items.length === 1) { - this.setActiveItem(undefined) + this.setActiveItem(undefined); } else if (index === 0) { - this.activateNextItem() + this.activateNextItem(); } else { - this.activatePreviousItem() + this.activatePreviousItem(); } } - this.items.splice(index, 1) - this.emitter.emit('did-remove-item', {item, index, destroyed: !moved, moved}) - if (!moved && this.container) this.container.didDestroyPaneItem({item, index, pane: this}) - if (this.items.length === 0 && this.config.get('core.destroyEmptyPanes')) this.destroy() + this.items.splice(index, 1); + this.emitter.emit('did-remove-item', { + item, + index, + destroyed: !moved, + moved + }); + if (!moved && this.container) + this.container.didDestroyPaneItem({ item, index, pane: this }); + if (this.items.length === 0 && this.config.get('core.destroyEmptyPanes')) + this.destroy(); } // Remove the given item from the itemStack. // // * `item` The item to remove. // * `index` {Number} indicating the index to which to remove the item from the itemStack. - removeItemFromStack (item) { - const index = this.itemStack.indexOf(item) - if (index !== -1) this.itemStack.splice(index, 1) + removeItemFromStack(item) { + const index = this.itemStack.indexOf(item); + if (index !== -1) this.itemStack.splice(index, 1); } // Public: Move the given item to the given index. // // * `item` The item to move. // * `index` {Number} indicating the index to which to move the item. - moveItem (item, newIndex) { - const oldIndex = this.items.indexOf(item) - this.items.splice(oldIndex, 1) - this.items.splice(newIndex, 0, item) - this.emitter.emit('did-move-item', {item, oldIndex, newIndex}) + moveItem(item, newIndex) { + const oldIndex = this.items.indexOf(item); + this.items.splice(oldIndex, 1); + this.items.splice(newIndex, 0, item); + this.emitter.emit('did-move-item', { item, oldIndex, newIndex }); } // Public: Move the given item to the given index on another pane. @@ -729,16 +796,16 @@ class Pane { // * `pane` {Pane} to which to move the item. // * `index` {Number} indicating the index to which to move the item in the // given pane. - moveItemToPane (item, pane, index) { - this.removeItem(item, true) - return pane.addItem(item, {index, moved: true}) + moveItemToPane(item, pane, index) { + this.removeItem(item, true); + return pane.addItem(item, { index, moved: true }); } // Public: Destroy the active item and activate the next item. // // Returns a {Promise} that resolves when the item is destroyed. - destroyActiveItem () { - return this.destroyItem(this.activeItem) + destroyActiveItem() { + return this.destroyItem(this.activeItem); } // Public: Destroy the given item. @@ -753,102 +820,126 @@ class Pane { // // Returns a {Promise} that resolves with a {Boolean} indicating whether or not // the item was destroyed. - async destroyItem (item, force) { - const index = this.items.indexOf(item) - if (index === -1) return false + async destroyItem(item, force) { + const index = this.items.indexOf(item); + if (index === -1) return false; - if (!force && - typeof item.isPermanentDockItem === 'function' && item.isPermanentDockItem() && - (!this.container || this.container.getLocation() !== 'center')) { - return false + if ( + !force && + typeof item.isPermanentDockItem === 'function' && + item.isPermanentDockItem() && + (!this.container || this.container.getLocation() !== 'center') + ) { + return false; } // In the case where there are no `onWillDestroyPaneItem` listeners, preserve the old behavior // where `Pane.destroyItem` and callers such as `Pane.close` take effect synchronously. if (this.emitter.listenerCountForEventName('will-destroy-item') > 0) { - await this.emitter.emitAsync('will-destroy-item', {item, index}) + await this.emitter.emitAsync('will-destroy-item', { item, index }); } - if (this.container && this.container.emitter.listenerCountForEventName('will-destroy-pane-item') > 0) { - await this.container.willDestroyPaneItem({item, index, pane: this}) + if ( + this.container && + this.container.emitter.listenerCountForEventName( + 'will-destroy-pane-item' + ) > 0 + ) { + await this.container.willDestroyPaneItem({ item, index, pane: this }); } - if (!force && typeof item.shouldPromptToSave === 'function' && item.shouldPromptToSave()) { - if (!await this.promptToSaveItem(item)) return false + if ( + !force && + typeof item.shouldPromptToSave === 'function' && + item.shouldPromptToSave() + ) { + if (!(await this.promptToSaveItem(item))) return false; } - this.removeItem(item, false) - if (typeof item.destroy === 'function') item.destroy() - return true + this.removeItem(item, false); + if (typeof item.destroy === 'function') item.destroy(); + return true; } // Public: Destroy all items. - destroyItems () { - return Promise.all( - this.getItems().map(item => this.destroyItem(item)) - ) + destroyItems() { + return Promise.all(this.getItems().map(item => this.destroyItem(item))); } // Public: Destroy all items except for the active item. - destroyInactiveItems () { + destroyInactiveItems() { return Promise.all( this.getItems() .filter(item => item !== this.activeItem) .map(item => this.destroyItem(item)) - ) + ); } - promptToSaveItem (item, options = {}) { + promptToSaveItem(item, options = {}) { return new Promise((resolve, reject) => { - if (typeof item.shouldPromptToSave !== 'function' || !item.shouldPromptToSave(options)) { - return resolve(true) + if ( + typeof item.shouldPromptToSave !== 'function' || + !item.shouldPromptToSave(options) + ) { + return resolve(true); } - let uri + 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(); } else { - return resolve(true) + return resolve(true); } - const title = (typeof item.getTitle === 'function' && item.getTitle()) || uri + const title = + (typeof item.getTitle === 'function' && item.getTitle()) || uri; const saveDialog = (saveButtonText, saveFn, message) => { - this.applicationDelegate.confirm({ - message, - detail: 'Your changes will be lost if you close this item without saving.', - buttons: [saveButtonText, 'Cancel', "&Don't Save"] - }, response => { - switch (response) { - case 0: - return saveFn(item, error => { - if (error instanceof SaveCancelledError) { - resolve(false) - } else if (error) { - saveDialog( - 'Save as', - this.saveItemAs, - `'${title}' could not be saved.\nError: ${this.getMessageForErrorCode(error.code)}` - ) - } else { - resolve(true) - } - }) - case 1: - return resolve(false) - case 2: - return resolve(true) + this.applicationDelegate.confirm( + { + message, + detail: + 'Your changes will be lost if you close this item without saving.', + buttons: [saveButtonText, 'Cancel', "&Don't Save"] + }, + response => { + switch (response) { + case 0: + return saveFn(item, error => { + if (error instanceof SaveCancelledError) { + resolve(false); + } else if (error) { + saveDialog( + 'Save as', + this.saveItemAs, + `'${title}' could not be saved.\nError: ${this.getMessageForErrorCode( + error.code + )}` + ); + } else { + resolve(true); + } + }); + case 1: + return resolve(false); + case 2: + return resolve(true); + } } - }) - } + ); + }; - saveDialog('Save', this.saveItem, `'${title}' has changes, do you want to save them?`) - }) + saveDialog( + 'Save', + this.saveItem, + `'${title}' has changes, do you want to save them?` + ); + }); } // Public: Save the active item. - saveActiveItem (nextAction) { - return this.saveItem(this.getActiveItem(), nextAction) + saveActiveItem(nextAction) { + return this.saveItem(this.getActiveItem(), nextAction); } // Public: Prompt the user for a location and save the active item with the @@ -858,8 +949,8 @@ class Pane { // successfully saved. // // Returns a {Promise} that resolves when the save is complete - saveActiveItemAs (nextAction) { - return this.saveItemAs(this.getActiveItem(), nextAction) + saveActiveItemAs(nextAction) { + return this.saveItemAs(this.getActiveItem(), nextAction); } // Public: Save the given item. @@ -871,35 +962,35 @@ class Pane { // provided // // Returns a {Promise} that resolves when the save is complete - saveItem (item, nextAction) { - if (!item) return Promise.resolve() + saveItem(item, nextAction) { + if (!item) return Promise.resolve(); - let itemURI + let itemURI; if (typeof item.getURI === 'function') { - itemURI = item.getURI() + itemURI = item.getURI(); } else if (typeof item.getUri === 'function') { - itemURI = item.getUri() + itemURI = item.getUri(); } if (itemURI != null) { if (typeof item.save === 'function') { return promisify(() => item.save()) .then(() => { - if (nextAction) nextAction() + if (nextAction) nextAction(); }) .catch(error => { if (nextAction) { - nextAction(error) + nextAction(error); } else { - this.handleSaveError(error, item) + this.handleSaveError(error, item); } - }) + }); } else if (nextAction) { - nextAction() - return Promise.resolve() + nextAction(); + return Promise.resolve(); } } else { - return this.saveItemAs(item, nextAction) + return this.saveItemAs(item, nextAction); } } @@ -911,52 +1002,58 @@ class Pane { // after the item is successfully saved, or with the error if it failed. // The return value will be that of `nextAction` or `undefined` if it was not // provided - async saveItemAs (item, nextAction) { - if (!item) return - if (typeof item.saveAs !== 'function') return + async saveItemAs(item, nextAction) { + if (!item) return; + if (typeof item.saveAs !== 'function') return; - const saveOptions = typeof item.getSaveDialogOptions === 'function' - ? item.getSaveDialogOptions() - : {} + const saveOptions = + typeof item.getSaveDialogOptions === 'function' + ? item.getSaveDialogOptions() + : {}; - const itemPath = item.getPath() - if (itemPath && !saveOptions.defaultPath) saveOptions.defaultPath = itemPath + const itemPath = item.getPath(); + if (itemPath && !saveOptions.defaultPath) + saveOptions.defaultPath = itemPath; - let resolveSaveDialogPromise = null - const saveDialogPromise = new Promise(resolve => { resolveSaveDialogPromise = resolve }) + let resolveSaveDialogPromise = null; + const saveDialogPromise = new Promise(resolve => { + resolveSaveDialogPromise = resolve; + }); this.applicationDelegate.showSaveDialog(saveOptions, newItemPath => { if (newItemPath) { promisify(() => item.saveAs(newItemPath)) .then(() => { if (nextAction) { - resolveSaveDialogPromise(nextAction()) + resolveSaveDialogPromise(nextAction()); } else { - resolveSaveDialogPromise() + resolveSaveDialogPromise(); } }) .catch(error => { if (nextAction) { - resolveSaveDialogPromise(nextAction(error)) + resolveSaveDialogPromise(nextAction(error)); } else { - this.handleSaveError(error, item) - resolveSaveDialogPromise() + this.handleSaveError(error, item); + resolveSaveDialogPromise(); } - }) + }); } else if (nextAction) { - resolveSaveDialogPromise(nextAction(new SaveCancelledError('Save Cancelled'))) + resolveSaveDialogPromise( + nextAction(new SaveCancelledError('Save Cancelled')) + ); } else { - resolveSaveDialogPromise() + resolveSaveDialogPromise(); } - }) + }); - return saveDialogPromise + return saveDialogPromise; } // Public: Save all items. - saveItems () { + saveItems() { for (let item of this.getItems()) { if (typeof item.isModified === 'function' && item.isModified()) { - this.saveItem(item) + this.saveItem(item); } } } @@ -965,14 +1062,14 @@ class Pane { // none exists. // // * `uri` {String} containing a URI. - itemForURI (uri) { + itemForURI(uri) { return this.items.find(item => { if (typeof item.getURI === 'function') { - return item.getURI() === uri + return item.getURI() === uri; } else if (typeof item.getUri === 'function') { - return item.getUri() === uri + return item.getUri() === uri; } - }) + }); } // Public: Activate the first item that matches the given URI. @@ -980,19 +1077,19 @@ class Pane { // * `uri` {String} containing a URI. // // Returns a {Boolean} indicating whether an item matching the URI was found. - activateItemForURI (uri) { - const item = this.itemForURI(uri) + activateItemForURI(uri) { + const item = this.itemForURI(uri); if (item) { - this.activateItem(item) - return true + this.activateItem(item); + return true; } else { - return false + return false; } } - copyActiveItem () { + copyActiveItem() { if (this.activeItem && typeof this.activeItem.copy === 'function') { - return this.activeItem.copy() + return this.activeItem.copy(); } } @@ -1003,48 +1100,56 @@ class Pane { // Public: Determine whether the pane is active. // // Returns a {Boolean}. - isActive () { - return this.container && this.container.getActivePane() === this + isActive() { + return this.container && this.container.getActivePane() === this; } // Public: Makes this pane the *active* pane, causing it to gain focus. - activate () { - if (this.isDestroyed()) throw new Error('Pane has been destroyed') - this.focused = true + activate() { + if (this.isDestroyed()) throw new Error('Pane has been destroyed'); + this.focused = true; - if (this.container) this.container.didActivatePane(this) - this.emitter.emit('did-activate') + if (this.container) this.container.didActivatePane(this); + this.emitter.emit('did-activate'); } // Public: Close the pane and destroy all its items. // // If this is the last pane, all the items will be destroyed but the pane // itself will not be destroyed. - destroy () { - if (this.container && this.container.isAlive() && this.container.getPanes().length === 1) { - return this.destroyItems() + destroy() { + if ( + this.container && + this.container.isAlive() && + this.container.getPanes().length === 1 + ) { + return this.destroyItems(); } - this.emitter.emit('will-destroy') - this.alive = false + this.emitter.emit('will-destroy'); + this.alive = false; if (this.container) { - this.container.willDestroyPane({pane: this}) - if (this.isActive()) this.container.activateNextPane() + this.container.willDestroyPane({ pane: this }); + if (this.isActive()) this.container.activateNextPane(); } - this.emitter.emit('did-destroy') - this.emitter.dispose() + this.emitter.emit('did-destroy'); + this.emitter.dispose(); for (let item of this.items.slice()) { - if (typeof item.destroy === 'function') item.destroy() + if (typeof item.destroy === 'function') item.destroy(); } - if (this.container) this.container.didDestroyPane({pane: this}) + if (this.container) this.container.didDestroyPane({ pane: this }); } - isAlive () { return this.alive } + isAlive() { + return this.alive; + } // Public: Determine whether this pane has been destroyed. // // Returns a {Boolean}. - isDestroyed () { return !this.isAlive() } + isDestroyed() { + return !this.isAlive(); + } /* Section: Splitting @@ -1057,8 +1162,8 @@ class Pane { // * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane // // Returns the new {Pane}. - splitLeft (params) { - return this.split('horizontal', 'before', params) + splitLeft(params) { + return this.split('horizontal', 'before', params); } // Public: Create a new pane to the right of this pane. @@ -1068,8 +1173,8 @@ class Pane { // * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane // // Returns the new {Pane}. - splitRight (params) { - return this.split('horizontal', 'after', params) + splitRight(params) { + return this.split('horizontal', 'after', params); } // Public: Creates a new pane above the receiver. @@ -1079,8 +1184,8 @@ class Pane { // * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane // // Returns the new {Pane}. - splitUp (params) { - return this.split('vertical', 'before', params) + splitUp(params) { + return this.split('vertical', 'before', params); } // Public: Creates a new pane below the receiver. @@ -1090,121 +1195,140 @@ class Pane { // * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane // // Returns the new {Pane}. - splitDown (params) { - return this.split('vertical', 'after', params) + splitDown(params) { + return this.split('vertical', 'after', params); } - split (orientation, side, params) { + split(orientation, side, params) { if (params && params.copyActiveItem) { - if (!params.items) params.items = [] - params.items.push(this.copyActiveItem()) + if (!params.items) params.items = []; + params.items.push(this.copyActiveItem()); } if (this.parent.orientation !== orientation) { - this.parent.replaceChild(this, new PaneAxis({ - container: this.container, - orientation, - children: [this], - flexScale: this.flexScale}, - this.viewRegistry - )) - this.setFlexScale(1) + this.parent.replaceChild( + this, + new PaneAxis( + { + container: this.container, + orientation, + children: [this], + flexScale: this.flexScale + }, + this.viewRegistry + ) + ); + this.setFlexScale(1); } - const newPane = new Pane(Object.assign({ - applicationDelegate: this.applicationDelegate, - notificationManager: this.notificationManager, - deserializerManager: this.deserializerManager, - config: this.config, - viewRegistry: this.viewRegistry - }, params)) + const newPane = new Pane( + Object.assign( + { + applicationDelegate: this.applicationDelegate, + notificationManager: this.notificationManager, + deserializerManager: this.deserializerManager, + config: this.config, + viewRegistry: this.viewRegistry + }, + params + ) + ); switch (side) { - case 'before': this.parent.insertChildBefore(this, newPane); break - case 'after': this.parent.insertChildAfter(this, newPane); break + case 'before': + this.parent.insertChildBefore(this, newPane); + break; + case 'after': + this.parent.insertChildAfter(this, newPane); + break; } - if (params && params.moveActiveItem && this.activeItem) this.moveItemToPane(this.activeItem, newPane) + if (params && params.moveActiveItem && this.activeItem) + this.moveItemToPane(this.activeItem, newPane); - newPane.activate() - return newPane + newPane.activate(); + return newPane; } // If the parent is a horizontal axis, returns its first child if it is a pane; // otherwise returns this pane. - findLeftmostSibling () { + findLeftmostSibling() { if (this.parent.orientation === 'horizontal') { - const [leftmostSibling] = this.parent.children + const [leftmostSibling] = this.parent.children; if (leftmostSibling instanceof PaneAxis) { - return this + return this; } else { - return leftmostSibling + return leftmostSibling; } } else { - return this + return this; } } - findRightmostSibling () { + findRightmostSibling() { if (this.parent.orientation === 'horizontal') { - const rightmostSibling = this.parent.children[this.parent.children.length - 1] + const rightmostSibling = this.parent.children[ + this.parent.children.length - 1 + ]; if (rightmostSibling instanceof PaneAxis) { - return this + return this; } else { - return rightmostSibling + return rightmostSibling; } } else { - return this + return this; } } // If the parent is a horizontal axis, returns its last child if it is a pane; // otherwise returns a new pane created by splitting this pane rightward. - findOrCreateRightmostSibling () { - const rightmostSibling = this.findRightmostSibling() + findOrCreateRightmostSibling() { + const rightmostSibling = this.findRightmostSibling(); if (rightmostSibling === this) { - return this.splitRight() + return this.splitRight(); } else { - return rightmostSibling + return rightmostSibling; } } // If the parent is a vertical axis, returns its first child if it is a pane; // otherwise returns this pane. - findTopmostSibling () { + findTopmostSibling() { if (this.parent.orientation === 'vertical') { - const [topmostSibling] = this.parent.children + const [topmostSibling] = this.parent.children; if (topmostSibling instanceof PaneAxis) { - return this + return this; } else { - return topmostSibling + return topmostSibling; } } else { - return this + return this; } } - findBottommostSibling () { + findBottommostSibling() { if (this.parent.orientation === 'vertical') { - const bottommostSibling = this.parent.children[this.parent.children.length - 1] + const bottommostSibling = this.parent.children[ + this.parent.children.length - 1 + ]; if (bottommostSibling instanceof PaneAxis) { - return this + return this; } else { - return bottommostSibling + return bottommostSibling; } } else { - return this + return this; } } // If the parent is a vertical axis, returns its last child if it is a pane; // otherwise returns a new pane created by splitting this pane bottomward. - findOrCreateBottommostSibling () { - const bottommostSibling = this.findBottommostSibling() + findOrCreateBottommostSibling() { + const bottommostSibling = this.findBottommostSibling(); if (bottommostSibling === this) { - return this.splitDown() + return this.splitDown(); } else { - return bottommostSibling + return bottommostSibling; } } @@ -1212,58 +1336,83 @@ class Pane { // // Returns a {Promise} that resolves once the pane is either closed, or the // closing has been cancelled. - close () { - return Promise.all(this.getItems().map(item => this.promptToSaveItem(item))) - .then(results => { - if (!results.includes(false)) return this.destroy() - }) + close() { + return Promise.all( + this.getItems().map(item => this.promptToSaveItem(item)) + ).then(results => { + if (!results.includes(false)) return this.destroy(); + }); } - handleSaveError (error, item) { - const itemPath = error.path || (typeof item.getPath === 'function' && item.getPath()) + handleSaveError(error, item) { + const itemPath = + error.path || (typeof item.getPath === 'function' && item.getPath()); const addWarningWithPath = (message, options) => { - if (itemPath) message = `${message} '${itemPath}'` - this.notificationManager.addWarning(message, options) - } + if (itemPath) message = `${message} '${itemPath}'`; + this.notificationManager.addWarning(message, options); + }; - const customMessage = this.getMessageForErrorCode(error.code) + const customMessage = this.getMessageForErrorCode(error.code); if (customMessage != null) { - addWarningWithPath(`Unable to save file: ${customMessage}`) - } else if (error.code === 'EISDIR' || (error.message && error.message.endsWith('is a directory'))) { - return this.notificationManager.addWarning(`Unable to save file: ${error.message}`) - } else if (['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN'].includes(error.code)) { - addWarningWithPath('Unable to save file', {detail: error.message}) + addWarningWithPath(`Unable to save file: ${customMessage}`); + } else if ( + error.code === 'EISDIR' || + (error.message && error.message.endsWith('is a directory')) + ) { + return this.notificationManager.addWarning( + `Unable to save file: ${error.message}` + ); + } else if ( + ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN'].includes( + error.code + ) + ) { + addWarningWithPath('Unable to save file', { detail: error.message }); } else { - const errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message) + const errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec( + error.message + ); if (errorMatch) { - const fileName = errorMatch[1] - this.notificationManager.addWarning(`Unable to save file: A directory in the path '${fileName}' could not be written to`) + const fileName = errorMatch[1]; + this.notificationManager.addWarning( + `Unable to save file: A directory in the path '${fileName}' could not be written to` + ); } else { - throw error + throw error; } } } - getMessageForErrorCode (errorCode) { + getMessageForErrorCode(errorCode) { switch (errorCode) { - case 'EACCES': return 'Permission denied' - case 'ECONNRESET': return 'Connection reset' - case 'EINTR': return 'Interrupted system call' - case 'EIO': return 'I/O error writing file' - case 'ENOSPC': return 'No space left on device' - case 'ENOTSUP': return 'Operation not supported on socket' - case 'ENXIO': return 'No such device or address' - case 'EROFS': return 'Read-only file system' - case 'ESPIPE': return 'Invalid seek' - case 'ETIMEDOUT': return 'Connection timed out' + case 'EACCES': + return 'Permission denied'; + case 'ECONNRESET': + return 'Connection reset'; + case 'EINTR': + return 'Interrupted system call'; + case 'EIO': + return 'I/O error writing file'; + case 'ENOSPC': + return 'No space left on device'; + case 'ENOTSUP': + return 'Operation not supported on socket'; + case 'ENXIO': + return 'No such device or address'; + case 'EROFS': + return 'Read-only file system'; + case 'ESPIPE': + return 'Invalid seek'; + case 'ETIMEDOUT': + return 'Connection timed out'; } } -} +}; -function promisify (callback) { +function promisify(callback) { try { - return Promise.resolve(callback()) + return Promise.resolve(callback()); } catch (error) { - return Promise.reject(error) + return Promise.reject(error); } } diff --git a/src/panel-container-element.js b/src/panel-container-element.js index a3dc9f62b..6ca03acb1 100644 --- a/src/panel-container-element.js +++ b/src/panel-container-element.js @@ -1,58 +1,69 @@ -'use strict' +'use strict'; -const focusTrap = require('focus-trap') -const {CompositeDisposable} = require('event-kit') +const focusTrap = require('focus-trap'); +const { CompositeDisposable } = require('event-kit'); class PanelContainerElement extends HTMLElement { - createdCallback () { - this.subscriptions = new CompositeDisposable() + createdCallback() { + this.subscriptions = new CompositeDisposable(); } - attachedCallback () { + attachedCallback() { if (this.model.dock) { - this.model.dock.elementAttached() + this.model.dock.elementAttached(); } } - initialize (model, viewRegistry) { - this.model = model - this.viewRegistry = viewRegistry + initialize(model, viewRegistry) { + this.model = model; + this.viewRegistry = viewRegistry; - 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()) + 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()); // Add the dock. if (this.model.dock != null) { - this.appendChild(this.model.dock.getElement()) + this.appendChild(this.model.dock.getElement()); } - return this + return this; } - getModel () { return this.model } + getModel() { + return this.model; + } - panelAdded ({panel, index}) { - const panelElement = panel.getElement() - panelElement.classList.add(this.model.getLocation()) + panelAdded({ panel, index }) { + const panelElement = panel.getElement(); + 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.subscriptions.add(panel.onDidChangeVisible(visible => { - if (visible) { this.hideAllPanelsExcept(panel) } - })) + this.hideAllPanelsExcept(panel); + this.subscriptions.add( + panel.onDidChangeVisible(visible => { + if (visible) { + this.hideAllPanelsExcept(panel); + } + }) + ); if (panel.autoFocus) { const focusOptions = { @@ -63,36 +74,42 @@ class PanelContainerElement extends HTMLElement { // closing is handled by core Atom commands and this already deactivates // on visibility changes escapeDeactivates: false - } + }; if (panel.autoFocus !== true) { - focusOptions.initialFocus = panel.autoFocus + focusOptions.initialFocus = panel.autoFocus; } - const modalFocusTrap = focusTrap(panelElement, focusOptions) + const modalFocusTrap = focusTrap(panelElement, focusOptions); - this.subscriptions.add(panel.onDidChangeVisible(visible => { - if (visible) { - modalFocusTrap.activate() - } else { - modalFocusTrap.deactivate() - } - })) + this.subscriptions.add( + panel.onDidChangeVisible(visible => { + if (visible) { + modalFocusTrap.activate(); + } else { + modalFocusTrap.deactivate(); + } + }) + ); } } } - destroyed () { - this.subscriptions.dispose() + destroyed() { + this.subscriptions.dispose(); if (this.parentNode != null) { - this.parentNode.removeChild(this) + this.parentNode.removeChild(this); } } - hideAllPanelsExcept (excludedPanel) { + hideAllPanelsExcept(excludedPanel) { for (let panel of this.model.getPanels()) { - if (panel !== excludedPanel) { panel.hide() } + if (panel !== excludedPanel) { + panel.hide(); + } } } } -module.exports = 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 6d5fb7398..16e81bbe1 100644 --- a/src/panel-container.js +++ b/src/panel-container.js @@ -1,101 +1,118 @@ -'use strict' +'use strict'; -const {Emitter, CompositeDisposable} = require('event-kit') -const PanelContainerElement = require('./panel-container-element') +const { Emitter, CompositeDisposable } = require('event-kit'); +const PanelContainerElement = require('./panel-container-element'); module.exports = class PanelContainer { - constructor ({location, dock, viewRegistry} = {}) { - this.location = location - this.emitter = new Emitter() - this.subscriptions = new CompositeDisposable() - this.panels = [] - this.dock = dock - this.viewRegistry = viewRegistry + constructor({ location, dock, viewRegistry } = {}) { + this.location = location; + this.emitter = new Emitter(); + this.subscriptions = new CompositeDisposable(); + this.panels = []; + this.dock = dock; + this.viewRegistry = viewRegistry; } - destroy () { - for (let panel of this.getPanels()) { panel.destroy() } - this.subscriptions.dispose() - this.emitter.emit('did-destroy', this) - this.emitter.dispose() - } - - getElement () { - if (!this.element) { - this.element = new PanelContainerElement().initialize(this, this.viewRegistry) + destroy() { + for (let panel of this.getPanels()) { + panel.destroy(); } - return this.element + this.subscriptions.dispose(); + this.emitter.emit('did-destroy', this); + this.emitter.dispose(); + } + + getElement() { + if (!this.element) { + this.element = new PanelContainerElement().initialize( + this, + this.viewRegistry + ); + } + return this.element; } /* 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.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('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) - this.emitter.emit('did-remove-panel', {panel, index}) + this.panels.splice(index, 1); + this.emitter.emit('did-remove-panel', { panel, index }); } } - getPanelIndex (panel) { - const priority = panel.getPriority() + getPanelIndex(panel) { + const priority = panel.getPriority(); if (['bottom', 'right'].includes(this.location)) { for (let i = this.panels.length - 1; i >= 0; i--) { - const p = this.panels[i] - if (priority < p.getPriority()) { return i + 1 } + const p = this.panels[i]; + if (priority < p.getPriority()) { + return i + 1; + } } - return 0 + return 0; } else { for (let i = 0; i < this.panels.length; i++) { - const p = this.panels[i] - if (priority < p.getPriority()) { return i } + const p = this.panels[i]; + if (priority < p.getPriority()) { + return i; + } } - return this.panels.length + return this.panels.length; } } -} +}; diff --git a/src/panel.js b/src/panel.js index a37758179..accc419cc 100644 --- a/src/panel.js +++ b/src/panel.js @@ -1,4 +1,4 @@ -const {Emitter} = require('event-kit') +const { Emitter } = require('event-kit'); // Extended: A container representing a panel on the edges of the editor window. // You should not create a `Panel` directly, instead use {Workspace::addTopPanel} @@ -7,41 +7,41 @@ const {Emitter} = require('event-kit') // Examples: [status-bar](https://github.com/atom/status-bar) // and [find-and-replace](https://github.com/atom/find-and-replace) both use // panels. -module.exports = -class Panel { +module.exports = class Panel { /* Section: Construction and Destruction */ - constructor ({item, autoFocus, visible, priority, className}, viewRegistry) { - this.destroyed = false - this.item = item - this.autoFocus = autoFocus == null ? false : autoFocus - this.visible = visible == null ? true : visible - this.priority = priority == null ? 100 : priority - this.className = className - this.viewRegistry = viewRegistry - this.emitter = new Emitter() + constructor({ item, autoFocus, visible, priority, className }, viewRegistry) { + this.destroyed = false; + this.item = item; + this.autoFocus = autoFocus == null ? false : autoFocus; + this.visible = visible == null ? true : visible; + this.priority = priority == null ? 100 : priority; + this.className = className; + this.viewRegistry = viewRegistry; + this.emitter = new Emitter(); } // Public: Destroy and remove this panel from the UI. - destroy () { - if (this.destroyed) return - this.destroyed = true - this.hide() - if (this.element) this.element.remove() - this.emitter.emit('did-destroy', this) - return this.emitter.dispose() + destroy() { + if (this.destroyed) return; + this.destroyed = true; + this.hide(); + if (this.element) this.element.remove(); + this.emitter.emit('did-destroy', this); + return this.emitter.dispose(); } - getElement () { + getElement() { if (!this.element) { - this.element = document.createElement('atom-panel') - if (!this.visible) this.element.style.display = 'none' - if (this.className) this.element.classList.add(...this.className.split(' ')) - this.element.appendChild(this.viewRegistry.getView(this.item)) + this.element = document.createElement('atom-panel'); + if (!this.visible) this.element.style.display = 'none'; + if (this.className) + this.element.classList.add(...this.className.split(' ')); + this.element.appendChild(this.viewRegistry.getView(this.item)); } - return this.element + return this.element; } /* @@ -54,8 +54,8 @@ class Panel { // * `visible` {Boolean} true when the panel has been shown // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeVisible (callback) { - return this.emitter.on('did-change-visible', callback) + onDidChangeVisible(callback) { + return this.emitter.on('did-change-visible', callback); } // Public: Invoke the given callback when the pane is destroyed. @@ -64,8 +64,8 @@ class Panel { // * `panel` {Panel} this panel // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } /* @@ -73,37 +73,37 @@ class Panel { */ // Public: Returns the panel's item. - getItem () { - return this.item + getItem() { + return this.item; } // Public: Returns a {Number} indicating this panel's priority. - getPriority () { - return this.priority + getPriority() { + return this.priority; } - getClassName () { - return this.className + getClassName() { + return this.className; } // Public: Returns a {Boolean} true when the panel is visible. - isVisible () { - return this.visible + isVisible() { + return this.visible; } // Public: Hide this panel - hide () { - let wasVisible = this.visible - this.visible = false - if (this.element) this.element.style.display = 'none' - if (wasVisible) this.emitter.emit('did-change-visible', this.visible) + hide() { + let wasVisible = this.visible; + this.visible = false; + if (this.element) this.element.style.display = 'none'; + if (wasVisible) this.emitter.emit('did-change-visible', this.visible); } // Public: Show this panel - show () { - let wasVisible = this.visible - this.visible = true - if (this.element) this.element.style.display = null - if (!wasVisible) this.emitter.emit('did-change-visible', this.visible) + show() { + let wasVisible = this.visible; + this.visible = true; + if (this.element) this.element.style.display = null; + if (!wasVisible) this.emitter.emit('did-change-visible', this.visible); } -} +}; diff --git a/src/path-watcher.js b/src/path-watcher.js index 0cde2f535..2fed722c5 100644 --- a/src/path-watcher.js +++ b/src/path-watcher.js @@ -1,10 +1,10 @@ -const fs = require('fs') -const path = require('path') +const fs = require('fs'); +const path = require('path'); -const {Emitter, Disposable, CompositeDisposable} = require('event-kit') -const nsfw = require('@atom/nsfw') -const watcher = require('@atom/watcher') -const {NativeWatcherRegistry} = require('./native-watcher-registry') +const { Emitter, Disposable, CompositeDisposable } = require('event-kit'); +const nsfw = require('@atom/nsfw'); +const watcher = require('@atom/watcher'); +const { NativeWatcherRegistry } = require('./native-watcher-registry'); // Private: Associate native watcher action flags with descriptive String equivalents. const ACTION_MAP = new Map([ @@ -12,7 +12,7 @@ const ACTION_MAP = new Map([ [nsfw.actions.CREATED, 'created'], [nsfw.actions.DELETED, 'deleted'], [nsfw.actions.RENAMED, 'renamed'] -]) +]); // Private: Possible states of a {NativeWatcher}. const WATCHER_STATE = { @@ -20,54 +20,53 @@ const WATCHER_STATE = { STARTING: Symbol('starting'), RUNNING: Symbol('running'), STOPPING: Symbol('stopping') -} +}; // Private: Interface with and normalize events from a filesystem watcher implementation. class NativeWatcher { - // Private: Initialize a native watcher on a path. // // Events will not be produced until {start()} is called. - constructor (normalizedPath) { - this.normalizedPath = normalizedPath - this.emitter = new Emitter() - this.subs = new CompositeDisposable() + constructor(normalizedPath) { + this.normalizedPath = normalizedPath; + this.emitter = new Emitter(); + this.subs = new CompositeDisposable(); - this.state = WATCHER_STATE.STOPPED + this.state = WATCHER_STATE.STOPPED; - this.onEvents = this.onEvents.bind(this) - this.onError = this.onError.bind(this) + this.onEvents = this.onEvents.bind(this); + this.onError = this.onError.bind(this); } // Private: Begin watching for filesystem events. // // Has no effect if the watcher has already been started. - async start () { + async start() { if (this.state !== WATCHER_STATE.STOPPED) { - return + return; } - this.state = WATCHER_STATE.STARTING + this.state = WATCHER_STATE.STARTING; - await this.doStart() + await this.doStart(); - this.state = WATCHER_STATE.RUNNING - this.emitter.emit('did-start') + this.state = WATCHER_STATE.RUNNING; + this.emitter.emit('did-start'); } - doStart () { - return Promise.reject(new Error('doStart() not overridden')) + doStart() { + return Promise.reject(new Error('doStart() not overridden')); } // Private: Return true if the underlying watcher is actively listening for filesystem events. - isRunning () { - return this.state === WATCHER_STATE.RUNNING + isRunning() { + return this.state === WATCHER_STATE.RUNNING; } // Private: Register a callback to be invoked when the filesystem watcher has been initialized. // // Returns: A {Disposable} to revoke the subscription. - onDidStart (callback) { - return this.emitter.on('did-start', callback) + onDidStart(callback) { + return this.emitter.on('did-start', callback); } // Private: Register a callback to be invoked with normalized filesystem events as they arrive. Starts the watcher @@ -75,44 +74,44 @@ class NativeWatcher { // dispose their subscriptions. // // Returns: A {Disposable} to revoke the subscription. - onDidChange (callback) { - this.start() + onDidChange(callback) { + this.start(); - const sub = this.emitter.on('did-change', callback) + const sub = this.emitter.on('did-change', callback); return new Disposable(() => { - sub.dispose() + sub.dispose(); if (this.emitter.listenerCountForEventName('did-change') === 0) { - this.stop() + this.stop(); } - }) + }); } // Private: Register a callback to be invoked when a {Watcher} should attach to a different {NativeWatcher}. // // Returns: A {Disposable} to revoke the subscription. - onShouldDetach (callback) { - return this.emitter.on('should-detach', callback) + onShouldDetach(callback) { + return this.emitter.on('should-detach', callback); } // Private: Register a callback to be invoked when a {NativeWatcher} is about to be stopped. // // Returns: A {Disposable} to revoke the subscription. - onWillStop (callback) { - return this.emitter.on('will-stop', callback) + onWillStop(callback) { + return this.emitter.on('will-stop', callback); } // Private: Register a callback to be invoked when the filesystem watcher has been stopped. // // Returns: A {Disposable} to revoke the subscription. - onDidStop (callback) { - return this.emitter.on('did-stop', callback) + onDidStop(callback) { + return this.emitter.on('did-stop', callback); } // Private: Register a callback to be invoked with any errors reported from the watcher. // // Returns: A {Disposable} to revoke the subscription. - onDidError (callback) { - return this.emitter.on('did-error', callback) + onDidError(callback) { + return this.emitter.on('did-error', callback); } // Private: Broadcast an `onShouldDetach` event to prompt any {Watcher} instances bound here to attach to a new @@ -120,176 +119,200 @@ class NativeWatcher { // // * `replacement` the new {NativeWatcher} instance that a live {Watcher} instance should reattach to instead. // * `watchedPath` absolute path watched by the new {NativeWatcher}. - reattachTo (replacement, watchedPath, options) { - this.emitter.emit('should-detach', {replacement, watchedPath, options}) + reattachTo(replacement, watchedPath, options) { + this.emitter.emit('should-detach', { replacement, watchedPath, options }); } // Private: Stop the native watcher and release any operating system resources associated with it. // // Has no effect if the watcher is not running. - async stop () { + async stop() { if (this.state !== WATCHER_STATE.RUNNING) { - return + return; } - this.state = WATCHER_STATE.STOPPING - this.emitter.emit('will-stop') + this.state = WATCHER_STATE.STOPPING; + this.emitter.emit('will-stop'); - await this.doStop() + await this.doStop(); - this.state = WATCHER_STATE.STOPPED + this.state = WATCHER_STATE.STOPPED; - this.emitter.emit('did-stop') + this.emitter.emit('did-stop'); } - doStop () { - return Promise.resolve() + doStop() { + return Promise.resolve(); } // Private: Detach any event subscribers. - dispose () { - this.emitter.dispose() + dispose() { + this.emitter.dispose(); } // Private: Callback function invoked by the native watcher when a debounced group of filesystem events arrive. // Normalize and re-broadcast them to any subscribers. // // * `events` An Array of filesystem events. - onEvents (events) { - this.emitter.emit('did-change', events) + onEvents(events) { + this.emitter.emit('did-change', events); } // Private: Callback function invoked by the native watcher when an error occurs. // // * `err` The native filesystem error. - onError (err) { - this.emitter.emit('did-error', err) + onError(err) { + this.emitter.emit('did-error', err); } } // Private: Emulate a "filesystem watcher" by subscribing to Atom events like buffers being saved. This will miss // any changes made to files outside of Atom, but it also has no overhead. class AtomNativeWatcher extends NativeWatcher { - async doStart () { + async doStart() { const getRealPath = givenPath => { if (!givenPath) { - return Promise.resolve(null) + return Promise.resolve(null); } return new Promise(resolve => { fs.realpath(givenPath, (err, resolvedPath) => { - err ? resolve(null) : resolve(resolvedPath) - }) + err ? resolve(null) : resolve(resolvedPath); + }); + }); + }; + + this.subs.add( + atom.workspace.observeTextEditors(async editor => { + let realPath = await getRealPath(editor.getPath()); + if (!realPath || !realPath.startsWith(this.normalizedPath)) { + return; + } + + const announce = (action, oldPath) => { + const payload = { action, path: realPath }; + if (oldPath) payload.oldPath = oldPath; + this.onEvents([payload]); + }; + + const buffer = editor.getBuffer(); + + this.subs.add(buffer.onDidConflict(() => announce('modified'))); + this.subs.add(buffer.onDidReload(() => announce('modified'))); + this.subs.add( + buffer.onDidSave(event => { + if (event.path === realPath) { + announce('modified'); + } else { + const oldPath = realPath; + realPath = event.path; + announce('renamed', oldPath); + } + }) + ); + + this.subs.add(buffer.onDidDelete(() => announce('deleted'))); + + this.subs.add( + buffer.onDidChangePath(newPath => { + if (newPath !== this.normalizedPath) { + const oldPath = this.normalizedPath; + this.normalizedPath = newPath; + announce('renamed', oldPath); + } + }) + ); }) - } - - this.subs.add(atom.workspace.observeTextEditors(async editor => { - let realPath = await getRealPath(editor.getPath()) - if (!realPath || !realPath.startsWith(this.normalizedPath)) { - return - } - - const announce = (action, oldPath) => { - const payload = {action, path: realPath} - if (oldPath) payload.oldPath = oldPath - this.onEvents([payload]) - } - - const buffer = editor.getBuffer() - - this.subs.add(buffer.onDidConflict(() => announce('modified'))) - this.subs.add(buffer.onDidReload(() => announce('modified'))) - this.subs.add(buffer.onDidSave(event => { - if (event.path === realPath) { - announce('modified') - } else { - const oldPath = realPath - realPath = event.path - announce('renamed', oldPath) - } - })) - - this.subs.add(buffer.onDidDelete(() => announce('deleted'))) - - this.subs.add(buffer.onDidChangePath(newPath => { - if (newPath !== this.normalizedPath) { - const oldPath = this.normalizedPath - this.normalizedPath = newPath - announce('renamed', oldPath) - } - })) - })) + ); // Giant-ass brittle hack to hook files (and eventually directories) created from the TreeView. - const treeViewPackage = await atom.packages.getLoadedPackage('tree-view') - if (!treeViewPackage) return - await treeViewPackage.activationPromise - const treeViewModule = treeViewPackage.mainModule - if (!treeViewModule) return - const treeView = treeViewModule.getTreeViewInstance() + const treeViewPackage = await atom.packages.getLoadedPackage('tree-view'); + if (!treeViewPackage) return; + await treeViewPackage.activationPromise; + const treeViewModule = treeViewPackage.mainModule; + if (!treeViewModule) return; + const treeView = treeViewModule.getTreeViewInstance(); const isOpenInEditor = async eventPath => { const openPaths = await Promise.all( - atom.workspace.getTextEditors().map(editor => getRealPath(editor.getPath())) - ) - return openPaths.includes(eventPath) - } + atom.workspace + .getTextEditors() + .map(editor => getRealPath(editor.getPath())) + ); + return openPaths.includes(eventPath); + }; - this.subs.add(treeView.onFileCreated(async event => { - const realPath = await getRealPath(event.path) - if (!realPath) return + this.subs.add( + treeView.onFileCreated(async event => { + const realPath = await getRealPath(event.path); + if (!realPath) return; - this.onEvents([{action: 'added', path: realPath}]) - })) + this.onEvents([{ action: 'added', path: realPath }]); + }) + ); - this.subs.add(treeView.onEntryDeleted(async event => { - const realPath = await getRealPath(event.path) - if (!realPath || await isOpenInEditor(realPath)) return + this.subs.add( + treeView.onEntryDeleted(async event => { + const realPath = await getRealPath(event.path); + if (!realPath || (await isOpenInEditor(realPath))) return; - this.onEvents([{action: 'deleted', path: realPath}]) - })) + this.onEvents([{ action: 'deleted', path: realPath }]); + }) + ); - this.subs.add(treeView.onEntryMoved(async event => { - const [realNewPath, realOldPath] = await Promise.all([ - getRealPath(event.newPath), - getRealPath(event.initialPath) - ]) - if (!realNewPath || !realOldPath || await isOpenInEditor(realNewPath) || await isOpenInEditor(realOldPath)) return + this.subs.add( + treeView.onEntryMoved(async event => { + const [realNewPath, realOldPath] = await Promise.all([ + getRealPath(event.newPath), + getRealPath(event.initialPath) + ]); + if ( + !realNewPath || + !realOldPath || + (await isOpenInEditor(realNewPath)) || + (await isOpenInEditor(realOldPath)) + ) + return; - this.onEvents([{action: 'renamed', path: realNewPath, oldPath: realOldPath}]) - })) + this.onEvents([ + { action: 'renamed', path: realNewPath, oldPath: realOldPath } + ]); + }) + ); } } // Private: Implement a native watcher by translating events from an NSFW watcher. class NSFWNativeWatcher extends NativeWatcher { - async doStart (rootPath, eventCallback, errorCallback) { + async doStart(rootPath, eventCallback, errorCallback) { const handler = events => { - this.onEvents(events.map(event => { - const action = ACTION_MAP.get(event.action) || `unexpected (${event.action})` - const payload = {action} + this.onEvents( + events.map(event => { + const action = + ACTION_MAP.get(event.action) || `unexpected (${event.action})`; + const payload = { action }; - if (event.file) { - payload.path = path.join(event.directory, event.file) - } else { - payload.oldPath = path.join(event.directory, event.oldFile) - payload.path = path.join(event.directory, event.newFile) - } + if (event.file) { + payload.path = path.join(event.directory, event.file); + } else { + payload.oldPath = path.join(event.directory, event.oldFile); + payload.path = path.join(event.directory, event.newFile); + } - return payload - })) - } + return payload; + }) + ); + }; - this.watcher = await nsfw( - this.normalizedPath, - handler, - {debounceMS: 100, errorCallback: this.onError} - ) + this.watcher = await nsfw(this.normalizedPath, handler, { + debounceMS: 100, + errorCallback: this.onError + }); - await this.watcher.start() + await this.watcher.start(); } - doStop () { - return this.watcher.stop() + doStop() { + return this.watcher.stop(); } } @@ -338,55 +361,54 @@ class NSFWNativeWatcher extends NativeWatcher { // `"deleted"`, or `"renamed"`; `path`, a {String} containing the absolute path to the filesystem entry that was acted // upon; for rename events only, `oldPath`, a {String} containing the filesystem entry's former absolute path. class PathWatcher { - // Private: Instantiate a new PathWatcher. Call {watchPath} instead. // // * `nativeWatcherRegistry` {NativeWatcherRegistry} used to find and consolidate redundant watchers. // * `watchedPath` {String} containing the absolute path to the root of the watched filesystem tree. // * `options` See {watchPath} for options. // - constructor (nativeWatcherRegistry, watchedPath, options) { - this.watchedPath = watchedPath - this.nativeWatcherRegistry = nativeWatcherRegistry + constructor(nativeWatcherRegistry, watchedPath, options) { + this.watchedPath = watchedPath; + this.nativeWatcherRegistry = nativeWatcherRegistry; - this.normalizedPath = null - this.native = null - this.changeCallbacks = new Map() + this.normalizedPath = null; + this.native = null; + this.changeCallbacks = new Map(); this.attachedPromise = new Promise(resolve => { - this.resolveAttachedPromise = resolve - }) + this.resolveAttachedPromise = resolve; + }); this.startPromise = new Promise((resolve, reject) => { - this.resolveStartPromise = resolve - this.rejectStartPromise = reject - }) + this.resolveStartPromise = resolve; + this.rejectStartPromise = reject; + }); this.normalizedPathPromise = new Promise((resolve, reject) => { fs.realpath(watchedPath, (err, real) => { if (err) { - reject(err) - return + reject(err); + return; } - this.normalizedPath = real - resolve(real) - }) - }) - this.normalizedPathPromise.catch(err => this.rejectStartPromise(err)) + this.normalizedPath = real; + resolve(real); + }); + }); + this.normalizedPathPromise.catch(err => this.rejectStartPromise(err)); - this.emitter = new Emitter() - this.subs = new CompositeDisposable() + this.emitter = new Emitter(); + this.subs = new CompositeDisposable(); } // Private: Return a {Promise} that will resolve with the normalized root path. - getNormalizedPathPromise () { - return this.normalizedPathPromise + getNormalizedPathPromise() { + return this.normalizedPathPromise; } // Private: Return a {Promise} that will resolve the first time that this watcher is attached to a native watcher. - getAttachedPromise () { - return this.attachedPromise + getAttachedPromise() { + return this.attachedPromise; } // Extended: Return a {Promise} that will resolve when the underlying native watcher is ready to begin sending events. @@ -412,8 +434,8 @@ class PathWatcher { // }) // }) // ``` - getStartPromise () { - return this.startPromise + getStartPromise() { + return this.startPromise; } // Private: Attach another {Function} to be called with each batch of filesystem events. See {watchPath} for the @@ -422,24 +444,26 @@ class PathWatcher { // * `callback` {Function} to be called with each batch of filesystem events. // // Returns a {Disposable} that will stop the underlying watcher when all callbacks mapped to it have been disposed. - onDidChange (callback) { + onDidChange(callback) { if (this.native) { - const sub = this.native.onDidChange(events => this.onNativeEvents(events, callback)) - this.changeCallbacks.set(callback, sub) + const sub = this.native.onDidChange(events => + this.onNativeEvents(events, callback) + ); + this.changeCallbacks.set(callback, sub); - this.native.start() + this.native.start(); } else { // Attach to a new native listener and retry this.nativeWatcherRegistry.attach(this).then(() => { - this.onDidChange(callback) - }) + this.onDidChange(callback); + }); } return new Disposable(() => { - const sub = this.changeCallbacks.get(callback) - this.changeCallbacks.delete(callback) - sub.dispose() - }) + const sub = this.changeCallbacks.get(callback); + this.changeCallbacks.delete(callback); + sub.dispose(); + }); } // Extended: Invoke a {Function} when any errors related to this watcher are reported. @@ -448,242 +472,271 @@ class PathWatcher { // * `err` An {Error} describing the failure condition. // // Returns a {Disposable}. - onDidError (callback) { - return this.emitter.on('did-error', callback) + onDidError(callback) { + return this.emitter.on('did-error', callback); } // Private: Wire this watcher to an operating system-level native watcher implementation. - attachToNative (native) { - this.subs.dispose() - this.native = native + attachToNative(native) { + this.subs.dispose(); + this.native = native; if (native.isRunning()) { - this.resolveStartPromise() + this.resolveStartPromise(); } else { - this.subs.add(native.onDidStart(() => { - this.resolveStartPromise() - })) + this.subs.add( + native.onDidStart(() => { + this.resolveStartPromise(); + }) + ); } // Transfer any native event subscriptions to the new NativeWatcher. for (const [callback, formerSub] of this.changeCallbacks) { - const newSub = native.onDidChange(events => this.onNativeEvents(events, callback)) - this.changeCallbacks.set(callback, newSub) - formerSub.dispose() + const newSub = native.onDidChange(events => + this.onNativeEvents(events, callback) + ); + this.changeCallbacks.set(callback, newSub); + formerSub.dispose(); } - this.subs.add(native.onDidError(err => { - this.emitter.emit('did-error', err) - })) + this.subs.add( + native.onDidError(err => { + this.emitter.emit('did-error', err); + }) + ); - this.subs.add(native.onShouldDetach(({replacement, watchedPath}) => { - if (this.native === native && replacement !== native && this.normalizedPath.startsWith(watchedPath)) { - this.attachToNative(replacement) - } - })) + this.subs.add( + native.onShouldDetach(({ replacement, watchedPath }) => { + if ( + this.native === native && + replacement !== native && + this.normalizedPath.startsWith(watchedPath) + ) { + this.attachToNative(replacement); + } + }) + ); - this.subs.add(native.onWillStop(() => { - if (this.native === native) { - this.subs.dispose() - this.native = null - } - })) + this.subs.add( + native.onWillStop(() => { + if (this.native === native) { + this.subs.dispose(); + this.native = null; + } + }) + ); - this.resolveAttachedPromise() + this.resolveAttachedPromise(); } // Private: Invoked when the attached native watcher creates a batch of native filesystem events. The native watcher's // events may include events for paths above this watcher's root path, so filter them to only include the relevant // ones, then re-broadcast them to our subscribers. - onNativeEvents (events, callback) { - const isWatchedPath = eventPath => eventPath.startsWith(this.normalizedPath) + onNativeEvents(events, callback) { + const isWatchedPath = eventPath => + eventPath.startsWith(this.normalizedPath); - const filtered = [] + const filtered = []; for (let i = 0; i < events.length; i++) { - const event = events[i] + const event = events[i]; if (event.action === 'renamed') { - const srcWatched = isWatchedPath(event.oldPath) - const destWatched = isWatchedPath(event.path) + const srcWatched = isWatchedPath(event.oldPath); + const destWatched = isWatchedPath(event.path); if (srcWatched && destWatched) { - filtered.push(event) + filtered.push(event); } else if (srcWatched && !destWatched) { - filtered.push({action: 'deleted', kind: event.kind, path: event.oldPath}) + filtered.push({ + action: 'deleted', + kind: event.kind, + path: event.oldPath + }); } else if (!srcWatched && destWatched) { - filtered.push({action: 'created', kind: event.kind, path: event.path}) + filtered.push({ + action: 'created', + kind: event.kind, + path: event.path + }); } } else { if (isWatchedPath(event.path)) { - filtered.push(event) + filtered.push(event); } } } if (filtered.length > 0) { - callback(filtered) + callback(filtered); } } // Extended: Unsubscribe all subscribers from filesystem events. Native resources will be released asynchronously, // but this watcher will stop broadcasting events immediately. - dispose () { + dispose() { for (const sub of this.changeCallbacks.values()) { - sub.dispose() + sub.dispose(); } - this.emitter.dispose() - this.subs.dispose() + this.emitter.dispose(); + this.subs.dispose(); } } // Private: Globally tracked state used to de-duplicate related [PathWatchers]{PathWatcher} backed by emulated Atom // events or NSFW. class PathWatcherManager { - // Private: Access the currently active manager instance, creating one if necessary. - static active () { + static active() { if (!this.activeManager) { - this.activeManager = new PathWatcherManager(atom.config.get('core.fileSystemWatcher')) - this.sub = atom.config.onDidChange('core.fileSystemWatcher', ({newValue}) => { this.transitionTo(newValue) }) + this.activeManager = new PathWatcherManager( + atom.config.get('core.fileSystemWatcher') + ); + this.sub = atom.config.onDidChange( + 'core.fileSystemWatcher', + ({ newValue }) => { + this.transitionTo(newValue); + } + ); } - return this.activeManager + return this.activeManager; } // Private: Replace the active {PathWatcherManager} with a new one that creates [NativeWatchers]{NativeWatcher} // based on the value of `setting`. - static async transitionTo (setting) { - const current = this.active() + static async transitionTo(setting) { + const current = this.active(); if (this.transitionPromise) { - await this.transitionPromise + await this.transitionPromise; } if (current.setting === setting) { - return + return; } - current.isShuttingDown = true + current.isShuttingDown = true; - let resolveTransitionPromise = () => {} + let resolveTransitionPromise = () => {}; this.transitionPromise = new Promise(resolve => { - resolveTransitionPromise = resolve - }) + resolveTransitionPromise = resolve; + }); - const replacement = new PathWatcherManager(setting) - this.activeManager = replacement + const replacement = new PathWatcherManager(setting); + this.activeManager = replacement; await Promise.all( Array.from(current.live, async ([root, native]) => { - const w = await replacement.createWatcher(root, {}, () => {}) - native.reattachTo(w.native, root, w.native.options || {}) + const w = await replacement.createWatcher(root, {}, () => {}); + native.reattachTo(w.native, root, w.native.options || {}); }) - ) + ); - current.stopAllWatchers() + current.stopAllWatchers(); - resolveTransitionPromise() - this.transitionPromise = null + resolveTransitionPromise(); + this.transitionPromise = null; } // Private: Initialize global {PathWatcher} state. - constructor (setting) { - this.setting = setting - this.live = new Map() + constructor(setting) { + this.setting = setting; + this.live = new Map(); const initLocal = NativeConstructor => { - this.nativeRegistry = new NativeWatcherRegistry( - normalizedPath => { - const nativeWatcher = new NativeConstructor(normalizedPath) + this.nativeRegistry = new NativeWatcherRegistry(normalizedPath => { + const nativeWatcher = new NativeConstructor(normalizedPath); - this.live.set(normalizedPath, nativeWatcher) - const sub = nativeWatcher.onWillStop(() => { - this.live.delete(normalizedPath) - sub.dispose() - }) + this.live.set(normalizedPath, nativeWatcher); + const sub = nativeWatcher.onWillStop(() => { + this.live.delete(normalizedPath); + sub.dispose(); + }); - return nativeWatcher - } - ) - } + return nativeWatcher; + }); + }; if (setting === 'atom') { - initLocal(AtomNativeWatcher) + initLocal(AtomNativeWatcher); } else if (setting === 'experimental') { // } else if (setting === 'poll') { // } else { - initLocal(NSFWNativeWatcher) + initLocal(NSFWNativeWatcher); } - this.isShuttingDown = false + this.isShuttingDown = false; } - useExperimentalWatcher () { - return this.setting === 'experimental' || this.setting === 'poll' + useExperimentalWatcher() { + return this.setting === 'experimental' || this.setting === 'poll'; } // Private: Create a {PathWatcher} tied to this global state. See {watchPath} for detailed arguments. - async createWatcher (rootPath, options, eventCallback) { + async createWatcher(rootPath, options, eventCallback) { if (this.isShuttingDown) { - await this.constructor.transitionPromise - return PathWatcherManager.active().createWatcher(rootPath, options, eventCallback) + await this.constructor.transitionPromise; + return PathWatcherManager.active().createWatcher( + rootPath, + options, + eventCallback + ); } if (this.useExperimentalWatcher()) { if (this.setting === 'poll') { - options.poll = true + options.poll = true; } - const w = await watcher.watchPath(rootPath, options, eventCallback) - this.live.set(rootPath, w.native) - return w + const w = await watcher.watchPath(rootPath, options, eventCallback); + this.live.set(rootPath, w.native); + return w; } - const w = new PathWatcher(this.nativeRegistry, rootPath, options) - w.onDidChange(eventCallback) - await w.getStartPromise() - return w + const w = new PathWatcher(this.nativeRegistry, rootPath, options); + w.onDidChange(eventCallback); + await w.getStartPromise(); + return w; } // Private: Directly access the {NativeWatcherRegistry}. - getRegistry () { + getRegistry() { if (this.useExperimentalWatcher()) { - return watcher.getRegistry() + return watcher.getRegistry(); } - return this.nativeRegistry + return this.nativeRegistry; } // Private: Sample watcher usage statistics. Only available for experimental watchers. - status () { + status() { if (this.useExperimentalWatcher()) { - return watcher.status() + return watcher.status(); } - return {} + return {}; } // Private: Return a {String} depicting the currently active native watchers. - print () { + print() { if (this.useExperimentalWatcher()) { - return watcher.printWatchers() + return watcher.printWatchers(); } - return this.nativeRegistry.print() + return this.nativeRegistry.print(); } // Private: Stop all living watchers. // // Returns a {Promise} that resolves when all native watcher resources are disposed. - stopAllWatchers () { + stopAllWatchers() { if (this.useExperimentalWatcher()) { - return watcher.stopAllWatchers() + return watcher.stopAllWatchers(); } - return Promise.all( - Array.from(this.live, ([, w]) => w.stop()) - ) + return Promise.all(Array.from(this.live, ([, w]) => w.stop())); } } @@ -726,34 +779,38 @@ class PathWatcherManager { // disposable.dispose() // ``` // -function watchPath (rootPath, options, eventCallback) { - return PathWatcherManager.active().createWatcher(rootPath, options, eventCallback) +function watchPath(rootPath, options, eventCallback) { + return PathWatcherManager.active().createWatcher( + rootPath, + options, + eventCallback + ); } // Private: Return a Promise that resolves when all {NativeWatcher} instances associated with a FileSystemManager // have stopped listening. This is useful for `afterEach()` blocks in unit tests. -function stopAllWatchers () { - return PathWatcherManager.active().stopAllWatchers() +function stopAllWatchers() { + return PathWatcherManager.active().stopAllWatchers(); } // Private: Show the currently active native watchers in a formatted {String}. -watchPath.printWatchers = function () { - return PathWatcherManager.active().print() -} +watchPath.printWatchers = function() { + return PathWatcherManager.active().print(); +}; // Private: Access the active {NativeWatcherRegistry}. -watchPath.getRegistry = function () { - return PathWatcherManager.active().getRegistry() -} +watchPath.getRegistry = function() { + return PathWatcherManager.active().getRegistry(); +}; // Private: Sample usage statistics for the active watcher. -watchPath.status = function () { - return PathWatcherManager.active().status() -} +watchPath.status = function() { + return PathWatcherManager.active().status(); +}; // Private: Configure @atom/watcher ("experimental") directly. -watchPath.configure = function (...args) { - return watcher.configure(...args) -} +watchPath.configure = function(...args) { + return watcher.configure(...args); +}; -module.exports = {watchPath, stopAllWatchers} +module.exports = { watchPath, stopAllWatchers }; diff --git a/src/project.js b/src/project.js index 3ceaa5cff..002408863 100644 --- a/src/project.js +++ b/src/project.js @@ -1,158 +1,178 @@ -const path = require('path') +const path = require('path'); -const _ = require('underscore-plus') -const fs = require('fs-plus') -const {Emitter, Disposable, CompositeDisposable} = require('event-kit') -const TextBuffer = require('text-buffer') -const {watchPath} = require('./path-watcher') +const _ = require('underscore-plus'); +const fs = require('fs-plus'); +const { Emitter, Disposable, CompositeDisposable } = require('event-kit'); +const TextBuffer = require('text-buffer'); +const { watchPath } = require('./path-watcher'); -const DefaultDirectoryProvider = require('./default-directory-provider') -const Model = require('./model') -const GitRepositoryProvider = require('./git-repository-provider') +const DefaultDirectoryProvider = require('./default-directory-provider'); +const Model = require('./model'); +const GitRepositoryProvider = require('./git-repository-provider'); // Extended: Represents a project that's opened in Atom. // // An instance of this class is always available as the `atom.project` global. -module.exports = -class Project extends Model { +module.exports = class Project extends Model { /* Section: Construction and Destruction */ - constructor ({notificationManager, packageManager, config, applicationDelegate, grammarRegistry}) { - super() - this.notificationManager = notificationManager - this.applicationDelegate = applicationDelegate - this.grammarRegistry = grammarRegistry + constructor({ + notificationManager, + packageManager, + config, + applicationDelegate, + grammarRegistry + }) { + super(); + this.notificationManager = notificationManager; + this.applicationDelegate = applicationDelegate; + this.grammarRegistry = grammarRegistry; - this.emitter = new Emitter() - this.buffers = [] - this.rootDirectories = [] - this.repositories = [] - this.directoryProviders = [] - this.defaultDirectoryProvider = new DefaultDirectoryProvider() - this.repositoryPromisesByPath = new Map() - this.repositoryProviders = [new GitRepositoryProvider(this, config)] - this.loadPromisesByPath = {} - this.watcherPromisesByPath = {} - this.retiredBufferIDs = new Set() - this.retiredBufferPaths = new Set() - this.subscriptions = new CompositeDisposable() - this.consumeServices(packageManager) + this.emitter = new Emitter(); + this.buffers = []; + this.rootDirectories = []; + this.repositories = []; + this.directoryProviders = []; + this.defaultDirectoryProvider = new DefaultDirectoryProvider(); + this.repositoryPromisesByPath = new Map(); + this.repositoryProviders = [new GitRepositoryProvider(this, config)]; + this.loadPromisesByPath = {}; + this.watcherPromisesByPath = {}; + this.retiredBufferIDs = new Set(); + this.retiredBufferPaths = new Set(); + this.subscriptions = new CompositeDisposable(); + this.consumeServices(packageManager); } - destroyed () { - for (let buffer of this.buffers.slice()) { buffer.destroy() } + destroyed() { + for (let buffer of this.buffers.slice()) { + buffer.destroy(); + } for (let repository of this.repositories.slice()) { - if (repository != null) repository.destroy() + if (repository != null) repository.destroy(); } for (let path in this.watcherPromisesByPath) { - this.watcherPromisesByPath[path].then(watcher => { watcher.dispose() }) + this.watcherPromisesByPath[path].then(watcher => { + watcher.dispose(); + }); } - this.rootDirectories = [] - this.repositories = [] + this.rootDirectories = []; + this.repositories = []; } - reset (packageManager) { - this.emitter.dispose() - this.emitter = new Emitter() + reset(packageManager) { + this.emitter.dispose(); + this.emitter = new Emitter(); - this.subscriptions.dispose() - this.subscriptions = new CompositeDisposable() + this.subscriptions.dispose(); + this.subscriptions = new CompositeDisposable(); for (let buffer of this.buffers) { - if (buffer != null) buffer.destroy() + if (buffer != null) buffer.destroy(); } - this.buffers = [] - this.setPaths([]) - this.loadPromisesByPath = {} - this.retiredBufferIDs = new Set() - this.retiredBufferPaths = new Set() - this.consumeServices(packageManager) + this.buffers = []; + this.setPaths([]); + this.loadPromisesByPath = {}; + this.retiredBufferIDs = new Set(); + this.retiredBufferPaths = new Set(); + this.consumeServices(packageManager); } - destroyUnretainedBuffers () { + destroyUnretainedBuffers() { for (let buffer of this.getBuffers()) { - if (!buffer.isRetained()) buffer.destroy() + if (!buffer.isRetained()) buffer.destroy(); } } // Layers the contents of a project's file's config // on top of the current global config. - replace (projectSpecification) { + replace(projectSpecification) { if (projectSpecification == null) { - atom.config.clearProjectSettings() - this.setPaths([]) + atom.config.clearProjectSettings(); + this.setPaths([]); } else { if (projectSpecification.originPath == null) { - return + return; } // If no path is specified, set to directory of originPath. if (!Array.isArray(projectSpecification.paths)) { - projectSpecification.paths = [path.dirname(projectSpecification.originPath)] + projectSpecification.paths = [ + path.dirname(projectSpecification.originPath) + ]; } - atom.config.resetProjectSettings(projectSpecification.config, projectSpecification.originPath) - this.setPaths(projectSpecification.paths) + atom.config.resetProjectSettings( + projectSpecification.config, + projectSpecification.originPath + ); + this.setPaths(projectSpecification.paths); } - this.emitter.emit('did-replace', projectSpecification) + this.emitter.emit('did-replace', projectSpecification); } - onDidReplace (callback) { - return this.emitter.on('did-replace', callback) + onDidReplace(callback) { + return this.emitter.on('did-replace', callback); } /* Section: Serialization */ - deserialize (state) { - this.retiredBufferIDs = new Set() - this.retiredBufferPaths = new Set() + deserialize(state) { + this.retiredBufferIDs = new Set(); + this.retiredBufferPaths = new Set(); - const handleBufferState = (bufferState) => { + const handleBufferState = bufferState => { if (bufferState.shouldDestroyOnFileDelete == null) { - bufferState.shouldDestroyOnFileDelete = () => atom.config.get('core.closeDeletedFileTabs') + bufferState.shouldDestroyOnFileDelete = () => + atom.config.get('core.closeDeletedFileTabs'); } // Use a little guilty knowledge of the way TextBuffers are serialized. // This allows TextBuffers that have never been saved (but have filePaths) to be deserialized, but prevents // TextBuffers backed by files that have been deleted from being saved. - bufferState.mustExist = bufferState.digestWhenLastPersisted !== false + bufferState.mustExist = bufferState.digestWhenLastPersisted !== false; - return TextBuffer.deserialize(bufferState).catch((_) => { - this.retiredBufferIDs.add(bufferState.id) - this.retiredBufferPaths.add(bufferState.filePath) - return null - }) - } + return TextBuffer.deserialize(bufferState).catch(_ => { + this.retiredBufferIDs.add(bufferState.id); + this.retiredBufferPaths.add(bufferState.filePath); + return null; + }); + }; - const bufferPromises = [] + const bufferPromises = []; for (let bufferState of state.buffers) { - bufferPromises.push(handleBufferState(bufferState)) + bufferPromises.push(handleBufferState(bufferState)); } return Promise.all(bufferPromises).then(buffers => { - this.buffers = buffers.filter(Boolean) + this.buffers = buffers.filter(Boolean); for (let buffer of this.buffers) { - this.grammarRegistry.maintainLanguageMode(buffer) - this.subscribeToBuffer(buffer) + this.grammarRegistry.maintainLanguageMode(buffer); + this.subscribeToBuffer(buffer); } - this.setPaths(state.paths || [], {mustExist: true, exact: true}) - }) + this.setPaths(state.paths || [], { mustExist: true, exact: true }); + }); } - serialize (options = {}) { + serialize(options = {}) { return { deserializer: 'Project', paths: this.getPaths(), - buffers: _.compact(this.buffers.map(function (buffer) { - if (buffer.isRetained()) { - const isUnloading = options.isUnloading === true - return buffer.serialize({markerLayers: isUnloading, history: isUnloading}) - } - })) - } + buffers: _.compact( + this.buffers.map(function(buffer) { + if (buffer.isRetained()) { + const isUnloading = options.isUnloading === true; + return buffer.serialize({ + markerLayers: isUnloading, + history: isUnloading + }); + } + }) + ) + }; } /* @@ -165,8 +185,8 @@ class Project extends Model { // * `projectPaths` An {Array} of {String} project paths. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangePaths (callback) { - return this.emitter.on('did-change-paths', callback) + onDidChangePaths(callback) { + return this.emitter.on('did-change-paths', callback); } // Public: Invoke the given callback when a text buffer is added to the @@ -176,8 +196,8 @@ class Project extends Model { // * `buffer` A {TextBuffer} item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddBuffer (callback) { - return this.emitter.on('did-add-buffer', callback) + onDidAddBuffer(callback) { + return this.emitter.on('did-add-buffer', callback); } // Public: Invoke the given callback with all current and future text @@ -187,9 +207,11 @@ class Project extends Model { // * `buffer` A {TextBuffer} item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeBuffers (callback) { - for (let buffer of this.getBuffers()) { callback(buffer) } - return this.onDidAddBuffer(callback) + observeBuffers(callback) { + for (let buffer of this.getBuffers()) { + callback(buffer); + } + return this.onDidAddBuffer(callback); } // Extended: Invoke a callback when a filesystem change occurs within any open @@ -230,8 +252,8 @@ class Project extends Model { // former absolute path. // // Returns a {Disposable} to manage this event subscription. - onDidChangeFiles (callback) { - return this.emitter.on('did-change-files', callback) + onDidChangeFiles(callback) { + return this.emitter.on('did-change-files', callback); } // Public: Invoke the given callback with all current and future @@ -244,14 +266,14 @@ class Project extends Model { // // Returns a {Disposable} on which `.dispose()` can be called to // unsubscribe. - observeRepositories (callback) { + observeRepositories(callback) { for (const repo of this.repositories) { if (repo != null) { - callback(repo) + callback(repo); } } - return this.onDidAddRepository(callback) + return this.onDidAddRepository(callback); } // Public: Invoke the given callback when a repository is added to the @@ -262,8 +284,8 @@ class Project extends Model { // // Returns a {Disposable} on which `.dispose()` can be called to // unsubscribe. - onDidAddRepository (callback) { - return this.emitter.on('did-add-repository', callback) + onDidAddRepository(callback) { + return this.emitter.on('did-add-repository', callback); } /* @@ -280,8 +302,8 @@ class Project extends Model { // Promise.all(atom.project.getDirectories().map( // atom.project.repositoryForDirectory.bind(atom.project))) // ``` - getRepositories () { - return this.repositories + getRepositories() { + return this.repositories; } // Public: Get the repository for a given directory asynchronously. @@ -291,31 +313,34 @@ class Project extends Model { // Returns a {Promise} that resolves with either: // * {GitRepository} if a repository can be created for the given directory // * `null` if no repository can be created for the given directory. - repositoryForDirectory (directory) { - const pathForDirectory = directory.getRealPathSync() - let promise = this.repositoryPromisesByPath.get(pathForDirectory) + repositoryForDirectory(directory) { + const pathForDirectory = directory.getRealPathSync(); + let promise = this.repositoryPromisesByPath.get(pathForDirectory); if (!promise) { - const promises = this.repositoryProviders.map((provider) => + const promises = this.repositoryProviders.map(provider => provider.repositoryForDirectory(directory) - ) - promise = Promise.all(promises).then((repositories) => { - const repo = repositories.find((repo) => repo != null) || null + ); + promise = Promise.all(promises).then(repositories => { + const repo = repositories.find(repo => repo != null) || null; // If no repository is found, remove the entry for the directory in // @repositoryPromisesByPath in case some other RepositoryProvider is // registered in the future that could supply a Repository for the // directory. - if (repo == null) this.repositoryPromisesByPath.delete(pathForDirectory) + if (repo == null) + this.repositoryPromisesByPath.delete(pathForDirectory); if (repo && repo.onDidDestroy) { - repo.onDidDestroy(() => this.repositoryPromisesByPath.delete(pathForDirectory)) + repo.onDidDestroy(() => + this.repositoryPromisesByPath.delete(pathForDirectory) + ); } - return repo - }) - this.repositoryPromisesByPath.set(pathForDirectory, promise) + return repo; + }); + this.repositoryPromisesByPath.set(pathForDirectory, promise); } - return promise + return promise; } /* @@ -324,8 +349,8 @@ class Project extends Model { // Public: Get an {Array} of {String}s containing the paths of the project's // directories. - getPaths () { - return this.rootDirectories.map((rootDirectory) => rootDirectory.getPath()) + getPaths() { + return this.rootDirectories.map(rootDirectory => rootDirectory.getPath()); } // Public: Set the paths of the project's directories. @@ -336,37 +361,43 @@ class Project extends Model { // do exist will still be added to the project. Default: `false`. // * `exact` If `true`, only add a `projectPath` if it names an existing directory. If `false` and any `projectPath` // is a file or does not exist, its parent directory will be added instead. Default: `false`. - setPaths (projectPaths, options = {}) { + setPaths(projectPaths, options = {}) { for (let repository of this.repositories) { - if (repository != null) repository.destroy() + if (repository != null) repository.destroy(); } - this.rootDirectories = [] - this.repositories = [] + this.rootDirectories = []; + this.repositories = []; for (let path in this.watcherPromisesByPath) { - this.watcherPromisesByPath[path].then(watcher => { watcher.dispose() }) + this.watcherPromisesByPath[path].then(watcher => { + watcher.dispose(); + }); } - this.watcherPromisesByPath = {} + this.watcherPromisesByPath = {}; - const missingProjectPaths = [] + const missingProjectPaths = []; for (let projectPath of projectPaths) { try { - this.addPath(projectPath, {emitEvent: false, mustExist: true, exact: options.exact === true}) + this.addPath(projectPath, { + emitEvent: false, + mustExist: true, + exact: options.exact === true + }); } catch (e) { if (e.missingProjectPaths != null) { - missingProjectPaths.push(...e.missingProjectPaths) + missingProjectPaths.push(...e.missingProjectPaths); } else { - throw e + throw e; } } } - this.emitter.emit('did-change-paths', projectPaths) + this.emitter.emit('did-change-paths', projectPaths); - if ((options.mustExist === true) && (missingProjectPaths.length > 0)) { - const err = new Error('One or more project directories do not exist') - err.missingProjectPaths = missingProjectPaths - throw err + if (options.mustExist === true && missingProjectPaths.length > 0) { + const err = new Error('One or more project directories do not exist'); + err.missingProjectPaths = missingProjectPaths; + throw err; } } @@ -378,37 +409,39 @@ class Project extends Model { // not exist is ignored. Default: `false`. // * `exact` If `true`, only add `projectPath` if it names an existing directory. If `false`, if `projectPath` is a // a file or does not exist, its parent directory will be added instead. - addPath (projectPath, options = {}) { - const directory = this.getDirectoryForProjectPath(projectPath) - let ok = true + addPath(projectPath, options = {}) { + const directory = this.getDirectoryForProjectPath(projectPath); + let ok = true; if (options.exact === true) { - ok = (directory.getPath() === projectPath) + ok = directory.getPath() === projectPath; } - ok = ok && directory.existsSync() + ok = ok && directory.existsSync(); if (!ok) { if (options.mustExist === true) { - const err = new Error(`Project directory ${directory} does not exist`) - err.missingProjectPaths = [projectPath] - throw err + const err = new Error(`Project directory ${directory} does not exist`); + err.missingProjectPaths = [projectPath]; + throw err; } else { - return + return; } } for (let existingDirectory of this.getDirectories()) { - if (existingDirectory.getPath() === directory.getPath()) { return } + if (existingDirectory.getPath() === directory.getPath()) { + return; + } } - this.rootDirectories.push(directory) + this.rootDirectories.push(directory); const didChangeCallback = events => { // Stop event delivery immediately on removal of a rootDirectory, even if its watcher // promise has yet to resolve at the time of removal if (this.rootDirectories.includes(directory)) { - this.emitter.emit('did-change-files', events) + this.emitter.emit('did-change-files', events); } - } + }; // We'll use the directory's custom onDidChangeFiles callback, if available. // CustomDirectory::onDidChangeFiles should match the signature of @@ -416,49 +449,55 @@ class Project extends Model { this.watcherPromisesByPath[directory.getPath()] = directory.onDidChangeFiles != null ? Promise.resolve(directory.onDidChangeFiles(didChangeCallback)) - : watchPath(directory.getPath(), {}, didChangeCallback) + : watchPath(directory.getPath(), {}, didChangeCallback); for (let watchedPath in this.watcherPromisesByPath) { if (!this.rootDirectories.find(dir => dir.getPath() === watchedPath)) { - this.watcherPromisesByPath[watchedPath].then(watcher => { watcher.dispose() }) + this.watcherPromisesByPath[watchedPath].then(watcher => { + watcher.dispose(); + }); } } - let repo = null + let repo = null; for (let provider of this.repositoryProviders) { if (provider.repositoryForDirectorySync) { - repo = provider.repositoryForDirectorySync(directory) + repo = provider.repositoryForDirectorySync(directory); + } + if (repo) { + break; } - if (repo) { break } } - this.repositories.push(repo != null ? repo : null) + this.repositories.push(repo != null ? repo : null); if (repo != null) { - this.emitter.emit('did-add-repository', repo) + this.emitter.emit('did-add-repository', repo); } if (options.emitEvent !== false) { - this.emitter.emit('did-change-paths', this.getPaths()) + this.emitter.emit('did-change-paths', this.getPaths()); } } - getProvidedDirectoryForProjectPath (projectPath) { + getProvidedDirectoryForProjectPath(projectPath) { for (let provider of this.directoryProviders) { if (typeof provider.directoryForURISync === 'function') { - const directory = provider.directoryForURISync(projectPath) + const directory = provider.directoryForURISync(projectPath); if (directory) { - return directory + return directory; } } } - return null + return null; } - getDirectoryForProjectPath (projectPath) { - let directory = this.getProvidedDirectoryForProjectPath(projectPath) + getDirectoryForProjectPath(projectPath) { + let directory = this.getProvidedDirectoryForProjectPath(projectPath); if (directory == null) { - directory = this.defaultDirectoryProvider.directoryForURISync(projectPath) + directory = this.defaultDirectoryProvider.directoryForURISync( + projectPath + ); } - return directory + return directory; } // Extended: Access a {Promise} that resolves when the filesystem watcher associated with a project @@ -472,71 +511,78 @@ class Project extends Model { // Returns a {Promise} that resolves with the {PathWatcher} associated with this project root // once it has initialized and is ready to start sending events. The Promise will reject with // an error instead if `projectPath` is not currently a root directory. - getWatcherPromise (projectPath) { - return this.watcherPromisesByPath[projectPath] || + getWatcherPromise(projectPath) { + return ( + this.watcherPromisesByPath[projectPath] || Promise.reject(new Error(`${projectPath} is not a project root`)) + ); } // Public: remove a path from the project's list of root paths. // // * `projectPath` {String} The path to remove. - removePath (projectPath) { + removePath(projectPath) { // The projectPath may be a URI, in which case it should not be normalized. if (!this.getPaths().includes(projectPath)) { - projectPath = this.defaultDirectoryProvider.normalizePath(projectPath) + projectPath = this.defaultDirectoryProvider.normalizePath(projectPath); } - let indexToRemove = null + let indexToRemove = null; for (let i = 0; i < this.rootDirectories.length; i++) { - const directory = this.rootDirectories[i] + const directory = this.rootDirectories[i]; if (directory.getPath() === projectPath) { - indexToRemove = i - break + indexToRemove = i; + break; } } if (indexToRemove != null) { - this.rootDirectories.splice(indexToRemove, 1) - const [removedRepository] = this.repositories.splice(indexToRemove, 1) + this.rootDirectories.splice(indexToRemove, 1); + const [removedRepository] = this.repositories.splice(indexToRemove, 1); if (!this.repositories.includes(removedRepository)) { - if (removedRepository) removedRepository.destroy() + if (removedRepository) removedRepository.destroy(); } if (this.watcherPromisesByPath[projectPath] != null) { - this.watcherPromisesByPath[projectPath].then(w => w.dispose()) + this.watcherPromisesByPath[projectPath].then(w => w.dispose()); } - delete this.watcherPromisesByPath[projectPath] - this.emitter.emit('did-change-paths', this.getPaths()) - return true + delete this.watcherPromisesByPath[projectPath]; + this.emitter.emit('did-change-paths', this.getPaths()); + return true; } else { - return false + return false; } } // Public: Get an {Array} of {Directory}s associated with this project. - getDirectories () { - return this.rootDirectories + getDirectories() { + return this.rootDirectories; } - resolvePath (uri) { - if (!uri) { return } + resolvePath(uri) { + if (!uri) { + return; + } - if (uri.match(/[A-Za-z0-9+-.]+:\/\//)) { // leave path alone if it has a scheme - return uri + if (uri.match(/[A-Za-z0-9+-.]+:\/\//)) { + // leave path alone if it has a scheme + return uri; } else { - let projectPath + let projectPath; if (fs.isAbsolute(uri)) { - return this.defaultDirectoryProvider.normalizePath(fs.resolveHome(uri)) - // TODO: what should we do here when there are multiple directories? + return this.defaultDirectoryProvider.normalizePath(fs.resolveHome(uri)); + // TODO: what should we do here when there are multiple directories? } else if ((projectPath = this.getPaths()[0])) { - return this.defaultDirectoryProvider.normalizePath(fs.resolveHome(path.join(projectPath, uri))) + return this.defaultDirectoryProvider.normalizePath( + fs.resolveHome(path.join(projectPath, uri)) + ); } else { - return undefined + return undefined; } } } - relativize (fullPath) { - return this.relativizePath(fullPath)[1] + relativize(fullPath) { + return this.relativizePath(fullPath)[1]; } // Public: Get the path to the project directory that contains the given path, @@ -549,17 +595,17 @@ class Project extends Model { // given path, or `null` if none is found. // * `relativePath` {String} The relative path from the project directory to // the given path. - relativizePath (fullPath) { - let result = [null, fullPath] + relativizePath(fullPath) { + let result = [null, fullPath]; if (fullPath != null) { for (let rootDirectory of this.rootDirectories) { - const relativePath = rootDirectory.relativize(fullPath) - if ((relativePath != null) && (relativePath.length < result[1].length)) { - result = [rootDirectory.getPath(), relativePath] + const relativePath = rootDirectory.relativize(fullPath); + if (relativePath != null && relativePath.length < result[1].length) { + result = [rootDirectory.getPath(), relativePath]; } } } - return result + return result; } // Public: Determines whether the given path (real or symbolic) is inside the @@ -589,76 +635,92 @@ class Project extends Model { // * `pathToCheck` {String} path // // Returns whether the path is inside the project's root directory. - contains (pathToCheck) { - return this.rootDirectories.some(dir => dir.contains(pathToCheck)) + contains(pathToCheck) { + return this.rootDirectories.some(dir => dir.contains(pathToCheck)); } /* Section: Private */ - consumeServices ({serviceHub}) { - serviceHub.consume( - 'atom.directory-provider', - '^0.1.0', - provider => { - this.directoryProviders.unshift(provider) - return new Disposable(() => { - return this.directoryProviders.splice(this.directoryProviders.indexOf(provider), 1) - }) - }) + consumeServices({ serviceHub }) { + serviceHub.consume('atom.directory-provider', '^0.1.0', provider => { + this.directoryProviders.unshift(provider); + return new Disposable(() => { + return this.directoryProviders.splice( + this.directoryProviders.indexOf(provider), + 1 + ); + }); + }); return serviceHub.consume( 'atom.repository-provider', '^0.1.0', provider => { - this.repositoryProviders.unshift(provider) - if (this.repositories.includes(null)) { this.setPaths(this.getPaths()) } + this.repositoryProviders.unshift(provider); + if (this.repositories.includes(null)) { + this.setPaths(this.getPaths()); + } return new Disposable(() => { - return this.repositoryProviders.splice(this.repositoryProviders.indexOf(provider), 1) - }) - }) + return this.repositoryProviders.splice( + this.repositoryProviders.indexOf(provider), + 1 + ); + }); + } + ); } // Retrieves all the {TextBuffer}s in the project; that is, the // buffers for all open files. // // Returns an {Array} of {TextBuffer}s. - getBuffers () { - return this.buffers.slice() + getBuffers() { + return this.buffers.slice(); } // Is the buffer for the given path modified? - isPathModified (filePath) { - const bufferForPath = this.findBufferForPath(this.resolvePath(filePath)) - return bufferForPath && bufferForPath.isModified() + isPathModified(filePath) { + const bufferForPath = this.findBufferForPath(this.resolvePath(filePath)); + return bufferForPath && bufferForPath.isModified(); } - findBufferForPath (filePath) { - return _.find(this.buffers, buffer => buffer.getPath() === filePath) + findBufferForPath(filePath) { + return _.find(this.buffers, buffer => buffer.getPath() === filePath); } - findBufferForId (id) { - return _.find(this.buffers, buffer => buffer.getId() === id) + findBufferForId(id) { + return _.find(this.buffers, buffer => buffer.getId() === id); } // Only to be used in specs - bufferForPathSync (filePath) { - const absoluteFilePath = this.resolvePath(filePath) - if (this.retiredBufferPaths.has(absoluteFilePath)) { return null } + bufferForPathSync(filePath) { + const absoluteFilePath = this.resolvePath(filePath); + if (this.retiredBufferPaths.has(absoluteFilePath)) { + return null; + } - let existingBuffer - if (filePath) { existingBuffer = this.findBufferForPath(absoluteFilePath) } - return existingBuffer != null ? existingBuffer : this.buildBufferSync(absoluteFilePath) + let existingBuffer; + if (filePath) { + existingBuffer = this.findBufferForPath(absoluteFilePath); + } + return existingBuffer != null + ? existingBuffer + : this.buildBufferSync(absoluteFilePath); } // Only to be used when deserializing - bufferForIdSync (id) { - if (this.retiredBufferIDs.has(id)) { return null } + bufferForIdSync(id) { + if (this.retiredBufferIDs.has(id)) { + return null; + } - let existingBuffer - if (id) { existingBuffer = this.findBufferForId(id) } - return existingBuffer != null ? existingBuffer : this.buildBufferSync() + let existingBuffer; + if (id) { + existingBuffer = this.findBufferForId(id); + } + return existingBuffer != null ? existingBuffer : this.buildBufferSync(); } // Given a file path, this retrieves or creates a new {TextBuffer}. @@ -669,32 +731,36 @@ class Project extends Model { // * `filePath` A {String} representing a path. If `null`, an "Untitled" buffer is created. // // Returns a {Promise} that resolves to the {TextBuffer}. - bufferForPath (absoluteFilePath) { - let existingBuffer - if (absoluteFilePath != null) { existingBuffer = this.findBufferForPath(absoluteFilePath) } + bufferForPath(absoluteFilePath) { + let existingBuffer; + if (absoluteFilePath != null) { + existingBuffer = this.findBufferForPath(absoluteFilePath); + } if (existingBuffer) { - return Promise.resolve(existingBuffer) + return Promise.resolve(existingBuffer); } else { - return this.buildBuffer(absoluteFilePath) + return this.buildBuffer(absoluteFilePath); } } - shouldDestroyBufferOnFileDelete () { - return atom.config.get('core.closeDeletedFileTabs') + shouldDestroyBufferOnFileDelete() { + return atom.config.get('core.closeDeletedFileTabs'); } // Still needed when deserializing a tokenized buffer - buildBufferSync (absoluteFilePath) { - const params = {shouldDestroyOnFileDelete: this.shouldDestroyBufferOnFileDelete} + buildBufferSync(absoluteFilePath) { + const params = { + shouldDestroyOnFileDelete: this.shouldDestroyBufferOnFileDelete + }; - let buffer + let buffer; if (absoluteFilePath != null) { - buffer = TextBuffer.loadSync(absoluteFilePath, params) + buffer = TextBuffer.loadSync(absoluteFilePath, params); } else { - buffer = new TextBuffer(params) + buffer = new TextBuffer(params); } - this.addBuffer(buffer) - return buffer + this.addBuffer(buffer); + return buffer; } // Given a file path, this sets its {TextBuffer}. @@ -703,86 +769,102 @@ class Project extends Model { // * `text` The {String} text to use as a buffer. // // Returns a {Promise} that resolves to the {TextBuffer}. - async buildBuffer (absoluteFilePath) { - const params = {shouldDestroyOnFileDelete: this.shouldDestroyBufferOnFileDelete} + async buildBuffer(absoluteFilePath) { + const params = { + shouldDestroyOnFileDelete: this.shouldDestroyBufferOnFileDelete + }; - let buffer + let buffer; if (absoluteFilePath != null) { if (this.loadPromisesByPath[absoluteFilePath] == null) { - this.loadPromisesByPath[absoluteFilePath] = - TextBuffer.load(absoluteFilePath, params) - .then(result => { - delete this.loadPromisesByPath[absoluteFilePath] - return result - }) - .catch(error => { - delete this.loadPromisesByPath[absoluteFilePath] - throw error - }) + this.loadPromisesByPath[absoluteFilePath] = TextBuffer.load( + absoluteFilePath, + params + ) + .then(result => { + delete this.loadPromisesByPath[absoluteFilePath]; + return result; + }) + .catch(error => { + delete this.loadPromisesByPath[absoluteFilePath]; + throw error; + }); } - buffer = await this.loadPromisesByPath[absoluteFilePath] + buffer = await this.loadPromisesByPath[absoluteFilePath]; } else { - buffer = new TextBuffer(params) + buffer = new TextBuffer(params); } - this.grammarRegistry.autoAssignLanguageMode(buffer) + this.grammarRegistry.autoAssignLanguageMode(buffer); - this.addBuffer(buffer) - return buffer + this.addBuffer(buffer); + return buffer; } - addBuffer (buffer, options = {}) { - this.buffers.push(buffer) - this.subscriptions.add(this.grammarRegistry.maintainLanguageMode(buffer)) - this.subscribeToBuffer(buffer) - this.emitter.emit('did-add-buffer', buffer) - return buffer + addBuffer(buffer, options = {}) { + this.buffers.push(buffer); + this.subscriptions.add(this.grammarRegistry.maintainLanguageMode(buffer)); + this.subscribeToBuffer(buffer); + this.emitter.emit('did-add-buffer', buffer); + return buffer; } // Removes a {TextBuffer} association from the project. // // Returns the removed {TextBuffer}. - removeBuffer (buffer) { - const index = this.buffers.indexOf(buffer) - if (index !== -1) { return this.removeBufferAtIndex(index) } - } - - removeBufferAtIndex (index, options = {}) { - const [buffer] = this.buffers.splice(index, 1) - return (buffer != null ? buffer.destroy() : undefined) - } - - eachBuffer (...args) { - let subscriber - if (args.length > 1) { subscriber = args.shift() } - const callback = args.shift() - - for (let buffer of this.getBuffers()) { callback(buffer) } - if (subscriber) { - return subscriber.subscribe(this, 'buffer-created', buffer => callback(buffer)) - } else { - return this.on('buffer-created', buffer => callback(buffer)) + removeBuffer(buffer) { + const index = this.buffers.indexOf(buffer); + if (index !== -1) { + return this.removeBufferAtIndex(index); } } - subscribeToBuffer (buffer) { - buffer.onWillSave(async ({path}) => this.applicationDelegate.emitWillSavePath(path)) - buffer.onDidSave(({path}) => this.applicationDelegate.emitDidSavePath(path)) - buffer.onDidDestroy(() => this.removeBuffer(buffer)) + removeBufferAtIndex(index, options = {}) { + const [buffer] = this.buffers.splice(index, 1); + return buffer != null ? buffer.destroy() : undefined; + } + + eachBuffer(...args) { + let subscriber; + if (args.length > 1) { + subscriber = args.shift(); + } + const callback = args.shift(); + + for (let buffer of this.getBuffers()) { + callback(buffer); + } + if (subscriber) { + return subscriber.subscribe(this, 'buffer-created', buffer => + callback(buffer) + ); + } else { + return this.on('buffer-created', buffer => callback(buffer)); + } + } + + subscribeToBuffer(buffer) { + buffer.onWillSave(async ({ path }) => + this.applicationDelegate.emitWillSavePath(path) + ); + buffer.onDidSave(({ path }) => + this.applicationDelegate.emitDidSavePath(path) + ); + buffer.onDidDestroy(() => this.removeBuffer(buffer)); buffer.onDidChangePath(() => { if (!(this.getPaths().length > 0)) { - this.setPaths([path.dirname(buffer.getPath())]) + this.setPaths([path.dirname(buffer.getPath())]); } - }) - buffer.onWillThrowWatchError(({error, handle}) => { - handle() + }); + buffer.onWillThrowWatchError(({ error, handle }) => { + handle(); const message = `Unable to read file after file \`${error.eventType}\` event.` + - `Make sure you have permission to access \`${buffer.getPath()}\`.` + `Make sure you have permission to access \`${buffer.getPath()}\`.`; this.notificationManager.addWarning(message, { detail: error.message, dismissable: true - }) - }) + }); + }); } -} +}; diff --git a/src/protocol-handler-installer.js b/src/protocol-handler-installer.js index 27a272ea0..2f3c0740d 100644 --- a/src/protocol-handler-installer.js +++ b/src/protocol-handler-installer.js @@ -1,101 +1,115 @@ -const {remote} = require('electron') +const { remote } = require('electron'); -const SETTING = 'core.uriHandlerRegistration' -const PROMPT = 'prompt' -const ALWAYS = 'always' -const NEVER = 'never' +const SETTING = 'core.uriHandlerRegistration'; +const PROMPT = 'prompt'; +const ALWAYS = 'always'; +const NEVER = 'never'; -module.exports = -class ProtocolHandlerInstaller { - isSupported () { - return ['win32', 'darwin'].includes(process.platform) +module.exports = class ProtocolHandlerInstaller { + isSupported() { + return ['win32', 'darwin'].includes(process.platform); } - isDefaultProtocolClient () { - return remote.app.isDefaultProtocolClient('atom', process.execPath, ['--uri-handler', '--']) + isDefaultProtocolClient() { + return remote.app.isDefaultProtocolClient('atom', process.execPath, [ + '--uri-handler', + '--' + ]); } - setAsDefaultProtocolClient () { + setAsDefaultProtocolClient() { // This Electron API is only available on Windows and macOS. There might be some // hacks to make it work on Linux; see https://github.com/electron/electron/issues/6440 - return this.isSupported() && remote.app.setAsDefaultProtocolClient('atom', process.execPath, ['--uri-handler', '--']) + return ( + this.isSupported() && + remote.app.setAsDefaultProtocolClient('atom', process.execPath, [ + '--uri-handler', + '--' + ]) + ); } - initialize (config, notifications) { + initialize(config, notifications) { if (!this.isSupported()) { - return + return; } - const behaviorWhenNotProtocolClient = config.get(SETTING) + const behaviorWhenNotProtocolClient = config.get(SETTING); switch (behaviorWhenNotProtocolClient) { case PROMPT: if (!this.isDefaultProtocolClient()) { - this.promptToBecomeProtocolClient(config, notifications) + this.promptToBecomeProtocolClient(config, notifications); } - break + break; case ALWAYS: if (!this.isDefaultProtocolClient()) { - this.setAsDefaultProtocolClient() + this.setAsDefaultProtocolClient(); } - break + break; case NEVER: if (process.platform === 'win32') { // Only win32 supports deregistration - const Registry = require('winreg') - const commandKey = new Registry({hive: 'HKCR', key: `\\atom`}) - commandKey.destroy((_err, _val) => { /* no op */ }) + const Registry = require('winreg'); + const commandKey = new Registry({ hive: 'HKCR', key: `\\atom` }); + commandKey.destroy((_err, _val) => { + /* no op */ + }); } - break + break; default: - // Do nothing + // Do nothing } } - promptToBecomeProtocolClient (config, notifications) { - let notification + promptToBecomeProtocolClient(config, notifications) { + let notification; const withSetting = (value, fn) => { - return function () { - config.set(SETTING, value) - fn() - } - } + return function() { + config.set(SETTING, value); + fn(); + }; + }; const accept = () => { - notification.dismiss() - this.setAsDefaultProtocolClient() - } + notification.dismiss(); + this.setAsDefaultProtocolClient(); + }; const decline = () => { - notification.dismiss() - } + notification.dismiss(); + }; - notification = notifications.addInfo('Register as default atom:// URI handler?', { - dismissable: true, - icon: 'link', - description: 'Atom is not currently set as the default handler for atom:// URIs. Would you like Atom to handle ' + - 'atom:// URIs?', - buttons: [ - { - text: 'Yes', - className: 'btn btn-info btn-primary', - onDidClick: accept - }, - { - text: 'Yes, Always', - className: 'btn btn-info', - onDidClick: withSetting(ALWAYS, accept) - }, - { - text: 'No', - className: 'btn btn-info', - onDidClick: decline - }, - { - text: 'No, Never', - className: 'btn btn-info', - onDidClick: withSetting(NEVER, decline) - } - ] - }) + notification = notifications.addInfo( + 'Register as default atom:// URI handler?', + { + dismissable: true, + icon: 'link', + description: + 'Atom is not currently set as the default handler for atom:// URIs. Would you like Atom to handle ' + + 'atom:// URIs?', + buttons: [ + { + text: 'Yes', + className: 'btn btn-info btn-primary', + onDidClick: accept + }, + { + text: 'Yes, Always', + className: 'btn btn-info', + onDidClick: withSetting(ALWAYS, accept) + }, + { + text: 'No', + className: 'btn btn-info', + onDidClick: decline + }, + { + text: 'No, Never', + className: 'btn btn-info', + onDidClick: withSetting(NEVER, decline) + } + ] + } + ); } -} +}; diff --git a/src/reopen-project-list-view.js b/src/reopen-project-list-view.js index d59577684..2ae7577e7 100644 --- a/src/reopen-project-list-view.js +++ b/src/reopen-project-list-view.js @@ -1,75 +1,77 @@ -const SelectListView = require('atom-select-list') +const SelectListView = require('atom-select-list'); -module.exports = -class ReopenProjectListView { - constructor (callback) { - this.callback = callback +module.exports = class ReopenProjectListView { + constructor(callback) { + this.callback = callback; this.selectListView = new SelectListView({ emptyMessage: 'No projects in history.', itemsClassList: ['mark-active'], items: [], - filterKeyForItem: (project) => project.name, - elementForItem: (project) => { - let element = document.createElement('li') + filterKeyForItem: project => project.name, + elementForItem: project => { + let element = document.createElement('li'); if (project.name === this.currentProjectName) { - element.classList.add('active') + element.classList.add('active'); } - element.textContent = project.name - return element + element.textContent = project.name; + return element; }, - didConfirmSelection: (project) => { - this.cancel() - this.callback(project.value) + didConfirmSelection: project => { + this.cancel(); + this.callback(project.value); }, didCancelSelection: () => { - this.cancel() + this.cancel(); } - }) - this.selectListView.element.classList.add('reopen-project') + }); + this.selectListView.element.classList.add('reopen-project'); } - get element () { - return this.selectListView.element + get element() { + return this.selectListView.element; } - dispose () { - this.cancel() - return this.selectListView.destroy() + dispose() { + this.cancel(); + return this.selectListView.destroy(); } - cancel () { + cancel() { if (this.panel != null) { - this.panel.destroy() + this.panel.destroy(); } - this.panel = null - this.currentProjectName = null + this.panel = null; + this.currentProjectName = null; if (this.previouslyFocusedElement) { - this.previouslyFocusedElement.focus() - this.previouslyFocusedElement = null + this.previouslyFocusedElement.focus(); + this.previouslyFocusedElement = null; } } - attach () { - this.previouslyFocusedElement = document.activeElement + attach() { + this.previouslyFocusedElement = document.activeElement; if (this.panel == null) { - this.panel = atom.workspace.addModalPanel({item: this}) + this.panel = atom.workspace.addModalPanel({ item: this }); } - this.selectListView.focus() - this.selectListView.reset() + this.selectListView.focus(); + this.selectListView.reset(); } - async toggle () { + async toggle() { if (this.panel != null) { - this.cancel() + this.cancel(); } else { - this.currentProjectName = atom.project != null ? this.makeName(atom.project.getPaths()) : null - const projects = atom.history.getProjects().map(p => ({ name: this.makeName(p.paths), value: p.paths })) - await this.selectListView.update({items: projects}) - this.attach() + this.currentProjectName = + atom.project != null ? this.makeName(atom.project.getPaths()) : null; + const projects = atom.history + .getProjects() + .map(p => ({ name: this.makeName(p.paths), value: p.paths })); + await this.selectListView.update({ items: projects }); + this.attach(); } } - makeName (paths) { - return paths.join(', ') + makeName(paths) { + return paths.join(', '); } -} +}; diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js index e8c6f03ff..9dd695b45 100644 --- a/src/reopen-project-menu-manager.js +++ b/src/reopen-project-menu-manager.js @@ -1,122 +1,149 @@ -const {CompositeDisposable} = require('event-kit') -const path = require('path') +const { CompositeDisposable } = require('event-kit'); +const path = require('path'); -module.exports = -class ReopenProjectMenuManager { - constructor ({menu, commands, history, config, open}) { - this.menuManager = menu - this.historyManager = history - this.config = config - this.open = open - this.projects = [] +module.exports = class ReopenProjectMenuManager { + constructor({ menu, commands, history, config, open }) { + this.menuManager = menu; + this.historyManager = history; + this.config = config; + this.open = open; + this.projects = []; - this.subscriptions = new CompositeDisposable() + this.subscriptions = new CompositeDisposable(); this.subscriptions.add( history.onDidChangeProjects(this.update.bind(this)), - config.onDidChange('core.reopenProjectMenuCount', ({oldValue, newValue}) => { - this.update() - }), - commands.add('atom-workspace', { 'application:reopen-project': this.reopenProjectCommand.bind(this) }) - ) + config.onDidChange( + 'core.reopenProjectMenuCount', + ({ oldValue, newValue }) => { + this.update(); + } + ), + commands.add('atom-workspace', { + 'application:reopen-project': this.reopenProjectCommand.bind(this) + }) + ); - this.applyWindowsJumpListRemovals() + this.applyWindowsJumpListRemovals(); } - reopenProjectCommand (e) { + reopenProjectCommand(e) { if (e.detail != null && e.detail.index != null) { - this.open(this.projects[e.detail.index].paths) + this.open(this.projects[e.detail.index].paths); } else { - this.createReopenProjectListView() + this.createReopenProjectListView(); } } - createReopenProjectListView () { + createReopenProjectListView() { if (this.reopenProjectListView == null) { - const ReopenProjectListView = require('./reopen-project-list-view') + const ReopenProjectListView = require('./reopen-project-list-view'); this.reopenProjectListView = new ReopenProjectListView(paths => { if (paths != null) { - this.open(paths) + this.open(paths); } - }) + }); } - this.reopenProjectListView.toggle() + this.reopenProjectListView.toggle(); } - update () { - this.disposeProjectMenu() - this.projects = this.historyManager.getProjects().slice(0, this.config.get('core.reopenProjectMenuCount')) - const newMenu = ReopenProjectMenuManager.createProjectsMenu(this.projects) - this.lastProjectMenu = this.menuManager.add([newMenu]) - this.updateWindowsJumpList() + update() { + this.disposeProjectMenu(); + this.projects = this.historyManager + .getProjects() + .slice(0, this.config.get('core.reopenProjectMenuCount')); + const newMenu = ReopenProjectMenuManager.createProjectsMenu(this.projects); + this.lastProjectMenu = this.menuManager.add([newMenu]); + this.updateWindowsJumpList(); } - static taskDescription (paths) { - return paths.map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`).join(' ') + static taskDescription(paths) { + return paths + .map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`) + .join(' '); } // 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. - async applyWindowsJumpListRemovals () { - if (process.platform !== 'win32') return + async applyWindowsJumpListRemovals() { + if (process.platform !== 'win32') return; if (this.app === undefined) { - this.app = require('remote').app + this.app = require('remote').app; } - const removed = this.app.getJumpListSettings().removedItems.map(i => i.description) - if (removed.length === 0) return + const removed = this.app + .getJumpListSettings() + .removedItems.map(i => i.description); + if (removed.length === 0) return; for (let project of this.historyManager.getProjects()) { - if (removed.includes(ReopenProjectMenuManager.taskDescription(project.paths))) { - await this.historyManager.removeProject(project.paths) + if ( + removed.includes( + ReopenProjectMenuManager.taskDescription(project.paths) + ) + ) { + await this.historyManager.removeProject(project.paths); } } } - updateWindowsJumpList () { - if (process.platform !== 'win32') return + updateWindowsJumpList() { + if (process.platform !== 'win32') return; if (this.app === undefined) { - this.app = require('remote').app + this.app = require('remote').app; } this.app.setJumpList([ { type: 'custom', name: 'Recent Projects', - items: this.projects.map(project => - ({ - type: 'task', - title: project.paths.map(ReopenProjectMenuManager.betterBaseName).join(', '), - description: ReopenProjectMenuManager.taskDescription(project.paths), - program: process.execPath, - args: project.paths.map(path => `"${path}"`).join(' '), - iconPath: path.join(path.dirname(process.execPath), 'resources', 'cli', 'folder.ico'), - iconIndex: 0 - }) - ) + items: this.projects.map(project => ({ + type: 'task', + title: project.paths + .map(ReopenProjectMenuManager.betterBaseName) + .join(', '), + description: ReopenProjectMenuManager.taskDescription(project.paths), + program: process.execPath, + args: project.paths.map(path => `"${path}"`).join(' '), + iconPath: path.join( + path.dirname(process.execPath), + 'resources', + 'cli', + 'folder.ico' + ), + iconIndex: 0 + })) }, { type: 'recent' }, - { items: [ - {type: 'task', title: 'New Window', program: process.execPath, args: '--new-window', description: 'Opens a new Atom window'} - ]} - ]) + { + items: [ + { + type: 'task', + title: 'New Window', + program: process.execPath, + args: '--new-window', + description: 'Opens a new Atom window' + } + ] + } + ]); } - dispose () { - this.subscriptions.dispose() - this.disposeProjectMenu() + dispose() { + this.subscriptions.dispose(); + this.disposeProjectMenu(); if (this.reopenProjectListView != null) { - this.reopenProjectListView.dispose() + this.reopenProjectListView.dispose(); } } - disposeProjectMenu () { + disposeProjectMenu() { if (this.lastProjectMenu) { - this.lastProjectMenu.dispose() - this.lastProjectMenu = null + this.lastProjectMenu.dispose(); + this.lastProjectMenu = null; } } - static createProjectsMenu (projects) { + static createProjectsMenu(projects) { return { label: 'File', submenu: [ @@ -129,18 +156,18 @@ class ReopenProjectMenuManager { })) } ] - } + }; } - static createLabel (project) { + static createLabel(project) { return project.paths.length === 1 ? project.paths[0] - : project.paths.map(this.betterBaseName).join(', ') + : project.paths.map(this.betterBaseName).join(', '); } - static betterBaseName (directory) { + static betterBaseName(directory) { // Handles Windows roots better than path.basename which returns '' for 'd:' and 'd:\' - const match = directory.match(/^([a-z]:)[\\]?$/i) - return match ? match[1] + '\\' : path.basename(directory) + const match = directory.match(/^([a-z]:)[\\]?$/i); + return match ? match[1] + '\\' : path.basename(directory); } -} +}; diff --git a/src/ripgrep-directory-searcher.js b/src/ripgrep-directory-searcher.js index ffb0f8614..cdef3fc88 100644 --- a/src/ripgrep-directory-searcher.js +++ b/src/ripgrep-directory-searcher.js @@ -1,5 +1,5 @@ -const { spawn } = require('child_process') -const path = require('path') +const { spawn } = require('child_process'); +const path = require('path'); // `ripgrep` and `scandal` have a different way of handling the trailing and leading // context lines: @@ -44,84 +44,86 @@ const path = require('path') // context needs to be generated after receiving a match, we keep all trailing context arrays that // haven't been fulfilled in this Set, and mutate them adding new lines until they are fulfilled. -function updateLeadingContext (message, pendingLeadingContext, options) { +function updateLeadingContext(message, pendingLeadingContext, options) { if (message.type !== 'match' && message.type !== 'context') { - return + return; } if (options.leadingContextLineCount) { - pendingLeadingContext.push(cleanResultLine(message.data.lines)) + pendingLeadingContext.push(cleanResultLine(message.data.lines)); if (pendingLeadingContext.length > options.leadingContextLineCount) { - pendingLeadingContext.shift() + pendingLeadingContext.shift(); } } } -function updateTrailingContexts (message, pendingTrailingContexts, options) { +function updateTrailingContexts(message, pendingTrailingContexts, options) { if (message.type !== 'match' && message.type !== 'context') { - return + return; } if (options.trailingContextLineCount) { for (const trailingContextLines of pendingTrailingContexts) { - trailingContextLines.push(cleanResultLine(message.data.lines)) + trailingContextLines.push(cleanResultLine(message.data.lines)); if (trailingContextLines.length === options.trailingContextLineCount) { - pendingTrailingContexts.delete(trailingContextLines) + pendingTrailingContexts.delete(trailingContextLines); } } } } -function cleanResultLine (resultLine) { - resultLine = getText(resultLine) +function cleanResultLine(resultLine) { + resultLine = getText(resultLine); - return resultLine[resultLine.length - 1] === '\n' ? resultLine.slice(0, -1) : resultLine + return resultLine[resultLine.length - 1] === '\n' + ? resultLine.slice(0, -1) + : resultLine; } -function getPositionFromColumn (lines, column) { - let currentLength = 0 - let currentLine = 0 - let previousLength = 0 +function getPositionFromColumn(lines, column) { + let currentLength = 0; + let currentLine = 0; + let previousLength = 0; while (column >= currentLength) { - previousLength = currentLength - currentLength += lines[currentLine].length + 1 - currentLine++ + previousLength = currentLength; + currentLength += lines[currentLine].length + 1; + currentLine++; } - return [currentLine - 1, column - previousLength] + return [currentLine - 1, column - previousLength]; } -function processUnicodeMatch (match) { - const text = getText(match.lines) +function processUnicodeMatch(match) { + const text = getText(match.lines); if (text.length === Buffer.byteLength(text)) { // fast codepath for lines that only contain characters of 1 byte length. - return + return; } - let remainingBuffer = Buffer.from(text) - let currentLength = 0 - let previousPosition = 0 + let remainingBuffer = Buffer.from(text); + let currentLength = 0; + let previousPosition = 0; - function convertPosition (position) { - const currentBuffer = remainingBuffer.slice(0, position - previousPosition) - currentLength = currentBuffer.toString().length + currentLength - remainingBuffer = remainingBuffer.slice(position) + function convertPosition(position) { + const currentBuffer = remainingBuffer.slice(0, position - previousPosition); + currentLength = currentBuffer.toString().length + currentLength; + remainingBuffer = remainingBuffer.slice(position); - previousPosition = position + previousPosition = position; - return currentLength + return currentLength; } // Iterate over all the submatches to find the convert the start and end values // (which come as bytes from ripgrep) to character positions. // We can do this because submatches come ordered by position. for (const submatch of match.submatches) { - submatch.start = convertPosition(submatch.start) - submatch.end = convertPosition(submatch.end) + submatch.start = convertPosition(submatch.start); + submatch.end = convertPosition(submatch.end); } } @@ -129,38 +131,40 @@ function processUnicodeMatch (match) { // range. This is mostly needed for multi-line results, since the range // will have differnt start and end rows and we need to calculate these // based on the lines that ripgrep returns. -function processSubmatch (submatch, lineText, offsetRow) { - const lineParts = lineText.split('\n') +function processSubmatch(submatch, lineText, offsetRow) { + const lineParts = lineText.split('\n'); - const start = getPositionFromColumn(lineParts, submatch.start) - const end = getPositionFromColumn(lineParts, submatch.end) + const start = getPositionFromColumn(lineParts, submatch.start); + const end = getPositionFromColumn(lineParts, submatch.end); // Make sure that the lineText string only contains lines that are // relevant to this submatch. This means getting rid of lines above // the start row and below the end row. for (let i = start[0]; i > 0; i--) { - lineParts.shift() + lineParts.shift(); } while (end[0] < lineParts.length - 1) { - lineParts.pop() + lineParts.pop(); } - start[0] += offsetRow - end[0] += offsetRow + start[0] += offsetRow; + end[0] += offsetRow; return { range: [start, end], lineText: cleanResultLine({ text: lineParts.join('\n') }) - } + }; } -function getText (input) { - return input.text ? input.text : Buffer.from(input.bytes, 'base64').toString() +function getText(input) { + return input.text + ? input.text + : Buffer.from(input.bytes, 'base64').toString(); } module.exports = class RipgrepDirectorySearcher { - canSearchDirectory () { - return true + canSearchDirectory() { + return true; } // Performs a text search for files in the specified `Directory`s, subject to the @@ -196,115 +200,124 @@ module.exports = class RipgrepDirectorySearcher { // // Returns a *thenable* `DirectorySearch` that includes a `cancel()` method. If `cancel()` is // invoked before the `DirectorySearch` is determined, it will resolve the `DirectorySearch`. - search (directories, regexp, options) { - const numPathsFound = { num: 0 } + search(directories, regexp, options) { + const numPathsFound = { num: 0 }; - const allPromises = directories.map( - directory => this.searchInDirectory(directory, regexp, options, numPathsFound) - ) + const allPromises = directories.map(directory => + this.searchInDirectory(directory, regexp, options, numPathsFound) + ); - const promise = Promise.all(allPromises) + const promise = Promise.all(allPromises); promise.cancel = () => { for (const promise of allPromises) { - promise.cancel() + promise.cancel(); } - } + }; - return promise + return promise; } - searchInDirectory (directory, regexp, options, numPathsFound) { + searchInDirectory(directory, regexp, options, numPathsFound) { // Delay the require of vscode-ripgrep to not mess with the snapshot creation. if (!this.rgPath) { - this.rgPath = require('vscode-ripgrep').rgPath.replace(/\bapp\.asar\b/, 'app.asar.unpacked') + this.rgPath = require('vscode-ripgrep').rgPath.replace( + /\bapp\.asar\b/, + 'app.asar.unpacked' + ); } - const directoryPath = directory.getPath() - const regexpStr = this.prepareRegexp(regexp.source) + const directoryPath = directory.getPath(); + const regexpStr = this.prepareRegexp(regexp.source); - const args = ['--hidden', '--json', '--regexp', regexpStr] + const args = ['--hidden', '--json', '--regexp', regexpStr]; if (options.leadingContextLineCount) { - args.push('--before-context', options.leadingContextLineCount) + args.push('--before-context', options.leadingContextLineCount); } if (options.trailingContextLineCount) { - args.push('--after-context', options.trailingContextLineCount) + args.push('--after-context', options.trailingContextLineCount); } if (regexp.ignoreCase) { - args.push('--ignore-case') + args.push('--ignore-case'); } - for (const inclusion of this.prepareGlobs(options.inclusions, directoryPath)) { - args.push('--glob', inclusion) + for (const inclusion of this.prepareGlobs( + options.inclusions, + directoryPath + )) { + args.push('--glob', inclusion); } - for (const exclusion of this.prepareGlobs(options.exclusions, directoryPath)) { - args.push('--glob', '!' + exclusion) + for (const exclusion of this.prepareGlobs( + options.exclusions, + directoryPath + )) { + args.push('--glob', '!' + exclusion); } if (this.isMultilineRegexp(regexpStr)) { - args.push('--multiline') + args.push('--multiline'); } - args.push(directoryPath) + args.push(directoryPath); const child = spawn(this.rgPath, args, { cwd: directory.getPath(), stdio: ['pipe', 'pipe', 'pipe'] - }) + }); - const didMatch = options.didMatch || (() => {}) - let cancelled = false + const didMatch = options.didMatch || (() => {}); + let cancelled = false; const returnedPromise = new Promise((resolve, reject) => { - let buffer = '' - let bufferError = '' - let pendingEvent - let pendingLeadingContext - let pendingTrailingContexts + let buffer = ''; + let bufferError = ''; + let pendingEvent; + let pendingLeadingContext; + let pendingTrailingContexts; child.on('close', (code, signal) => { // code 1 is used when no results are found. if (code !== null && code > 1) { - reject(new Error(bufferError)) + reject(new Error(bufferError)); } else { - resolve() + resolve(); } - }) + }); child.stderr.on('data', chunk => { - bufferError += chunk - }) + bufferError += chunk; + }); child.stdout.on('data', chunk => { if (cancelled) { - return + return; } - buffer += chunk - const lines = buffer.split('\n') - buffer = lines.pop() + buffer += chunk; + const lines = buffer.split('\n'); + buffer = lines.pop(); for (const line of lines) { - const message = JSON.parse(line) - updateTrailingContexts(message, pendingTrailingContexts, options) + const message = JSON.parse(line); + updateTrailingContexts(message, pendingTrailingContexts, options); if (message.type === 'begin') { pendingEvent = { filePath: getText(message.data.path), matches: [] - } - pendingLeadingContext = [] - pendingTrailingContexts = new Set() + }; + pendingLeadingContext = []; + pendingTrailingContexts = new Set(); } else if (message.type === 'match') { - const trailingContextLines = [] - pendingTrailingContexts.add(trailingContextLines) + const trailingContextLines = []; + pendingTrailingContexts.add(trailingContextLines); - processUnicodeMatch(message.data) + processUnicodeMatch(message.data); for (const submatch of message.data.submatches) { const { lineText, range } = processSubmatch( submatch, getText(message.data.lines), message.data.line_number - 1 - ) + ); pendingEvent.matches.push({ matchText: getText(submatch.match), @@ -313,87 +326,87 @@ module.exports = class RipgrepDirectorySearcher { range, leadingContextLines: [...pendingLeadingContext], trailingContextLines - }) + }); } } else if (message.type === 'end') { - options.didSearchPaths(++numPathsFound.num) - didMatch(pendingEvent) - pendingEvent = null + options.didSearchPaths(++numPathsFound.num); + didMatch(pendingEvent); + pendingEvent = null; } - updateLeadingContext(message, pendingLeadingContext, options) + updateLeadingContext(message, pendingLeadingContext, options); } - }) - }) + }); + }); returnedPromise.cancel = () => { - child.kill() - cancelled = true - } + child.kill(); + cancelled = true; + }; - return returnedPromise + return returnedPromise; } // We need to prepare the "globs" that we receive from the user to make their behaviour more // user-friendly (e.g when adding `src/` the user probably means `src/**/*`). // This helper function takes care of that. - prepareGlobs (globs, projectRootPath) { - const output = [] + prepareGlobs(globs, projectRootPath) { + const output = []; for (let pattern of globs) { // we need to replace path separators by slashes since globs should // always use always slashes as path separators. - pattern = pattern.replace(new RegExp(`\\${path.sep}`, 'g'), '/') + pattern = pattern.replace(new RegExp(`\\${path.sep}`, 'g'), '/'); if (pattern.length === 0) { - continue + continue; } - const projectName = path.basename(projectRootPath) + const projectName = path.basename(projectRootPath); // The user can just search inside one of the opened projects. When we detect // this scenario we just consider the glob to include every file. if (pattern === projectName) { - output.push('**/*') - continue + output.push('**/*'); + continue; } if (pattern.startsWith(projectName + '/')) { - pattern = pattern.slice(projectName.length + 1) + pattern = pattern.slice(projectName.length + 1); } if (pattern.endsWith('/')) { - pattern = pattern.slice(0, -1) + pattern = pattern.slice(0, -1); } - pattern = pattern.startsWith('**/') ? pattern : `**/${pattern}` + pattern = pattern.startsWith('**/') ? pattern : `**/${pattern}`; - output.push(pattern) - output.push(pattern.endsWith('/**') ? pattern : `${pattern}/**`) + output.push(pattern); + output.push(pattern.endsWith('/**') ? pattern : `${pattern}/**`); } - return output + return output; } - prepareRegexp (regexpStr) { + prepareRegexp(regexpStr) { // ripgrep handles `--` as the arguments separator, so we need to escape it if the // user searches for that exact same string. if (regexpStr === '--') { - return '\\-\\-' + return '\\-\\-'; } // ripgrep is quite picky about unnecessarily escaped sequences, so we need to unescape // them: https://github.com/BurntSushi/ripgrep/issues/434. - regexpStr = regexpStr.replace(/\\\//g, '/') + regexpStr = regexpStr.replace(/\\\//g, '/'); - return regexpStr + return regexpStr; } - isMultilineRegexp (regexpStr) { + isMultilineRegexp(regexpStr) { if (regexpStr.includes('\\n')) { - return true + return true; } - return false + return false; } -} +}; diff --git a/src/scope-descriptor.js b/src/scope-descriptor.js index 63075e8a1..111ce5676 100644 --- a/src/scope-descriptor.js +++ b/src/scope-descriptor.js @@ -17,13 +17,12 @@ // // See the [scopes and scope descriptor guide](http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/) // for more information. -module.exports = -class ScopeDescriptor { - static fromObject (scopes) { +module.exports = class ScopeDescriptor { + static fromObject(scopes) { if (scopes instanceof ScopeDescriptor) { - return scopes + return scopes; } else { - return new ScopeDescriptor({scopes}) + return new ScopeDescriptor({ scopes }); } } @@ -35,46 +34,50 @@ class ScopeDescriptor { // // * `object` {Object} // * `scopes` {Array} of {String}s - constructor ({scopes}) { - this.scopes = scopes + constructor({ scopes }) { + this.scopes = scopes; } // Public: Returns an {Array} of {String}s - getScopesArray () { - return this.scopes + getScopesArray() { + return this.scopes; } - getScopeChain () { + getScopeChain() { // For backward compatibility, prefix TextMate-style scope names with // leading dots (e.g. 'source.js' -> '.source.js'). if (this.scopes[0] != null && this.scopes[0].includes('.')) { - let result = '' + let result = ''; for (let i = 0; i < this.scopes.length; i++) { - const scope = this.scopes[i] - if (i > 0) { result += ' ' } - if (scope[0] !== '.') { result += '.' } - result += scope + const scope = this.scopes[i]; + if (i > 0) { + result += ' '; + } + if (scope[0] !== '.') { + result += '.'; + } + result += scope; } - return result + return result; } else { - return this.scopes.join(' ') + return this.scopes.join(' '); } } - toString () { - return this.getScopeChain() + toString() { + return this.getScopeChain(); } - isEqual (other) { + isEqual(other) { if (this.scopes.length !== other.scopes.length) { - return false + return false; } for (let i = 0; i < this.scopes.length; i++) { - const scope = this.scopes[i] + const scope = this.scopes[i]; if (scope !== other.scopes[i]) { - return false + return false; } } - return true + return true; } -} +}; diff --git a/src/selection.js b/src/selection.js index 351dfc16c..29381a0fd 100644 --- a/src/selection.js +++ b/src/selection.js @@ -1,33 +1,35 @@ -const {Point, Range} = require('text-buffer') -const {pick} = require('underscore-plus') -const {Emitter} = require('event-kit') +const { Point, Range } = require('text-buffer'); +const { pick } = require('underscore-plus'); +const { Emitter } = require('event-kit'); -const NonWhitespaceRegExp = /\S/ -let nextId = 0 +const NonWhitespaceRegExp = /\S/; +let nextId = 0; // Extended: Represents a selection in the {TextEditor}. -module.exports = -class Selection { - constructor ({cursor, marker, editor, id}) { - this.id = (id != null) ? id : nextId++ - this.cursor = cursor - this.marker = marker - this.editor = editor - this.emitter = new Emitter() - this.initialScreenRange = null - this.wordwise = false - this.cursor.selection = this - this.decoration = this.editor.decorateMarker(this.marker, {type: 'highlight', class: 'selection'}) - this.marker.onDidChange(e => this.markerDidChange(e)) - this.marker.onDidDestroy(() => this.markerDidDestroy()) +module.exports = class Selection { + constructor({ cursor, marker, editor, id }) { + this.id = id != null ? id : nextId++; + this.cursor = cursor; + this.marker = marker; + this.editor = editor; + this.emitter = new Emitter(); + this.initialScreenRange = null; + this.wordwise = false; + this.cursor.selection = this; + this.decoration = this.editor.decorateMarker(this.marker, { + type: 'highlight', + class: 'selection' + }); + this.marker.onDidChange(e => this.markerDidChange(e)); + this.marker.onDidDestroy(() => this.markerDidDestroy()); } - destroy () { - this.marker.destroy() + destroy() { + this.marker.destroy(); } - isLastSelection () { - return this === this.editor.getLastSelection() + isLastSelection() { + return this === this.editor.getLastSelection(); } /* @@ -45,8 +47,8 @@ class Selection { // * `selection` {Selection} that triggered the event // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeRange (callback) { - return this.emitter.on('did-change-range', callback) + onDidChangeRange(callback) { + return this.emitter.on('did-change-range', callback); } // Extended: Calls your `callback` when the selection was destroyed @@ -54,8 +56,8 @@ class Selection { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } /* @@ -63,21 +65,24 @@ class Selection { */ // Public: Returns the screen {Range} for the selection. - getScreenRange () { - return this.marker.getScreenRange() + getScreenRange() { + return this.marker.getScreenRange(); } // Public: Modifies the screen range for the selection. // // * `screenRange` The new {Range} to use. // * `options` (optional) {Object} options matching those found in {::setBufferRange}. - setScreenRange (screenRange, options) { - return this.setBufferRange(this.editor.bufferRangeForScreenRange(screenRange), options) + setScreenRange(screenRange, options) { + return this.setBufferRange( + this.editor.bufferRangeForScreenRange(screenRange), + options + ); } // Public: Returns the buffer {Range} for the selection. - getBufferRange () { - return this.marker.getBufferRange() + getBufferRange() { + return this.marker.getBufferRange(); } // Public: Modifies the buffer {Range} for the selection. @@ -91,46 +96,54 @@ class Selection { // * `autoscroll` {Boolean} indicating whether to autoscroll to the new // range. Defaults to `true` if this is the most recently added selection, // `false` otherwise. - setBufferRange (bufferRange, options = {}) { - bufferRange = Range.fromObject(bufferRange) - if (options.reversed == null) options.reversed = this.isReversed() - if (!options.preserveFolds) this.editor.destroyFoldsContainingBufferPositions([bufferRange.start, bufferRange.end], true) + setBufferRange(bufferRange, options = {}) { + bufferRange = Range.fromObject(bufferRange); + if (options.reversed == null) options.reversed = this.isReversed(); + if (!options.preserveFolds) + this.editor.destroyFoldsContainingBufferPositions( + [bufferRange.start, bufferRange.end], + true + ); this.modifySelection(() => { - const needsFlash = options.flash - options.flash = null - this.marker.setBufferRange(bufferRange, options) - const autoscroll = options.autoscroll != null ? options.autoscroll : this.isLastSelection() - if (autoscroll) this.autoscroll() - if (needsFlash) this.decoration.flash('flash', this.editor.selectionFlashDuration) - }) + const needsFlash = options.flash; + options.flash = null; + this.marker.setBufferRange(bufferRange, options); + const autoscroll = + options.autoscroll != null + ? options.autoscroll + : this.isLastSelection(); + if (autoscroll) this.autoscroll(); + if (needsFlash) + this.decoration.flash('flash', this.editor.selectionFlashDuration); + }); } // Public: Returns the starting and ending buffer rows the selection is // highlighting. // // Returns an {Array} of two {Number}s: the starting row, and the ending row. - getBufferRowRange () { - const range = this.getBufferRange() - const start = range.start.row - let end = range.end.row - if (range.end.column === 0) end = Math.max(start, end - 1) - return [start, end] + getBufferRowRange() { + const range = this.getBufferRange(); + const start = range.start.row; + let end = range.end.row; + if (range.end.column === 0) end = Math.max(start, end - 1); + return [start, end]; } - getTailScreenPosition () { - return this.marker.getTailScreenPosition() + getTailScreenPosition() { + return this.marker.getTailScreenPosition(); } - getTailBufferPosition () { - return this.marker.getTailBufferPosition() + getTailBufferPosition() { + return this.marker.getTailBufferPosition(); } - getHeadScreenPosition () { - return this.marker.getHeadScreenPosition() + getHeadScreenPosition() { + return this.marker.getHeadScreenPosition(); } - getHeadBufferPosition () { - return this.marker.getHeadBufferPosition() + getHeadBufferPosition() { + return this.marker.getHeadBufferPosition(); } /* @@ -138,26 +151,26 @@ class Selection { */ // Public: Determines if the selection contains anything. - isEmpty () { - return this.getBufferRange().isEmpty() + isEmpty() { + return this.getBufferRange().isEmpty(); } // Public: Determines if the ending position of a marker is greater than the // starting position. // // This can happen when, for example, you highlight text "up" in a {TextBuffer}. - isReversed () { - return this.marker.isReversed() + isReversed() { + return this.marker.isReversed(); } // Public: Returns whether the selection is a single line or not. - isSingleScreenLine () { - return this.getScreenRange().isSingleLine() + isSingleScreenLine() { + return this.getScreenRange().isSingleLine(); } // Public: Returns the text in the selection. - getText () { - return this.editor.buffer.getTextInRange(this.getBufferRange()) + getText() { + return this.editor.buffer.getTextInRange(this.getBufferRange()); } // Public: Identifies if a selection intersects with a given buffer range. @@ -165,16 +178,16 @@ class Selection { // * `bufferRange` A {Range} to check against. // // Returns a {Boolean} - intersectsBufferRange (bufferRange) { - return this.getBufferRange().intersectsWith(bufferRange) + intersectsBufferRange(bufferRange) { + return this.getBufferRange().intersectsWith(bufferRange); } - intersectsScreenRowRange (startRow, endRow) { - return this.getScreenRange().intersectsRowRange(startRow, endRow) + intersectsScreenRowRange(startRow, endRow) { + return this.getScreenRange().intersectsRowRange(startRow, endRow); } - intersectsScreenRow (screenRow) { - return this.getScreenRange().intersectsRow(screenRow) + intersectsScreenRow(screenRow) { + return this.getScreenRange().intersectsRow(screenRow); } // Public: Identifies if a selection intersects with another selection. @@ -182,8 +195,11 @@ class Selection { // * `otherSelection` A {Selection} to check against. // // Returns a {Boolean} - intersectsWith (otherSelection, exclusive) { - return this.getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive) + intersectsWith(otherSelection, exclusive) { + return this.getBufferRange().intersectsWith( + otherSelection.getBufferRange(), + exclusive + ); } /* @@ -196,236 +212,268 @@ class Selection { // * `autoscroll` {Boolean} indicating whether to autoscroll to the new // range. Defaults to `true` if this is the most recently added selection, // `false` otherwise. - clear (options) { - this.goalScreenRange = null - if (!this.retainSelection) this.marker.clearTail() - const autoscroll = options && options.autoscroll != null - ? options.autoscroll - : this.isLastSelection() - if (autoscroll) this.autoscroll() - this.finalize() + clear(options) { + this.goalScreenRange = null; + if (!this.retainSelection) this.marker.clearTail(); + const autoscroll = + options && options.autoscroll != null + ? options.autoscroll + : this.isLastSelection(); + if (autoscroll) this.autoscroll(); + this.finalize(); } // Public: Selects the text from the current cursor position to a given screen // position. // // * `position` An instance of {Point}, with a given `row` and `column`. - selectToScreenPosition (position, options) { - position = Point.fromObject(position) + selectToScreenPosition(position, options) { + position = Point.fromObject(position); this.modifySelection(() => { if (this.initialScreenRange) { if (position.isLessThan(this.initialScreenRange.start)) { - this.marker.setScreenRange([position, this.initialScreenRange.end], {reversed: true}) + this.marker.setScreenRange([position, this.initialScreenRange.end], { + reversed: true + }); } else { - this.marker.setScreenRange([this.initialScreenRange.start, position], {reversed: false}) + this.marker.setScreenRange( + [this.initialScreenRange.start, position], + { reversed: false } + ); } } else { - this.cursor.setScreenPosition(position, options) + this.cursor.setScreenPosition(position, options); } if (this.linewise) { - this.expandOverLine(options) + this.expandOverLine(options); } else if (this.wordwise) { - this.expandOverWord(options) + this.expandOverWord(options); } - }) + }); } // Public: Selects the text from the current cursor position to a given buffer // position. // // * `position` An instance of {Point}, with a given `row` and `column`. - selectToBufferPosition (position) { - this.modifySelection(() => this.cursor.setBufferPosition(position)) + selectToBufferPosition(position) { + this.modifySelection(() => this.cursor.setBufferPosition(position)); } // Public: Selects the text one position right of the cursor. // // * `columnCount` (optional) {Number} number of columns to select (default: 1) - selectRight (columnCount) { - this.modifySelection(() => this.cursor.moveRight(columnCount)) + selectRight(columnCount) { + this.modifySelection(() => this.cursor.moveRight(columnCount)); } // Public: Selects the text one position left of the cursor. // // * `columnCount` (optional) {Number} number of columns to select (default: 1) - selectLeft (columnCount) { - this.modifySelection(() => this.cursor.moveLeft(columnCount)) + selectLeft(columnCount) { + this.modifySelection(() => this.cursor.moveLeft(columnCount)); } // Public: Selects all the text one position above the cursor. // // * `rowCount` (optional) {Number} number of rows to select (default: 1) - selectUp (rowCount) { - this.modifySelection(() => this.cursor.moveUp(rowCount)) + selectUp(rowCount) { + this.modifySelection(() => this.cursor.moveUp(rowCount)); } // Public: Selects all the text one position below the cursor. // // * `rowCount` (optional) {Number} number of rows to select (default: 1) - selectDown (rowCount) { - this.modifySelection(() => this.cursor.moveDown(rowCount)) + selectDown(rowCount) { + this.modifySelection(() => this.cursor.moveDown(rowCount)); } // Public: Selects all the text from the current cursor position to the top of // the buffer. - selectToTop () { - this.modifySelection(() => this.cursor.moveToTop()) + selectToTop() { + this.modifySelection(() => this.cursor.moveToTop()); } // Public: Selects all the text from the current cursor position to the bottom // of the buffer. - selectToBottom () { - this.modifySelection(() => this.cursor.moveToBottom()) + selectToBottom() { + this.modifySelection(() => this.cursor.moveToBottom()); } // Public: Selects all the text in the buffer. - selectAll () { - this.setBufferRange(this.editor.buffer.getRange(), {autoscroll: false}) + selectAll() { + this.setBufferRange(this.editor.buffer.getRange(), { autoscroll: false }); } // Public: Selects all the text from the current cursor position to the // beginning of the line. - selectToBeginningOfLine () { - this.modifySelection(() => this.cursor.moveToBeginningOfLine()) + selectToBeginningOfLine() { + this.modifySelection(() => this.cursor.moveToBeginningOfLine()); } // Public: Selects all the text from the current cursor position to the first // character of the line. - selectToFirstCharacterOfLine () { - this.modifySelection(() => this.cursor.moveToFirstCharacterOfLine()) + selectToFirstCharacterOfLine() { + this.modifySelection(() => this.cursor.moveToFirstCharacterOfLine()); } // Public: Selects all the text from the current cursor position to the end of // the screen line. - selectToEndOfLine () { - this.modifySelection(() => this.cursor.moveToEndOfScreenLine()) + selectToEndOfLine() { + this.modifySelection(() => this.cursor.moveToEndOfScreenLine()); } // Public: Selects all the text from the current cursor position to the end of // the buffer line. - selectToEndOfBufferLine () { - this.modifySelection(() => this.cursor.moveToEndOfLine()) + selectToEndOfBufferLine() { + this.modifySelection(() => this.cursor.moveToEndOfLine()); } // Public: Selects all the text from the current cursor position to the // beginning of the word. - selectToBeginningOfWord () { - this.modifySelection(() => this.cursor.moveToBeginningOfWord()) + selectToBeginningOfWord() { + this.modifySelection(() => this.cursor.moveToBeginningOfWord()); } // Public: Selects all the text from the current cursor position to the end of // the word. - selectToEndOfWord () { - this.modifySelection(() => this.cursor.moveToEndOfWord()) + selectToEndOfWord() { + this.modifySelection(() => this.cursor.moveToEndOfWord()); } // Public: Selects all the text from the current cursor position to the // beginning of the next word. - selectToBeginningOfNextWord () { - this.modifySelection(() => this.cursor.moveToBeginningOfNextWord()) + selectToBeginningOfNextWord() { + this.modifySelection(() => this.cursor.moveToBeginningOfNextWord()); } // Public: Selects text to the previous word boundary. - selectToPreviousWordBoundary () { - this.modifySelection(() => this.cursor.moveToPreviousWordBoundary()) + selectToPreviousWordBoundary() { + this.modifySelection(() => this.cursor.moveToPreviousWordBoundary()); } // Public: Selects text to the next word boundary. - selectToNextWordBoundary () { - this.modifySelection(() => this.cursor.moveToNextWordBoundary()) + selectToNextWordBoundary() { + this.modifySelection(() => this.cursor.moveToNextWordBoundary()); } // Public: Selects text to the previous subword boundary. - selectToPreviousSubwordBoundary () { - this.modifySelection(() => this.cursor.moveToPreviousSubwordBoundary()) + selectToPreviousSubwordBoundary() { + this.modifySelection(() => this.cursor.moveToPreviousSubwordBoundary()); } // Public: Selects text to the next subword boundary. - selectToNextSubwordBoundary () { - this.modifySelection(() => this.cursor.moveToNextSubwordBoundary()) + selectToNextSubwordBoundary() { + this.modifySelection(() => this.cursor.moveToNextSubwordBoundary()); } // Public: Selects all the text from the current cursor position to the // beginning of the next paragraph. - selectToBeginningOfNextParagraph () { - this.modifySelection(() => this.cursor.moveToBeginningOfNextParagraph()) + selectToBeginningOfNextParagraph() { + this.modifySelection(() => this.cursor.moveToBeginningOfNextParagraph()); } // Public: Selects all the text from the current cursor position to the // beginning of the previous paragraph. - selectToBeginningOfPreviousParagraph () { - this.modifySelection(() => this.cursor.moveToBeginningOfPreviousParagraph()) + selectToBeginningOfPreviousParagraph() { + this.modifySelection(() => + this.cursor.moveToBeginningOfPreviousParagraph() + ); } // Public: Modifies the selection to encompass the current word. // // Returns a {Range}. - selectWord (options = {}) { - if (this.cursor.isSurroundedByWhitespace()) options.wordRegex = /[\t ]*/ + selectWord(options = {}) { + if (this.cursor.isSurroundedByWhitespace()) options.wordRegex = /[\t ]*/; if (this.cursor.isBetweenWordAndNonWord()) { - options.includeNonWordCharacters = false + options.includeNonWordCharacters = false; } - this.setBufferRange(this.cursor.getCurrentWordBufferRange(options), options) - this.wordwise = true - this.initialScreenRange = this.getScreenRange() + this.setBufferRange( + this.cursor.getCurrentWordBufferRange(options), + options + ); + this.wordwise = true; + this.initialScreenRange = this.getScreenRange(); } // Public: Expands the newest selection to include the entire word on which // the cursors rests. - expandOverWord (options) { - this.setBufferRange(this.getBufferRange().union(this.cursor.getCurrentWordBufferRange()), {autoscroll: false}) - const autoscroll = options && options.autoscroll != null ? options.autoscroll : this.isLastSelection() - if (autoscroll) this.cursor.autoscroll() + expandOverWord(options) { + this.setBufferRange( + this.getBufferRange().union(this.cursor.getCurrentWordBufferRange()), + { autoscroll: false } + ); + const autoscroll = + options && options.autoscroll != null + ? options.autoscroll + : this.isLastSelection(); + if (autoscroll) this.cursor.autoscroll(); } // Public: Selects an entire line in the buffer. // // * `row` The line {Number} to select (default: the row of the cursor). - selectLine (row, options) { + selectLine(row, options) { if (row != null) { - this.setBufferRange(this.editor.bufferRangeForBufferRow(row, {includeNewline: true}), options) + this.setBufferRange( + this.editor.bufferRangeForBufferRow(row, { includeNewline: true }), + options + ); } else { - const startRange = this.editor.bufferRangeForBufferRow(this.marker.getStartBufferPosition().row) - const endRange = this.editor.bufferRangeForBufferRow(this.marker.getEndBufferPosition().row, {includeNewline: true}) - this.setBufferRange(startRange.union(endRange), options) + const startRange = this.editor.bufferRangeForBufferRow( + this.marker.getStartBufferPosition().row + ); + const endRange = this.editor.bufferRangeForBufferRow( + this.marker.getEndBufferPosition().row, + { includeNewline: true } + ); + this.setBufferRange(startRange.union(endRange), options); } - this.linewise = true - this.wordwise = false - this.initialScreenRange = this.getScreenRange() + this.linewise = true; + this.wordwise = false; + this.initialScreenRange = this.getScreenRange(); } // Public: Expands the newest selection to include the entire line on which // the cursor currently rests. // // It also includes the newline character. - expandOverLine (options) { - const range = this.getBufferRange().union(this.cursor.getCurrentLineBufferRange({includeNewline: true})) - this.setBufferRange(range, {autoscroll: false}) - const autoscroll = options && options.autoscroll != null ? options.autoscroll : this.isLastSelection() - if (autoscroll) this.cursor.autoscroll() + expandOverLine(options) { + const range = this.getBufferRange().union( + this.cursor.getCurrentLineBufferRange({ includeNewline: true }) + ); + this.setBufferRange(range, { autoscroll: false }); + const autoscroll = + options && options.autoscroll != null + ? options.autoscroll + : this.isLastSelection(); + if (autoscroll) this.cursor.autoscroll(); } // Private: Ensure that the {TextEditor} is not marked read-only before allowing a buffer modification to occur. if // the editor is read-only, require an explicit opt-in option to proceed (`bypassReadOnly`) or throw an Error. - ensureWritable (methodName, opts) { + ensureWritable(methodName, opts) { if (!opts.bypassReadOnly && this.editor.isReadOnly()) { if (atom.inDevMode() || atom.inSpecMode()) { - const e = new Error('Attempt to mutate a read-only TextEditor through a Selection') + const e = new Error( + 'Attempt to mutate a read-only TextEditor through a Selection' + ); e.detail = `Your package is attempting to call ${methodName} on a selection within an editor that has been marked ` + ' read-only. Pass {bypassReadOnly: true} to modify it anyway, or test editors with .isReadOnly() before ' + - ' attempting modifications.' - throw e + ' attempting modifications.'; + throw e; } - return false + return false; } - return true + return true; } /* @@ -450,68 +498,92 @@ class Selection { // * `normalizeLineEndings` (optional) {Boolean} (default: true) // * `undo` *Deprecated* If `skip`, skips the undo stack for this operation. This property is deprecated. Call groupLastChanges() on the {TextBuffer} afterward instead. // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - insertText (text, options = {}) { - if (!this.ensureWritable('insertText', options)) return + insertText(text, options = {}) { + if (!this.ensureWritable('insertText', options)) return; - let desiredIndentLevel, indentAdjustment - const oldBufferRange = this.getBufferRange() - const wasReversed = this.isReversed() - this.clear(options) + let desiredIndentLevel, indentAdjustment; + const oldBufferRange = this.getBufferRange(); + const wasReversed = this.isReversed(); + this.clear(options); - let autoIndentFirstLine = false - const precedingText = this.editor.getTextInRange([[oldBufferRange.start.row, 0], oldBufferRange.start]) - const remainingLines = text.split('\n') - const firstInsertedLine = remainingLines.shift() + let autoIndentFirstLine = false; + const precedingText = this.editor.getTextInRange([ + [oldBufferRange.start.row, 0], + oldBufferRange.start + ]); + const remainingLines = text.split('\n'); + const firstInsertedLine = remainingLines.shift(); - if (options.indentBasis != null && !options.preserveTrailingLineIndentation) { - indentAdjustment = this.editor.indentLevelForLine(precedingText) - options.indentBasis - this.adjustIndent(remainingLines, indentAdjustment) + if ( + options.indentBasis != null && + !options.preserveTrailingLineIndentation + ) { + indentAdjustment = + this.editor.indentLevelForLine(precedingText) - options.indentBasis; + this.adjustIndent(remainingLines, indentAdjustment); } - const textIsAutoIndentable = (text === '\n') || (text === '\r\n') || NonWhitespaceRegExp.test(text) - if (options.autoIndent && textIsAutoIndentable && !NonWhitespaceRegExp.test(precedingText) && (remainingLines.length > 0)) { - autoIndentFirstLine = true - const firstLine = precedingText + firstInsertedLine - const languageMode = this.editor.buffer.getLanguageMode() - desiredIndentLevel = ( + const textIsAutoIndentable = + text === '\n' || text === '\r\n' || NonWhitespaceRegExp.test(text); + if ( + options.autoIndent && + textIsAutoIndentable && + !NonWhitespaceRegExp.test(precedingText) && + remainingLines.length > 0 + ) { + autoIndentFirstLine = true; + const firstLine = precedingText + firstInsertedLine; + const languageMode = this.editor.buffer.getLanguageMode(); + desiredIndentLevel = languageMode.suggestedIndentForLineAtBufferRow && languageMode.suggestedIndentForLineAtBufferRow( oldBufferRange.start.row, firstLine, this.editor.getTabLength() - ) - ) + ); if (desiredIndentLevel != null) { - indentAdjustment = desiredIndentLevel - this.editor.indentLevelForLine(firstLine) - this.adjustIndent(remainingLines, indentAdjustment) + indentAdjustment = + desiredIndentLevel - this.editor.indentLevelForLine(firstLine); + this.adjustIndent(remainingLines, indentAdjustment); } } - text = firstInsertedLine - if (remainingLines.length > 0) text += `\n${remainingLines.join('\n')}` + text = firstInsertedLine; + if (remainingLines.length > 0) text += `\n${remainingLines.join('\n')}`; - const newBufferRange = this.editor.buffer.setTextInRange(oldBufferRange, text, pick(options, 'undo', 'normalizeLineEndings')) + const newBufferRange = this.editor.buffer.setTextInRange( + oldBufferRange, + text, + pick(options, 'undo', 'normalizeLineEndings') + ); if (options.select) { - this.setBufferRange(newBufferRange, {reversed: wasReversed}) + this.setBufferRange(newBufferRange, { reversed: wasReversed }); } else { - if (wasReversed) this.cursor.setBufferPosition(newBufferRange.end) + if (wasReversed) this.cursor.setBufferPosition(newBufferRange.end); } if (autoIndentFirstLine) { - this.editor.setIndentationForBufferRow(oldBufferRange.start.row, desiredIndentLevel) + this.editor.setIndentationForBufferRow( + oldBufferRange.start.row, + desiredIndentLevel + ); } - if (options.autoIndentNewline && (text === '\n')) { - this.editor.autoIndentBufferRow(newBufferRange.end.row, {preserveLeadingWhitespace: true, skipBlankLines: false}) + if (options.autoIndentNewline && text === '\n') { + this.editor.autoIndentBufferRow(newBufferRange.end.row, { + preserveLeadingWhitespace: true, + skipBlankLines: false + }); } else if (options.autoDecreaseIndent && NonWhitespaceRegExp.test(text)) { - this.editor.autoDecreaseIndentForBufferRow(newBufferRange.start.row) + this.editor.autoDecreaseIndentForBufferRow(newBufferRange.start.row); } - const autoscroll = options.autoscroll != null ? options.autoscroll : this.isLastSelection() - if (autoscroll) this.autoscroll() + const autoscroll = + options.autoscroll != null ? options.autoscroll : this.isLastSelection(); + if (autoscroll) this.autoscroll(); - return newBufferRange + return newBufferRange; } // Public: Removes the first character before the selection if the selection @@ -519,10 +591,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - backspace (options = {}) { - if (!this.ensureWritable('backspace', options)) return - if (this.isEmpty()) this.selectLeft() - this.deleteSelectedText(options) + backspace(options = {}) { + if (!this.ensureWritable('backspace', options)) return; + if (this.isEmpty()) this.selectLeft(); + this.deleteSelectedText(options); } // Public: Removes the selection or, if nothing is selected, then all @@ -531,10 +603,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToPreviousWordBoundary (options = {}) { - if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return - if (this.isEmpty()) this.selectToPreviousWordBoundary() - this.deleteSelectedText(options) + deleteToPreviousWordBoundary(options = {}) { + if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return; + if (this.isEmpty()) this.selectToPreviousWordBoundary(); + this.deleteSelectedText(options); } // Public: Removes the selection or, if nothing is selected, then all @@ -543,10 +615,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToNextWordBoundary (options = {}) { - if (!this.ensureWritable('deleteToNextWordBoundary', options)) return - if (this.isEmpty()) this.selectToNextWordBoundary() - this.deleteSelectedText(options) + deleteToNextWordBoundary(options = {}) { + if (!this.ensureWritable('deleteToNextWordBoundary', options)) return; + if (this.isEmpty()) this.selectToNextWordBoundary(); + this.deleteSelectedText(options); } // Public: Removes from the start of the selection to the beginning of the @@ -554,10 +626,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToBeginningOfWord (options = {}) { - if (!this.ensureWritable('deleteToBeginningOfWord', options)) return - if (this.isEmpty()) this.selectToBeginningOfWord() - this.deleteSelectedText(options) + deleteToBeginningOfWord(options = {}) { + if (!this.ensureWritable('deleteToBeginningOfWord', options)) return; + if (this.isEmpty()) this.selectToBeginningOfWord(); + this.deleteSelectedText(options); } // Public: Removes from the beginning of the line which the selection begins on @@ -565,14 +637,14 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToBeginningOfLine (options = {}) { - if (!this.ensureWritable('deleteToBeginningOfLine', options)) return + deleteToBeginningOfLine(options = {}) { + if (!this.ensureWritable('deleteToBeginningOfLine', options)) return; if (this.isEmpty() && this.cursor.isAtBeginningOfLine()) { - this.selectLeft() + this.selectLeft(); } else { - this.selectToBeginningOfLine() + this.selectToBeginningOfLine(); } - this.deleteSelectedText(options) + this.deleteSelectedText(options); } // Public: Removes the selection or the next character after the start of the @@ -580,10 +652,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - delete (options = {}) { - if (!this.ensureWritable('delete', options)) return - if (this.isEmpty()) this.selectRight() - this.deleteSelectedText(options) + delete(options = {}) { + if (!this.ensureWritable('delete', options)) return; + if (this.isEmpty()) this.selectRight(); + this.deleteSelectedText(options); } // Public: If the selection is empty, removes all text from the cursor to the @@ -593,16 +665,16 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToEndOfLine (options = {}) { - if (!this.ensureWritable('deleteToEndOfLine', options)) return + deleteToEndOfLine(options = {}) { + if (!this.ensureWritable('deleteToEndOfLine', options)) return; if (this.isEmpty()) { if (this.cursor.isAtEndOfLine()) { - this.delete(options) - return + this.delete(options); + return; } - this.selectToEndOfLine() + this.selectToEndOfLine(); } - this.deleteSelectedText(options) + this.deleteSelectedText(options); } // Public: Removes the selection or all characters from the start of the @@ -610,10 +682,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToEndOfWord (options = {}) { - if (!this.ensureWritable('deleteToEndOfWord', options)) return - if (this.isEmpty()) this.selectToEndOfWord() - this.deleteSelectedText(options) + deleteToEndOfWord(options = {}) { + if (!this.ensureWritable('deleteToEndOfWord', options)) return; + if (this.isEmpty()) this.selectToEndOfWord(); + this.deleteSelectedText(options); } // Public: Removes the selection or all characters from the start of the @@ -621,10 +693,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToBeginningOfSubword (options = {}) { - if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return - if (this.isEmpty()) this.selectToPreviousSubwordBoundary() - this.deleteSelectedText(options) + deleteToBeginningOfSubword(options = {}) { + if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return; + if (this.isEmpty()) this.selectToPreviousSubwordBoundary(); + this.deleteSelectedText(options); } // Public: Removes the selection or all characters from the start of the @@ -632,21 +704,21 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteToEndOfSubword (options = {}) { - if (!this.ensureWritable('deleteToEndOfSubword', options)) return - if (this.isEmpty()) this.selectToNextSubwordBoundary() - this.deleteSelectedText(options) + deleteToEndOfSubword(options = {}) { + if (!this.ensureWritable('deleteToEndOfSubword', options)) return; + if (this.isEmpty()) this.selectToNextSubwordBoundary(); + this.deleteSelectedText(options); } // Public: Removes only the selected text. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteSelectedText (options = {}) { - if (!this.ensureWritable('deleteSelectedText', options)) return - const bufferRange = this.getBufferRange() - if (!bufferRange.isEmpty()) this.editor.buffer.delete(bufferRange) - if (this.cursor) this.cursor.setBufferPosition(bufferRange.start) + deleteSelectedText(options = {}) { + if (!this.ensureWritable('deleteSelectedText', options)) return; + const bufferRange = this.getBufferRange(); + if (!bufferRange.isEmpty()) this.editor.buffer.delete(bufferRange); + if (this.cursor) this.cursor.setBufferPosition(bufferRange.start); } // Public: Removes the line at the beginning of the selection if the selection @@ -655,24 +727,28 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - deleteLine (options = {}) { - if (!this.ensureWritable('deleteLine', options)) return - const range = this.getBufferRange() + deleteLine(options = {}) { + if (!this.ensureWritable('deleteLine', options)) return; + const range = this.getBufferRange(); if (range.isEmpty()) { - const start = this.cursor.getScreenRow() - const range = this.editor.bufferRowsForScreenRows(start, start + 1) + const start = this.cursor.getScreenRow(); + const range = this.editor.bufferRowsForScreenRows(start, start + 1); if (range[1] > range[0]) { - this.editor.buffer.deleteRows(range[0], range[1] - 1) + this.editor.buffer.deleteRows(range[0], range[1] - 1); } else { - this.editor.buffer.deleteRow(range[0]) + this.editor.buffer.deleteRow(range[0]); } } else { - const start = range.start.row - let end = range.end.row - if (end !== this.editor.buffer.getLastRow() && range.end.column === 0) end-- - this.editor.buffer.deleteRows(start, end) + const start = range.start.row; + let end = range.end.row; + if (end !== this.editor.buffer.getLastRow() && range.end.column === 0) + end--; + this.editor.buffer.deleteRows(start, end); } - this.cursor.setBufferPosition({row: this.cursor.getBufferRow(), column: range.start.column}) + this.cursor.setBufferPosition({ + row: this.cursor.getBufferRow(), + column: range.start.column + }); } // Public: Joins the current line with the one below it. Lines will @@ -682,56 +758,58 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - joinLines (options = {}) { - if (!this.ensureWritable('joinLines', options)) return - let joinMarker - const selectedRange = this.getBufferRange() + joinLines(options = {}) { + if (!this.ensureWritable('joinLines', options)) return; + let joinMarker; + const selectedRange = this.getBufferRange(); if (selectedRange.isEmpty()) { - if (selectedRange.start.row === this.editor.buffer.getLastRow()) return + if (selectedRange.start.row === this.editor.buffer.getLastRow()) return; } else { - joinMarker = this.editor.markBufferRange(selectedRange, {invalidate: 'never'}) + joinMarker = this.editor.markBufferRange(selectedRange, { + invalidate: 'never' + }); } - const rowCount = Math.max(1, selectedRange.getRowCount() - 1) + const rowCount = Math.max(1, selectedRange.getRowCount() - 1); for (let i = 0; i < rowCount; i++) { - this.cursor.setBufferPosition([selectedRange.start.row]) - this.cursor.moveToEndOfLine() + this.cursor.setBufferPosition([selectedRange.start.row]); + this.cursor.moveToEndOfLine(); // Remove trailing whitespace from the current line - const scanRange = this.cursor.getCurrentLineBufferRange() - let trailingWhitespaceRange = null - this.editor.scanInBufferRange(/[ \t]+$/, scanRange, ({range}) => { - trailingWhitespaceRange = range - }) + const scanRange = this.cursor.getCurrentLineBufferRange(); + let trailingWhitespaceRange = null; + this.editor.scanInBufferRange(/[ \t]+$/, scanRange, ({ range }) => { + trailingWhitespaceRange = range; + }); if (trailingWhitespaceRange) { - this.setBufferRange(trailingWhitespaceRange) - this.deleteSelectedText(options) + this.setBufferRange(trailingWhitespaceRange); + this.deleteSelectedText(options); } - const currentRow = selectedRange.start.row - const nextRow = currentRow + 1 + const currentRow = selectedRange.start.row; + const nextRow = currentRow + 1; const insertSpace = - (nextRow <= this.editor.buffer.getLastRow()) && - (this.editor.buffer.lineLengthForRow(nextRow) > 0) && - (this.editor.buffer.lineLengthForRow(currentRow) > 0) - if (insertSpace) this.insertText(' ', options) + nextRow <= this.editor.buffer.getLastRow() && + this.editor.buffer.lineLengthForRow(nextRow) > 0 && + this.editor.buffer.lineLengthForRow(currentRow) > 0; + if (insertSpace) this.insertText(' ', options); - this.cursor.moveToEndOfLine() + this.cursor.moveToEndOfLine(); // Remove leading whitespace from the line below this.modifySelection(() => { - this.cursor.moveRight() - this.cursor.moveToFirstCharacterOfLine() - }) - this.deleteSelectedText(options) + this.cursor.moveRight(); + this.cursor.moveToFirstCharacterOfLine(); + }); + this.deleteSelectedText(options); - if (insertSpace) this.cursor.moveLeft() + if (insertSpace) this.cursor.moveLeft(); } if (joinMarker) { - const newSelectedRange = joinMarker.getBufferRange() - this.setBufferRange(newSelectedRange) - joinMarker.destroy() + const newSelectedRange = joinMarker.getBufferRange(); + this.setBufferRange(newSelectedRange); + joinMarker.destroy(); } } @@ -739,15 +817,17 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - outdentSelectedRows (options = {}) { - if (!this.ensureWritable('outdentSelectedRows', options)) return - const [start, end] = this.getBufferRowRange() - const {buffer} = this.editor - const leadingTabRegex = new RegExp(`^( {1,${this.editor.getTabLength()}}|\t)`) + outdentSelectedRows(options = {}) { + if (!this.ensureWritable('outdentSelectedRows', options)) return; + const [start, end] = this.getBufferRowRange(); + const { buffer } = this.editor; + const leadingTabRegex = new RegExp( + `^( {1,${this.editor.getTabLength()}}|\t)` + ); for (let row = start; row <= end; row++) { - const match = buffer.lineForRow(row).match(leadingTabRegex) + const match = buffer.lineForRow(row).match(leadingTabRegex); if (match && match[0].length > 0) { - buffer.delete([[row, 0], [row, match[0].length]]) + buffer.delete([[row, 0], [row, match[0].length]]); } } } @@ -757,10 +837,10 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - autoIndentSelectedRows (options = {}) { - if (!this.ensureWritable('autoIndentSelectedRows', options)) return - const [start, end] = this.getBufferRowRange() - return this.editor.autoIndentBufferRows(start, end) + autoIndentSelectedRows(options = {}) { + if (!this.ensureWritable('autoIndentSelectedRows', options)) return; + const [start, end] = this.getBufferRowRange(); + return this.editor.autoIndentBufferRows(start, end); } // Public: Wraps the selected lines in comments if they aren't currently part @@ -770,10 +850,13 @@ class Selection { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - toggleLineComments (options = {}) { - if (!this.ensureWritable('toggleLineComments', options)) return - let bufferRowRange = this.getBufferRowRange() || [null, null] - this.editor.toggleLineCommentsForBufferRows(...bufferRowRange, {correctSelection: true, selection: this}) + toggleLineComments(options = {}) { + if (!this.ensureWritable('toggleLineComments', options)) return; + let bufferRowRange = this.getBufferRowRange() || [null, null]; + this.editor.toggleLineCommentsForBufferRows(...bufferRowRange, { + correctSelection: true, + selection: this + }); } // Public: Cuts the selection until the end of the screen line. @@ -781,10 +864,10 @@ class Selection { // * `maintainClipboard` {Boolean} // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - cutToEndOfLine (maintainClipboard, options = {}) { - if (!this.ensureWritable('cutToEndOfLine', options)) return - if (this.isEmpty()) this.selectToEndOfLine() - return this.cut(maintainClipboard, false, options.bypassReadOnly) + cutToEndOfLine(maintainClipboard, options = {}) { + if (!this.ensureWritable('cutToEndOfLine', options)) return; + if (this.isEmpty()) this.selectToEndOfLine(); + return this.cut(maintainClipboard, false, options.bypassReadOnly); } // Public: Cuts the selection until the end of the buffer line. @@ -792,10 +875,10 @@ class Selection { // * `maintainClipboard` {Boolean} // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - cutToEndOfBufferLine (maintainClipboard, options = {}) { - if (!this.ensureWritable('cutToEndOfBufferLine', options)) return - if (this.isEmpty()) this.selectToEndOfBufferLine() - this.cut(maintainClipboard, false, options.bypassReadOnly) + cutToEndOfBufferLine(maintainClipboard, options = {}) { + if (!this.ensureWritable('cutToEndOfBufferLine', options)) return; + if (this.isEmpty()) this.selectToEndOfBufferLine(); + this.cut(maintainClipboard, false, options.bypassReadOnly); } // Public: Copies the selection to the clipboard and then deletes it. @@ -803,10 +886,10 @@ class Selection { // * `maintainClipboard` {Boolean} (default: false) See {::copy} // * `fullLine` {Boolean} (default: false) See {::copy} // * `bypassReadOnly` {Boolean} (default: false) Must be `true` to modify text within a read-only editor. - cut (maintainClipboard = false, fullLine = false, bypassReadOnly = false) { - if (!this.ensureWritable('cut', {bypassReadOnly})) return - this.copy(maintainClipboard, fullLine) - this.delete({bypassReadOnly}) + cut(maintainClipboard = false, fullLine = false, bypassReadOnly = false) { + if (!this.ensureWritable('cut', { bypassReadOnly })) return; + this.copy(maintainClipboard, fullLine); + this.delete({ bypassReadOnly }); } // Public: Copies the current selection to the clipboard. @@ -818,59 +901,70 @@ class Selection { // * `fullLine` {Boolean} if `true`, the copied text will always be pasted // at the beginning of the line containing the cursor, regardless of the // cursor's horizontal position. (default: false) - copy (maintainClipboard = false, fullLine = false) { - if (this.isEmpty()) return - const {start, end} = this.getBufferRange() - const selectionText = this.editor.getTextInRange([start, end]) - const precedingText = this.editor.getTextInRange([[start.row, 0], start]) - const startLevel = this.editor.indentLevelForLine(precedingText) + copy(maintainClipboard = false, fullLine = false) { + if (this.isEmpty()) return; + const { start, end } = this.getBufferRange(); + const selectionText = this.editor.getTextInRange([start, end]); + const precedingText = this.editor.getTextInRange([[start.row, 0], start]); + const startLevel = this.editor.indentLevelForLine(precedingText); if (maintainClipboard) { - let {text: clipboardText, metadata} = this.editor.constructor.clipboard.readWithMetadata() - if (!metadata) metadata = {} + let { + text: clipboardText, + metadata + } = this.editor.constructor.clipboard.readWithMetadata(); + if (!metadata) metadata = {}; if (!metadata.selections) { - metadata.selections = [{ - text: clipboardText, - indentBasis: metadata.indentBasis, - fullLine: metadata.fullLine - }] + metadata.selections = [ + { + text: clipboardText, + indentBasis: metadata.indentBasis, + fullLine: metadata.fullLine + } + ]; } metadata.selections.push({ text: selectionText, indentBasis: startLevel, fullLine - }) - this.editor.constructor.clipboard.write([clipboardText, selectionText].join('\n'), metadata) + }); + this.editor.constructor.clipboard.write( + [clipboardText, selectionText].join('\n'), + metadata + ); } else { this.editor.constructor.clipboard.write(selectionText, { indentBasis: startLevel, fullLine - }) + }); } } // Public: Creates a fold containing the current selection. - fold () { - const range = this.getBufferRange() + fold() { + const range = this.getBufferRange(); if (!range.isEmpty()) { - this.editor.foldBufferRange(range) - this.cursor.setBufferPosition(range.end) + this.editor.foldBufferRange(range); + this.cursor.setBufferPosition(range.end); } } // Private: Increase the indentation level of the given text by given number // of levels. Leaves the first line unchanged. - adjustIndent (lines, indentAdjustment) { + adjustIndent(lines, indentAdjustment) { for (let i = 0; i < lines.length; i++) { - const line = lines[i] + const line = lines[i]; if (indentAdjustment === 0 || line === '') { - continue + continue; } else if (indentAdjustment > 0) { - lines[i] = this.editor.buildIndentString(indentAdjustment) + line + lines[i] = this.editor.buildIndentString(indentAdjustment) + line; } else { - const currentIndentLevel = this.editor.indentLevelForLine(lines[i]) - const indentLevel = Math.max(0, currentIndentLevel + indentAdjustment) - lines[i] = line.replace(/^[\t ]+/, this.editor.buildIndentString(indentLevel)) + const currentIndentLevel = this.editor.indentLevelForLine(lines[i]); + const indentLevel = Math.max(0, currentIndentLevel + indentAdjustment); + lines[i] = line.replace( + /^[\t ]+/, + this.editor.buildIndentString(indentLevel) + ); } } } @@ -885,23 +979,28 @@ class Selection { // * `autoIndent` If `true`, the line is indented to an automatically-inferred // level. Otherwise, {TextEditor::getTabText} is inserted. // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - indent ({autoIndent, bypassReadOnly} = {}) { - if (!this.ensureWritable('indent', {bypassReadOnly})) return - const {row} = this.cursor.getBufferPosition() + indent({ autoIndent, bypassReadOnly } = {}) { + if (!this.ensureWritable('indent', { bypassReadOnly })) return; + const { row } = this.cursor.getBufferPosition(); if (this.isEmpty()) { - this.cursor.skipLeadingWhitespace() - const desiredIndent = this.editor.suggestedIndentForBufferRow(row) - let delta = desiredIndent - this.cursor.getIndentLevel() + this.cursor.skipLeadingWhitespace(); + const desiredIndent = this.editor.suggestedIndentForBufferRow(row); + let delta = desiredIndent - this.cursor.getIndentLevel(); if (autoIndent && delta > 0) { - if (!this.editor.getSoftTabs()) delta = Math.max(delta, 1) - this.insertText(this.editor.buildIndentString(delta), {bypassReadOnly}) + if (!this.editor.getSoftTabs()) delta = Math.max(delta, 1); + this.insertText(this.editor.buildIndentString(delta), { + bypassReadOnly + }); } else { - this.insertText(this.editor.buildIndentString(1, this.cursor.getBufferColumn()), {bypassReadOnly}) + this.insertText( + this.editor.buildIndentString(1, this.cursor.getBufferColumn()), + { bypassReadOnly } + ); } } else { - this.indentSelectedRows({bypassReadOnly}) + this.indentSelectedRows({ bypassReadOnly }); } } @@ -909,12 +1008,12 @@ class Selection { // // * `options` (optional) {Object} with the keys: // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) - indentSelectedRows (options = {}) { - if (!this.ensureWritable('indentSelectedRows', options)) return - const [start, end] = this.getBufferRowRange() + indentSelectedRows(options = {}) { + if (!this.ensureWritable('indentSelectedRows', options)) return; + const [start, end] = this.getBufferRowRange(); for (let row = start; row <= end; row++) { if (this.editor.buffer.lineLengthForRow(row) !== 0) { - this.editor.buffer.insert([row, 0], this.editor.getTabText()) + this.editor.buffer.insert([row, 0], this.editor.getTabText()); } } } @@ -924,54 +1023,66 @@ class Selection { */ // Public: Moves the selection down one row. - addSelectionBelow () { - const range = this.getGoalScreenRange().copy() - const nextRow = range.end.row + 1 + addSelectionBelow() { + const range = this.getGoalScreenRange().copy(); + const nextRow = range.end.row + 1; - for (let row = nextRow, end = this.editor.getLastScreenRow(); row <= end; row++) { - range.start.row = row - range.end.row = row - const clippedRange = this.editor.clipScreenRange(range, {skipSoftWrapIndentation: true}) + for ( + let row = nextRow, end = this.editor.getLastScreenRow(); + row <= end; + row++ + ) { + range.start.row = row; + range.end.row = row; + const clippedRange = this.editor.clipScreenRange(range, { + skipSoftWrapIndentation: true + }); if (range.isEmpty()) { - if (range.end.column > 0 && clippedRange.end.column === 0) continue + if (range.end.column > 0 && clippedRange.end.column === 0) continue; } else { - if (clippedRange.isEmpty()) continue + if (clippedRange.isEmpty()) continue; } - const containingSelections = this.editor.selectionsMarkerLayer.findMarkers({containsScreenRange: clippedRange}) + const containingSelections = this.editor.selectionsMarkerLayer.findMarkers( + { containsScreenRange: clippedRange } + ); if (containingSelections.length === 0) { - const selection = this.editor.addSelectionForScreenRange(clippedRange) - selection.setGoalScreenRange(range) + const selection = this.editor.addSelectionForScreenRange(clippedRange); + selection.setGoalScreenRange(range); } - break + break; } } // Public: Moves the selection up one row. - addSelectionAbove () { - const range = this.getGoalScreenRange().copy() - const previousRow = range.end.row - 1 + addSelectionAbove() { + const range = this.getGoalScreenRange().copy(); + const previousRow = range.end.row - 1; for (let row = previousRow; row >= 0; row--) { - range.start.row = row - range.end.row = row - const clippedRange = this.editor.clipScreenRange(range, {skipSoftWrapIndentation: true}) + range.start.row = row; + range.end.row = row; + const clippedRange = this.editor.clipScreenRange(range, { + skipSoftWrapIndentation: true + }); if (range.isEmpty()) { - if (range.end.column > 0 && clippedRange.end.column === 0) continue + if (range.end.column > 0 && clippedRange.end.column === 0) continue; } else { - if (clippedRange.isEmpty()) continue + if (clippedRange.isEmpty()) continue; } - const containingSelections = this.editor.selectionsMarkerLayer.findMarkers({containsScreenRange: clippedRange}) + const containingSelections = this.editor.selectionsMarkerLayer.findMarkers( + { containsScreenRange: clippedRange } + ); if (containingSelections.length === 0) { - const selection = this.editor.addSelectionForScreenRange(clippedRange) - selection.setGoalScreenRange(range) + const selection = this.editor.addSelectionForScreenRange(clippedRange); + selection.setGoalScreenRange(range); } - break + break; } } @@ -980,19 +1091,24 @@ class Selection { // // * `otherSelection` A {Selection} to merge with. // * `options` (optional) {Object} options matching those found in {::setBufferRange}. - merge (otherSelection, options = {}) { - const myGoalScreenRange = this.getGoalScreenRange() - const otherGoalScreenRange = otherSelection.getGoalScreenRange() + merge(otherSelection, options = {}) { + const myGoalScreenRange = this.getGoalScreenRange(); + const otherGoalScreenRange = otherSelection.getGoalScreenRange(); if (myGoalScreenRange && otherGoalScreenRange) { - options.goalScreenRange = myGoalScreenRange.union(otherGoalScreenRange) + options.goalScreenRange = myGoalScreenRange.union(otherGoalScreenRange); } else { - options.goalScreenRange = myGoalScreenRange || otherGoalScreenRange + options.goalScreenRange = myGoalScreenRange || otherGoalScreenRange; } - const bufferRange = this.getBufferRange().union(otherSelection.getBufferRange()) - this.setBufferRange(bufferRange, Object.assign({autoscroll: false}, options)) - otherSelection.destroy() + const bufferRange = this.getBufferRange().union( + otherSelection.getBufferRange() + ); + this.setBufferRange( + bufferRange, + Object.assign({ autoscroll: false }, options) + ); + otherSelection.destroy(); } /* @@ -1005,29 +1121,37 @@ class Selection { // See {Range::compare} for more details. // // * `otherSelection` A {Selection} to compare against - compare (otherSelection) { - return this.marker.compare(otherSelection.marker) + compare(otherSelection) { + return this.marker.compare(otherSelection.marker); } /* Section: Private Utilities */ - setGoalScreenRange (range) { - this.goalScreenRange = Range.fromObject(range) + setGoalScreenRange(range) { + this.goalScreenRange = Range.fromObject(range); } - getGoalScreenRange () { - return this.goalScreenRange || this.getScreenRange() + getGoalScreenRange() { + return this.goalScreenRange || this.getScreenRange(); } - markerDidChange (e) { - const {oldHeadBufferPosition, oldTailBufferPosition, newHeadBufferPosition} = e - const {oldHeadScreenPosition, oldTailScreenPosition, newHeadScreenPosition} = e - const {textChanged} = e + markerDidChange(e) { + const { + oldHeadBufferPosition, + oldTailBufferPosition, + newHeadBufferPosition + } = e; + const { + oldHeadScreenPosition, + oldTailScreenPosition, + newHeadScreenPosition + } = e; + const { textChanged } = e; if (!oldHeadScreenPosition.isEqual(newHeadScreenPosition)) { - this.cursor.goalColumn = null + this.cursor.goalColumn = null; const cursorMovedEvent = { oldBufferPosition: oldHeadBufferPosition, oldScreenPosition: oldHeadScreenPosition, @@ -1035,9 +1159,9 @@ class Selection { newScreenPosition: newHeadScreenPosition, textChanged, cursor: this.cursor - } - this.cursor.emitter.emit('did-change-position', cursorMovedEvent) - this.editor.cursorMoved(cursorMovedEvent) + }; + this.cursor.emitter.emit('did-change-position', cursorMovedEvent); + this.editor.cursorMoved(cursorMovedEvent); } const rangeChangedEvent = { @@ -1046,51 +1170,57 @@ class Selection { newBufferRange: this.getBufferRange(), newScreenRange: this.getScreenRange(), selection: this - } - this.emitter.emit('did-change-range', rangeChangedEvent) - this.editor.selectionRangeChanged(rangeChangedEvent) + }; + this.emitter.emit('did-change-range', rangeChangedEvent); + this.editor.selectionRangeChanged(rangeChangedEvent); } - markerDidDestroy () { - if (this.editor.isDestroyed()) return + markerDidDestroy() { + if (this.editor.isDestroyed()) return; - this.destroyed = true - this.cursor.destroyed = true + this.destroyed = true; + this.cursor.destroyed = true; - this.editor.removeSelection(this) + this.editor.removeSelection(this); - this.cursor.emitter.emit('did-destroy') - this.emitter.emit('did-destroy') + this.cursor.emitter.emit('did-destroy'); + this.emitter.emit('did-destroy'); - this.cursor.emitter.dispose() - this.emitter.dispose() + this.cursor.emitter.dispose(); + this.emitter.dispose(); } - finalize () { - if (!this.initialScreenRange || !this.initialScreenRange.isEqual(this.getScreenRange())) { - this.initialScreenRange = null + finalize() { + if ( + !this.initialScreenRange || + !this.initialScreenRange.isEqual(this.getScreenRange()) + ) { + this.initialScreenRange = null; } if (this.isEmpty()) { - this.wordwise = false - this.linewise = false + this.wordwise = false; + this.linewise = false; } } - autoscroll (options) { + autoscroll(options) { if (this.marker.hasTail()) { - this.editor.scrollToScreenRange(this.getScreenRange(), Object.assign({reversed: this.isReversed()}, options)) + this.editor.scrollToScreenRange( + this.getScreenRange(), + Object.assign({ reversed: this.isReversed() }, options) + ); } else { - this.cursor.autoscroll(options) + this.cursor.autoscroll(options); } } - clearAutoscroll () {} + clearAutoscroll() {} - modifySelection (fn) { - this.retainSelection = true - this.plantTail() - fn() - this.retainSelection = false + modifySelection(fn) { + this.retainSelection = true; + this.plantTail(); + fn(); + this.retainSelection = false; } // Sets the marker's tail to the same position as the marker's head. @@ -1098,7 +1228,7 @@ class Selection { // This only works if there isn't already a tail position. // // Returns a {Point} representing the new tail position. - plantTail () { - this.marker.plantTail() + plantTail() { + this.marker.plantTail(); } -} +}; diff --git a/src/selectors.js b/src/selectors.js index ce03b80b4..5ee2d681e 100644 --- a/src/selectors.js +++ b/src/selectors.js @@ -1,31 +1,29 @@ -module.exports = {selectorMatchesAnyScope, matcherForSelector} +module.exports = { selectorMatchesAnyScope, matcherForSelector }; -const {isSubset} = require('underscore-plus') +const { isSubset } = require('underscore-plus'); // Private: Parse a selector into parts. // If already parsed, returns the selector unmodified. // // * `selector` a {String|Array} specifying what to match // Returns selector parts, an {Array}. -function parse (selector) { +function parse(selector) { return typeof selector === 'string' ? selector.replace(/^\./, '').split('.') - : selector + : selector; } -const always = scope => true +const always = scope => true; // Essential: Return a matcher function for a selector. // // * selector, a {String} selector // Returns {(scope: String) -> Boolean}, a matcher function returning // true iff the scope matches the selector. -function matcherForSelector (selector) { - const parts = parse(selector) - if (typeof parts === 'function') return parts - return selector - ? scope => isSubset(parts, parse(scope)) - : always +function matcherForSelector(selector) { + const parts = parse(selector); + if (typeof parts === 'function') return parts; + return selector ? scope => isSubset(parts, parse(scope)) : always; } // Essential: Return true iff the selector matches any provided scope. @@ -33,6 +31,6 @@ function matcherForSelector (selector) { // * {String} selector // * {Array} scopes // Returns {Boolean} true if any scope matches the selector. -function selectorMatchesAnyScope (selector, scopes) { - return !selector || scopes.some(matcherForSelector(selector)) +function selectorMatchesAnyScope(selector, scopes) { + return !selector || scopes.some(matcherForSelector(selector)); } diff --git a/src/startup-time.js b/src/startup-time.js index 4e31c3cc0..314415336 100644 --- a/src/startup-time.js +++ b/src/startup-time.js @@ -1,33 +1,33 @@ -let startTime -let markers = [] +let startTime; +let markers = []; module.exports = { - setStartTime () { + setStartTime() { if (!startTime) { - startTime = Date.now() + startTime = Date.now(); } }, - addMarker (label, dateTime) { + addMarker(label, dateTime) { if (!startTime) { - return + return; } - dateTime = dateTime || Date.now() - markers.push({label, time: dateTime - startTime}) + dateTime = dateTime || Date.now(); + markers.push({ label, time: dateTime - startTime }); }, - importData (data) { - startTime = data.startTime - markers = data.markers + importData(data) { + startTime = data.startTime; + markers = data.markers; }, - exportData () { + exportData() { if (!startTime) { - return undefined + return undefined; } - return { startTime, markers } + return { startTime, markers }; }, - deleteData () { - startTime = undefined - markers = [] + deleteData() { + startTime = undefined; + markers = []; } -} +}; diff --git a/src/state-store.js b/src/state-store.js index 278696cb4..e8e51afff 100644 --- a/src/state-store.js +++ b/src/state-store.js @@ -1,126 +1,130 @@ -'use strict' +'use strict'; -module.exports = -class StateStore { - constructor (databaseName, version) { - this.connected = false - this.databaseName = databaseName - this.version = version +module.exports = class StateStore { + constructor(databaseName, version) { + this.connected = false; + this.databaseName = databaseName; + this.version = version; } - get dbPromise () { + 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') - } + 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) - } - }) + 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 + return this._dbPromise; } - isConnected () { - return this.connected + isConnected() { + return this.connected; } - connect () { - return this.dbPromise.then((db) => !!db) + connect() { + return this.dbPromise.then(db => !!db); } - save (key, value) { + save(key, value) { return new Promise((resolve, reject) => { - this.dbPromise.then((db) => { - if (db == null) return resolve() + this.dbPromise.then(db => { + if (db == null) return resolve(); - var request = db.transaction(['states'], 'readwrite') + var request = db + .transaction(['states'], 'readwrite') .objectStore('states') - .put({value: value, storedAt: new Date().toString()}, key) + .put({ value: value, storedAt: new Date().toString() }, key); - request.onsuccess = resolve - request.onerror = reject - }) - }) + request.onsuccess = resolve; + request.onerror = reject; + }); + }); } - load (key) { - return this.dbPromise.then((db) => { - if (!db) return + load(key) { + return this.dbPromise.then(db => { + if (!db) return; return new Promise((resolve, reject) => { - var request = db.transaction(['states']) + var request = db + .transaction(['states']) .objectStore('states') - .get(key) + .get(key); - request.onsuccess = (event) => { - let result = event.target.result + request.onsuccess = event => { + let result = event.target.result; if (result && !result.isJSON) { - resolve(result.value) + resolve(result.value); } else { - resolve(null) + resolve(null); } - } + }; - request.onerror = (event) => reject(event) - }) - }) + request.onerror = event => reject(event); + }); + }); } - delete (key) { + delete(key) { return new Promise((resolve, reject) => { - this.dbPromise.then((db) => { - if (db == null) return resolve() + this.dbPromise.then(db => { + if (db == null) return resolve(); - var request = db.transaction(['states'], 'readwrite') + var request = db + .transaction(['states'], 'readwrite') .objectStore('states') - .delete(key) + .delete(key); - request.onsuccess = resolve - request.onerror = reject - }) - }) + request.onsuccess = resolve; + request.onerror = reject; + }); + }); } - clear () { - return this.dbPromise.then((db) => { - if (!db) return + clear() { + return this.dbPromise.then(db => { + if (!db) return; return new Promise((resolve, reject) => { - var request = db.transaction(['states'], 'readwrite') + var request = db + .transaction(['states'], 'readwrite') .objectStore('states') - .clear() + .clear(); - request.onsuccess = resolve - request.onerror = reject - }) - }) + request.onsuccess = resolve; + request.onerror = reject; + }); + }); } - count () { - return this.dbPromise.then((db) => { - if (!db) return + count() { + return this.dbPromise.then(db => { + if (!db) return; return new Promise((resolve, reject) => { - var request = db.transaction(['states']) + var request = db + .transaction(['states']) .objectStore('states') - .count() + .count(); request.onsuccess = () => { - resolve(request.result) - } - request.onerror = reject - }) - }) + resolve(request.result); + }; + request.onerror = reject; + }); + }); } -} +}; diff --git a/src/storage-folder.js b/src/storage-folder.js index 4931dab11..da78f9733 100644 --- a/src/storage-folder.js +++ b/src/storage-folder.js @@ -1,49 +1,59 @@ -const path = require('path') -const fs = require('fs-plus') +const path = require('path'); +const fs = require('fs-plus'); -module.exports = -class StorageFolder { - constructor (containingPath) { +module.exports = class StorageFolder { + constructor(containingPath) { if (containingPath) { - this.path = path.join(containingPath, 'storage') + this.path = path.join(containingPath, 'storage'); } } - store (name, object) { + store(name, object) { return new Promise((resolve, reject) => { - if (!this.path) return resolve() - fs.writeFile(this.pathForKey(name), JSON.stringify(object), 'utf8', error => - error ? reject(error) : resolve() - ) - }) + if (!this.path) return resolve(); + fs.writeFile( + this.pathForKey(name), + JSON.stringify(object), + 'utf8', + error => (error ? reject(error) : resolve()) + ); + }); } - load (name) { + load(name) { return new Promise(resolve => { - if (!this.path) return resolve(null) - const statePath = this.pathForKey(name) + if (!this.path) return resolve(null); + const statePath = this.pathForKey(name); fs.readFile(statePath, 'utf8', (error, stateString) => { if (error && error.code !== 'ENOENT') { - console.warn(`Error reading state file: ${statePath}`, error.stack, error) + console.warn( + `Error reading state file: ${statePath}`, + error.stack, + error + ); } - if (!stateString) return resolve(null) + if (!stateString) return resolve(null); try { - resolve(JSON.parse(stateString)) + resolve(JSON.parse(stateString)); } catch (error) { - console.warn(`Error parsing state file: ${statePath}`, error.stack, error) - resolve(null) + console.warn( + `Error parsing state file: ${statePath}`, + error.stack, + error + ); + resolve(null); } - }) - }) + }); + }); } - pathForKey (name) { - return path.join(this.getPath(), name) + pathForKey(name) { + return path.join(this.getPath(), name); } - getPath () { - return this.path + getPath() { + return this.path; } -} +}; diff --git a/src/style-manager.js b/src/style-manager.js index 6ffc8de7c..d750f7602 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -1,11 +1,11 @@ -const {Emitter, Disposable} = require('event-kit') -const crypto = require('crypto') -const fs = require('fs-plus') -const path = require('path') -const postcss = require('postcss') -const selectorParser = require('postcss-selector-parser') -const StylesElement = require('./styles-element') -const DEPRECATED_SYNTAX_SELECTORS = require('./deprecated-syntax-selectors') +const { Emitter, Disposable } = require('event-kit'); +const crypto = require('crypto'); +const fs = require('fs-plus'); +const path = require('path'); +const postcss = require('postcss'); +const selectorParser = require('postcss-selector-parser'); +const StylesElement = require('./styles-element'); +const DEPRECATED_SYNTAX_SELECTORS = require('./deprecated-syntax-selectors'); // Extended: A singleton instance of this class available via `atom.styles`, // which you can use to globally query and observe the set of active style @@ -13,17 +13,21 @@ 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 () { - this.emitter = new Emitter() - this.styleElements = [] - this.styleElementsBySourcePath = {} - this.deprecationsBySourcePath = {} + constructor() { + this.emitter = new Emitter(); + this.styleElements = []; + this.styleElementsBySourcePath = {}; + this.deprecationsBySourcePath = {}; } - initialize ({configDirPath}) { - this.configDirPath = configDirPath + initialize({ configDirPath }) { + this.configDirPath = configDirPath; if (this.configDirPath != null) { - this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager') + this.cacheDirPath = path.join( + this.configDirPath, + 'compile-cache', + 'style-manager' + ); } } @@ -46,12 +50,12 @@ module.exports = class StyleManager { // // Returns a {Disposable} on which `.dispose()` can be called to cancel the // subscription. - observeStyleElements (callback) { + observeStyleElements(callback) { for (let styleElement of this.getStyleElements()) { - callback(styleElement) + callback(styleElement); } - return this.onDidAddStyleElement(callback) + return this.onDidAddStyleElement(callback); } // Extended: Invoke `callback` when a style element is added. @@ -69,8 +73,8 @@ module.exports = class StyleManager { // // Returns a {Disposable} on which `.dispose()` can be called to cancel the // subscription. - onDidAddStyleElement (callback) { - return this.emitter.on('did-add-style-element', callback) + onDidAddStyleElement(callback) { + return this.emitter.on('did-add-style-element', callback); } // Extended: Invoke `callback` when a style element is removed. @@ -80,8 +84,8 @@ module.exports = class StyleManager { // // Returns a {Disposable} on which `.dispose()` can be called to cancel the // subscription. - onDidRemoveStyleElement (callback) { - return this.emitter.on('did-remove-style-element', callback) + onDidRemoveStyleElement(callback) { + return this.emitter.on('did-remove-style-element', callback); } // Extended: Invoke `callback` when an existing style element is updated. @@ -97,12 +101,12 @@ module.exports = class StyleManager { // // Returns a {Disposable} on which `.dispose()` can be called to cancel the // subscription. - onDidUpdateStyleElement (callback) { - return this.emitter.on('did-update-style-element', callback) + onDidUpdateStyleElement(callback) { + return this.emitter.on('did-update-style-element', callback); } - onDidUpdateDeprecations (callback) { - return this.emitter.on('did-update-deprecations', callback) + onDidUpdateDeprecations(callback) { + return this.emitter.on('did-update-deprecations', callback); } /* @@ -110,133 +114,149 @@ module.exports = class StyleManager { */ // Extended: Get all loaded style elements. - getStyleElements () { - return this.styleElements.slice() + getStyleElements() { + return this.styleElements.slice(); } - addStyleSheet (source, params = {}) { - let styleElement - let updated - if (params.sourcePath != null && this.styleElementsBySourcePath[params.sourcePath] != null) { - updated = true - styleElement = this.styleElementsBySourcePath[params.sourcePath] + addStyleSheet(source, params = {}) { + let styleElement; + let updated; + if ( + params.sourcePath != null && + this.styleElementsBySourcePath[params.sourcePath] != null + ) { + updated = true; + styleElement = this.styleElementsBySourcePath[params.sourcePath]; } else { - updated = false - styleElement = document.createElement('style') + updated = false; + styleElement = document.createElement('style'); if (params.sourcePath != null) { - styleElement.sourcePath = params.sourcePath - styleElement.setAttribute('source-path', params.sourcePath) + styleElement.sourcePath = params.sourcePath; + styleElement.setAttribute('source-path', params.sourcePath); } if (params.context != null) { - styleElement.context = params.context - styleElement.setAttribute('context', params.context) + styleElement.context = params.context; + styleElement.setAttribute('context', params.context); } if (params.priority != null) { - styleElement.priority = params.priority - styleElement.setAttribute('priority', params.priority) + styleElement.priority = params.priority; + styleElement.setAttribute('priority', params.priority); } } if (params.skipDeprecatedSelectorsTransformation) { - styleElement.textContent = source + styleElement.textContent = source; } else { - const transformed = this.upgradeDeprecatedSelectorsForStyleSheet(source, params.context) - styleElement.textContent = transformed.source + const transformed = this.upgradeDeprecatedSelectorsForStyleSheet( + source, + params.context + ); + styleElement.textContent = transformed.source; if (transformed.deprecationMessage) { - this.deprecationsBySourcePath[params.sourcePath] = {message: transformed.deprecationMessage} - this.emitter.emit('did-update-deprecations') + this.deprecationsBySourcePath[params.sourcePath] = { + message: transformed.deprecationMessage + }; + this.emitter.emit('did-update-deprecations'); } } if (updated) { - this.emitter.emit('did-update-style-element', styleElement) + this.emitter.emit('did-update-style-element', styleElement); } else { - this.addStyleElement(styleElement) + this.addStyleElement(styleElement); } - return new Disposable(() => { this.removeStyleElement(styleElement) }) + return new Disposable(() => { + this.removeStyleElement(styleElement); + }); } - addStyleElement (styleElement) { - let insertIndex = this.styleElements.length + addStyleElement(styleElement) { + let insertIndex = this.styleElements.length; if (styleElement.priority != null) { for (let i = 0; i < this.styleElements.length; i++) { - const existingElement = this.styleElements[i] + const existingElement = this.styleElements[i]; if (existingElement.priority > styleElement.priority) { - insertIndex = i - break + insertIndex = i; + break; } } } - this.styleElements.splice(insertIndex, 0, styleElement) - if (styleElement.sourcePath != null && this.styleElementsBySourcePath[styleElement.sourcePath] == null) { - this.styleElementsBySourcePath[styleElement.sourcePath] = styleElement + this.styleElements.splice(insertIndex, 0, styleElement); + if ( + styleElement.sourcePath != null && + this.styleElementsBySourcePath[styleElement.sourcePath] == null + ) { + this.styleElementsBySourcePath[styleElement.sourcePath] = styleElement; } - this.emitter.emit('did-add-style-element', styleElement) + this.emitter.emit('did-add-style-element', styleElement); } - removeStyleElement (styleElement) { - const index = this.styleElements.indexOf(styleElement) + removeStyleElement(styleElement) { + const index = this.styleElements.indexOf(styleElement); if (index !== -1) { - this.styleElements.splice(index, 1) + this.styleElements.splice(index, 1); if (styleElement.sourcePath != null) { - delete this.styleElementsBySourcePath[styleElement.sourcePath] + delete this.styleElementsBySourcePath[styleElement.sourcePath]; } - this.emitter.emit('did-remove-style-element', styleElement) + this.emitter.emit('did-remove-style-element', styleElement); } } - upgradeDeprecatedSelectorsForStyleSheet (styleSheet, context) { + upgradeDeprecatedSelectorsForStyleSheet(styleSheet, context) { if (this.cacheDirPath != null) { - const hash = crypto.createHash('sha1') + const hash = crypto.createHash('sha1'); if (context != null) { - hash.update(context) + hash.update(context); } - hash.update(styleSheet) - const cacheFilePath = path.join(this.cacheDirPath, hash.digest('hex')) + hash.update(styleSheet); + const cacheFilePath = path.join(this.cacheDirPath, hash.digest('hex')); try { - return JSON.parse(fs.readFileSync(cacheFilePath)) + return JSON.parse(fs.readFileSync(cacheFilePath)); } catch (e) { - const transformed = transformDeprecatedShadowDOMSelectors(styleSheet, context) - fs.writeFileSync(cacheFilePath, JSON.stringify(transformed)) - return transformed + const transformed = transformDeprecatedShadowDOMSelectors( + styleSheet, + context + ); + fs.writeFileSync(cacheFilePath, JSON.stringify(transformed)); + return transformed; } } else { - return transformDeprecatedShadowDOMSelectors(styleSheet, context) + return transformDeprecatedShadowDOMSelectors(styleSheet, context); } } - getDeprecations () { - return this.deprecationsBySourcePath + getDeprecations() { + return this.deprecationsBySourcePath; } - clearDeprecations () { - this.deprecationsBySourcePath = {} + clearDeprecations() { + this.deprecationsBySourcePath = {}; } - getSnapshot () { - return this.styleElements.slice() + getSnapshot() { + return this.styleElements.slice(); } - restoreSnapshot (styleElementsToRestore) { + restoreSnapshot(styleElementsToRestore) { for (let styleElement of this.getStyleElements()) { if (!styleElementsToRestore.includes(styleElement)) { - this.removeStyleElement(styleElement) + this.removeStyleElement(styleElement); } } - const existingStyleElements = this.getStyleElements() + const existingStyleElements = this.getStyleElements(); for (let styleElement of styleElementsToRestore) { if (!existingStyleElements.includes(styleElement)) { - this.addStyleElement(styleElement) + this.addStyleElement(styleElement); } } } - buildStylesElement () { - var stylesElement = new StylesElement() - stylesElement.initialize(this) - return stylesElement + buildStylesElement() { + var stylesElement = new StylesElement(); + stylesElement.initialize(this); + return stylesElement; } /* @@ -246,86 +266,113 @@ module.exports = class StyleManager { // Extended: Get the path of the user style sheet in `~/.atom`. // // Returns a {String}. - getUserStyleSheetPath () { + getUserStyleSheetPath() { if (this.configDirPath == null) { - return '' + return ''; } else { - const stylesheetPath = fs.resolve(path.join(this.configDirPath, 'styles'), ['css', 'less']) + const stylesheetPath = fs.resolve( + path.join(this.configDirPath, 'styles'), + ['css', 'less'] + ); if (fs.isFileSync(stylesheetPath)) { - return stylesheetPath + return stylesheetPath; } else { - return path.join(this.configDirPath, 'styles.less') + return path.join(this.configDirPath, 'styles.less'); } } } -} +}; -function transformDeprecatedShadowDOMSelectors (css, context) { - const transformedSelectors = [] - let transformedSource +function transformDeprecatedShadowDOMSelectors(css, context) { + const transformedSelectors = []; + let transformedSource; try { - transformedSource = postcss.parse(css) + transformedSource = postcss.parse(css); } catch (e) { - transformedSource = null + transformedSource = null; } if (transformedSource) { - transformedSource.walkRules((rule) => { - const transformedSelector = selectorParser((selectors) => { - selectors.each((selector) => { - const firstNode = selector.nodes[0] - if (context === 'atom-text-editor' && firstNode.type === 'pseudo' && firstNode.value === ':host') { - const atomTextEditorElementNode = selectorParser.tag({value: 'atom-text-editor'}) - firstNode.replaceWith(atomTextEditorElementNode) + transformedSource.walkRules(rule => { + const transformedSelector = selectorParser(selectors => { + selectors.each(selector => { + const firstNode = selector.nodes[0]; + if ( + context === 'atom-text-editor' && + firstNode.type === 'pseudo' && + firstNode.value === ':host' + ) { + const atomTextEditorElementNode = selectorParser.tag({ + value: 'atom-text-editor' + }); + firstNode.replaceWith(atomTextEditorElementNode); } - let previousNodeIsAtomTextEditor = false - let targetsAtomTextEditorShadow = context === 'atom-text-editor' - let previousNode - selector.each((node) => { + let previousNodeIsAtomTextEditor = false; + let targetsAtomTextEditorShadow = context === 'atom-text-editor'; + let previousNode; + selector.each(node => { if (targetsAtomTextEditorShadow && node.type === 'class') { if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) { - node.value = `syntax--${node.value}` + node.value = `syntax--${node.value}`; } } else { - if (previousNodeIsAtomTextEditor && node.type === 'pseudo' && node.value === '::shadow') { - node.type = 'className' - node.value = '.editor' - targetsAtomTextEditorShadow = true + if ( + previousNodeIsAtomTextEditor && + node.type === 'pseudo' && + node.value === '::shadow' + ) { + node.type = 'className'; + node.value = '.editor'; + targetsAtomTextEditorShadow = true; } } - previousNode = node + previousNode = node; if (node.type === 'combinator') { - previousNodeIsAtomTextEditor = false - } else if (previousNode.type === 'tag' && previousNode.value === 'atom-text-editor') { - previousNodeIsAtomTextEditor = true + previousNodeIsAtomTextEditor = false; + } else if ( + previousNode.type === 'tag' && + previousNode.value === 'atom-text-editor' + ) { + previousNodeIsAtomTextEditor = true; } - }) - }) - }).process(rule.selector, {lossless: true}).result + }); + }); + }).process(rule.selector, { lossless: true }).result; if (transformedSelector !== rule.selector) { - transformedSelectors.push({before: rule.selector, after: transformedSelector}) - rule.selector = transformedSelector + transformedSelectors.push({ + before: rule.selector, + after: transformedSelector + }); + rule.selector = transformedSelector; } - }) - let deprecationMessage + }); + let deprecationMessage; if (transformedSelectors.length > 0) { - deprecationMessage = 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements ' - deprecationMessage += 'are no longer encapsulated within a shadow DOM boundary. ' - deprecationMessage += 'This means you should stop using `:host` and `::shadow` ' - deprecationMessage += 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. ' - deprecationMessage += 'To prevent breakage with existing style sheets, Atom will automatically ' - deprecationMessage += 'upgrade the following selectors:\n\n' - deprecationMessage += transformedSelectors - .map((selector) => `* \`${selector.before}\` => \`${selector.after}\``) - .join('\n\n') + '\n\n' - deprecationMessage += 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. ' - deprecationMessage += 'Please, make sure to upgrade the above selectors as soon as possible.' + deprecationMessage = + 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements '; + deprecationMessage += + 'are no longer encapsulated within a shadow DOM boundary. '; + deprecationMessage += + 'This means you should stop using `:host` and `::shadow` '; + deprecationMessage += + 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. '; + deprecationMessage += + 'To prevent breakage with existing style sheets, Atom will automatically '; + deprecationMessage += 'upgrade the following selectors:\n\n'; + deprecationMessage += + transformedSelectors + .map(selector => `* \`${selector.before}\` => \`${selector.after}\``) + .join('\n\n') + '\n\n'; + deprecationMessage += + 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. '; + deprecationMessage += + 'Please, make sure to upgrade the above selectors as soon as possible.'; } - return {source: transformedSource.toString(), deprecationMessage} + return { source: transformedSource.toString(), deprecationMessage }; } else { // CSS was malformed so we don't transform it. - return {source: css} + return { source: css }; } } diff --git a/src/syntax-scope-map.js b/src/syntax-scope-map.js index 39b127079..fe3f07dd7 100644 --- a/src/syntax-scope-map.js +++ b/src/syntax-scope-map.js @@ -1,178 +1,182 @@ -const parser = require('postcss-selector-parser') +const parser = require('postcss-selector-parser'); -module.exports = -class SyntaxScopeMap { - constructor (resultsBySelector) { - this.namedScopeTable = {} - this.anonymousScopeTable = {} +module.exports = class SyntaxScopeMap { + constructor(resultsBySelector) { + this.namedScopeTable = {}; + this.anonymousScopeTable = {}; for (let selector in resultsBySelector) { - this.addSelector(selector, resultsBySelector[selector]) + this.addSelector(selector, resultsBySelector[selector]); } - setTableDefaults(this.namedScopeTable, true) - setTableDefaults(this.anonymousScopeTable, false) + setTableDefaults(this.namedScopeTable, true); + setTableDefaults(this.anonymousScopeTable, false); } - addSelector (selector, result) { - parser((parseResult) => { + addSelector(selector, result) { + parser(parseResult => { for (let selectorNode of parseResult.nodes) { - let currentTable = null - let currentIndexValue = null + let currentTable = null; + let currentIndexValue = null; for (let i = selectorNode.nodes.length - 1; i >= 0; i--) { - const termNode = selectorNode.nodes[i] + const termNode = selectorNode.nodes[i]; switch (termNode.type) { case 'tag': - if (!currentTable) currentTable = this.namedScopeTable - if (!currentTable[termNode.value]) currentTable[termNode.value] = {} - currentTable = currentTable[termNode.value] + if (!currentTable) currentTable = this.namedScopeTable; + if (!currentTable[termNode.value]) + currentTable[termNode.value] = {}; + currentTable = currentTable[termNode.value]; if (currentIndexValue != null) { - if (!currentTable.indices) currentTable.indices = {} - if (!currentTable.indices[currentIndexValue]) currentTable.indices[currentIndexValue] = {} - currentTable = currentTable.indices[currentIndexValue] - currentIndexValue = null + if (!currentTable.indices) currentTable.indices = {}; + if (!currentTable.indices[currentIndexValue]) + currentTable.indices[currentIndexValue] = {}; + currentTable = currentTable.indices[currentIndexValue]; + currentIndexValue = null; } - break + break; case 'string': - if (!currentTable) currentTable = this.anonymousScopeTable - const value = termNode.value.slice(1, -1).replace(/\\"/g, '"') - if (!currentTable[value]) currentTable[value] = {} - currentTable = currentTable[value] + if (!currentTable) currentTable = this.anonymousScopeTable; + const value = termNode.value.slice(1, -1).replace(/\\"/g, '"'); + if (!currentTable[value]) currentTable[value] = {}; + currentTable = currentTable[value]; if (currentIndexValue != null) { - if (!currentTable.indices) currentTable.indices = {} - if (!currentTable.indices[currentIndexValue]) currentTable.indices[currentIndexValue] = {} - currentTable = currentTable.indices[currentIndexValue] - currentIndexValue = null + if (!currentTable.indices) currentTable.indices = {}; + if (!currentTable.indices[currentIndexValue]) + currentTable.indices[currentIndexValue] = {}; + currentTable = currentTable.indices[currentIndexValue]; + currentIndexValue = null; } - break + break; case 'universal': if (currentTable) { - if (!currentTable['*']) currentTable['*'] = {} - currentTable = currentTable['*'] + if (!currentTable['*']) currentTable['*'] = {}; + currentTable = currentTable['*']; } else { if (!this.namedScopeTable['*']) { - this.namedScopeTable['*'] = this.anonymousScopeTable['*'] = {} + this.namedScopeTable['*'] = this.anonymousScopeTable[ + '*' + ] = {}; } - currentTable = this.namedScopeTable['*'] + currentTable = this.namedScopeTable['*']; } if (currentIndexValue != null) { - if (!currentTable.indices) currentTable.indices = {} - if (!currentTable.indices[currentIndexValue]) currentTable.indices[currentIndexValue] = {} - currentTable = currentTable.indices[currentIndexValue] - currentIndexValue = null + if (!currentTable.indices) currentTable.indices = {}; + if (!currentTable.indices[currentIndexValue]) + currentTable.indices[currentIndexValue] = {}; + currentTable = currentTable.indices[currentIndexValue]; + currentIndexValue = null; } - break + break; case 'combinator': if (currentIndexValue != null) { - rejectSelector(selector) + rejectSelector(selector); } if (termNode.value === '>') { - if (!currentTable.parents) currentTable.parents = {} - currentTable = currentTable.parents + if (!currentTable.parents) currentTable.parents = {}; + currentTable = currentTable.parents; } else { - rejectSelector(selector) + rejectSelector(selector); } - break + break; case 'pseudo': if (termNode.value === ':nth-child') { - currentIndexValue = termNode.nodes[0].nodes[0].value + currentIndexValue = termNode.nodes[0].nodes[0].value; } else { - rejectSelector(selector) + rejectSelector(selector); } - break + break; default: - rejectSelector(selector) + rejectSelector(selector); } } - currentTable.result = result + currentTable.result = result; } - }).process(selector) + }).process(selector); } - get (nodeTypes, childIndices, leafIsNamed = true) { - let result - let i = nodeTypes.length - 1 + get(nodeTypes, childIndices, leafIsNamed = true) { + let result; + let i = nodeTypes.length - 1; let currentTable = leafIsNamed ? this.namedScopeTable[nodeTypes[i]] - : this.anonymousScopeTable[nodeTypes[i]] + : this.anonymousScopeTable[nodeTypes[i]]; - if (!currentTable) currentTable = this.namedScopeTable['*'] + if (!currentTable) currentTable = this.namedScopeTable['*']; while (currentTable) { if (currentTable.indices && currentTable.indices[childIndices[i]]) { - currentTable = currentTable.indices[childIndices[i]] + currentTable = currentTable.indices[childIndices[i]]; } if (currentTable.result != null) { - result = currentTable.result + result = currentTable.result; } - if (i === 0) break - i-- - currentTable = currentTable.parents && ( - currentTable.parents[nodeTypes[i]] || - currentTable.parents['*'] - ) + if (i === 0) break; + i--; + currentTable = + currentTable.parents && + (currentTable.parents[nodeTypes[i]] || currentTable.parents['*']); } - return result + return result; } -} +}; -function setTableDefaults (table, allowWildcardSelector) { - const defaultTypeTable = allowWildcardSelector ? table['*'] : null +function setTableDefaults(table, allowWildcardSelector) { + const defaultTypeTable = allowWildcardSelector ? table['*'] : null; for (let type in table) { - let typeTable = table[type] - if (typeTable === defaultTypeTable) continue + let typeTable = table[type]; + if (typeTable === defaultTypeTable) continue; if (defaultTypeTable) { - mergeTable(typeTable, defaultTypeTable) + mergeTable(typeTable, defaultTypeTable); } if (typeTable.parents) { - setTableDefaults(typeTable.parents, true) + setTableDefaults(typeTable.parents, true); } for (let key in typeTable.indices) { - const indexTable = typeTable.indices[key] - mergeTable(indexTable, typeTable, false) + const indexTable = typeTable.indices[key]; + mergeTable(indexTable, typeTable, false); if (indexTable.parents) { - setTableDefaults(indexTable.parents, true) + setTableDefaults(indexTable.parents, true); } } } } -function mergeTable (table, defaultTable, mergeIndices = true) { +function mergeTable(table, defaultTable, mergeIndices = true) { if (mergeIndices && defaultTable.indices) { - if (!table.indices) table.indices = {} + if (!table.indices) table.indices = {}; for (let key in defaultTable.indices) { - if (!table.indices[key]) table.indices[key] = {} - mergeTable(table.indices[key], defaultTable.indices[key]) + if (!table.indices[key]) table.indices[key] = {}; + mergeTable(table.indices[key], defaultTable.indices[key]); } } if (defaultTable.parents) { - if (!table.parents) table.parents = {} + if (!table.parents) table.parents = {}; for (let key in defaultTable.parents) { - if (!table.parents[key]) table.parents[key] = {} - mergeTable(table.parents[key], defaultTable.parents[key]) + if (!table.parents[key]) table.parents[key] = {}; + mergeTable(table.parents[key], defaultTable.parents[key]); } } if (defaultTable.result != null && table.result == null) { - table.result = defaultTable.result + table.result = defaultTable.result; } } -function rejectSelector (selector) { - throw new TypeError(`Unsupported selector '${selector}'`) +function rejectSelector(selector) { + throw new TypeError(`Unsupported selector '${selector}'`); } diff --git a/src/task-bootstrap.js b/src/task-bootstrap.js index a7076ebd4..1edbce8fa 100644 --- a/src/task-bootstrap.js +++ b/src/task-bootstrap.js @@ -1,68 +1,90 @@ -const {userAgent} = process.env -const [compileCachePath, taskPath] = process.argv.slice(2) +const { userAgent } = process.env; +const [compileCachePath, taskPath] = process.argv.slice(2); -const CompileCache = require('./compile-cache') -CompileCache.setCacheDirectory(compileCachePath) -CompileCache.install(`${process.resourcesPath}`, require) +const CompileCache = require('./compile-cache'); +CompileCache.setCacheDirectory(compileCachePath); +CompileCache.install(`${process.resourcesPath}`, require); -const setupGlobals = function () { - global.attachEvent = function () {} +const setupGlobals = function() { + global.attachEvent = function() {}; const console = { - warn () { return global.emit('task:warn', ...arguments) }, - log () { return global.emit('task:log', ...arguments) }, - error () { return global.emit('task:error', ...arguments) }, - trace () {} - } - global.__defineGetter__('console', () => console) + warn() { + return global.emit('task:warn', ...arguments); + }, + log() { + return global.emit('task:log', ...arguments); + }, + error() { + return global.emit('task:error', ...arguments); + }, + trace() {} + }; + global.__defineGetter__('console', () => console); global.document = { - createElement () { + createElement() { return { - setAttribute () {}, - getElementsByTagName () { return [] }, - appendChild () {} - } + setAttribute() {}, + getElementsByTagName() { + return []; + }, + appendChild() {} + }; }, documentElement: { - insertBefore () {}, - removeChild () {} + insertBefore() {}, + removeChild() {} }, - getElementById () { return {} }, - createComment () { return {} }, - createDocumentFragment () { return {} } - } - - global.emit = (event, ...args) => process.send({event, args}) - global.navigator = {userAgent} - return (global.window = global) -} - -const handleEvents = function () { - process.on('uncaughtException', error => console.error(error.message, error.stack)) - - return process.on('message', function ({event, args} = {}) { - if (event !== 'start') { return } - - let isAsync = false - const async = function () { - isAsync = true - return result => global.emit('task:completed', result) + getElementById() { + return {}; + }, + createComment() { + return {}; + }, + createDocumentFragment() { + return {}; } - const result = handler.bind({async})(...args) - if (!isAsync) { return global.emit('task:completed', result) } - }) -} + }; -const setupDeprecations = function () { - const Grim = require('grim') - return Grim.on('updated', function () { - const deprecations = Grim.getDeprecations().map(deprecation => deprecation.serialize()) - Grim.clearDeprecations() - return global.emit('task:deprecations', deprecations) - }) -} + global.emit = (event, ...args) => process.send({ event, args }); + global.navigator = { userAgent }; + return (global.window = global); +}; -setupGlobals() -handleEvents() -setupDeprecations() -const handler = require(taskPath) +const handleEvents = function() { + process.on('uncaughtException', error => + console.error(error.message, error.stack) + ); + + return process.on('message', function({ event, args } = {}) { + if (event !== 'start') { + return; + } + + let isAsync = false; + const async = function() { + isAsync = true; + return result => global.emit('task:completed', result); + }; + const result = handler.bind({ async })(...args); + if (!isAsync) { + return global.emit('task:completed', result); + } + }); +}; + +const setupDeprecations = function() { + const Grim = require('grim'); + return Grim.on('updated', function() { + const deprecations = Grim.getDeprecations().map(deprecation => + deprecation.serialize() + ); + Grim.clearDeprecations(); + return global.emit('task:deprecations', deprecations); + }); +}; + +setupGlobals(); +handleEvents(); +setupDeprecations(); +const handler = require(taskPath); diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 30af9fd38..04537c038 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -1,100 +1,103 @@ /* global ResizeObserver */ -const etch = require('etch') -const {Point, Range} = require('text-buffer') -const LineTopIndex = require('line-top-index') -const TextEditor = require('./text-editor') -const {isPairedCharacter} = require('./text-utils') -const electron = require('electron') -const clipboard = electron.clipboard -const $ = etch.dom +const etch = require('etch'); +const { Point, Range } = require('text-buffer'); +const LineTopIndex = require('line-top-index'); +const TextEditor = require('./text-editor'); +const { isPairedCharacter } = require('./text-utils'); +const electron = require('electron'); +const clipboard = electron.clipboard; +const $ = etch.dom; -let TextEditorElement +let TextEditorElement; -const DEFAULT_ROWS_PER_TILE = 6 -const NORMAL_WIDTH_CHARACTER = 'x' -const DOUBLE_WIDTH_CHARACTER = '我' -const HALF_WIDTH_CHARACTER = 'ハ' -const KOREAN_CHARACTER = '세' -const NBSP_CHARACTER = '\u00a0' -const ZERO_WIDTH_NBSP_CHARACTER = '\ufeff' -const MOUSE_DRAG_AUTOSCROLL_MARGIN = 40 -const CURSOR_BLINK_RESUME_DELAY = 300 -const CURSOR_BLINK_PERIOD = 800 +const DEFAULT_ROWS_PER_TILE = 6; +const NORMAL_WIDTH_CHARACTER = 'x'; +const DOUBLE_WIDTH_CHARACTER = '我'; +const HALF_WIDTH_CHARACTER = 'ハ'; +const KOREAN_CHARACTER = '세'; +const NBSP_CHARACTER = '\u00a0'; +const ZERO_WIDTH_NBSP_CHARACTER = '\ufeff'; +const MOUSE_DRAG_AUTOSCROLL_MARGIN = 40; +const CURSOR_BLINK_RESUME_DELAY = 300; +const CURSOR_BLINK_PERIOD = 800; -function scaleMouseDragAutoscrollDelta (delta) { - return Math.pow(delta / 3, 3) / 280 +function scaleMouseDragAutoscrollDelta(delta) { + return Math.pow(delta / 3, 3) / 280; } -module.exports = -class TextEditorComponent { - static setScheduler (scheduler) { - etch.setScheduler(scheduler) +module.exports = class TextEditorComponent { + static setScheduler(scheduler) { + etch.setScheduler(scheduler); } - static getScheduler () { - return etch.getScheduler() + static getScheduler() { + return etch.getScheduler(); } - static didUpdateStyles () { + static didUpdateStyles() { if (this.attachedComponents) { - this.attachedComponents.forEach((component) => { - component.didUpdateStyles() - }) + this.attachedComponents.forEach(component => { + component.didUpdateStyles(); + }); } } - static didUpdateScrollbarStyles () { + static didUpdateScrollbarStyles() { if (this.attachedComponents) { - this.attachedComponents.forEach((component) => { - component.didUpdateScrollbarStyles() - }) + this.attachedComponents.forEach(component => { + component.didUpdateScrollbarStyles(); + }); } } - constructor (props) { - this.props = props + constructor(props) { + this.props = props; if (!props.model) { - props.model = new TextEditor({mini: props.mini, readOnly: props.readOnly}) + props.model = new TextEditor({ + mini: props.mini, + readOnly: props.readOnly + }); } - this.props.model.component = this + this.props.model.component = this; if (props.element) { - this.element = props.element + this.element = props.element; } else { - if (!TextEditorElement) TextEditorElement = require('./text-editor-element') - this.element = new TextEditorElement() + if (!TextEditorElement) + TextEditorElement = require('./text-editor-element'); + this.element = new TextEditorElement(); } - this.element.initialize(this) - this.virtualNode = $('atom-text-editor') - this.virtualNode.domNode = this.element - this.refs = {} + this.element.initialize(this); + this.virtualNode = $('atom-text-editor'); + this.virtualNode.domNode = this.element; + this.refs = {}; - this.updateSync = this.updateSync.bind(this) - this.didBlurHiddenInput = this.didBlurHiddenInput.bind(this) - this.didFocusHiddenInput = this.didFocusHiddenInput.bind(this) - this.didPaste = this.didPaste.bind(this) - this.didTextInput = this.didTextInput.bind(this) - this.didKeydown = this.didKeydown.bind(this) - this.didKeyup = this.didKeyup.bind(this) - this.didKeypress = this.didKeypress.bind(this) - this.didCompositionStart = this.didCompositionStart.bind(this) - this.didCompositionUpdate = this.didCompositionUpdate.bind(this) - this.didCompositionEnd = this.didCompositionEnd.bind(this) + this.updateSync = this.updateSync.bind(this); + this.didBlurHiddenInput = this.didBlurHiddenInput.bind(this); + this.didFocusHiddenInput = this.didFocusHiddenInput.bind(this); + this.didPaste = this.didPaste.bind(this); + this.didTextInput = this.didTextInput.bind(this); + this.didKeydown = this.didKeydown.bind(this); + this.didKeyup = this.didKeyup.bind(this); + this.didKeypress = this.didKeypress.bind(this); + this.didCompositionStart = this.didCompositionStart.bind(this); + this.didCompositionUpdate = this.didCompositionUpdate.bind(this); + this.didCompositionEnd = this.didCompositionEnd.bind(this); - this.updatedSynchronously = this.props.updatedSynchronously - this.didScrollDummyScrollbar = this.didScrollDummyScrollbar.bind(this) - this.didMouseDownOnContent = this.didMouseDownOnContent.bind(this) + this.updatedSynchronously = this.props.updatedSynchronously; + this.didScrollDummyScrollbar = this.didScrollDummyScrollbar.bind(this); + this.didMouseDownOnContent = this.didMouseDownOnContent.bind(this); this.debouncedResumeCursorBlinking = debounce( this.resumeCursorBlinking.bind(this), - (this.props.cursorBlinkResumeDelay || CURSOR_BLINK_RESUME_DELAY) - ) - this.lineTopIndex = new LineTopIndex() - this.lineNodesPool = new NodePool() - this.updateScheduled = false - this.suppressUpdates = false - this.hasInitialMeasurements = false + this.props.cursorBlinkResumeDelay || CURSOR_BLINK_RESUME_DELAY + ); + this.lineTopIndex = new LineTopIndex(); + this.lineNodesPool = new NodePool(); + this.updateScheduled = false; + this.suppressUpdates = false; + this.hasInitialMeasurements = false; this.measurements = { lineHeight: 0, baseCharacterWidth: 0, @@ -108,43 +111,45 @@ class TextEditorComponent { verticalScrollbarWidth: 0, horizontalScrollbarHeight: 0, longestLineWidth: 0 - } - this.derivedDimensionsCache = {} - this.visible = false - this.cursorsBlinking = false - this.cursorsBlinkedOff = false - this.nextUpdateOnlyBlinksCursors = null - this.linesToMeasure = new Map() - this.extraRenderedScreenLines = new Map() - this.horizontalPositionsToMeasure = new Map() // Keys are rows with positions we want to measure, values are arrays of columns to measure - this.horizontalPixelPositionsByScreenLineId = new Map() // Values are maps from column to horizontal pixel positions - this.blockDecorationsToMeasure = new Set() - this.blockDecorationsByElement = new WeakMap() - this.blockDecorationSentinel = document.createElement('div') - this.blockDecorationSentinel.style.height = '1px' - this.heightsByBlockDecoration = new WeakMap() - this.blockDecorationResizeObserver = new ResizeObserver(this.didResizeBlockDecorations.bind(this)) - this.lineComponentsByScreenLineId = new Map() - this.overlayComponents = new Set() - this.shouldRenderDummyScrollbars = true - this.remeasureScrollbars = false - this.pendingAutoscroll = null - this.scrollTopPending = false - this.scrollLeftPending = false - this.scrollTop = 0 - this.scrollLeft = 0 - this.previousScrollWidth = 0 - this.previousScrollHeight = 0 - this.lastKeydown = null - this.lastKeydownBeforeKeypress = null - this.accentedCharacterMenuIsOpen = false - this.remeasureGutterDimensions = false - this.guttersToRender = [this.props.model.getLineNumberGutter()] - this.guttersVisibility = [this.guttersToRender[0].visible] - this.idsByTileStartRow = new Map() - this.nextTileId = 0 - this.renderedTileStartRows = [] - this.showLineNumbers = this.props.model.doesShowLineNumbers() + }; + this.derivedDimensionsCache = {}; + this.visible = false; + this.cursorsBlinking = false; + this.cursorsBlinkedOff = false; + this.nextUpdateOnlyBlinksCursors = null; + this.linesToMeasure = new Map(); + this.extraRenderedScreenLines = new Map(); + this.horizontalPositionsToMeasure = new Map(); // Keys are rows with positions we want to measure, values are arrays of columns to measure + this.horizontalPixelPositionsByScreenLineId = new Map(); // Values are maps from column to horizontal pixel positions + this.blockDecorationsToMeasure = new Set(); + this.blockDecorationsByElement = new WeakMap(); + this.blockDecorationSentinel = document.createElement('div'); + this.blockDecorationSentinel.style.height = '1px'; + this.heightsByBlockDecoration = new WeakMap(); + this.blockDecorationResizeObserver = new ResizeObserver( + this.didResizeBlockDecorations.bind(this) + ); + this.lineComponentsByScreenLineId = new Map(); + this.overlayComponents = new Set(); + this.shouldRenderDummyScrollbars = true; + this.remeasureScrollbars = false; + this.pendingAutoscroll = null; + this.scrollTopPending = false; + this.scrollLeftPending = false; + this.scrollTop = 0; + this.scrollLeft = 0; + this.previousScrollWidth = 0; + this.previousScrollHeight = 0; + this.lastKeydown = null; + this.lastKeydownBeforeKeypress = null; + this.accentedCharacterMenuIsOpen = false; + this.remeasureGutterDimensions = false; + this.guttersToRender = [this.props.model.getLineNumberGutter()]; + this.guttersVisibility = [this.guttersToRender[0].visible]; + this.idsByTileStartRow = new Map(); + this.nextTileId = 0; + this.renderedTileStartRows = []; + this.showLineNumbers = this.props.model.doesShowLineNumbers(); this.lineNumbersToRender = { maxDigits: 2, bufferRows: [], @@ -152,7 +157,7 @@ class TextEditorComponent { keys: [], softWrappedFlags: [], foldableFlags: [] - } + }; this.decorationsToRender = { lineNumbers: new Map(), lines: null, @@ -162,344 +167,363 @@ class TextEditorComponent { customGutter: new Map(), blocks: new Map(), text: [] - } + }; this.decorationsToMeasure = { highlights: [], cursors: new Map() - } - this.textDecorationsByMarker = new Map() - this.textDecorationBoundaries = [] - this.pendingScrollTopRow = this.props.initialScrollTopRow - this.pendingScrollLeftColumn = this.props.initialScrollLeftColumn - this.tabIndex = this.props.element && this.props.element.tabIndex ? this.props.element.tabIndex : -1 + }; + this.textDecorationsByMarker = new Map(); + this.textDecorationBoundaries = []; + this.pendingScrollTopRow = this.props.initialScrollTopRow; + this.pendingScrollLeftColumn = this.props.initialScrollLeftColumn; + this.tabIndex = + this.props.element && this.props.element.tabIndex + ? this.props.element.tabIndex + : -1; - this.measuredContent = false - this.queryGuttersToRender() - this.queryMaxLineNumberDigits() - this.observeBlockDecorations() - this.updateClassList() - etch.updateSync(this) + this.measuredContent = false; + this.queryGuttersToRender(); + this.queryMaxLineNumberDigits(); + this.observeBlockDecorations(); + this.updateClassList(); + etch.updateSync(this); } - update (props) { + update(props) { if (props.model !== this.props.model) { - this.props.model.component = null - props.model.component = this + this.props.model.component = null; + props.model.component = this; } - this.props = props - this.scheduleUpdate() + this.props = props; + this.scheduleUpdate(); } - pixelPositionForScreenPosition ({row, column}) { - const top = this.pixelPositionAfterBlocksForRow(row) - let left = column === 0 ? 0 : this.pixelLeftForRowAndColumn(row, column) + pixelPositionForScreenPosition({ row, column }) { + const top = this.pixelPositionAfterBlocksForRow(row); + let left = column === 0 ? 0 : this.pixelLeftForRowAndColumn(row, column); if (left == null) { - this.requestHorizontalMeasurement(row, column) - this.updateSync() - left = this.pixelLeftForRowAndColumn(row, column) + this.requestHorizontalMeasurement(row, column); + this.updateSync(); + left = this.pixelLeftForRowAndColumn(row, column); } - return {top, left} + return { top, left }; } - scheduleUpdate (nextUpdateOnlyBlinksCursors = false) { - if (!this.visible) return - if (this.suppressUpdates) return + scheduleUpdate(nextUpdateOnlyBlinksCursors = false) { + if (!this.visible) return; + if (this.suppressUpdates) return; this.nextUpdateOnlyBlinksCursors = - this.nextUpdateOnlyBlinksCursors !== false && nextUpdateOnlyBlinksCursors === true + this.nextUpdateOnlyBlinksCursors !== false && + nextUpdateOnlyBlinksCursors === true; if (this.updatedSynchronously) { - this.updateSync() + this.updateSync(); } else if (!this.updateScheduled) { - this.updateScheduled = true + this.updateScheduled = true; etch.getScheduler().updateDocument(() => { - if (this.updateScheduled) this.updateSync(true) - }) + if (this.updateScheduled) this.updateSync(true); + }); } } - updateSync (useScheduler = false) { + updateSync(useScheduler = false) { // Don't proceed if we know we are not visible if (!this.visible) { - this.updateScheduled = false - return + this.updateScheduled = false; + return; } // Don't proceed if we have to pay for a measurement anyway and detect // that we are no longer visible. - if ((this.remeasureCharacterDimensions || this.remeasureAllBlockDecorations) && !this.isVisible()) { - if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise() - this.updateScheduled = false - return + if ( + (this.remeasureCharacterDimensions || + this.remeasureAllBlockDecorations) && + !this.isVisible() + ) { + if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise(); + this.updateScheduled = false; + return; } - const onlyBlinkingCursors = this.nextUpdateOnlyBlinksCursors - this.nextUpdateOnlyBlinksCursors = null + const onlyBlinkingCursors = this.nextUpdateOnlyBlinksCursors; + this.nextUpdateOnlyBlinksCursors = null; if (useScheduler && onlyBlinkingCursors) { - this.refs.cursorsAndInput.updateCursorBlinkSync(this.cursorsBlinkedOff) - if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise() - this.updateScheduled = false - return + this.refs.cursorsAndInput.updateCursorBlinkSync(this.cursorsBlinkedOff); + if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise(); + this.updateScheduled = false; + return; } if (this.remeasureCharacterDimensions) { - const originalLineHeight = this.getLineHeight() - const originalBaseCharacterWidth = this.getBaseCharacterWidth() - const scrollTopRow = this.getScrollTopRow() - const scrollLeftColumn = this.getScrollLeftColumn() + const originalLineHeight = this.getLineHeight(); + const originalBaseCharacterWidth = this.getBaseCharacterWidth(); + const scrollTopRow = this.getScrollTopRow(); + const scrollLeftColumn = this.getScrollLeftColumn(); - this.measureCharacterDimensions() - this.measureGutterDimensions() - this.queryLongestLine() + this.measureCharacterDimensions(); + this.measureGutterDimensions(); + this.queryLongestLine(); if (this.getLineHeight() !== originalLineHeight) { - this.setScrollTopRow(scrollTopRow) + this.setScrollTopRow(scrollTopRow); } if (this.getBaseCharacterWidth() !== originalBaseCharacterWidth) { - this.setScrollLeftColumn(scrollLeftColumn) + this.setScrollLeftColumn(scrollLeftColumn); } - this.remeasureCharacterDimensions = false + this.remeasureCharacterDimensions = false; } - this.measureBlockDecorations() + this.measureBlockDecorations(); - this.updateSyncBeforeMeasuringContent() + this.updateSyncBeforeMeasuringContent(); if (useScheduler === true) { - const scheduler = etch.getScheduler() + const scheduler = etch.getScheduler(); scheduler.readDocument(() => { - const restartFrame = this.measureContentDuringUpdateSync() + const restartFrame = this.measureContentDuringUpdateSync(); scheduler.updateDocument(() => { if (restartFrame) { - this.updateSync(true) + this.updateSync(true); } else { - this.updateSyncAfterMeasuringContent() + this.updateSyncAfterMeasuringContent(); } - }) - }) + }); + }); } else { - const restartFrame = this.measureContentDuringUpdateSync() + const restartFrame = this.measureContentDuringUpdateSync(); if (restartFrame) { - this.updateSync(false) + this.updateSync(false); } else { - this.updateSyncAfterMeasuringContent() + this.updateSyncAfterMeasuringContent(); } } - this.updateScheduled = false + this.updateScheduled = false; } - measureBlockDecorations () { + measureBlockDecorations() { if (this.remeasureAllBlockDecorations) { - this.remeasureAllBlockDecorations = false + this.remeasureAllBlockDecorations = false; - const decorations = this.props.model.getDecorations() + const decorations = this.props.model.getDecorations(); for (var i = 0; i < decorations.length; i++) { - const decoration = decorations[i] - const marker = decoration.getMarker() + const decoration = decorations[i]; + const marker = decoration.getMarker(); if (marker.isValid() && decoration.getProperties().type === 'block') { - this.blockDecorationsToMeasure.add(decoration) + this.blockDecorationsToMeasure.add(decoration); } } // Update the width of the line tiles to ensure block decorations are // measured with the most recent width. if (this.blockDecorationsToMeasure.size > 0) { - this.updateSyncBeforeMeasuringContent() + this.updateSyncBeforeMeasuringContent(); } } if (this.blockDecorationsToMeasure.size > 0) { - const {blockDecorationMeasurementArea} = this.refs - const sentinelElements = new Set() + const { blockDecorationMeasurementArea } = this.refs; + const sentinelElements = new Set(); - blockDecorationMeasurementArea.appendChild(document.createElement('div')) - this.blockDecorationsToMeasure.forEach((decoration) => { - const {item} = decoration.getProperties() - const decorationElement = TextEditor.viewForItem(item) + blockDecorationMeasurementArea.appendChild(document.createElement('div')); + this.blockDecorationsToMeasure.forEach(decoration => { + const { item } = decoration.getProperties(); + const decorationElement = TextEditor.viewForItem(item); if (document.contains(decorationElement)) { - const parentElement = decorationElement.parentElement + const parentElement = decorationElement.parentElement; if (!decorationElement.previousSibling) { - const sentinelElement = this.blockDecorationSentinel.cloneNode() - parentElement.insertBefore(sentinelElement, decorationElement) - sentinelElements.add(sentinelElement) + const sentinelElement = this.blockDecorationSentinel.cloneNode(); + parentElement.insertBefore(sentinelElement, decorationElement); + sentinelElements.add(sentinelElement); } if (!decorationElement.nextSibling) { - const sentinelElement = this.blockDecorationSentinel.cloneNode() - parentElement.appendChild(sentinelElement) - sentinelElements.add(sentinelElement) + const sentinelElement = this.blockDecorationSentinel.cloneNode(); + parentElement.appendChild(sentinelElement); + sentinelElements.add(sentinelElement); } - this.didMeasureVisibleBlockDecoration = true + this.didMeasureVisibleBlockDecoration = true; } else { - blockDecorationMeasurementArea.appendChild(this.blockDecorationSentinel.cloneNode()) - blockDecorationMeasurementArea.appendChild(decorationElement) - blockDecorationMeasurementArea.appendChild(this.blockDecorationSentinel.cloneNode()) + blockDecorationMeasurementArea.appendChild( + this.blockDecorationSentinel.cloneNode() + ); + blockDecorationMeasurementArea.appendChild(decorationElement); + blockDecorationMeasurementArea.appendChild( + this.blockDecorationSentinel.cloneNode() + ); } - }) + }); if (this.resizeBlockDecorationMeasurementsArea) { - this.resizeBlockDecorationMeasurementsArea = false - this.refs.blockDecorationMeasurementArea.style.width = this.getScrollWidth() + 'px' + this.resizeBlockDecorationMeasurementsArea = false; + this.refs.blockDecorationMeasurementArea.style.width = + this.getScrollWidth() + 'px'; } - this.blockDecorationsToMeasure.forEach((decoration) => { - const {item} = decoration.getProperties() - const decorationElement = TextEditor.viewForItem(item) - const {previousSibling, nextSibling} = decorationElement - const height = nextSibling.getBoundingClientRect().top - previousSibling.getBoundingClientRect().bottom - this.heightsByBlockDecoration.set(decoration, height) - this.lineTopIndex.resizeBlock(decoration, height) - }) + this.blockDecorationsToMeasure.forEach(decoration => { + const { item } = decoration.getProperties(); + const decorationElement = TextEditor.viewForItem(item); + const { previousSibling, nextSibling } = decorationElement; + const height = + nextSibling.getBoundingClientRect().top - + previousSibling.getBoundingClientRect().bottom; + this.heightsByBlockDecoration.set(decoration, height); + this.lineTopIndex.resizeBlock(decoration, height); + }); - sentinelElements.forEach((sentinelElement) => sentinelElement.remove()) + sentinelElements.forEach(sentinelElement => sentinelElement.remove()); while (blockDecorationMeasurementArea.firstChild) { - blockDecorationMeasurementArea.firstChild.remove() + blockDecorationMeasurementArea.firstChild.remove(); } - this.blockDecorationsToMeasure.clear() + this.blockDecorationsToMeasure.clear(); } } - updateSyncBeforeMeasuringContent () { - this.measuredContent = false - this.derivedDimensionsCache = {} - this.updateModelSoftWrapColumn() + updateSyncBeforeMeasuringContent() { + this.measuredContent = false; + this.derivedDimensionsCache = {}; + this.updateModelSoftWrapColumn(); if (this.pendingAutoscroll) { - let {screenRange, options} = this.pendingAutoscroll - this.autoscrollVertically(screenRange, options) - this.requestHorizontalMeasurement(screenRange.start.row, screenRange.start.column) - this.requestHorizontalMeasurement(screenRange.end.row, screenRange.end.column) + let { screenRange, options } = this.pendingAutoscroll; + this.autoscrollVertically(screenRange, options); + this.requestHorizontalMeasurement( + screenRange.start.row, + screenRange.start.column + ); + this.requestHorizontalMeasurement( + screenRange.end.row, + screenRange.end.column + ); } - this.populateVisibleRowRange(this.getRenderedStartRow()) - this.populateVisibleTiles() - this.queryScreenLinesToRender() - this.queryLongestLine() - this.queryLineNumbersToRender() - this.queryGuttersToRender() - this.queryDecorationsToRender() - this.queryExtraScreenLinesToRender() - this.shouldRenderDummyScrollbars = !this.remeasureScrollbars - etch.updateSync(this) - this.updateClassList() - this.shouldRenderDummyScrollbars = true - this.didMeasureVisibleBlockDecoration = false + this.populateVisibleRowRange(this.getRenderedStartRow()); + this.populateVisibleTiles(); + this.queryScreenLinesToRender(); + this.queryLongestLine(); + this.queryLineNumbersToRender(); + this.queryGuttersToRender(); + this.queryDecorationsToRender(); + this.queryExtraScreenLinesToRender(); + this.shouldRenderDummyScrollbars = !this.remeasureScrollbars; + etch.updateSync(this); + this.updateClassList(); + this.shouldRenderDummyScrollbars = true; + this.didMeasureVisibleBlockDecoration = false; } - measureContentDuringUpdateSync () { - let gutterDimensionsChanged = false + measureContentDuringUpdateSync() { + let gutterDimensionsChanged = false; if (this.remeasureGutterDimensions) { - gutterDimensionsChanged = this.measureGutterDimensions() - this.remeasureGutterDimensions = false + gutterDimensionsChanged = this.measureGutterDimensions(); + this.remeasureGutterDimensions = false; } - const wasHorizontalScrollbarVisible = ( - this.canScrollHorizontally() && - this.getHorizontalScrollbarHeight() > 0 - ) + const wasHorizontalScrollbarVisible = + this.canScrollHorizontally() && this.getHorizontalScrollbarHeight() > 0; - this.measureLongestLineWidth() - this.measureHorizontalPositions() - this.updateAbsolutePositionedDecorations() + this.measureLongestLineWidth(); + this.measureHorizontalPositions(); + this.updateAbsolutePositionedDecorations(); - const isHorizontalScrollbarVisible = ( - this.canScrollHorizontally() && - this.getHorizontalScrollbarHeight() > 0 - ) + const isHorizontalScrollbarVisible = + this.canScrollHorizontally() && this.getHorizontalScrollbarHeight() > 0; if (this.pendingAutoscroll) { - this.derivedDimensionsCache = {} - const {screenRange, options} = this.pendingAutoscroll - this.autoscrollHorizontally(screenRange, options) + this.derivedDimensionsCache = {}; + const { screenRange, options } = this.pendingAutoscroll; + this.autoscrollHorizontally(screenRange, options); if (!wasHorizontalScrollbarVisible && isHorizontalScrollbarVisible) { - this.autoscrollVertically(screenRange, options) + this.autoscrollVertically(screenRange, options); } - this.pendingAutoscroll = null + this.pendingAutoscroll = null; } - this.linesToMeasure.clear() - this.measuredContent = true + this.linesToMeasure.clear(); + this.measuredContent = true; - return gutterDimensionsChanged || wasHorizontalScrollbarVisible !== isHorizontalScrollbarVisible + return ( + gutterDimensionsChanged || + wasHorizontalScrollbarVisible !== isHorizontalScrollbarVisible + ); } - updateSyncAfterMeasuringContent () { - this.derivedDimensionsCache = {} - etch.updateSync(this) + updateSyncAfterMeasuringContent() { + this.derivedDimensionsCache = {}; + etch.updateSync(this); - this.currentFrameLineNumberGutterProps = null - this.scrollTopPending = false - this.scrollLeftPending = false + this.currentFrameLineNumberGutterProps = null; + this.scrollTopPending = false; + this.scrollLeftPending = false; if (this.remeasureScrollbars) { // Flush stored scroll positions to the vertical and the horizontal // scrollbars. This is because they have just been destroyed and recreated // as a result of their remeasurement, but we could not assign the scroll // top while they were initialized because they were not attached to the // DOM yet. - this.refs.verticalScrollbar.flushScrollPosition() - this.refs.horizontalScrollbar.flushScrollPosition() + this.refs.verticalScrollbar.flushScrollPosition(); + this.refs.horizontalScrollbar.flushScrollPosition(); - this.measureScrollbarDimensions() - this.remeasureScrollbars = false - etch.updateSync(this) + this.measureScrollbarDimensions(); + this.remeasureScrollbars = false; + etch.updateSync(this); } - this.derivedDimensionsCache = {} - if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise() + this.derivedDimensionsCache = {}; + if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise(); } - render () { - const {model} = this.props - const style = {} + render() { + const { model } = this.props; + const style = {}; if (!model.getAutoHeight() && !model.getAutoWidth()) { - style.contain = 'size' + style.contain = 'size'; } - let clientContainerHeight = '100%' - let clientContainerWidth = '100%' + let clientContainerHeight = '100%'; + let clientContainerWidth = '100%'; if (this.hasInitialMeasurements) { if (model.getAutoHeight()) { clientContainerHeight = - this.getContentHeight() + - this.getHorizontalScrollbarHeight() + - 'px' + this.getContentHeight() + this.getHorizontalScrollbarHeight() + 'px'; } if (model.getAutoWidth()) { - style.width = 'min-content' + style.width = 'min-content'; clientContainerWidth = this.getGutterContainerWidth() + this.getContentWidth() + this.getVerticalScrollbarWidth() + - 'px' + 'px'; } else { - style.width = this.element.style.width + style.width = this.element.style.width; } } - let attributes = {} + let attributes = {}; if (model.isMini()) { - attributes.mini = '' + attributes.mini = ''; } if (model.isReadOnly()) { - attributes.readonly = '' + attributes.readonly = ''; } - const dataset = {encoding: model.getEncoding()} - const grammar = model.getGrammar() + const dataset = { encoding: model.getEncoding() }; + const grammar = model.getGrammar(); if (grammar && grammar.scopeName) { - dataset.grammar = grammar.scopeName.replace(/\./g, ' ') + dataset.grammar = grammar.scopeName.replace(/\./g, ' '); } - return $('atom-text-editor', + return $( + 'atom-text-editor', { // See this.updateClassList() for construction of the class name style, attributes, dataset, tabIndex: -1, - on: {mousewheel: this.didMouseWheel} + on: { mousewheel: this.didMouseWheel } }, $.div( { @@ -517,12 +541,12 @@ class TextEditorComponent { this.renderScrollContainer() ), this.renderOverlayDecorations() - ) + ); } - renderGutterContainer () { + renderGutterContainer() { if (this.props.model.isMini()) { - return null + return null; } else { return $(GutterContainerComponent, { ref: 'gutterContainer', @@ -543,11 +567,11 @@ class TextEditorComponent { showLineNumbers: this.showLineNumbers, lineNumbersToRender: this.lineNumbersToRender, didMeasureVisibleBlockDecoration: this.didMeasureVisibleBlockDecoration - }) + }); } } - renderScrollContainer () { + renderScrollContainer() { const style = { position: 'absolute', contain: 'strict', @@ -555,11 +579,11 @@ class TextEditorComponent { top: 0, bottom: 0, backgroundColor: 'inherit' - } + }; if (this.hasInitialMeasurements) { - style.left = this.getGutterContainerWidth() + 'px' - style.width = this.getScrollContainerWidth() + 'px' + style.left = this.getGutterContainerWidth() + 'px'; + style.width = this.getScrollContainerWidth() + 'px'; } return $.div( @@ -571,115 +595,132 @@ class TextEditorComponent { }, this.renderContent(), this.renderDummyScrollbars() - ) + ); } - renderContent () { + renderContent() { let style = { contain: 'strict', overflow: 'hidden', backgroundColor: 'inherit' - } + }; if (this.hasInitialMeasurements) { - style.width = ceilToPhysicalPixelBoundary(this.getScrollWidth()) + 'px' - style.height = ceilToPhysicalPixelBoundary(this.getScrollHeight()) + 'px' - style.willChange = 'transform' - style.transform = `translate(${-roundToPhysicalPixelBoundary(this.getScrollLeft())}px, ${-roundToPhysicalPixelBoundary(this.getScrollTop())}px)` + style.width = ceilToPhysicalPixelBoundary(this.getScrollWidth()) + 'px'; + style.height = ceilToPhysicalPixelBoundary(this.getScrollHeight()) + 'px'; + style.willChange = 'transform'; + style.transform = `translate(${-roundToPhysicalPixelBoundary( + this.getScrollLeft() + )}px, ${-roundToPhysicalPixelBoundary(this.getScrollTop())}px)`; } return $.div( { ref: 'content', - on: {mousedown: this.didMouseDownOnContent}, + on: { mousedown: this.didMouseDownOnContent }, style }, this.renderLineTiles(), this.renderBlockDecorationMeasurementArea(), this.renderCharacterMeasurementLine() - ) + ); } - renderHighlightDecorations () { + renderHighlightDecorations() { return $(HighlightsComponent, { hasInitialMeasurements: this.hasInitialMeasurements, highlightDecorations: this.decorationsToRender.highlights.slice(), width: this.getScrollWidth(), height: this.getScrollHeight(), lineHeight: this.getLineHeight() - }) + }); } - renderLineTiles () { + renderLineTiles() { const style = { position: 'absolute', contain: 'strict', overflow: 'hidden' - } + }; - const children = [] - children.push(this.renderHighlightDecorations()) + const children = []; + children.push(this.renderHighlightDecorations()); if (this.hasInitialMeasurements) { - const {lineComponentsByScreenLineId} = this + const { lineComponentsByScreenLineId } = this; - const startRow = this.getRenderedStartRow() - const endRow = this.getRenderedEndRow() - const rowsPerTile = this.getRowsPerTile() - const tileWidth = this.getScrollWidth() + const startRow = this.getRenderedStartRow(); + const endRow = this.getRenderedEndRow(); + const rowsPerTile = this.getRowsPerTile(); + const tileWidth = this.getScrollWidth(); for (let i = 0; i < this.renderedTileStartRows.length; i++) { - const tileStartRow = this.renderedTileStartRows[i] - const tileEndRow = Math.min(endRow, tileStartRow + rowsPerTile) - const tileHeight = this.pixelPositionBeforeBlocksForRow(tileEndRow) - this.pixelPositionBeforeBlocksForRow(tileStartRow) + const tileStartRow = this.renderedTileStartRows[i]; + const tileEndRow = Math.min(endRow, tileStartRow + rowsPerTile); + const tileHeight = + this.pixelPositionBeforeBlocksForRow(tileEndRow) - + this.pixelPositionBeforeBlocksForRow(tileStartRow); - children.push($(LinesTileComponent, { - key: this.idsByTileStartRow.get(tileStartRow), - measuredContent: this.measuredContent, - height: tileHeight, - width: tileWidth, - top: this.pixelPositionBeforeBlocksForRow(tileStartRow), - lineHeight: this.getLineHeight(), - renderedStartRow: startRow, - tileStartRow, - tileEndRow, - screenLines: this.renderedScreenLines.slice(tileStartRow - startRow, tileEndRow - startRow), - lineDecorations: this.decorationsToRender.lines.slice(tileStartRow - startRow, tileEndRow - startRow), - textDecorations: this.decorationsToRender.text.slice(tileStartRow - startRow, tileEndRow - startRow), - blockDecorations: this.decorationsToRender.blocks.get(tileStartRow), - displayLayer: this.props.model.displayLayer, - nodePool: this.lineNodesPool, - lineComponentsByScreenLineId - })) + children.push( + $(LinesTileComponent, { + key: this.idsByTileStartRow.get(tileStartRow), + measuredContent: this.measuredContent, + height: tileHeight, + width: tileWidth, + top: this.pixelPositionBeforeBlocksForRow(tileStartRow), + lineHeight: this.getLineHeight(), + renderedStartRow: startRow, + tileStartRow, + tileEndRow, + screenLines: this.renderedScreenLines.slice( + tileStartRow - startRow, + tileEndRow - startRow + ), + lineDecorations: this.decorationsToRender.lines.slice( + tileStartRow - startRow, + tileEndRow - startRow + ), + textDecorations: this.decorationsToRender.text.slice( + tileStartRow - startRow, + tileEndRow - startRow + ), + blockDecorations: this.decorationsToRender.blocks.get(tileStartRow), + displayLayer: this.props.model.displayLayer, + nodePool: this.lineNodesPool, + lineComponentsByScreenLineId + }) + ); } this.extraRenderedScreenLines.forEach((screenLine, screenRow) => { if (screenRow < startRow || screenRow >= endRow) { - children.push($(LineComponent, { - key: 'extra-' + screenLine.id, - offScreen: true, - screenLine, - screenRow, - displayLayer: this.props.model.displayLayer, - nodePool: this.lineNodesPool, - lineComponentsByScreenLineId - })) + children.push( + $(LineComponent, { + key: 'extra-' + screenLine.id, + offScreen: true, + screenLine, + screenRow, + displayLayer: this.props.model.displayLayer, + nodePool: this.lineNodesPool, + lineComponentsByScreenLineId + }) + ); } - }) + }); - style.width = this.getScrollWidth() + 'px' - style.height = this.getScrollHeight() + 'px' + style.width = this.getScrollWidth() + 'px'; + style.height = this.getScrollHeight() + 'px'; } - children.push(this.renderPlaceholderText()) - children.push(this.renderCursorsAndInput()) + children.push(this.renderPlaceholderText()); + children.push(this.renderCursorsAndInput()); return $.div( - {key: 'lineTiles', ref: 'lineTiles', className: 'lines', style}, + { key: 'lineTiles', ref: 'lineTiles', className: 'lines', style }, children - ) + ); } - renderCursorsAndInput () { + renderCursorsAndInput() { return $(CursorsAndInputComponent, { ref: 'cursorsAndInput', key: 'cursorsAndInput', @@ -701,36 +742,36 @@ class TextEditorComponent { cursorsBlinkedOff: this.cursorsBlinkedOff, hiddenInputPosition: this.hiddenInputPosition, tabIndex: this.tabIndex - }) + }); } - renderPlaceholderText () { - const {model} = this.props + renderPlaceholderText() { + const { model } = this.props; if (model.isEmpty()) { - const placeholderText = model.getPlaceholderText() + const placeholderText = model.getPlaceholderText(); if (placeholderText != null) { - return $.div({className: 'placeholder-text'}, placeholderText) + return $.div({ className: 'placeholder-text' }, placeholderText); } } - return null + return null; } - renderCharacterMeasurementLine () { + renderCharacterMeasurementLine() { return $.div( { key: 'characterMeasurementLine', ref: 'characterMeasurementLine', className: 'line dummy', - style: {position: 'absolute', visibility: 'hidden'} + style: { position: 'absolute', visibility: 'hidden' } }, - $.span({ref: 'normalWidthCharacterSpan'}, NORMAL_WIDTH_CHARACTER), - $.span({ref: 'doubleWidthCharacterSpan'}, DOUBLE_WIDTH_CHARACTER), - $.span({ref: 'halfWidthCharacterSpan'}, HALF_WIDTH_CHARACTER), - $.span({ref: 'koreanCharacterSpan'}, KOREAN_CHARACTER) - ) + $.span({ ref: 'normalWidthCharacterSpan' }, NORMAL_WIDTH_CHARACTER), + $.span({ ref: 'doubleWidthCharacterSpan' }, DOUBLE_WIDTH_CHARACTER), + $.span({ ref: 'halfWidthCharacterSpan' }, HALF_WIDTH_CHARACTER), + $.span({ ref: 'koreanCharacterSpan' }, KOREAN_CHARACTER) + ); } - renderBlockDecorationMeasurementArea () { + renderBlockDecorationMeasurementArea() { return $.div({ ref: 'blockDecorationMeasurementArea', key: 'blockDecorationMeasurementArea', @@ -740,27 +781,30 @@ class TextEditorComponent { visibility: 'hidden', width: this.getScrollWidth() + 'px' } - }) + }); } - renderDummyScrollbars () { + renderDummyScrollbars() { if (this.shouldRenderDummyScrollbars && !this.props.model.isMini()) { - let scrollHeight, scrollTop, horizontalScrollbarHeight - let scrollWidth, scrollLeft, verticalScrollbarWidth, forceScrollbarVisible - let canScrollHorizontally, canScrollVertically + let scrollHeight, scrollTop, horizontalScrollbarHeight; + let scrollWidth, + scrollLeft, + verticalScrollbarWidth, + forceScrollbarVisible; + let canScrollHorizontally, canScrollVertically; if (this.hasInitialMeasurements) { - scrollHeight = this.getScrollHeight() - scrollWidth = this.getScrollWidth() - scrollTop = this.getScrollTop() - scrollLeft = this.getScrollLeft() - canScrollHorizontally = this.canScrollHorizontally() - canScrollVertically = this.canScrollVertically() - horizontalScrollbarHeight = this.getHorizontalScrollbarHeight() - verticalScrollbarWidth = this.getVerticalScrollbarWidth() - forceScrollbarVisible = this.remeasureScrollbars + scrollHeight = this.getScrollHeight(); + scrollWidth = this.getScrollWidth(); + scrollTop = this.getScrollTop(); + scrollLeft = this.getScrollLeft(); + canScrollHorizontally = this.canScrollHorizontally(); + canScrollVertically = this.canScrollVertically(); + horizontalScrollbarHeight = this.getHorizontalScrollbarHeight(); + verticalScrollbarWidth = this.getVerticalScrollbarWidth(); + forceScrollbarVisible = this.remeasureScrollbars; } else { - forceScrollbarVisible = true + forceScrollbarVisible = true; } return [ @@ -788,311 +832,363 @@ class TextEditorComponent { }), // Force a "corner" to render where the two scrollbars meet at the lower right - $.div( - { - ref: 'scrollbarCorner', - className: 'scrollbar-corner', - style: { - position: 'absolute', - height: '20px', - width: '20px', - bottom: 0, - right: 0, - overflow: 'scroll' - } + $.div({ + ref: 'scrollbarCorner', + className: 'scrollbar-corner', + style: { + position: 'absolute', + height: '20px', + width: '20px', + bottom: 0, + right: 0, + overflow: 'scroll' } - ) - ] + }) + ]; } else { - return null + return null; } } - renderOverlayDecorations () { - return this.decorationsToRender.overlays.map((overlayProps) => - $(OverlayComponent, Object.assign( - { - key: overlayProps.element, - overlayComponents: this.overlayComponents, - didResize: (overlayComponent) => { - this.updateOverlayToRender(overlayProps) - overlayComponent.update(overlayProps) - } - }, - overlayProps - )) - ) + renderOverlayDecorations() { + return this.decorationsToRender.overlays.map(overlayProps => + $( + OverlayComponent, + Object.assign( + { + key: overlayProps.element, + overlayComponents: this.overlayComponents, + didResize: overlayComponent => { + this.updateOverlayToRender(overlayProps); + overlayComponent.update(overlayProps); + } + }, + overlayProps + ) + ) + ); } // Imperatively manipulate the class list of the root element to avoid // clearing classes assigned by package authors. - updateClassList () { - const {model} = this.props + updateClassList() { + const { model } = this.props; - const oldClassList = this.classList - const newClassList = ['editor'] - if (this.focused) newClassList.push('is-focused') - if (model.isMini()) newClassList.push('mini') + const oldClassList = this.classList; + const newClassList = ['editor']; + if (this.focused) newClassList.push('is-focused'); + if (model.isMini()) newClassList.push('mini'); for (var i = 0; i < model.selections.length; i++) { if (!model.selections[i].isEmpty()) { - newClassList.push('has-selection') - break + newClassList.push('has-selection'); + break; } } if (oldClassList) { for (let i = 0; i < oldClassList.length; i++) { - const className = oldClassList[i] + const className = oldClassList[i]; if (!newClassList.includes(className)) { - this.element.classList.remove(className) + this.element.classList.remove(className); } } } for (let i = 0; i < newClassList.length; i++) { - this.element.classList.add(newClassList[i]) + this.element.classList.add(newClassList[i]); } - this.classList = newClassList + this.classList = newClassList; } - queryScreenLinesToRender () { - const {model} = this.props + queryScreenLinesToRender() { + const { model } = this.props; this.renderedScreenLines = model.displayLayer.getScreenLines( this.getRenderedStartRow(), this.getRenderedEndRow() - ) + ); } - queryLongestLine () { - const {model} = this.props + queryLongestLine() { + const { model } = this.props; - const longestLineRow = model.getApproximateLongestScreenRow() - const longestLine = model.screenLineForScreenRow(longestLineRow) - if (longestLine !== this.previousLongestLine || this.remeasureCharacterDimensions) { - this.requestLineToMeasure(longestLineRow, longestLine) - this.longestLineToMeasure = longestLine - this.previousLongestLine = longestLine + const longestLineRow = model.getApproximateLongestScreenRow(); + const longestLine = model.screenLineForScreenRow(longestLineRow); + if ( + longestLine !== this.previousLongestLine || + this.remeasureCharacterDimensions + ) { + this.requestLineToMeasure(longestLineRow, longestLine); + this.longestLineToMeasure = longestLine; + this.previousLongestLine = longestLine; } } - queryExtraScreenLinesToRender () { - this.extraRenderedScreenLines.clear() + queryExtraScreenLinesToRender() { + this.extraRenderedScreenLines.clear(); this.linesToMeasure.forEach((screenLine, row) => { if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) { - this.extraRenderedScreenLines.set(row, screenLine) + this.extraRenderedScreenLines.set(row, screenLine); } - }) + }); } - queryLineNumbersToRender () { - const {model} = this.props - if (!model.anyLineNumberGutterVisible()) return + queryLineNumbersToRender() { + const { model } = this.props; + if (!model.anyLineNumberGutterVisible()) return; if (this.showLineNumbers !== model.doesShowLineNumbers()) { - this.remeasureGutterDimensions = true - this.showLineNumbers = model.doesShowLineNumbers() + this.remeasureGutterDimensions = true; + this.showLineNumbers = model.doesShowLineNumbers(); } - this.queryMaxLineNumberDigits() + this.queryMaxLineNumberDigits(); - const startRow = this.getRenderedStartRow() - const endRow = this.getRenderedEndRow() - const renderedRowCount = this.getRenderedRowCount() + const startRow = this.getRenderedStartRow(); + const endRow = this.getRenderedEndRow(); + const renderedRowCount = this.getRenderedRowCount(); - const bufferRows = model.bufferRowsForScreenRows(startRow, endRow) - const screenRows = new Array(renderedRowCount) - const keys = new Array(renderedRowCount) - const foldableFlags = new Array(renderedRowCount) - const softWrappedFlags = new Array(renderedRowCount) + const bufferRows = model.bufferRowsForScreenRows(startRow, endRow); + const screenRows = new Array(renderedRowCount); + const keys = new Array(renderedRowCount); + const foldableFlags = new Array(renderedRowCount); + const softWrappedFlags = new Array(renderedRowCount); - let previousBufferRow = (startRow > 0) ? model.bufferRowForScreenRow(startRow - 1) : -1 - let softWrapCount = 0 + let previousBufferRow = + startRow > 0 ? model.bufferRowForScreenRow(startRow - 1) : -1; + let softWrapCount = 0; for (let row = startRow; row < endRow; row++) { - const i = row - startRow - const bufferRow = bufferRows[i] + const i = row - startRow; + const bufferRow = bufferRows[i]; if (bufferRow === previousBufferRow) { - softWrapCount++ - softWrappedFlags[i] = true - keys[i] = bufferRow + '-' + softWrapCount + softWrapCount++; + softWrappedFlags[i] = true; + keys[i] = bufferRow + '-' + softWrapCount; } else { - softWrapCount = 0 - softWrappedFlags[i] = false - keys[i] = bufferRow + softWrapCount = 0; + softWrappedFlags[i] = false; + keys[i] = bufferRow; } - const nextBufferRow = bufferRows[i + 1] + const nextBufferRow = bufferRows[i + 1]; if (bufferRow !== nextBufferRow) { - foldableFlags[i] = model.isFoldableAtBufferRow(bufferRow) + foldableFlags[i] = model.isFoldableAtBufferRow(bufferRow); } else { - foldableFlags[i] = false + foldableFlags[i] = false; } - screenRows[i] = row - previousBufferRow = bufferRow + screenRows[i] = row; + previousBufferRow = bufferRow; } // Delete extra buffer row at the end because it's not currently on screen. - bufferRows.pop() + bufferRows.pop(); - this.lineNumbersToRender.bufferRows = bufferRows - this.lineNumbersToRender.screenRows = screenRows - this.lineNumbersToRender.keys = keys - this.lineNumbersToRender.foldableFlags = foldableFlags - this.lineNumbersToRender.softWrappedFlags = softWrappedFlags + this.lineNumbersToRender.bufferRows = bufferRows; + this.lineNumbersToRender.screenRows = screenRows; + this.lineNumbersToRender.keys = keys; + this.lineNumbersToRender.foldableFlags = foldableFlags; + this.lineNumbersToRender.softWrappedFlags = softWrappedFlags; } - queryMaxLineNumberDigits () { - const {model} = this.props + queryMaxLineNumberDigits() { + const { model } = this.props; if (model.anyLineNumberGutterVisible()) { - const maxDigits = Math.max(2, model.getLineCount().toString().length) + const maxDigits = Math.max(2, model.getLineCount().toString().length); if (maxDigits !== this.lineNumbersToRender.maxDigits) { - this.remeasureGutterDimensions = true - this.lineNumbersToRender.maxDigits = maxDigits + this.remeasureGutterDimensions = true; + this.lineNumbersToRender.maxDigits = maxDigits; } } } - renderedScreenLineForRow (row) { + renderedScreenLineForRow(row) { return ( this.renderedScreenLines[row - this.getRenderedStartRow()] || this.extraRenderedScreenLines.get(row) - ) + ); } - queryGuttersToRender () { - const oldGuttersToRender = this.guttersToRender - const oldGuttersVisibility = this.guttersVisibility - this.guttersToRender = this.props.model.getGutters() - this.guttersVisibility = this.guttersToRender.map(g => g.visible) + queryGuttersToRender() { + const oldGuttersToRender = this.guttersToRender; + const oldGuttersVisibility = this.guttersVisibility; + this.guttersToRender = this.props.model.getGutters(); + this.guttersVisibility = this.guttersToRender.map(g => g.visible); - if (!oldGuttersToRender || oldGuttersToRender.length !== this.guttersToRender.length) { - this.remeasureGutterDimensions = true + if ( + !oldGuttersToRender || + oldGuttersToRender.length !== this.guttersToRender.length + ) { + this.remeasureGutterDimensions = true; } else { for (let i = 0, length = this.guttersToRender.length; i < length; i++) { - if (this.guttersToRender[i] !== oldGuttersToRender[i] || this.guttersVisibility[i] !== oldGuttersVisibility[i]) { - this.remeasureGutterDimensions = true - break + if ( + this.guttersToRender[i] !== oldGuttersToRender[i] || + this.guttersVisibility[i] !== oldGuttersVisibility[i] + ) { + this.remeasureGutterDimensions = true; + break; } } } } - queryDecorationsToRender () { - this.decorationsToRender.lineNumbers.clear() - this.decorationsToRender.lines = [] - this.decorationsToRender.overlays.length = 0 - this.decorationsToRender.customGutter.clear() - this.decorationsToRender.blocks = new Map() - this.decorationsToRender.text = [] - this.decorationsToMeasure.highlights.length = 0 - this.decorationsToMeasure.cursors.clear() - this.textDecorationsByMarker.clear() - this.textDecorationBoundaries.length = 0 + queryDecorationsToRender() { + this.decorationsToRender.lineNumbers.clear(); + this.decorationsToRender.lines = []; + this.decorationsToRender.overlays.length = 0; + this.decorationsToRender.customGutter.clear(); + this.decorationsToRender.blocks = new Map(); + this.decorationsToRender.text = []; + this.decorationsToMeasure.highlights.length = 0; + this.decorationsToMeasure.cursors.clear(); + this.textDecorationsByMarker.clear(); + this.textDecorationBoundaries.length = 0; - const decorationsByMarker = - this.props.model.decorationManager.decorationPropertiesByMarkerForScreenRowRange( - this.getRenderedStartRow(), - this.getRenderedEndRow() - ) + const decorationsByMarker = this.props.model.decorationManager.decorationPropertiesByMarkerForScreenRowRange( + this.getRenderedStartRow(), + this.getRenderedEndRow() + ); decorationsByMarker.forEach((decorations, marker) => { - const screenRange = marker.getScreenRange() - const reversed = marker.isReversed() + const screenRange = marker.getScreenRange(); + const reversed = marker.isReversed(); for (let i = 0; i < decorations.length; i++) { - const decoration = decorations[i] - this.addDecorationToRender(decoration.type, decoration, marker, screenRange, reversed) + const decoration = decorations[i]; + this.addDecorationToRender( + decoration.type, + decoration, + marker, + screenRange, + reversed + ); } - }) + }); - this.populateTextDecorationsToRender() + this.populateTextDecorationsToRender(); } - addDecorationToRender (type, decoration, marker, screenRange, reversed) { + addDecorationToRender(type, decoration, marker, screenRange, reversed) { if (Array.isArray(type)) { for (let i = 0, length = type.length; i < length; i++) { - this.addDecorationToRender(type[i], decoration, marker, screenRange, reversed) + this.addDecorationToRender( + type[i], + decoration, + marker, + screenRange, + reversed + ); } } else { switch (type) { case 'line': case 'line-number': - this.addLineDecorationToRender(type, decoration, screenRange, reversed) - break + this.addLineDecorationToRender( + type, + decoration, + screenRange, + reversed + ); + break; case 'highlight': - this.addHighlightDecorationToMeasure(decoration, screenRange, marker.id) - break + this.addHighlightDecorationToMeasure( + decoration, + screenRange, + marker.id + ); + break; case 'cursor': - this.addCursorDecorationToMeasure(decoration, marker, screenRange, reversed) - break + this.addCursorDecorationToMeasure( + decoration, + marker, + screenRange, + reversed + ); + break; case 'overlay': - this.addOverlayDecorationToRender(decoration, marker) - break + this.addOverlayDecorationToRender(decoration, marker); + break; case 'gutter': - this.addCustomGutterDecorationToRender(decoration, screenRange) - break + this.addCustomGutterDecorationToRender(decoration, screenRange); + break; case 'block': - this.addBlockDecorationToRender(decoration, screenRange, reversed) - break + this.addBlockDecorationToRender(decoration, screenRange, reversed); + break; case 'text': - this.addTextDecorationToRender(decoration, screenRange, marker) - break + this.addTextDecorationToRender(decoration, screenRange, marker); + break; } } } - addLineDecorationToRender (type, decoration, screenRange, reversed) { - let decorationsToRender + addLineDecorationToRender(type, decoration, screenRange, reversed) { + let decorationsToRender; if (type === 'line') { - decorationsToRender = this.decorationsToRender.lines + decorationsToRender = this.decorationsToRender.lines; } else { - const gutterName = decoration.gutterName || 'line-number' - decorationsToRender = this.decorationsToRender.lineNumbers.get(gutterName) + const gutterName = decoration.gutterName || 'line-number'; + decorationsToRender = this.decorationsToRender.lineNumbers.get( + gutterName + ); if (!decorationsToRender) { - decorationsToRender = [] - this.decorationsToRender.lineNumbers.set(gutterName, decorationsToRender) + decorationsToRender = []; + this.decorationsToRender.lineNumbers.set( + gutterName, + decorationsToRender + ); } } - let omitLastRow = false + let omitLastRow = false; if (screenRange.isEmpty()) { - if (decoration.onlyNonEmpty) return + if (decoration.onlyNonEmpty) return; } else { - if (decoration.onlyEmpty) return + if (decoration.onlyEmpty) return; if (decoration.omitEmptyLastRow !== false) { - omitLastRow = screenRange.end.column === 0 + omitLastRow = screenRange.end.column === 0; } } - const renderedStartRow = this.getRenderedStartRow() - let rangeStartRow = screenRange.start.row - let rangeEndRow = screenRange.end.row + const renderedStartRow = this.getRenderedStartRow(); + let rangeStartRow = screenRange.start.row; + let rangeEndRow = screenRange.end.row; if (decoration.onlyHead) { if (reversed) { - rangeEndRow = rangeStartRow + rangeEndRow = rangeStartRow; } else { - rangeStartRow = rangeEndRow + rangeStartRow = rangeEndRow; } } - rangeStartRow = Math.max(rangeStartRow, this.getRenderedStartRow()) - rangeEndRow = Math.min(rangeEndRow, this.getRenderedEndRow() - 1) + rangeStartRow = Math.max(rangeStartRow, this.getRenderedStartRow()); + rangeEndRow = Math.min(rangeEndRow, this.getRenderedEndRow() - 1); for (let row = rangeStartRow; row <= rangeEndRow; row++) { - if (omitLastRow && row === screenRange.end.row) break - const currentClassName = decorationsToRender[row - renderedStartRow] - const newClassName = currentClassName ? currentClassName + ' ' + decoration.class : decoration.class - decorationsToRender[row - renderedStartRow] = newClassName + if (omitLastRow && row === screenRange.end.row) break; + const currentClassName = decorationsToRender[row - renderedStartRow]; + const newClassName = currentClassName + ? currentClassName + ' ' + decoration.class + : decoration.class; + decorationsToRender[row - renderedStartRow] = newClassName; } } - addHighlightDecorationToMeasure (decoration, screenRange, key) { - screenRange = constrainRangeToRows(screenRange, this.getRenderedStartRow(), this.getRenderedEndRow()) - if (screenRange.isEmpty()) return + addHighlightDecorationToMeasure(decoration, screenRange, key) { + screenRange = constrainRangeToRows( + screenRange, + this.getRenderedStartRow(), + this.getRenderedEndRow() + ); + if (screenRange.isEmpty()) return; - const {class: className, flashRequested, flashClass, flashDuration} = decoration - decoration.flashRequested = false + const { + class: className, + flashRequested, + flashClass, + flashDuration + } = decoration; + decoration.flashRequested = false; this.decorationsToMeasure.highlights.push({ screenRange, key, @@ -1100,193 +1196,237 @@ class TextEditorComponent { flashRequested, flashClass, flashDuration - }) - this.requestHorizontalMeasurement(screenRange.start.row, screenRange.start.column) - this.requestHorizontalMeasurement(screenRange.end.row, screenRange.end.column) + }); + this.requestHorizontalMeasurement( + screenRange.start.row, + screenRange.start.column + ); + this.requestHorizontalMeasurement( + screenRange.end.row, + screenRange.end.column + ); } - addCursorDecorationToMeasure (decoration, marker, screenRange, reversed) { - const {model} = this.props - if (!model.getShowCursorOnSelection() && !screenRange.isEmpty()) return + addCursorDecorationToMeasure(decoration, marker, screenRange, reversed) { + const { model } = this.props; + if (!model.getShowCursorOnSelection() && !screenRange.isEmpty()) return; - let decorationToMeasure = this.decorationsToMeasure.cursors.get(marker) + let decorationToMeasure = this.decorationsToMeasure.cursors.get(marker); if (!decorationToMeasure) { - const isLastCursor = model.getLastCursor().getMarker() === marker - const screenPosition = reversed ? screenRange.start : screenRange.end - const {row, column} = screenPosition + const isLastCursor = model.getLastCursor().getMarker() === marker; + const screenPosition = reversed ? screenRange.start : screenRange.end; + const { row, column } = screenPosition; - if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) return + if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) + return; - this.requestHorizontalMeasurement(row, column) - let columnWidth = 0 + this.requestHorizontalMeasurement(row, column); + let columnWidth = 0; if (model.lineLengthForScreenRow(row) > column) { - columnWidth = 1 - this.requestHorizontalMeasurement(row, column + 1) + columnWidth = 1; + this.requestHorizontalMeasurement(row, column + 1); } - decorationToMeasure = {screenPosition, columnWidth, isLastCursor} - this.decorationsToMeasure.cursors.set(marker, decorationToMeasure) + decorationToMeasure = { screenPosition, columnWidth, isLastCursor }; + this.decorationsToMeasure.cursors.set(marker, decorationToMeasure); } if (decoration.class) { if (decorationToMeasure.className) { - decorationToMeasure.className += ' ' + decoration.class + decorationToMeasure.className += ' ' + decoration.class; } else { - decorationToMeasure.className = decoration.class + decorationToMeasure.className = decoration.class; } } if (decoration.style) { if (decorationToMeasure.style) { - Object.assign(decorationToMeasure.style, decoration.style) + Object.assign(decorationToMeasure.style, decoration.style); } else { - decorationToMeasure.style = Object.assign({}, decoration.style) + decorationToMeasure.style = Object.assign({}, decoration.style); } } } - addOverlayDecorationToRender (decoration, marker) { - const {class: className, item, position, avoidOverflow} = decoration - const element = TextEditor.viewForItem(item) - const screenPosition = (position === 'tail') - ? marker.getTailScreenPosition() - : marker.getHeadScreenPosition() + addOverlayDecorationToRender(decoration, marker) { + const { class: className, item, position, avoidOverflow } = decoration; + const element = TextEditor.viewForItem(item); + const screenPosition = + position === 'tail' + ? marker.getTailScreenPosition() + : marker.getHeadScreenPosition(); - this.requestHorizontalMeasurement(screenPosition.row, screenPosition.column) - this.decorationsToRender.overlays.push({className, element, avoidOverflow, screenPosition}) + this.requestHorizontalMeasurement( + screenPosition.row, + screenPosition.column + ); + this.decorationsToRender.overlays.push({ + className, + element, + avoidOverflow, + screenPosition + }); } - addCustomGutterDecorationToRender (decoration, screenRange) { - let decorations = this.decorationsToRender.customGutter.get(decoration.gutterName) + addCustomGutterDecorationToRender(decoration, screenRange) { + let decorations = this.decorationsToRender.customGutter.get( + decoration.gutterName + ); if (!decorations) { - decorations = [] - this.decorationsToRender.customGutter.set(decoration.gutterName, decorations) + decorations = []; + this.decorationsToRender.customGutter.set( + decoration.gutterName, + decorations + ); } - const top = this.pixelPositionAfterBlocksForRow(screenRange.start.row) - const height = this.pixelPositionBeforeBlocksForRow(screenRange.end.row + 1) - top + const top = this.pixelPositionAfterBlocksForRow(screenRange.start.row); + const height = + this.pixelPositionBeforeBlocksForRow(screenRange.end.row + 1) - top; decorations.push({ - className: 'decoration' + (decoration.class ? ' ' + decoration.class : ''), + className: + 'decoration' + (decoration.class ? ' ' + decoration.class : ''), element: TextEditor.viewForItem(decoration.item), top, height - }) + }); } - addBlockDecorationToRender (decoration, screenRange, reversed) { - const {row} = reversed ? screenRange.start : screenRange.end - if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) return + addBlockDecorationToRender(decoration, screenRange, reversed) { + const { row } = reversed ? screenRange.start : screenRange.end; + if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) + return; - const tileStartRow = this.tileStartRowForRow(row) - const screenLine = this.renderedScreenLines[row - this.getRenderedStartRow()] + const tileStartRow = this.tileStartRowForRow(row); + const screenLine = this.renderedScreenLines[ + row - this.getRenderedStartRow() + ]; - let decorationsByScreenLine = this.decorationsToRender.blocks.get(tileStartRow) + let decorationsByScreenLine = this.decorationsToRender.blocks.get( + tileStartRow + ); if (!decorationsByScreenLine) { - decorationsByScreenLine = new Map() - this.decorationsToRender.blocks.set(tileStartRow, decorationsByScreenLine) + decorationsByScreenLine = new Map(); + this.decorationsToRender.blocks.set( + tileStartRow, + decorationsByScreenLine + ); } - let decorations = decorationsByScreenLine.get(screenLine.id) + let decorations = decorationsByScreenLine.get(screenLine.id); if (!decorations) { - decorations = [] - decorationsByScreenLine.set(screenLine.id, decorations) + decorations = []; + decorationsByScreenLine.set(screenLine.id, decorations); } - decorations.push(decoration) + decorations.push(decoration); // Order block decorations by increasing values of their "order" property. Break ties with "id", which mirrors // their creation sequence. - decorations.sort((a, b) => a.order !== b.order ? a.order - b.order : a.id - b.id) + decorations.sort((a, b) => + a.order !== b.order ? a.order - b.order : a.id - b.id + ); } - addTextDecorationToRender (decoration, screenRange, marker) { - if (screenRange.isEmpty()) return + addTextDecorationToRender(decoration, screenRange, marker) { + if (screenRange.isEmpty()) return; - let decorationsForMarker = this.textDecorationsByMarker.get(marker) + let decorationsForMarker = this.textDecorationsByMarker.get(marker); if (!decorationsForMarker) { - decorationsForMarker = [] - this.textDecorationsByMarker.set(marker, decorationsForMarker) - this.textDecorationBoundaries.push({position: screenRange.start, starting: [marker]}) - this.textDecorationBoundaries.push({position: screenRange.end, ending: [marker]}) + decorationsForMarker = []; + this.textDecorationsByMarker.set(marker, decorationsForMarker); + this.textDecorationBoundaries.push({ + position: screenRange.start, + starting: [marker] + }); + this.textDecorationBoundaries.push({ + position: screenRange.end, + ending: [marker] + }); } - decorationsForMarker.push(decoration) + decorationsForMarker.push(decoration); } - populateTextDecorationsToRender () { + populateTextDecorationsToRender() { // Sort all boundaries in ascending order of position - this.textDecorationBoundaries.sort((a, b) => a.position.compare(b.position)) + this.textDecorationBoundaries.sort((a, b) => + a.position.compare(b.position) + ); // Combine adjacent boundaries with the same position - for (let i = 0; i < this.textDecorationBoundaries.length;) { - const boundary = this.textDecorationBoundaries[i] - const nextBoundary = this.textDecorationBoundaries[i + 1] + for (let i = 0; i < this.textDecorationBoundaries.length; ) { + const boundary = this.textDecorationBoundaries[i]; + const nextBoundary = this.textDecorationBoundaries[i + 1]; if (nextBoundary && nextBoundary.position.isEqual(boundary.position)) { if (nextBoundary.starting) { if (boundary.starting) { - boundary.starting.push(...nextBoundary.starting) + boundary.starting.push(...nextBoundary.starting); } else { - boundary.starting = nextBoundary.starting + boundary.starting = nextBoundary.starting; } } if (nextBoundary.ending) { if (boundary.ending) { - boundary.ending.push(...nextBoundary.ending) + boundary.ending.push(...nextBoundary.ending); } else { - boundary.ending = nextBoundary.ending + boundary.ending = nextBoundary.ending; } } - this.textDecorationBoundaries.splice(i + 1, 1) + this.textDecorationBoundaries.splice(i + 1, 1); } else { - i++ + i++; } } - const renderedStartRow = this.getRenderedStartRow() - const renderedEndRow = this.getRenderedEndRow() - const containingMarkers = [] + const renderedStartRow = this.getRenderedStartRow(); + const renderedEndRow = this.getRenderedEndRow(); + const containingMarkers = []; // Iterate over boundaries to build up text decorations. for (let i = 0; i < this.textDecorationBoundaries.length; i++) { - const boundary = this.textDecorationBoundaries[i] + const boundary = this.textDecorationBoundaries[i]; // If multiple markers start here, sort them by order of nesting (markers ending later come first) if (boundary.starting && boundary.starting.length > 1) { - boundary.starting.sort((a, b) => a.compare(b)) + boundary.starting.sort((a, b) => a.compare(b)); } // If multiple markers start here, sort them by order of nesting (markers starting earlier come first) if (boundary.ending && boundary.ending.length > 1) { - boundary.ending.sort((a, b) => b.compare(a)) + boundary.ending.sort((a, b) => b.compare(a)); } // Remove markers ending here from containing markers array if (boundary.ending) { for (let j = boundary.ending.length - 1; j >= 0; j--) { - containingMarkers.splice(containingMarkers.lastIndexOf(boundary.ending[j]), 1) + containingMarkers.splice( + containingMarkers.lastIndexOf(boundary.ending[j]), + 1 + ); } } // Add markers starting here to containing markers array - if (boundary.starting) containingMarkers.push(...boundary.starting) + if (boundary.starting) containingMarkers.push(...boundary.starting); // Determine desired className and style based on containing markers - let className, style + let className, style; for (let j = 0; j < containingMarkers.length; j++) { - const marker = containingMarkers[j] - const decorations = this.textDecorationsByMarker.get(marker) + const marker = containingMarkers[j]; + const decorations = this.textDecorationsByMarker.get(marker); for (let k = 0; k < decorations.length; k++) { - const decoration = decorations[k] + const decoration = decorations[k]; if (decoration.class) { if (className) { - className += ' ' + decoration.class + className += ' ' + decoration.class; } else { - className = decoration.class + className = decoration.class; } } if (decoration.style) { if (style) { - Object.assign(style, decoration.style) + Object.assign(style, decoration.style); } else { - style = Object.assign({}, decoration.style) + style = Object.assign({}, decoration.style); } } } @@ -1295,337 +1435,391 @@ class TextEditorComponent { // Add decoration start with className/style for current position's column, // and also for the start of every row up until the next decoration boundary if (boundary.position.row >= renderedStartRow) { - this.addTextDecorationStart(boundary.position.row, boundary.position.column, className, style) + this.addTextDecorationStart( + boundary.position.row, + boundary.position.column, + className, + style + ); } - const nextBoundary = this.textDecorationBoundaries[i + 1] + const nextBoundary = this.textDecorationBoundaries[i + 1]; if (nextBoundary) { - let row = Math.max(boundary.position.row + 1, renderedStartRow) - const endRow = Math.min(nextBoundary.position.row, renderedEndRow) + let row = Math.max(boundary.position.row + 1, renderedStartRow); + const endRow = Math.min(nextBoundary.position.row, renderedEndRow); for (; row < endRow; row++) { - this.addTextDecorationStart(row, 0, className, style) + this.addTextDecorationStart(row, 0, className, style); } - if (row === nextBoundary.position.row && nextBoundary.position.column !== 0) { - this.addTextDecorationStart(row, 0, className, style) + if ( + row === nextBoundary.position.row && + nextBoundary.position.column !== 0 + ) { + this.addTextDecorationStart(row, 0, className, style); } } } } - addTextDecorationStart (row, column, className, style) { - const renderedStartRow = this.getRenderedStartRow() - let decorationStarts = this.decorationsToRender.text[row - renderedStartRow] + addTextDecorationStart(row, column, className, style) { + const renderedStartRow = this.getRenderedStartRow(); + let decorationStarts = this.decorationsToRender.text[ + row - renderedStartRow + ]; if (!decorationStarts) { - decorationStarts = [] - this.decorationsToRender.text[row - renderedStartRow] = decorationStarts + decorationStarts = []; + this.decorationsToRender.text[row - renderedStartRow] = decorationStarts; } - decorationStarts.push({column, className, style}) + decorationStarts.push({ column, className, style }); } - updateAbsolutePositionedDecorations () { - this.updateHighlightsToRender() - this.updateCursorsToRender() - this.updateOverlaysToRender() + updateAbsolutePositionedDecorations() { + this.updateHighlightsToRender(); + this.updateCursorsToRender(); + this.updateOverlaysToRender(); } - updateHighlightsToRender () { - this.decorationsToRender.highlights.length = 0 + updateHighlightsToRender() { + this.decorationsToRender.highlights.length = 0; for (let i = 0; i < this.decorationsToMeasure.highlights.length; i++) { - const highlight = this.decorationsToMeasure.highlights[i] - const {start, end} = highlight.screenRange - highlight.startPixelTop = this.pixelPositionAfterBlocksForRow(start.row) - highlight.startPixelLeft = this.pixelLeftForRowAndColumn(start.row, start.column) - highlight.endPixelTop = this.pixelPositionAfterBlocksForRow(end.row) + this.getLineHeight() - highlight.endPixelLeft = this.pixelLeftForRowAndColumn(end.row, end.column) - this.decorationsToRender.highlights.push(highlight) + const highlight = this.decorationsToMeasure.highlights[i]; + const { start, end } = highlight.screenRange; + highlight.startPixelTop = this.pixelPositionAfterBlocksForRow(start.row); + highlight.startPixelLeft = this.pixelLeftForRowAndColumn( + start.row, + start.column + ); + highlight.endPixelTop = + this.pixelPositionAfterBlocksForRow(end.row) + this.getLineHeight(); + highlight.endPixelLeft = this.pixelLeftForRowAndColumn( + end.row, + end.column + ); + this.decorationsToRender.highlights.push(highlight); } } - updateCursorsToRender () { - this.decorationsToRender.cursors.length = 0 + updateCursorsToRender() { + this.decorationsToRender.cursors.length = 0; - this.decorationsToMeasure.cursors.forEach((cursor) => { - const {screenPosition, className, style} = cursor - const {row, column} = screenPosition + this.decorationsToMeasure.cursors.forEach(cursor => { + const { screenPosition, className, style } = cursor; + const { row, column } = screenPosition; - const pixelTop = this.pixelPositionAfterBlocksForRow(row) - const pixelLeft = this.pixelLeftForRowAndColumn(row, column) - let pixelWidth + const pixelTop = this.pixelPositionAfterBlocksForRow(row); + const pixelLeft = this.pixelLeftForRowAndColumn(row, column); + let pixelWidth; if (cursor.columnWidth === 0) { - pixelWidth = this.getBaseCharacterWidth() + pixelWidth = this.getBaseCharacterWidth(); } else { - pixelWidth = this.pixelLeftForRowAndColumn(row, column + 1) - pixelLeft + pixelWidth = this.pixelLeftForRowAndColumn(row, column + 1) - pixelLeft; } - const cursorPosition = {pixelTop, pixelLeft, pixelWidth, className, style} - this.decorationsToRender.cursors.push(cursorPosition) - if (cursor.isLastCursor) this.hiddenInputPosition = cursorPosition - }) + const cursorPosition = { + pixelTop, + pixelLeft, + pixelWidth, + className, + style + }; + this.decorationsToRender.cursors.push(cursorPosition); + if (cursor.isLastCursor) this.hiddenInputPosition = cursorPosition; + }); } - updateOverlayToRender (decoration) { - const windowInnerHeight = this.getWindowInnerHeight() - const windowInnerWidth = this.getWindowInnerWidth() - const contentClientRect = this.refs.content.getBoundingClientRect() + updateOverlayToRender(decoration) { + const windowInnerHeight = this.getWindowInnerHeight(); + const windowInnerWidth = this.getWindowInnerWidth(); + const contentClientRect = this.refs.content.getBoundingClientRect(); - const {element, screenPosition, avoidOverflow} = decoration - const {row, column} = screenPosition - let wrapperTop = contentClientRect.top + this.pixelPositionAfterBlocksForRow(row) + this.getLineHeight() - let wrapperLeft = contentClientRect.left + this.pixelLeftForRowAndColumn(row, column) - const clientRect = element.getBoundingClientRect() + const { element, screenPosition, avoidOverflow } = decoration; + const { row, column } = screenPosition; + let wrapperTop = + contentClientRect.top + + this.pixelPositionAfterBlocksForRow(row) + + this.getLineHeight(); + let wrapperLeft = + contentClientRect.left + this.pixelLeftForRowAndColumn(row, column); + const clientRect = element.getBoundingClientRect(); if (avoidOverflow !== false) { - const computedStyle = window.getComputedStyle(element) - const elementTop = wrapperTop + parseInt(computedStyle.marginTop) - const elementBottom = elementTop + clientRect.height - const flippedElementTop = wrapperTop - this.getLineHeight() - clientRect.height - parseInt(computedStyle.marginBottom) - const elementLeft = wrapperLeft + parseInt(computedStyle.marginLeft) - const elementRight = elementLeft + clientRect.width + const computedStyle = window.getComputedStyle(element); + const elementTop = wrapperTop + parseInt(computedStyle.marginTop); + const elementBottom = elementTop + clientRect.height; + const flippedElementTop = + wrapperTop - + this.getLineHeight() - + clientRect.height - + parseInt(computedStyle.marginBottom); + const elementLeft = wrapperLeft + parseInt(computedStyle.marginLeft); + const elementRight = elementLeft + clientRect.width; if (elementBottom > windowInnerHeight && flippedElementTop >= 0) { - wrapperTop -= (elementTop - flippedElementTop) + wrapperTop -= elementTop - flippedElementTop; } if (elementLeft < 0) { - wrapperLeft -= elementLeft + wrapperLeft -= elementLeft; } else if (elementRight > windowInnerWidth) { - wrapperLeft -= (elementRight - windowInnerWidth) + wrapperLeft -= elementRight - windowInnerWidth; } } - decoration.pixelTop = Math.round(wrapperTop) - decoration.pixelLeft = Math.round(wrapperLeft) + decoration.pixelTop = Math.round(wrapperTop); + decoration.pixelLeft = Math.round(wrapperLeft); } - updateOverlaysToRender () { - const overlayCount = this.decorationsToRender.overlays.length - if (overlayCount === 0) return null + updateOverlaysToRender() { + const overlayCount = this.decorationsToRender.overlays.length; + if (overlayCount === 0) return null; for (let i = 0; i < overlayCount; i++) { - const decoration = this.decorationsToRender.overlays[i] - this.updateOverlayToRender(decoration) + const decoration = this.decorationsToRender.overlays[i]; + this.updateOverlayToRender(decoration); } } - didAttach () { + didAttach() { if (!this.attached) { - this.attached = true - this.intersectionObserver = new IntersectionObserver((entries) => { - const {intersectionRect} = entries[entries.length - 1] + this.attached = true; + this.intersectionObserver = new IntersectionObserver(entries => { + const { intersectionRect } = entries[entries.length - 1]; if (intersectionRect.width > 0 || intersectionRect.height > 0) { - this.didShow() + this.didShow(); } else { - this.didHide() + this.didHide(); } - }) - this.intersectionObserver.observe(this.element) + }); + this.intersectionObserver.observe(this.element); - this.resizeObserver = new ResizeObserver(this.didResize.bind(this)) - this.resizeObserver.observe(this.element) + this.resizeObserver = new ResizeObserver(this.didResize.bind(this)); + this.resizeObserver.observe(this.element); if (this.refs.gutterContainer) { - this.gutterContainerResizeObserver = new ResizeObserver(this.didResizeGutterContainer.bind(this)) - this.gutterContainerResizeObserver.observe(this.refs.gutterContainer.element) + this.gutterContainerResizeObserver = new ResizeObserver( + this.didResizeGutterContainer.bind(this) + ); + this.gutterContainerResizeObserver.observe( + this.refs.gutterContainer.element + ); } - this.overlayComponents.forEach((component) => component.didAttach()) + this.overlayComponents.forEach(component => component.didAttach()); if (this.isVisible()) { - this.didShow() + this.didShow(); - if (this.refs.verticalScrollbar) this.refs.verticalScrollbar.flushScrollPosition() - if (this.refs.horizontalScrollbar) this.refs.horizontalScrollbar.flushScrollPosition() + if (this.refs.verticalScrollbar) + this.refs.verticalScrollbar.flushScrollPosition(); + if (this.refs.horizontalScrollbar) + this.refs.horizontalScrollbar.flushScrollPosition(); } else { - this.didHide() + this.didHide(); } if (!this.constructor.attachedComponents) { - this.constructor.attachedComponents = new Set() + this.constructor.attachedComponents = new Set(); } - this.constructor.attachedComponents.add(this) + this.constructor.attachedComponents.add(this); } } - didDetach () { + didDetach() { if (this.attached) { - this.intersectionObserver.disconnect() - this.resizeObserver.disconnect() - if (this.gutterContainerResizeObserver) this.gutterContainerResizeObserver.disconnect() - this.overlayComponents.forEach((component) => component.didDetach()) + this.intersectionObserver.disconnect(); + this.resizeObserver.disconnect(); + if (this.gutterContainerResizeObserver) + this.gutterContainerResizeObserver.disconnect(); + this.overlayComponents.forEach(component => component.didDetach()); - this.didHide() - this.attached = false - this.constructor.attachedComponents.delete(this) + this.didHide(); + this.attached = false; + this.constructor.attachedComponents.delete(this); } } - didShow () { + didShow() { if (!this.visible && this.isVisible()) { - if (!this.hasInitialMeasurements) this.measureDimensions() - this.visible = true - this.props.model.setVisible(true) - this.resizeBlockDecorationMeasurementsArea = true - this.updateSync() - this.flushPendingLogicalScrollPosition() + if (!this.hasInitialMeasurements) this.measureDimensions(); + this.visible = true; + this.props.model.setVisible(true); + this.resizeBlockDecorationMeasurementsArea = true; + this.updateSync(); + this.flushPendingLogicalScrollPosition(); } } - didHide () { + didHide() { if (this.visible) { - this.visible = false - this.props.model.setVisible(false) + this.visible = false; + this.props.model.setVisible(false); } } // Called by TextEditorElement so that focus events can be handled before // the element is attached to the DOM. - didFocus () { + didFocus() { // This element can be focused from a parent custom element's // attachedCallback before *its* attachedCallback is fired. This protects // against that case. - if (!this.attached) this.didAttach() + if (!this.attached) this.didAttach(); // The element can be focused before the intersection observer detects that // it has been shown for the first time. If this element is being focused, // it is necessarily visible, so we call `didShow` to ensure the hidden // input is rendered before we try to shift focus to it. - if (!this.visible) this.didShow() + if (!this.visible) this.didShow(); if (!this.focused) { - this.focused = true - this.startCursorBlinking() - this.scheduleUpdate() + this.focused = true; + this.startCursorBlinking(); + this.scheduleUpdate(); } - this.getHiddenInput().focus() + this.getHiddenInput().focus(); } // Called by TextEditorElement so that this function is always the first // listener to be fired, even if other listeners are bound before creating // the component. - didBlur (event) { + didBlur(event) { if (event.relatedTarget === this.getHiddenInput()) { - event.stopImmediatePropagation() + event.stopImmediatePropagation(); } } - didBlurHiddenInput (event) { - if (this.element !== event.relatedTarget && !this.element.contains(event.relatedTarget)) { - this.focused = false - this.stopCursorBlinking() - this.scheduleUpdate() - this.element.dispatchEvent(new FocusEvent(event.type, event)) + didBlurHiddenInput(event) { + if ( + this.element !== event.relatedTarget && + !this.element.contains(event.relatedTarget) + ) { + this.focused = false; + this.stopCursorBlinking(); + this.scheduleUpdate(); + this.element.dispatchEvent(new FocusEvent(event.type, event)); } } - didFocusHiddenInput () { + didFocusHiddenInput() { // Focusing the hidden input when it is off-screen causes the browser to // scroll it into view. Since we use synthetic scrolling this behavior // causes all the lines to disappear so we counteract it by always setting // the scroll position to 0. - this.refs.scrollContainer.scrollTop = 0 - this.refs.scrollContainer.scrollLeft = 0 + this.refs.scrollContainer.scrollTop = 0; + this.refs.scrollContainer.scrollLeft = 0; if (!this.focused) { - this.focused = true - this.startCursorBlinking() - this.scheduleUpdate() + this.focused = true; + this.startCursorBlinking(); + this.scheduleUpdate(); } } - didMouseWheel (event) { - const scrollSensitivity = this.props.model.getScrollSensitivity() / 100 + didMouseWheel(event) { + const scrollSensitivity = this.props.model.getScrollSensitivity() / 100; - let {wheelDeltaX, wheelDeltaY} = event + let { wheelDeltaX, wheelDeltaY } = event; if (Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)) { - wheelDeltaX = wheelDeltaX * scrollSensitivity - wheelDeltaY = 0 + wheelDeltaX = wheelDeltaX * scrollSensitivity; + wheelDeltaY = 0; } else { - wheelDeltaX = 0 - wheelDeltaY = wheelDeltaY * scrollSensitivity + wheelDeltaX = 0; + wheelDeltaY = wheelDeltaY * scrollSensitivity; } if (this.getPlatform() !== 'darwin' && event.shiftKey) { - let temp = wheelDeltaX - wheelDeltaX = wheelDeltaY - wheelDeltaY = temp + let temp = wheelDeltaX; + wheelDeltaX = wheelDeltaY; + wheelDeltaY = temp; } - const scrollLeftChanged = wheelDeltaX !== 0 && this.setScrollLeft(this.getScrollLeft() - wheelDeltaX) - const scrollTopChanged = wheelDeltaY !== 0 && this.setScrollTop(this.getScrollTop() - wheelDeltaY) + const scrollLeftChanged = + wheelDeltaX !== 0 && + this.setScrollLeft(this.getScrollLeft() - wheelDeltaX); + const scrollTopChanged = + wheelDeltaY !== 0 && this.setScrollTop(this.getScrollTop() - wheelDeltaY); - if (scrollLeftChanged || scrollTopChanged) this.updateSync() + if (scrollLeftChanged || scrollTopChanged) this.updateSync(); } - didResize () { + didResize() { // Prevent the component from measuring the client container dimensions when // getting spurious resize events. if (this.isVisible()) { - const clientContainerWidthChanged = this.measureClientContainerWidth() - const clientContainerHeightChanged = this.measureClientContainerHeight() + const clientContainerWidthChanged = this.measureClientContainerWidth(); + const clientContainerHeightChanged = this.measureClientContainerHeight(); if (clientContainerWidthChanged || clientContainerHeightChanged) { if (clientContainerWidthChanged) { - this.remeasureAllBlockDecorations = true + this.remeasureAllBlockDecorations = true; } - this.resizeObserver.disconnect() - this.scheduleUpdate() - process.nextTick(() => { this.resizeObserver.observe(this.element) }) + this.resizeObserver.disconnect(); + this.scheduleUpdate(); + process.nextTick(() => { + this.resizeObserver.observe(this.element); + }); } } } - didResizeGutterContainer () { + didResizeGutterContainer() { // Prevent the component from measuring the gutter dimensions when getting // spurious resize events. if (this.isVisible() && this.measureGutterDimensions()) { - this.gutterContainerResizeObserver.disconnect() - this.scheduleUpdate() - process.nextTick(() => { this.gutterContainerResizeObserver.observe(this.refs.gutterContainer.element) }) + this.gutterContainerResizeObserver.disconnect(); + this.scheduleUpdate(); + process.nextTick(() => { + this.gutterContainerResizeObserver.observe( + this.refs.gutterContainer.element + ); + }); } } - didScrollDummyScrollbar () { - let scrollTopChanged = false - let scrollLeftChanged = false + didScrollDummyScrollbar() { + let scrollTopChanged = false; + let scrollLeftChanged = false; if (!this.scrollTopPending) { - scrollTopChanged = this.setScrollTop(this.refs.verticalScrollbar.element.scrollTop) + scrollTopChanged = this.setScrollTop( + this.refs.verticalScrollbar.element.scrollTop + ); } if (!this.scrollLeftPending) { - scrollLeftChanged = this.setScrollLeft(this.refs.horizontalScrollbar.element.scrollLeft) + scrollLeftChanged = this.setScrollLeft( + this.refs.horizontalScrollbar.element.scrollLeft + ); } - if (scrollTopChanged || scrollLeftChanged) this.updateSync() + if (scrollTopChanged || scrollLeftChanged) this.updateSync(); } - didUpdateStyles () { - this.remeasureCharacterDimensions = true - this.horizontalPixelPositionsByScreenLineId.clear() - this.scheduleUpdate() + didUpdateStyles() { + this.remeasureCharacterDimensions = true; + this.horizontalPixelPositionsByScreenLineId.clear(); + this.scheduleUpdate(); } - didUpdateScrollbarStyles () { + didUpdateScrollbarStyles() { if (!this.props.model.isMini()) { - this.remeasureScrollbars = true - this.scheduleUpdate() + this.remeasureScrollbars = true; + this.scheduleUpdate(); } } - didPaste (event) { + didPaste(event) { // On Linux, Chromium translates a middle-button mouse click into a // mousedown event *and* a paste event. Since Atom supports the middle mouse // click as a way of closing a tab, we only want the mousedown event, not // the paste event. And since we don't use the `paste` event for any // behavior in Atom, we can no-op the event to eliminate this issue. // See https://github.com/atom/atom/pull/15183#issue-248432413. - if (this.getPlatform() === 'linux') event.preventDefault() + if (this.getPlatform() === 'linux') event.preventDefault(); } - didTextInput (event) { + didTextInput(event) { if (this.compositionCheckpoint) { - this.props.model.revertToCheckpoint(this.compositionCheckpoint) - this.compositionCheckpoint = null + this.props.model.revertToCheckpoint(this.compositionCheckpoint); + this.compositionCheckpoint = null; } if (this.isInputEnabled()) { - event.stopPropagation() + event.stopPropagation(); // WARNING: If we call preventDefault on the input of a space // character, then the browser interprets the spacebar keypress as a @@ -1638,11 +1832,11 @@ class TextEditorComponent { // typing a space. None of this can really be tested. if (event.data === ' ') { window.setImmediate(() => { - this.refs.scrollContainer.scrollTop = 0 - this.refs.scrollContainer.scrollLeft = 0 - }) + this.refs.scrollContainer.scrollTop = 0; + this.refs.scrollContainer.scrollLeft = 0; + }); } else { - event.preventDefault() + event.preventDefault(); } // If the input event is fired while the accented character menu is open it @@ -1650,10 +1844,10 @@ class TextEditorComponent { // will replace the original non accented character with the selected // alternative. if (this.accentedCharacterMenuIsOpen) { - this.props.model.selectLeft() + this.props.model.selectLeft(); } - this.props.model.insertText(event.data, {groupUndo: true}) + this.props.model.insertText(event.data, { groupUndo: true }); } } @@ -1673,37 +1867,46 @@ class TextEditorComponent { // The code X must be the same in the keydown events that bracket the // keypress, meaning we're *holding* the _same_ key we intially pressed. // Got that? - didKeydown (event) { + didKeydown(event) { // Stop dragging when user interacts with the keyboard. This prevents // unwanted selections in the case edits are performed while selecting text // at the same time. Modifier keys are exempt to preserve the ability to // add selections, shift-scroll horizontally while selecting. - if (this.stopDragging && event.key !== 'Control' && event.key !== 'Alt' && event.key !== 'Meta' && event.key !== 'Shift') { - this.stopDragging() + if ( + this.stopDragging && + event.key !== 'Control' && + event.key !== 'Alt' && + event.key !== 'Meta' && + event.key !== 'Shift' + ) { + this.stopDragging(); } if (this.lastKeydownBeforeKeypress != null) { if (this.lastKeydownBeforeKeypress.code === event.code) { - this.accentedCharacterMenuIsOpen = true + this.accentedCharacterMenuIsOpen = true; } - this.lastKeydownBeforeKeypress = null + this.lastKeydownBeforeKeypress = null; } - this.lastKeydown = event + this.lastKeydown = event; } - didKeypress (event) { - this.lastKeydownBeforeKeypress = this.lastKeydown + didKeypress(event) { + this.lastKeydownBeforeKeypress = this.lastKeydown; // This cancels the accented character behavior if we type a key normally // with the menu open. - this.accentedCharacterMenuIsOpen = false + this.accentedCharacterMenuIsOpen = false; } - didKeyup (event) { - if (this.lastKeydownBeforeKeypress && this.lastKeydownBeforeKeypress.code === event.code) { - this.lastKeydownBeforeKeypress = null + didKeyup(event) { + if ( + this.lastKeydownBeforeKeypress && + this.lastKeydownBeforeKeypress.code === event.code + ) { + this.lastKeydownBeforeKeypress = null; } } @@ -1717,419 +1920,508 @@ class TextEditorComponent { // User escape to cancel OR User chooses a completion // 4. compositionend fired // 5. textInput fired; event.data == the completion string - didCompositionStart () { + didCompositionStart() { // Workaround for Chromium not preventing composition events when // preventDefault is called on the keydown event that precipitated them. if (this.lastKeydown && this.lastKeydown.defaultPrevented) { - this.getHiddenInput().disabled = true + this.getHiddenInput().disabled = true; process.nextTick(() => { // Disabling the hidden input makes it lose focus as well, so we have to // re-enable and re-focus it. - this.getHiddenInput().disabled = false - this.getHiddenInput().focus() - }) - return + this.getHiddenInput().disabled = false; + this.getHiddenInput().focus(); + }); + return; } - this.compositionCheckpoint = this.props.model.createCheckpoint() + this.compositionCheckpoint = this.props.model.createCheckpoint(); if (this.accentedCharacterMenuIsOpen) { - this.props.model.selectLeft() + this.props.model.selectLeft(); } } - didCompositionUpdate (event) { - this.props.model.insertText(event.data, {select: true}) + didCompositionUpdate(event) { + this.props.model.insertText(event.data, { select: true }); } - didCompositionEnd (event) { - event.target.value = '' + didCompositionEnd(event) { + event.target.value = ''; } - didMouseDownOnContent (event) { - const {model} = this.props - const {target, button, detail, ctrlKey, shiftKey, metaKey} = event - const platform = this.getPlatform() + didMouseDownOnContent(event) { + const { model } = this.props; + const { target, button, detail, ctrlKey, shiftKey, metaKey } = event; + const platform = this.getPlatform(); // Ignore clicks on block decorations. if (target) { - let element = target + let element = target; while (element && element !== this.element) { if (this.blockDecorationsByElement.has(element)) { - return + return; } - element = element.parentElement + element = element.parentElement; } } - const screenPosition = this.screenPositionForMouseEvent(event) + const screenPosition = this.screenPositionForMouseEvent(event); if (button === 1) { - model.setCursorScreenPosition(screenPosition, {autoscroll: false}) + model.setCursorScreenPosition(screenPosition, { autoscroll: false }); // On Linux, pasting happens on middle click. A textInput event with the // contents of the selection clipboard will be dispatched by the browser // automatically on mouseup. - if (platform === 'linux' && this.isInputEnabled()) model.insertText(clipboard.readText('selection')) - return + if (platform === 'linux' && this.isInputEnabled()) + model.insertText(clipboard.readText('selection')); + return; } - if (button !== 0) return + if (button !== 0) return; // Ctrl-click brings up the context menu on macOS - if (platform === 'darwin' && ctrlKey) return + if (platform === 'darwin' && ctrlKey) return; if (target && target.matches('.fold-marker')) { - const bufferPosition = model.bufferPositionForScreenPosition(screenPosition) - model.destroyFoldsContainingBufferPositions([bufferPosition], false) - return + const bufferPosition = model.bufferPositionForScreenPosition( + screenPosition + ); + model.destroyFoldsContainingBufferPositions([bufferPosition], false); + return; } - const addOrRemoveSelection = metaKey || (ctrlKey && platform !== 'darwin') + const addOrRemoveSelection = metaKey || (ctrlKey && platform !== 'darwin'); switch (detail) { case 1: if (addOrRemoveSelection) { - const existingSelection = model.getSelectionAtScreenPosition(screenPosition) + const existingSelection = model.getSelectionAtScreenPosition( + screenPosition + ); if (existingSelection) { - if (model.hasMultipleCursors()) existingSelection.destroy() + if (model.hasMultipleCursors()) existingSelection.destroy(); } else { - model.addCursorAtScreenPosition(screenPosition, {autoscroll: false}) + model.addCursorAtScreenPosition(screenPosition, { + autoscroll: false + }); } } else { if (shiftKey) { - model.selectToScreenPosition(screenPosition, {autoscroll: false}) + model.selectToScreenPosition(screenPosition, { autoscroll: false }); } else { - model.setCursorScreenPosition(screenPosition, {autoscroll: false}) + model.setCursorScreenPosition(screenPosition, { + autoscroll: false + }); } } - break + break; case 2: - if (addOrRemoveSelection) model.addCursorAtScreenPosition(screenPosition, {autoscroll: false}) - model.getLastSelection().selectWord({autoscroll: false}) - break + if (addOrRemoveSelection) + model.addCursorAtScreenPosition(screenPosition, { + autoscroll: false + }); + model.getLastSelection().selectWord({ autoscroll: false }); + break; case 3: - if (addOrRemoveSelection) model.addCursorAtScreenPosition(screenPosition, {autoscroll: false}) - model.getLastSelection().selectLine(null, {autoscroll: false}) - break + if (addOrRemoveSelection) + model.addCursorAtScreenPosition(screenPosition, { + autoscroll: false + }); + model.getLastSelection().selectLine(null, { autoscroll: false }); + break; } this.handleMouseDragUntilMouseUp({ - didDrag: (event) => { - this.autoscrollOnMouseDrag(event) - const screenPosition = this.screenPositionForMouseEvent(event) - model.selectToScreenPosition(screenPosition, {suppressSelectionMerge: true, autoscroll: false}) - this.updateSync() + didDrag: event => { + this.autoscrollOnMouseDrag(event); + const screenPosition = this.screenPositionForMouseEvent(event); + model.selectToScreenPosition(screenPosition, { + suppressSelectionMerge: true, + autoscroll: false + }); + this.updateSync(); }, didStopDragging: () => { - model.finalizeSelections() - model.mergeIntersectingSelections() - this.updateSync() + model.finalizeSelections(); + model.mergeIntersectingSelections(); + this.updateSync(); } - }) + }); } - didMouseDownOnLineNumberGutter (event) { - const {model} = this.props - const {target, button, ctrlKey, shiftKey, metaKey} = event + didMouseDownOnLineNumberGutter(event) { + const { model } = this.props; + const { target, button, ctrlKey, shiftKey, metaKey } = event; // Only handle mousedown events for left mouse button - if (button !== 0) return + if (button !== 0) return; - const clickedScreenRow = this.screenPositionForMouseEvent(event).row - const startBufferRow = model.bufferPositionForScreenPosition([clickedScreenRow, 0]).row + const clickedScreenRow = this.screenPositionForMouseEvent(event).row; + const startBufferRow = model.bufferPositionForScreenPosition([ + clickedScreenRow, + 0 + ]).row; - if (target && (target.matches('.foldable .icon-right') || target.matches('.folded .icon-right'))) { - model.toggleFoldAtBufferRow(startBufferRow) - return + if ( + target && + (target.matches('.foldable .icon-right') || + target.matches('.folded .icon-right')) + ) { + model.toggleFoldAtBufferRow(startBufferRow); + return; } - const addOrRemoveSelection = metaKey || (ctrlKey && this.getPlatform() !== 'darwin') - const endBufferRow = model.bufferPositionForScreenPosition([clickedScreenRow, Infinity]).row - const clickedLineBufferRange = Range(Point(startBufferRow, 0), Point(endBufferRow + 1, 0)) + const addOrRemoveSelection = + metaKey || (ctrlKey && this.getPlatform() !== 'darwin'); + const endBufferRow = model.bufferPositionForScreenPosition([ + clickedScreenRow, + Infinity + ]).row; + const clickedLineBufferRange = Range( + Point(startBufferRow, 0), + Point(endBufferRow + 1, 0) + ); - let initialBufferRange + let initialBufferRange; if (shiftKey) { - const lastSelection = model.getLastSelection() - initialBufferRange = lastSelection.getBufferRange() - lastSelection.setBufferRange(initialBufferRange.union(clickedLineBufferRange), { - reversed: clickedScreenRow < lastSelection.getScreenRange().start.row, - autoscroll: false, - preserveFolds: true, - suppressSelectionMerge: true - }) + const lastSelection = model.getLastSelection(); + initialBufferRange = lastSelection.getBufferRange(); + lastSelection.setBufferRange( + initialBufferRange.union(clickedLineBufferRange), + { + reversed: clickedScreenRow < lastSelection.getScreenRange().start.row, + autoscroll: false, + preserveFolds: true, + suppressSelectionMerge: true + } + ); } else { - initialBufferRange = clickedLineBufferRange + initialBufferRange = clickedLineBufferRange; if (addOrRemoveSelection) { - model.addSelectionForBufferRange(clickedLineBufferRange, {autoscroll: false, preserveFolds: true}) - } else { - model.setSelectedBufferRange(clickedLineBufferRange, {autoscroll: false, preserveFolds: true}) - } - } - - const initialScreenRange = model.screenRangeForBufferRange(initialBufferRange) - this.handleMouseDragUntilMouseUp({ - didDrag: (event) => { - this.autoscrollOnMouseDrag(event, true) - const dragRow = this.screenPositionForMouseEvent(event).row - const draggedLineScreenRange = Range(Point(dragRow, 0), Point(dragRow + 1, 0)) - model.getLastSelection().setScreenRange(draggedLineScreenRange.union(initialScreenRange), { - reversed: dragRow < initialScreenRange.start.row, + model.addSelectionForBufferRange(clickedLineBufferRange, { autoscroll: false, preserveFolds: true - }) - this.updateSync() + }); + } else { + model.setSelectedBufferRange(clickedLineBufferRange, { + autoscroll: false, + preserveFolds: true + }); + } + } + + const initialScreenRange = model.screenRangeForBufferRange( + initialBufferRange + ); + this.handleMouseDragUntilMouseUp({ + didDrag: event => { + this.autoscrollOnMouseDrag(event, true); + const dragRow = this.screenPositionForMouseEvent(event).row; + const draggedLineScreenRange = Range( + Point(dragRow, 0), + Point(dragRow + 1, 0) + ); + model + .getLastSelection() + .setScreenRange(draggedLineScreenRange.union(initialScreenRange), { + reversed: dragRow < initialScreenRange.start.row, + autoscroll: false, + preserveFolds: true + }); + this.updateSync(); }, didStopDragging: () => { - model.mergeIntersectingSelections() - this.updateSync() + model.mergeIntersectingSelections(); + this.updateSync(); } - }) + }); } - handleMouseDragUntilMouseUp ({didDrag, didStopDragging}) { - let dragging = false - let lastMousemoveEvent + handleMouseDragUntilMouseUp({ didDrag, didStopDragging }) { + let dragging = false; + let lastMousemoveEvent; const animationFrameLoop = () => { window.requestAnimationFrame(() => { if (dragging && this.visible) { - didDrag(lastMousemoveEvent) - animationFrameLoop() + didDrag(lastMousemoveEvent); + animationFrameLoop(); } - }) - } + }); + }; - function didMouseMove (event) { - lastMousemoveEvent = event + function didMouseMove(event) { + lastMousemoveEvent = event; if (!dragging) { - dragging = true - animationFrameLoop() + dragging = true; + animationFrameLoop(); } } - function didMouseUp () { - this.stopDragging = null - window.removeEventListener('mousemove', didMouseMove) - window.removeEventListener('mouseup', didMouseUp, {capture: true}) + function didMouseUp() { + this.stopDragging = null; + window.removeEventListener('mousemove', didMouseMove); + window.removeEventListener('mouseup', didMouseUp, { capture: true }); if (dragging) { - dragging = false - didStopDragging() + dragging = false; + didStopDragging(); } } - window.addEventListener('mousemove', didMouseMove) - window.addEventListener('mouseup', didMouseUp, {capture: true}) - this.stopDragging = didMouseUp + window.addEventListener('mousemove', didMouseMove); + window.addEventListener('mouseup', didMouseUp, { capture: true }); + this.stopDragging = didMouseUp; } - autoscrollOnMouseDrag ({clientX, clientY}, verticalOnly = false) { - var {top, bottom, left, right} = this.refs.scrollContainer.getBoundingClientRect() // Using var to avoid deopt on += assignments below - top += MOUSE_DRAG_AUTOSCROLL_MARGIN - bottom -= MOUSE_DRAG_AUTOSCROLL_MARGIN - left += MOUSE_DRAG_AUTOSCROLL_MARGIN - right -= MOUSE_DRAG_AUTOSCROLL_MARGIN + autoscrollOnMouseDrag({ clientX, clientY }, verticalOnly = false) { + var { + top, + bottom, + left, + right + } = this.refs.scrollContainer.getBoundingClientRect(); // Using var to avoid deopt on += assignments below + top += MOUSE_DRAG_AUTOSCROLL_MARGIN; + bottom -= MOUSE_DRAG_AUTOSCROLL_MARGIN; + left += MOUSE_DRAG_AUTOSCROLL_MARGIN; + right -= MOUSE_DRAG_AUTOSCROLL_MARGIN; - let yDelta, yDirection + let yDelta, yDirection; if (clientY < top) { - yDelta = top - clientY - yDirection = -1 + yDelta = top - clientY; + yDirection = -1; } else if (clientY > bottom) { - yDelta = clientY - bottom - yDirection = 1 + yDelta = clientY - bottom; + yDirection = 1; } - let xDelta, xDirection + let xDelta, xDirection; if (clientX < left) { - xDelta = left - clientX - xDirection = -1 + xDelta = left - clientX; + xDirection = -1; } else if (clientX > right) { - xDelta = clientX - right - xDirection = 1 + xDelta = clientX - right; + xDirection = 1; } - let scrolled = false + let scrolled = false; if (yDelta != null) { - const scaledDelta = scaleMouseDragAutoscrollDelta(yDelta) * yDirection - scrolled = this.setScrollTop(this.getScrollTop() + scaledDelta) + const scaledDelta = scaleMouseDragAutoscrollDelta(yDelta) * yDirection; + scrolled = this.setScrollTop(this.getScrollTop() + scaledDelta); } if (!verticalOnly && xDelta != null) { - const scaledDelta = scaleMouseDragAutoscrollDelta(xDelta) * xDirection - scrolled = this.setScrollLeft(this.getScrollLeft() + scaledDelta) + const scaledDelta = scaleMouseDragAutoscrollDelta(xDelta) * xDirection; + scrolled = this.setScrollLeft(this.getScrollLeft() + scaledDelta); } - if (scrolled) this.updateSync() + if (scrolled) this.updateSync(); } - screenPositionForMouseEvent (event) { - return this.screenPositionForPixelPosition(this.pixelPositionForMouseEvent(event)) + screenPositionForMouseEvent(event) { + return this.screenPositionForPixelPosition( + this.pixelPositionForMouseEvent(event) + ); } - pixelPositionForMouseEvent ({clientX, clientY}) { - const scrollContainerRect = this.refs.scrollContainer.getBoundingClientRect() - clientX = Math.min(scrollContainerRect.right, Math.max(scrollContainerRect.left, clientX)) - clientY = Math.min(scrollContainerRect.bottom, Math.max(scrollContainerRect.top, clientY)) - const linesRect = this.refs.lineTiles.getBoundingClientRect() + pixelPositionForMouseEvent({ clientX, clientY }) { + const scrollContainerRect = this.refs.scrollContainer.getBoundingClientRect(); + clientX = Math.min( + scrollContainerRect.right, + Math.max(scrollContainerRect.left, clientX) + ); + clientY = Math.min( + scrollContainerRect.bottom, + Math.max(scrollContainerRect.top, clientY) + ); + const linesRect = this.refs.lineTiles.getBoundingClientRect(); return { top: clientY - linesRect.top, left: clientX - linesRect.left - } + }; } - didUpdateSelections () { - this.pauseCursorBlinking() - this.scheduleUpdate() + didUpdateSelections() { + this.pauseCursorBlinking(); + this.scheduleUpdate(); } - pauseCursorBlinking () { - this.stopCursorBlinking() - this.debouncedResumeCursorBlinking() + pauseCursorBlinking() { + this.stopCursorBlinking(); + this.debouncedResumeCursorBlinking(); } - resumeCursorBlinking () { - this.cursorsBlinkedOff = true - this.startCursorBlinking() + resumeCursorBlinking() { + this.cursorsBlinkedOff = true; + this.startCursorBlinking(); } - stopCursorBlinking () { + stopCursorBlinking() { if (this.cursorsBlinking) { - this.cursorsBlinkedOff = false - this.cursorsBlinking = false - window.clearInterval(this.cursorBlinkIntervalHandle) - this.cursorBlinkIntervalHandle = null - this.scheduleUpdate() + this.cursorsBlinkedOff = false; + this.cursorsBlinking = false; + window.clearInterval(this.cursorBlinkIntervalHandle); + this.cursorBlinkIntervalHandle = null; + this.scheduleUpdate(); } } - startCursorBlinking () { + startCursorBlinking() { if (!this.cursorsBlinking) { this.cursorBlinkIntervalHandle = window.setInterval(() => { - this.cursorsBlinkedOff = !this.cursorsBlinkedOff - this.scheduleUpdate(true) - }, (this.props.cursorBlinkPeriod || CURSOR_BLINK_PERIOD) / 2) - this.cursorsBlinking = true - this.scheduleUpdate(true) + this.cursorsBlinkedOff = !this.cursorsBlinkedOff; + this.scheduleUpdate(true); + }, (this.props.cursorBlinkPeriod || CURSOR_BLINK_PERIOD) / 2); + this.cursorsBlinking = true; + this.scheduleUpdate(true); } } - didRequestAutoscroll (autoscroll) { - this.pendingAutoscroll = autoscroll - this.scheduleUpdate() + didRequestAutoscroll(autoscroll) { + this.pendingAutoscroll = autoscroll; + this.scheduleUpdate(); } - flushPendingLogicalScrollPosition () { - let changedScrollTop = false + flushPendingLogicalScrollPosition() { + let changedScrollTop = false; if (this.pendingScrollTopRow > 0) { - changedScrollTop = this.setScrollTopRow(this.pendingScrollTopRow, false) - this.pendingScrollTopRow = null + changedScrollTop = this.setScrollTopRow(this.pendingScrollTopRow, false); + this.pendingScrollTopRow = null; } - let changedScrollLeft = false + let changedScrollLeft = false; if (this.pendingScrollLeftColumn > 0) { - changedScrollLeft = this.setScrollLeftColumn(this.pendingScrollLeftColumn, false) - this.pendingScrollLeftColumn = null + changedScrollLeft = this.setScrollLeftColumn( + this.pendingScrollLeftColumn, + false + ); + this.pendingScrollLeftColumn = null; } if (changedScrollTop || changedScrollLeft) { - this.updateSync() + this.updateSync(); } } - autoscrollVertically (screenRange, options) { - const screenRangeTop = this.pixelPositionAfterBlocksForRow(screenRange.start.row) - const screenRangeBottom = this.pixelPositionAfterBlocksForRow(screenRange.end.row) + this.getLineHeight() - const verticalScrollMargin = this.getVerticalAutoscrollMargin() + autoscrollVertically(screenRange, options) { + const screenRangeTop = this.pixelPositionAfterBlocksForRow( + screenRange.start.row + ); + const screenRangeBottom = + this.pixelPositionAfterBlocksForRow(screenRange.end.row) + + this.getLineHeight(); + const verticalScrollMargin = this.getVerticalAutoscrollMargin(); - let desiredScrollTop, desiredScrollBottom + let desiredScrollTop, desiredScrollBottom; if (options && options.center) { - const desiredScrollCenter = (screenRangeTop + screenRangeBottom) / 2 - if (desiredScrollCenter < this.getScrollTop() || desiredScrollCenter > this.getScrollBottom()) { - desiredScrollTop = desiredScrollCenter - this.getScrollContainerClientHeight() / 2 - desiredScrollBottom = desiredScrollCenter + this.getScrollContainerClientHeight() / 2 + const desiredScrollCenter = (screenRangeTop + screenRangeBottom) / 2; + if ( + desiredScrollCenter < this.getScrollTop() || + desiredScrollCenter > this.getScrollBottom() + ) { + desiredScrollTop = + desiredScrollCenter - this.getScrollContainerClientHeight() / 2; + desiredScrollBottom = + desiredScrollCenter + this.getScrollContainerClientHeight() / 2; } } else { - desiredScrollTop = screenRangeTop - verticalScrollMargin - desiredScrollBottom = screenRangeBottom + verticalScrollMargin + desiredScrollTop = screenRangeTop - verticalScrollMargin; + desiredScrollBottom = screenRangeBottom + verticalScrollMargin; } if (!options || options.reversed !== false) { if (desiredScrollBottom > this.getScrollBottom()) { - this.setScrollBottom(desiredScrollBottom) + this.setScrollBottom(desiredScrollBottom); } if (desiredScrollTop < this.getScrollTop()) { - this.setScrollTop(desiredScrollTop) + this.setScrollTop(desiredScrollTop); } } else { if (desiredScrollTop < this.getScrollTop()) { - this.setScrollTop(desiredScrollTop) + this.setScrollTop(desiredScrollTop); } if (desiredScrollBottom > this.getScrollBottom()) { - this.setScrollBottom(desiredScrollBottom) + this.setScrollBottom(desiredScrollBottom); } } - return false + return false; } - autoscrollHorizontally (screenRange, options) { - const horizontalScrollMargin = this.getHorizontalAutoscrollMargin() + autoscrollHorizontally(screenRange, options) { + const horizontalScrollMargin = this.getHorizontalAutoscrollMargin(); - const gutterContainerWidth = this.getGutterContainerWidth() - let left = this.pixelLeftForRowAndColumn(screenRange.start.row, screenRange.start.column) + gutterContainerWidth - let right = this.pixelLeftForRowAndColumn(screenRange.end.row, screenRange.end.column) + gutterContainerWidth - const desiredScrollLeft = Math.max(0, left - horizontalScrollMargin - gutterContainerWidth) - const desiredScrollRight = Math.min(this.getScrollWidth(), right + horizontalScrollMargin) + const gutterContainerWidth = this.getGutterContainerWidth(); + let left = + this.pixelLeftForRowAndColumn( + screenRange.start.row, + screenRange.start.column + ) + gutterContainerWidth; + let right = + this.pixelLeftForRowAndColumn( + screenRange.end.row, + screenRange.end.column + ) + gutterContainerWidth; + const desiredScrollLeft = Math.max( + 0, + left - horizontalScrollMargin - gutterContainerWidth + ); + const desiredScrollRight = Math.min( + this.getScrollWidth(), + right + horizontalScrollMargin + ); if (!options || options.reversed !== false) { if (desiredScrollRight > this.getScrollRight()) { - this.setScrollRight(desiredScrollRight) + this.setScrollRight(desiredScrollRight); } if (desiredScrollLeft < this.getScrollLeft()) { - this.setScrollLeft(desiredScrollLeft) + this.setScrollLeft(desiredScrollLeft); } } else { if (desiredScrollLeft < this.getScrollLeft()) { - this.setScrollLeft(desiredScrollLeft) + this.setScrollLeft(desiredScrollLeft); } if (desiredScrollRight > this.getScrollRight()) { - this.setScrollRight(desiredScrollRight) + this.setScrollRight(desiredScrollRight); } } } - getVerticalAutoscrollMargin () { + getVerticalAutoscrollMargin() { const maxMarginInLines = Math.floor( (this.getScrollContainerClientHeight() / this.getLineHeight() - 1) / 2 - ) + ); const marginInLines = Math.min( this.props.model.verticalScrollMargin, maxMarginInLines - ) - return marginInLines * this.getLineHeight() + ); + return marginInLines * this.getLineHeight(); } - getHorizontalAutoscrollMargin () { + getHorizontalAutoscrollMargin() { const maxMarginInBaseCharacters = Math.floor( - (this.getScrollContainerClientWidth() / this.getBaseCharacterWidth() - 1) / 2 - ) + (this.getScrollContainerClientWidth() / this.getBaseCharacterWidth() - + 1) / + 2 + ); const marginInBaseCharacters = Math.min( this.props.model.horizontalScrollMargin, maxMarginInBaseCharacters - ) - return marginInBaseCharacters * this.getBaseCharacterWidth() + ); + return marginInBaseCharacters * this.getBaseCharacterWidth(); } // This method is called at the beginning of a frame render to relay any // potential changes in the editor's width into the model before proceeding. - updateModelSoftWrapColumn () { - const {model} = this.props - const newEditorWidthInChars = this.getScrollContainerClientWidthInBaseCharacters() + updateModelSoftWrapColumn() { + const { model } = this.props; + const newEditorWidthInChars = this.getScrollContainerClientWidthInBaseCharacters(); if (newEditorWidthInChars !== model.getEditorWidthInChars()) { - this.suppressUpdates = true + this.suppressUpdates = true; - const renderedStartRow = this.getRenderedStartRow() - this.props.model.setEditorWidthInChars(newEditorWidthInChars) + const renderedStartRow = this.getRenderedStartRow(); + this.props.model.setEditorWidthInChars(newEditorWidthInChars); // Relaying a change in to the editor's client width may cause the // vertical scrollbar to appear or disappear, which causes the editor's @@ -2140,640 +2432,750 @@ class TextEditorComponent { // differently. We capture the renderedStartRow before resetting the // display layer because once it has been reset, we can't compute the // rendered start row accurately. 😥 - this.populateVisibleRowRange(renderedStartRow) - this.props.model.setEditorWidthInChars(this.getScrollContainerClientWidthInBaseCharacters()) - this.derivedDimensionsCache = {} + this.populateVisibleRowRange(renderedStartRow); + this.props.model.setEditorWidthInChars( + this.getScrollContainerClientWidthInBaseCharacters() + ); + this.derivedDimensionsCache = {}; - this.suppressUpdates = false + this.suppressUpdates = false; } } // This method exists because it existed in the previous implementation and some // package tests relied on it - measureDimensions () { - this.measureCharacterDimensions() - this.measureGutterDimensions() - this.measureClientContainerHeight() - this.measureClientContainerWidth() - this.measureScrollbarDimensions() - this.hasInitialMeasurements = true + measureDimensions() { + this.measureCharacterDimensions(); + this.measureGutterDimensions(); + this.measureClientContainerHeight(); + this.measureClientContainerWidth(); + this.measureScrollbarDimensions(); + this.hasInitialMeasurements = true; } - measureCharacterDimensions () { - this.measurements.lineHeight = Math.max(1, this.refs.characterMeasurementLine.getBoundingClientRect().height) - this.measurements.baseCharacterWidth = this.refs.normalWidthCharacterSpan.getBoundingClientRect().width - this.measurements.doubleWidthCharacterWidth = this.refs.doubleWidthCharacterSpan.getBoundingClientRect().width - this.measurements.halfWidthCharacterWidth = this.refs.halfWidthCharacterSpan.getBoundingClientRect().width - this.measurements.koreanCharacterWidth = this.refs.koreanCharacterSpan.getBoundingClientRect().width + measureCharacterDimensions() { + this.measurements.lineHeight = Math.max( + 1, + this.refs.characterMeasurementLine.getBoundingClientRect().height + ); + this.measurements.baseCharacterWidth = this.refs.normalWidthCharacterSpan.getBoundingClientRect().width; + this.measurements.doubleWidthCharacterWidth = this.refs.doubleWidthCharacterSpan.getBoundingClientRect().width; + this.measurements.halfWidthCharacterWidth = this.refs.halfWidthCharacterSpan.getBoundingClientRect().width; + this.measurements.koreanCharacterWidth = this.refs.koreanCharacterSpan.getBoundingClientRect().width; - this.props.model.setLineHeightInPixels(this.measurements.lineHeight) + this.props.model.setLineHeightInPixels(this.measurements.lineHeight); this.props.model.setDefaultCharWidth( this.measurements.baseCharacterWidth, this.measurements.doubleWidthCharacterWidth, this.measurements.halfWidthCharacterWidth, this.measurements.koreanCharacterWidth - ) - this.lineTopIndex.setDefaultLineHeight(this.measurements.lineHeight) + ); + this.lineTopIndex.setDefaultLineHeight(this.measurements.lineHeight); } - measureGutterDimensions () { - let dimensionsChanged = false + measureGutterDimensions() { + let dimensionsChanged = false; if (this.refs.gutterContainer) { - const gutterContainerWidth = this.refs.gutterContainer.element.offsetWidth + const gutterContainerWidth = this.refs.gutterContainer.element + .offsetWidth; if (gutterContainerWidth !== this.measurements.gutterContainerWidth) { - dimensionsChanged = true - this.measurements.gutterContainerWidth = gutterContainerWidth + dimensionsChanged = true; + this.measurements.gutterContainerWidth = gutterContainerWidth; } } else { - this.measurements.gutterContainerWidth = 0 + this.measurements.gutterContainerWidth = 0; } - if (this.refs.gutterContainer && this.refs.gutterContainer.refs.lineNumberGutter) { - const lineNumberGutterWidth = this.refs.gutterContainer.refs.lineNumberGutter.element.offsetWidth + if ( + this.refs.gutterContainer && + this.refs.gutterContainer.refs.lineNumberGutter + ) { + const lineNumberGutterWidth = this.refs.gutterContainer.refs + .lineNumberGutter.element.offsetWidth; if (lineNumberGutterWidth !== this.measurements.lineNumberGutterWidth) { - dimensionsChanged = true - this.measurements.lineNumberGutterWidth = lineNumberGutterWidth + dimensionsChanged = true; + this.measurements.lineNumberGutterWidth = lineNumberGutterWidth; } } else { - this.measurements.lineNumberGutterWidth = 0 + this.measurements.lineNumberGutterWidth = 0; } - return dimensionsChanged + return dimensionsChanged; } - measureClientContainerHeight () { - const clientContainerHeight = this.refs.clientContainer.offsetHeight + measureClientContainerHeight() { + const clientContainerHeight = this.refs.clientContainer.offsetHeight; if (clientContainerHeight !== this.measurements.clientContainerHeight) { - this.measurements.clientContainerHeight = clientContainerHeight - return true + this.measurements.clientContainerHeight = clientContainerHeight; + return true; } else { - return false + return false; } } - measureClientContainerWidth () { - const clientContainerWidth = this.refs.clientContainer.offsetWidth + measureClientContainerWidth() { + const clientContainerWidth = this.refs.clientContainer.offsetWidth; if (clientContainerWidth !== this.measurements.clientContainerWidth) { - this.measurements.clientContainerWidth = clientContainerWidth - return true + this.measurements.clientContainerWidth = clientContainerWidth; + return true; } else { - return false + return false; } } - measureScrollbarDimensions () { + measureScrollbarDimensions() { if (this.props.model.isMini()) { - this.measurements.verticalScrollbarWidth = 0 - this.measurements.horizontalScrollbarHeight = 0 + this.measurements.verticalScrollbarWidth = 0; + this.measurements.horizontalScrollbarHeight = 0; } else { - this.measurements.verticalScrollbarWidth = this.refs.verticalScrollbar.getRealScrollbarWidth() - this.measurements.horizontalScrollbarHeight = this.refs.horizontalScrollbar.getRealScrollbarHeight() + this.measurements.verticalScrollbarWidth = this.refs.verticalScrollbar.getRealScrollbarWidth(); + this.measurements.horizontalScrollbarHeight = this.refs.horizontalScrollbar.getRealScrollbarHeight(); } } - measureLongestLineWidth () { + measureLongestLineWidth() { if (this.longestLineToMeasure) { - const lineComponent = this.lineComponentsByScreenLineId.get(this.longestLineToMeasure.id) - this.measurements.longestLineWidth = lineComponent.element.firstChild.offsetWidth - this.longestLineToMeasure = null + const lineComponent = this.lineComponentsByScreenLineId.get( + this.longestLineToMeasure.id + ); + this.measurements.longestLineWidth = + lineComponent.element.firstChild.offsetWidth; + this.longestLineToMeasure = null; } } - requestLineToMeasure (row, screenLine) { - this.linesToMeasure.set(row, screenLine) + requestLineToMeasure(row, screenLine) { + this.linesToMeasure.set(row, screenLine); } - requestHorizontalMeasurement (row, column) { - if (column === 0) return + requestHorizontalMeasurement(row, column) { + if (column === 0) return; - const screenLine = this.props.model.screenLineForScreenRow(row) + const screenLine = this.props.model.screenLineForScreenRow(row); if (screenLine) { - this.requestLineToMeasure(row, screenLine) + this.requestLineToMeasure(row, screenLine); - let columns = this.horizontalPositionsToMeasure.get(row) + let columns = this.horizontalPositionsToMeasure.get(row); if (columns == null) { - columns = [] - this.horizontalPositionsToMeasure.set(row, columns) + columns = []; + this.horizontalPositionsToMeasure.set(row, columns); } - columns.push(column) + columns.push(column); } } - measureHorizontalPositions () { + measureHorizontalPositions() { this.horizontalPositionsToMeasure.forEach((columnsToMeasure, row) => { - columnsToMeasure.sort((a, b) => a - b) + columnsToMeasure.sort((a, b) => a - b); - const screenLine = this.renderedScreenLineForRow(row) - const lineComponent = this.lineComponentsByScreenLineId.get(screenLine.id) + const screenLine = this.renderedScreenLineForRow(row); + const lineComponent = this.lineComponentsByScreenLineId.get( + screenLine.id + ); if (!lineComponent) { - const error = new Error('Requested measurement of a line component that is not currently rendered') + const error = new Error( + 'Requested measurement of a line component that is not currently rendered' + ); error.metadata = { row, columnsToMeasure, - renderedScreenLineIds: this.renderedScreenLines.map((line) => line.id), - extraRenderedScreenLineIds: Array.from(this.extraRenderedScreenLines.keys()), - lineComponentScreenLineIds: Array.from(this.lineComponentsByScreenLineId.keys()), + renderedScreenLineIds: this.renderedScreenLines.map(line => line.id), + extraRenderedScreenLineIds: Array.from( + this.extraRenderedScreenLines.keys() + ), + lineComponentScreenLineIds: Array.from( + this.lineComponentsByScreenLineId.keys() + ), renderedStartRow: this.getRenderedStartRow(), renderedEndRow: this.getRenderedEndRow(), requestedScreenLineId: screenLine.id - } - throw error + }; + throw error; } - const lineNode = lineComponent.element - const textNodes = lineComponent.textNodes - let positionsForLine = this.horizontalPixelPositionsByScreenLineId.get(screenLine.id) + const lineNode = lineComponent.element; + const textNodes = lineComponent.textNodes; + let positionsForLine = this.horizontalPixelPositionsByScreenLineId.get( + screenLine.id + ); if (positionsForLine == null) { - positionsForLine = new Map() - this.horizontalPixelPositionsByScreenLineId.set(screenLine.id, positionsForLine) + positionsForLine = new Map(); + this.horizontalPixelPositionsByScreenLineId.set( + screenLine.id, + positionsForLine + ); } - this.measureHorizontalPositionsOnLine(lineNode, textNodes, columnsToMeasure, positionsForLine) - }) - this.horizontalPositionsToMeasure.clear() + this.measureHorizontalPositionsOnLine( + lineNode, + textNodes, + columnsToMeasure, + positionsForLine + ); + }); + this.horizontalPositionsToMeasure.clear(); } - measureHorizontalPositionsOnLine (lineNode, textNodes, columnsToMeasure, positions) { - let lineNodeClientLeft = -1 - let textNodeStartColumn = 0 - let textNodesIndex = 0 - let lastTextNodeRight = null + measureHorizontalPositionsOnLine( + lineNode, + textNodes, + columnsToMeasure, + positions + ) { + let lineNodeClientLeft = -1; + let textNodeStartColumn = 0; + let textNodesIndex = 0; + let lastTextNodeRight = null; // eslint-disable-next-line no-labels - columnLoop: - for (let columnsIndex = 0; columnsIndex < columnsToMeasure.length; columnsIndex++) { - const nextColumnToMeasure = columnsToMeasure[columnsIndex] + columnLoop: for ( + let columnsIndex = 0; + columnsIndex < columnsToMeasure.length; + columnsIndex++ + ) { + const nextColumnToMeasure = columnsToMeasure[columnsIndex]; while (textNodesIndex < textNodes.length) { if (nextColumnToMeasure === 0) { - positions.set(0, 0) - continue columnLoop // eslint-disable-line no-labels + positions.set(0, 0); + continue columnLoop; // eslint-disable-line no-labels } - if (positions.has(nextColumnToMeasure)) continue columnLoop // eslint-disable-line no-labels - const textNode = textNodes[textNodesIndex] - const textNodeEndColumn = textNodeStartColumn + textNode.textContent.length + if (positions.has(nextColumnToMeasure)) continue columnLoop; // eslint-disable-line no-labels + const textNode = textNodes[textNodesIndex]; + const textNodeEndColumn = + textNodeStartColumn + textNode.textContent.length; if (nextColumnToMeasure < textNodeEndColumn) { - let clientPixelPosition + let clientPixelPosition; if (nextColumnToMeasure === textNodeStartColumn) { - clientPixelPosition = clientRectForRange(textNode, 0, 1).left + clientPixelPosition = clientRectForRange(textNode, 0, 1).left; } else { - clientPixelPosition = clientRectForRange(textNode, 0, nextColumnToMeasure - textNodeStartColumn).right + clientPixelPosition = clientRectForRange( + textNode, + 0, + nextColumnToMeasure - textNodeStartColumn + ).right; } if (lineNodeClientLeft === -1) { - lineNodeClientLeft = lineNode.getBoundingClientRect().left + lineNodeClientLeft = lineNode.getBoundingClientRect().left; } - positions.set(nextColumnToMeasure, Math.round(clientPixelPosition - lineNodeClientLeft)) - continue columnLoop // eslint-disable-line no-labels + positions.set( + nextColumnToMeasure, + Math.round(clientPixelPosition - lineNodeClientLeft) + ); + continue columnLoop; // eslint-disable-line no-labels } else { - textNodesIndex++ - textNodeStartColumn = textNodeEndColumn + textNodesIndex++; + textNodeStartColumn = textNodeEndColumn; } } if (lastTextNodeRight == null) { - const lastTextNode = textNodes[textNodes.length - 1] - lastTextNodeRight = clientRectForRange(lastTextNode, 0, lastTextNode.textContent.length).right + const lastTextNode = textNodes[textNodes.length - 1]; + lastTextNodeRight = clientRectForRange( + lastTextNode, + 0, + lastTextNode.textContent.length + ).right; } if (lineNodeClientLeft === -1) { - lineNodeClientLeft = lineNode.getBoundingClientRect().left + lineNodeClientLeft = lineNode.getBoundingClientRect().left; } - positions.set(nextColumnToMeasure, Math.round(lastTextNodeRight - lineNodeClientLeft)) + positions.set( + nextColumnToMeasure, + Math.round(lastTextNodeRight - lineNodeClientLeft) + ); } } - rowForPixelPosition (pixelPosition) { - return Math.max(0, this.lineTopIndex.rowForPixelPosition(pixelPosition)) + rowForPixelPosition(pixelPosition) { + return Math.max(0, this.lineTopIndex.rowForPixelPosition(pixelPosition)); } - heightForBlockDecorationsBeforeRow (row) { - return this.pixelPositionAfterBlocksForRow(row) - this.pixelPositionBeforeBlocksForRow(row) + heightForBlockDecorationsBeforeRow(row) { + return ( + this.pixelPositionAfterBlocksForRow(row) - + this.pixelPositionBeforeBlocksForRow(row) + ); } - heightForBlockDecorationsAfterRow (row) { - const currentRowBottom = this.pixelPositionAfterBlocksForRow(row) + this.getLineHeight() - const nextRowTop = this.pixelPositionBeforeBlocksForRow(row + 1) - return nextRowTop - currentRowBottom + heightForBlockDecorationsAfterRow(row) { + const currentRowBottom = + this.pixelPositionAfterBlocksForRow(row) + this.getLineHeight(); + const nextRowTop = this.pixelPositionBeforeBlocksForRow(row + 1); + return nextRowTop - currentRowBottom; } - pixelPositionBeforeBlocksForRow (row) { - return this.lineTopIndex.pixelPositionBeforeBlocksForRow(row) + pixelPositionBeforeBlocksForRow(row) { + return this.lineTopIndex.pixelPositionBeforeBlocksForRow(row); } - pixelPositionAfterBlocksForRow (row) { - return this.lineTopIndex.pixelPositionAfterBlocksForRow(row) + pixelPositionAfterBlocksForRow(row) { + return this.lineTopIndex.pixelPositionAfterBlocksForRow(row); } - pixelLeftForRowAndColumn (row, column) { - if (column === 0) return 0 - const screenLine = this.renderedScreenLineForRow(row) + pixelLeftForRowAndColumn(row, column) { + if (column === 0) return 0; + const screenLine = this.renderedScreenLineForRow(row); if (screenLine) { - const horizontalPositionsByColumn = this.horizontalPixelPositionsByScreenLineId.get(screenLine.id) + const horizontalPositionsByColumn = this.horizontalPixelPositionsByScreenLineId.get( + screenLine.id + ); if (horizontalPositionsByColumn) { - return horizontalPositionsByColumn.get(column) + return horizontalPositionsByColumn.get(column); } } } - screenPositionForPixelPosition ({top, left}) { - const {model} = this.props + screenPositionForPixelPosition({ top, left }) { + const { model } = this.props; const row = Math.min( this.rowForPixelPosition(top), model.getApproximateScreenLineCount() - 1 - ) + ); - let screenLine = this.renderedScreenLineForRow(row) + let screenLine = this.renderedScreenLineForRow(row); if (!screenLine) { - this.requestLineToMeasure(row, model.screenLineForScreenRow(row)) - this.updateSyncBeforeMeasuringContent() - this.measureContentDuringUpdateSync() - screenLine = this.renderedScreenLineForRow(row) + this.requestLineToMeasure(row, model.screenLineForScreenRow(row)); + this.updateSyncBeforeMeasuringContent(); + this.measureContentDuringUpdateSync(); + screenLine = this.renderedScreenLineForRow(row); } - const linesClientLeft = this.refs.lineTiles.getBoundingClientRect().left - const targetClientLeft = linesClientLeft + Math.max(0, left) - const {textNodes} = this.lineComponentsByScreenLineId.get(screenLine.id) + const linesClientLeft = this.refs.lineTiles.getBoundingClientRect().left; + const targetClientLeft = linesClientLeft + Math.max(0, left); + const { textNodes } = this.lineComponentsByScreenLineId.get(screenLine.id); - let containingTextNodeIndex + let containingTextNodeIndex; { - let low = 0 - let high = textNodes.length - 1 + let low = 0; + let high = textNodes.length - 1; while (low <= high) { - const mid = low + ((high - low) >> 1) - const textNode = textNodes[mid] - const textNodeRect = clientRectForRange(textNode, 0, textNode.length) + const mid = low + ((high - low) >> 1); + const textNode = textNodes[mid]; + const textNodeRect = clientRectForRange(textNode, 0, textNode.length); if (targetClientLeft < textNodeRect.left) { - high = mid - 1 - containingTextNodeIndex = Math.max(0, mid - 1) + high = mid - 1; + containingTextNodeIndex = Math.max(0, mid - 1); } else if (targetClientLeft > textNodeRect.right) { - low = mid + 1 - containingTextNodeIndex = Math.min(textNodes.length - 1, mid + 1) + low = mid + 1; + containingTextNodeIndex = Math.min(textNodes.length - 1, mid + 1); } else { - containingTextNodeIndex = mid - break + containingTextNodeIndex = mid; + break; } } } - const containingTextNode = textNodes[containingTextNodeIndex] - let characterIndex = 0 + const containingTextNode = textNodes[containingTextNodeIndex]; + let characterIndex = 0; { - let low = 0 - let high = containingTextNode.length - 1 + let low = 0; + let high = containingTextNode.length - 1; while (low <= high) { - const charIndex = low + ((high - low) >> 1) - const nextCharIndex = isPairedCharacter(containingTextNode.textContent, charIndex) + const charIndex = low + ((high - low) >> 1); + const nextCharIndex = isPairedCharacter( + containingTextNode.textContent, + charIndex + ) ? charIndex + 2 - : charIndex + 1 + : charIndex + 1; - const rangeRect = clientRectForRange(containingTextNode, charIndex, nextCharIndex) + const rangeRect = clientRectForRange( + containingTextNode, + charIndex, + nextCharIndex + ); if (targetClientLeft < rangeRect.left) { - high = charIndex - 1 - characterIndex = Math.max(0, charIndex - 1) + high = charIndex - 1; + characterIndex = Math.max(0, charIndex - 1); } else if (targetClientLeft > rangeRect.right) { - low = nextCharIndex - characterIndex = Math.min(containingTextNode.textContent.length, nextCharIndex) + low = nextCharIndex; + characterIndex = Math.min( + containingTextNode.textContent.length, + nextCharIndex + ); } else { - if (targetClientLeft <= ((rangeRect.left + rangeRect.right) / 2)) { - characterIndex = charIndex + if (targetClientLeft <= (rangeRect.left + rangeRect.right) / 2) { + characterIndex = charIndex; } else { - characterIndex = nextCharIndex + characterIndex = nextCharIndex; } - break + break; } } } - let textNodeStartColumn = 0 + let textNodeStartColumn = 0; for (let i = 0; i < containingTextNodeIndex; i++) { - textNodeStartColumn = textNodeStartColumn + textNodes[i].length + textNodeStartColumn = textNodeStartColumn + textNodes[i].length; } - const column = textNodeStartColumn + characterIndex + const column = textNodeStartColumn + characterIndex; - return Point(row, column) + return Point(row, column); } - didResetDisplayLayer () { - this.spliceLineTopIndex(0, Infinity, Infinity) - this.scheduleUpdate() + didResetDisplayLayer() { + this.spliceLineTopIndex(0, Infinity, Infinity); + this.scheduleUpdate(); } - didChangeDisplayLayer (changes) { + didChangeDisplayLayer(changes) { for (let i = 0; i < changes.length; i++) { - const {oldRange, newRange} = changes[i] + const { oldRange, newRange } = changes[i]; this.spliceLineTopIndex( newRange.start.row, oldRange.end.row - oldRange.start.row, newRange.end.row - newRange.start.row - ) + ); } - this.scheduleUpdate() + this.scheduleUpdate(); } - didChangeSelectionRange () { - const {model} = this.props + didChangeSelectionRange() { + const { model } = this.props; if (this.getPlatform() === 'linux') { if (this.selectionClipboardImmediateId) { - clearImmediate(this.selectionClipboardImmediateId) + clearImmediate(this.selectionClipboardImmediateId); } this.selectionClipboardImmediateId = setImmediate(() => { - this.selectionClipboardImmediateId = null + this.selectionClipboardImmediateId = null; - if (model.isDestroyed()) return + if (model.isDestroyed()) return; - const selectedText = model.getSelectedText() + const selectedText = model.getSelectedText(); if (selectedText) { // This uses ipcRenderer.send instead of clipboard.writeText because // clipboard.writeText is a sync ipcRenderer call on Linux and that // will slow down selections. - electron.ipcRenderer.send('write-text-to-selection-clipboard', selectedText) + electron.ipcRenderer.send( + 'write-text-to-selection-clipboard', + selectedText + ); } - }) + }); } } - observeBlockDecorations () { - const {model} = this.props - const decorations = model.getDecorations({type: 'block'}) + observeBlockDecorations() { + const { model } = this.props; + const decorations = model.getDecorations({ type: 'block' }); for (let i = 0; i < decorations.length; i++) { - this.addBlockDecoration(decorations[i]) + this.addBlockDecoration(decorations[i]); } } - addBlockDecoration (decoration, subscribeToChanges = true) { - const marker = decoration.getMarker() - const {item, position} = decoration.getProperties() - const element = TextEditor.viewForItem(item) + addBlockDecoration(decoration, subscribeToChanges = true) { + const marker = decoration.getMarker(); + const { item, position } = decoration.getProperties(); + const element = TextEditor.viewForItem(item); if (marker.isValid()) { - const row = marker.getHeadScreenPosition().row - this.lineTopIndex.insertBlock(decoration, row, 0, position === 'after') - this.blockDecorationsToMeasure.add(decoration) - this.blockDecorationsByElement.set(element, decoration) - this.blockDecorationResizeObserver.observe(element) + const row = marker.getHeadScreenPosition().row; + this.lineTopIndex.insertBlock(decoration, row, 0, position === 'after'); + this.blockDecorationsToMeasure.add(decoration); + this.blockDecorationsByElement.set(element, decoration); + this.blockDecorationResizeObserver.observe(element); - this.scheduleUpdate() + this.scheduleUpdate(); } if (subscribeToChanges) { - let wasValid = marker.isValid() + let wasValid = marker.isValid(); - const didUpdateDisposable = marker.bufferMarker.onDidChange(({textChanged}) => { - const isValid = marker.isValid() - if (wasValid && !isValid) { - wasValid = false - this.blockDecorationsToMeasure.delete(decoration) - this.heightsByBlockDecoration.delete(decoration) - this.blockDecorationsByElement.delete(element) - this.blockDecorationResizeObserver.unobserve(element) - this.lineTopIndex.removeBlock(decoration) - this.scheduleUpdate() - } else if (!wasValid && isValid) { - wasValid = true - this.addBlockDecoration(decoration, false) - } else if (isValid && !textChanged) { - this.lineTopIndex.moveBlock(decoration, marker.getHeadScreenPosition().row) - this.scheduleUpdate() + const didUpdateDisposable = marker.bufferMarker.onDidChange( + ({ textChanged }) => { + const isValid = marker.isValid(); + if (wasValid && !isValid) { + wasValid = false; + this.blockDecorationsToMeasure.delete(decoration); + this.heightsByBlockDecoration.delete(decoration); + this.blockDecorationsByElement.delete(element); + this.blockDecorationResizeObserver.unobserve(element); + this.lineTopIndex.removeBlock(decoration); + this.scheduleUpdate(); + } else if (!wasValid && isValid) { + wasValid = true; + this.addBlockDecoration(decoration, false); + } else if (isValid && !textChanged) { + this.lineTopIndex.moveBlock( + decoration, + marker.getHeadScreenPosition().row + ); + this.scheduleUpdate(); + } } - }) + ); const didDestroyDisposable = decoration.onDidDestroy(() => { - didUpdateDisposable.dispose() - didDestroyDisposable.dispose() + didUpdateDisposable.dispose(); + didDestroyDisposable.dispose(); if (wasValid) { - wasValid = false - this.blockDecorationsToMeasure.delete(decoration) - this.heightsByBlockDecoration.delete(decoration) - this.blockDecorationsByElement.delete(element) - this.blockDecorationResizeObserver.unobserve(element) - this.lineTopIndex.removeBlock(decoration) - this.scheduleUpdate() + wasValid = false; + this.blockDecorationsToMeasure.delete(decoration); + this.heightsByBlockDecoration.delete(decoration); + this.blockDecorationsByElement.delete(element); + this.blockDecorationResizeObserver.unobserve(element); + this.lineTopIndex.removeBlock(decoration); + this.scheduleUpdate(); } - }) + }); } } - didResizeBlockDecorations (entries) { - if (!this.visible) return + didResizeBlockDecorations(entries) { + if (!this.visible) return; for (let i = 0; i < entries.length; i++) { - const {target, contentRect} = entries[i] - const decoration = this.blockDecorationsByElement.get(target) - const previousHeight = this.heightsByBlockDecoration.get(decoration) - if (this.element.contains(target) && contentRect.height !== previousHeight) { - this.invalidateBlockDecorationDimensions(decoration) + const { target, contentRect } = entries[i]; + const decoration = this.blockDecorationsByElement.get(target); + const previousHeight = this.heightsByBlockDecoration.get(decoration); + if ( + this.element.contains(target) && + contentRect.height !== previousHeight + ) { + this.invalidateBlockDecorationDimensions(decoration); } } } - invalidateBlockDecorationDimensions (decoration) { - this.blockDecorationsToMeasure.add(decoration) - this.scheduleUpdate() + invalidateBlockDecorationDimensions(decoration) { + this.blockDecorationsToMeasure.add(decoration); + this.scheduleUpdate(); } - spliceLineTopIndex (startRow, oldExtent, newExtent) { - const invalidatedBlockDecorations = this.lineTopIndex.splice(startRow, oldExtent, newExtent) - invalidatedBlockDecorations.forEach((decoration) => { - const newPosition = decoration.getMarker().getHeadScreenPosition() - this.lineTopIndex.moveBlock(decoration, newPosition.row) - }) + spliceLineTopIndex(startRow, oldExtent, newExtent) { + const invalidatedBlockDecorations = this.lineTopIndex.splice( + startRow, + oldExtent, + newExtent + ); + invalidatedBlockDecorations.forEach(decoration => { + const newPosition = decoration.getMarker().getHeadScreenPosition(); + this.lineTopIndex.moveBlock(decoration, newPosition.row); + }); } - isVisible () { - return this.element.offsetWidth > 0 || this.element.offsetHeight > 0 + isVisible() { + return this.element.offsetWidth > 0 || this.element.offsetHeight > 0; } - getWindowInnerHeight () { - return window.innerHeight + getWindowInnerHeight() { + return window.innerHeight; } - getWindowInnerWidth () { - return window.innerWidth + getWindowInnerWidth() { + return window.innerWidth; } - getLineHeight () { - return this.measurements.lineHeight + getLineHeight() { + return this.measurements.lineHeight; } - getBaseCharacterWidth () { - return this.measurements.baseCharacterWidth + getBaseCharacterWidth() { + return this.measurements.baseCharacterWidth; } - getLongestLineWidth () { - return this.measurements.longestLineWidth + getLongestLineWidth() { + return this.measurements.longestLineWidth; } - getClientContainerHeight () { - return this.measurements.clientContainerHeight + getClientContainerHeight() { + return this.measurements.clientContainerHeight; } - getClientContainerWidth () { - return this.measurements.clientContainerWidth + getClientContainerWidth() { + return this.measurements.clientContainerWidth; } - getScrollContainerWidth () { + getScrollContainerWidth() { if (this.props.model.getAutoWidth()) { - return this.getScrollWidth() + return this.getScrollWidth(); } else { - return this.getClientContainerWidth() - this.getGutterContainerWidth() + return this.getClientContainerWidth() - this.getGutterContainerWidth(); } } - getScrollContainerHeight () { + getScrollContainerHeight() { if (this.props.model.getAutoHeight()) { - return this.getScrollHeight() + this.getHorizontalScrollbarHeight() + return this.getScrollHeight() + this.getHorizontalScrollbarHeight(); } else { - return this.getClientContainerHeight() + return this.getClientContainerHeight(); } } - getScrollContainerClientWidth () { - return this.getScrollContainerWidth() - this.getVerticalScrollbarWidth() + getScrollContainerClientWidth() { + return this.getScrollContainerWidth() - this.getVerticalScrollbarWidth(); } - getScrollContainerClientHeight () { - return this.getScrollContainerHeight() - this.getHorizontalScrollbarHeight() + getScrollContainerClientHeight() { + return ( + this.getScrollContainerHeight() - this.getHorizontalScrollbarHeight() + ); } - canScrollVertically () { - const {model} = this.props - if (model.isMini()) return false - if (model.getAutoHeight()) return false - return this.getContentHeight() > this.getScrollContainerClientHeight() + canScrollVertically() { + const { model } = this.props; + if (model.isMini()) return false; + if (model.getAutoHeight()) return false; + return this.getContentHeight() > this.getScrollContainerClientHeight(); } - canScrollHorizontally () { - const {model} = this.props - if (model.isMini()) return false - if (model.getAutoWidth()) return false - if (model.isSoftWrapped()) return false - return this.getContentWidth() > this.getScrollContainerClientWidth() + canScrollHorizontally() { + const { model } = this.props; + if (model.isMini()) return false; + if (model.getAutoWidth()) return false; + if (model.isSoftWrapped()) return false; + return this.getContentWidth() > this.getScrollContainerClientWidth(); } - getScrollHeight () { + getScrollHeight() { if (this.props.model.getScrollPastEnd()) { - return this.getContentHeight() + Math.max( - 3 * this.getLineHeight(), - this.getScrollContainerClientHeight() - (3 * this.getLineHeight()) - ) + return ( + this.getContentHeight() + + Math.max( + 3 * this.getLineHeight(), + this.getScrollContainerClientHeight() - 3 * this.getLineHeight() + ) + ); } else if (this.props.model.getAutoHeight()) { - return this.getContentHeight() + return this.getContentHeight(); } else { - return Math.max(this.getContentHeight(), this.getScrollContainerClientHeight()) + return Math.max( + this.getContentHeight(), + this.getScrollContainerClientHeight() + ); } } - getScrollWidth () { - const {model} = this.props + getScrollWidth() { + const { model } = this.props; if (model.isSoftWrapped()) { - return this.getScrollContainerClientWidth() + return this.getScrollContainerClientWidth(); } else if (model.getAutoWidth()) { - return this.getContentWidth() + return this.getContentWidth(); } else { - return Math.max(this.getContentWidth(), this.getScrollContainerClientWidth()) + return Math.max( + this.getContentWidth(), + this.getScrollContainerClientWidth() + ); } } - getContentHeight () { - return this.pixelPositionAfterBlocksForRow(this.props.model.getApproximateScreenLineCount()) + getContentHeight() { + return this.pixelPositionAfterBlocksForRow( + this.props.model.getApproximateScreenLineCount() + ); } - getContentWidth () { - return Math.ceil(this.getLongestLineWidth() + this.getBaseCharacterWidth()) + getContentWidth() { + return Math.ceil(this.getLongestLineWidth() + this.getBaseCharacterWidth()); } - getScrollContainerClientWidthInBaseCharacters () { - return Math.floor(this.getScrollContainerClientWidth() / this.getBaseCharacterWidth()) + getScrollContainerClientWidthInBaseCharacters() { + return Math.floor( + this.getScrollContainerClientWidth() / this.getBaseCharacterWidth() + ); } - getGutterContainerWidth () { - return this.measurements.gutterContainerWidth + getGutterContainerWidth() { + return this.measurements.gutterContainerWidth; } - getLineNumberGutterWidth () { - return this.measurements.lineNumberGutterWidth + getLineNumberGutterWidth() { + return this.measurements.lineNumberGutterWidth; } - getVerticalScrollbarWidth () { - return this.measurements.verticalScrollbarWidth + getVerticalScrollbarWidth() { + return this.measurements.verticalScrollbarWidth; } - getHorizontalScrollbarHeight () { - return this.measurements.horizontalScrollbarHeight + getHorizontalScrollbarHeight() { + return this.measurements.horizontalScrollbarHeight; } - getRowsPerTile () { - return this.props.rowsPerTile || DEFAULT_ROWS_PER_TILE + getRowsPerTile() { + return this.props.rowsPerTile || DEFAULT_ROWS_PER_TILE; } - tileStartRowForRow (row) { - return row - (row % this.getRowsPerTile()) + tileStartRowForRow(row) { + return row - (row % this.getRowsPerTile()); } - getRenderedStartRow () { + getRenderedStartRow() { if (this.derivedDimensionsCache.renderedStartRow == null) { - this.derivedDimensionsCache.renderedStartRow = this.tileStartRowForRow(this.getFirstVisibleRow()) + this.derivedDimensionsCache.renderedStartRow = this.tileStartRowForRow( + this.getFirstVisibleRow() + ); } - return this.derivedDimensionsCache.renderedStartRow + return this.derivedDimensionsCache.renderedStartRow; } - getRenderedEndRow () { + getRenderedEndRow() { if (this.derivedDimensionsCache.renderedEndRow == null) { this.derivedDimensionsCache.renderedEndRow = Math.min( this.props.model.getApproximateScreenLineCount(), - this.getRenderedStartRow() + this.getVisibleTileCount() * this.getRowsPerTile() - ) + this.getRenderedStartRow() + + this.getVisibleTileCount() * this.getRowsPerTile() + ); } - return this.derivedDimensionsCache.renderedEndRow + return this.derivedDimensionsCache.renderedEndRow; } - getRenderedRowCount () { + getRenderedRowCount() { if (this.derivedDimensionsCache.renderedRowCount == null) { - this.derivedDimensionsCache.renderedRowCount = Math.max(0, this.getRenderedEndRow() - this.getRenderedStartRow()) + this.derivedDimensionsCache.renderedRowCount = Math.max( + 0, + this.getRenderedEndRow() - this.getRenderedStartRow() + ); } - return this.derivedDimensionsCache.renderedRowCount + return this.derivedDimensionsCache.renderedRowCount; } - getRenderedTileCount () { + getRenderedTileCount() { if (this.derivedDimensionsCache.renderedTileCount == null) { - this.derivedDimensionsCache.renderedTileCount = Math.ceil(this.getRenderedRowCount() / this.getRowsPerTile()) + this.derivedDimensionsCache.renderedTileCount = Math.ceil( + this.getRenderedRowCount() / this.getRowsPerTile() + ); } - return this.derivedDimensionsCache.renderedTileCount + return this.derivedDimensionsCache.renderedTileCount; } - getFirstVisibleRow () { + getFirstVisibleRow() { if (this.derivedDimensionsCache.firstVisibleRow == null) { - this.derivedDimensionsCache.firstVisibleRow = this.rowForPixelPosition(this.getScrollTop()) + this.derivedDimensionsCache.firstVisibleRow = this.rowForPixelPosition( + this.getScrollTop() + ); } - return this.derivedDimensionsCache.firstVisibleRow + return this.derivedDimensionsCache.firstVisibleRow; } - getLastVisibleRow () { + getLastVisibleRow() { if (this.derivedDimensionsCache.lastVisibleRow == null) { this.derivedDimensionsCache.lastVisibleRow = Math.min( this.props.model.getApproximateScreenLineCount() - 1, this.rowForPixelPosition(this.getScrollBottom()) - ) + ); } - return this.derivedDimensionsCache.lastVisibleRow + return this.derivedDimensionsCache.lastVisibleRow; } // We may render more tiles than needed if some contain block decorations, @@ -2781,264 +3183,304 @@ class TextEditorComponent { // fixed for a given editor height, which eliminates situations where a // tile is repeatedly added and removed during scrolling in certain // combinations of editor height and line height. - getVisibleTileCount () { + getVisibleTileCount() { if (this.derivedDimensionsCache.visibleTileCount == null) { - const editorHeightInTiles = this.getScrollContainerHeight() / this.getLineHeight() / this.getRowsPerTile() - this.derivedDimensionsCache.visibleTileCount = Math.ceil(editorHeightInTiles) + 1 + const editorHeightInTiles = + this.getScrollContainerHeight() / + this.getLineHeight() / + this.getRowsPerTile(); + this.derivedDimensionsCache.visibleTileCount = + Math.ceil(editorHeightInTiles) + 1; } - return this.derivedDimensionsCache.visibleTileCount + return this.derivedDimensionsCache.visibleTileCount; } - getFirstVisibleColumn () { - return Math.floor(this.getScrollLeft() / this.getBaseCharacterWidth()) + getFirstVisibleColumn() { + return Math.floor(this.getScrollLeft() / this.getBaseCharacterWidth()); } - getScrollTop () { - this.scrollTop = Math.min(this.getMaxScrollTop(), this.scrollTop) - return this.scrollTop + getScrollTop() { + this.scrollTop = Math.min(this.getMaxScrollTop(), this.scrollTop); + return this.scrollTop; } - setScrollTop (scrollTop) { - if (Number.isNaN(scrollTop) || scrollTop == null) return false + setScrollTop(scrollTop) { + if (Number.isNaN(scrollTop) || scrollTop == null) return false; - scrollTop = roundToPhysicalPixelBoundary(Math.max(0, Math.min(this.getMaxScrollTop(), scrollTop))) + scrollTop = roundToPhysicalPixelBoundary( + Math.max(0, Math.min(this.getMaxScrollTop(), scrollTop)) + ); if (scrollTop !== this.scrollTop) { - this.derivedDimensionsCache = {} - this.scrollTopPending = true - this.scrollTop = scrollTop - this.element.emitter.emit('did-change-scroll-top', scrollTop) - return true + this.derivedDimensionsCache = {}; + this.scrollTopPending = true; + this.scrollTop = scrollTop; + this.element.emitter.emit('did-change-scroll-top', scrollTop); + return true; } else { - return false + return false; } } - getMaxScrollTop () { - return Math.round(Math.max(0, this.getScrollHeight() - this.getScrollContainerClientHeight())) + getMaxScrollTop() { + return Math.round( + Math.max( + 0, + this.getScrollHeight() - this.getScrollContainerClientHeight() + ) + ); } - getScrollBottom () { - return this.getScrollTop() + this.getScrollContainerClientHeight() + getScrollBottom() { + return this.getScrollTop() + this.getScrollContainerClientHeight(); } - setScrollBottom (scrollBottom) { - return this.setScrollTop(scrollBottom - this.getScrollContainerClientHeight()) + setScrollBottom(scrollBottom) { + return this.setScrollTop( + scrollBottom - this.getScrollContainerClientHeight() + ); } - getScrollLeft () { - return this.scrollLeft + getScrollLeft() { + return this.scrollLeft; } - setScrollLeft (scrollLeft) { - if (Number.isNaN(scrollLeft) || scrollLeft == null) return false + setScrollLeft(scrollLeft) { + if (Number.isNaN(scrollLeft) || scrollLeft == null) return false; - scrollLeft = roundToPhysicalPixelBoundary(Math.max(0, Math.min(this.getMaxScrollLeft(), scrollLeft))) + scrollLeft = roundToPhysicalPixelBoundary( + Math.max(0, Math.min(this.getMaxScrollLeft(), scrollLeft)) + ); if (scrollLeft !== this.scrollLeft) { - this.scrollLeftPending = true - this.scrollLeft = scrollLeft - this.element.emitter.emit('did-change-scroll-left', scrollLeft) - return true + this.scrollLeftPending = true; + this.scrollLeft = scrollLeft; + this.element.emitter.emit('did-change-scroll-left', scrollLeft); + return true; } else { - return false + return false; } } - getMaxScrollLeft () { - return Math.round(Math.max(0, this.getScrollWidth() - this.getScrollContainerClientWidth())) + getMaxScrollLeft() { + return Math.round( + Math.max(0, this.getScrollWidth() - this.getScrollContainerClientWidth()) + ); } - getScrollRight () { - return this.getScrollLeft() + this.getScrollContainerClientWidth() + getScrollRight() { + return this.getScrollLeft() + this.getScrollContainerClientWidth(); } - setScrollRight (scrollRight) { - return this.setScrollLeft(scrollRight - this.getScrollContainerClientWidth()) + setScrollRight(scrollRight) { + return this.setScrollLeft( + scrollRight - this.getScrollContainerClientWidth() + ); } - setScrollTopRow (scrollTopRow, scheduleUpdate = true) { + setScrollTopRow(scrollTopRow, scheduleUpdate = true) { if (this.hasInitialMeasurements) { - const didScroll = this.setScrollTop(this.pixelPositionBeforeBlocksForRow(scrollTopRow)) + const didScroll = this.setScrollTop( + this.pixelPositionBeforeBlocksForRow(scrollTopRow) + ); if (didScroll && scheduleUpdate) { - this.scheduleUpdate() + this.scheduleUpdate(); } - return didScroll + return didScroll; } else { - this.pendingScrollTopRow = scrollTopRow - return false + this.pendingScrollTopRow = scrollTopRow; + return false; } } - getScrollTopRow () { + getScrollTopRow() { if (this.hasInitialMeasurements) { - return this.rowForPixelPosition(this.getScrollTop()) + return this.rowForPixelPosition(this.getScrollTop()); } else { - return this.pendingScrollTopRow || 0 + return this.pendingScrollTopRow || 0; } } - setScrollLeftColumn (scrollLeftColumn, scheduleUpdate = true) { + setScrollLeftColumn(scrollLeftColumn, scheduleUpdate = true) { if (this.hasInitialMeasurements && this.getLongestLineWidth() != null) { - const didScroll = this.setScrollLeft(scrollLeftColumn * this.getBaseCharacterWidth()) + const didScroll = this.setScrollLeft( + scrollLeftColumn * this.getBaseCharacterWidth() + ); if (didScroll && scheduleUpdate) { - this.scheduleUpdate() + this.scheduleUpdate(); } - return didScroll + return didScroll; } else { - this.pendingScrollLeftColumn = scrollLeftColumn - return false + this.pendingScrollLeftColumn = scrollLeftColumn; + return false; } } - getScrollLeftColumn () { + getScrollLeftColumn() { if (this.hasInitialMeasurements && this.getLongestLineWidth() != null) { - return Math.round(this.getScrollLeft() / this.getBaseCharacterWidth()) + return Math.round(this.getScrollLeft() / this.getBaseCharacterWidth()); } else { - return this.pendingScrollLeftColumn || 0 + return this.pendingScrollLeftColumn || 0; } } // Ensure the spatial index is populated with rows that are currently visible - populateVisibleRowRange (renderedStartRow) { - const {model} = this.props - const previousScreenLineCount = model.getApproximateScreenLineCount() + populateVisibleRowRange(renderedStartRow) { + const { model } = this.props; + const previousScreenLineCount = model.getApproximateScreenLineCount(); - const renderedEndRow = renderedStartRow + (this.getVisibleTileCount() * this.getRowsPerTile()) - this.props.model.displayLayer.populateSpatialIndexIfNeeded(Infinity, renderedEndRow) + const renderedEndRow = + renderedStartRow + this.getVisibleTileCount() * this.getRowsPerTile(); + this.props.model.displayLayer.populateSpatialIndexIfNeeded( + Infinity, + renderedEndRow + ); // If the approximate screen line count changes, previously-cached derived // dimensions could now be out of date. if (model.getApproximateScreenLineCount() !== previousScreenLineCount) { - this.derivedDimensionsCache = {} + this.derivedDimensionsCache = {}; } } - populateVisibleTiles () { - const startRow = this.getRenderedStartRow() - const endRow = this.getRenderedEndRow() - const freeTileIds = [] + populateVisibleTiles() { + const startRow = this.getRenderedStartRow(); + const endRow = this.getRenderedEndRow(); + const freeTileIds = []; for (let i = 0; i < this.renderedTileStartRows.length; i++) { - const tileStartRow = this.renderedTileStartRows[i] + const tileStartRow = this.renderedTileStartRows[i]; if (tileStartRow < startRow || tileStartRow >= endRow) { - const tileId = this.idsByTileStartRow.get(tileStartRow) - freeTileIds.push(tileId) - this.idsByTileStartRow.delete(tileStartRow) + const tileId = this.idsByTileStartRow.get(tileStartRow); + freeTileIds.push(tileId); + this.idsByTileStartRow.delete(tileStartRow); } } - const rowsPerTile = this.getRowsPerTile() - this.renderedTileStartRows.length = this.getRenderedTileCount() - for (let tileStartRow = startRow, i = 0; tileStartRow < endRow; tileStartRow = tileStartRow + rowsPerTile, i++) { - this.renderedTileStartRows[i] = tileStartRow + const rowsPerTile = this.getRowsPerTile(); + this.renderedTileStartRows.length = this.getRenderedTileCount(); + for ( + let tileStartRow = startRow, i = 0; + tileStartRow < endRow; + tileStartRow = tileStartRow + rowsPerTile, i++ + ) { + this.renderedTileStartRows[i] = tileStartRow; if (!this.idsByTileStartRow.has(tileStartRow)) { if (freeTileIds.length > 0) { - this.idsByTileStartRow.set(tileStartRow, freeTileIds.shift()) + this.idsByTileStartRow.set(tileStartRow, freeTileIds.shift()); } else { - this.idsByTileStartRow.set(tileStartRow, this.nextTileId++) + this.idsByTileStartRow.set(tileStartRow, this.nextTileId++); } } } - this.renderedTileStartRows.sort((a, b) => this.idsByTileStartRow.get(a) - this.idsByTileStartRow.get(b)) + this.renderedTileStartRows.sort( + (a, b) => this.idsByTileStartRow.get(a) - this.idsByTileStartRow.get(b) + ); } - getNextUpdatePromise () { + getNextUpdatePromise() { if (!this.nextUpdatePromise) { - this.nextUpdatePromise = new Promise((resolve) => { + this.nextUpdatePromise = new Promise(resolve => { this.resolveNextUpdatePromise = () => { - this.nextUpdatePromise = null - this.resolveNextUpdatePromise = null - resolve() - } - }) + this.nextUpdatePromise = null; + this.resolveNextUpdatePromise = null; + resolve(); + }; + }); } - return this.nextUpdatePromise + return this.nextUpdatePromise; } - setInputEnabled (inputEnabled) { - this.props.model.update({keyboardInputEnabled: inputEnabled}) + setInputEnabled(inputEnabled) { + this.props.model.update({ keyboardInputEnabled: inputEnabled }); } - isInputEnabled () { - return !this.props.model.isReadOnly() && this.props.model.isKeyboardInputEnabled() + isInputEnabled() { + return ( + !this.props.model.isReadOnly() && + this.props.model.isKeyboardInputEnabled() + ); } - getHiddenInput () { - return this.refs.cursorsAndInput.refs.hiddenInput + getHiddenInput() { + return this.refs.cursorsAndInput.refs.hiddenInput; } - getPlatform () { - return this.props.platform || process.platform + getPlatform() { + return this.props.platform || process.platform; } - getChromeVersion () { - return this.props.chromeVersion || parseInt(process.versions.chrome) + getChromeVersion() { + return this.props.chromeVersion || parseInt(process.versions.chrome); } -} +}; class DummyScrollbarComponent { - constructor (props) { - this.props = props - etch.initialize(this) + constructor(props) { + this.props = props; + etch.initialize(this); } - update (newProps) { - const oldProps = this.props - this.props = newProps - etch.updateSync(this) + update(newProps) { + const oldProps = this.props; + this.props = newProps; + etch.updateSync(this); - const shouldFlushScrollPosition = ( + const shouldFlushScrollPosition = newProps.scrollTop !== oldProps.scrollTop || - newProps.scrollLeft !== oldProps.scrollLeft - ) - if (shouldFlushScrollPosition) this.flushScrollPosition() + newProps.scrollLeft !== oldProps.scrollLeft; + if (shouldFlushScrollPosition) this.flushScrollPosition(); } - flushScrollPosition () { + flushScrollPosition() { if (this.props.orientation === 'horizontal') { - this.element.scrollLeft = this.props.scrollLeft + this.element.scrollLeft = this.props.scrollLeft; } else { - this.element.scrollTop = this.props.scrollTop + this.element.scrollTop = this.props.scrollTop; } } - render () { + render() { const { - orientation, scrollWidth, scrollHeight, - verticalScrollbarWidth, horizontalScrollbarHeight, - canScroll, forceScrollbarVisible, didScroll - } = this.props + orientation, + scrollWidth, + scrollHeight, + verticalScrollbarWidth, + horizontalScrollbarHeight, + canScroll, + forceScrollbarVisible, + didScroll + } = this.props; const outerStyle = { position: 'absolute', contain: 'content', zIndex: 1, willChange: 'transform' - } - if (!canScroll) outerStyle.visibility = 'hidden' + }; + if (!canScroll) outerStyle.visibility = 'hidden'; - const innerStyle = {} + const innerStyle = {}; if (orientation === 'horizontal') { - let right = (verticalScrollbarWidth || 0) - outerStyle.bottom = 0 - outerStyle.left = 0 - outerStyle.right = right + 'px' - outerStyle.height = '15px' - outerStyle.overflowY = 'hidden' - outerStyle.overflowX = forceScrollbarVisible ? 'scroll' : 'auto' - outerStyle.cursor = 'default' - innerStyle.height = '15px' - innerStyle.width = (scrollWidth || 0) + 'px' + let right = verticalScrollbarWidth || 0; + outerStyle.bottom = 0; + outerStyle.left = 0; + outerStyle.right = right + 'px'; + outerStyle.height = '15px'; + outerStyle.overflowY = 'hidden'; + outerStyle.overflowX = forceScrollbarVisible ? 'scroll' : 'auto'; + outerStyle.cursor = 'default'; + innerStyle.height = '15px'; + innerStyle.width = (scrollWidth || 0) + 'px'; } else { - let bottom = (horizontalScrollbarHeight || 0) - outerStyle.right = 0 - outerStyle.top = 0 - outerStyle.bottom = bottom + 'px' - outerStyle.width = '15px' - outerStyle.overflowX = 'hidden' - outerStyle.overflowY = forceScrollbarVisible ? 'scroll' : 'auto' - outerStyle.cursor = 'default' - innerStyle.width = '15px' - innerStyle.height = (scrollHeight || 0) + 'px' + let bottom = horizontalScrollbarHeight || 0; + outerStyle.right = 0; + outerStyle.top = 0; + outerStyle.bottom = bottom + 'px'; + outerStyle.width = '15px'; + outerStyle.overflowX = 'hidden'; + outerStyle.overflowY = forceScrollbarVisible ? 'scroll' : 'auto'; + outerStyle.cursor = 'default'; + innerStyle.width = '15px'; + innerStyle.height = (scrollHeight || 0) + 'px'; } return $.div( @@ -3050,57 +3492,66 @@ class DummyScrollbarComponent { mousedown: this.didMouseDown } }, - $.div({style: innerStyle}) - ) + $.div({ style: innerStyle }) + ); } - didMouseDown (event) { - let {bottom, right} = this.element.getBoundingClientRect() - const clickedOnScrollbar = (this.props.orientation === 'horizontal') - ? event.clientY >= (bottom - this.getRealScrollbarHeight()) - : event.clientX >= (right - this.getRealScrollbarWidth()) - if (!clickedOnScrollbar) this.props.didMouseDown(event) + didMouseDown(event) { + let { bottom, right } = this.element.getBoundingClientRect(); + const clickedOnScrollbar = + this.props.orientation === 'horizontal' + ? event.clientY >= bottom - this.getRealScrollbarHeight() + : event.clientX >= right - this.getRealScrollbarWidth(); + if (!clickedOnScrollbar) this.props.didMouseDown(event); } - getRealScrollbarWidth () { - return this.element.offsetWidth - this.element.clientWidth + getRealScrollbarWidth() { + return this.element.offsetWidth - this.element.clientWidth; } - getRealScrollbarHeight () { - return this.element.offsetHeight - this.element.clientHeight + getRealScrollbarHeight() { + return this.element.offsetHeight - this.element.clientHeight; } } class GutterContainerComponent { - constructor (props) { - this.props = props - etch.initialize(this) + constructor(props) { + this.props = props; + etch.initialize(this); } - update (props) { + update(props) { if (this.shouldUpdate(props)) { - this.props = props - etch.updateSync(this) + this.props = props; + etch.updateSync(this); } } - shouldUpdate (props) { + shouldUpdate(props) { return ( !props.measuredContent || props.lineNumberGutterWidth !== this.props.lineNumberGutterWidth - ) + ); } - render () { - const {hasInitialMeasurements, scrollTop, scrollHeight, guttersToRender, decorationsToRender} = this.props + render() { + const { + hasInitialMeasurements, + scrollTop, + scrollHeight, + guttersToRender, + decorationsToRender + } = this.props; const innerStyle = { willChange: 'transform', display: 'flex' - } + }; if (hasInitialMeasurements) { - innerStyle.transform = `translateY(${-roundToPhysicalPixelBoundary(scrollTop)}px)` + innerStyle.transform = `translateY(${-roundToPhysicalPixelBoundary( + scrollTop + )}px)`; } return $.div( @@ -3114,10 +3565,11 @@ class GutterContainerComponent { backgroundColor: 'inherit' } }, - $.div({style: innerStyle}, - guttersToRender.map((gutter) => { + $.div( + { style: innerStyle }, + guttersToRender.map(gutter => { if (gutter.type === 'line-number') { - return this.renderLineNumberGutter(gutter) + return this.renderLineNumberGutter(gutter); } else { return $(CustomGutterComponent, { key: gutter, @@ -3126,30 +3578,46 @@ class GutterContainerComponent { visible: gutter.isVisible(), height: scrollHeight, decorations: decorationsToRender.customGutter.get(gutter.name) - }) + }); } }) ) - ) + ); } - renderLineNumberGutter (gutter) { + renderLineNumberGutter(gutter) { const { - rootComponent, showLineNumbers, hasInitialMeasurements, lineNumbersToRender, - renderedStartRow, renderedEndRow, rowsPerTile, decorationsToRender, didMeasureVisibleBlockDecoration, - scrollHeight, lineNumberGutterWidth, lineHeight - } = this.props + rootComponent, + showLineNumbers, + hasInitialMeasurements, + lineNumbersToRender, + renderedStartRow, + renderedEndRow, + rowsPerTile, + decorationsToRender, + didMeasureVisibleBlockDecoration, + scrollHeight, + lineNumberGutterWidth, + lineHeight + } = this.props; if (!gutter.isVisible()) { - return null + return null; } - const oneTrueLineNumberGutter = gutter.name === 'line-number' - const ref = oneTrueLineNumberGutter ? 'lineNumberGutter' : undefined - const width = oneTrueLineNumberGutter ? lineNumberGutterWidth : undefined + const oneTrueLineNumberGutter = gutter.name === 'line-number'; + const ref = oneTrueLineNumberGutter ? 'lineNumberGutter' : undefined; + const width = oneTrueLineNumberGutter ? lineNumberGutterWidth : undefined; if (hasInitialMeasurements) { - const {maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags} = lineNumbersToRender + const { + maxDigits, + keys, + bufferRows, + screenRows, + softWrappedFlags, + foldableFlags + } = lineNumbersToRender; return $(LineNumberGutterComponent, { ref, element: gutter.getElement(), @@ -3175,7 +3643,7 @@ class GutterContainerComponent { width, lineHeight: lineHeight, showLineNumbers - }) + }); } else { return $(LineNumberGutterComponent, { ref, @@ -3186,65 +3654,85 @@ class GutterContainerComponent { onMouseMove: gutter.onMouseMove, maxDigits: lineNumbersToRender.maxDigits, showLineNumbers - }) + }); } } } class LineNumberGutterComponent { - constructor (props) { - this.props = props - this.element = this.props.element - this.virtualNode = $.div(null) - this.virtualNode.domNode = this.element - this.nodePool = new NodePool() - etch.updateSync(this) + constructor(props) { + this.props = props; + this.element = this.props.element; + this.virtualNode = $.div(null); + this.virtualNode.domNode = this.element; + this.nodePool = new NodePool(); + etch.updateSync(this); } - update (newProps) { + update(newProps) { if (this.shouldUpdate(newProps)) { - this.props = newProps - etch.updateSync(this) + this.props = newProps; + etch.updateSync(this); } } - render () { + render() { const { - rootComponent, showLineNumbers, height, width, startRow, endRow, rowsPerTile, - maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags, decorations, + rootComponent, + showLineNumbers, + height, + width, + startRow, + endRow, + rowsPerTile, + maxDigits, + keys, + bufferRows, + screenRows, + softWrappedFlags, + foldableFlags, + decorations, className - } = this.props + } = this.props; - let children = null + let children = null; if (bufferRows) { - children = new Array(rootComponent.renderedTileStartRows.length) + children = new Array(rootComponent.renderedTileStartRows.length); for (let i = 0; i < rootComponent.renderedTileStartRows.length; i++) { - const tileStartRow = rootComponent.renderedTileStartRows[i] - const tileEndRow = Math.min(endRow, tileStartRow + rowsPerTile) - const tileChildren = new Array(tileEndRow - tileStartRow) + const tileStartRow = rootComponent.renderedTileStartRows[i]; + const tileEndRow = Math.min(endRow, tileStartRow + rowsPerTile); + const tileChildren = new Array(tileEndRow - tileStartRow); for (let row = tileStartRow; row < tileEndRow; row++) { - const indexInTile = row - tileStartRow - const j = row - startRow - const key = keys[j] - const softWrapped = softWrappedFlags[j] - const foldable = foldableFlags[j] - const bufferRow = bufferRows[j] - const screenRow = screenRows[j] + const indexInTile = row - tileStartRow; + const j = row - startRow; + const key = keys[j]; + const softWrapped = softWrappedFlags[j]; + const foldable = foldableFlags[j]; + const bufferRow = bufferRows[j]; + const screenRow = screenRows[j]; - let className = 'line-number' - if (foldable) className = className + ' foldable' + let className = 'line-number'; + if (foldable) className = className + ' foldable'; - const decorationsForRow = decorations[row - startRow] - if (decorationsForRow) className = className + ' ' + decorationsForRow + const decorationsForRow = decorations[row - startRow]; + if (decorationsForRow) + className = className + ' ' + decorationsForRow; - let number = null + let number = null; if (showLineNumbers) { if (this.props.labelFn == null) { - number = softWrapped ? '•' : bufferRow + 1 - number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number + number = softWrapped ? '•' : bufferRow + 1; + number = + NBSP_CHARACTER.repeat(maxDigits - number.length) + number; } else { - number = this.props.labelFn({bufferRow, screenRow, foldable, softWrapped, maxDigits}) + number = this.props.labelFn({ + bufferRow, + screenRow, + foldable, + softWrapped, + maxDigits + }); } } @@ -3254,8 +3742,11 @@ class LineNumberGutterComponent { // the beginning of the tile, because the tile will already be // positioned to take into account block decorations added after the // last row of the previous tile. - let marginTop = rootComponent.heightForBlockDecorationsBeforeRow(row) - if (indexInTile > 0) marginTop += rootComponent.heightForBlockDecorationsAfterRow(row - 1) + let marginTop = rootComponent.heightForBlockDecorationsBeforeRow(row); + if (indexInTile > 0) + marginTop += rootComponent.heightForBlockDecorationsAfterRow( + row - 1 + ); tileChildren[row - tileStartRow] = $(LineNumberComponent, { key, @@ -3266,217 +3757,264 @@ class LineNumberGutterComponent { number, marginTop, nodePool: this.nodePool - }) + }); } - const tileTop = rootComponent.pixelPositionBeforeBlocksForRow(tileStartRow) - const tileBottom = rootComponent.pixelPositionBeforeBlocksForRow(tileEndRow) - const tileHeight = tileBottom - tileTop - const tileWidth = width != null && width > 0 ? width + 'px' : '' + const tileTop = rootComponent.pixelPositionBeforeBlocksForRow( + tileStartRow + ); + const tileBottom = rootComponent.pixelPositionBeforeBlocksForRow( + tileEndRow + ); + const tileHeight = tileBottom - tileTop; + const tileWidth = width != null && width > 0 ? width + 'px' : ''; - children[i] = $.div({ - key: rootComponent.idsByTileStartRow.get(tileStartRow), - style: { - contain: 'layout style', - position: 'absolute', - top: 0, - height: tileHeight + 'px', - width: tileWidth, - transform: `translateY(${tileTop}px)` - } - }, ...tileChildren) + children[i] = $.div( + { + key: rootComponent.idsByTileStartRow.get(tileStartRow), + style: { + contain: 'layout style', + position: 'absolute', + top: 0, + height: tileHeight + 'px', + width: tileWidth, + transform: `translateY(${tileTop}px)` + } + }, + ...tileChildren + ); } } - let rootClassName = 'gutter line-numbers' + let rootClassName = 'gutter line-numbers'; if (className) { - rootClassName += ' ' + className + rootClassName += ' ' + className; } return $.div( { className: rootClassName, - attributes: {'gutter-name': this.props.name}, - style: {position: 'relative', height: ceilToPhysicalPixelBoundary(height) + 'px'}, + attributes: { 'gutter-name': this.props.name }, + style: { + position: 'relative', + height: ceilToPhysicalPixelBoundary(height) + 'px' + }, on: { mousedown: this.didMouseDown, mousemove: this.didMouseMove } }, - $.div({key: 'placeholder', className: 'line-number dummy', style: {visibility: 'hidden'}}, + $.div( + { + key: 'placeholder', + className: 'line-number dummy', + style: { visibility: 'hidden' } + }, showLineNumbers ? '0'.repeat(maxDigits) : null, - $.div({className: 'icon-right'}) + $.div({ className: 'icon-right' }) ), children - ) + ); } - shouldUpdate (newProps) { - const oldProps = this.props + shouldUpdate(newProps) { + const oldProps = this.props; - if (oldProps.showLineNumbers !== newProps.showLineNumbers) return true - if (oldProps.height !== newProps.height) return true - if (oldProps.width !== newProps.width) return true - if (oldProps.lineHeight !== newProps.lineHeight) return true - if (oldProps.startRow !== newProps.startRow) return true - if (oldProps.endRow !== newProps.endRow) return true - if (oldProps.rowsPerTile !== newProps.rowsPerTile) return true - if (oldProps.maxDigits !== newProps.maxDigits) return true - if (oldProps.labelFn !== newProps.labelFn) return true - if (oldProps.className !== newProps.className) return true - if (newProps.didMeasureVisibleBlockDecoration) return true - if (!arraysEqual(oldProps.keys, newProps.keys)) return true - if (!arraysEqual(oldProps.bufferRows, newProps.bufferRows)) return true - if (!arraysEqual(oldProps.foldableFlags, newProps.foldableFlags)) return true - if (!arraysEqual(oldProps.decorations, newProps.decorations)) return true + if (oldProps.showLineNumbers !== newProps.showLineNumbers) return true; + if (oldProps.height !== newProps.height) return true; + if (oldProps.width !== newProps.width) return true; + if (oldProps.lineHeight !== newProps.lineHeight) return true; + if (oldProps.startRow !== newProps.startRow) return true; + if (oldProps.endRow !== newProps.endRow) return true; + if (oldProps.rowsPerTile !== newProps.rowsPerTile) return true; + if (oldProps.maxDigits !== newProps.maxDigits) return true; + if (oldProps.labelFn !== newProps.labelFn) return true; + if (oldProps.className !== newProps.className) return true; + if (newProps.didMeasureVisibleBlockDecoration) return true; + if (!arraysEqual(oldProps.keys, newProps.keys)) return true; + if (!arraysEqual(oldProps.bufferRows, newProps.bufferRows)) return true; + if (!arraysEqual(oldProps.foldableFlags, newProps.foldableFlags)) + return true; + if (!arraysEqual(oldProps.decorations, newProps.decorations)) return true; - let oldTileStartRow = oldProps.startRow - let newTileStartRow = newProps.startRow - while (oldTileStartRow < oldProps.endRow || newTileStartRow < newProps.endRow) { - let oldTileBlockDecorations = oldProps.blockDecorations.get(oldTileStartRow) - let newTileBlockDecorations = newProps.blockDecorations.get(newTileStartRow) + let oldTileStartRow = oldProps.startRow; + let newTileStartRow = newProps.startRow; + while ( + oldTileStartRow < oldProps.endRow || + newTileStartRow < newProps.endRow + ) { + let oldTileBlockDecorations = oldProps.blockDecorations.get( + oldTileStartRow + ); + let newTileBlockDecorations = newProps.blockDecorations.get( + newTileStartRow + ); if (oldTileBlockDecorations && newTileBlockDecorations) { - if (oldTileBlockDecorations.size !== newTileBlockDecorations.size) return true + if (oldTileBlockDecorations.size !== newTileBlockDecorations.size) + return true; - let blockDecorationsChanged = false + let blockDecorationsChanged = false; oldTileBlockDecorations.forEach((oldDecorations, screenLineId) => { if (!blockDecorationsChanged) { - const newDecorations = newTileBlockDecorations.get(screenLineId) - blockDecorationsChanged = (newDecorations == null || !arraysEqual(oldDecorations, newDecorations)) + const newDecorations = newTileBlockDecorations.get(screenLineId); + blockDecorationsChanged = + newDecorations == null || + !arraysEqual(oldDecorations, newDecorations); } - }) - if (blockDecorationsChanged) return true + }); + if (blockDecorationsChanged) return true; newTileBlockDecorations.forEach((newDecorations, screenLineId) => { if (!blockDecorationsChanged) { - const oldDecorations = oldTileBlockDecorations.get(screenLineId) - blockDecorationsChanged = (oldDecorations == null) + const oldDecorations = oldTileBlockDecorations.get(screenLineId); + blockDecorationsChanged = oldDecorations == null; } - }) - if (blockDecorationsChanged) return true + }); + if (blockDecorationsChanged) return true; } else if (oldTileBlockDecorations) { - return true + return true; } else if (newTileBlockDecorations) { - return true + return true; } - oldTileStartRow += oldProps.rowsPerTile - newTileStartRow += newProps.rowsPerTile + oldTileStartRow += oldProps.rowsPerTile; + newTileStartRow += newProps.rowsPerTile; } - return false + return false; } - didMouseDown (event) { + didMouseDown(event) { if (this.props.onMouseDown == null) { - this.props.rootComponent.didMouseDownOnLineNumberGutter(event) + this.props.rootComponent.didMouseDownOnLineNumberGutter(event); } else { - const {bufferRow, screenRow} = event.target.dataset + const { bufferRow, screenRow } = event.target.dataset; this.props.onMouseDown({ bufferRow: parseInt(bufferRow, 10), screenRow: parseInt(screenRow, 10), domEvent: event - }) + }); } } - didMouseMove (event) { + didMouseMove(event) { if (this.props.onMouseMove != null) { - const {bufferRow, screenRow} = event.target.dataset + const { bufferRow, screenRow } = event.target.dataset; this.props.onMouseMove({ bufferRow: parseInt(bufferRow, 10), screenRow: parseInt(screenRow, 10), domEvent: event - }) + }); } } } class LineNumberComponent { - constructor (props) { - const {className, width, marginTop, bufferRow, screenRow, number, nodePool} = props - this.props = props - const style = {} - if (width != null && width > 0) style.width = width + 'px' - if (marginTop != null && marginTop > 0) style.marginTop = marginTop + 'px' - this.element = nodePool.getElement('DIV', className, style) - this.element.dataset.bufferRow = bufferRow - this.element.dataset.screenRow = screenRow - if (number) this.element.appendChild(nodePool.getTextNode(number)) - this.element.appendChild(nodePool.getElement('DIV', 'icon-right', null)) + constructor(props) { + const { + className, + width, + marginTop, + bufferRow, + screenRow, + number, + nodePool + } = props; + this.props = props; + const style = {}; + if (width != null && width > 0) style.width = width + 'px'; + if (marginTop != null && marginTop > 0) style.marginTop = marginTop + 'px'; + this.element = nodePool.getElement('DIV', className, style); + this.element.dataset.bufferRow = bufferRow; + this.element.dataset.screenRow = screenRow; + if (number) this.element.appendChild(nodePool.getTextNode(number)); + this.element.appendChild(nodePool.getElement('DIV', 'icon-right', null)); } - destroy () { - this.element.remove() - this.props.nodePool.release(this.element) + destroy() { + this.element.remove(); + this.props.nodePool.release(this.element); } - update (props) { - const {nodePool, className, width, marginTop, bufferRow, screenRow, number} = props + update(props) { + const { + nodePool, + className, + width, + marginTop, + bufferRow, + screenRow, + number + } = props; - if (this.props.bufferRow !== bufferRow) this.element.dataset.bufferRow = bufferRow - if (this.props.screenRow !== screenRow) this.element.dataset.screenRow = screenRow - if (this.props.className !== className) this.element.className = className + if (this.props.bufferRow !== bufferRow) + this.element.dataset.bufferRow = bufferRow; + if (this.props.screenRow !== screenRow) + this.element.dataset.screenRow = screenRow; + if (this.props.className !== className) this.element.className = className; if (this.props.width !== width) { if (width != null && width > 0) { - this.element.style.width = width + 'px' + this.element.style.width = width + 'px'; } else { - this.element.style.width = '' + this.element.style.width = ''; } } if (this.props.marginTop !== marginTop) { if (marginTop != null && marginTop > 0) { - this.element.style.marginTop = marginTop + 'px' + this.element.style.marginTop = marginTop + 'px'; } else { - this.element.style.marginTop = '' + this.element.style.marginTop = ''; } } if (this.props.number !== number) { if (this.props.number != null) { - const numberNode = this.element.firstChild - numberNode.remove() - nodePool.release(numberNode) + const numberNode = this.element.firstChild; + numberNode.remove(); + nodePool.release(numberNode); } if (number != null) { - this.element.insertBefore(nodePool.getTextNode(number), this.element.firstChild) + this.element.insertBefore( + nodePool.getTextNode(number), + this.element.firstChild + ); } } - this.props = props + this.props = props; } } class CustomGutterComponent { - constructor (props) { - this.props = props - this.element = this.props.element - this.virtualNode = $.div(null) - this.virtualNode.domNode = this.element - etch.updateSync(this) + constructor(props) { + this.props = props; + this.element = this.props.element; + this.virtualNode = $.div(null); + this.virtualNode.domNode = this.element; + etch.updateSync(this); } - update (props) { - this.props = props - etch.updateSync(this) + update(props) { + this.props = props; + etch.updateSync(this); } - destroy () { - etch.destroy(this) + destroy() { + etch.destroy(this); } - render () { - let className = 'gutter' + render() { + let className = 'gutter'; if (this.props.className) { - className += ' ' + this.props.className + className += ' ' + this.props.className; } return $.div( { className, - attributes: {'gutter-name': this.props.name}, + attributes: { 'gutter-name': this.props.name }, style: { display: this.props.visible ? '' : 'none' } @@ -3484,142 +4022,171 @@ class CustomGutterComponent { $.div( { className: 'custom-decorations', - style: {height: this.props.height + 'px'} + style: { height: this.props.height + 'px' } }, this.renderDecorations() ) - ) + ); } - renderDecorations () { - if (!this.props.decorations) return null + renderDecorations() { + if (!this.props.decorations) return null; - return this.props.decorations.map(({className, element, top, height}) => { + return this.props.decorations.map(({ className, element, top, height }) => { return $(CustomGutterDecorationComponent, { className, element, top, height - }) - }) + }); + }); } } class CustomGutterDecorationComponent { - constructor (props) { - this.props = props - this.element = document.createElement('div') - const {top, height, className, element} = this.props + constructor(props) { + this.props = props; + this.element = document.createElement('div'); + const { top, height, className, element } = this.props; - this.element.style.position = 'absolute' - this.element.style.top = top + 'px' - this.element.style.height = height + 'px' - if (className != null) this.element.className = className + this.element.style.position = 'absolute'; + this.element.style.top = top + 'px'; + this.element.style.height = height + 'px'; + if (className != null) this.element.className = className; if (element != null) { - this.element.appendChild(element) - element.style.height = height + 'px' + this.element.appendChild(element); + element.style.height = height + 'px'; } } - update (newProps) { - const oldProps = this.props - this.props = newProps + update(newProps) { + const oldProps = this.props; + this.props = newProps; - if (newProps.top !== oldProps.top) this.element.style.top = newProps.top + 'px' + if (newProps.top !== oldProps.top) + this.element.style.top = newProps.top + 'px'; if (newProps.height !== oldProps.height) { - this.element.style.height = newProps.height + 'px' - if (newProps.element) newProps.element.style.height = newProps.height + 'px' + this.element.style.height = newProps.height + 'px'; + if (newProps.element) + newProps.element.style.height = newProps.height + 'px'; } - if (newProps.className !== oldProps.className) this.element.className = newProps.className || '' + if (newProps.className !== oldProps.className) + this.element.className = newProps.className || ''; if (newProps.element !== oldProps.element) { - if (this.element.firstChild) this.element.firstChild.remove() + if (this.element.firstChild) this.element.firstChild.remove(); if (newProps.element != null) { - this.element.appendChild(newProps.element) - newProps.element.style.height = newProps.height + 'px' + this.element.appendChild(newProps.element); + newProps.element.style.height = newProps.height + 'px'; } } } } class CursorsAndInputComponent { - constructor (props) { - this.props = props - etch.initialize(this) + constructor(props) { + this.props = props; + etch.initialize(this); } - update (props) { + update(props) { if (props.measuredContent) { - this.props = props - etch.updateSync(this) + this.props = props; + etch.updateSync(this); } } - updateCursorBlinkSync (cursorsBlinkedOff) { - this.props.cursorsBlinkedOff = cursorsBlinkedOff - const className = this.getCursorsClassName() - this.refs.cursors.className = className - this.virtualNode.props.className = className + updateCursorBlinkSync(cursorsBlinkedOff) { + this.props.cursorsBlinkedOff = cursorsBlinkedOff; + const className = this.getCursorsClassName(); + this.refs.cursors.className = className; + this.virtualNode.props.className = className; } - render () { - const {lineHeight, decorationsToRender, scrollHeight, scrollWidth} = this.props + render() { + const { + lineHeight, + decorationsToRender, + scrollHeight, + scrollWidth + } = this.props; - const className = this.getCursorsClassName() - const cursorHeight = lineHeight + 'px' + const className = this.getCursorsClassName(); + const cursorHeight = lineHeight + 'px'; - const children = [this.renderHiddenInput()] + const children = [this.renderHiddenInput()]; for (let i = 0; i < decorationsToRender.cursors.length; i++) { - const {pixelLeft, pixelTop, pixelWidth, className: extraCursorClassName, style: extraCursorStyle} = decorationsToRender.cursors[i] - let cursorClassName = 'cursor' - if (extraCursorClassName) cursorClassName += ' ' + extraCursorClassName + const { + pixelLeft, + pixelTop, + pixelWidth, + className: extraCursorClassName, + style: extraCursorStyle + } = decorationsToRender.cursors[i]; + let cursorClassName = 'cursor'; + if (extraCursorClassName) cursorClassName += ' ' + extraCursorClassName; const cursorStyle = { height: cursorHeight, width: Math.min(pixelWidth, scrollWidth - pixelLeft) + 'px', transform: `translate(${pixelLeft}px, ${pixelTop}px)` - } - if (extraCursorStyle) Object.assign(cursorStyle, extraCursorStyle) + }; + if (extraCursorStyle) Object.assign(cursorStyle, extraCursorStyle); - children.push($.div({ - className: cursorClassName, - style: cursorStyle - })) + children.push( + $.div({ + className: cursorClassName, + style: cursorStyle + }) + ); } - return $.div({ - key: 'cursors', - ref: 'cursors', - className, - style: { - position: 'absolute', - contain: 'strict', - zIndex: 1, - width: scrollWidth + 'px', - height: scrollHeight + 'px', - pointerEvents: 'none', - userSelect: 'none' - } - }, children) + return $.div( + { + key: 'cursors', + ref: 'cursors', + className, + style: { + position: 'absolute', + contain: 'strict', + zIndex: 1, + width: scrollWidth + 'px', + height: scrollHeight + 'px', + pointerEvents: 'none', + userSelect: 'none' + } + }, + children + ); } - getCursorsClassName () { - return this.props.cursorsBlinkedOff ? 'cursors blink-off' : 'cursors' + getCursorsClassName() { + return this.props.cursorsBlinkedOff ? 'cursors blink-off' : 'cursors'; } - renderHiddenInput () { + renderHiddenInput() { const { - lineHeight, hiddenInputPosition, didBlurHiddenInput, didFocusHiddenInput, - didPaste, didTextInput, didKeydown, didKeyup, didKeypress, - didCompositionStart, didCompositionUpdate, didCompositionEnd, tabIndex - } = this.props + lineHeight, + hiddenInputPosition, + didBlurHiddenInput, + didFocusHiddenInput, + didPaste, + didTextInput, + didKeydown, + didKeyup, + didKeypress, + didCompositionStart, + didCompositionUpdate, + didCompositionEnd, + tabIndex + } = this.props; - let top, left + let top, left; if (hiddenInputPosition) { - top = hiddenInputPosition.pixelTop - left = hiddenInputPosition.pixelLeft + top = hiddenInputPosition.pixelTop; + left = hiddenInputPosition.pixelLeft; } else { - top = 0 - left = 0 + top = 0; + left = 0; } return $.input({ @@ -3649,41 +4216,41 @@ class CursorsAndInputComponent { padding: 0, border: 0 } - }) + }); } } class LinesTileComponent { - constructor (props) { - this.props = props - etch.initialize(this) - this.createLines() - this.updateBlockDecorations({}, props) + constructor(props) { + this.props = props; + etch.initialize(this); + this.createLines(); + this.updateBlockDecorations({}, props); } - update (newProps) { + update(newProps) { if (this.shouldUpdate(newProps)) { - const oldProps = this.props - this.props = newProps - etch.updateSync(this) + const oldProps = this.props; + this.props = newProps; + etch.updateSync(this); if (!newProps.measuredContent) { - this.updateLines(oldProps, newProps) - this.updateBlockDecorations(oldProps, newProps) + this.updateLines(oldProps, newProps); + this.updateBlockDecorations(oldProps, newProps); } } } - destroy () { + destroy() { for (let i = 0; i < this.lineComponents.length; i++) { - this.lineComponents[i].destroy() + this.lineComponents[i].destroy(); } - this.lineComponents.length = 0 + this.lineComponents.length = 0; - return etch.destroy(this) + return etch.destroy(this); } - render () { - const {height, width, top} = this.props + render() { + const { height, width, top } = this.props; return $.div( { @@ -3696,16 +4263,21 @@ class LinesTileComponent { } } // Lines and block decorations will be manually inserted here for efficiency - ) + ); } - createLines () { + createLines() { const { - tileStartRow, screenLines, lineDecorations, textDecorations, - nodePool, displayLayer, lineComponentsByScreenLineId - } = this.props + tileStartRow, + screenLines, + lineDecorations, + textDecorations, + nodePool, + displayLayer, + lineComponentsByScreenLineId + } = this.props; - this.lineComponents = [] + this.lineComponents = []; for (let i = 0, length = screenLines.length; i < length; i++) { const component = new LineComponent({ screenLine: screenLines[i], @@ -3715,29 +4287,37 @@ class LinesTileComponent { displayLayer, nodePool, lineComponentsByScreenLineId - }) - this.element.appendChild(component.element) - this.lineComponents.push(component) + }); + this.element.appendChild(component.element); + this.lineComponents.push(component); } } - updateLines (oldProps, newProps) { + updateLines(oldProps, newProps) { var { - screenLines, tileStartRow, lineDecorations, textDecorations, - nodePool, displayLayer, lineComponentsByScreenLineId - } = newProps + screenLines, + tileStartRow, + lineDecorations, + textDecorations, + nodePool, + displayLayer, + lineComponentsByScreenLineId + } = newProps; - var oldScreenLines = oldProps.screenLines - var newScreenLines = screenLines - var oldScreenLinesEndIndex = oldScreenLines.length - var newScreenLinesEndIndex = newScreenLines.length - var oldScreenLineIndex = 0 - var newScreenLineIndex = 0 - var lineComponentIndex = 0 + var oldScreenLines = oldProps.screenLines; + var newScreenLines = screenLines; + var oldScreenLinesEndIndex = oldScreenLines.length; + var newScreenLinesEndIndex = newScreenLines.length; + var oldScreenLineIndex = 0; + var newScreenLineIndex = 0; + var lineComponentIndex = 0; - while (oldScreenLineIndex < oldScreenLinesEndIndex || newScreenLineIndex < newScreenLinesEndIndex) { - var oldScreenLine = oldScreenLines[oldScreenLineIndex] - var newScreenLine = newScreenLines[newScreenLineIndex] + while ( + oldScreenLineIndex < oldScreenLinesEndIndex || + newScreenLineIndex < newScreenLinesEndIndex + ) { + var oldScreenLine = oldScreenLines[oldScreenLineIndex]; + var newScreenLine = newScreenLines[newScreenLineIndex]; if (oldScreenLineIndex >= oldScreenLinesEndIndex) { var newScreenLineComponent = new LineComponent({ @@ -3748,33 +4328,40 @@ class LinesTileComponent { displayLayer, nodePool, lineComponentsByScreenLineId - }) - this.element.appendChild(newScreenLineComponent.element) - this.lineComponents.push(newScreenLineComponent) + }); + this.element.appendChild(newScreenLineComponent.element); + this.lineComponents.push(newScreenLineComponent); - newScreenLineIndex++ - lineComponentIndex++ + newScreenLineIndex++; + lineComponentIndex++; } else if (newScreenLineIndex >= newScreenLinesEndIndex) { - this.lineComponents[lineComponentIndex].destroy() - this.lineComponents.splice(lineComponentIndex, 1) + this.lineComponents[lineComponentIndex].destroy(); + this.lineComponents.splice(lineComponentIndex, 1); - oldScreenLineIndex++ + oldScreenLineIndex++; } else if (oldScreenLine === newScreenLine) { - var lineComponent = this.lineComponents[lineComponentIndex] + var lineComponent = this.lineComponents[lineComponentIndex]; lineComponent.update({ screenRow: tileStartRow + newScreenLineIndex, lineDecoration: lineDecorations[newScreenLineIndex], textDecorations: textDecorations[newScreenLineIndex] - }) + }); - oldScreenLineIndex++ - newScreenLineIndex++ - lineComponentIndex++ + oldScreenLineIndex++; + newScreenLineIndex++; + lineComponentIndex++; } else { - var oldScreenLineIndexInNewScreenLines = newScreenLines.indexOf(oldScreenLine) - var newScreenLineIndexInOldScreenLines = oldScreenLines.indexOf(newScreenLine) - if (newScreenLineIndex < oldScreenLineIndexInNewScreenLines && oldScreenLineIndexInNewScreenLines < newScreenLinesEndIndex) { - var newScreenLineComponents = [] + var oldScreenLineIndexInNewScreenLines = newScreenLines.indexOf( + oldScreenLine + ); + var newScreenLineIndexInOldScreenLines = oldScreenLines.indexOf( + newScreenLine + ); + if ( + newScreenLineIndex < oldScreenLineIndexInNewScreenLines && + oldScreenLineIndexInNewScreenLines < newScreenLinesEndIndex + ) { + var newScreenLineComponents = []; while (newScreenLineIndex < oldScreenLineIndexInNewScreenLines) { // eslint-disable-next-line no-redeclare var newScreenLineComponent = new LineComponent({ @@ -3785,24 +4372,35 @@ class LinesTileComponent { displayLayer, nodePool, lineComponentsByScreenLineId - }) - this.element.insertBefore(newScreenLineComponent.element, this.getFirstElementForScreenLine(oldProps, oldScreenLine)) - newScreenLineComponents.push(newScreenLineComponent) + }); + this.element.insertBefore( + newScreenLineComponent.element, + this.getFirstElementForScreenLine(oldProps, oldScreenLine) + ); + newScreenLineComponents.push(newScreenLineComponent); - newScreenLineIndex++ + newScreenLineIndex++; } - this.lineComponents.splice(lineComponentIndex, 0, ...newScreenLineComponents) - lineComponentIndex = lineComponentIndex + newScreenLineComponents.length - } else if (oldScreenLineIndex < newScreenLineIndexInOldScreenLines && newScreenLineIndexInOldScreenLines < oldScreenLinesEndIndex) { + this.lineComponents.splice( + lineComponentIndex, + 0, + ...newScreenLineComponents + ); + lineComponentIndex = + lineComponentIndex + newScreenLineComponents.length; + } else if ( + oldScreenLineIndex < newScreenLineIndexInOldScreenLines && + newScreenLineIndexInOldScreenLines < oldScreenLinesEndIndex + ) { while (oldScreenLineIndex < newScreenLineIndexInOldScreenLines) { - this.lineComponents[lineComponentIndex].destroy() - this.lineComponents.splice(lineComponentIndex, 1) + this.lineComponents[lineComponentIndex].destroy(); + this.lineComponents.splice(lineComponentIndex, 1); - oldScreenLineIndex++ + oldScreenLineIndex++; } } else { - var oldScreenLineComponent = this.lineComponents[lineComponentIndex] + var oldScreenLineComponent = this.lineComponents[lineComponentIndex]; // eslint-disable-next-line no-redeclare var newScreenLineComponent = new LineComponent({ screenLine: newScreenLines[newScreenLineIndex], @@ -3812,407 +4410,498 @@ class LinesTileComponent { displayLayer, nodePool, lineComponentsByScreenLineId - }) - this.element.insertBefore(newScreenLineComponent.element, oldScreenLineComponent.element) - oldScreenLineComponent.destroy() - this.lineComponents[lineComponentIndex] = newScreenLineComponent + }); + this.element.insertBefore( + newScreenLineComponent.element, + oldScreenLineComponent.element + ); + oldScreenLineComponent.destroy(); + this.lineComponents[lineComponentIndex] = newScreenLineComponent; - oldScreenLineIndex++ - newScreenLineIndex++ - lineComponentIndex++ + oldScreenLineIndex++; + newScreenLineIndex++; + lineComponentIndex++; } } } } - getFirstElementForScreenLine (oldProps, screenLine) { - var blockDecorations = oldProps.blockDecorations ? oldProps.blockDecorations.get(screenLine.id) : null + getFirstElementForScreenLine(oldProps, screenLine) { + var blockDecorations = oldProps.blockDecorations + ? oldProps.blockDecorations.get(screenLine.id) + : null; if (blockDecorations) { - var blockDecorationElementsBeforeOldScreenLine = [] + var blockDecorationElementsBeforeOldScreenLine = []; for (let i = 0; i < blockDecorations.length; i++) { - var decoration = blockDecorations[i] + var decoration = blockDecorations[i]; if (decoration.position !== 'after') { blockDecorationElementsBeforeOldScreenLine.push( TextEditor.viewForItem(decoration.item) - ) + ); } } - for (let i = 0; i < blockDecorationElementsBeforeOldScreenLine.length; i++) { - var blockDecorationElement = blockDecorationElementsBeforeOldScreenLine[i] - if (!blockDecorationElementsBeforeOldScreenLine.includes(blockDecorationElement.previousSibling)) { - return blockDecorationElement + for ( + let i = 0; + i < blockDecorationElementsBeforeOldScreenLine.length; + i++ + ) { + var blockDecorationElement = + blockDecorationElementsBeforeOldScreenLine[i]; + if ( + !blockDecorationElementsBeforeOldScreenLine.includes( + blockDecorationElement.previousSibling + ) + ) { + return blockDecorationElement; } } } - return oldProps.lineComponentsByScreenLineId.get(screenLine.id).element + return oldProps.lineComponentsByScreenLineId.get(screenLine.id).element; } - updateBlockDecorations (oldProps, newProps) { - var {blockDecorations, lineComponentsByScreenLineId} = newProps + updateBlockDecorations(oldProps, newProps) { + var { blockDecorations, lineComponentsByScreenLineId } = newProps; if (oldProps.blockDecorations) { oldProps.blockDecorations.forEach((oldDecorations, screenLineId) => { - var newDecorations = newProps.blockDecorations ? newProps.blockDecorations.get(screenLineId) : null + var newDecorations = newProps.blockDecorations + ? newProps.blockDecorations.get(screenLineId) + : null; for (var i = 0; i < oldDecorations.length; i++) { - var oldDecoration = oldDecorations[i] - if (newDecorations && newDecorations.includes(oldDecoration)) continue + var oldDecoration = oldDecorations[i]; + if (newDecorations && newDecorations.includes(oldDecoration)) + continue; - var element = TextEditor.viewForItem(oldDecoration.item) - if (element.parentElement !== this.element) continue + var element = TextEditor.viewForItem(oldDecoration.item); + if (element.parentElement !== this.element) continue; - element.remove() + element.remove(); } - }) + }); } if (blockDecorations) { blockDecorations.forEach((newDecorations, screenLineId) => { - const oldDecorations = oldProps.blockDecorations ? oldProps.blockDecorations.get(screenLineId) : null - const lineNode = lineComponentsByScreenLineId.get(screenLineId).element - let lastAfter = lineNode + const oldDecorations = oldProps.blockDecorations + ? oldProps.blockDecorations.get(screenLineId) + : null; + const lineNode = lineComponentsByScreenLineId.get(screenLineId).element; + let lastAfter = lineNode; for (let i = 0; i < newDecorations.length; i++) { - const newDecoration = newDecorations[i] - const element = TextEditor.viewForItem(newDecoration.item) + const newDecoration = newDecorations[i]; + const element = TextEditor.viewForItem(newDecoration.item); if (oldDecorations && oldDecorations.includes(newDecoration)) { if (newDecoration.position === 'after') { - lastAfter = element + lastAfter = element; } - continue + continue; } if (newDecoration.position === 'after') { - this.element.insertBefore(element, lastAfter.nextSibling) - lastAfter = element + this.element.insertBefore(element, lastAfter.nextSibling); + lastAfter = element; } else { - this.element.insertBefore(element, lineNode) + this.element.insertBefore(element, lineNode); } } - }) + }); } } - shouldUpdate (newProps) { - const oldProps = this.props - if (oldProps.top !== newProps.top) return true - if (oldProps.height !== newProps.height) return true - if (oldProps.width !== newProps.width) return true - if (oldProps.lineHeight !== newProps.lineHeight) return true - if (oldProps.tileStartRow !== newProps.tileStartRow) return true - if (oldProps.tileEndRow !== newProps.tileEndRow) return true - if (!arraysEqual(oldProps.screenLines, newProps.screenLines)) return true - if (!arraysEqual(oldProps.lineDecorations, newProps.lineDecorations)) return true + shouldUpdate(newProps) { + const oldProps = this.props; + if (oldProps.top !== newProps.top) return true; + if (oldProps.height !== newProps.height) return true; + if (oldProps.width !== newProps.width) return true; + if (oldProps.lineHeight !== newProps.lineHeight) return true; + if (oldProps.tileStartRow !== newProps.tileStartRow) return true; + if (oldProps.tileEndRow !== newProps.tileEndRow) return true; + if (!arraysEqual(oldProps.screenLines, newProps.screenLines)) return true; + if (!arraysEqual(oldProps.lineDecorations, newProps.lineDecorations)) + return true; if (oldProps.blockDecorations && newProps.blockDecorations) { - if (oldProps.blockDecorations.size !== newProps.blockDecorations.size) return true + if (oldProps.blockDecorations.size !== newProps.blockDecorations.size) + return true; - let blockDecorationsChanged = false + let blockDecorationsChanged = false; oldProps.blockDecorations.forEach((oldDecorations, screenLineId) => { if (!blockDecorationsChanged) { - const newDecorations = newProps.blockDecorations.get(screenLineId) - blockDecorationsChanged = (newDecorations == null || !arraysEqual(oldDecorations, newDecorations)) + const newDecorations = newProps.blockDecorations.get(screenLineId); + blockDecorationsChanged = + newDecorations == null || + !arraysEqual(oldDecorations, newDecorations); } - }) - if (blockDecorationsChanged) return true + }); + if (blockDecorationsChanged) return true; newProps.blockDecorations.forEach((newDecorations, screenLineId) => { if (!blockDecorationsChanged) { - const oldDecorations = oldProps.blockDecorations.get(screenLineId) - blockDecorationsChanged = (oldDecorations == null) + const oldDecorations = oldProps.blockDecorations.get(screenLineId); + blockDecorationsChanged = oldDecorations == null; } - }) - if (blockDecorationsChanged) return true + }); + if (blockDecorationsChanged) return true; } else if (oldProps.blockDecorations) { - return true + return true; } else if (newProps.blockDecorations) { - return true + return true; } - if (oldProps.textDecorations.length !== newProps.textDecorations.length) return true + if (oldProps.textDecorations.length !== newProps.textDecorations.length) + return true; for (let i = 0; i < oldProps.textDecorations.length; i++) { - if (!textDecorationsEqual(oldProps.textDecorations[i], newProps.textDecorations[i])) return true + if ( + !textDecorationsEqual( + oldProps.textDecorations[i], + newProps.textDecorations[i] + ) + ) + return true; } - return false + return false; } } class LineComponent { - constructor (props) { - const {nodePool, screenRow, screenLine, lineComponentsByScreenLineId, offScreen} = props - this.props = props - this.element = nodePool.getElement('DIV', this.buildClassName(), null) - this.element.dataset.screenRow = screenRow - this.textNodes = [] + constructor(props) { + const { + nodePool, + screenRow, + screenLine, + lineComponentsByScreenLineId, + offScreen + } = props; + this.props = props; + this.element = nodePool.getElement('DIV', this.buildClassName(), null); + this.element.dataset.screenRow = screenRow; + this.textNodes = []; if (offScreen) { - this.element.style.position = 'absolute' - this.element.style.visibility = 'hidden' - this.element.dataset.offScreen = true + this.element.style.position = 'absolute'; + this.element.style.visibility = 'hidden'; + this.element.dataset.offScreen = true; } - this.appendContents() - lineComponentsByScreenLineId.set(screenLine.id, this) + this.appendContents(); + lineComponentsByScreenLineId.set(screenLine.id, this); } - update (newProps) { + update(newProps) { if (this.props.lineDecoration !== newProps.lineDecoration) { - this.props.lineDecoration = newProps.lineDecoration - this.element.className = this.buildClassName() + this.props.lineDecoration = newProps.lineDecoration; + this.element.className = this.buildClassName(); } if (this.props.screenRow !== newProps.screenRow) { - this.props.screenRow = newProps.screenRow - this.element.dataset.screenRow = newProps.screenRow + this.props.screenRow = newProps.screenRow; + this.element.dataset.screenRow = newProps.screenRow; } - if (!textDecorationsEqual(this.props.textDecorations, newProps.textDecorations)) { - this.props.textDecorations = newProps.textDecorations - this.element.firstChild.remove() - this.appendContents() + if ( + !textDecorationsEqual( + this.props.textDecorations, + newProps.textDecorations + ) + ) { + this.props.textDecorations = newProps.textDecorations; + this.element.firstChild.remove(); + this.appendContents(); } } - destroy () { - const {nodePool, lineComponentsByScreenLineId, screenLine} = this.props + destroy() { + const { nodePool, lineComponentsByScreenLineId, screenLine } = this.props; if (lineComponentsByScreenLineId.get(screenLine.id) === this) { - lineComponentsByScreenLineId.delete(screenLine.id) + lineComponentsByScreenLineId.delete(screenLine.id); } - this.element.remove() - nodePool.release(this.element) + this.element.remove(); + nodePool.release(this.element); } - appendContents () { - const {displayLayer, nodePool, screenLine, textDecorations} = this.props + appendContents() { + const { displayLayer, nodePool, screenLine, textDecorations } = this.props; - this.textNodes.length = 0 + this.textNodes.length = 0; - const {lineText, tags} = screenLine - let openScopeNode = nodePool.getElement('SPAN', null, null) - this.element.appendChild(openScopeNode) + const { lineText, tags } = screenLine; + let openScopeNode = nodePool.getElement('SPAN', null, null); + this.element.appendChild(openScopeNode); - let decorationIndex = 0 - let column = 0 - let activeClassName = null - let activeStyle = null - let nextDecoration = textDecorations ? textDecorations[decorationIndex] : null + let decorationIndex = 0; + let column = 0; + let activeClassName = null; + let activeStyle = null; + let nextDecoration = textDecorations + ? textDecorations[decorationIndex] + : null; if (nextDecoration && nextDecoration.column === 0) { - column = nextDecoration.column - activeClassName = nextDecoration.className - activeStyle = nextDecoration.style - nextDecoration = textDecorations[++decorationIndex] + column = nextDecoration.column; + activeClassName = nextDecoration.className; + activeStyle = nextDecoration.style; + nextDecoration = textDecorations[++decorationIndex]; } for (let i = 0; i < tags.length; i++) { - const tag = tags[i] + const tag = tags[i]; if (tag !== 0) { if (displayLayer.isCloseTag(tag)) { - openScopeNode = openScopeNode.parentElement + openScopeNode = openScopeNode.parentElement; } else if (displayLayer.isOpenTag(tag)) { - const newScopeNode = nodePool.getElement('SPAN', displayLayer.classNameForTag(tag), null) - openScopeNode.appendChild(newScopeNode) - openScopeNode = newScopeNode + const newScopeNode = nodePool.getElement( + 'SPAN', + displayLayer.classNameForTag(tag), + null + ); + openScopeNode.appendChild(newScopeNode); + openScopeNode = newScopeNode; } else { - const nextTokenColumn = column + tag + const nextTokenColumn = column + tag; while (nextDecoration && nextDecoration.column <= nextTokenColumn) { - const text = lineText.substring(column, nextDecoration.column) - this.appendTextNode(openScopeNode, text, activeClassName, activeStyle) - column = nextDecoration.column - activeClassName = nextDecoration.className - activeStyle = nextDecoration.style - nextDecoration = textDecorations[++decorationIndex] + const text = lineText.substring(column, nextDecoration.column); + this.appendTextNode( + openScopeNode, + text, + activeClassName, + activeStyle + ); + column = nextDecoration.column; + activeClassName = nextDecoration.className; + activeStyle = nextDecoration.style; + nextDecoration = textDecorations[++decorationIndex]; } if (column < nextTokenColumn) { - const text = lineText.substring(column, nextTokenColumn) - this.appendTextNode(openScopeNode, text, activeClassName, activeStyle) - column = nextTokenColumn + const text = lineText.substring(column, nextTokenColumn); + this.appendTextNode( + openScopeNode, + text, + activeClassName, + activeStyle + ); + column = nextTokenColumn; } } } } if (column === 0) { - const textNode = nodePool.getTextNode(' ') - this.element.appendChild(textNode) - this.textNodes.push(textNode) + const textNode = nodePool.getTextNode(' '); + this.element.appendChild(textNode); + this.textNodes.push(textNode); } if (lineText.endsWith(displayLayer.foldCharacter)) { // Insert a zero-width non-breaking whitespace, so that LinesYardstick can // take the fold-marker::after pseudo-element into account during // measurements when such marker is the last character on the line. - const textNode = nodePool.getTextNode(ZERO_WIDTH_NBSP_CHARACTER) - this.element.appendChild(textNode) - this.textNodes.push(textNode) + const textNode = nodePool.getTextNode(ZERO_WIDTH_NBSP_CHARACTER); + this.element.appendChild(textNode); + this.textNodes.push(textNode); } } - appendTextNode (openScopeNode, text, activeClassName, activeStyle) { - const {nodePool} = this.props + appendTextNode(openScopeNode, text, activeClassName, activeStyle) { + const { nodePool } = this.props; if (activeClassName || activeStyle) { - const decorationNode = nodePool.getElement('SPAN', activeClassName, activeStyle) - openScopeNode.appendChild(decorationNode) - openScopeNode = decorationNode + const decorationNode = nodePool.getElement( + 'SPAN', + activeClassName, + activeStyle + ); + openScopeNode.appendChild(decorationNode); + openScopeNode = decorationNode; } - const textNode = nodePool.getTextNode(text) - openScopeNode.appendChild(textNode) - this.textNodes.push(textNode) + const textNode = nodePool.getTextNode(text); + openScopeNode.appendChild(textNode); + this.textNodes.push(textNode); } - buildClassName () { - const {lineDecoration} = this.props - let className = 'line' - if (lineDecoration != null) className = className + ' ' + lineDecoration - return className + buildClassName() { + const { lineDecoration } = this.props; + let className = 'line'; + if (lineDecoration != null) className = className + ' ' + lineDecoration; + return className; } } class HighlightsComponent { - constructor (props) { - this.props = {} - this.element = document.createElement('div') - this.element.className = 'highlights' - this.element.style.contain = 'strict' - this.element.style.position = 'absolute' - this.element.style.overflow = 'hidden' - this.element.style.userSelect = 'none' - this.highlightComponentsByKey = new Map() - this.update(props) + constructor(props) { + this.props = {}; + this.element = document.createElement('div'); + this.element.className = 'highlights'; + this.element.style.contain = 'strict'; + this.element.style.position = 'absolute'; + this.element.style.overflow = 'hidden'; + this.element.style.userSelect = 'none'; + this.highlightComponentsByKey = new Map(); + this.update(props); } - destroy () { - this.highlightComponentsByKey.forEach((highlightComponent) => { - highlightComponent.destroy() - }) - this.highlightComponentsByKey.clear() + destroy() { + this.highlightComponentsByKey.forEach(highlightComponent => { + highlightComponent.destroy(); + }); + this.highlightComponentsByKey.clear(); } - update (newProps) { + update(newProps) { if (this.shouldUpdate(newProps)) { - this.props = newProps - const {height, width, lineHeight, highlightDecorations} = this.props + this.props = newProps; + const { height, width, lineHeight, highlightDecorations } = this.props; - this.element.style.height = height + 'px' - this.element.style.width = width + 'px' + this.element.style.height = height + 'px'; + this.element.style.width = width + 'px'; - const visibleHighlightDecorations = new Set() + const visibleHighlightDecorations = new Set(); if (highlightDecorations) { for (let i = 0; i < highlightDecorations.length; i++) { - const highlightDecoration = highlightDecorations[i] - const highlightProps = Object.assign({lineHeight}, highlightDecorations[i]) + const highlightDecoration = highlightDecorations[i]; + const highlightProps = Object.assign( + { lineHeight }, + highlightDecorations[i] + ); - let highlightComponent = this.highlightComponentsByKey.get(highlightDecoration.key) + let highlightComponent = this.highlightComponentsByKey.get( + highlightDecoration.key + ); if (highlightComponent) { - highlightComponent.update(highlightProps) + highlightComponent.update(highlightProps); } else { - highlightComponent = new HighlightComponent(highlightProps) - this.element.appendChild(highlightComponent.element) - this.highlightComponentsByKey.set(highlightDecoration.key, highlightComponent) + highlightComponent = new HighlightComponent(highlightProps); + this.element.appendChild(highlightComponent.element); + this.highlightComponentsByKey.set( + highlightDecoration.key, + highlightComponent + ); } - highlightDecorations[i].flashRequested = false - visibleHighlightDecorations.add(highlightDecoration.key) + highlightDecorations[i].flashRequested = false; + visibleHighlightDecorations.add(highlightDecoration.key); } } this.highlightComponentsByKey.forEach((highlightComponent, key) => { if (!visibleHighlightDecorations.has(key)) { - highlightComponent.destroy() - this.highlightComponentsByKey.delete(key) + highlightComponent.destroy(); + this.highlightComponentsByKey.delete(key); } - }) + }); } } - shouldUpdate (newProps) { - const oldProps = this.props + shouldUpdate(newProps) { + const oldProps = this.props; - if (!newProps.hasInitialMeasurements) return false + if (!newProps.hasInitialMeasurements) return false; - if (oldProps.width !== newProps.width) return true - if (oldProps.height !== newProps.height) return true - if (oldProps.lineHeight !== newProps.lineHeight) return true - if (!oldProps.highlightDecorations && newProps.highlightDecorations) return true - if (oldProps.highlightDecorations && !newProps.highlightDecorations) return true + if (oldProps.width !== newProps.width) return true; + if (oldProps.height !== newProps.height) return true; + if (oldProps.lineHeight !== newProps.lineHeight) return true; + if (!oldProps.highlightDecorations && newProps.highlightDecorations) + return true; + if (oldProps.highlightDecorations && !newProps.highlightDecorations) + return true; if (oldProps.highlightDecorations && newProps.highlightDecorations) { - if (oldProps.highlightDecorations.length !== newProps.highlightDecorations.length) return true + if ( + oldProps.highlightDecorations.length !== + newProps.highlightDecorations.length + ) + return true; - for (let i = 0, length = oldProps.highlightDecorations.length; i < length; i++) { - const oldHighlight = oldProps.highlightDecorations[i] - const newHighlight = newProps.highlightDecorations[i] - if (oldHighlight.className !== newHighlight.className) return true - if (newHighlight.flashRequested) return true - if (oldHighlight.startPixelTop !== newHighlight.startPixelTop) return true - if (oldHighlight.startPixelLeft !== newHighlight.startPixelLeft) return true - if (oldHighlight.endPixelTop !== newHighlight.endPixelTop) return true - if (oldHighlight.endPixelLeft !== newHighlight.endPixelLeft) return true - if (!oldHighlight.screenRange.isEqual(newHighlight.screenRange)) return true + for ( + let i = 0, length = oldProps.highlightDecorations.length; + i < length; + i++ + ) { + const oldHighlight = oldProps.highlightDecorations[i]; + const newHighlight = newProps.highlightDecorations[i]; + if (oldHighlight.className !== newHighlight.className) return true; + if (newHighlight.flashRequested) return true; + if (oldHighlight.startPixelTop !== newHighlight.startPixelTop) + return true; + if (oldHighlight.startPixelLeft !== newHighlight.startPixelLeft) + return true; + if (oldHighlight.endPixelTop !== newHighlight.endPixelTop) return true; + if (oldHighlight.endPixelLeft !== newHighlight.endPixelLeft) + return true; + if (!oldHighlight.screenRange.isEqual(newHighlight.screenRange)) + return true; } } } } class HighlightComponent { - constructor (props) { - this.props = props - etch.initialize(this) - if (this.props.flashRequested) this.performFlash() + constructor(props) { + this.props = props; + etch.initialize(this); + if (this.props.flashRequested) this.performFlash(); } - destroy () { + destroy() { if (this.timeoutsByClassName) { - this.timeoutsByClassName.forEach((timeout) => { - window.clearTimeout(timeout) - }) - this.timeoutsByClassName.clear() + this.timeoutsByClassName.forEach(timeout => { + window.clearTimeout(timeout); + }); + this.timeoutsByClassName.clear(); } - return etch.destroy(this) + return etch.destroy(this); } - update (newProps) { - this.props = newProps - etch.updateSync(this) - if (newProps.flashRequested) this.performFlash() + update(newProps) { + this.props = newProps; + etch.updateSync(this); + if (newProps.flashRequested) this.performFlash(); } - performFlash () { - const {flashClass, flashDuration} = this.props - if (!this.timeoutsByClassName) this.timeoutsByClassName = new Map() + performFlash() { + const { flashClass, flashDuration } = this.props; + if (!this.timeoutsByClassName) this.timeoutsByClassName = new Map(); // If a flash of this class is already in progress, clear it early and // flash again on the next frame to ensure CSS transitions apply to the // second flash. if (this.timeoutsByClassName.has(flashClass)) { - window.clearTimeout(this.timeoutsByClassName.get(flashClass)) - this.timeoutsByClassName.delete(flashClass) - this.element.classList.remove(flashClass) - requestAnimationFrame(() => this.performFlash()) + window.clearTimeout(this.timeoutsByClassName.get(flashClass)); + this.timeoutsByClassName.delete(flashClass); + this.element.classList.remove(flashClass); + requestAnimationFrame(() => this.performFlash()); } else { - this.element.classList.add(flashClass) - this.timeoutsByClassName.set(flashClass, window.setTimeout(() => { - this.element.classList.remove(flashClass) - }, flashDuration)) + this.element.classList.add(flashClass); + this.timeoutsByClassName.set( + flashClass, + window.setTimeout(() => { + this.element.classList.remove(flashClass); + }, flashDuration) + ); } } - render () { + render() { const { - className, screenRange, lineHeight, - startPixelTop, startPixelLeft, endPixelTop, endPixelLeft - } = this.props - const regionClassName = 'region ' + className + className, + screenRange, + lineHeight, + startPixelTop, + startPixelLeft, + endPixelTop, + endPixelLeft + } = this.props; + const regionClassName = 'region ' + className; - let children + let children; if (screenRange.start.row === screenRange.end.row) { children = $.div({ className: regionClassName, @@ -4224,287 +4913,308 @@ class HighlightComponent { width: endPixelLeft - startPixelLeft + 'px', height: lineHeight + 'px' } - }) + }); } else { - children = [] - children.push($.div({ - className: regionClassName, - style: { - position: 'absolute', - boxSizing: 'border-box', - top: startPixelTop + 'px', - left: startPixelLeft + 'px', - right: 0, - height: lineHeight + 'px' - } - })) - - if (screenRange.end.row - screenRange.start.row > 1) { - children.push($.div({ + children = []; + children.push( + $.div({ className: regionClassName, style: { position: 'absolute', boxSizing: 'border-box', - top: startPixelTop + lineHeight + 'px', - left: 0, + top: startPixelTop + 'px', + left: startPixelLeft + 'px', right: 0, - height: endPixelTop - startPixelTop - (lineHeight * 2) + 'px' + height: lineHeight + 'px' } - })) + }) + ); + + if (screenRange.end.row - screenRange.start.row > 1) { + children.push( + $.div({ + className: regionClassName, + style: { + position: 'absolute', + boxSizing: 'border-box', + top: startPixelTop + lineHeight + 'px', + left: 0, + right: 0, + height: endPixelTop - startPixelTop - lineHeight * 2 + 'px' + } + }) + ); } if (endPixelLeft > 0) { - children.push($.div({ - className: regionClassName, - style: { - position: 'absolute', - boxSizing: 'border-box', - top: endPixelTop - lineHeight + 'px', - left: 0, - width: endPixelLeft + 'px', - height: lineHeight + 'px' - } - })) + children.push( + $.div({ + className: regionClassName, + style: { + position: 'absolute', + boxSizing: 'border-box', + top: endPixelTop - lineHeight + 'px', + left: 0, + width: endPixelLeft + 'px', + height: lineHeight + 'px' + } + }) + ); } } - return $.div({className: 'highlight ' + className}, children) + return $.div({ className: 'highlight ' + className }, children); } } class OverlayComponent { - constructor (props) { - this.props = props - this.element = document.createElement('atom-overlay') - if (this.props.className != null) this.element.classList.add(this.props.className) - this.element.appendChild(this.props.element) - this.element.style.position = 'fixed' - this.element.style.zIndex = 4 - this.element.style.top = (this.props.pixelTop || 0) + 'px' - this.element.style.left = (this.props.pixelLeft || 0) + 'px' - this.currentContentRect = null + constructor(props) { + this.props = props; + this.element = document.createElement('atom-overlay'); + if (this.props.className != null) + this.element.classList.add(this.props.className); + this.element.appendChild(this.props.element); + this.element.style.position = 'fixed'; + this.element.style.zIndex = 4; + this.element.style.top = (this.props.pixelTop || 0) + 'px'; + this.element.style.left = (this.props.pixelLeft || 0) + 'px'; + this.currentContentRect = null; // Synchronous DOM updates in response to resize events might trigger a // "loop limit exceeded" error. We disconnect the observer before // potentially mutating the DOM, and then reconnect it on the next tick. // Note: ResizeObserver calls its callback when .observe is called - this.resizeObserver = new ResizeObserver((entries) => { - const {contentRect} = entries[0] + this.resizeObserver = new ResizeObserver(entries => { + const { contentRect } = entries[0]; if ( this.currentContentRect && (this.currentContentRect.width !== contentRect.width || this.currentContentRect.height !== contentRect.height) ) { - this.resizeObserver.disconnect() - this.props.didResize(this) - process.nextTick(() => { this.resizeObserver.observe(this.props.element) }) + this.resizeObserver.disconnect(); + this.props.didResize(this); + process.nextTick(() => { + this.resizeObserver.observe(this.props.element); + }); } - this.currentContentRect = contentRect - }) - this.didAttach() - this.props.overlayComponents.add(this) + this.currentContentRect = contentRect; + }); + this.didAttach(); + this.props.overlayComponents.add(this); } - destroy () { - this.props.overlayComponents.delete(this) - this.didDetach() + destroy() { + this.props.overlayComponents.delete(this); + this.didDetach(); } - getNextUpdatePromise () { + getNextUpdatePromise() { if (!this.nextUpdatePromise) { - this.nextUpdatePromise = new Promise((resolve) => { + this.nextUpdatePromise = new Promise(resolve => { this.resolveNextUpdatePromise = () => { - this.nextUpdatePromise = null - this.resolveNextUpdatePromise = null - resolve() - } - }) + this.nextUpdatePromise = null; + this.resolveNextUpdatePromise = null; + resolve(); + }; + }); } - return this.nextUpdatePromise + return this.nextUpdatePromise; } - update (newProps) { - const oldProps = this.props - this.props = Object.assign({}, oldProps, newProps) - if (this.props.pixelTop != null) this.element.style.top = this.props.pixelTop + 'px' - if (this.props.pixelLeft != null) this.element.style.left = this.props.pixelLeft + 'px' + update(newProps) { + const oldProps = this.props; + this.props = Object.assign({}, oldProps, newProps); + if (this.props.pixelTop != null) + this.element.style.top = this.props.pixelTop + 'px'; + if (this.props.pixelLeft != null) + this.element.style.left = this.props.pixelLeft + 'px'; if (newProps.className !== oldProps.className) { - if (oldProps.className != null) this.element.classList.remove(oldProps.className) - if (newProps.className != null) this.element.classList.add(newProps.className) + if (oldProps.className != null) + this.element.classList.remove(oldProps.className); + if (newProps.className != null) + this.element.classList.add(newProps.className); } - if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise() + if (this.resolveNextUpdatePromise) this.resolveNextUpdatePromise(); } - didAttach () { - this.resizeObserver.observe(this.props.element) + didAttach() { + this.resizeObserver.observe(this.props.element); } - didDetach () { - this.resizeObserver.disconnect() + didDetach() { + this.resizeObserver.disconnect(); } } -let rangeForMeasurement -function clientRectForRange (textNode, startIndex, endIndex) { - if (!rangeForMeasurement) rangeForMeasurement = document.createRange() - rangeForMeasurement.setStart(textNode, startIndex) - rangeForMeasurement.setEnd(textNode, endIndex) - return rangeForMeasurement.getBoundingClientRect() +let rangeForMeasurement; +function clientRectForRange(textNode, startIndex, endIndex) { + if (!rangeForMeasurement) rangeForMeasurement = document.createRange(); + rangeForMeasurement.setStart(textNode, startIndex); + rangeForMeasurement.setEnd(textNode, endIndex); + return rangeForMeasurement.getBoundingClientRect(); } -function textDecorationsEqual (oldDecorations, newDecorations) { - if (!oldDecorations && newDecorations) return false - if (oldDecorations && !newDecorations) return false +function textDecorationsEqual(oldDecorations, newDecorations) { + if (!oldDecorations && newDecorations) return false; + if (oldDecorations && !newDecorations) return false; if (oldDecorations && newDecorations) { - if (oldDecorations.length !== newDecorations.length) return false + if (oldDecorations.length !== newDecorations.length) return false; for (let j = 0; j < oldDecorations.length; j++) { - if (oldDecorations[j].column !== newDecorations[j].column) return false - if (oldDecorations[j].className !== newDecorations[j].className) return false - if (!objectsEqual(oldDecorations[j].style, newDecorations[j].style)) return false + if (oldDecorations[j].column !== newDecorations[j].column) return false; + if (oldDecorations[j].className !== newDecorations[j].className) + return false; + if (!objectsEqual(oldDecorations[j].style, newDecorations[j].style)) + return false; } } - return true + return true; } -function arraysEqual (a, b) { - if (a.length !== b.length) return false +function arraysEqual(a, b) { + if (a.length !== b.length) return false; for (let i = 0, length = a.length; i < length; i++) { - if (a[i] !== b[i]) return false + if (a[i] !== b[i]) return false; } - return true + return true; } -function objectsEqual (a, b) { - if (!a && b) return false - if (a && !b) return false +function objectsEqual(a, b) { + if (!a && b) return false; + if (a && !b) return false; if (a && b) { for (const key in a) { - if (a[key] !== b[key]) return false + if (a[key] !== b[key]) return false; } for (const key in b) { - if (a[key] !== b[key]) return false + if (a[key] !== b[key]) return false; } } - return true + return true; } -function constrainRangeToRows (range, startRow, endRow) { +function constrainRangeToRows(range, startRow, endRow) { if (range.start.row < startRow || range.end.row >= endRow) { - range = range.copy() + range = range.copy(); if (range.start.row < startRow) { - range.start.row = startRow - range.start.column = 0 + range.start.row = startRow; + range.start.column = 0; } if (range.end.row >= endRow) { - range.end.row = endRow - range.end.column = 0 + range.end.row = endRow; + range.end.column = 0; } } - return range + return range; } -function debounce (fn, wait) { - let timestamp, timeout +function debounce(fn, wait) { + let timestamp, timeout; - function later () { - const last = Date.now() - timestamp + function later() { + const last = Date.now() - timestamp; if (last < wait && last >= 0) { - timeout = setTimeout(later, wait - last) + timeout = setTimeout(later, wait - last); } else { - timeout = null - fn() + timeout = null; + fn(); } } - return function () { - timestamp = Date.now() - if (!timeout) timeout = setTimeout(later, wait) - } + return function() { + timestamp = Date.now(); + if (!timeout) timeout = setTimeout(later, wait); + }; } class NodePool { - constructor () { - this.elementsByType = {} - this.textNodes = [] + constructor() { + this.elementsByType = {}; + this.textNodes = []; } - getElement (type, className, style) { - var element - var elementsByDepth = this.elementsByType[type] + getElement(type, className, style) { + var element; + var elementsByDepth = this.elementsByType[type]; if (elementsByDepth) { while (elementsByDepth.length > 0) { - var elements = elementsByDepth[elementsByDepth.length - 1] + var elements = elementsByDepth[elementsByDepth.length - 1]; if (elements && elements.length > 0) { - element = elements.pop() - if (elements.length === 0) elementsByDepth.pop() - break + element = elements.pop(); + if (elements.length === 0) elementsByDepth.pop(); + break; } else { - elementsByDepth.pop() + elementsByDepth.pop(); } } } if (element) { - element.className = className || '' + element.className = className || ''; element.attributeStyleMap.forEach((value, key) => { - if (!style || style[key] == null) element.style[key] = '' - }) - if (style) Object.assign(element.style, style) - for (const key in element.dataset) delete element.dataset[key] - while (element.firstChild) element.firstChild.remove() - return element + if (!style || style[key] == null) element.style[key] = ''; + }); + if (style) Object.assign(element.style, style); + for (const key in element.dataset) delete element.dataset[key]; + while (element.firstChild) element.firstChild.remove(); + return element; } else { - var newElement = document.createElement(type) - if (className) newElement.className = className - if (style) Object.assign(newElement.style, style) - return newElement + var newElement = document.createElement(type); + if (className) newElement.className = className; + if (style) Object.assign(newElement.style, style); + return newElement; } } - getTextNode (text) { + getTextNode(text) { if (this.textNodes.length > 0) { - var node = this.textNodes.pop() - node.textContent = text - return node + var node = this.textNodes.pop(); + node.textContent = text; + return node; } else { - return document.createTextNode(text) + return document.createTextNode(text); } } - release (node, depth = 0) { - var {nodeName} = node + release(node, depth = 0) { + var { nodeName } = node; if (nodeName === '#text') { - this.textNodes.push(node) + this.textNodes.push(node); } else { - var elementsByDepth = this.elementsByType[nodeName] + var elementsByDepth = this.elementsByType[nodeName]; if (!elementsByDepth) { - elementsByDepth = [] - this.elementsByType[nodeName] = elementsByDepth + elementsByDepth = []; + this.elementsByType[nodeName] = elementsByDepth; } - var elements = elementsByDepth[depth] + var elements = elementsByDepth[depth]; if (!elements) { - elements = [] - elementsByDepth[depth] = elements + elements = []; + elementsByDepth[depth] = elements; } - elements.push(node) + elements.push(node); for (var i = 0; i < node.childNodes.length; i++) { - this.release(node.childNodes[i], depth + 1) + this.release(node.childNodes[i], depth + 1); } } } } -function roundToPhysicalPixelBoundary (virtualPixelPosition) { - const virtualPixelsPerPhysicalPixel = (1 / window.devicePixelRatio) - return Math.round(virtualPixelPosition / virtualPixelsPerPhysicalPixel) * virtualPixelsPerPhysicalPixel +function roundToPhysicalPixelBoundary(virtualPixelPosition) { + const virtualPixelsPerPhysicalPixel = 1 / window.devicePixelRatio; + return ( + Math.round(virtualPixelPosition / virtualPixelsPerPhysicalPixel) * + virtualPixelsPerPhysicalPixel + ); } -function ceilToPhysicalPixelBoundary (virtualPixelPosition) { - const virtualPixelsPerPhysicalPixel = (1 / window.devicePixelRatio) - return Math.ceil(virtualPixelPosition / virtualPixelsPerPhysicalPixel) * virtualPixelsPerPhysicalPixel +function ceilToPhysicalPixelBoundary(virtualPixelPosition) { + const virtualPixelsPerPhysicalPixel = 1 / window.devicePixelRatio; + return ( + Math.ceil(virtualPixelPosition / virtualPixelsPerPhysicalPixel) * + virtualPixelsPerPhysicalPixel + ); } diff --git a/src/text-editor-element.js b/src/text-editor-element.js index 926f7af44..419767bd0 100644 --- a/src/text-editor-element.js +++ b/src/text-editor-element.js @@ -1,67 +1,69 @@ -const {Emitter, Range} = require('atom') -const Grim = require('grim') -const TextEditorComponent = require('./text-editor-component') -const dedent = require('dedent') +const { Emitter, Range } = require('atom'); +const Grim = require('grim'); +const TextEditorComponent = require('./text-editor-component'); +const dedent = require('dedent'); class TextEditorElement extends HTMLElement { - initialize (component) { - this.component = component - return this + initialize(component) { + this.component = component; + return this; } - get shadowRoot () { + get shadowRoot() { Grim.deprecate(dedent` The contents of \`atom-text-editor\` elements are no longer encapsulated within a shadow DOM boundary. Please, stop using \`shadowRoot\` and access the editor contents directly instead. - `) + `); - return this + return this; } - get rootElement () { + get rootElement() { Grim.deprecate(dedent` The contents of \`atom-text-editor\` elements are no longer encapsulated within a shadow DOM boundary. Please, stop using \`rootElement\` and access the editor contents directly instead. - `) + `); - return this + return this; } - createdCallback () { - this.emitter = new Emitter() - this.initialText = this.textContent - if (this.tabIndex == null) this.tabIndex = -1 - this.addEventListener('focus', (event) => this.getComponent().didFocus(event)) - this.addEventListener('blur', (event) => this.getComponent().didBlur(event)) + createdCallback() { + this.emitter = new Emitter(); + this.initialText = this.textContent; + if (this.tabIndex == null) this.tabIndex = -1; + this.addEventListener('focus', event => + this.getComponent().didFocus(event) + ); + this.addEventListener('blur', event => this.getComponent().didBlur(event)); } - attachedCallback () { - this.getComponent().didAttach() - this.emitter.emit('did-attach') + attachedCallback() { + this.getComponent().didAttach(); + this.emitter.emit('did-attach'); } - detachedCallback () { - this.emitter.emit('did-detach') - this.getComponent().didDetach() + detachedCallback() { + this.emitter.emit('did-detach'); + this.getComponent().didDetach(); } - attributeChangedCallback (name, oldValue, newValue) { + attributeChangedCallback(name, oldValue, newValue) { if (this.component) { switch (name) { case 'mini': - this.getModel().update({mini: newValue != null}) - break + this.getModel().update({ mini: newValue != null }); + break; case 'placeholder-text': - this.getModel().update({placeholderText: newValue}) - break + this.getModel().update({ placeholderText: newValue }); + break; case 'gutter-hidden': - this.getModel().update({lineNumberGutterVisible: newValue == null}) - break + this.getModel().update({ lineNumberGutterVisible: newValue == null }); + break; case 'readonly': - this.getModel().update({readOnly: newValue != null}) - break + this.getModel().update({ readOnly: newValue != null }); + break; } } } @@ -73,146 +75,149 @@ class TextEditorElement extends HTMLElement { // be sure this change has been flushed to the DOM. // // Returns a {Promise}. - getNextUpdatePromise () { - return this.getComponent().getNextUpdatePromise() + getNextUpdatePromise() { + return this.getComponent().getNextUpdatePromise(); } - getModel () { - return this.getComponent().props.model + getModel() { + return this.getComponent().props.model; } - setModel (model) { - this.getComponent().update({model}) - this.updateModelFromAttributes() + setModel(model) { + this.getComponent().update({ model }); + this.updateModelFromAttributes(); } - updateModelFromAttributes () { - const props = {mini: this.hasAttribute('mini')} - if (this.hasAttribute('placeholder-text')) props.placeholderText = this.getAttribute('placeholder-text') - if (this.hasAttribute('gutter-hidden')) props.lineNumberGutterVisible = false + updateModelFromAttributes() { + const props = { mini: this.hasAttribute('mini') }; + if (this.hasAttribute('placeholder-text')) + props.placeholderText = this.getAttribute('placeholder-text'); + if (this.hasAttribute('gutter-hidden')) + props.lineNumberGutterVisible = false; - this.getModel().update(props) - if (this.initialText) this.getModel().setText(this.initialText) + this.getModel().update(props); + if (this.initialText) this.getModel().setText(this.initialText); } - onDidAttach (callback) { - return this.emitter.on('did-attach', callback) + onDidAttach(callback) { + return this.emitter.on('did-attach', callback); } - onDidDetach (callback) { - return this.emitter.on('did-detach', callback) + onDidDetach(callback) { + return this.emitter.on('did-detach', callback); } - measureDimensions () { - this.getComponent().measureDimensions() + measureDimensions() { + this.getComponent().measureDimensions(); } - setWidth (width) { - this.style.width = this.getComponent().getGutterContainerWidth() + width + 'px' + setWidth(width) { + this.style.width = + this.getComponent().getGutterContainerWidth() + width + 'px'; } - getWidth () { - return this.getComponent().getScrollContainerWidth() + getWidth() { + return this.getComponent().getScrollContainerWidth(); } - setHeight (height) { - this.style.height = height + 'px' + setHeight(height) { + this.style.height = height + 'px'; } - getHeight () { - return this.getComponent().getScrollContainerHeight() + getHeight() { + return this.getComponent().getScrollContainerHeight(); } - onDidChangeScrollLeft (callback) { - return this.emitter.on('did-change-scroll-left', callback) + onDidChangeScrollLeft(callback) { + return this.emitter.on('did-change-scroll-left', callback); } - onDidChangeScrollTop (callback) { - return this.emitter.on('did-change-scroll-top', callback) + onDidChangeScrollTop(callback) { + return this.emitter.on('did-change-scroll-top', callback); } // Deprecated: get the width of an `x` character displayed in this element. // // Returns a {Number} of pixels. - getDefaultCharacterWidth () { - return this.getComponent().getBaseCharacterWidth() + getDefaultCharacterWidth() { + return this.getComponent().getBaseCharacterWidth(); } // Extended: get the width of an `x` character displayed in this element. // // Returns a {Number} of pixels. - getBaseCharacterWidth () { - return this.getComponent().getBaseCharacterWidth() + getBaseCharacterWidth() { + return this.getComponent().getBaseCharacterWidth(); } - getMaxScrollTop () { - return this.getComponent().getMaxScrollTop() + getMaxScrollTop() { + return this.getComponent().getMaxScrollTop(); } - getScrollHeight () { - return this.getComponent().getScrollHeight() + getScrollHeight() { + return this.getComponent().getScrollHeight(); } - getScrollWidth () { - return this.getComponent().getScrollWidth() + getScrollWidth() { + return this.getComponent().getScrollWidth(); } - getVerticalScrollbarWidth () { - return this.getComponent().getVerticalScrollbarWidth() + getVerticalScrollbarWidth() { + return this.getComponent().getVerticalScrollbarWidth(); } - getHorizontalScrollbarHeight () { - return this.getComponent().getHorizontalScrollbarHeight() + getHorizontalScrollbarHeight() { + return this.getComponent().getHorizontalScrollbarHeight(); } - getScrollTop () { - return this.getComponent().getScrollTop() + getScrollTop() { + return this.getComponent().getScrollTop(); } - setScrollTop (scrollTop) { - const component = this.getComponent() - component.setScrollTop(scrollTop) - component.scheduleUpdate() + setScrollTop(scrollTop) { + const component = this.getComponent(); + component.setScrollTop(scrollTop); + component.scheduleUpdate(); } - getScrollBottom () { - return this.getComponent().getScrollBottom() + getScrollBottom() { + return this.getComponent().getScrollBottom(); } - setScrollBottom (scrollBottom) { - return this.getComponent().setScrollBottom(scrollBottom) + setScrollBottom(scrollBottom) { + return this.getComponent().setScrollBottom(scrollBottom); } - getScrollLeft () { - return this.getComponent().getScrollLeft() + getScrollLeft() { + return this.getComponent().getScrollLeft(); } - setScrollLeft (scrollLeft) { - const component = this.getComponent() - component.setScrollLeft(scrollLeft) - component.scheduleUpdate() + setScrollLeft(scrollLeft) { + const component = this.getComponent(); + component.setScrollLeft(scrollLeft); + component.scheduleUpdate(); } - getScrollRight () { - return this.getComponent().getScrollRight() + getScrollRight() { + return this.getComponent().getScrollRight(); } - setScrollRight (scrollRight) { - return this.getComponent().setScrollRight(scrollRight) + setScrollRight(scrollRight) { + return this.getComponent().setScrollRight(scrollRight); } // Essential: Scrolls the editor to the top. - scrollToTop () { - this.setScrollTop(0) + scrollToTop() { + this.setScrollTop(0); } // Essential: Scrolls the editor to the bottom. - scrollToBottom () { - this.setScrollTop(Infinity) + scrollToBottom() { + this.setScrollTop(Infinity); } - hasFocus () { - return this.getComponent().focused + hasFocus() { + return this.getComponent().focused; } // Extended: Converts a buffer position to a pixel position. @@ -226,9 +231,11 @@ class TextEditorElement extends HTMLElement { // // Returns an {Object} with two values: `top` and `left`, representing the // pixel position. - pixelPositionForBufferPosition (bufferPosition) { - const screenPosition = this.getModel().screenPositionForBufferPosition(bufferPosition) - return this.getComponent().pixelPositionForScreenPosition(screenPosition) + pixelPositionForBufferPosition(bufferPosition) { + const screenPosition = this.getModel().screenPositionForBufferPosition( + bufferPosition + ); + return this.getComponent().pixelPositionForScreenPosition(screenPosition); } // Extended: Converts a screen position to a pixel position. @@ -241,60 +248,63 @@ class TextEditorElement extends HTMLElement { // // Returns an {Object} with two values: `top` and `left`, representing the // pixel position. - pixelPositionForScreenPosition (screenPosition) { - screenPosition = this.getModel().clipScreenPosition(screenPosition) - return this.getComponent().pixelPositionForScreenPosition(screenPosition) + pixelPositionForScreenPosition(screenPosition) { + screenPosition = this.getModel().clipScreenPosition(screenPosition); + return this.getComponent().pixelPositionForScreenPosition(screenPosition); } - screenPositionForPixelPosition (pixelPosition) { - return this.getComponent().screenPositionForPixelPosition(pixelPosition) + screenPositionForPixelPosition(pixelPosition) { + return this.getComponent().screenPositionForPixelPosition(pixelPosition); } - pixelRectForScreenRange (range) { - range = Range.fromObject(range) + pixelRectForScreenRange(range) { + range = Range.fromObject(range); - const start = this.pixelPositionForScreenPosition(range.start) - const end = this.pixelPositionForScreenPosition(range.end) - const lineHeight = this.getComponent().getLineHeight() + const start = this.pixelPositionForScreenPosition(range.start); + const end = this.pixelPositionForScreenPosition(range.end); + const lineHeight = this.getComponent().getLineHeight(); return { top: start.top, left: start.left, height: end.top + lineHeight - start.top, width: end.left - start.left - } + }; } - pixelRangeForScreenRange (range) { - range = Range.fromObject(range) + pixelRangeForScreenRange(range) { + range = Range.fromObject(range); return { start: this.pixelPositionForScreenPosition(range.start), end: this.pixelPositionForScreenPosition(range.end) - } + }; } - getComponent () { + getComponent() { if (!this.component) { this.component = new TextEditorComponent({ element: this, mini: this.hasAttribute('mini'), updatedSynchronously: this.updatedSynchronously, readOnly: this.hasAttribute('readonly') - }) - this.updateModelFromAttributes() + }); + this.updateModelFromAttributes(); } + return this.component; + } + + setUpdatedSynchronously(updatedSynchronously) { + this.updatedSynchronously = updatedSynchronously; + if (this.component) + this.component.updatedSynchronously = updatedSynchronously; + return updatedSynchronously; + } + + isUpdatedSynchronously() { return this.component - } - - setUpdatedSynchronously (updatedSynchronously) { - this.updatedSynchronously = updatedSynchronously - if (this.component) this.component.updatedSynchronously = updatedSynchronously - return updatedSynchronously - } - - isUpdatedSynchronously () { - return this.component ? this.component.updatedSynchronously : this.updatedSynchronously + ? this.component.updatedSynchronously + : this.updatedSynchronously; } // Experimental: Invalidate the passed block {Decoration}'s dimensions, @@ -303,48 +313,47 @@ class TextEditorElement extends HTMLElement { // // * {blockDecoration} A {Decoration} representing the block decoration you // want to update the dimensions of. - invalidateBlockDecorationDimensions () { - this.getComponent().invalidateBlockDecorationDimensions(...arguments) + invalidateBlockDecorationDimensions() { + this.getComponent().invalidateBlockDecorationDimensions(...arguments); } - setFirstVisibleScreenRow (row) { - this.getModel().setFirstVisibleScreenRow(row) + setFirstVisibleScreenRow(row) { + this.getModel().setFirstVisibleScreenRow(row); } - getFirstVisibleScreenRow () { - return this.getModel().getFirstVisibleScreenRow() + getFirstVisibleScreenRow() { + return this.getModel().getFirstVisibleScreenRow(); } - getLastVisibleScreenRow () { - return this.getModel().getLastVisibleScreenRow() + getLastVisibleScreenRow() { + return this.getModel().getLastVisibleScreenRow(); } - getVisibleRowRange () { - return this.getModel().getVisibleRowRange() + getVisibleRowRange() { + return this.getModel().getVisibleRowRange(); } - intersectsVisibleRowRange (startRow, endRow) { + intersectsVisibleRowRange(startRow, endRow) { return !( endRow <= this.getFirstVisibleScreenRow() || this.getLastVisibleScreenRow() <= startRow - ) + ); } - selectionIntersectsVisibleRowRange (selection) { - const {start, end} = selection.getScreenRange() - return this.intersectsVisibleRowRange(start.row, end.row + 1) + selectionIntersectsVisibleRowRange(selection) { + const { start, end } = selection.getScreenRange(); + return this.intersectsVisibleRowRange(start.row, end.row + 1); } - setFirstVisibleScreenColumn (column) { - return this.getModel().setFirstVisibleScreenColumn(column) + setFirstVisibleScreenColumn(column) { + return this.getModel().setFirstVisibleScreenColumn(column); } - getFirstVisibleScreenColumn () { - return this.getModel().getFirstVisibleScreenColumn() + getFirstVisibleScreenColumn() { + return this.getModel().getFirstVisibleScreenColumn(); } } -module.exports = -document.registerElement('atom-text-editor', { +module.exports = document.registerElement('atom-text-editor', { prototype: TextEditorElement.prototype -}) +}); diff --git a/src/text-editor-registry.js b/src/text-editor-registry.js index 37a546c87..ba62169a2 100644 --- a/src/text-editor-registry.js +++ b/src/text-editor-registry.js @@ -1,7 +1,7 @@ -const _ = require('underscore-plus') -const {Emitter, Disposable, CompositeDisposable} = require('event-kit') -const TextEditor = require('./text-editor') -const ScopeDescriptor = require('./scope-descriptor') +const _ = require('underscore-plus'); +const { Emitter, Disposable, CompositeDisposable } = require('event-kit'); +const TextEditor = require('./text-editor'); +const ScopeDescriptor = require('./scope-descriptor'); const EDITOR_PARAMS_BY_SETTING_KEY = [ ['core.fileEncoding', 'encoding'], @@ -22,7 +22,7 @@ const EDITOR_PARAMS_BY_SETTING_KEY = [ ['editor.scrollPastEnd', 'scrollPastEnd'], ['editor.undoGroupingInterval', 'undoGroupingInterval'], ['editor.scrollSensitivity', 'scrollSensitivity'] -] +]; // Experimental: This global registry tracks registered `TextEditors`. // @@ -35,43 +35,42 @@ const EDITOR_PARAMS_BY_SETTING_KEY = [ // them for observation via `atom.textEditors.add`. **Important:** When you're // done using your editor, be sure to call `dispose` on the returned disposable // to avoid leaking editors. -module.exports = -class TextEditorRegistry { - constructor ({config, assert, packageManager}) { - this.config = config - this.assert = assert - this.packageManager = packageManager - this.clear() +module.exports = class TextEditorRegistry { + constructor({ config, assert, packageManager }) { + this.config = config; + this.assert = assert; + this.packageManager = packageManager; + this.clear(); } - deserialize (state) { - this.editorGrammarOverrides = state.editorGrammarOverrides + deserialize(state) { + this.editorGrammarOverrides = state.editorGrammarOverrides; } - serialize () { + serialize() { return { editorGrammarOverrides: Object.assign({}, this.editorGrammarOverrides) - } + }; } - clear () { + clear() { if (this.subscriptions) { - this.subscriptions.dispose() + this.subscriptions.dispose(); } - this.subscriptions = new CompositeDisposable() - this.editors = new Set() - this.emitter = new Emitter() - this.scopesWithConfigSubscriptions = new Set() - this.editorsWithMaintainedConfig = new Set() - this.editorsWithMaintainedGrammar = new Set() - this.editorGrammarOverrides = {} - this.editorGrammarScores = new WeakMap() + this.subscriptions = new CompositeDisposable(); + this.editors = new Set(); + this.emitter = new Emitter(); + this.scopesWithConfigSubscriptions = new Set(); + this.editorsWithMaintainedConfig = new Set(); + this.editorsWithMaintainedGrammar = new Set(); + this.editorGrammarOverrides = {}; + this.editorGrammarScores = new WeakMap(); } - destroy () { - this.subscriptions.dispose() - this.editorsWithMaintainedConfig = null + destroy() { + this.subscriptions.dispose(); + this.editorsWithMaintainedConfig = null; } // Register a `TextEditor`. @@ -81,28 +80,28 @@ class TextEditorRegistry { // Returns a {Disposable} on which `.dispose()` can be called to remove the // added editor. To avoid any memory leaks this should be called when the // editor is destroyed. - add (editor) { - this.editors.add(editor) - editor.registered = true - this.emitter.emit('did-add-editor', editor) + add(editor) { + this.editors.add(editor); + editor.registered = true; + this.emitter.emit('did-add-editor', editor); - return new Disposable(() => this.remove(editor)) + return new Disposable(() => this.remove(editor)); } - build (params) { - params = Object.assign({assert: this.assert}, params) + build(params) { + params = Object.assign({ assert: this.assert }, params); - let scope = null + let scope = null; if (params.buffer) { - const {grammar} = params.buffer.getLanguageMode() + const { grammar } = params.buffer.getLanguageMode(); if (grammar) { - scope = new ScopeDescriptor({scopes: [grammar.scopeName]}) + scope = new ScopeDescriptor({ scopes: [grammar.scopeName] }); } } - Object.assign(params, this.textEditorParamsForScope(scope)) + Object.assign(params, this.textEditorParamsForScope(scope)); - return new TextEditor(params) + return new TextEditor(params); } // Remove a `TextEditor`. @@ -110,10 +109,10 @@ class TextEditorRegistry { // * `editor` The editor to remove. // // Returns a {Boolean} indicating whether the editor was successfully removed. - remove (editor) { - var removed = this.editors.delete(editor) - editor.registered = false - return removed + remove(editor) { + var removed = this.editors.delete(editor); + editor.registered = false; + return removed; } // Invoke the given callback with all the current and future registered @@ -122,9 +121,9 @@ class TextEditorRegistry { // * `callback` {Function} to be called with current and future text editors. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observe (callback) { - this.editors.forEach(callback) - return this.emitter.on('did-add-editor', callback) + observe(callback) { + this.editors.forEach(callback); + return this.emitter.on('did-add-editor', callback); } // Keep a {TextEditor}'s configuration in sync with Atom's settings. @@ -133,38 +132,42 @@ class TextEditorRegistry { // // Returns a {Disposable} that can be used to stop updating the editor's // configuration. - maintainConfig (editor) { + maintainConfig(editor) { if (this.editorsWithMaintainedConfig.has(editor)) { - return new Disposable(noop) + return new Disposable(noop); } - this.editorsWithMaintainedConfig.add(editor) + this.editorsWithMaintainedConfig.add(editor); - this.updateAndMonitorEditorSettings(editor) - const languageChangeSubscription = editor.buffer.onDidChangeLanguageMode((newLanguageMode, oldLanguageMode) => { - this.updateAndMonitorEditorSettings(editor, oldLanguageMode) - }) - this.subscriptions.add(languageChangeSubscription) + this.updateAndMonitorEditorSettings(editor); + const languageChangeSubscription = editor.buffer.onDidChangeLanguageMode( + (newLanguageMode, oldLanguageMode) => { + this.updateAndMonitorEditorSettings(editor, oldLanguageMode); + } + ); + this.subscriptions.add(languageChangeSubscription); const updateTabTypes = () => { - const configOptions = {scope: editor.getRootScopeDescriptor()} - editor.setSoftTabs(shouldEditorUseSoftTabs( - editor, - this.config.get('editor.tabType', configOptions), - this.config.get('editor.softTabs', configOptions) - )) - } + const configOptions = { scope: editor.getRootScopeDescriptor() }; + editor.setSoftTabs( + shouldEditorUseSoftTabs( + editor, + this.config.get('editor.tabType', configOptions), + this.config.get('editor.softTabs', configOptions) + ) + ); + }; - updateTabTypes() - const tokenizeSubscription = editor.onDidTokenize(updateTabTypes) - this.subscriptions.add(tokenizeSubscription) + updateTabTypes(); + const tokenizeSubscription = editor.onDidTokenize(updateTabTypes); + this.subscriptions.add(tokenizeSubscription); return new Disposable(() => { - this.editorsWithMaintainedConfig.delete(editor) - tokenizeSubscription.dispose() - languageChangeSubscription.dispose() - this.subscriptions.remove(languageChangeSubscription) - this.subscriptions.remove(tokenizeSubscription) - }) + this.editorsWithMaintainedConfig.delete(editor); + tokenizeSubscription.dispose(); + languageChangeSubscription.dispose(); + this.subscriptions.remove(languageChangeSubscription); + this.subscriptions.remove(tokenizeSubscription); + }); } // Deprecated: set a {TextEditor}'s grammar based on its path and content, @@ -175,8 +178,8 @@ class TextEditorRegistry { // // Returns a {Disposable} that can be used to stop updating the editor's // grammar. - maintainGrammar (editor) { - atom.grammars.maintainLanguageMode(editor.getBuffer()) + maintainGrammar(editor) { + atom.grammars.maintainLanguageMode(editor.getBuffer()); } // Deprecated: Force a {TextEditor} to use a different grammar than the @@ -184,8 +187,8 @@ class TextEditorRegistry { // // * `editor` The editor whose gramamr will be set. // * `languageId` The {String} language ID for the desired {Grammar}. - setGrammarOverride (editor, languageId) { - atom.grammars.assignLanguageMode(editor.getBuffer(), languageId) + setGrammarOverride(editor, languageId) { + atom.grammars.assignLanguageMode(editor.getBuffer(), languageId); } // Deprecated: Retrieve the grammar scope name that has been set as a @@ -195,113 +198,129 @@ class TextEditorRegistry { // // Returns a {String} scope name, or `null` if no override has been set // for the given editor. - getGrammarOverride (editor) { - return atom.grammars.getAssignedLanguageId(editor.getBuffer()) + getGrammarOverride(editor) { + return atom.grammars.getAssignedLanguageId(editor.getBuffer()); } // Deprecated: Remove any grammar override that has been set for the given {TextEditor}. // // * `editor` The editor. - clearGrammarOverride (editor) { - atom.grammars.autoAssignLanguageMode(editor.getBuffer()) + clearGrammarOverride(editor) { + atom.grammars.autoAssignLanguageMode(editor.getBuffer()); } - async updateAndMonitorEditorSettings (editor, oldLanguageMode) { - await this.packageManager.getActivatePromise() - this.updateEditorSettingsForLanguageMode(editor, oldLanguageMode) - this.subscribeToSettingsForEditorScope(editor) + async updateAndMonitorEditorSettings(editor, oldLanguageMode) { + await this.packageManager.getActivatePromise(); + this.updateEditorSettingsForLanguageMode(editor, oldLanguageMode); + this.subscribeToSettingsForEditorScope(editor); } - updateEditorSettingsForLanguageMode (editor, oldLanguageMode) { - const newLanguageMode = editor.buffer.getLanguageMode() + updateEditorSettingsForLanguageMode(editor, oldLanguageMode) { + const newLanguageMode = editor.buffer.getLanguageMode(); if (oldLanguageMode) { - const newSettings = this.textEditorParamsForScope(newLanguageMode.rootScopeDescriptor) - const oldSettings = this.textEditorParamsForScope(oldLanguageMode.rootScopeDescriptor) + const newSettings = this.textEditorParamsForScope( + newLanguageMode.rootScopeDescriptor + ); + const oldSettings = this.textEditorParamsForScope( + oldLanguageMode.rootScopeDescriptor + ); - const updatedSettings = {} + const updatedSettings = {}; for (const [, paramName] of EDITOR_PARAMS_BY_SETTING_KEY) { // Update the setting only if it has changed between the two language // modes. This prevents user-modified settings in an editor (like // 'softWrapped') from being reset when the language mode changes. if (!_.isEqual(newSettings[paramName], oldSettings[paramName])) { - updatedSettings[paramName] = newSettings[paramName] + updatedSettings[paramName] = newSettings[paramName]; } } if (_.size(updatedSettings) > 0) { - editor.update(updatedSettings) + editor.update(updatedSettings); } } else { - editor.update(this.textEditorParamsForScope(newLanguageMode.rootScopeDescriptor)) + editor.update( + this.textEditorParamsForScope(newLanguageMode.rootScopeDescriptor) + ); } } - subscribeToSettingsForEditorScope (editor) { - if (!this.editorsWithMaintainedConfig) return + subscribeToSettingsForEditorScope(editor) { + if (!this.editorsWithMaintainedConfig) return; - const scopeDescriptor = editor.getRootScopeDescriptor() - const scopeChain = scopeDescriptor.getScopeChain() + const scopeDescriptor = editor.getRootScopeDescriptor(); + const scopeChain = scopeDescriptor.getScopeChain(); if (!this.scopesWithConfigSubscriptions.has(scopeChain)) { - this.scopesWithConfigSubscriptions.add(scopeChain) - const configOptions = {scope: scopeDescriptor} + this.scopesWithConfigSubscriptions.add(scopeChain); + const configOptions = { scope: scopeDescriptor }; for (const [settingKey, paramName] of EDITOR_PARAMS_BY_SETTING_KEY) { this.subscriptions.add( - this.config.onDidChange(settingKey, configOptions, ({newValue}) => { - this.editorsWithMaintainedConfig.forEach((editor) => { + this.config.onDidChange(settingKey, configOptions, ({ newValue }) => { + this.editorsWithMaintainedConfig.forEach(editor => { if (editor.getRootScopeDescriptor().isEqual(scopeDescriptor)) { - editor.update({[paramName]: newValue}) + editor.update({ [paramName]: newValue }); } - }) + }); }) - ) + ); } const updateTabTypes = () => { - const tabType = this.config.get('editor.tabType', configOptions) - const softTabs = this.config.get('editor.softTabs', configOptions) - this.editorsWithMaintainedConfig.forEach((editor) => { + const tabType = this.config.get('editor.tabType', configOptions); + const softTabs = this.config.get('editor.softTabs', configOptions); + this.editorsWithMaintainedConfig.forEach(editor => { if (editor.getRootScopeDescriptor().isEqual(scopeDescriptor)) { - editor.setSoftTabs(shouldEditorUseSoftTabs(editor, tabType, softTabs)) + editor.setSoftTabs( + shouldEditorUseSoftTabs(editor, tabType, softTabs) + ); } - }) - } + }); + }; this.subscriptions.add( - this.config.onDidChange('editor.tabType', configOptions, updateTabTypes), - this.config.onDidChange('editor.softTabs', configOptions, updateTabTypes) - ) + this.config.onDidChange( + 'editor.tabType', + configOptions, + updateTabTypes + ), + this.config.onDidChange( + 'editor.softTabs', + configOptions, + updateTabTypes + ) + ); } } - textEditorParamsForScope (scopeDescriptor) { - const result = {} - const configOptions = {scope: scopeDescriptor} + textEditorParamsForScope(scopeDescriptor) { + const result = {}; + const configOptions = { scope: scopeDescriptor }; for (const [settingKey, paramName] of EDITOR_PARAMS_BY_SETTING_KEY) { - result[paramName] = this.config.get(settingKey, configOptions) + result[paramName] = this.config.get(settingKey, configOptions); } - return result + return result; } -} +}; -function shouldEditorUseSoftTabs (editor, tabType, softTabs) { +function shouldEditorUseSoftTabs(editor, tabType, softTabs) { switch (tabType) { case 'hard': - return false + return false; case 'soft': - return true + return true; case 'auto': switch (editor.usesSoftTabs()) { case true: - return true + return true; case false: - return false + return false; default: - return softTabs + return softTabs; } } } -function noop () {} +function noop() {} diff --git a/src/text-editor.js b/src/text-editor.js index 8fd799a70..46e692807 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -1,30 +1,35 @@ -const _ = require('underscore-plus') -const path = require('path') -const fs = require('fs-plus') -const Grim = require('grim') -const dedent = require('dedent') -const {CompositeDisposable, Disposable, Emitter} = require('event-kit') -const TextBuffer = require('text-buffer') -const {Point, Range} = TextBuffer -const DecorationManager = require('./decoration-manager') -const Cursor = require('./cursor') -const Selection = require('./selection') -const NullGrammar = require('./null-grammar') -const TextMateLanguageMode = require('./text-mate-language-mode') -const ScopeDescriptor = require('./scope-descriptor') +const _ = require('underscore-plus'); +const path = require('path'); +const fs = require('fs-plus'); +const Grim = require('grim'); +const dedent = require('dedent'); +const { CompositeDisposable, Disposable, Emitter } = require('event-kit'); +const TextBuffer = require('text-buffer'); +const { Point, Range } = TextBuffer; +const DecorationManager = require('./decoration-manager'); +const Cursor = require('./cursor'); +const Selection = require('./selection'); +const NullGrammar = require('./null-grammar'); +const TextMateLanguageMode = require('./text-mate-language-mode'); +const ScopeDescriptor = require('./scope-descriptor'); -const TextMateScopeSelector = require('first-mate').ScopeSelector -const GutterContainer = require('./gutter-container') -let TextEditorComponent = null -let TextEditorElement = null -const {isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary} = require('./text-utils') +const TextMateScopeSelector = require('first-mate').ScopeSelector; +const GutterContainer = require('./gutter-container'); +let TextEditorComponent = null; +let TextEditorElement = null; +const { + isDoubleWidthCharacter, + isHalfWidthCharacter, + isKoreanCharacter, + isWrapBoundary +} = require('./text-utils'); -const SERIALIZATION_VERSION = 1 -const NON_WHITESPACE_REGEXP = /\S/ -const ZERO_WIDTH_NBSP = '\ufeff' -let nextId = 0 +const SERIALIZATION_VERSION = 1; +const NON_WHITESPACE_REGEXP = /\S/; +const ZERO_WIDTH_NBSP = '\ufeff'; +let nextId = 0; -const DEFAULT_NON_WORD_CHARACTERS = "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…" +const DEFAULT_NON_WORD_CHARACTERS = '/\\()"\':,.;<>~!@#$%^&*|+=[]{}`?-…'; // Essential: This class represents all essential editing state for a single // {TextBuffer}, including cursor and selection positions, folds, and soft wraps. @@ -65,484 +70,555 @@ const DEFAULT_NON_WORD_CHARACTERS = "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…" // // **When in doubt, just default to buffer coordinates**, then experiment with // soft wraps and folds to ensure your code interacts with them correctly. -module.exports = -class TextEditor { - static setClipboard (clipboard) { - this.clipboard = clipboard +module.exports = class TextEditor { + static setClipboard(clipboard) { + this.clipboard = clipboard; } - static setScheduler (scheduler) { - if (TextEditorComponent == null) { TextEditorComponent = require('./text-editor-component') } - return TextEditorComponent.setScheduler(scheduler) + static setScheduler(scheduler) { + if (TextEditorComponent == null) { + TextEditorComponent = require('./text-editor-component'); + } + return TextEditorComponent.setScheduler(scheduler); } - static didUpdateStyles () { - if (TextEditorComponent == null) { TextEditorComponent = require('./text-editor-component') } - return TextEditorComponent.didUpdateStyles() + static didUpdateStyles() { + if (TextEditorComponent == null) { + TextEditorComponent = require('./text-editor-component'); + } + return TextEditorComponent.didUpdateStyles(); } - static didUpdateScrollbarStyles () { - if (TextEditorComponent == null) { TextEditorComponent = require('./text-editor-component') } - return TextEditorComponent.didUpdateScrollbarStyles() + static didUpdateScrollbarStyles() { + if (TextEditorComponent == null) { + TextEditorComponent = require('./text-editor-component'); + } + return TextEditorComponent.didUpdateScrollbarStyles(); } - static viewForItem (item) { return item.element || item } + static viewForItem(item) { + return item.element || item; + } - static deserialize (state, atomEnvironment) { - if (state.version !== SERIALIZATION_VERSION) return null + static deserialize(state, atomEnvironment) { + if (state.version !== SERIALIZATION_VERSION) return null; let bufferId = state.tokenizedBuffer ? state.tokenizedBuffer.bufferId - : state.bufferId + : state.bufferId; try { - state.buffer = atomEnvironment.project.bufferForIdSync(bufferId) - if (!state.buffer) return null + state.buffer = atomEnvironment.project.bufferForIdSync(bufferId); + if (!state.buffer) return null; } catch (error) { if (error.syscall === 'read') { - return // Error reading the file, don't deserialize an editor for it + return; // Error reading the file, don't deserialize an editor for it } else { - throw error + throw error; } } - state.assert = atomEnvironment.assert.bind(atomEnvironment) + state.assert = atomEnvironment.assert.bind(atomEnvironment); // Semantics of the readOnly flag have changed since its introduction. // Only respect readOnly2, which has been set with the current readOnly semantics. - delete state.readOnly - state.readOnly = state.readOnly2 - delete state.readOnly2 + delete state.readOnly; + state.readOnly = state.readOnly2; + delete state.readOnly2; - const editor = new TextEditor(state) + const editor = new TextEditor(state); if (state.registered) { - const disposable = atomEnvironment.textEditors.add(editor) - editor.onDidDestroy(() => disposable.dispose()) + const disposable = atomEnvironment.textEditors.add(editor); + editor.onDidDestroy(() => disposable.dispose()); } - return editor + return editor; } - constructor (params = {}) { + constructor(params = {}) { if (this.constructor.clipboard == null) { - throw new Error('Must call TextEditor.setClipboard at least once before creating TextEditor instances') + throw new Error( + 'Must call TextEditor.setClipboard at least once before creating TextEditor instances' + ); } - this.id = params.id != null ? params.id : nextId++ + this.id = params.id != null ? params.id : nextId++; if (this.id >= nextId) { // Ensure that new editors get unique ids: - nextId = this.id + 1 + nextId = this.id + 1; } - this.initialScrollTopRow = params.initialScrollTopRow - this.initialScrollLeftColumn = params.initialScrollLeftColumn - this.decorationManager = params.decorationManager - this.selectionsMarkerLayer = params.selectionsMarkerLayer - this.mini = (params.mini != null) ? params.mini : false - this.keyboardInputEnabled = (params.keyboardInputEnabled != null) ? params.keyboardInputEnabled : true - this.readOnly = (params.readOnly != null) ? params.readOnly : false - this.placeholderText = params.placeholderText - this.showLineNumbers = params.showLineNumbers - this.assert = params.assert || (condition => condition) - this.showInvisibles = (params.showInvisibles != null) ? params.showInvisibles : true - this.autoHeight = params.autoHeight - this.autoWidth = params.autoWidth - this.scrollPastEnd = (params.scrollPastEnd != null) ? params.scrollPastEnd : false - this.scrollSensitivity = (params.scrollSensitivity != null) ? params.scrollSensitivity : 40 - this.editorWidthInChars = params.editorWidthInChars - this.invisibles = params.invisibles - this.showIndentGuide = params.showIndentGuide - this.softWrapped = params.softWrapped - this.softWrapAtPreferredLineLength = params.softWrapAtPreferredLineLength - this.preferredLineLength = params.preferredLineLength - this.showCursorOnSelection = (params.showCursorOnSelection != null) ? params.showCursorOnSelection : true - this.maxScreenLineLength = params.maxScreenLineLength - this.softTabs = (params.softTabs != null) ? params.softTabs : true - this.autoIndent = (params.autoIndent != null) ? params.autoIndent : true - this.autoIndentOnPaste = (params.autoIndentOnPaste != null) ? params.autoIndentOnPaste : true - this.undoGroupingInterval = (params.undoGroupingInterval != null) ? params.undoGroupingInterval : 300 - this.softWrapped = (params.softWrapped != null) ? params.softWrapped : false - this.softWrapAtPreferredLineLength = (params.softWrapAtPreferredLineLength != null) ? params.softWrapAtPreferredLineLength : false - this.preferredLineLength = (params.preferredLineLength != null) ? params.preferredLineLength : 80 - this.maxScreenLineLength = (params.maxScreenLineLength != null) ? params.maxScreenLineLength : 500 - this.showLineNumbers = (params.showLineNumbers != null) ? params.showLineNumbers : true - const {tabLength = 2} = params + this.initialScrollTopRow = params.initialScrollTopRow; + this.initialScrollLeftColumn = params.initialScrollLeftColumn; + this.decorationManager = params.decorationManager; + this.selectionsMarkerLayer = params.selectionsMarkerLayer; + this.mini = params.mini != null ? params.mini : false; + this.keyboardInputEnabled = + params.keyboardInputEnabled != null ? params.keyboardInputEnabled : true; + this.readOnly = params.readOnly != null ? params.readOnly : false; + this.placeholderText = params.placeholderText; + this.showLineNumbers = params.showLineNumbers; + this.assert = params.assert || (condition => condition); + this.showInvisibles = + params.showInvisibles != null ? params.showInvisibles : true; + this.autoHeight = params.autoHeight; + this.autoWidth = params.autoWidth; + this.scrollPastEnd = + params.scrollPastEnd != null ? params.scrollPastEnd : false; + this.scrollSensitivity = + params.scrollSensitivity != null ? params.scrollSensitivity : 40; + this.editorWidthInChars = params.editorWidthInChars; + this.invisibles = params.invisibles; + this.showIndentGuide = params.showIndentGuide; + this.softWrapped = params.softWrapped; + this.softWrapAtPreferredLineLength = params.softWrapAtPreferredLineLength; + this.preferredLineLength = params.preferredLineLength; + this.showCursorOnSelection = + params.showCursorOnSelection != null + ? params.showCursorOnSelection + : true; + this.maxScreenLineLength = params.maxScreenLineLength; + this.softTabs = params.softTabs != null ? params.softTabs : true; + this.autoIndent = params.autoIndent != null ? params.autoIndent : true; + this.autoIndentOnPaste = + params.autoIndentOnPaste != null ? params.autoIndentOnPaste : true; + this.undoGroupingInterval = + params.undoGroupingInterval != null ? params.undoGroupingInterval : 300; + this.softWrapped = params.softWrapped != null ? params.softWrapped : false; + this.softWrapAtPreferredLineLength = + params.softWrapAtPreferredLineLength != null + ? params.softWrapAtPreferredLineLength + : false; + this.preferredLineLength = + params.preferredLineLength != null ? params.preferredLineLength : 80; + this.maxScreenLineLength = + params.maxScreenLineLength != null ? params.maxScreenLineLength : 500; + this.showLineNumbers = + params.showLineNumbers != null ? params.showLineNumbers : true; + const { tabLength = 2 } = params; - this.alive = true - this.doBackgroundWork = this.doBackgroundWork.bind(this) - this.serializationVersion = 1 - this.suppressSelectionMerging = false - this.selectionFlashDuration = 500 - this.gutterContainer = null - this.verticalScrollMargin = 2 - this.horizontalScrollMargin = 6 - this.lineHeightInPixels = null - this.defaultCharWidth = null - this.height = null - this.width = null - this.registered = false - this.atomicSoftTabs = true - this.emitter = new Emitter() - this.disposables = new CompositeDisposable() - this.cursors = [] - this.cursorsByMarkerId = new Map() - this.selections = [] - this.hasTerminatedPendingState = false + this.alive = true; + this.doBackgroundWork = this.doBackgroundWork.bind(this); + this.serializationVersion = 1; + this.suppressSelectionMerging = false; + this.selectionFlashDuration = 500; + this.gutterContainer = null; + this.verticalScrollMargin = 2; + this.horizontalScrollMargin = 6; + this.lineHeightInPixels = null; + this.defaultCharWidth = null; + this.height = null; + this.width = null; + this.registered = false; + this.atomicSoftTabs = true; + this.emitter = new Emitter(); + this.disposables = new CompositeDisposable(); + this.cursors = []; + this.cursorsByMarkerId = new Map(); + this.selections = []; + this.hasTerminatedPendingState = false; if (params.buffer) { - this.buffer = params.buffer + this.buffer = params.buffer; } else { this.buffer = new TextBuffer({ - shouldDestroyOnFileDelete () { return atom.config.get('core.closeDeletedFileTabs') } - }) - this.buffer.setLanguageMode(new TextMateLanguageMode({buffer: this.buffer, config: atom.config})) + shouldDestroyOnFileDelete() { + return atom.config.get('core.closeDeletedFileTabs'); + } + }); + this.buffer.setLanguageMode( + new TextMateLanguageMode({ buffer: this.buffer, config: atom.config }) + ); } - const languageMode = this.buffer.getLanguageMode() - this.languageModeSubscription = languageMode.onDidTokenize && languageMode.onDidTokenize(() => { - this.emitter.emit('did-tokenize') - }) - if (this.languageModeSubscription) this.disposables.add(this.languageModeSubscription) + const languageMode = this.buffer.getLanguageMode(); + this.languageModeSubscription = + languageMode.onDidTokenize && + languageMode.onDidTokenize(() => { + this.emitter.emit('did-tokenize'); + }); + if (this.languageModeSubscription) + this.disposables.add(this.languageModeSubscription); if (params.displayLayer) { - this.displayLayer = params.displayLayer + this.displayLayer = params.displayLayer; } else { const displayLayerParams = { invisibles: this.getInvisibles(), softWrapColumn: this.getSoftWrapColumn(), showIndentGuides: this.doesShowIndentGuide(), - atomicSoftTabs: params.atomicSoftTabs != null ? params.atomicSoftTabs : true, + atomicSoftTabs: + params.atomicSoftTabs != null ? params.atomicSoftTabs : true, tabLength, ratioForCharacter: this.ratioForCharacter.bind(this), isWrapBoundary, foldCharacter: ZERO_WIDTH_NBSP, - softWrapHangingIndent: params.softWrapHangingIndentLength != null ? params.softWrapHangingIndentLength : 0 - } + softWrapHangingIndent: + params.softWrapHangingIndentLength != null + ? params.softWrapHangingIndentLength + : 0 + }; - this.displayLayer = this.buffer.getDisplayLayer(params.displayLayerId) + this.displayLayer = this.buffer.getDisplayLayer(params.displayLayerId); if (this.displayLayer) { - this.displayLayer.reset(displayLayerParams) - this.selectionsMarkerLayer = this.displayLayer.getMarkerLayer(params.selectionsMarkerLayerId) + this.displayLayer.reset(displayLayerParams); + this.selectionsMarkerLayer = this.displayLayer.getMarkerLayer( + params.selectionsMarkerLayerId + ); } else { - this.displayLayer = this.buffer.addDisplayLayer(displayLayerParams) + this.displayLayer = this.buffer.addDisplayLayer(displayLayerParams); } } - this.backgroundWorkHandle = requestIdleCallback(this.doBackgroundWork) - this.disposables.add(new Disposable(() => { - if (this.backgroundWorkHandle != null) return cancelIdleCallback(this.backgroundWorkHandle) - })) + this.backgroundWorkHandle = requestIdleCallback(this.doBackgroundWork); + this.disposables.add( + new Disposable(() => { + if (this.backgroundWorkHandle != null) + return cancelIdleCallback(this.backgroundWorkHandle); + }) + ); - this.defaultMarkerLayer = this.displayLayer.addMarkerLayer() + this.defaultMarkerLayer = this.displayLayer.addMarkerLayer(); if (!this.selectionsMarkerLayer) { - this.selectionsMarkerLayer = this.addMarkerLayer({maintainHistory: true, persistent: true, role: 'selections'}) + this.selectionsMarkerLayer = this.addMarkerLayer({ + maintainHistory: true, + persistent: true, + role: 'selections' + }); } - this.decorationManager = new DecorationManager(this) - this.decorateMarkerLayer(this.selectionsMarkerLayer, {type: 'cursor'}) - if (!this.isMini()) this.decorateCursorLine() + this.decorationManager = new DecorationManager(this); + this.decorateMarkerLayer(this.selectionsMarkerLayer, { type: 'cursor' }); + if (!this.isMini()) this.decorateCursorLine(); - this.decorateMarkerLayer(this.displayLayer.foldsMarkerLayer, {type: 'line-number', class: 'folded'}) + this.decorateMarkerLayer(this.displayLayer.foldsMarkerLayer, { + type: 'line-number', + class: 'folded' + }); for (let marker of this.selectionsMarkerLayer.getMarkers()) { - this.addSelection(marker) + this.addSelection(marker); } - this.subscribeToBuffer() - this.subscribeToDisplayLayer() + this.subscribeToBuffer(); + this.subscribeToDisplayLayer(); if (this.cursors.length === 0 && !params.suppressCursorCreation) { - const initialLine = Math.max(parseInt(params.initialLine) || 0, 0) - const initialColumn = Math.max(parseInt(params.initialColumn) || 0, 0) - this.addCursorAtBufferPosition([initialLine, initialColumn]) + const initialLine = Math.max(parseInt(params.initialLine) || 0, 0); + const initialColumn = Math.max(parseInt(params.initialColumn) || 0, 0); + this.addCursorAtBufferPosition([initialLine, initialColumn]); } - this.gutterContainer = new GutterContainer(this) + this.gutterContainer = new GutterContainer(this); this.lineNumberGutter = this.gutterContainer.addGutter({ name: 'line-number', type: 'line-number', priority: 0, visible: params.lineNumberGutterVisible - }) + }); } - get element () { - return this.getElement() + get element() { + return this.getElement(); } - get editorElement () { + get editorElement() { Grim.deprecate(dedent`\ \`TextEditor.prototype.editorElement\` has always been private, but now it is gone. Reading the \`editorElement\` property still returns a reference to the editor element but this field will be removed in a later version of Atom, so we recommend using the \`element\` property instead.\ - `) + `); - return this.getElement() + return this.getElement(); } - get displayBuffer () { + get displayBuffer() { Grim.deprecate(dedent`\ \`TextEditor.prototype.displayBuffer\` has always been private, but now it is gone. Reading the \`displayBuffer\` property now returns a reference to the containing \`TextEditor\`, which now provides *some* of the API of the defunct \`DisplayBuffer\` class.\ - `) - return this + `); + return this; } - get languageMode () { return this.buffer.getLanguageMode() } - get tokenizedBuffer () { return this.buffer.getLanguageMode() } - - get rowsPerPage () { - return this.getRowsPerPage() + get languageMode() { + return this.buffer.getLanguageMode(); + } + get tokenizedBuffer() { + return this.buffer.getLanguageMode(); } - decorateCursorLine () { + get rowsPerPage() { + return this.getRowsPerPage(); + } + + decorateCursorLine() { this.cursorLineDecorations = [ - this.decorateMarkerLayer(this.selectionsMarkerLayer, {type: 'line', class: 'cursor-line', onlyEmpty: true}), - this.decorateMarkerLayer(this.selectionsMarkerLayer, {type: 'line-number', class: 'cursor-line'}), - this.decorateMarkerLayer(this.selectionsMarkerLayer, {type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true}) - ] + this.decorateMarkerLayer(this.selectionsMarkerLayer, { + type: 'line', + class: 'cursor-line', + onlyEmpty: true + }), + this.decorateMarkerLayer(this.selectionsMarkerLayer, { + type: 'line-number', + class: 'cursor-line' + }), + this.decorateMarkerLayer(this.selectionsMarkerLayer, { + type: 'line-number', + class: 'cursor-line-no-selection', + onlyHead: true, + onlyEmpty: true + }) + ]; } - doBackgroundWork (deadline) { - const previousLongestRow = this.getApproximateLongestScreenRow() + doBackgroundWork(deadline) { + const previousLongestRow = this.getApproximateLongestScreenRow(); if (this.displayLayer.doBackgroundWork(deadline)) { - this.backgroundWorkHandle = requestIdleCallback(this.doBackgroundWork) + this.backgroundWorkHandle = requestIdleCallback(this.doBackgroundWork); } else { - this.backgroundWorkHandle = null + this.backgroundWorkHandle = null; } - if (this.component && this.getApproximateLongestScreenRow() !== previousLongestRow) { - this.component.scheduleUpdate() + if ( + this.component && + this.getApproximateLongestScreenRow() !== previousLongestRow + ) { + this.component.scheduleUpdate(); } } - update (params) { - const displayLayerParams = {} + update(params) { + const displayLayerParams = {}; for (let param of Object.keys(params)) { - const value = params[param] + const value = params[param]; switch (param) { case 'autoIndent': - this.autoIndent = value - break + this.autoIndent = value; + break; case 'autoIndentOnPaste': - this.autoIndentOnPaste = value - break + this.autoIndentOnPaste = value; + break; case 'undoGroupingInterval': - this.undoGroupingInterval = value - break + this.undoGroupingInterval = value; + break; case 'scrollSensitivity': - this.scrollSensitivity = value - break + this.scrollSensitivity = value; + break; case 'encoding': - this.buffer.setEncoding(value) - break + this.buffer.setEncoding(value); + break; case 'softTabs': if (value !== this.softTabs) { - this.softTabs = value + this.softTabs = value; } - break + break; case 'atomicSoftTabs': if (value !== this.displayLayer.atomicSoftTabs) { - displayLayerParams.atomicSoftTabs = value + displayLayerParams.atomicSoftTabs = value; } - break + break; case 'tabLength': if (value > 0 && value !== this.displayLayer.tabLength) { - displayLayerParams.tabLength = value + displayLayerParams.tabLength = value; } - break + break; case 'softWrapped': if (value !== this.softWrapped) { - this.softWrapped = value - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() - this.emitter.emit('did-change-soft-wrapped', this.isSoftWrapped()) + this.softWrapped = value; + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); + this.emitter.emit('did-change-soft-wrapped', this.isSoftWrapped()); } - break + break; case 'softWrapHangingIndentLength': if (value !== this.displayLayer.softWrapHangingIndent) { - displayLayerParams.softWrapHangingIndent = value + displayLayerParams.softWrapHangingIndent = value; } - break + break; case 'softWrapAtPreferredLineLength': if (value !== this.softWrapAtPreferredLineLength) { - this.softWrapAtPreferredLineLength = value - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() + this.softWrapAtPreferredLineLength = value; + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); } - break + break; case 'preferredLineLength': if (value !== this.preferredLineLength) { - this.preferredLineLength = value - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() + this.preferredLineLength = value; + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); } - break + break; case 'maxScreenLineLength': if (value !== this.maxScreenLineLength) { - this.maxScreenLineLength = value - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() + this.maxScreenLineLength = value; + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); } - break + break; case 'mini': if (value !== this.mini) { - this.mini = value - this.emitter.emit('did-change-mini', value) - displayLayerParams.invisibles = this.getInvisibles() - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() - displayLayerParams.showIndentGuides = this.doesShowIndentGuide() + this.mini = value; + this.emitter.emit('did-change-mini', value); + displayLayerParams.invisibles = this.getInvisibles(); + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); + displayLayerParams.showIndentGuides = this.doesShowIndentGuide(); if (this.mini) { - for (let decoration of this.cursorLineDecorations) { decoration.destroy() } - this.cursorLineDecorations = null + for (let decoration of this.cursorLineDecorations) { + decoration.destroy(); + } + this.cursorLineDecorations = null; } else { - this.decorateCursorLine() + this.decorateCursorLine(); } if (this.component != null) { - this.component.scheduleUpdate() + this.component.scheduleUpdate(); } } - break + break; case 'readOnly': if (value !== this.readOnly) { - this.readOnly = value + this.readOnly = value; if (this.component != null) { - this.component.scheduleUpdate() + this.component.scheduleUpdate(); } } - break + break; case 'keyboardInputEnabled': if (value !== this.keyboardInputEnabled) { - this.keyboardInputEnabled = value + this.keyboardInputEnabled = value; if (this.component != null) { - this.component.scheduleUpdate() + this.component.scheduleUpdate(); } } - break + break; case 'placeholderText': if (value !== this.placeholderText) { - this.placeholderText = value - this.emitter.emit('did-change-placeholder-text', value) + this.placeholderText = value; + this.emitter.emit('did-change-placeholder-text', value); } - break + break; case 'lineNumberGutterVisible': if (value !== this.lineNumberGutterVisible) { if (value) { - this.lineNumberGutter.show() + this.lineNumberGutter.show(); } else { - this.lineNumberGutter.hide() + this.lineNumberGutter.hide(); } - this.emitter.emit('did-change-line-number-gutter-visible', this.lineNumberGutter.isVisible()) + this.emitter.emit( + 'did-change-line-number-gutter-visible', + this.lineNumberGutter.isVisible() + ); } - break + break; case 'showIndentGuide': if (value !== this.showIndentGuide) { - this.showIndentGuide = value - displayLayerParams.showIndentGuides = this.doesShowIndentGuide() + this.showIndentGuide = value; + displayLayerParams.showIndentGuides = this.doesShowIndentGuide(); } - break + break; case 'showLineNumbers': if (value !== this.showLineNumbers) { - this.showLineNumbers = value + this.showLineNumbers = value; if (this.component != null) { - this.component.scheduleUpdate() + this.component.scheduleUpdate(); } } - break + break; case 'showInvisibles': if (value !== this.showInvisibles) { - this.showInvisibles = value - displayLayerParams.invisibles = this.getInvisibles() + this.showInvisibles = value; + displayLayerParams.invisibles = this.getInvisibles(); } - break + break; case 'invisibles': if (!_.isEqual(value, this.invisibles)) { - this.invisibles = value - displayLayerParams.invisibles = this.getInvisibles() + this.invisibles = value; + displayLayerParams.invisibles = this.getInvisibles(); } - break + break; case 'editorWidthInChars': if (value > 0 && value !== this.editorWidthInChars) { - this.editorWidthInChars = value - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() + this.editorWidthInChars = value; + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); } - break + break; case 'width': if (value !== this.width) { - this.width = value - displayLayerParams.softWrapColumn = this.getSoftWrapColumn() + this.width = value; + displayLayerParams.softWrapColumn = this.getSoftWrapColumn(); } - break + break; case 'scrollPastEnd': if (value !== this.scrollPastEnd) { - this.scrollPastEnd = value - if (this.component) this.component.scheduleUpdate() + this.scrollPastEnd = value; + if (this.component) this.component.scheduleUpdate(); } - break + break; case 'autoHeight': if (value !== this.autoHeight) { - this.autoHeight = value + this.autoHeight = value; } - break + break; case 'autoWidth': if (value !== this.autoWidth) { - this.autoWidth = value + this.autoWidth = value; } - break + break; case 'showCursorOnSelection': if (value !== this.showCursorOnSelection) { - this.showCursorOnSelection = value - if (this.component) this.component.scheduleUpdate() + this.showCursorOnSelection = value; + if (this.component) this.component.scheduleUpdate(); } - break + break; default: if (param !== 'ref' && param !== 'key') { - throw new TypeError(`Invalid TextEditor parameter: '${param}'`) + throw new TypeError(`Invalid TextEditor parameter: '${param}'`); } } } - this.displayLayer.reset(displayLayerParams) + this.displayLayer.reset(displayLayerParams); if (this.component) { - return this.component.getNextUpdatePromise() + return this.component.getNextUpdatePromise(); } else { - return Promise.resolve() + return Promise.resolve(); } } - scheduleComponentUpdate () { - if (this.component) this.component.scheduleUpdate() + scheduleComponentUpdate() { + if (this.component) this.component.scheduleUpdate(); } - serialize () { + serialize() { return { deserializer: 'TextEditor', version: SERIALIZATION_VERSION, @@ -575,69 +651,100 @@ class TextEditor { showIndentGuide: this.showIndentGuide, autoHeight: this.autoHeight, autoWidth: this.autoWidth - } + }; } - subscribeToBuffer () { - this.buffer.retain() - this.disposables.add(this.buffer.onDidChangeLanguageMode(this.handleLanguageModeChange.bind(this))) - this.disposables.add(this.buffer.onDidChangePath(() => { - this.emitter.emit('did-change-title', this.getTitle()) - this.emitter.emit('did-change-path', this.getPath()) - })) - this.disposables.add(this.buffer.onDidChangeEncoding(() => { - this.emitter.emit('did-change-encoding', this.getEncoding()) - })) - this.disposables.add(this.buffer.onDidDestroy(() => this.destroy())) - this.disposables.add(this.buffer.onDidChangeModified(() => { - if (!this.hasTerminatedPendingState && this.buffer.isModified()) this.terminatePendingState() - })) + subscribeToBuffer() { + this.buffer.retain(); + this.disposables.add( + this.buffer.onDidChangeLanguageMode( + this.handleLanguageModeChange.bind(this) + ) + ); + this.disposables.add( + this.buffer.onDidChangePath(() => { + this.emitter.emit('did-change-title', this.getTitle()); + this.emitter.emit('did-change-path', this.getPath()); + }) + ); + this.disposables.add( + this.buffer.onDidChangeEncoding(() => { + this.emitter.emit('did-change-encoding', this.getEncoding()); + }) + ); + this.disposables.add(this.buffer.onDidDestroy(() => this.destroy())); + this.disposables.add( + this.buffer.onDidChangeModified(() => { + if (!this.hasTerminatedPendingState && this.buffer.isModified()) + this.terminatePendingState(); + }) + ); } - terminatePendingState () { - if (!this.hasTerminatedPendingState) this.emitter.emit('did-terminate-pending-state') - this.hasTerminatedPendingState = true + terminatePendingState() { + if (!this.hasTerminatedPendingState) + this.emitter.emit('did-terminate-pending-state'); + this.hasTerminatedPendingState = true; } - onDidTerminatePendingState (callback) { - return this.emitter.on('did-terminate-pending-state', callback) + onDidTerminatePendingState(callback) { + return this.emitter.on('did-terminate-pending-state', callback); } - subscribeToDisplayLayer () { - this.disposables.add(this.displayLayer.onDidChange(changes => { - this.mergeIntersectingSelections() - if (this.component) this.component.didChangeDisplayLayer(changes) - this.emitter.emit('did-change', changes.map(change => new ChangeEvent(change))) - })) - this.disposables.add(this.displayLayer.onDidReset(() => { - this.mergeIntersectingSelections() - if (this.component) this.component.didResetDisplayLayer() - this.emitter.emit('did-change', {}) - })) - this.disposables.add(this.selectionsMarkerLayer.onDidCreateMarker(this.addSelection.bind(this))) - return this.disposables.add(this.selectionsMarkerLayer.onDidUpdate(() => (this.component != null ? this.component.didUpdateSelections() : undefined))) + subscribeToDisplayLayer() { + this.disposables.add( + this.displayLayer.onDidChange(changes => { + this.mergeIntersectingSelections(); + if (this.component) this.component.didChangeDisplayLayer(changes); + this.emitter.emit( + 'did-change', + changes.map(change => new ChangeEvent(change)) + ); + }) + ); + this.disposables.add( + this.displayLayer.onDidReset(() => { + this.mergeIntersectingSelections(); + if (this.component) this.component.didResetDisplayLayer(); + this.emitter.emit('did-change', {}); + }) + ); + this.disposables.add( + this.selectionsMarkerLayer.onDidCreateMarker(this.addSelection.bind(this)) + ); + return this.disposables.add( + this.selectionsMarkerLayer.onDidUpdate(() => + this.component != null + ? this.component.didUpdateSelections() + : undefined + ) + ); } - destroy () { - if (!this.alive) return - this.alive = false - this.disposables.dispose() - this.displayLayer.destroy() + destroy() { + if (!this.alive) return; + this.alive = false; + this.disposables.dispose(); + this.displayLayer.destroy(); for (let selection of this.selections.slice()) { - selection.destroy() + selection.destroy(); } - this.buffer.release() - this.gutterContainer.destroy() - this.emitter.emit('did-destroy') - this.emitter.clear() - if (this.component) this.component.element.component = null - this.component = null - this.lineNumberGutter.element = null + this.buffer.release(); + this.gutterContainer.destroy(); + this.emitter.emit('did-destroy'); + this.emitter.clear(); + if (this.component) this.component.element.component = null; + this.component = null; + this.lineNumberGutter.element = null; } - isAlive () { return this.alive } + isAlive() { + return this.alive; + } - isDestroyed () { return !this.alive } + isDestroyed() { + return !this.alive; + } /* Section: Event Subscription @@ -648,8 +755,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeTitle (callback) { - return this.emitter.on('did-change-title', callback) + onDidChangeTitle(callback) { + return this.emitter.on('did-change-title', callback); } // Essential: Calls your `callback` when the buffer's path, and therefore title, has changed. @@ -657,8 +764,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangePath (callback) { - return this.emitter.on('did-change-path', callback) + onDidChangePath(callback) { + return this.emitter.on('did-change-path', callback); } // Essential: Invoke the given callback synchronously when the content of the @@ -671,8 +778,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChange (callback) { - return this.emitter.on('did-change', callback) + onDidChange(callback) { + return this.emitter.on('did-change', callback); } // Essential: Invoke `callback` when the buffer's contents change. It is @@ -682,8 +789,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidStopChanging (callback) { - return this.getBuffer().onDidStopChanging(callback) + onDidStopChanging(callback) { + return this.getBuffer().onDidStopChanging(callback); } // Essential: Calls your `callback` when a {Cursor} is moved. If there are @@ -699,8 +806,8 @@ class TextEditor { // * `cursor` {Cursor} that triggered the event // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeCursorPosition (callback) { - return this.emitter.on('did-change-cursor-position', callback) + onDidChangeCursorPosition(callback) { + return this.emitter.on('did-change-cursor-position', callback); } // Essential: Calls your `callback` when a selection's screen range changes. @@ -714,8 +821,8 @@ class TextEditor { // * `selection` {Selection} that triggered the event // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeSelectionRange (callback) { - return this.emitter.on('did-change-selection-range', callback) + onDidChangeSelectionRange(callback) { + return this.emitter.on('did-change-selection-range', callback); } // Extended: Calls your `callback` when soft wrap was enabled or disabled. @@ -723,8 +830,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeSoftWrapped (callback) { - return this.emitter.on('did-change-soft-wrapped', callback) + onDidChangeSoftWrapped(callback) { + return this.emitter.on('did-change-soft-wrapped', callback); } // Extended: Calls your `callback` when the buffer's encoding has changed. @@ -732,8 +839,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeEncoding (callback) { - return this.emitter.on('did-change-encoding', callback) + onDidChangeEncoding(callback) { + return this.emitter.on('did-change-encoding', callback); } // Extended: Calls your `callback` when the grammar that interprets and @@ -744,9 +851,9 @@ class TextEditor { // * `grammar` {Grammar} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeGrammar (callback) { - callback(this.getGrammar()) - return this.onDidChangeGrammar(callback) + observeGrammar(callback) { + callback(this.getGrammar()); + return this.onDidChangeGrammar(callback); } // Extended: Calls your `callback` when the grammar that interprets and @@ -756,10 +863,10 @@ class TextEditor { // * `grammar` {Grammar} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeGrammar (callback) { + onDidChangeGrammar(callback) { return this.buffer.onDidChangeLanguageMode(() => { - callback(this.buffer.getLanguageMode().grammar) - }) + callback(this.buffer.getLanguageMode().grammar); + }); } // Extended: Calls your `callback` when the result of {::isModified} changes. @@ -767,8 +874,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeModified (callback) { - return this.getBuffer().onDidChangeModified(callback) + onDidChangeModified(callback) { + return this.getBuffer().onDidChangeModified(callback); } // Extended: Calls your `callback` when the buffer's underlying file changes on @@ -777,8 +884,8 @@ class TextEditor { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidConflict (callback) { - return this.getBuffer().onDidConflict(callback) + onDidConflict(callback) { + return this.getBuffer().onDidConflict(callback); } // Extended: Calls your `callback` before text has been inserted. @@ -789,8 +896,8 @@ class TextEditor { // * `cancel` {Function} Call to prevent the text from being inserted // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillInsertText (callback) { - return this.emitter.on('will-insert-text', callback) + onWillInsertText(callback) { + return this.emitter.on('will-insert-text', callback); } // Extended: Calls your `callback` after text has been inserted. @@ -800,8 +907,8 @@ class TextEditor { // * `text` {String} text to be inserted // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidInsertText (callback) { - return this.emitter.on('did-insert-text', callback) + onDidInsertText(callback) { + return this.emitter.on('did-insert-text', callback); } // Essential: Invoke the given callback after the buffer is saved to disk. @@ -811,8 +918,8 @@ class TextEditor { // * `path` The path to which the buffer was saved. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidSave (callback) { - return this.getBuffer().onDidSave(callback) + onDidSave(callback) { + return this.getBuffer().onDidSave(callback); } // Essential: Invoke the given callback when the editor is destroyed. @@ -820,8 +927,8 @@ class TextEditor { // * `callback` {Function} to be called when the editor is destroyed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroy (callback) { - return this.emitter.once('did-destroy', callback) + onDidDestroy(callback) { + return this.emitter.once('did-destroy', callback); } // Extended: Calls your `callback` when a {Cursor} is added to the editor. @@ -831,9 +938,9 @@ class TextEditor { // * `cursor` {Cursor} that was added // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeCursors (callback) { - this.getCursors().forEach(callback) - return this.onDidAddCursor(callback) + observeCursors(callback) { + this.getCursors().forEach(callback); + return this.onDidAddCursor(callback); } // Extended: Calls your `callback` when a {Cursor} is added to the editor. @@ -842,8 +949,8 @@ class TextEditor { // * `cursor` {Cursor} that was added // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddCursor (callback) { - return this.emitter.on('did-add-cursor', callback) + onDidAddCursor(callback) { + return this.emitter.on('did-add-cursor', callback); } // Extended: Calls your `callback` when a {Cursor} is removed from the editor. @@ -852,8 +959,8 @@ class TextEditor { // * `cursor` {Cursor} that was removed // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidRemoveCursor (callback) { - return this.emitter.on('did-remove-cursor', callback) + onDidRemoveCursor(callback) { + return this.emitter.on('did-remove-cursor', callback); } // Extended: Calls your `callback` when a {Selection} is added to the editor. @@ -863,9 +970,9 @@ class TextEditor { // * `selection` {Selection} that was added // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeSelections (callback) { - this.getSelections().forEach(callback) - return this.onDidAddSelection(callback) + observeSelections(callback) { + this.getSelections().forEach(callback); + return this.onDidAddSelection(callback); } // Extended: Calls your `callback` when a {Selection} is added to the editor. @@ -874,8 +981,8 @@ class TextEditor { // * `selection` {Selection} that was added // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddSelection (callback) { - return this.emitter.on('did-add-selection', callback) + onDidAddSelection(callback) { + return this.emitter.on('did-add-selection', callback); } // Extended: Calls your `callback` when a {Selection} is removed from the editor. @@ -884,8 +991,8 @@ class TextEditor { // * `selection` {Selection} that was removed // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidRemoveSelection (callback) { - return this.emitter.on('did-remove-selection', callback) + onDidRemoveSelection(callback) { + return this.emitter.on('did-remove-selection', callback); } // Extended: Calls your `callback` with each {Decoration} added to the editor. @@ -895,8 +1002,8 @@ class TextEditor { // * `decoration` {Decoration} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeDecorations (callback) { - return this.decorationManager.observeDecorations(callback) + observeDecorations(callback) { + return this.decorationManager.observeDecorations(callback); } // Extended: Calls your `callback` when a {Decoration} is added to the editor. @@ -905,8 +1012,8 @@ class TextEditor { // * `decoration` {Decoration} that was added // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddDecoration (callback) { - return this.decorationManager.onDidAddDecoration(callback) + onDidAddDecoration(callback) { + return this.decorationManager.onDidAddDecoration(callback); } // Extended: Calls your `callback` when a {Decoration} is removed from the editor. @@ -915,14 +1022,14 @@ class TextEditor { // * `decoration` {Decoration} that was removed // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidRemoveDecoration (callback) { - return this.decorationManager.onDidRemoveDecoration(callback) + onDidRemoveDecoration(callback) { + return this.decorationManager.onDidRemoveDecoration(callback); } // Called by DecorationManager when a decoration is added. - didAddDecoration (decoration) { + didAddDecoration(decoration) { if (this.component && decoration.isType('block')) { - this.component.addBlockDecoration(decoration) + this.component.addBlockDecoration(decoration); } } @@ -932,41 +1039,49 @@ class TextEditor { // * `placeholderText` {String} new text // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangePlaceholderText (callback) { - return this.emitter.on('did-change-placeholder-text', callback) + onDidChangePlaceholderText(callback) { + return this.emitter.on('did-change-placeholder-text', callback); } - onDidChangeScrollTop (callback) { - Grim.deprecate('This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.') - return this.getElement().onDidChangeScrollTop(callback) + onDidChangeScrollTop(callback) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.' + ); + return this.getElement().onDidChangeScrollTop(callback); } - onDidChangeScrollLeft (callback) { - Grim.deprecate('This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.') - return this.getElement().onDidChangeScrollLeft(callback) + onDidChangeScrollLeft(callback) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.' + ); + return this.getElement().onDidChangeScrollLeft(callback); } - onDidRequestAutoscroll (callback) { - return this.emitter.on('did-request-autoscroll', callback) + onDidRequestAutoscroll(callback) { + return this.emitter.on('did-request-autoscroll', callback); } // TODO Remove once the tabs package no longer uses .on subscriptions - onDidChangeIcon (callback) { - return this.emitter.on('did-change-icon', callback) + onDidChangeIcon(callback) { + return this.emitter.on('did-change-icon', callback); } - onDidUpdateDecorations (callback) { - return this.decorationManager.onDidUpdateDecorations(callback) + onDidUpdateDecorations(callback) { + return this.decorationManager.onDidUpdateDecorations(callback); } // Retrieves the current buffer's URI. - getURI () { return this.buffer.getUri() } + getURI() { + return this.buffer.getUri(); + } // Create an {TextEditor} with its initial state based on this object - copy () { - const displayLayer = this.displayLayer.copy() - const selectionsMarkerLayer = displayLayer.getMarkerLayer(this.buffer.getMarkerLayer(this.selectionsMarkerLayer.id).copy().id) - const softTabs = this.getSoftTabs() + copy() { + const displayLayer = this.displayLayer.copy(); + const selectionsMarkerLayer = displayLayer.getMarkerLayer( + this.buffer.getMarkerLayer(this.selectionsMarkerLayer.id).copy().id + ); + const softTabs = this.getSoftTabs(); return new TextEditor({ buffer: this.buffer, selectionsMarkerLayer, @@ -981,49 +1096,61 @@ class TextEditor { autoWidth: this.autoWidth, autoHeight: this.autoHeight, showCursorOnSelection: this.showCursorOnSelection - }) + }); } // Controls visibility based on the given {Boolean}. - setVisible (visible) { + setVisible(visible) { if (visible) { - const languageMode = this.buffer.getLanguageMode() - if (languageMode.startTokenizing) languageMode.startTokenizing() + const languageMode = this.buffer.getLanguageMode(); + if (languageMode.startTokenizing) languageMode.startTokenizing(); } } - setMini (mini) { - this.update({mini}) + setMini(mini) { + this.update({ mini }); } - isMini () { return this.mini } - - setReadOnly (readOnly) { - this.update({readOnly}) + isMini() { + return this.mini; } - isReadOnly () { return this.readOnly } - - enableKeyboardInput (enabled) { - this.update({keyboardInputEnabled: enabled}) + setReadOnly(readOnly) { + this.update({ readOnly }); } - isKeyboardInputEnabled () { return this.keyboardInputEnabled } - - onDidChangeMini (callback) { - return this.emitter.on('did-change-mini', callback) + isReadOnly() { + return this.readOnly; } - setLineNumberGutterVisible (lineNumberGutterVisible) { this.update({lineNumberGutterVisible}) } - - isLineNumberGutterVisible () { return this.lineNumberGutter.isVisible() } - - anyLineNumberGutterVisible () { - return this.getGutters().some(gutter => gutter.type === 'line-number' && gutter.visible) + enableKeyboardInput(enabled) { + this.update({ keyboardInputEnabled: enabled }); } - onDidChangeLineNumberGutterVisible (callback) { - return this.emitter.on('did-change-line-number-gutter-visible', callback) + isKeyboardInputEnabled() { + return this.keyboardInputEnabled; + } + + onDidChangeMini(callback) { + return this.emitter.on('did-change-mini', callback); + } + + setLineNumberGutterVisible(lineNumberGutterVisible) { + this.update({ lineNumberGutterVisible }); + } + + isLineNumberGutterVisible() { + return this.lineNumberGutter.isVisible(); + } + + anyLineNumberGutterVisible() { + return this.getGutters().some( + gutter => gutter.type === 'line-number' && gutter.visible + ); + } + + onDidChangeLineNumberGutterVisible(callback) { + return this.emitter.on('did-change-line-number-gutter-visible', callback); } // Essential: Calls your `callback` when a {Gutter} is added to the editor. @@ -1033,8 +1160,8 @@ class TextEditor { // * `gutter` {Gutter} that currently exists/was added. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeGutters (callback) { - return this.gutterContainer.observeGutters(callback) + observeGutters(callback) { + return this.gutterContainer.observeGutters(callback); } // Essential: Calls your `callback` when a {Gutter} is added to the editor. @@ -1043,8 +1170,8 @@ class TextEditor { // * `gutter` {Gutter} that was added. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddGutter (callback) { - return this.gutterContainer.onDidAddGutter(callback) + onDidAddGutter(callback) { + return this.gutterContainer.onDidAddGutter(callback); } // Essential: Calls your `callback` when a {Gutter} is removed from the editor. @@ -1053,8 +1180,8 @@ class TextEditor { // * `name` The name of the {Gutter} that was removed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidRemoveGutter (callback) { - return this.gutterContainer.onDidRemoveGutter(callback) + onDidRemoveGutter(callback) { + return this.gutterContainer.onDidRemoveGutter(callback); } // Set the number of characters that can be displayed horizontally in the @@ -1062,14 +1189,16 @@ class TextEditor { // // * `editorWidthInChars` A {Number} representing the width of the // {TextEditorElement} in characters. - setEditorWidthInChars (editorWidthInChars) { this.update({editorWidthInChars}) } + setEditorWidthInChars(editorWidthInChars) { + this.update({ editorWidthInChars }); + } // Returns the editor width in characters. - getEditorWidthInChars () { + getEditorWidthInChars() { if (this.width != null && this.defaultCharWidth > 0) { - return Math.max(0, Math.floor(this.width / this.defaultCharWidth)) + return Math.max(0, Math.floor(this.width / this.defaultCharWidth)); } else { - return this.editorWidthInChars + return this.editorWidthInChars; } } @@ -1078,8 +1207,8 @@ class TextEditor { */ // Essential: Retrieves the current {TextBuffer}. - getBuffer () { - return this.buffer + getBuffer() { + return this.buffer; } /* @@ -1093,8 +1222,8 @@ class TextEditor { // unsaved, its title is "untitled". // // Returns a {String}. - getTitle () { - return this.getFileName() || 'untitled' + getTitle() { + return this.getFileName() || 'untitled'; } // Essential: Get unique title for display in other parts of the UI, such as @@ -1107,67 +1236,88 @@ class TextEditor { // * "" when other buffers have this file name. // // Returns a {String} - getLongTitle () { + getLongTitle() { if (this.getPath()) { - const fileName = this.getFileName() + const fileName = this.getFileName(); - let myPathSegments - const openEditorPathSegmentsWithSameFilename = [] + let myPathSegments; + const openEditorPathSegmentsWithSameFilename = []; for (const textEditor of atom.workspace.getTextEditors()) { if (textEditor.getFileName() === fileName) { - const pathSegments = fs.tildify(textEditor.getDirectoryPath()).split(path.sep) - openEditorPathSegmentsWithSameFilename.push(pathSegments) - if (textEditor === this) myPathSegments = pathSegments + const pathSegments = fs + .tildify(textEditor.getDirectoryPath()) + .split(path.sep); + openEditorPathSegmentsWithSameFilename.push(pathSegments); + if (textEditor === this) myPathSegments = pathSegments; } } - if (!myPathSegments || openEditorPathSegmentsWithSameFilename.length === 1) return fileName + if ( + !myPathSegments || + openEditorPathSegmentsWithSameFilename.length === 1 + ) + return fileName; - let commonPathSegmentCount - for (let i = 0, {length} = myPathSegments; i < length; i++) { - const myPathSegment = myPathSegments[i] - if (openEditorPathSegmentsWithSameFilename.some(segments => (segments.length === i + 1) || (segments[i] !== myPathSegment))) { - commonPathSegmentCount = i - break + let commonPathSegmentCount; + for (let i = 0, { length } = myPathSegments; i < length; i++) { + const myPathSegment = myPathSegments[i]; + if ( + openEditorPathSegmentsWithSameFilename.some( + segments => + segments.length === i + 1 || segments[i] !== myPathSegment + ) + ) { + commonPathSegmentCount = i; + break; } } - return `${fileName} \u2014 ${path.join(...myPathSegments.slice(commonPathSegmentCount))}` + return `${fileName} \u2014 ${path.join( + ...myPathSegments.slice(commonPathSegmentCount) + )}`; } else { - return 'untitled' + return 'untitled'; } } // Essential: Returns the {String} path of this editor's text buffer. - getPath () { - return this.buffer.getPath() + getPath() { + return this.buffer.getPath(); } - getFileName () { - const fullPath = this.getPath() - if (fullPath) return path.basename(fullPath) + getFileName() { + const fullPath = this.getPath(); + if (fullPath) return path.basename(fullPath); } - getDirectoryPath () { - const fullPath = this.getPath() - if (fullPath) return path.dirname(fullPath) + getDirectoryPath() { + const fullPath = this.getPath(); + if (fullPath) return path.dirname(fullPath); } // Extended: Returns the {String} character set encoding of this editor's text // buffer. - getEncoding () { return this.buffer.getEncoding() } + getEncoding() { + return this.buffer.getEncoding(); + } // Extended: Set the character set encoding to use in this editor's text // buffer. // // * `encoding` The {String} character set encoding name such as 'utf8' - setEncoding (encoding) { this.buffer.setEncoding(encoding) } + setEncoding(encoding) { + this.buffer.setEncoding(encoding); + } // Essential: Returns {Boolean} `true` if this editor has been modified. - isModified () { return this.buffer.isModified() } + isModified() { + return this.buffer.isModified(); + } // Essential: Returns {Boolean} `true` if this editor has no content. - isEmpty () { return this.buffer.isEmpty() } + isEmpty() { + return this.buffer.isEmpty(); + } /* Section: File Operations @@ -1176,132 +1326,171 @@ class TextEditor { // Essential: Saves the editor's text buffer. // // See {TextBuffer::save} for more details. - save () { return this.buffer.save() } + save() { + return this.buffer.save(); + } // Essential: Saves the editor's text buffer as the given path. // // See {TextBuffer::saveAs} for more details. // // * `filePath` A {String} path. - saveAs (filePath) { return this.buffer.saveAs(filePath) } + saveAs(filePath) { + return this.buffer.saveAs(filePath); + } // Determine whether the user should be prompted to save before closing // this editor. - shouldPromptToSave ({windowCloseRequested, projectHasPaths} = {}) { - if (windowCloseRequested && projectHasPaths && atom.stateStore.isConnected()) { - return this.buffer.isInConflict() + shouldPromptToSave({ windowCloseRequested, projectHasPaths } = {}) { + if ( + windowCloseRequested && + projectHasPaths && + atom.stateStore.isConnected() + ) { + return this.buffer.isInConflict(); } else { - return this.isModified() && !this.buffer.hasMultipleEditors() + return this.isModified() && !this.buffer.hasMultipleEditors(); } } // Returns an {Object} to configure dialog shown when this editor is saved // via {Pane::saveItemAs}. - getSaveDialogOptions () { return {} } + getSaveDialogOptions() { + return {}; + } /* Section: Reading Text */ // Essential: Returns a {String} representing the entire contents of the editor. - getText () { return this.buffer.getText() } + getText() { + return this.buffer.getText(); + } // Essential: Get the text in the given {Range} in buffer coordinates. // // * `range` A {Range} or range-compatible {Array}. // // Returns a {String}. - getTextInBufferRange (range) { - return this.buffer.getTextInRange(range) + getTextInBufferRange(range) { + return this.buffer.getTextInRange(range); } // Essential: Returns a {Number} representing the number of lines in the buffer. - getLineCount () { return this.buffer.getLineCount() } + getLineCount() { + return this.buffer.getLineCount(); + } // Essential: Returns a {Number} representing the number of screen lines in the // editor. This accounts for folds. - getScreenLineCount () { return this.displayLayer.getScreenLineCount() } + getScreenLineCount() { + return this.displayLayer.getScreenLineCount(); + } - getApproximateScreenLineCount () { return this.displayLayer.getApproximateScreenLineCount() } + getApproximateScreenLineCount() { + return this.displayLayer.getApproximateScreenLineCount(); + } // Essential: Returns a {Number} representing the last zero-indexed buffer row // number of the editor. - getLastBufferRow () { return this.buffer.getLastRow() } + getLastBufferRow() { + return this.buffer.getLastRow(); + } // Essential: Returns a {Number} representing the last zero-indexed screen row // number of the editor. - getLastScreenRow () { return this.getScreenLineCount() - 1 } + getLastScreenRow() { + return this.getScreenLineCount() - 1; + } // Essential: Returns a {String} representing the contents of the line at the // given buffer row. // // * `bufferRow` A {Number} representing a zero-indexed buffer row. - lineTextForBufferRow (bufferRow) { return this.buffer.lineForRow(bufferRow) } + lineTextForBufferRow(bufferRow) { + return this.buffer.lineForRow(bufferRow); + } // Essential: Returns a {String} representing the contents of the line at the // given screen row. // // * `screenRow` A {Number} representing a zero-indexed screen row. - lineTextForScreenRow (screenRow) { - const screenLine = this.screenLineForScreenRow(screenRow) - if (screenLine) return screenLine.lineText + lineTextForScreenRow(screenRow) { + const screenLine = this.screenLineForScreenRow(screenRow); + if (screenLine) return screenLine.lineText; } - logScreenLines (start = 0, end = this.getLastScreenRow()) { + logScreenLines(start = 0, end = this.getLastScreenRow()) { for (let row = start; row <= end; row++) { - const line = this.lineTextForScreenRow(row) - console.log(row, this.bufferRowForScreenRow(row), line, line.length) + const line = this.lineTextForScreenRow(row); + console.log(row, this.bufferRowForScreenRow(row), line, line.length); } } - tokensForScreenRow (screenRow) { - const tokens = [] - let lineTextIndex = 0 - const currentTokenScopes = [] - const {lineText, tags} = this.screenLineForScreenRow(screenRow) + tokensForScreenRow(screenRow) { + const tokens = []; + let lineTextIndex = 0; + const currentTokenScopes = []; + const { lineText, tags } = this.screenLineForScreenRow(screenRow); for (const tag of tags) { if (this.displayLayer.isOpenTag(tag)) { - currentTokenScopes.push(this.displayLayer.classNameForTag(tag)) + currentTokenScopes.push(this.displayLayer.classNameForTag(tag)); } else if (this.displayLayer.isCloseTag(tag)) { - currentTokenScopes.pop() + currentTokenScopes.pop(); } else { tokens.push({ text: lineText.substr(lineTextIndex, tag), scopes: currentTokenScopes.slice() - }) - lineTextIndex += tag + }); + lineTextIndex += tag; } } - return tokens + return tokens; } - screenLineForScreenRow (screenRow) { - return this.displayLayer.getScreenLine(screenRow) + screenLineForScreenRow(screenRow) { + return this.displayLayer.getScreenLine(screenRow); } - bufferRowForScreenRow (screenRow) { - return this.displayLayer.translateScreenPosition(Point(screenRow, 0)).row + bufferRowForScreenRow(screenRow) { + return this.displayLayer.translateScreenPosition(Point(screenRow, 0)).row; } - bufferRowsForScreenRows (startScreenRow, endScreenRow) { - return this.displayLayer.bufferRowsForScreenRows(startScreenRow, endScreenRow + 1) + bufferRowsForScreenRows(startScreenRow, endScreenRow) { + return this.displayLayer.bufferRowsForScreenRows( + startScreenRow, + endScreenRow + 1 + ); } - screenRowForBufferRow (row) { - return this.displayLayer.translateBufferPosition(Point(row, 0)).row + screenRowForBufferRow(row) { + return this.displayLayer.translateBufferPosition(Point(row, 0)).row; } - getRightmostScreenPosition () { return this.displayLayer.getRightmostScreenPosition() } + getRightmostScreenPosition() { + return this.displayLayer.getRightmostScreenPosition(); + } - getApproximateRightmostScreenPosition () { return this.displayLayer.getApproximateRightmostScreenPosition() } + getApproximateRightmostScreenPosition() { + return this.displayLayer.getApproximateRightmostScreenPosition(); + } - getMaxScreenLineLength () { return this.getRightmostScreenPosition().column } + getMaxScreenLineLength() { + return this.getRightmostScreenPosition().column; + } - getLongestScreenRow () { return this.getRightmostScreenPosition().row } + getLongestScreenRow() { + return this.getRightmostScreenPosition().row; + } - getApproximateLongestScreenRow () { return this.getApproximateRightmostScreenPosition().row } + getApproximateLongestScreenRow() { + return this.getApproximateRightmostScreenPosition().row; + } - lineLengthForScreenRow (screenRow) { return this.displayLayer.lineLengthForScreenRow(screenRow) } + lineLengthForScreenRow(screenRow) { + return this.displayLayer.lineLengthForScreenRow(screenRow); + } // Returns the range for the given buffer row. // @@ -1309,30 +1498,38 @@ class TextEditor { // * `options` (optional) An options hash with an `includeNewline` key. // // Returns a {Range}. - bufferRangeForBufferRow (row, options) { - return this.buffer.rangeForRow(row, options && options.includeNewline) + bufferRangeForBufferRow(row, options) { + return this.buffer.rangeForRow(row, options && options.includeNewline); } // Get the text in the given {Range}. // // Returns a {String}. - getTextInRange (range) { return this.buffer.getTextInRange(range) } + getTextInRange(range) { + return this.buffer.getTextInRange(range); + } // {Delegates to: TextBuffer.isRowBlank} - isBufferRowBlank (bufferRow) { return this.buffer.isRowBlank(bufferRow) } + isBufferRowBlank(bufferRow) { + return this.buffer.isRowBlank(bufferRow); + } // {Delegates to: TextBuffer.nextNonBlankRow} - nextNonBlankBufferRow (bufferRow) { return this.buffer.nextNonBlankRow(bufferRow) } + nextNonBlankBufferRow(bufferRow) { + return this.buffer.nextNonBlankRow(bufferRow); + } // {Delegates to: TextBuffer.getEndPosition} - getEofBufferPosition () { return this.buffer.getEndPosition() } + getEofBufferPosition() { + return this.buffer.getEndPosition(); + } // Essential: Get the {Range} of the paragraph surrounding the most recently added // cursor. // // Returns a {Range}. - getCurrentParagraphBufferRange () { - return this.getLastCursor().getCurrentParagraphBufferRange() + getCurrentParagraphBufferRange() { + return this.getLastCursor().getCurrentParagraphBufferRange(); } /* @@ -1344,9 +1541,9 @@ class TextEditor { // * `text` A {String} to replace with // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - setText (text, options = {}) { - if (!this.ensureWritable('setText', options)) return - return this.buffer.setText(text) + setText(text, options = {}) { + if (!this.ensureWritable('setText', options)) return; + return this.buffer.setText(text); } // Essential: Set the text in the given {Range} in buffer coordinates. @@ -1359,9 +1556,9 @@ class TextEditor { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) // // Returns the {Range} of the newly-inserted text. - setTextInBufferRange (range, text, options = {}) { - if (!this.ensureWritable('setTextInBufferRange', options)) return - return this.getBuffer().setTextInRange(range, text, options) + setTextInBufferRange(range, text, options = {}) { + if (!this.ensureWritable('setTextInBufferRange', options)) return; + return this.getBuffer().setTextInRange(range, text, options); } // Essential: For each selection, replace the selected text with the given text. @@ -1370,36 +1567,38 @@ class TextEditor { // * `options` (optional) See {Selection::insertText}. // // Returns a {Range} when the text has been inserted. Returns a {Boolean} `false` when the text has not been inserted. - insertText (text, options = {}) { - if (!this.ensureWritable('insertText', options)) return - if (!this.emitWillInsertTextEvent(text)) return false + insertText(text, options = {}) { + if (!this.ensureWritable('insertText', options)) return; + if (!this.emitWillInsertTextEvent(text)) return false; - let groupLastChanges = false + let groupLastChanges = false; if (options.undo === 'skip') { - options = Object.assign({}, options) - delete options.undo - groupLastChanges = true + options = Object.assign({}, options); + delete options.undo; + groupLastChanges = true; } - const groupingInterval = options.groupUndo ? this.undoGroupingInterval : 0 - if (options.autoIndentNewline == null) options.autoIndentNewline = this.shouldAutoIndent() - if (options.autoDecreaseIndent == null) options.autoDecreaseIndent = this.shouldAutoIndent() + const groupingInterval = options.groupUndo ? this.undoGroupingInterval : 0; + if (options.autoIndentNewline == null) + options.autoIndentNewline = this.shouldAutoIndent(); + if (options.autoDecreaseIndent == null) + options.autoDecreaseIndent = this.shouldAutoIndent(); const result = this.mutateSelectedText(selection => { - const range = selection.insertText(text, options) - const didInsertEvent = {text, range} - this.emitter.emit('did-insert-text', didInsertEvent) - return range - }, groupingInterval) - if (groupLastChanges) this.buffer.groupLastChanges() - return result + const range = selection.insertText(text, options); + const didInsertEvent = { text, range }; + this.emitter.emit('did-insert-text', didInsertEvent); + return range; + }, groupingInterval); + if (groupLastChanges) this.buffer.groupLastChanges(); + return result; } // Essential: For each selection, replace the selected text with a newline. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - insertNewline (options = {}) { - return this.insertText('\n', options) + insertNewline(options = {}) { + return this.insertText('\n', options); } // Essential: For each selection, if the selection is empty, delete the character @@ -1407,9 +1606,9 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - delete (options = {}) { - if (!this.ensureWritable('delete', options)) return - return this.mutateSelectedText(selection => selection.delete(options)) + delete(options = {}) { + if (!this.ensureWritable('delete', options)) return; + return this.mutateSelectedText(selection => selection.delete(options)); } // Essential: For each selection, if the selection is empty, delete the character @@ -1417,9 +1616,9 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - backspace (options = {}) { - if (!this.ensureWritable('backspace', options)) return - return this.mutateSelectedText(selection => selection.backspace(options)) + backspace(options = {}) { + if (!this.ensureWritable('backspace', options)) return; + return this.mutateSelectedText(selection => selection.backspace(options)); } // Extended: Mutate the text of all the selections in a single transaction. @@ -1430,12 +1629,14 @@ class TextEditor { // * `fn` A {Function} that will be called once for each {Selection}. The first // argument will be a {Selection} and the second argument will be the // {Number} index of that selection. - mutateSelectedText (fn, groupingInterval = 0) { + mutateSelectedText(fn, groupingInterval = 0) { return this.mergeIntersectingSelections(() => { return this.transact(groupingInterval, () => { - return this.getSelectionsOrderedByBufferPosition().map((selection, index) => fn(selection, index)) - }) - }) + return this.getSelectionsOrderedByBufferPosition().map( + (selection, index) => fn(selection, index) + ); + }); + }); } // Move lines intersecting the most recent selection or multiple selections @@ -1443,72 +1644,91 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - moveLineUp (options = {}) { - if (!this.ensureWritable('moveLineUp', options)) return + moveLineUp(options = {}) { + if (!this.ensureWritable('moveLineUp', options)) return; - const selections = this.getSelectedBufferRanges().sort((a, b) => a.compare(b)) + const selections = this.getSelectedBufferRanges().sort((a, b) => + a.compare(b) + ); - if (selections[0].start.row === 0) return - if (selections[selections.length - 1].start.row === this.getLastBufferRow() && this.buffer.getLastLine() === '') return + if (selections[0].start.row === 0) return; + if ( + selections[selections.length - 1].start.row === this.getLastBufferRow() && + this.buffer.getLastLine() === '' + ) + return; this.transact(() => { - const newSelectionRanges = [] + const newSelectionRanges = []; while (selections.length > 0) { // Find selections spanning a contiguous set of lines - const selection = selections.shift() - const selectionsToMove = [selection] + const selection = selections.shift(); + const selectionsToMove = [selection]; - while (selection.end.row === (selections[0] != null ? selections[0].start.row : undefined)) { - selectionsToMove.push(selections[0]) - selection.end.row = selections[0].end.row - selections.shift() + while ( + selection.end.row === + (selections[0] != null ? selections[0].start.row : undefined) + ) { + selectionsToMove.push(selections[0]); + selection.end.row = selections[0].end.row; + selections.shift(); } // Compute the buffer range spanned by all these selections, expanding it // so that it includes any folded region that intersects them. - let startRow = selection.start.row - let endRow = selection.end.row - if (selection.end.row > selection.start.row && selection.end.column === 0) { + let startRow = selection.start.row; + let endRow = selection.end.row; + if ( + selection.end.row > selection.start.row && + selection.end.column === 0 + ) { // Don't move the last line of a multi-line selection if the selection ends at column 0 - endRow-- + endRow--; } - startRow = this.displayLayer.findBoundaryPrecedingBufferRow(startRow) - endRow = this.displayLayer.findBoundaryFollowingBufferRow(endRow + 1) - const linesRange = new Range(Point(startRow, 0), Point(endRow, 0)) + startRow = this.displayLayer.findBoundaryPrecedingBufferRow(startRow); + endRow = this.displayLayer.findBoundaryFollowingBufferRow(endRow + 1); + const linesRange = new Range(Point(startRow, 0), Point(endRow, 0)); // If selected line range is preceded by a fold, one line above on screen // could be multiple lines in the buffer. - const precedingRow = this.displayLayer.findBoundaryPrecedingBufferRow(startRow - 1) - const insertDelta = linesRange.start.row - precedingRow + const precedingRow = this.displayLayer.findBoundaryPrecedingBufferRow( + startRow - 1 + ); + const insertDelta = linesRange.start.row - precedingRow; // Any folds in the text that is moved will need to be re-created. // It includes the folds that were intersecting with the selection. const rangesToRefold = this.displayLayer .destroyFoldsIntersectingBufferRange(linesRange) - .map(range => range.translate([-insertDelta, 0])) + .map(range => range.translate([-insertDelta, 0])); // Delete lines spanned by selection and insert them on the preceding buffer row - let lines = this.buffer.getTextInRange(linesRange) - if (lines[lines.length - 1] !== '\n') { lines += this.buffer.lineEndingForRow(linesRange.end.row - 2) } - this.buffer.delete(linesRange) - this.buffer.insert([precedingRow, 0], lines) + let lines = this.buffer.getTextInRange(linesRange); + if (lines[lines.length - 1] !== '\n') { + lines += this.buffer.lineEndingForRow(linesRange.end.row - 2); + } + this.buffer.delete(linesRange); + this.buffer.insert([precedingRow, 0], lines); // Restore folds that existed before the lines were moved for (let rangeToRefold of rangesToRefold) { - this.displayLayer.foldBufferRange(rangeToRefold) + this.displayLayer.foldBufferRange(rangeToRefold); } for (const selectionToMove of selectionsToMove) { - newSelectionRanges.push(selectionToMove.translate([-insertDelta, 0])) + newSelectionRanges.push(selectionToMove.translate([-insertDelta, 0])); } } - this.setSelectedBufferRanges(newSelectionRanges, {autoscroll: false, preserveFolds: true}) - if (this.shouldAutoIndent()) this.autoIndentSelectedRows() - this.scrollToBufferPosition([newSelectionRanges[0].start.row, 0]) - }) + this.setSelectedBufferRanges(newSelectionRanges, { + autoscroll: false, + preserveFolds: true + }); + if (this.shouldAutoIndent()) this.autoIndentSelectedRows(); + this.scrollToBufferPosition([newSelectionRanges[0].start.row, 0]); + }); } // Move lines intersecting the most recent selection or multiple selections @@ -1516,104 +1736,123 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - moveLineDown (options = {}) { - if (!this.ensureWritable('moveLineDown', options)) return + moveLineDown(options = {}) { + if (!this.ensureWritable('moveLineDown', options)) return; - const selections = this.getSelectedBufferRanges() - selections.sort((a, b) => b.compare(a)) + const selections = this.getSelectedBufferRanges(); + selections.sort((a, b) => b.compare(a)); this.transact(() => { - this.consolidateSelections() - const newSelectionRanges = [] + this.consolidateSelections(); + const newSelectionRanges = []; while (selections.length > 0) { // Find selections spanning a contiguous set of lines - const selection = selections.shift() - const selectionsToMove = [selection] + const selection = selections.shift(); + const selectionsToMove = [selection]; // if the current selection start row matches the next selections' end row - make them one selection - while (selection.start.row === (selections[0] != null ? selections[0].end.row : undefined)) { - selectionsToMove.push(selections[0]) - selection.start.row = selections[0].start.row - selections.shift() + while ( + selection.start.row === + (selections[0] != null ? selections[0].end.row : undefined) + ) { + selectionsToMove.push(selections[0]); + selection.start.row = selections[0].start.row; + selections.shift(); } // Compute the buffer range spanned by all these selections, expanding it // so that it includes any folded region that intersects them. - let startRow = selection.start.row - let endRow = selection.end.row - if (selection.end.row > selection.start.row && selection.end.column === 0) { + let startRow = selection.start.row; + let endRow = selection.end.row; + if ( + selection.end.row > selection.start.row && + selection.end.column === 0 + ) { // Don't move the last line of a multi-line selection if the selection ends at column 0 - endRow-- + endRow--; } - startRow = this.displayLayer.findBoundaryPrecedingBufferRow(startRow) - endRow = this.displayLayer.findBoundaryFollowingBufferRow(endRow + 1) - const linesRange = new Range(Point(startRow, 0), Point(endRow, 0)) + startRow = this.displayLayer.findBoundaryPrecedingBufferRow(startRow); + endRow = this.displayLayer.findBoundaryFollowingBufferRow(endRow + 1); + const linesRange = new Range(Point(startRow, 0), Point(endRow, 0)); // If selected line range is followed by a fold, one line below on screen // could be multiple lines in the buffer. But at the same time, if the // next buffer row is wrapped, one line in the buffer can represent many // screen rows. - const followingRow = Math.min(this.buffer.getLineCount(), this.displayLayer.findBoundaryFollowingBufferRow(endRow + 1)) - const insertDelta = followingRow - linesRange.end.row + const followingRow = Math.min( + this.buffer.getLineCount(), + this.displayLayer.findBoundaryFollowingBufferRow(endRow + 1) + ); + const insertDelta = followingRow - linesRange.end.row; // Any folds in the text that is moved will need to be re-created. // It includes the folds that were intersecting with the selection. const rangesToRefold = this.displayLayer .destroyFoldsIntersectingBufferRange(linesRange) - .map(range => range.translate([insertDelta, 0])) + .map(range => range.translate([insertDelta, 0])); // Delete lines spanned by selection and insert them on the following correct buffer row - let lines = this.buffer.getTextInRange(linesRange) + let lines = this.buffer.getTextInRange(linesRange); if (followingRow - 1 === this.buffer.getLastRow()) { - lines = `\n${lines}` + lines = `\n${lines}`; } - this.buffer.insert([followingRow, 0], lines) - this.buffer.delete(linesRange) + this.buffer.insert([followingRow, 0], lines); + this.buffer.delete(linesRange); // Restore folds that existed before the lines were moved for (let rangeToRefold of rangesToRefold) { - this.displayLayer.foldBufferRange(rangeToRefold) + this.displayLayer.foldBufferRange(rangeToRefold); } for (const selectionToMove of selectionsToMove) { - newSelectionRanges.push(selectionToMove.translate([insertDelta, 0])) + newSelectionRanges.push(selectionToMove.translate([insertDelta, 0])); } } - this.setSelectedBufferRanges(newSelectionRanges, {autoscroll: false, preserveFolds: true}) - if (this.shouldAutoIndent()) this.autoIndentSelectedRows() - this.scrollToBufferPosition([newSelectionRanges[0].start.row - 1, 0]) - }) + this.setSelectedBufferRanges(newSelectionRanges, { + autoscroll: false, + preserveFolds: true + }); + if (this.shouldAutoIndent()) this.autoIndentSelectedRows(); + this.scrollToBufferPosition([newSelectionRanges[0].start.row - 1, 0]); + }); } // Move any active selections one column to the left. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - moveSelectionLeft (options = {}) { - if (!this.ensureWritable('moveSelectionLeft', options)) return - const selections = this.getSelectedBufferRanges() - const noSelectionAtStartOfLine = selections.every(selection => selection.start.column !== 0) + moveSelectionLeft(options = {}) { + if (!this.ensureWritable('moveSelectionLeft', options)) return; + const selections = this.getSelectedBufferRanges(); + const noSelectionAtStartOfLine = selections.every( + selection => selection.start.column !== 0 + ); - const translationDelta = [0, -1] - const translatedRanges = [] + const translationDelta = [0, -1]; + const translatedRanges = []; if (noSelectionAtStartOfLine) { this.transact(() => { for (let selection of selections) { - const charToLeftOfSelection = new Range(selection.start.translate(translationDelta), selection.start) - const charTextToLeftOfSelection = this.buffer.getTextInRange(charToLeftOfSelection) + const charToLeftOfSelection = new Range( + selection.start.translate(translationDelta), + selection.start + ); + const charTextToLeftOfSelection = this.buffer.getTextInRange( + charToLeftOfSelection + ); - this.buffer.insert(selection.end, charTextToLeftOfSelection) - this.buffer.delete(charToLeftOfSelection) - translatedRanges.push(selection.translate(translationDelta)) + this.buffer.insert(selection.end, charTextToLeftOfSelection); + this.buffer.delete(charToLeftOfSelection); + translatedRanges.push(selection.translate(translationDelta)); } - this.setSelectedBufferRanges(translatedRanges) - }) + this.setSelectedBufferRanges(translatedRanges); + }); } } @@ -1621,29 +1860,36 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - moveSelectionRight (options = {}) { - if (!this.ensureWritable('moveSelectionRight', options)) return - const selections = this.getSelectedBufferRanges() + moveSelectionRight(options = {}) { + if (!this.ensureWritable('moveSelectionRight', options)) return; + const selections = this.getSelectedBufferRanges(); const noSelectionAtEndOfLine = selections.every(selection => { - return selection.end.column !== this.buffer.lineLengthForRow(selection.end.row) - }) + return ( + selection.end.column !== this.buffer.lineLengthForRow(selection.end.row) + ); + }); - const translationDelta = [0, 1] - const translatedRanges = [] + const translationDelta = [0, 1]; + const translatedRanges = []; if (noSelectionAtEndOfLine) { this.transact(() => { for (let selection of selections) { - const charToRightOfSelection = new Range(selection.end, selection.end.translate(translationDelta)) - const charTextToRightOfSelection = this.buffer.getTextInRange(charToRightOfSelection) + const charToRightOfSelection = new Range( + selection.end, + selection.end.translate(translationDelta) + ); + const charTextToRightOfSelection = this.buffer.getTextInRange( + charToRightOfSelection + ); - this.buffer.delete(charToRightOfSelection) - this.buffer.insert(selection.start, charTextToRightOfSelection) - translatedRanges.push(selection.translate(translationDelta)) + this.buffer.delete(charToRightOfSelection); + this.buffer.insert(selection.start, charTextToRightOfSelection); + translatedRanges.push(selection.translate(translationDelta)); } - this.setSelectedBufferRanges(translatedRanges) - }) + this.setSelectedBufferRanges(translatedRanges); + }); } } @@ -1651,65 +1897,80 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - duplicateLines (options = {}) { - if (!this.ensureWritable('duplicateLines', options)) return + duplicateLines(options = {}) { + if (!this.ensureWritable('duplicateLines', options)) return; this.transact(() => { - const selections = this.getSelectionsOrderedByBufferPosition() - const previousSelectionRanges = [] + const selections = this.getSelectionsOrderedByBufferPosition(); + const previousSelectionRanges = []; - let i = selections.length - 1 + let i = selections.length - 1; while (i >= 0) { - const j = i - previousSelectionRanges[i] = selections[i].getBufferRange() + const j = i; + previousSelectionRanges[i] = selections[i].getBufferRange(); if (selections[i].isEmpty()) { - const {start} = selections[i].getScreenRange() - selections[i].setScreenRange([[start.row, 0], [start.row + 1, 0]], {preserveFolds: true}) + const { start } = selections[i].getScreenRange(); + selections[i].setScreenRange([[start.row, 0], [start.row + 1, 0]], { + preserveFolds: true + }); } - let [startRow, endRow] = selections[i].getBufferRowRange() - endRow++ + let [startRow, endRow] = selections[i].getBufferRowRange(); + endRow++; while (i > 0) { - const [previousSelectionStartRow, previousSelectionEndRow] = selections[i - 1].getBufferRowRange() + const [ + previousSelectionStartRow, + previousSelectionEndRow + ] = selections[i - 1].getBufferRowRange(); if (previousSelectionEndRow === startRow) { - startRow = previousSelectionStartRow - previousSelectionRanges[i - 1] = selections[i - 1].getBufferRange() - i-- + startRow = previousSelectionStartRow; + previousSelectionRanges[i - 1] = selections[i - 1].getBufferRange(); + i--; } else { - break + break; } } - const intersectingFolds = this.displayLayer.foldsIntersectingBufferRange([[startRow, 0], [endRow, 0]]) - let textToDuplicate = this.getTextInBufferRange([[startRow, 0], [endRow, 0]]) - if (endRow > this.getLastBufferRow()) textToDuplicate = `\n${textToDuplicate}` - this.buffer.insert([endRow, 0], textToDuplicate) + const intersectingFolds = this.displayLayer.foldsIntersectingBufferRange( + [[startRow, 0], [endRow, 0]] + ); + let textToDuplicate = this.getTextInBufferRange([ + [startRow, 0], + [endRow, 0] + ]); + if (endRow > this.getLastBufferRow()) + textToDuplicate = `\n${textToDuplicate}`; + this.buffer.insert([endRow, 0], textToDuplicate); - const insertedRowCount = endRow - startRow + const insertedRowCount = endRow - startRow; for (let k = i; k <= j; k++) { - selections[k].setBufferRange(previousSelectionRanges[k].translate([insertedRowCount, 0])) + selections[k].setBufferRange( + previousSelectionRanges[k].translate([insertedRowCount, 0]) + ); } for (const fold of intersectingFolds) { - const foldRange = this.displayLayer.bufferRangeForFold(fold) - this.displayLayer.foldBufferRange(foldRange.translate([insertedRowCount, 0])) + const foldRange = this.displayLayer.bufferRangeForFold(fold); + this.displayLayer.foldBufferRange( + foldRange.translate([insertedRowCount, 0]) + ); } - i-- + i--; } - }) + }); } - replaceSelectedText (options, fn) { - this.mutateSelectedText((selection) => { - selection.getBufferRange() + replaceSelectedText(options, fn) { + this.mutateSelectedText(selection => { + selection.getBufferRange(); if (options && options.selectWordIfEmpty && selection.isEmpty()) { - selection.selectWord() + selection.selectWord(); } - const text = selection.getText() - selection.deleteSelectedText() - const range = selection.insertText(fn(text)) - selection.setBufferRange(range) - }) + const text = selection.getText(); + selection.deleteSelectedText(); + const range = selection.insertText(fn(text)); + selection.setBufferRange(range); + }); } // Split multi-line selections into one selection per line. @@ -1717,22 +1978,26 @@ class TextEditor { // Operates on all selections. This method breaks apart all multi-line // selections to create multiple single-line selections that cumulatively cover // the same original area. - splitSelectionsIntoLines () { + splitSelectionsIntoLines() { this.mergeIntersectingSelections(() => { for (const selection of this.getSelections()) { - const range = selection.getBufferRange() - if (range.isSingleLine()) continue + const range = selection.getBufferRange(); + if (range.isSingleLine()) continue; - const {start, end} = range - this.addSelectionForBufferRange([start, [start.row, Infinity]]) - let {row} = start + const { start, end } = range; + this.addSelectionForBufferRange([start, [start.row, Infinity]]); + let { row } = start; while (++row < end.row) { - this.addSelectionForBufferRange([[row, 0], [row, Infinity]]) + this.addSelectionForBufferRange([[row, 0], [row, Infinity]]); } - if (end.column !== 0) this.addSelectionForBufferRange([[end.row, 0], [end.row, end.column]]) - selection.destroy() + if (end.column !== 0) + this.addSelectionForBufferRange([ + [end.row, 0], + [end.row, end.column] + ]); + selection.destroy(); } - }) + }); } // Extended: For each selection, transpose the selected text. @@ -1742,19 +2007,25 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - transpose (options = {}) { - if (!this.ensureWritable('transpose', options)) return + transpose(options = {}) { + if (!this.ensureWritable('transpose', options)) return; this.mutateSelectedText(selection => { if (selection.isEmpty()) { - selection.selectRight() - const text = selection.getText() - selection.delete() - selection.cursor.moveLeft() - selection.insertText(text) + selection.selectRight(); + const text = selection.getText(); + selection.delete(); + selection.cursor.moveLeft(); + selection.insertText(text); } else { - selection.insertText(selection.getText().split('').reverse().join('')) + selection.insertText( + selection + .getText() + .split('') + .reverse() + .join('') + ); } - }) + }); } // Extended: Convert the selected text to upper case. @@ -1764,9 +2035,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - upperCase (options = {}) { - if (!this.ensureWritable('upperCase', options)) return - this.replaceSelectedText({selectWordIfEmpty: true}, text => text.toUpperCase(options)) + upperCase(options = {}) { + if (!this.ensureWritable('upperCase', options)) return; + this.replaceSelectedText({ selectWordIfEmpty: true }, text => + text.toUpperCase(options) + ); } // Extended: Convert the selected text to lower case. @@ -1776,9 +2049,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - lowerCase (options = {}) { - if (!this.ensureWritable('lowerCase', options)) return - this.replaceSelectedText({selectWordIfEmpty: true}, text => text.toLowerCase(options)) + lowerCase(options = {}) { + if (!this.ensureWritable('lowerCase', options)) return; + this.replaceSelectedText({ selectWordIfEmpty: true }, text => + text.toLowerCase(options) + ); } // Extended: Toggle line comments for rows intersecting selections. @@ -1787,9 +2062,9 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - toggleLineCommentsInSelection (options = {}) { - if (!this.ensureWritable('toggleLineCommentsInSelection', options)) return - this.mutateSelectedText(selection => selection.toggleLineComments(options)) + toggleLineCommentsInSelection(options = {}) { + if (!this.ensureWritable('toggleLineCommentsInSelection', options)) return; + this.mutateSelectedText(selection => selection.toggleLineComments(options)); } // Convert multiple lines to a single line. @@ -1803,47 +2078,50 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - joinLines (options = {}) { - if (!this.ensureWritable('joinLines', options)) return - this.mutateSelectedText(selection => selection.joinLines()) + joinLines(options = {}) { + if (!this.ensureWritable('joinLines', options)) return; + this.mutateSelectedText(selection => selection.joinLines()); } // Extended: For each cursor, insert a newline at beginning the following line. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - insertNewlineBelow (options = {}) { - if (!this.ensureWritable('insertNewlineBelow', options)) return + insertNewlineBelow(options = {}) { + if (!this.ensureWritable('insertNewlineBelow', options)) return; this.transact(() => { - this.moveToEndOfLine() - this.insertNewline(options) - }) + this.moveToEndOfLine(); + this.insertNewline(options); + }); } // Extended: For each cursor, insert a newline at the end of the preceding line. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - insertNewlineAbove (options = {}) { - if (!this.ensureWritable('insertNewlineAbove', options)) return + insertNewlineAbove(options = {}) { + if (!this.ensureWritable('insertNewlineAbove', options)) return; this.transact(() => { - const bufferRow = this.getCursorBufferPosition().row - const indentLevel = this.indentationForBufferRow(bufferRow) - const onFirstLine = bufferRow === 0 + const bufferRow = this.getCursorBufferPosition().row; + const indentLevel = this.indentationForBufferRow(bufferRow); + const onFirstLine = bufferRow === 0; - this.moveToBeginningOfLine() - this.moveLeft() - this.insertNewline(options) + this.moveToBeginningOfLine(); + this.moveLeft(); + this.insertNewline(options); - if (this.shouldAutoIndent() && (this.indentationForBufferRow(bufferRow) < indentLevel)) { - this.setIndentationForBufferRow(bufferRow, indentLevel) + if ( + this.shouldAutoIndent() && + this.indentationForBufferRow(bufferRow) < indentLevel + ) { + this.setIndentationForBufferRow(bufferRow, indentLevel); } if (onFirstLine) { - this.moveUp() - this.moveToEndOfLine() + this.moveUp(); + this.moveToEndOfLine(); } - }) + }); } // Extended: For each selection, if the selection is empty, delete all characters @@ -1852,9 +2130,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToBeginningOfWord (options = {}) { - if (!this.ensureWritable('deleteToBeginningOfWord', options)) return - this.mutateSelectedText(selection => selection.deleteToBeginningOfWord(options)) + deleteToBeginningOfWord(options = {}) { + if (!this.ensureWritable('deleteToBeginningOfWord', options)) return; + this.mutateSelectedText(selection => + selection.deleteToBeginningOfWord(options) + ); } // Extended: Similar to {::deleteToBeginningOfWord}, but deletes only back to the @@ -1862,9 +2142,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToPreviousWordBoundary (options = {}) { - if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return - this.mutateSelectedText(selection => selection.deleteToPreviousWordBoundary(options)) + deleteToPreviousWordBoundary(options = {}) { + if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return; + this.mutateSelectedText(selection => + selection.deleteToPreviousWordBoundary(options) + ); } // Extended: Similar to {::deleteToEndOfWord}, but deletes only up to the @@ -1872,9 +2154,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToNextWordBoundary (options = {}) { - if (!this.ensureWritable('deleteToNextWordBoundary', options)) return - this.mutateSelectedText(selection => selection.deleteToNextWordBoundary(options)) + deleteToNextWordBoundary(options = {}) { + if (!this.ensureWritable('deleteToNextWordBoundary', options)) return; + this.mutateSelectedText(selection => + selection.deleteToNextWordBoundary(options) + ); } // Extended: For each selection, if the selection is empty, delete all characters @@ -1883,9 +2167,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToBeginningOfSubword (options = {}) { - if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return - this.mutateSelectedText(selection => selection.deleteToBeginningOfSubword(options)) + deleteToBeginningOfSubword(options = {}) { + if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return; + this.mutateSelectedText(selection => + selection.deleteToBeginningOfSubword(options) + ); } // Extended: For each selection, if the selection is empty, delete all characters @@ -1894,9 +2180,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToEndOfSubword (options = {}) { - if (!this.ensureWritable('deleteToEndOfSubword', options)) return - this.mutateSelectedText(selection => selection.deleteToEndOfSubword(options)) + deleteToEndOfSubword(options = {}) { + if (!this.ensureWritable('deleteToEndOfSubword', options)) return; + this.mutateSelectedText(selection => + selection.deleteToEndOfSubword(options) + ); } // Extended: For each selection, if the selection is empty, delete all characters @@ -1905,9 +2193,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToBeginningOfLine (options = {}) { - if (!this.ensureWritable('deleteToBeginningOfLine', options)) return - this.mutateSelectedText(selection => selection.deleteToBeginningOfLine(options)) + deleteToBeginningOfLine(options = {}) { + if (!this.ensureWritable('deleteToBeginningOfLine', options)) return; + this.mutateSelectedText(selection => + selection.deleteToBeginningOfLine(options) + ); } // Extended: For each selection, if the selection is not empty, deletes the @@ -1917,9 +2207,9 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToEndOfLine (options = {}) { - if (!this.ensureWritable('deleteToEndOfLine', options)) return - this.mutateSelectedText(selection => selection.deleteToEndOfLine(options)) + deleteToEndOfLine(options = {}) { + if (!this.ensureWritable('deleteToEndOfLine', options)) return; + this.mutateSelectedText(selection => selection.deleteToEndOfLine(options)); } // Extended: For each selection, if the selection is empty, delete all characters @@ -1928,38 +2218,38 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteToEndOfWord (options = {}) { - if (!this.ensureWritable('deleteToEndOfWord', options)) return - this.mutateSelectedText(selection => selection.deleteToEndOfWord(options)) + deleteToEndOfWord(options = {}) { + if (!this.ensureWritable('deleteToEndOfWord', options)) return; + this.mutateSelectedText(selection => selection.deleteToEndOfWord(options)); } // Extended: Delete all lines intersecting selections. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - deleteLine (options = {}) { - if (!this.ensureWritable('deleteLine', options)) return - this.mergeSelectionsOnSameRows() - this.mutateSelectedText(selection => selection.deleteLine(options)) + deleteLine(options = {}) { + if (!this.ensureWritable('deleteLine', options)) return; + this.mergeSelectionsOnSameRows(); + this.mutateSelectedText(selection => selection.deleteLine(options)); } // Private: Ensure that this editor is not marked read-only before allowing a buffer modification to occur. If // the editor is read-only, require an explicit opt-in option to proceed (`bypassReadOnly`) or throw an Error. - ensureWritable (methodName, opts) { + ensureWritable(methodName, opts) { if (!opts.bypassReadOnly && this.isReadOnly()) { if (atom.inDevMode() || atom.inSpecMode()) { - const e = new Error('Attempt to mutate a read-only TextEditor') + const e = new Error('Attempt to mutate a read-only TextEditor'); e.detail = `Your package is attempting to call ${methodName} on an editor that has been marked read-only. ` + 'Pass {bypassReadOnly: true} to modify it anyway, or test editors with .isReadOnly() before attempting ' + - 'modifications.' - throw e + 'modifications.'; + throw e; } - return false + return false; } - return true + return true; } /* @@ -1970,20 +2260,24 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - undo (options = {}) { - if (!this.ensureWritable('undo', options)) return - this.avoidMergingSelections(() => this.buffer.undo({selectionsMarkerLayer: this.selectionsMarkerLayer})) - this.getLastSelection().autoscroll() + undo(options = {}) { + if (!this.ensureWritable('undo', options)) return; + this.avoidMergingSelections(() => + this.buffer.undo({ selectionsMarkerLayer: this.selectionsMarkerLayer }) + ); + this.getLastSelection().autoscroll(); } // Essential: Redo the last change. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) - redo (options = {}) { - if (!this.ensureWritable('redo', options)) return - this.avoidMergingSelections(() => this.buffer.redo({selectionsMarkerLayer: this.selectionsMarkerLayer})) - this.getLastSelection().autoscroll() + redo(options = {}) { + if (!this.ensureWritable('redo', options)) return; + this.avoidMergingSelections(() => + this.buffer.redo({ selectionsMarkerLayer: this.selectionsMarkerLayer }) + ); + this.getLastSelection().autoscroll(); } // Extended: Batch multiple operations as a single undo/redo step. @@ -1998,26 +2292,30 @@ class TextEditor { // with a positive `groupingInterval` is committed while the previous transaction is // still 'groupable', the two transactions are merged with respect to undo and redo. // * `fn` A {Function} to call inside the transaction. - transact (groupingInterval, fn) { - const options = {selectionsMarkerLayer: this.selectionsMarkerLayer} + transact(groupingInterval, fn) { + const options = { selectionsMarkerLayer: this.selectionsMarkerLayer }; if (typeof groupingInterval === 'function') { - fn = groupingInterval + fn = groupingInterval; } else { - options.groupingInterval = groupingInterval + options.groupingInterval = groupingInterval; } - return this.buffer.transact(options, fn) + return this.buffer.transact(options, fn); } // Extended: Abort an open transaction, undoing any operations performed so far // within the transaction. - abortTransaction () { return this.buffer.abortTransaction() } + abortTransaction() { + return this.buffer.abortTransaction(); + } // Extended: Create a pointer to the current state of the buffer for use // with {::revertToCheckpoint} and {::groupChangesSinceCheckpoint}. // // Returns a checkpoint value. - createCheckpoint () { - return this.buffer.createCheckpoint({selectionsMarkerLayer: this.selectionsMarkerLayer}) + createCheckpoint() { + return this.buffer.createCheckpoint({ + selectionsMarkerLayer: this.selectionsMarkerLayer + }); } // Extended: Revert the buffer to the state it was in when the given @@ -2031,7 +2329,9 @@ class TextEditor { // * `checkpoint` The checkpoint to revert to. // // Returns a {Boolean} indicating whether the operation succeeded. - revertToCheckpoint (checkpoint) { return this.buffer.revertToCheckpoint(checkpoint) } + revertToCheckpoint(checkpoint) { + return this.buffer.revertToCheckpoint(checkpoint); + } // Extended: Group all changes since the given checkpoint into a single // transaction for purposes of undo/redo. @@ -2042,8 +2342,10 @@ class TextEditor { // * `checkpoint` The checkpoint from which to group changes. // // Returns a {Boolean} indicating whether the operation succeeded. - groupChangesSinceCheckpoint (checkpoint) { - return this.buffer.groupChangesSinceCheckpoint(checkpoint, {selectionsMarkerLayer: this.selectionsMarkerLayer}) + groupChangesSinceCheckpoint(checkpoint) { + return this.buffer.groupChangesSinceCheckpoint(checkpoint, { + selectionsMarkerLayer: this.selectionsMarkerLayer + }); } /* @@ -2060,21 +2362,33 @@ class TextEditor { // * `options` (optional) An options hash for {::clipScreenPosition}. // // Returns a {Point}. - screenPositionForBufferPosition (bufferPosition, options) { + screenPositionForBufferPosition(bufferPosition, options) { if (options && options.clip) { - Grim.deprecate('The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.') - if (options.clipDirection) options.clipDirection = options.clip + Grim.deprecate( + 'The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.' + ); + if (options.clipDirection) options.clipDirection = options.clip; } if (options && options.wrapAtSoftNewlines != null) { - Grim.deprecate("The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapAtSoftNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapAtSoftNewlines + ? 'forward' + : 'backward'; } if (options && options.wrapBeyondNewlines != null) { - Grim.deprecate("The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapBeyondNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapBeyondNewlines + ? 'forward' + : 'backward'; } - return this.displayLayer.translateBufferPosition(bufferPosition, options) + return this.displayLayer.translateBufferPosition(bufferPosition, options); } // Essential: Convert a position in screen-coordinates to buffer-coordinates. @@ -2085,21 +2399,33 @@ class TextEditor { // * `options` (optional) An options hash for {::clipScreenPosition}. // // Returns a {Point}. - bufferPositionForScreenPosition (screenPosition, options) { + bufferPositionForScreenPosition(screenPosition, options) { if (options && options.clip) { - Grim.deprecate('The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.') - if (options.clipDirection) options.clipDirection = options.clip + Grim.deprecate( + 'The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.' + ); + if (options.clipDirection) options.clipDirection = options.clip; } if (options && options.wrapAtSoftNewlines != null) { - Grim.deprecate("The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapAtSoftNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapAtSoftNewlines + ? 'forward' + : 'backward'; } if (options && options.wrapBeyondNewlines != null) { - Grim.deprecate("The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapBeyondNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapBeyondNewlines + ? 'forward' + : 'backward'; } - return this.displayLayer.translateScreenPosition(screenPosition, options) + return this.displayLayer.translateScreenPosition(screenPosition, options); } // Essential: Convert a range in buffer-coordinates to screen-coordinates. @@ -2107,11 +2433,14 @@ class TextEditor { // * `bufferRange` {Range} in buffer coordinates to translate into screen coordinates. // // Returns a {Range}. - screenRangeForBufferRange (bufferRange, options) { - bufferRange = Range.fromObject(bufferRange) - const start = this.screenPositionForBufferPosition(bufferRange.start, options) - const end = this.screenPositionForBufferPosition(bufferRange.end, options) - return new Range(start, end) + screenRangeForBufferRange(bufferRange, options) { + bufferRange = Range.fromObject(bufferRange); + const start = this.screenPositionForBufferPosition( + bufferRange.start, + options + ); + const end = this.screenPositionForBufferPosition(bufferRange.end, options); + return new Range(start, end); } // Essential: Convert a range in screen-coordinates to buffer-coordinates. @@ -2119,11 +2448,11 @@ class TextEditor { // * `screenRange` {Range} in screen coordinates to translate into buffer coordinates. // // Returns a {Range}. - bufferRangeForScreenRange (screenRange) { - screenRange = Range.fromObject(screenRange) - const start = this.bufferPositionForScreenPosition(screenRange.start) - const end = this.bufferPositionForScreenPosition(screenRange.end) - return new Range(start, end) + bufferRangeForScreenRange(screenRange) { + screenRange = Range.fromObject(screenRange); + const start = this.bufferPositionForScreenPosition(screenRange.start); + const end = this.bufferPositionForScreenPosition(screenRange.end); + return new Range(start, end); } // Extended: Clip the given {Point} to a valid position in the buffer. @@ -2145,7 +2474,9 @@ class TextEditor { // * `bufferPosition` The {Point} representing the position to clip. // // Returns a {Point}. - clipBufferPosition (bufferPosition) { return this.buffer.clipPosition(bufferPosition) } + clipBufferPosition(bufferPosition) { + return this.buffer.clipPosition(bufferPosition); + } // Extended: Clip the start and end of the given range to valid positions in the // buffer. See {::clipBufferPosition} for more information. @@ -2153,7 +2484,9 @@ class TextEditor { // * `range` The {Range} to clip. // // Returns a {Range}. - clipBufferRange (range) { return this.buffer.clipRange(range) } + clipBufferRange(range) { + return this.buffer.clipRange(range); + } // Extended: Clip the given {Point} to a valid position on screen. // @@ -2180,21 +2513,33 @@ class TextEditor { // Defaults to `'closest'`. // // Returns a {Point}. - clipScreenPosition (screenPosition, options) { + clipScreenPosition(screenPosition, options) { if (options && options.clip) { - Grim.deprecate('The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.') - if (options.clipDirection) options.clipDirection = options.clip + Grim.deprecate( + 'The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.' + ); + if (options.clipDirection) options.clipDirection = options.clip; } if (options && options.wrapAtSoftNewlines != null) { - Grim.deprecate("The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapAtSoftNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapAtSoftNewlines + ? 'forward' + : 'backward'; } if (options && options.wrapBeyondNewlines != null) { - Grim.deprecate("The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapBeyondNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapBeyondNewlines + ? 'forward' + : 'backward'; } - return this.displayLayer.clipScreenPosition(screenPosition, options) + return this.displayLayer.clipScreenPosition(screenPosition, options); } // Extended: Clip the start and end of the given range to valid positions on screen. @@ -2204,11 +2549,14 @@ class TextEditor { // * `options` (optional) See {::clipScreenPosition} `options`. // // Returns a {Range}. - clipScreenRange (screenRange, options) { - screenRange = Range.fromObject(screenRange) - const start = this.displayLayer.clipScreenPosition(screenRange.start, options) - const end = this.displayLayer.clipScreenPosition(screenRange.end, options) - return Range(start, end) + clipScreenRange(screenRange, options) { + screenRange = Range.fromObject(screenRange); + const start = this.displayLayer.clipScreenPosition( + screenRange.start, + options + ); + const end = this.displayLayer.clipScreenPosition(screenRange.end, options); + return Range(start, end); } /* @@ -2292,8 +2640,8 @@ class TextEditor { // overflow the editor. Defaults to `true`. // // Returns the created {Decoration} object. - decorateMarker (marker, decorationParams) { - return this.decorationManager.decorateMarker(marker, decorationParams) + decorateMarker(marker, decorationParams) { + return this.decorationManager.decorateMarker(marker, decorationParams); } // Essential: Add a decoration to every marker in the given marker layer. Can @@ -2305,8 +2653,11 @@ class TextEditor { // {TextEditor::decorateMarker}, except the `type` cannot be `overlay` or `gutter`. // // Returns a {LayerDecoration}. - decorateMarkerLayer (markerLayer, decorationParams) { - return this.decorationManager.decorateMarkerLayer(markerLayer, decorationParams) + decorateMarkerLayer(markerLayer, decorationParams) { + return this.decorationManager.decorateMarkerLayer( + markerLayer, + decorationParams + ); } // Deprecated: Get all the decorations within a screen row range on the default @@ -2320,12 +2671,18 @@ class TextEditor { // where the keys are {DisplayMarker} IDs, and the values are an array of decoration // params objects attached to the marker. // Returns an empty object when no decorations are found - decorationsForScreenRowRange (startScreenRow, endScreenRow) { - return this.decorationManager.decorationsForScreenRowRange(startScreenRow, endScreenRow) + decorationsForScreenRowRange(startScreenRow, endScreenRow) { + return this.decorationManager.decorationsForScreenRowRange( + startScreenRow, + endScreenRow + ); } - decorationsStateForScreenRowRange (startScreenRow, endScreenRow) { - return this.decorationManager.decorationsStateForScreenRowRange(startScreenRow, endScreenRow) + decorationsStateForScreenRowRange(startScreenRow, endScreenRow) { + return this.decorationManager.decorationsStateForScreenRowRange( + startScreenRow, + endScreenRow + ); } // Extended: Get all decorations. @@ -2334,8 +2691,8 @@ class TextEditor { // the returned decorations' properties must match. // // Returns an {Array} of {Decoration}s. - getDecorations (propertyFilter) { - return this.decorationManager.getDecorations(propertyFilter) + getDecorations(propertyFilter) { + return this.decorationManager.getDecorations(propertyFilter); } // Extended: Get all decorations of type 'line'. @@ -2344,8 +2701,8 @@ class TextEditor { // the returned decorations' properties must match. // // Returns an {Array} of {Decoration}s. - getLineDecorations (propertyFilter) { - return this.decorationManager.getLineDecorations(propertyFilter) + getLineDecorations(propertyFilter) { + return this.decorationManager.getLineDecorations(propertyFilter); } // Extended: Get all decorations of type 'line-number'. @@ -2354,8 +2711,8 @@ class TextEditor { // the returned decorations' properties must match. // // Returns an {Array} of {Decoration}s. - getLineNumberDecorations (propertyFilter) { - return this.decorationManager.getLineNumberDecorations(propertyFilter) + getLineNumberDecorations(propertyFilter) { + return this.decorationManager.getLineNumberDecorations(propertyFilter); } // Extended: Get all decorations of type 'highlight'. @@ -2364,8 +2721,8 @@ class TextEditor { // the returned decorations' properties must match. // // Returns an {Array} of {Decoration}s. - getHighlightDecorations (propertyFilter) { - return this.decorationManager.getHighlightDecorations(propertyFilter) + getHighlightDecorations(propertyFilter) { + return this.decorationManager.getHighlightDecorations(propertyFilter); } // Extended: Get all decorations of type 'overlay'. @@ -2374,8 +2731,8 @@ class TextEditor { // the returned decorations' properties must match. // // Returns an {Array} of {Decoration}s. - getOverlayDecorations (propertyFilter) { - return this.decorationManager.getOverlayDecorations(propertyFilter) + getOverlayDecorations(propertyFilter) { + return this.decorationManager.getOverlayDecorations(propertyFilter); } /* @@ -2412,8 +2769,8 @@ class TextEditor { // start or start at the marker's end. This is the most fragile strategy. // // Returns a {DisplayMarker}. - markBufferRange (bufferRange, options) { - return this.defaultMarkerLayer.markBufferRange(bufferRange, options) + markBufferRange(bufferRange, options) { + return this.defaultMarkerLayer.markBufferRange(bufferRange, options); } // Essential: Create a marker on the default marker layer with the given range @@ -2446,8 +2803,8 @@ class TextEditor { // start or start at the marker's end. This is the most fragile strategy. // // Returns a {DisplayMarker}. - markScreenRange (screenRange, options) { - return this.defaultMarkerLayer.markScreenRange(screenRange, options) + markScreenRange(screenRange, options) { + return this.defaultMarkerLayer.markScreenRange(screenRange, options); } // Essential: Create a marker on the default marker layer with the given buffer @@ -2472,8 +2829,8 @@ class TextEditor { // start or start at the marker's end. This is the most fragile strategy. // // Returns a {DisplayMarker}. - markBufferPosition (bufferPosition, options) { - return this.defaultMarkerLayer.markBufferPosition(bufferPosition, options) + markBufferPosition(bufferPosition, options) { + return this.defaultMarkerLayer.markBufferPosition(bufferPosition, options); } // Essential: Create a marker on the default marker layer with the given screen @@ -2503,8 +2860,8 @@ class TextEditor { // Defaults to `'closest'`. // // Returns a {DisplayMarker}. - markScreenPosition (screenPosition, options) { - return this.defaultMarkerLayer.markScreenPosition(screenPosition, options) + markScreenPosition(screenPosition, options) { + return this.defaultMarkerLayer.markScreenPosition(screenPosition, options); } // Essential: Find all {DisplayMarker}s on the default marker layer that @@ -2529,34 +2886,34 @@ class TextEditor { // or {Array} of `[row, column]` in buffer coordinates. // // Returns an {Array} of {DisplayMarker}s - findMarkers (params) { - return this.defaultMarkerLayer.findMarkers(params) + findMarkers(params) { + return this.defaultMarkerLayer.findMarkers(params); } // Extended: Get the {DisplayMarker} on the default layer for the given // marker id. // // * `id` {Number} id of the marker - getMarker (id) { - return this.defaultMarkerLayer.getMarker(id) + getMarker(id) { + return this.defaultMarkerLayer.getMarker(id); } // Extended: Get all {DisplayMarker}s on the default marker layer. Consider // using {::findMarkers} - getMarkers () { - return this.defaultMarkerLayer.getMarkers() + getMarkers() { + return this.defaultMarkerLayer.getMarkers(); } // Extended: Get the number of markers in the default marker layer. // // Returns a {Number}. - getMarkerCount () { - return this.defaultMarkerLayer.getMarkerCount() + getMarkerCount() { + return this.defaultMarkerLayer.getMarkerCount(); } - destroyMarker (id) { - const marker = this.getMarker(id) - if (marker) marker.destroy() + destroyMarker(id) { + const marker = this.getMarker(id); + if (marker) marker.destroy(); } // Essential: Create a marker layer to group related markers. @@ -2571,8 +2928,8 @@ class TextEditor { // it via {::getMarkerLayer}. // // Returns a {DisplayMarkerLayer}. - addMarkerLayer (options) { - return this.displayLayer.addMarkerLayer(options) + addMarkerLayer(options) { + return this.displayLayer.addMarkerLayer(options); } // Essential: Get a {DisplayMarkerLayer} by id. @@ -2581,8 +2938,8 @@ class TextEditor { // // Returns a {DisplayMarkerLayer} or `undefined` if no layer exists with the // given id. - getMarkerLayer (id) { - return this.displayLayer.getMarkerLayer(id) + getMarkerLayer(id) { + return this.displayLayer.getMarkerLayer(id); } // Essential: Get the default {DisplayMarkerLayer}. @@ -2591,8 +2948,8 @@ class TextEditor { // layer. // // Returns a {DisplayMarkerLayer}. - getDefaultMarkerLayer () { - return this.defaultMarkerLayer + getDefaultMarkerLayer() { + return this.defaultMarkerLayer; } /* @@ -2603,15 +2960,15 @@ class TextEditor { // coordinates. // // Returns a {Point} - getCursorBufferPosition () { - return this.getLastCursor().getBufferPosition() + getCursorBufferPosition() { + return this.getLastCursor().getBufferPosition(); } // Essential: Get the position of all the cursor positions in buffer coordinates. // // Returns {Array} of {Point}s in the order they were added - getCursorBufferPositions () { - return this.getCursors().map((cursor) => cursor.getBufferPosition()) + getCursorBufferPositions() { + return this.getCursors().map(cursor => cursor.getBufferPosition()); } // Essential: Move the cursor to the given position in buffer coordinates. @@ -2622,8 +2979,10 @@ class TextEditor { // * `options` (optional) An {Object} containing the following keys: // * `autoscroll` Determines whether the editor scrolls to the new cursor's // position. Defaults to true. - setCursorBufferPosition (position, options) { - return this.moveCursors(cursor => cursor.setBufferPosition(position, options)) + setCursorBufferPosition(position, options) { + return this.moveCursors(cursor => + cursor.setBufferPosition(position, options) + ); } // Essential: Get a {Cursor} at given screen coordinates {Point} @@ -2631,10 +2990,10 @@ class TextEditor { // * `position` A {Point} or {Array} of `[row, column]` // // Returns the first matched {Cursor} or undefined - getCursorAtScreenPosition (position) { - const selection = this.getSelectionAtScreenPosition(position) + getCursorAtScreenPosition(position) { + const selection = this.getSelectionAtScreenPosition(position); if (selection && selection.getHeadScreenPosition().isEqual(position)) { - return selection.cursor + return selection.cursor; } } @@ -2642,15 +3001,15 @@ class TextEditor { // coordinates. // // Returns a {Point}. - getCursorScreenPosition () { - return this.getLastCursor().getScreenPosition() + getCursorScreenPosition() { + return this.getLastCursor().getScreenPosition(); } // Essential: Get the position of all the cursor positions in screen coordinates. // // Returns {Array} of {Point}s in the order the cursors were added - getCursorScreenPositions () { - return this.getCursors().map((cursor) => cursor.getScreenPosition()) + getCursorScreenPositions() { + return this.getCursors().map(cursor => cursor.getScreenPosition()); } // Essential: Move the cursor to the given position in screen coordinates. @@ -2661,21 +3020,35 @@ class TextEditor { // * `options` (optional) An {Object} combining options for {::clipScreenPosition} with: // * `autoscroll` Determines whether the editor scrolls to the new cursor's // position. Defaults to true. - setCursorScreenPosition (position, options) { + setCursorScreenPosition(position, options) { if (options && options.clip) { - Grim.deprecate('The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.') - if (options.clipDirection) options.clipDirection = options.clip + Grim.deprecate( + 'The `clip` parameter has been deprecated and will be removed soon. Please, use `clipDirection` instead.' + ); + if (options.clipDirection) options.clipDirection = options.clip; } if (options && options.wrapAtSoftNewlines != null) { - Grim.deprecate("The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapAtSoftNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapAtSoftNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapAtSoftNewlines + ? 'forward' + : 'backward'; } if (options && options.wrapBeyondNewlines != null) { - Grim.deprecate("The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead.") - if (options.clipDirection) options.clipDirection = options.wrapBeyondNewlines ? 'forward' : 'backward' + Grim.deprecate( + "The `wrapBeyondNewlines` parameter has been deprecated and will be removed soon. Please, use `clipDirection: 'forward'` instead." + ); + if (options.clipDirection) + options.clipDirection = options.wrapBeyondNewlines + ? 'forward' + : 'backward'; } - return this.moveCursors(cursor => cursor.setScreenPosition(position, options)) + return this.moveCursors(cursor => + cursor.setScreenPosition(position, options) + ); } // Essential: Add a cursor at the given position in buffer coordinates. @@ -2683,10 +3056,13 @@ class TextEditor { // * `bufferPosition` A {Point} or {Array} of `[row, column]` // // Returns a {Cursor}. - addCursorAtBufferPosition (bufferPosition, options) { - this.selectionsMarkerLayer.markBufferPosition(bufferPosition, {invalidate: 'never'}) - if (!options || options.autoscroll !== false) this.getLastSelection().cursor.autoscroll() - return this.getLastSelection().cursor + addCursorAtBufferPosition(bufferPosition, options) { + this.selectionsMarkerLayer.markBufferPosition(bufferPosition, { + invalidate: 'never' + }); + if (!options || options.autoscroll !== false) + this.getLastSelection().cursor.autoscroll(); + return this.getLastSelection().cursor; } // Essential: Add a cursor at the position in screen coordinates. @@ -2694,78 +3070,89 @@ class TextEditor { // * `screenPosition` A {Point} or {Array} of `[row, column]` // // Returns a {Cursor}. - addCursorAtScreenPosition (screenPosition, options) { - this.selectionsMarkerLayer.markScreenPosition(screenPosition, {invalidate: 'never'}) - if (!options || options.autoscroll !== false) this.getLastSelection().cursor.autoscroll() - return this.getLastSelection().cursor + addCursorAtScreenPosition(screenPosition, options) { + this.selectionsMarkerLayer.markScreenPosition(screenPosition, { + invalidate: 'never' + }); + if (!options || options.autoscroll !== false) + this.getLastSelection().cursor.autoscroll(); + return this.getLastSelection().cursor; } // Essential: Returns {Boolean} indicating whether or not there are multiple cursors. - hasMultipleCursors () { - return this.getCursors().length > 1 + hasMultipleCursors() { + return this.getCursors().length > 1; } // Essential: Move every cursor up one row in screen coordinates. // // * `lineCount` (optional) {Number} number of lines to move - moveUp (lineCount) { - return this.moveCursors(cursor => cursor.moveUp(lineCount, {moveToEndOfSelection: true})) + moveUp(lineCount) { + return this.moveCursors(cursor => + cursor.moveUp(lineCount, { moveToEndOfSelection: true }) + ); } // Essential: Move every cursor down one row in screen coordinates. // // * `lineCount` (optional) {Number} number of lines to move - moveDown (lineCount) { - return this.moveCursors(cursor => cursor.moveDown(lineCount, {moveToEndOfSelection: true})) + moveDown(lineCount) { + return this.moveCursors(cursor => + cursor.moveDown(lineCount, { moveToEndOfSelection: true }) + ); } // Essential: Move every cursor left one column. // // * `columnCount` (optional) {Number} number of columns to move (default: 1) - moveLeft (columnCount) { - return this.moveCursors(cursor => cursor.moveLeft(columnCount, {moveToEndOfSelection: true})) + moveLeft(columnCount) { + return this.moveCursors(cursor => + cursor.moveLeft(columnCount, { moveToEndOfSelection: true }) + ); } // Essential: Move every cursor right one column. // // * `columnCount` (optional) {Number} number of columns to move (default: 1) - moveRight (columnCount) { - return this.moveCursors(cursor => cursor.moveRight(columnCount, {moveToEndOfSelection: true})) + moveRight(columnCount) { + return this.moveCursors(cursor => + cursor.moveRight(columnCount, { moveToEndOfSelection: true }) + ); } // Essential: Move every cursor to the beginning of its line in buffer coordinates. - moveToBeginningOfLine () { - return this.moveCursors(cursor => cursor.moveToBeginningOfLine()) + moveToBeginningOfLine() { + return this.moveCursors(cursor => cursor.moveToBeginningOfLine()); } // Essential: Move every cursor to the beginning of its line in screen coordinates. - moveToBeginningOfScreenLine () { - return this.moveCursors(cursor => cursor.moveToBeginningOfScreenLine()) + moveToBeginningOfScreenLine() { + return this.moveCursors(cursor => cursor.moveToBeginningOfScreenLine()); } // Essential: Move every cursor to the first non-whitespace character of its line. - moveToFirstCharacterOfLine () { - return this.moveCursors(cursor => cursor.moveToFirstCharacterOfLine()) + moveToFirstCharacterOfLine() { + return this.moveCursors(cursor => cursor.moveToFirstCharacterOfLine()); } // Essential: Move every cursor to the end of its line in buffer coordinates. - moveToEndOfLine () { - return this.moveCursors(cursor => cursor.moveToEndOfLine()) + moveToEndOfLine() { + return this.moveCursors(cursor => cursor.moveToEndOfLine()); } // Essential: Move every cursor to the end of its line in screen coordinates. - moveToEndOfScreenLine () { - return this.moveCursors(cursor => cursor.moveToEndOfScreenLine()) + moveToEndOfScreenLine() { + return this.moveCursors(cursor => cursor.moveToEndOfScreenLine()); } // Essential: Move every cursor to the beginning of its surrounding word. - moveToBeginningOfWord () { - return this.moveCursors(cursor => cursor.moveToBeginningOfWord()) + moveToBeginningOfWord() { + return this.moveCursors(cursor => cursor.moveToBeginningOfWord()); } // Essential: Move every cursor to the end of its surrounding word. - moveToEndOfWord () { - return this.moveCursors(cursor => cursor.moveToEndOfWord()) + moveToEndOfWord() { + return this.moveCursors(cursor => cursor.moveToEndOfWord()); } // Cursor Extended @@ -2773,116 +3160,126 @@ class TextEditor { // Extended: Move every cursor to the top of the buffer. // // If there are multiple cursors, they will be merged into a single cursor. - moveToTop () { - return this.moveCursors(cursor => cursor.moveToTop()) + moveToTop() { + return this.moveCursors(cursor => cursor.moveToTop()); } // Extended: Move every cursor to the bottom of the buffer. // // If there are multiple cursors, they will be merged into a single cursor. - moveToBottom () { - return this.moveCursors(cursor => cursor.moveToBottom()) + moveToBottom() { + return this.moveCursors(cursor => cursor.moveToBottom()); } // Extended: Move every cursor to the beginning of the next word. - moveToBeginningOfNextWord () { - return this.moveCursors(cursor => cursor.moveToBeginningOfNextWord()) + moveToBeginningOfNextWord() { + return this.moveCursors(cursor => cursor.moveToBeginningOfNextWord()); } // Extended: Move every cursor to the previous word boundary. - moveToPreviousWordBoundary () { - return this.moveCursors(cursor => cursor.moveToPreviousWordBoundary()) + moveToPreviousWordBoundary() { + return this.moveCursors(cursor => cursor.moveToPreviousWordBoundary()); } // Extended: Move every cursor to the next word boundary. - moveToNextWordBoundary () { - return this.moveCursors(cursor => cursor.moveToNextWordBoundary()) + moveToNextWordBoundary() { + return this.moveCursors(cursor => cursor.moveToNextWordBoundary()); } // Extended: Move every cursor to the previous subword boundary. - moveToPreviousSubwordBoundary () { - return this.moveCursors(cursor => cursor.moveToPreviousSubwordBoundary()) + moveToPreviousSubwordBoundary() { + return this.moveCursors(cursor => cursor.moveToPreviousSubwordBoundary()); } // Extended: Move every cursor to the next subword boundary. - moveToNextSubwordBoundary () { - return this.moveCursors(cursor => cursor.moveToNextSubwordBoundary()) + moveToNextSubwordBoundary() { + return this.moveCursors(cursor => cursor.moveToNextSubwordBoundary()); } // Extended: Move every cursor to the beginning of the next paragraph. - moveToBeginningOfNextParagraph () { - return this.moveCursors(cursor => cursor.moveToBeginningOfNextParagraph()) + moveToBeginningOfNextParagraph() { + return this.moveCursors(cursor => cursor.moveToBeginningOfNextParagraph()); } // Extended: Move every cursor to the beginning of the previous paragraph. - moveToBeginningOfPreviousParagraph () { - return this.moveCursors(cursor => cursor.moveToBeginningOfPreviousParagraph()) + moveToBeginningOfPreviousParagraph() { + return this.moveCursors(cursor => + cursor.moveToBeginningOfPreviousParagraph() + ); } // Extended: Returns the most recently added {Cursor} - getLastCursor () { - this.createLastSelectionIfNeeded() - return _.last(this.cursors) + getLastCursor() { + this.createLastSelectionIfNeeded(); + return _.last(this.cursors); } // Extended: Returns the word surrounding the most recently added cursor. // // * `options` (optional) See {Cursor::getBeginningOfCurrentWordBufferPosition}. - getWordUnderCursor (options) { - return this.getTextInBufferRange(this.getLastCursor().getCurrentWordBufferRange(options)) + getWordUnderCursor(options) { + return this.getTextInBufferRange( + this.getLastCursor().getCurrentWordBufferRange(options) + ); } // Extended: Get an Array of all {Cursor}s. - getCursors () { - this.createLastSelectionIfNeeded() - return this.cursors.slice() + getCursors() { + this.createLastSelectionIfNeeded(); + return this.cursors.slice(); } // Extended: Get all {Cursor}s, ordered by their position in the buffer // instead of the order in which they were added. // // Returns an {Array} of {Selection}s. - getCursorsOrderedByBufferPosition () { - return this.getCursors().sort((a, b) => a.compare(b)) + getCursorsOrderedByBufferPosition() { + return this.getCursors().sort((a, b) => a.compare(b)); } - cursorsForScreenRowRange (startScreenRow, endScreenRow) { - const cursors = [] - for (let marker of this.selectionsMarkerLayer.findMarkers({intersectsScreenRowRange: [startScreenRow, endScreenRow]})) { - const cursor = this.cursorsByMarkerId.get(marker.id) - if (cursor) cursors.push(cursor) + cursorsForScreenRowRange(startScreenRow, endScreenRow) { + const cursors = []; + for (let marker of this.selectionsMarkerLayer.findMarkers({ + intersectsScreenRowRange: [startScreenRow, endScreenRow] + })) { + const cursor = this.cursorsByMarkerId.get(marker.id); + if (cursor) cursors.push(cursor); } - return cursors + return cursors; } // Add a cursor based on the given {DisplayMarker}. - addCursor (marker) { - const cursor = new Cursor({editor: this, marker, showCursorOnSelection: this.showCursorOnSelection}) - this.cursors.push(cursor) - this.cursorsByMarkerId.set(marker.id, cursor) - return cursor + addCursor(marker) { + const cursor = new Cursor({ + editor: this, + marker, + showCursorOnSelection: this.showCursorOnSelection + }); + this.cursors.push(cursor); + this.cursorsByMarkerId.set(marker.id, cursor); + return cursor; } - moveCursors (fn) { + moveCursors(fn) { return this.transact(() => { - this.getCursors().forEach(fn) - return this.mergeCursors() - }) + this.getCursors().forEach(fn); + return this.mergeCursors(); + }); } - cursorMoved (event) { - return this.emitter.emit('did-change-cursor-position', event) + cursorMoved(event) { + return this.emitter.emit('did-change-cursor-position', event); } // Merge cursors that have the same screen position - mergeCursors () { - const positions = {} + mergeCursors() { + const positions = {}; for (let cursor of this.getCursors()) { - const position = cursor.getBufferPosition().toString() + const position = cursor.getBufferPosition().toString(); if (positions.hasOwnProperty(position)) { - cursor.destroy() + cursor.destroy(); } else { - positions[position] = true + positions[position] = true; } } } @@ -2894,16 +3291,16 @@ class TextEditor { // Essential: Get the selected text of the most recently added selection. // // Returns a {String}. - getSelectedText () { - return this.getLastSelection().getText() + getSelectedText() { + return this.getLastSelection().getText(); } // Essential: Get the {Range} of the most recently added selection in buffer // coordinates. // // Returns a {Range}. - getSelectedBufferRange () { - return this.getLastSelection().getBufferRange() + getSelectedBufferRange() { + return this.getLastSelection().getBufferRange(); } // Essential: Get the {Range}s of all selections in buffer coordinates. @@ -2911,8 +3308,8 @@ class TextEditor { // The ranges are sorted by when the selections were added. Most recent at the end. // // Returns an {Array} of {Range}s. - getSelectedBufferRanges () { - return this.getSelections().map((selection) => selection.getBufferRange()) + getSelectedBufferRanges() { + return this.getSelections().map(selection => selection.getBufferRange()); } // Essential: Set the selected range in buffer coordinates. If there are multiple @@ -2924,8 +3321,8 @@ class TextEditor { // reversed orientation. // * `preserveFolds` A {Boolean}, which if `true` preserves the fold settings after the // selection is set. - setSelectedBufferRange (bufferRange, options) { - return this.setSelectedBufferRanges([bufferRange], options) + setSelectedBufferRange(bufferRange, options) { + return this.setSelectedBufferRanges([bufferRange], options); } // Essential: Set the selected ranges in buffer coordinates. If there are multiple @@ -2937,33 +3334,34 @@ class TextEditor { // reversed orientation. // * `preserveFolds` A {Boolean}, which if `true` preserves the fold settings after the // selection is set. - setSelectedBufferRanges (bufferRanges, options = {}) { - if (!bufferRanges.length) throw new Error('Passed an empty array to setSelectedBufferRanges') + setSelectedBufferRanges(bufferRanges, options = {}) { + if (!bufferRanges.length) + throw new Error('Passed an empty array to setSelectedBufferRanges'); - const selections = this.getSelections() + const selections = this.getSelections(); for (let selection of selections.slice(bufferRanges.length)) { - selection.destroy() + selection.destroy(); } this.mergeIntersectingSelections(options, () => { for (let i = 0; i < bufferRanges.length; i++) { - let bufferRange = bufferRanges[i] - bufferRange = Range.fromObject(bufferRange) + let bufferRange = bufferRanges[i]; + bufferRange = Range.fromObject(bufferRange); if (selections[i]) { - selections[i].setBufferRange(bufferRange, options) + selections[i].setBufferRange(bufferRange, options); } else { - this.addSelectionForBufferRange(bufferRange, options) + this.addSelectionForBufferRange(bufferRange, options); } } - }) + }); } // Essential: Get the {Range} of the most recently added selection in screen // coordinates. // // Returns a {Range}. - getSelectedScreenRange () { - return this.getLastSelection().getScreenRange() + getSelectedScreenRange() { + return this.getLastSelection().getScreenRange(); } // Essential: Get the {Range}s of all selections in screen coordinates. @@ -2971,8 +3369,8 @@ class TextEditor { // The ranges are sorted by when the selections were added. Most recent at the end. // // Returns an {Array} of {Range}s. - getSelectedScreenRanges () { - return this.getSelections().map((selection) => selection.getScreenRange()) + getSelectedScreenRanges() { + return this.getSelections().map(selection => selection.getScreenRange()); } // Essential: Set the selected range in screen coordinates. If there are multiple @@ -2982,8 +3380,11 @@ class TextEditor { // * `options` (optional) An options {Object}: // * `reversed` A {Boolean} indicating whether to create the selection in a // reversed orientation. - setSelectedScreenRange (screenRange, options) { - return this.setSelectedBufferRange(this.bufferRangeForScreenRange(screenRange, options), options) + setSelectedScreenRange(screenRange, options) { + return this.setSelectedBufferRange( + this.bufferRangeForScreenRange(screenRange, options), + options + ); } // Essential: Set the selected ranges in screen coordinates. If there are multiple @@ -2993,25 +3394,26 @@ class TextEditor { // * `options` (optional) An options {Object}: // * `reversed` A {Boolean} indicating whether to create the selection in a // reversed orientation. - setSelectedScreenRanges (screenRanges, options = {}) { - if (!screenRanges.length) throw new Error('Passed an empty array to setSelectedScreenRanges') + setSelectedScreenRanges(screenRanges, options = {}) { + if (!screenRanges.length) + throw new Error('Passed an empty array to setSelectedScreenRanges'); - const selections = this.getSelections() + const selections = this.getSelections(); for (let selection of selections.slice(screenRanges.length)) { - selection.destroy() + selection.destroy(); } this.mergeIntersectingSelections(options, () => { for (let i = 0; i < screenRanges.length; i++) { - let screenRange = screenRanges[i] - screenRange = Range.fromObject(screenRange) + let screenRange = screenRanges[i]; + screenRange = Range.fromObject(screenRange); if (selections[i]) { - selections[i].setScreenRange(screenRange, options) + selections[i].setScreenRange(screenRange, options); } else { - this.addSelectionForScreenRange(screenRange, options) + this.addSelectionForScreenRange(screenRange, options); } } - }) + }); } // Essential: Add a selection for the given range in buffer coordinates. @@ -3024,14 +3426,20 @@ class TextEditor { // selection is set. // // Returns the added {Selection}. - addSelectionForBufferRange (bufferRange, options = {}) { - bufferRange = Range.fromObject(bufferRange) + addSelectionForBufferRange(bufferRange, options = {}) { + bufferRange = Range.fromObject(bufferRange); if (!options.preserveFolds) { - this.displayLayer.destroyFoldsContainingBufferPositions([bufferRange.start, bufferRange.end], true) + this.displayLayer.destroyFoldsContainingBufferPositions( + [bufferRange.start, bufferRange.end], + true + ); } - this.selectionsMarkerLayer.markBufferRange(bufferRange, {invalidate: 'never', reversed: options.reversed != null ? options.reversed : false}) - if (options.autoscroll !== false) this.getLastSelection().autoscroll() - return this.getLastSelection() + this.selectionsMarkerLayer.markBufferRange(bufferRange, { + invalidate: 'never', + reversed: options.reversed != null ? options.reversed : false + }); + if (options.autoscroll !== false) this.getLastSelection().autoscroll(); + return this.getLastSelection(); } // Essential: Add a selection for the given range in screen coordinates. @@ -3043,8 +3451,11 @@ class TextEditor { // * `preserveFolds` A {Boolean}, which if `true` preserves the fold settings after the // selection is set. // Returns the added {Selection}. - addSelectionForScreenRange (screenRange, options = {}) { - return this.addSelectionForBufferRange(this.bufferRangeForScreenRange(screenRange), options) + addSelectionForScreenRange(screenRange, options = {}) { + return this.addSelectionForBufferRange( + this.bufferRangeForScreenRange(screenRange), + options + ); } // Essential: Select from the current cursor position to the given position in @@ -3053,10 +3464,12 @@ class TextEditor { // This method may merge selections that end up intersecting. // // * `position` An instance of {Point}, with a given `row` and `column`. - selectToBufferPosition (position) { - const lastSelection = this.getLastSelection() - lastSelection.selectToBufferPosition(position) - return this.mergeIntersectingSelections({reversed: lastSelection.isReversed()}) + selectToBufferPosition(position) { + const lastSelection = this.getLastSelection(); + lastSelection.selectToBufferPosition(position); + return this.mergeIntersectingSelections({ + reversed: lastSelection.isReversed() + }); } // Essential: Select from the current cursor position to the given position in @@ -3065,11 +3478,13 @@ class TextEditor { // This method may merge selections that end up intersecting. // // * `position` An instance of {Point}, with a given `row` and `column`. - selectToScreenPosition (position, options) { - const lastSelection = this.getLastSelection() - lastSelection.selectToScreenPosition(position, options) + selectToScreenPosition(position, options) { + const lastSelection = this.getLastSelection(); + lastSelection.selectToScreenPosition(position, options); if (!options || !options.suppressSelectionMerge) { - return this.mergeIntersectingSelections({reversed: lastSelection.isReversed()}) + return this.mergeIntersectingSelections({ + reversed: lastSelection.isReversed() + }); } } @@ -3079,8 +3494,10 @@ class TextEditor { // * `rowCount` (optional) {Number} number of rows to select (default: 1) // // This method may merge selections that end up intersecting. - selectUp (rowCount) { - return this.expandSelectionsBackward(selection => selection.selectUp(rowCount)) + selectUp(rowCount) { + return this.expandSelectionsBackward(selection => + selection.selectUp(rowCount) + ); } // Essential: Move the cursor of each selection one character downward while @@ -3089,8 +3506,10 @@ class TextEditor { // * `rowCount` (optional) {Number} number of rows to select (default: 1) // // This method may merge selections that end up intersecting. - selectDown (rowCount) { - return this.expandSelectionsForward(selection => selection.selectDown(rowCount)) + selectDown(rowCount) { + return this.expandSelectionsForward(selection => + selection.selectDown(rowCount) + ); } // Essential: Move the cursor of each selection one character leftward while @@ -3099,8 +3518,10 @@ class TextEditor { // * `columnCount` (optional) {Number} number of columns to select (default: 1) // // This method may merge selections that end up intersecting. - selectLeft (columnCount) { - return this.expandSelectionsBackward(selection => selection.selectLeft(columnCount)) + selectLeft(columnCount) { + return this.expandSelectionsBackward(selection => + selection.selectLeft(columnCount) + ); } // Essential: Move the cursor of each selection one character rightward while @@ -3109,39 +3530,45 @@ class TextEditor { // * `columnCount` (optional) {Number} number of columns to select (default: 1) // // This method may merge selections that end up intersecting. - selectRight (columnCount) { - return this.expandSelectionsForward(selection => selection.selectRight(columnCount)) + selectRight(columnCount) { + return this.expandSelectionsForward(selection => + selection.selectRight(columnCount) + ); } // Essential: Select from the top of the buffer to the end of the last selection // in the buffer. // // This method merges multiple selections into a single selection. - selectToTop () { - return this.expandSelectionsBackward(selection => selection.selectToTop()) + selectToTop() { + return this.expandSelectionsBackward(selection => selection.selectToTop()); } // Essential: Selects from the top of the first selection in the buffer to the end // of the buffer. // // This method merges multiple selections into a single selection. - selectToBottom () { - return this.expandSelectionsForward(selection => selection.selectToBottom()) + selectToBottom() { + return this.expandSelectionsForward(selection => + selection.selectToBottom() + ); } // Essential: Select all text in the buffer. // // This method merges multiple selections into a single selection. - selectAll () { - return this.expandSelectionsForward(selection => selection.selectAll()) + selectAll() { + return this.expandSelectionsForward(selection => selection.selectAll()); } // Essential: Move the cursor of each selection to the beginning of its line // while preserving the selection's tail position. // // This method may merge selections that end up intersecting. - selectToBeginningOfLine () { - return this.expandSelectionsBackward(selection => selection.selectToBeginningOfLine()) + selectToBeginningOfLine() { + return this.expandSelectionsBackward(selection => + selection.selectToBeginningOfLine() + ); } // Essential: Move the cursor of each selection to the first non-whitespace @@ -3150,60 +3577,72 @@ class TextEditor { // beginning of the line. // // This method may merge selections that end up intersecting. - selectToFirstCharacterOfLine () { - return this.expandSelectionsBackward(selection => selection.selectToFirstCharacterOfLine()) + selectToFirstCharacterOfLine() { + return this.expandSelectionsBackward(selection => + selection.selectToFirstCharacterOfLine() + ); } // Essential: Move the cursor of each selection to the end of its line while // preserving the selection's tail position. // // This method may merge selections that end up intersecting. - selectToEndOfLine () { - return this.expandSelectionsForward(selection => selection.selectToEndOfLine()) + selectToEndOfLine() { + return this.expandSelectionsForward(selection => + selection.selectToEndOfLine() + ); } // Essential: Expand selections to the beginning of their containing word. // // Operates on all selections. Moves the cursor to the beginning of the // containing word while preserving the selection's tail position. - selectToBeginningOfWord () { - return this.expandSelectionsBackward(selection => selection.selectToBeginningOfWord()) + selectToBeginningOfWord() { + return this.expandSelectionsBackward(selection => + selection.selectToBeginningOfWord() + ); } // Essential: Expand selections to the end of their containing word. // // Operates on all selections. Moves the cursor to the end of the containing // word while preserving the selection's tail position. - selectToEndOfWord () { - return this.expandSelectionsForward(selection => selection.selectToEndOfWord()) + selectToEndOfWord() { + return this.expandSelectionsForward(selection => + selection.selectToEndOfWord() + ); } // Extended: For each selection, move its cursor to the preceding subword // boundary while maintaining the selection's tail position. // // This method may merge selections that end up intersecting. - selectToPreviousSubwordBoundary () { - return this.expandSelectionsBackward(selection => selection.selectToPreviousSubwordBoundary()) + selectToPreviousSubwordBoundary() { + return this.expandSelectionsBackward(selection => + selection.selectToPreviousSubwordBoundary() + ); } // Extended: For each selection, move its cursor to the next subword boundary // while maintaining the selection's tail position. // // This method may merge selections that end up intersecting. - selectToNextSubwordBoundary () { - return this.expandSelectionsForward(selection => selection.selectToNextSubwordBoundary()) + selectToNextSubwordBoundary() { + return this.expandSelectionsForward(selection => + selection.selectToNextSubwordBoundary() + ); } // Essential: For each cursor, select the containing line. // // This method merges selections on successive lines. - selectLinesContainingCursors () { - return this.expandSelectionsForward(selection => selection.selectLine()) + selectLinesContainingCursors() { + return this.expandSelectionsForward(selection => selection.selectLine()); } // Essential: Select the word surrounding each cursor. - selectWordsContainingCursors () { - return this.expandSelectionsForward(selection => selection.selectWord()) + selectWordsContainingCursors() { + return this.expandSelectionsForward(selection => selection.selectWord()); } // Selection Extended @@ -3212,70 +3651,83 @@ class TextEditor { // while maintaining the selection's tail position. // // This method may merge selections that end up intersecting. - selectToPreviousWordBoundary () { - return this.expandSelectionsBackward(selection => selection.selectToPreviousWordBoundary()) + selectToPreviousWordBoundary() { + return this.expandSelectionsBackward(selection => + selection.selectToPreviousWordBoundary() + ); } // Extended: For each selection, move its cursor to the next word boundary while // maintaining the selection's tail position. // // This method may merge selections that end up intersecting. - selectToNextWordBoundary () { - return this.expandSelectionsForward(selection => selection.selectToNextWordBoundary()) + selectToNextWordBoundary() { + return this.expandSelectionsForward(selection => + selection.selectToNextWordBoundary() + ); } // Extended: Expand selections to the beginning of the next word. // // Operates on all selections. Moves the cursor to the beginning of the next // word while preserving the selection's tail position. - selectToBeginningOfNextWord () { - return this.expandSelectionsForward(selection => selection.selectToBeginningOfNextWord()) + selectToBeginningOfNextWord() { + return this.expandSelectionsForward(selection => + selection.selectToBeginningOfNextWord() + ); } // Extended: Expand selections to the beginning of the next paragraph. // // Operates on all selections. Moves the cursor to the beginning of the next // paragraph while preserving the selection's tail position. - selectToBeginningOfNextParagraph () { - return this.expandSelectionsForward(selection => selection.selectToBeginningOfNextParagraph()) + selectToBeginningOfNextParagraph() { + return this.expandSelectionsForward(selection => + selection.selectToBeginningOfNextParagraph() + ); } // Extended: Expand selections to the beginning of the next paragraph. // // Operates on all selections. Moves the cursor to the beginning of the next // paragraph while preserving the selection's tail position. - selectToBeginningOfPreviousParagraph () { - return this.expandSelectionsBackward(selection => selection.selectToBeginningOfPreviousParagraph()) + selectToBeginningOfPreviousParagraph() { + return this.expandSelectionsBackward(selection => + selection.selectToBeginningOfPreviousParagraph() + ); } // Extended: For each selection, select the syntax node that contains // that selection. - selectLargerSyntaxNode () { - const languageMode = this.buffer.getLanguageMode() - if (!languageMode.getRangeForSyntaxNodeContainingRange) return + selectLargerSyntaxNode() { + const languageMode = this.buffer.getLanguageMode(); + if (!languageMode.getRangeForSyntaxNodeContainingRange) return; this.expandSelectionsForward(selection => { - const currentRange = selection.getBufferRange() - const newRange = languageMode.getRangeForSyntaxNodeContainingRange(currentRange) + const currentRange = selection.getBufferRange(); + const newRange = languageMode.getRangeForSyntaxNodeContainingRange( + currentRange + ); if (newRange) { - if (!selection._rangeStack) selection._rangeStack = [] - selection._rangeStack.push(currentRange) - selection.setBufferRange(newRange) + if (!selection._rangeStack) selection._rangeStack = []; + selection._rangeStack.push(currentRange); + selection.setBufferRange(newRange); } - }) + }); } // Extended: Undo the effect a preceding call to {::selectLargerSyntaxNode}. - selectSmallerSyntaxNode () { + selectSmallerSyntaxNode() { this.expandSelectionsForward(selection => { if (selection._rangeStack) { - const lastRange = selection._rangeStack[selection._rangeStack.length - 1] + const lastRange = + selection._rangeStack[selection._rangeStack.length - 1]; if (lastRange && selection.getBufferRange().containsRange(lastRange)) { - selection._rangeStack.length-- - selection.setBufferRange(lastRange) + selection._rangeStack.length--; + selection.setBufferRange(lastRange); } } - }) + }); } // Extended: Select the range of the given marker if it is valid. @@ -3283,41 +3735,44 @@ class TextEditor { // * `marker` A {DisplayMarker} // // Returns the selected {Range} or `undefined` if the marker is invalid. - selectMarker (marker) { + selectMarker(marker) { if (marker.isValid()) { - const range = marker.getBufferRange() - this.setSelectedBufferRange(range) - return range + const range = marker.getBufferRange(); + this.setSelectedBufferRange(range); + return range; } } // Extended: Get the most recently added {Selection}. // // Returns a {Selection}. - getLastSelection () { - this.createLastSelectionIfNeeded() - return _.last(this.selections) + getLastSelection() { + this.createLastSelectionIfNeeded(); + return _.last(this.selections); } - getSelectionAtScreenPosition (position) { - const markers = this.selectionsMarkerLayer.findMarkers({containsScreenPosition: position}) - if (markers.length > 0) return this.cursorsByMarkerId.get(markers[0].id).selection + getSelectionAtScreenPosition(position) { + const markers = this.selectionsMarkerLayer.findMarkers({ + containsScreenPosition: position + }); + if (markers.length > 0) + return this.cursorsByMarkerId.get(markers[0].id).selection; } // Extended: Get current {Selection}s. // // Returns: An {Array} of {Selection}s. - getSelections () { - this.createLastSelectionIfNeeded() - return this.selections.slice() + getSelections() { + this.createLastSelectionIfNeeded(); + return this.selections.slice(); } // Extended: Get all {Selection}s, ordered by their position in the buffer // instead of the order in which they were added. // // Returns an {Array} of {Selection}s. - getSelectionsOrderedByBufferPosition () { - return this.getSelections().sort((a, b) => a.compare(b)) + getSelectionsOrderedByBufferPosition() { + return this.getSelections().sort((a, b) => a.compare(b)); } // Extended: Determine if a given range in buffer coordinates intersects a @@ -3326,8 +3781,10 @@ class TextEditor { // * `bufferRange` A {Range} or range-compatible {Array}. // // Returns a {Boolean}. - selectionIntersectsBufferRange (bufferRange) { - return this.getSelections().some(selection => selection.intersectsBufferRange(bufferRange)) + selectionIntersectsBufferRange(bufferRange) { + return this.getSelections().some(selection => + selection.intersectsBufferRange(bufferRange) + ); } // Selections Private @@ -3340,8 +3797,10 @@ class TextEditor { // selection's column as possible. If the selection is non-empty, adds a // selection to the next line that is long enough for a non-empty selection // starting at the same column as the current selection to be added to it. - addSelectionBelow () { - return this.expandSelectionsForward(selection => selection.addSelectionBelow()) + addSelectionBelow() { + return this.expandSelectionsForward(selection => + selection.addSelectionBelow() + ); } // Add a similarly-shaped selection to the next eligible line above @@ -3352,76 +3811,94 @@ class TextEditor { // selection's column as possible. If the selection is non-empty, adds a // selection to the next line that is long enough for a non-empty selection // starting at the same column as the current selection to be added to it. - addSelectionAbove () { - return this.expandSelectionsBackward(selection => selection.addSelectionAbove()) + addSelectionAbove() { + return this.expandSelectionsBackward(selection => + selection.addSelectionAbove() + ); } // Calls the given function with each selection, then merges selections - expandSelectionsForward (fn) { - this.mergeIntersectingSelections(() => this.getSelections().forEach(fn)) + expandSelectionsForward(fn) { + this.mergeIntersectingSelections(() => this.getSelections().forEach(fn)); } // Calls the given function with each selection, then merges selections in the // reversed orientation - expandSelectionsBackward (fn) { - this.mergeIntersectingSelections({reversed: true}, () => this.getSelections().forEach(fn)) + expandSelectionsBackward(fn) { + this.mergeIntersectingSelections({ reversed: true }, () => + this.getSelections().forEach(fn) + ); } - finalizeSelections () { - for (let selection of this.getSelections()) { selection.finalize() } + finalizeSelections() { + for (let selection of this.getSelections()) { + selection.finalize(); + } } - selectionsForScreenRows (startRow, endRow) { - return this.getSelections().filter(selection => selection.intersectsScreenRowRange(startRow, endRow)) + selectionsForScreenRows(startRow, endRow) { + return this.getSelections().filter(selection => + selection.intersectsScreenRowRange(startRow, endRow) + ); } // Merges intersecting selections. If passed a function, it executes // the function with merging suppressed, then merges intersecting selections // afterward. - mergeIntersectingSelections (...args) { - return this.mergeSelections(...args, (previousSelection, currentSelection) => { - const exclusive = !currentSelection.isEmpty() && !previousSelection.isEmpty() - return previousSelection.intersectsWith(currentSelection, exclusive) - }) + mergeIntersectingSelections(...args) { + return this.mergeSelections( + ...args, + (previousSelection, currentSelection) => { + const exclusive = + !currentSelection.isEmpty() && !previousSelection.isEmpty(); + return previousSelection.intersectsWith(currentSelection, exclusive); + } + ); } - mergeSelectionsOnSameRows (...args) { - return this.mergeSelections(...args, (previousSelection, currentSelection) => { - const screenRange = currentSelection.getScreenRange() - return previousSelection.intersectsScreenRowRange(screenRange.start.row, screenRange.end.row) - }) + mergeSelectionsOnSameRows(...args) { + return this.mergeSelections( + ...args, + (previousSelection, currentSelection) => { + const screenRange = currentSelection.getScreenRange(); + return previousSelection.intersectsScreenRowRange( + screenRange.start.row, + screenRange.end.row + ); + } + ); } - avoidMergingSelections (...args) { - return this.mergeSelections(...args, () => false) + avoidMergingSelections(...args) { + return this.mergeSelections(...args, () => false); } - mergeSelections (...args) { - const mergePredicate = args.pop() - let fn = args.pop() - let options = args.pop() + mergeSelections(...args) { + const mergePredicate = args.pop(); + let fn = args.pop(); + let options = args.pop(); if (typeof fn !== 'function') { - options = fn - fn = () => {} + options = fn; + fn = () => {}; } - if (this.suppressSelectionMerging) return fn() + if (this.suppressSelectionMerging) return fn(); - this.suppressSelectionMerging = true - const result = fn() - this.suppressSelectionMerging = false + this.suppressSelectionMerging = true; + const result = fn(); + this.suppressSelectionMerging = false; - const selections = this.getSelectionsOrderedByBufferPosition() - let lastSelection = selections.shift() + const selections = this.getSelectionsOrderedByBufferPosition(); + let lastSelection = selections.shift(); for (const selection of selections) { if (mergePredicate(lastSelection, selection)) { - lastSelection.merge(selection, options) + lastSelection.merge(selection, options); } else { - lastSelection = selection + lastSelection = selection; } } - return result + return result; } // Add a {Selection} based on the given {DisplayMarker}. @@ -3430,61 +3907,69 @@ class TextEditor { // * `options` (optional) An {Object} that pertains to the {Selection} constructor. // // Returns the new {Selection}. - addSelection (marker, options = {}) { - const cursor = this.addCursor(marker) - let selection = new Selection(Object.assign({editor: this, marker, cursor}, options)) - this.selections.push(selection) - const selectionBufferRange = selection.getBufferRange() - this.mergeIntersectingSelections({preserveFolds: options.preserveFolds}) + addSelection(marker, options = {}) { + const cursor = this.addCursor(marker); + let selection = new Selection( + Object.assign({ editor: this, marker, cursor }, options) + ); + this.selections.push(selection); + const selectionBufferRange = selection.getBufferRange(); + this.mergeIntersectingSelections({ preserveFolds: options.preserveFolds }); if (selection.destroyed) { for (selection of this.getSelections()) { - if (selection.intersectsBufferRange(selectionBufferRange)) return selection + if (selection.intersectsBufferRange(selectionBufferRange)) + return selection; } } else { - this.emitter.emit('did-add-cursor', cursor) - this.emitter.emit('did-add-selection', selection) - return selection + this.emitter.emit('did-add-cursor', cursor); + this.emitter.emit('did-add-selection', selection); + return selection; } } // Remove the given selection. - removeSelection (selection) { - _.remove(this.cursors, selection.cursor) - _.remove(this.selections, selection) - this.cursorsByMarkerId.delete(selection.cursor.marker.id) - this.emitter.emit('did-remove-cursor', selection.cursor) - return this.emitter.emit('did-remove-selection', selection) + removeSelection(selection) { + _.remove(this.cursors, selection.cursor); + _.remove(this.selections, selection); + this.cursorsByMarkerId.delete(selection.cursor.marker.id); + this.emitter.emit('did-remove-cursor', selection.cursor); + return this.emitter.emit('did-remove-selection', selection); } // Reduce one or more selections to a single empty selection based on the most // recently added cursor. - clearSelections (options) { - this.consolidateSelections() - this.getLastSelection().clear(options) + clearSelections(options) { + this.consolidateSelections(); + this.getLastSelection().clear(options); } // Reduce multiple selections to the least recently added selection. - consolidateSelections () { - const selections = this.getSelections() + consolidateSelections() { + const selections = this.getSelections(); if (selections.length > 1) { - for (let selection of selections.slice(1, (selections.length))) { selection.destroy() } - selections[0].autoscroll({center: true}) - return true + for (let selection of selections.slice(1, selections.length)) { + selection.destroy(); + } + selections[0].autoscroll({ center: true }); + return true; } else { - return false + return false; } } // Called by the selection - selectionRangeChanged (event) { - if (this.component) this.component.didChangeSelectionRange() - this.emitter.emit('did-change-selection-range', event) + selectionRangeChanged(event) { + if (this.component) this.component.didChangeSelectionRange(); + this.emitter.emit('did-change-selection-range', event); } - createLastSelectionIfNeeded () { + createLastSelectionIfNeeded() { if (this.selections.length === 0) { - this.addSelectionForBufferRange([[0, 0], [0, 0]], {autoscroll: false, preserveFolds: true}) + this.addSelectionForBufferRange([[0, 0], [0, 0]], { + autoscroll: false, + preserveFolds: true + }); } } @@ -3513,13 +3998,13 @@ class TextEditor { // * `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, options = {}, iterator) { + scan(regex, options = {}, iterator) { if (_.isFunction(options)) { - iterator = options - options = {} + iterator = options; + options = {}; } - return this.buffer.scan(regex, options, iterator) + return this.buffer.scan(regex, options, iterator); } // Essential: Scan regular expression matches in a given range, calling the given @@ -3534,7 +4019,9 @@ class TextEditor { // * `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. - scanInBufferRange (regex, range, iterator) { return this.buffer.scanInRange(regex, range, iterator) } + scanInBufferRange(regex, range, iterator) { + return this.buffer.scanInRange(regex, range, iterator); + } // Essential: Scan regular expression matches in a given range in reverse order, // calling the given iterator function on each match. @@ -3548,7 +4035,9 @@ class TextEditor { // * `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. - backwardsScanInBufferRange (regex, range, iterator) { return this.buffer.backwardsScanInRange(regex, range, iterator) } + backwardsScanInBufferRange(regex, range, iterator) { + return this.buffer.backwardsScanInRange(regex, range, iterator); + } /* Section: Tab Behavior @@ -3556,47 +4045,61 @@ class TextEditor { // Essential: Returns a {Boolean} indicating whether softTabs are enabled for this // editor. - getSoftTabs () { return this.softTabs } + getSoftTabs() { + return this.softTabs; + } // Essential: Enable or disable soft tabs for this editor. // // * `softTabs` A {Boolean} - setSoftTabs (softTabs) { - this.softTabs = softTabs - this.update({softTabs: this.softTabs}) + setSoftTabs(softTabs) { + this.softTabs = softTabs; + this.update({ softTabs: this.softTabs }); } // Returns a {Boolean} indicating whether atomic soft tabs are enabled for this editor. - hasAtomicSoftTabs () { return this.displayLayer.atomicSoftTabs } + hasAtomicSoftTabs() { + return this.displayLayer.atomicSoftTabs; + } // Essential: Toggle soft tabs for this editor - toggleSoftTabs () { this.setSoftTabs(!this.getSoftTabs()) } + toggleSoftTabs() { + this.setSoftTabs(!this.getSoftTabs()); + } // Essential: Get the on-screen length of tab characters. // // Returns a {Number}. - getTabLength () { return this.displayLayer.tabLength } + getTabLength() { + return this.displayLayer.tabLength; + } // Essential: Set the on-screen length of tab characters. Setting this to a // {Number} This will override the `editor.tabLength` setting. // // * `tabLength` {Number} length of a single tab. Setting to `null` will // fallback to using the `editor.tabLength` config setting - setTabLength (tabLength) { this.update({tabLength}) } + setTabLength(tabLength) { + this.update({ tabLength }); + } // Returns an {Object} representing the current invisible character // substitutions for this editor. See {::setInvisibles}. - getInvisibles () { - if (!this.mini && this.showInvisibles && (this.invisibles != null)) { - return this.invisibles + getInvisibles() { + if (!this.mini && this.showInvisibles && this.invisibles != null) { + return this.invisibles; } else { - return {} + return {}; } } - doesShowIndentGuide () { return this.showIndentGuide && !this.mini } + doesShowIndentGuide() { + return this.showIndentGuide && !this.mini; + } - getSoftWrapHangingIndentLength () { return this.displayLayer.softWrapHangingIndent } + getSoftWrapHangingIndentLength() { + return this.displayLayer.softWrapHangingIndent; + } // Extended: Determine if the buffer uses hard or soft tabs. // @@ -3605,14 +4108,18 @@ class TextEditor { // // Returns a {Boolean} or undefined if no non-comment lines had leading // whitespace. - usesSoftTabs () { - const languageMode = this.buffer.getLanguageMode() - const hasIsRowCommented = languageMode.isRowCommented - for (let bufferRow = 0, end = Math.min(1000, this.buffer.getLastRow()); bufferRow <= end; bufferRow++) { - if (hasIsRowCommented && languageMode.isRowCommented(bufferRow)) continue - const line = this.buffer.lineForRow(bufferRow) - if (line[0] === ' ') return true - if (line[0] === '\t') return false + usesSoftTabs() { + const languageMode = this.buffer.getLanguageMode(); + const hasIsRowCommented = languageMode.isRowCommented; + for ( + let bufferRow = 0, end = Math.min(1000, this.buffer.getLastRow()); + bufferRow <= end; + bufferRow++ + ) { + if (hasIsRowCommented && languageMode.isRowCommented(bufferRow)) continue; + const line = this.buffer.lineForRow(bufferRow); + if (line[0] === ' ') return true; + if (line[0] === '\t') return false; } } @@ -3622,13 +4129,19 @@ class TextEditor { // tab length. Otherwise the text is a tab character (`\t`). // // Returns a {String}. - getTabText () { return this.buildIndentString(1) } + getTabText() { + return this.buildIndentString(1); + } // If soft tabs are enabled, convert all hard tabs to soft tabs in the given // {Range}. - normalizeTabsInBufferRange (bufferRange) { - if (!this.getSoftTabs()) { return } - return this.scanInBufferRange(/\t/g, bufferRange, ({replace}) => replace(this.getTabText())) + normalizeTabsInBufferRange(bufferRange) { + if (!this.getSoftTabs()) { + return; + } + return this.scanInBufferRange(/\t/g, bufferRange, ({ replace }) => + replace(this.getTabText()) + ); } /* @@ -3638,35 +4151,41 @@ class TextEditor { // Essential: Determine whether lines in this editor are soft-wrapped. // // Returns a {Boolean}. - isSoftWrapped () { return this.softWrapped } + isSoftWrapped() { + return this.softWrapped; + } // Essential: Enable or disable soft wrapping for this editor. // // * `softWrapped` A {Boolean} // // Returns a {Boolean}. - setSoftWrapped (softWrapped) { - this.update({softWrapped}) - return this.isSoftWrapped() + setSoftWrapped(softWrapped) { + this.update({ softWrapped }); + return this.isSoftWrapped(); } - getPreferredLineLength () { return this.preferredLineLength } + getPreferredLineLength() { + return this.preferredLineLength; + } // Essential: Toggle soft wrapping for this editor // // Returns a {Boolean}. - toggleSoftWrapped () { return this.setSoftWrapped(!this.isSoftWrapped()) } + toggleSoftWrapped() { + return this.setSoftWrapped(!this.isSoftWrapped()); + } // Essential: Gets the column at which column will soft wrap - getSoftWrapColumn () { + getSoftWrapColumn() { if (this.isSoftWrapped() && !this.mini) { if (this.softWrapAtPreferredLineLength) { - return Math.min(this.getEditorWidthInChars(), this.preferredLineLength) + return Math.min(this.getEditorWidthInChars(), this.preferredLineLength); } else { - return this.getEditorWidthInChars() + return this.getEditorWidthInChars(); } } else { - return this.maxScreenLineLength + return this.maxScreenLineLength; } } @@ -3684,8 +4203,8 @@ class TextEditor { // * `bufferRow` A {Number} indicating the buffer row. // // Returns a {Number}. - indentationForBufferRow (bufferRow) { - return this.indentLevelForLine(this.lineTextForBufferRow(bufferRow)) + indentationForBufferRow(bufferRow) { + return this.indentLevelForLine(this.lineTextForBufferRow(bufferRow)); } // Essential: Set the indentation level for the given buffer row. @@ -3700,33 +4219,44 @@ class TextEditor { // * `options` (optional) An {Object} with the following keys: // * `preserveLeadingWhitespace` `true` to preserve any whitespace already at // the beginning of the line (default: false). - setIndentationForBufferRow (bufferRow, newLevel, {preserveLeadingWhitespace} = {}) { - let endColumn + setIndentationForBufferRow( + bufferRow, + newLevel, + { preserveLeadingWhitespace } = {} + ) { + let endColumn; if (preserveLeadingWhitespace) { - endColumn = 0 + endColumn = 0; } else { - endColumn = this.lineTextForBufferRow(bufferRow).match(/^\s*/)[0].length + endColumn = this.lineTextForBufferRow(bufferRow).match(/^\s*/)[0].length; } - const newIndentString = this.buildIndentString(newLevel) - return this.buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString) + const newIndentString = this.buildIndentString(newLevel); + return this.buffer.setTextInRange( + [[bufferRow, 0], [bufferRow, endColumn]], + newIndentString + ); } // Extended: Indent rows intersecting selections by one level. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - indentSelectedRows (options = {}) { - if (!this.ensureWritable('indentSelectedRows', options)) return - return this.mutateSelectedText(selection => selection.indentSelectedRows(options)) + indentSelectedRows(options = {}) { + if (!this.ensureWritable('indentSelectedRows', options)) return; + return this.mutateSelectedText(selection => + selection.indentSelectedRows(options) + ); } // Extended: Outdent rows intersecting selections by one level. // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - outdentSelectedRows (options = {}) { - if (!this.ensureWritable('outdentSelectedRows', options)) return - return this.mutateSelectedText(selection => selection.outdentSelectedRows(options)) + outdentSelectedRows(options = {}) { + if (!this.ensureWritable('outdentSelectedRows', options)) return; + return this.mutateSelectedText(selection => + selection.outdentSelectedRows(options) + ); } // Extended: Get the indentation level of the given line of text. @@ -3739,20 +4269,20 @@ class TextEditor { // * `line` A {String} representing a line of text. // // Returns a {Number}. - indentLevelForLine (line) { - const tabLength = this.getTabLength() - let indentLength = 0 - for (let i = 0, {length} = line; i < length; i++) { - const char = line[i] + indentLevelForLine(line) { + const tabLength = this.getTabLength(); + let indentLength = 0; + for (let i = 0, { length } = line; i < length; i++) { + const char = line[i]; if (char === '\t') { - indentLength += tabLength - (indentLength % tabLength) + indentLength += tabLength - (indentLength % tabLength); } else if (char === ' ') { - indentLength++ + indentLength++; } else { - break + break; } } - return indentLength / tabLength + return indentLength / tabLength; } // Extended: Indent rows intersecting selections based on the grammar's suggested @@ -3760,9 +4290,11 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - autoIndentSelectedRows (options = {}) { - if (!this.ensureWritable('autoIndentSelectedRows', options)) return - return this.mutateSelectedText(selection => selection.autoIndentSelectedRows(options)) + autoIndentSelectedRows(options = {}) { + if (!this.ensureWritable('autoIndentSelectedRows', options)) return; + return this.mutateSelectedText(selection => + selection.autoIndentSelectedRows(options) + ); } // Indent all lines intersecting selections. See {Selection::indent} for more @@ -3770,20 +4302,27 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - indent (options = {}) { - if (!this.ensureWritable('indent', options)) return - if (options.autoIndent == null) options.autoIndent = this.shouldAutoIndent() - this.mutateSelectedText(selection => selection.indent(options)) + indent(options = {}) { + if (!this.ensureWritable('indent', options)) return; + if (options.autoIndent == null) + options.autoIndent = this.shouldAutoIndent(); + this.mutateSelectedText(selection => selection.indent(options)); } // Constructs the string used for indents. - buildIndentString (level, column = 0) { + buildIndentString(level, column = 0) { if (this.getSoftTabs()) { - const tabStopViolation = column % this.getTabLength() - return _.multiplyString(' ', Math.floor(level * this.getTabLength()) - tabStopViolation) + const tabStopViolation = column % this.getTabLength(); + return _.multiplyString( + ' ', + Math.floor(level * this.getTabLength()) - tabStopViolation + ); } else { - const excessWhitespace = _.multiplyString(' ', Math.round((level - Math.floor(level)) * this.getTabLength())) - return _.multiplyString('\t', Math.floor(level)) + excessWhitespace + const excessWhitespace = _.multiplyString( + ' ', + Math.round((level - Math.floor(level)) * this.getTabLength()) + ); + return _.multiplyString('\t', Math.floor(level)) + excessWhitespace; } } @@ -3792,9 +4331,11 @@ class TextEditor { */ // Essential: Get the current {Grammar} of this editor. - getGrammar () { - const languageMode = this.buffer.getLanguageMode() - return languageMode.getGrammar && languageMode.getGrammar() || NullGrammar + getGrammar() { + const languageMode = this.buffer.getLanguageMode(); + return ( + (languageMode.getGrammar && languageMode.getGrammar()) || NullGrammar + ); } // Deprecated: Set the current {Grammar} of this editor. @@ -3803,14 +4344,16 @@ class TextEditor { // grammar. // // * `grammar` {Grammar} - setGrammar (grammar) { - const buffer = this.getBuffer() - buffer.setLanguageMode(atom.grammars.languageModeForGrammarAndBuffer(grammar, buffer)) + setGrammar(grammar) { + const buffer = this.getBuffer(); + buffer.setLanguageMode( + atom.grammars.languageModeForGrammarAndBuffer(grammar, buffer) + ); } // Experimental: Get a notification when async tokenization is completed. - onDidTokenize (callback) { - return this.emitter.on('did-tokenize', callback) + onDidTokenize(callback) { + return this.emitter.on('did-tokenize', callback); } /* @@ -3820,8 +4363,8 @@ class TextEditor { // Essential: Returns a {ScopeDescriptor} that includes this editor's language. // e.g. `['.source.ruby']`, or `['.source.coffee']`. You can use this with // {Config::get} to get language specific config values. - getRootScopeDescriptor () { - return this.buffer.getLanguageMode().rootScopeDescriptor + getRootScopeDescriptor() { + return this.buffer.getLanguageMode().rootScopeDescriptor; } // Essential: Get the syntactic {ScopeDescriptor} for the given position in buffer @@ -3835,11 +4378,11 @@ class TextEditor { // * `bufferPosition` A {Point} or {Array} of `[row, column]`. // // Returns a {ScopeDescriptor}. - scopeDescriptorForBufferPosition (bufferPosition) { - const languageMode = this.buffer.getLanguageMode() + scopeDescriptorForBufferPosition(bufferPosition) { + const languageMode = this.buffer.getLanguageMode(); return languageMode.scopeDescriptorForPosition ? languageMode.scopeDescriptorForPosition(bufferPosition) - : new ScopeDescriptor({scopes: ['text']}) + : new ScopeDescriptor({ scopes: ['text'] }); } // Essential: Get the syntactic tree {ScopeDescriptor} for the given position in buffer @@ -3857,11 +4400,11 @@ class TextEditor { // * `bufferPosition` A {Point} or {Array} of `[row, column]`. // // Returns a {ScopeDescriptor}. - syntaxTreeScopeDescriptorForBufferPosition (bufferPosition) { - const languageMode = this.buffer.getLanguageMode() + syntaxTreeScopeDescriptorForBufferPosition(bufferPosition) { + const languageMode = this.buffer.getLanguageMode(); return languageMode.syntaxTreeScopeDescriptorForPosition ? languageMode.syntaxTreeScopeDescriptorForPosition(bufferPosition) - : this.scopeDescriptorForBufferPosition(bufferPosition) + : this.scopeDescriptorForBufferPosition(bufferPosition); } // Extended: Get the range in buffer coordinates of all tokens surrounding the @@ -3873,35 +4416,43 @@ class TextEditor { // * `scopeSelector` {String} selector. e.g. `'.source.ruby'` // // Returns a {Range}. - bufferRangeForScopeAtCursor (scopeSelector) { - return this.bufferRangeForScopeAtPosition(scopeSelector, this.getCursorBufferPosition()) + bufferRangeForScopeAtCursor(scopeSelector) { + return this.bufferRangeForScopeAtPosition( + scopeSelector, + this.getCursorBufferPosition() + ); } - bufferRangeForScopeAtPosition (scopeSelector, position) { - return this.buffer.getLanguageMode().bufferRangeForScopeAtPosition(scopeSelector, position) + bufferRangeForScopeAtPosition(scopeSelector, position) { + return this.buffer + .getLanguageMode() + .bufferRangeForScopeAtPosition(scopeSelector, position); } // Extended: Determine if the given row is entirely a comment - isBufferRowCommented (bufferRow) { - const match = this.lineTextForBufferRow(bufferRow).match(/\S/) + isBufferRowCommented(bufferRow) { + const match = this.lineTextForBufferRow(bufferRow).match(/\S/); if (match) { - if (!this.commentScopeSelector) this.commentScopeSelector = new TextMateScopeSelector('comment.*') - return this.commentScopeSelector.matches(this.scopeDescriptorForBufferPosition([bufferRow, match.index]).scopes) + if (!this.commentScopeSelector) + this.commentScopeSelector = new TextMateScopeSelector('comment.*'); + return this.commentScopeSelector.matches( + this.scopeDescriptorForBufferPosition([bufferRow, match.index]).scopes + ); } } // Get the scope descriptor at the cursor. - getCursorScope () { - return this.getLastCursor().getScopeDescriptor() + getCursorScope() { + return this.getLastCursor().getScopeDescriptor(); } // Get the syntax nodes at the cursor. - getCursorSyntaxTreeScope () { - return this.getLastCursor().getSyntaxTreeScopeDescriptor() + getCursorSyntaxTreeScope() { + return this.getLastCursor().getSyntaxTreeScopeDescriptor(); } - tokenForBufferPosition (bufferPosition) { - return this.buffer.getLanguageMode().tokenForPosition(bufferPosition) + tokenForBufferPosition(bufferPosition) { + return this.buffer.getLanguageMode().tokenForPosition(bufferPosition); } /* @@ -3909,28 +4460,28 @@ class TextEditor { */ // Essential: For each selection, copy the selected text. - copySelectedText () { - let maintainClipboard = false + copySelectedText() { + let maintainClipboard = false; for (let selection of this.getSelectionsOrderedByBufferPosition()) { if (selection.isEmpty()) { - const previousRange = selection.getBufferRange() - selection.selectLine() - selection.copy(maintainClipboard, true) - selection.setBufferRange(previousRange) + const previousRange = selection.getBufferRange(); + selection.selectLine(); + selection.copy(maintainClipboard, true); + selection.setBufferRange(previousRange); } else { - selection.copy(maintainClipboard, false) + selection.copy(maintainClipboard, false); } - maintainClipboard = true + maintainClipboard = true; } } // Private: For each selection, only copy highlighted text. - copyOnlySelectedText () { - let maintainClipboard = false + copyOnlySelectedText() { + let maintainClipboard = false; for (let selection of this.getSelectionsOrderedByBufferPosition()) { if (!selection.isEmpty()) { - selection.copy(maintainClipboard, false) - maintainClipboard = true + selection.copy(maintainClipboard, false); + maintainClipboard = true; } } } @@ -3939,18 +4490,18 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - cutSelectedText (options = {}) { - if (!this.ensureWritable('cutSelectedText', options)) return - let maintainClipboard = false + cutSelectedText(options = {}) { + if (!this.ensureWritable('cutSelectedText', options)) return; + let maintainClipboard = false; this.mutateSelectedText(selection => { if (selection.isEmpty()) { - selection.selectLine() - selection.cut(maintainClipboard, true, options.bypassReadOnly) + selection.selectLine(); + selection.cut(maintainClipboard, true, options.bypassReadOnly); } else { - selection.cut(maintainClipboard, false, options.bypassReadOnly) + selection.cut(maintainClipboard, false, options.bypassReadOnly); } - maintainClipboard = true - }) + maintainClipboard = true; + }); } // Essential: For each selection, replace the selected text with the contents of @@ -3961,43 +4512,54 @@ class TextEditor { // corresponding clipboard selection text. // // * `options` (optional) See {Selection::insertText}. - pasteText (options = {}) { - if (!this.ensureWritable('parseText', options)) return - options = Object.assign({}, options) - let {text: clipboardText, metadata} = this.constructor.clipboard.readWithMetadata() - if (!this.emitWillInsertTextEvent(clipboardText)) return false + pasteText(options = {}) { + if (!this.ensureWritable('parseText', options)) return; + options = Object.assign({}, options); + let { + text: clipboardText, + metadata + } = this.constructor.clipboard.readWithMetadata(); + if (!this.emitWillInsertTextEvent(clipboardText)) return false; - if (!metadata) metadata = {} - if (options.autoIndent == null) options.autoIndent = this.shouldAutoIndentOnPaste() + if (!metadata) metadata = {}; + if (options.autoIndent == null) + options.autoIndent = this.shouldAutoIndentOnPaste(); this.mutateSelectedText((selection, index) => { - let fullLine, indentBasis, text - if (metadata.selections && metadata.selections.length === this.getSelections().length) { - ({text, indentBasis, fullLine} = metadata.selections[index]) + let fullLine, indentBasis, text; + if ( + metadata.selections && + metadata.selections.length === this.getSelections().length + ) { + ({ text, indentBasis, fullLine } = metadata.selections[index]); } else { - ({indentBasis, fullLine} = metadata) - text = clipboardText + ({ indentBasis, fullLine } = metadata); + text = clipboardText; } - if (indentBasis != null && (text.includes('\n') || !selection.cursor.hasPrecedingCharactersOnLine())) { - options.indentBasis = indentBasis + if ( + indentBasis != null && + (text.includes('\n') || + !selection.cursor.hasPrecedingCharactersOnLine()) + ) { + options.indentBasis = indentBasis; } else { - options.indentBasis = null + options.indentBasis = null; } - let range + let range; if (fullLine && selection.isEmpty()) { - const oldPosition = selection.getBufferRange().start - selection.setBufferRange([[oldPosition.row, 0], [oldPosition.row, 0]]) - range = selection.insertText(text, options) - const newPosition = oldPosition.translate([1, 0]) - selection.setBufferRange([newPosition, newPosition]) + const oldPosition = selection.getBufferRange().start; + selection.setBufferRange([[oldPosition.row, 0], [oldPosition.row, 0]]); + range = selection.insertText(text, options); + const newPosition = oldPosition.translate([1, 0]); + selection.setBufferRange([newPosition, newPosition]); } else { - range = selection.insertText(text, options) + range = selection.insertText(text, options); } - this.emitter.emit('did-insert-text', {text, range}) - }) + this.emitter.emit('did-insert-text', { text, range }); + }); } // Essential: For each selection, if the selection is empty, cut all characters @@ -4006,13 +4568,13 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - cutToEndOfLine (options = {}) { - if (!this.ensureWritable('cutToEndOfLine', options)) return - let maintainClipboard = false + cutToEndOfLine(options = {}) { + if (!this.ensureWritable('cutToEndOfLine', options)) return; + let maintainClipboard = false; this.mutateSelectedText(selection => { - selection.cutToEndOfLine(maintainClipboard, options) - maintainClipboard = true - }) + selection.cutToEndOfLine(maintainClipboard, options); + maintainClipboard = true; + }); } // Essential: For each selection, if the selection is empty, cut all characters @@ -4021,13 +4583,13 @@ class TextEditor { // // * `options` (optional) {Object} // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. - cutToEndOfBufferLine (options = {}) { - if (!this.ensureWritable('cutToEndOfBufferLine', options)) return - let maintainClipboard = false + cutToEndOfBufferLine(options = {}) { + if (!this.ensureWritable('cutToEndOfBufferLine', options)) return; + let maintainClipboard = false; this.mutateSelectedText(selection => { - selection.cutToEndOfBufferLine(maintainClipboard, options) - maintainClipboard = true - }) + selection.cutToEndOfBufferLine(maintainClipboard, options); + maintainClipboard = true; + }); } /* @@ -4039,20 +4601,25 @@ class TextEditor { // The fold will extend from the nearest preceding line with a lower // indentation level up to the nearest following row with a lower indentation // level. - foldCurrentRow () { - const {row} = this.getCursorBufferPosition() - const languageMode = this.buffer.getLanguageMode() - const range = ( + foldCurrentRow() { + const { row } = this.getCursorBufferPosition(); + const languageMode = this.buffer.getLanguageMode(); + const range = languageMode.getFoldableRangeContainingPoint && - languageMode.getFoldableRangeContainingPoint(Point(row, Infinity), this.getTabLength()) - ) - if (range) return this.displayLayer.foldBufferRange(range) + languageMode.getFoldableRangeContainingPoint( + Point(row, Infinity), + this.getTabLength() + ); + if (range) return this.displayLayer.foldBufferRange(range); } // Essential: Unfold the most recent cursor's row by one level. - unfoldCurrentRow () { - const {row} = this.getCursorBufferPosition() - return this.displayLayer.destroyFoldsContainingBufferPositions([Point(row, Infinity)], false) + unfoldCurrentRow() { + const { row } = this.getCursorBufferPosition(); + return this.displayLayer.destroyFoldsContainingBufferPositions( + [Point(row, Infinity)], + false + ); } // Essential: Fold the given row in buffer coordinates based on its indentation @@ -4062,77 +4629,81 @@ class TextEditor { // begin at the first foldable row preceding the given row. // // * `bufferRow` A {Number}. - foldBufferRow (bufferRow) { - let position = Point(bufferRow, Infinity) - const languageMode = this.buffer.getLanguageMode() + foldBufferRow(bufferRow) { + let position = Point(bufferRow, Infinity); + const languageMode = this.buffer.getLanguageMode(); while (true) { - const foldableRange = ( + const foldableRange = languageMode.getFoldableRangeContainingPoint && - languageMode.getFoldableRangeContainingPoint(position, this.getTabLength()) - ) + languageMode.getFoldableRangeContainingPoint( + position, + this.getTabLength() + ); if (foldableRange) { - const existingFolds = this.displayLayer.foldsIntersectingBufferRange(Range(foldableRange.start, foldableRange.start)) + const existingFolds = this.displayLayer.foldsIntersectingBufferRange( + Range(foldableRange.start, foldableRange.start) + ); if (existingFolds.length === 0) { - this.displayLayer.foldBufferRange(foldableRange) + this.displayLayer.foldBufferRange(foldableRange); } else { - const firstExistingFoldRange = this.displayLayer.bufferRangeForFold(existingFolds[0]) + const firstExistingFoldRange = this.displayLayer.bufferRangeForFold( + existingFolds[0] + ); if (firstExistingFoldRange.start.isLessThan(position)) { - position = Point(firstExistingFoldRange.start.row, 0) - continue + position = Point(firstExistingFoldRange.start.row, 0); + continue; } } } - break + break; } } // Essential: Unfold all folds containing the given row in buffer coordinates. // // * `bufferRow` A {Number} - unfoldBufferRow (bufferRow) { - const position = Point(bufferRow, Infinity) - return this.displayLayer.destroyFoldsContainingBufferPositions([position]) + unfoldBufferRow(bufferRow) { + const position = Point(bufferRow, Infinity); + return this.displayLayer.destroyFoldsContainingBufferPositions([position]); } // Extended: For each selection, fold the rows it intersects. - foldSelectedLines () { + foldSelectedLines() { for (let selection of this.selections) { - selection.fold() + selection.fold(); } } // Extended: Fold all foldable lines. - foldAll () { - const languageMode = this.buffer.getLanguageMode() - const foldableRanges = ( + foldAll() { + const languageMode = this.buffer.getLanguageMode(); + const foldableRanges = languageMode.getFoldableRanges && - languageMode.getFoldableRanges(this.getTabLength()) - ) - this.displayLayer.destroyAllFolds() + languageMode.getFoldableRanges(this.getTabLength()); + this.displayLayer.destroyAllFolds(); for (let range of foldableRanges || []) { - this.displayLayer.foldBufferRange(range) + this.displayLayer.foldBufferRange(range); } } // Extended: Unfold all existing folds. - unfoldAll () { - const result = this.displayLayer.destroyAllFolds() - if (result.length > 0) this.scrollToCursorPosition() - return result + unfoldAll() { + const result = this.displayLayer.destroyAllFolds(); + if (result.length > 0) this.scrollToCursorPosition(); + return result; } // Extended: Fold all foldable lines at the given indent level. // // * `level` A {Number} starting at 0. - foldAllAtIndentLevel (level) { - const languageMode = this.buffer.getLanguageMode() - const foldableRanges = ( + foldAllAtIndentLevel(level) { + const languageMode = this.buffer.getLanguageMode(); + const foldableRanges = languageMode.getFoldableRangesAtIndentLevel && - languageMode.getFoldableRangesAtIndentLevel(level, this.getTabLength()) - ) - this.displayLayer.destroyAllFolds() + languageMode.getFoldableRangesAtIndentLevel(level, this.getTabLength()); + this.displayLayer.destroyAllFolds(); for (let range of foldableRanges || []) { - this.displayLayer.foldBufferRange(range) + this.displayLayer.foldBufferRange(range); } } @@ -4143,9 +4714,11 @@ class TextEditor { // * `bufferRow` A {Number} // // Returns a {Boolean}. - isFoldableAtBufferRow (bufferRow) { - const languageMode = this.buffer.getLanguageMode() - return languageMode.isFoldableAtRow && languageMode.isFoldableAtRow(bufferRow) + isFoldableAtBufferRow(bufferRow) { + const languageMode = this.buffer.getLanguageMode(); + return ( + languageMode.isFoldableAtRow && languageMode.isFoldableAtRow(bufferRow) + ); } // Extended: Determine whether the given row in screen coordinates is foldable. @@ -4155,25 +4728,25 @@ class TextEditor { // * `bufferRow` A {Number} // // Returns a {Boolean}. - isFoldableAtScreenRow (screenRow) { - return this.isFoldableAtBufferRow(this.bufferRowForScreenRow(screenRow)) + isFoldableAtScreenRow(screenRow) { + return this.isFoldableAtBufferRow(this.bufferRowForScreenRow(screenRow)); } // Extended: Fold the given buffer row if it isn't currently folded, and unfold // it otherwise. - toggleFoldAtBufferRow (bufferRow) { + toggleFoldAtBufferRow(bufferRow) { if (this.isFoldedAtBufferRow(bufferRow)) { - return this.unfoldBufferRow(bufferRow) + return this.unfoldBufferRow(bufferRow); } else { - return this.foldBufferRow(bufferRow) + return this.foldBufferRow(bufferRow); } } // Extended: Determine whether the most recently added cursor's row is folded. // // Returns a {Boolean}. - isFoldedAtCursorRow () { - return this.isFoldedAtBufferRow(this.getCursorBufferPosition().row) + isFoldedAtCursorRow() { + return this.isFoldedAtBufferRow(this.getCursorBufferPosition().row); } // Extended: Determine whether the given row in buffer coordinates is folded. @@ -4181,12 +4754,12 @@ class TextEditor { // * `bufferRow` A {Number} // // Returns a {Boolean}. - isFoldedAtBufferRow (bufferRow) { + isFoldedAtBufferRow(bufferRow) { const range = Range( Point(bufferRow, 0), Point(bufferRow, this.buffer.lineLengthForRow(bufferRow)) - ) - return this.displayLayer.foldsIntersectingBufferRange(range).length > 0 + ); + return this.displayLayer.foldsIntersectingBufferRange(range).length > 0; } // Extended: Determine whether the given row in screen coordinates is folded. @@ -4194,8 +4767,8 @@ class TextEditor { // * `screenRow` A {Number} // // Returns a {Boolean}. - isFoldedAtScreenRow (screenRow) { - return this.isFoldedAtBufferRow(this.bufferRowForScreenRow(screenRow)) + isFoldedAtScreenRow(screenRow) { + return this.isFoldedAtBufferRow(this.bufferRowForScreenRow(screenRow)); } // Creates a new fold between two row numbers. @@ -4204,22 +4777,27 @@ class TextEditor { // endRow - The row {Number} to end the fold // // Returns the new {Fold}. - foldBufferRowRange (startRow, endRow) { - return this.foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity))) + foldBufferRowRange(startRow, endRow) { + return this.foldBufferRange( + Range(Point(startRow, Infinity), Point(endRow, Infinity)) + ); } - foldBufferRange (range) { - return this.displayLayer.foldBufferRange(range) + foldBufferRange(range) { + return this.displayLayer.foldBufferRange(range); } // Remove any {Fold}s found that intersect the given buffer range. - destroyFoldsIntersectingBufferRange (bufferRange) { - return this.displayLayer.destroyFoldsIntersectingBufferRange(bufferRange) + destroyFoldsIntersectingBufferRange(bufferRange) { + return this.displayLayer.destroyFoldsIntersectingBufferRange(bufferRange); } // Remove any {Fold}s found that contain the given array of buffer positions. - destroyFoldsContainingBufferPositions (bufferPositions, excludeEndpoints) { - return this.displayLayer.destroyFoldsContainingBufferPositions(bufferPositions, excludeEndpoints) + destroyFoldsContainingBufferPositions(bufferPositions, excludeEndpoints) { + return this.displayLayer.destroyFoldsContainingBufferPositions( + bufferPositions, + excludeEndpoints + ); } /* @@ -4260,26 +4838,26 @@ class TextEditor { // * `screenRow` {Number} // // Returns the newly-created {Gutter}. - addGutter (options) { - return this.gutterContainer.addGutter(options) + addGutter(options) { + return this.gutterContainer.addGutter(options); } // Essential: Get this editor's gutters. // // Returns an {Array} of {Gutter}s. - getGutters () { - return this.gutterContainer.getGutters() + getGutters() { + return this.gutterContainer.getGutters(); } - getLineNumberGutter () { - return this.lineNumberGutter + getLineNumberGutter() { + return this.lineNumberGutter; } // Essential: Get the gutter with the given name. // // Returns a {Gutter}, or `null` if no gutter exists for the given name. - gutterWithName (name) { - return this.gutterContainer.gutterWithName(name) + gutterWithName(name) { + return this.gutterContainer.gutterWithName(name); } /* @@ -4291,8 +4869,10 @@ class TextEditor { // // * `options` (optional) {Object} // * `center` Center the editor around the cursor if possible. (default: true) - scrollToCursorPosition (options) { - this.getLastCursor().autoscroll({center: options && options.center !== false}) + scrollToCursorPosition(options) { + this.getLastCursor().autoscroll({ + center: options && options.center !== false + }); } // Essential: Scrolls the editor to the given buffer position. @@ -4301,8 +4881,11 @@ class TextEditor { // an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} // * `options` (optional) {Object} // * `center` Center the editor around the position if possible. (default: false) - scrollToBufferPosition (bufferPosition, options) { - return this.scrollToScreenPosition(this.screenPositionForBufferPosition(bufferPosition), options) + scrollToBufferPosition(bufferPosition, options) { + return this.scrollToScreenPosition( + this.screenPositionForBufferPosition(bufferPosition), + options + ); } // Essential: Scrolls the editor to the given screen position. @@ -4311,61 +4894,72 @@ class TextEditor { // an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} // * `options` (optional) {Object} // * `center` Center the editor around the position if possible. (default: false) - scrollToScreenPosition (screenPosition, options) { - this.scrollToScreenRange(new Range(screenPosition, screenPosition), options) + scrollToScreenPosition(screenPosition, options) { + this.scrollToScreenRange( + new Range(screenPosition, screenPosition), + options + ); } - scrollToTop () { - Grim.deprecate('This is now a view method. Call TextEditorElement::scrollToTop instead.') - this.getElement().scrollToTop() + scrollToTop() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::scrollToTop instead.' + ); + this.getElement().scrollToTop(); } - scrollToBottom () { - Grim.deprecate('This is now a view method. Call TextEditorElement::scrollToTop instead.') - this.getElement().scrollToBottom() + scrollToBottom() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::scrollToTop instead.' + ); + this.getElement().scrollToBottom(); } - scrollToScreenRange (screenRange, options = {}) { - if (options.clip !== false) screenRange = this.clipScreenRange(screenRange) - const scrollEvent = {screenRange, options} - if (this.component) this.component.didRequestAutoscroll(scrollEvent) - this.emitter.emit('did-request-autoscroll', scrollEvent) + scrollToScreenRange(screenRange, options = {}) { + if (options.clip !== false) screenRange = this.clipScreenRange(screenRange); + const scrollEvent = { screenRange, options }; + if (this.component) this.component.didRequestAutoscroll(scrollEvent); + this.emitter.emit('did-request-autoscroll', scrollEvent); } - getHorizontalScrollbarHeight () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.') - return this.getElement().getHorizontalScrollbarHeight() + getHorizontalScrollbarHeight() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.' + ); + return this.getElement().getHorizontalScrollbarHeight(); } - getVerticalScrollbarWidth () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.') - return this.getElement().getVerticalScrollbarWidth() + getVerticalScrollbarWidth() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.' + ); + return this.getElement().getVerticalScrollbarWidth(); } - pageUp () { - this.moveUp(this.getRowsPerPage()) + pageUp() { + this.moveUp(this.getRowsPerPage()); } - pageDown () { - this.moveDown(this.getRowsPerPage()) + pageDown() { + this.moveDown(this.getRowsPerPage()); } - selectPageUp () { - this.selectUp(this.getRowsPerPage()) + selectPageUp() { + this.selectUp(this.getRowsPerPage()); } - selectPageDown () { - this.selectDown(this.getRowsPerPage()) + selectPageDown() { + this.selectDown(this.getRowsPerPage()); } // Returns the number of rows per page - getRowsPerPage () { + getRowsPerPage() { if (this.component) { - const clientHeight = this.component.getScrollContainerClientHeight() - const lineHeight = this.component.getLineHeight() - return Math.max(1, Math.ceil(clientHeight / lineHeight)) + const clientHeight = this.component.getScrollContainerClientHeight(); + const lineHeight = this.component.getLineHeight(); + return Math.max(1, Math.ceil(clientHeight / lineHeight)); } else { - return 1 + return 1; } } @@ -4376,21 +4970,25 @@ class TextEditor { // Experimental: Is auto-indentation enabled for this editor? // // Returns a {Boolean}. - shouldAutoIndent () { return this.autoIndent } + shouldAutoIndent() { + return this.autoIndent; + } // Experimental: Is auto-indentation on paste enabled for this editor? // // Returns a {Boolean}. - shouldAutoIndentOnPaste () { return this.autoIndentOnPaste } + shouldAutoIndentOnPaste() { + return this.autoIndentOnPaste; + } // Experimental: Does this editor allow scrolling past the last line? // // Returns a {Boolean}. - getScrollPastEnd () { + getScrollPastEnd() { if (this.getAutoHeight()) { - return false + return false; } else { - return this.scrollPastEnd + return this.scrollPastEnd; } } @@ -4398,56 +4996,72 @@ class TextEditor { // movements? // // Returns a positive {Number}. - getScrollSensitivity () { return this.scrollSensitivity } + getScrollSensitivity() { + return this.scrollSensitivity; + } // Experimental: Does this editor show cursors while there is a selection? // // Returns a positive {Boolean}. - getShowCursorOnSelection () { return this.showCursorOnSelection } + getShowCursorOnSelection() { + return this.showCursorOnSelection; + } // Experimental: Are line numbers enabled for this editor? // // Returns a {Boolean} - doesShowLineNumbers () { return this.showLineNumbers } + doesShowLineNumbers() { + return this.showLineNumbers; + } // Experimental: Get the time interval within which text editing operations // are grouped together in the editor's undo history. // // Returns the time interval {Number} in milliseconds. - getUndoGroupingInterval () { return this.undoGroupingInterval } + getUndoGroupingInterval() { + return this.undoGroupingInterval; + } // Experimental: Get the characters that are *not* considered part of words, // for the purpose of word-based cursor movements. // // Returns a {String} containing the non-word characters. - getNonWordCharacters (position) { - const languageMode = this.buffer.getLanguageMode() + getNonWordCharacters(position) { + const languageMode = this.buffer.getLanguageMode(); return ( - languageMode.getNonWordCharacters && - languageMode.getNonWordCharacters(position || Point(0, 0)) - ) || DEFAULT_NON_WORD_CHARACTERS + (languageMode.getNonWordCharacters && + languageMode.getNonWordCharacters(position || Point(0, 0))) || + DEFAULT_NON_WORD_CHARACTERS + ); } /* Section: Event Handlers */ - handleLanguageModeChange () { - this.unfoldAll() + handleLanguageModeChange() { + this.unfoldAll(); if (this.languageModeSubscription) { - this.languageModeSubscription.dispose() - this.disposables.remove(this.languageModeSubscription) + this.languageModeSubscription.dispose(); + this.disposables.remove(this.languageModeSubscription); } - const languageMode = this.buffer.getLanguageMode() + const languageMode = this.buffer.getLanguageMode(); - if (this.component && this.component.visible && languageMode.startTokenizing) { - languageMode.startTokenizing() + if ( + this.component && + this.component.visible && + languageMode.startTokenizing + ) { + languageMode.startTokenizing(); } - this.languageModeSubscription = languageMode.onDidTokenize && languageMode.onDidTokenize(() => { - this.emitter.emit('did-tokenize') - }) - if (this.languageModeSubscription) this.disposables.add(this.languageModeSubscription) - this.emitter.emit('did-change-grammar', languageMode.grammar) + this.languageModeSubscription = + languageMode.onDidTokenize && + languageMode.onDidTokenize(() => { + this.emitter.emit('did-tokenize'); + }); + if (this.languageModeSubscription) + this.disposables.add(this.languageModeSubscription); + this.emitter.emit('did-change-grammar', languageMode.grammar); } /* @@ -4455,493 +5069,622 @@ class TextEditor { */ // Get the Element for the editor. - getElement () { + getElement() { if (!this.component) { - if (!TextEditorComponent) TextEditorComponent = require('./text-editor-component') - if (!TextEditorElement) TextEditorElement = require('./text-editor-element') + if (!TextEditorComponent) + TextEditorComponent = require('./text-editor-component'); + if (!TextEditorElement) + TextEditorElement = require('./text-editor-element'); this.component = new TextEditorComponent({ model: this, updatedSynchronously: TextEditorElement.prototype.updatedSynchronously, initialScrollTopRow: this.initialScrollTopRow, initialScrollLeftColumn: this.initialScrollLeftColumn - }) + }); } - return this.component.element + return this.component.element; } - getAllowedLocations () { - return ['center'] + getAllowedLocations() { + return ['center']; } // Essential: Retrieves the greyed out placeholder of a mini editor. // // Returns a {String}. - getPlaceholderText () { return this.placeholderText } + getPlaceholderText() { + return this.placeholderText; + } // Essential: Set the greyed out placeholder of a mini editor. Placeholder text // will be displayed when the editor has no content. // // * `placeholderText` {String} text that is displayed when the editor has no content. - setPlaceholderText (placeholderText) { this.update({placeholderText}) } - - pixelPositionForBufferPosition (bufferPosition) { - Grim.deprecate('This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead') - return this.getElement().pixelPositionForBufferPosition(bufferPosition) + setPlaceholderText(placeholderText) { + this.update({ placeholderText }); } - pixelPositionForScreenPosition (screenPosition) { - Grim.deprecate('This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead') - return this.getElement().pixelPositionForScreenPosition(screenPosition) + pixelPositionForBufferPosition(bufferPosition) { + Grim.deprecate( + 'This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead' + ); + return this.getElement().pixelPositionForBufferPosition(bufferPosition); } - getVerticalScrollMargin () { - const maxScrollMargin = Math.floor(((this.height / this.getLineHeightInPixels()) - 1) / 2) - return Math.min(this.verticalScrollMargin, maxScrollMargin) + pixelPositionForScreenPosition(screenPosition) { + Grim.deprecate( + 'This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead' + ); + return this.getElement().pixelPositionForScreenPosition(screenPosition); } - setVerticalScrollMargin (verticalScrollMargin) { - this.verticalScrollMargin = verticalScrollMargin - return this.verticalScrollMargin + getVerticalScrollMargin() { + const maxScrollMargin = Math.floor( + (this.height / this.getLineHeightInPixels() - 1) / 2 + ); + return Math.min(this.verticalScrollMargin, maxScrollMargin); } - getHorizontalScrollMargin () { - return Math.min(this.horizontalScrollMargin, Math.floor(((this.width / this.getDefaultCharWidth()) - 1) / 2)) - } - setHorizontalScrollMargin (horizontalScrollMargin) { - this.horizontalScrollMargin = horizontalScrollMargin - return this.horizontalScrollMargin + setVerticalScrollMargin(verticalScrollMargin) { + this.verticalScrollMargin = verticalScrollMargin; + return this.verticalScrollMargin; } - getLineHeightInPixels () { return this.lineHeightInPixels } - setLineHeightInPixels (lineHeightInPixels) { - this.lineHeightInPixels = lineHeightInPixels - return this.lineHeightInPixels + getHorizontalScrollMargin() { + return Math.min( + this.horizontalScrollMargin, + Math.floor((this.width / this.getDefaultCharWidth() - 1) / 2) + ); + } + setHorizontalScrollMargin(horizontalScrollMargin) { + this.horizontalScrollMargin = horizontalScrollMargin; + return this.horizontalScrollMargin; } - getKoreanCharWidth () { return this.koreanCharWidth } - getHalfWidthCharWidth () { return this.halfWidthCharWidth } - getDoubleWidthCharWidth () { return this.doubleWidthCharWidth } - getDefaultCharWidth () { return this.defaultCharWidth } + getLineHeightInPixels() { + return this.lineHeightInPixels; + } + setLineHeightInPixels(lineHeightInPixels) { + this.lineHeightInPixels = lineHeightInPixels; + return this.lineHeightInPixels; + } - ratioForCharacter (character) { + getKoreanCharWidth() { + return this.koreanCharWidth; + } + getHalfWidthCharWidth() { + return this.halfWidthCharWidth; + } + getDoubleWidthCharWidth() { + return this.doubleWidthCharWidth; + } + getDefaultCharWidth() { + return this.defaultCharWidth; + } + + ratioForCharacter(character) { if (isKoreanCharacter(character)) { - return this.getKoreanCharWidth() / this.getDefaultCharWidth() + return this.getKoreanCharWidth() / this.getDefaultCharWidth(); } else if (isHalfWidthCharacter(character)) { - return this.getHalfWidthCharWidth() / this.getDefaultCharWidth() + return this.getHalfWidthCharWidth() / this.getDefaultCharWidth(); } else if (isDoubleWidthCharacter(character)) { - return this.getDoubleWidthCharWidth() / this.getDefaultCharWidth() + return this.getDoubleWidthCharWidth() / this.getDefaultCharWidth(); } else { - return 1 + return 1; } } - setDefaultCharWidth (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) { - if (doubleWidthCharWidth == null) { doubleWidthCharWidth = defaultCharWidth } - if (halfWidthCharWidth == null) { halfWidthCharWidth = defaultCharWidth } - if (koreanCharWidth == null) { koreanCharWidth = defaultCharWidth } - if (defaultCharWidth !== this.defaultCharWidth || - (doubleWidthCharWidth !== this.doubleWidthCharWidth && - halfWidthCharWidth !== this.halfWidthCharWidth && - koreanCharWidth !== this.koreanCharWidth)) { - this.defaultCharWidth = defaultCharWidth - this.doubleWidthCharWidth = doubleWidthCharWidth - this.halfWidthCharWidth = halfWidthCharWidth - this.koreanCharWidth = koreanCharWidth + setDefaultCharWidth( + defaultCharWidth, + doubleWidthCharWidth, + halfWidthCharWidth, + koreanCharWidth + ) { + if (doubleWidthCharWidth == null) { + doubleWidthCharWidth = defaultCharWidth; + } + if (halfWidthCharWidth == null) { + halfWidthCharWidth = defaultCharWidth; + } + if (koreanCharWidth == null) { + koreanCharWidth = defaultCharWidth; + } + if ( + defaultCharWidth !== this.defaultCharWidth || + (doubleWidthCharWidth !== this.doubleWidthCharWidth && + halfWidthCharWidth !== this.halfWidthCharWidth && + koreanCharWidth !== this.koreanCharWidth) + ) { + this.defaultCharWidth = defaultCharWidth; + this.doubleWidthCharWidth = doubleWidthCharWidth; + this.halfWidthCharWidth = halfWidthCharWidth; + this.koreanCharWidth = koreanCharWidth; if (this.isSoftWrapped()) { this.displayLayer.reset({ softWrapColumn: this.getSoftWrapColumn() - }) + }); } } - return defaultCharWidth + return defaultCharWidth; } - setHeight (height) { - Grim.deprecate('This is now a view method. Call TextEditorElement::setHeight instead.') - this.getElement().setHeight(height) + setHeight(height) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::setHeight instead.' + ); + this.getElement().setHeight(height); } - getHeight () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getHeight instead.') - return this.getElement().getHeight() + getHeight() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getHeight instead.' + ); + return this.getElement().getHeight(); } - getAutoHeight () { return this.autoHeight != null ? this.autoHeight : true } - - getAutoWidth () { return this.autoWidth != null ? this.autoWidth : false } - - setWidth (width) { - Grim.deprecate('This is now a view method. Call TextEditorElement::setWidth instead.') - this.getElement().setWidth(width) + getAutoHeight() { + return this.autoHeight != null ? this.autoHeight : true; } - getWidth () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getWidth instead.') - return this.getElement().getWidth() + getAutoWidth() { + return this.autoWidth != null ? this.autoWidth : false; + } + + setWidth(width) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::setWidth instead.' + ); + this.getElement().setWidth(width); + } + + getWidth() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getWidth instead.' + ); + return this.getElement().getWidth(); } // Use setScrollTopRow instead of this method - setFirstVisibleScreenRow (screenRow) { - this.setScrollTopRow(screenRow) + setFirstVisibleScreenRow(screenRow) { + this.setScrollTopRow(screenRow); } - getFirstVisibleScreenRow () { - return this.getElement().component.getFirstVisibleRow() + getFirstVisibleScreenRow() { + return this.getElement().component.getFirstVisibleRow(); } - getLastVisibleScreenRow () { - return this.getElement().component.getLastVisibleRow() + getLastVisibleScreenRow() { + return this.getElement().component.getLastVisibleRow(); } - getVisibleRowRange () { - return [this.getFirstVisibleScreenRow(), this.getLastVisibleScreenRow()] + getVisibleRowRange() { + return [this.getFirstVisibleScreenRow(), this.getLastVisibleScreenRow()]; } // Use setScrollLeftColumn instead of this method - setFirstVisibleScreenColumn (column) { - return this.setScrollLeftColumn(column) + setFirstVisibleScreenColumn(column) { + return this.setScrollLeftColumn(column); } - getFirstVisibleScreenColumn () { - return this.getElement().component.getFirstVisibleColumn() + getFirstVisibleScreenColumn() { + return this.getElement().component.getFirstVisibleColumn(); } - getScrollTop () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getScrollTop instead.') - return this.getElement().getScrollTop() + getScrollTop() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getScrollTop instead.' + ); + return this.getElement().getScrollTop(); } - setScrollTop (scrollTop) { - Grim.deprecate('This is now a view method. Call TextEditorElement::setScrollTop instead.') - this.getElement().setScrollTop(scrollTop) + setScrollTop(scrollTop) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::setScrollTop instead.' + ); + this.getElement().setScrollTop(scrollTop); } - getScrollBottom () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getScrollBottom instead.') - return this.getElement().getScrollBottom() + getScrollBottom() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getScrollBottom instead.' + ); + return this.getElement().getScrollBottom(); } - setScrollBottom (scrollBottom) { - Grim.deprecate('This is now a view method. Call TextEditorElement::setScrollBottom instead.') - this.getElement().setScrollBottom(scrollBottom) + setScrollBottom(scrollBottom) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::setScrollBottom instead.' + ); + this.getElement().setScrollBottom(scrollBottom); } - getScrollLeft () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getScrollLeft instead.') - return this.getElement().getScrollLeft() + getScrollLeft() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getScrollLeft instead.' + ); + return this.getElement().getScrollLeft(); } - setScrollLeft (scrollLeft) { - Grim.deprecate('This is now a view method. Call TextEditorElement::setScrollLeft instead.') - this.getElement().setScrollLeft(scrollLeft) + setScrollLeft(scrollLeft) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::setScrollLeft instead.' + ); + this.getElement().setScrollLeft(scrollLeft); } - getScrollRight () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getScrollRight instead.') - return this.getElement().getScrollRight() + getScrollRight() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getScrollRight instead.' + ); + return this.getElement().getScrollRight(); } - setScrollRight (scrollRight) { - Grim.deprecate('This is now a view method. Call TextEditorElement::setScrollRight instead.') - this.getElement().setScrollRight(scrollRight) + setScrollRight(scrollRight) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::setScrollRight instead.' + ); + this.getElement().setScrollRight(scrollRight); } - getScrollHeight () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getScrollHeight instead.') - return this.getElement().getScrollHeight() + getScrollHeight() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getScrollHeight instead.' + ); + return this.getElement().getScrollHeight(); } - getScrollWidth () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getScrollWidth instead.') - return this.getElement().getScrollWidth() + getScrollWidth() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getScrollWidth instead.' + ); + return this.getElement().getScrollWidth(); } - getMaxScrollTop () { - Grim.deprecate('This is now a view method. Call TextEditorElement::getMaxScrollTop instead.') - return this.getElement().getMaxScrollTop() + getMaxScrollTop() { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::getMaxScrollTop instead.' + ); + return this.getElement().getMaxScrollTop(); } - getScrollTopRow () { - return this.getElement().component.getScrollTopRow() + getScrollTopRow() { + return this.getElement().component.getScrollTopRow(); } - setScrollTopRow (scrollTopRow) { - this.getElement().component.setScrollTopRow(scrollTopRow) + setScrollTopRow(scrollTopRow) { + this.getElement().component.setScrollTopRow(scrollTopRow); } - getScrollLeftColumn () { - return this.getElement().component.getScrollLeftColumn() + getScrollLeftColumn() { + return this.getElement().component.getScrollLeftColumn(); } - setScrollLeftColumn (scrollLeftColumn) { - this.getElement().component.setScrollLeftColumn(scrollLeftColumn) + setScrollLeftColumn(scrollLeftColumn) { + this.getElement().component.setScrollLeftColumn(scrollLeftColumn); } - intersectsVisibleRowRange (startRow, endRow) { - Grim.deprecate('This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.') - return this.getElement().intersectsVisibleRowRange(startRow, endRow) + intersectsVisibleRowRange(startRow, endRow) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.' + ); + return this.getElement().intersectsVisibleRowRange(startRow, endRow); } - selectionIntersectsVisibleRowRange (selection) { - Grim.deprecate('This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.') - return this.getElement().selectionIntersectsVisibleRowRange(selection) + selectionIntersectsVisibleRowRange(selection) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.' + ); + return this.getElement().selectionIntersectsVisibleRowRange(selection); } - screenPositionForPixelPosition (pixelPosition) { - Grim.deprecate('This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.') - return this.getElement().screenPositionForPixelPosition(pixelPosition) + screenPositionForPixelPosition(pixelPosition) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.' + ); + return this.getElement().screenPositionForPixelPosition(pixelPosition); } - pixelRectForScreenRange (screenRange) { - Grim.deprecate('This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.') - return this.getElement().pixelRectForScreenRange(screenRange) + pixelRectForScreenRange(screenRange) { + Grim.deprecate( + 'This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.' + ); + return this.getElement().pixelRectForScreenRange(screenRange); } /* Section: Utility */ - inspect () { - return `` + inspect() { + return ``; } - emitWillInsertTextEvent (text) { - let result = true - const cancel = () => { result = false } - this.emitter.emit('will-insert-text', {cancel, text}) - return result + emitWillInsertTextEvent(text) { + let result = true; + const cancel = () => { + result = false; + }; + this.emitter.emit('will-insert-text', { cancel, text }); + return result; } /* Section: Language Mode Delegated Methods */ - suggestedIndentForBufferRow (bufferRow, options) { - const languageMode = this.buffer.getLanguageMode() + suggestedIndentForBufferRow(bufferRow, options) { + const languageMode = this.buffer.getLanguageMode(); return ( languageMode.suggestedIndentForBufferRow && - languageMode.suggestedIndentForBufferRow(bufferRow, this.getTabLength(), options) - ) + languageMode.suggestedIndentForBufferRow( + bufferRow, + this.getTabLength(), + options + ) + ); } // Given a buffer row, indent it. // // * bufferRow - The row {Number}. // * options - An options {Object} to pass through to {TextEditor::setIndentationForBufferRow}. - autoIndentBufferRow (bufferRow, options) { - const indentLevel = this.suggestedIndentForBufferRow(bufferRow, options) - return this.setIndentationForBufferRow(bufferRow, indentLevel, options) + autoIndentBufferRow(bufferRow, options) { + const indentLevel = this.suggestedIndentForBufferRow(bufferRow, options); + return this.setIndentationForBufferRow(bufferRow, indentLevel, options); } // Indents all the rows between two buffer row numbers. // // * startRow - The row {Number} to start at // * endRow - The row {Number} to end at - autoIndentBufferRows (startRow, endRow) { - let row = startRow + autoIndentBufferRows(startRow, endRow) { + let row = startRow; while (row <= endRow) { - this.autoIndentBufferRow(row) - row++ + this.autoIndentBufferRow(row); + row++; } } - autoDecreaseIndentForBufferRow (bufferRow) { - const languageMode = this.buffer.getLanguageMode() - const indentLevel = ( + autoDecreaseIndentForBufferRow(bufferRow) { + const languageMode = this.buffer.getLanguageMode(); + const indentLevel = languageMode.suggestedIndentForEditedBufferRow && - languageMode.suggestedIndentForEditedBufferRow(bufferRow, this.getTabLength()) - ) - if (indentLevel != null) this.setIndentationForBufferRow(bufferRow, indentLevel) + languageMode.suggestedIndentForEditedBufferRow( + bufferRow, + this.getTabLength() + ); + if (indentLevel != null) + this.setIndentationForBufferRow(bufferRow, indentLevel); } - toggleLineCommentForBufferRow (row) { this.toggleLineCommentsForBufferRows(row, row) } + toggleLineCommentForBufferRow(row) { + this.toggleLineCommentsForBufferRows(row, row); + } - toggleLineCommentsForBufferRows (start, end, options = {}) { - const languageMode = this.buffer.getLanguageMode() - let {commentStartString, commentEndString} = - languageMode.commentStringsForPosition && - languageMode.commentStringsForPosition(new Point(start, 0)) || {} - if (!commentStartString) return - commentStartString = commentStartString.trim() + toggleLineCommentsForBufferRows(start, end, options = {}) { + const languageMode = this.buffer.getLanguageMode(); + let { commentStartString, commentEndString } = + (languageMode.commentStringsForPosition && + languageMode.commentStringsForPosition(new Point(start, 0))) || + {}; + if (!commentStartString) return; + commentStartString = commentStartString.trim(); if (commentEndString) { - commentEndString = commentEndString.trim() + commentEndString = commentEndString.trim(); const startDelimiterColumnRange = columnRangeForStartDelimiter( this.buffer.lineForRow(start), commentStartString - ) + ); if (startDelimiterColumnRange) { const endDelimiterColumnRange = columnRangeForEndDelimiter( this.buffer.lineForRow(end), commentEndString - ) + ); if (endDelimiterColumnRange) { this.buffer.transact(() => { - this.buffer.delete([[end, endDelimiterColumnRange[0]], [end, endDelimiterColumnRange[1]]]) - this.buffer.delete([[start, startDelimiterColumnRange[0]], [start, startDelimiterColumnRange[1]]]) - }) + this.buffer.delete([ + [end, endDelimiterColumnRange[0]], + [end, endDelimiterColumnRange[1]] + ]); + this.buffer.delete([ + [start, startDelimiterColumnRange[0]], + [start, startDelimiterColumnRange[1]] + ]); + }); } } else { this.buffer.transact(() => { - const indentLength = this.buffer.lineForRow(start).match(/^\s*/)[0].length - this.buffer.insert([start, indentLength], commentStartString + ' ') - this.buffer.insert([end, this.buffer.lineLengthForRow(end)], ' ' + commentEndString) + const indentLength = this.buffer.lineForRow(start).match(/^\s*/)[0] + .length; + this.buffer.insert([start, indentLength], commentStartString + ' '); + this.buffer.insert( + [end, this.buffer.lineLengthForRow(end)], + ' ' + commentEndString + ); // Prevent the cursor from selecting / passing the delimiters // See https://github.com/atom/atom/pull/17519 if (options.correctSelection && options.selection) { - const endLineLength = this.buffer.lineLengthForRow(end) - const oldRange = options.selection.getBufferRange() + const endLineLength = this.buffer.lineLengthForRow(end); + const oldRange = options.selection.getBufferRange(); if (oldRange.isEmpty()) { if (oldRange.start.column === endLineLength) { - const endCol = endLineLength - commentEndString.length - 1 - options.selection.setBufferRange([[end, endCol], [end, endCol]], {autoscroll: false}) + const endCol = endLineLength - commentEndString.length - 1; + options.selection.setBufferRange( + [[end, endCol], [end, endCol]], + { autoscroll: false } + ); } } else { - const startDelta = oldRange.start.column === indentLength ? [0, commentStartString.length + 1] : [0, 0] - const endDelta = oldRange.end.column === endLineLength ? [0, -commentEndString.length - 1] : [0, 0] - options.selection.setBufferRange(oldRange.translate(startDelta, endDelta), {autoscroll: false}) + const startDelta = + oldRange.start.column === indentLength + ? [0, commentStartString.length + 1] + : [0, 0]; + const endDelta = + oldRange.end.column === endLineLength + ? [0, -commentEndString.length - 1] + : [0, 0]; + options.selection.setBufferRange( + oldRange.translate(startDelta, endDelta), + { autoscroll: false } + ); } } - }) + }); } } else { - let hasCommentedLines = false - let hasUncommentedLines = false + let hasCommentedLines = false; + let hasUncommentedLines = false; for (let row = start; row <= end; row++) { - const line = this.buffer.lineForRow(row) + const line = this.buffer.lineForRow(row); if (NON_WHITESPACE_REGEXP.test(line)) { if (columnRangeForStartDelimiter(line, commentStartString)) { - hasCommentedLines = true + hasCommentedLines = true; } else { - hasUncommentedLines = true + hasUncommentedLines = true; } } } - const shouldUncomment = hasCommentedLines && !hasUncommentedLines + const shouldUncomment = hasCommentedLines && !hasUncommentedLines; if (shouldUncomment) { for (let row = start; row <= end; row++) { const columnRange = columnRangeForStartDelimiter( this.buffer.lineForRow(row), commentStartString - ) - if (columnRange) this.buffer.delete([[row, columnRange[0]], [row, columnRange[1]]]) + ); + if (columnRange) + this.buffer.delete([[row, columnRange[0]], [row, columnRange[1]]]); } } else { - let minIndentLevel = Infinity - let minBlankIndentLevel = Infinity + let minIndentLevel = Infinity; + let minBlankIndentLevel = Infinity; for (let row = start; row <= end; row++) { - const line = this.buffer.lineForRow(row) - const indentLevel = this.indentLevelForLine(line) + const line = this.buffer.lineForRow(row); + const indentLevel = this.indentLevelForLine(line); if (NON_WHITESPACE_REGEXP.test(line)) { - if (indentLevel < minIndentLevel) minIndentLevel = indentLevel + if (indentLevel < minIndentLevel) minIndentLevel = indentLevel; } else { - if (indentLevel < minBlankIndentLevel) minBlankIndentLevel = indentLevel + if (indentLevel < minBlankIndentLevel) + minBlankIndentLevel = indentLevel; } } minIndentLevel = Number.isFinite(minIndentLevel) ? minIndentLevel : Number.isFinite(minBlankIndentLevel) - ? minBlankIndentLevel - : 0 + ? minBlankIndentLevel + : 0; - const indentString = this.buildIndentString(minIndentLevel) + const indentString = this.buildIndentString(minIndentLevel); for (let row = start; row <= end; row++) { - const line = this.buffer.lineForRow(row) + const line = this.buffer.lineForRow(row); if (NON_WHITESPACE_REGEXP.test(line)) { - const indentColumn = columnForIndentLevel(line, minIndentLevel, this.getTabLength()) - this.buffer.insert(Point(row, indentColumn), commentStartString + ' ') + const indentColumn = columnForIndentLevel( + line, + minIndentLevel, + this.getTabLength() + ); + this.buffer.insert( + Point(row, indentColumn), + commentStartString + ' ' + ); } else { this.buffer.setTextInRange( new Range(new Point(row, 0), new Point(row, Infinity)), indentString + commentStartString + ' ' - ) + ); } } } } } - rowRangeForParagraphAtBufferRow (bufferRow) { - if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(bufferRow))) return + rowRangeForParagraphAtBufferRow(bufferRow) { + if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(bufferRow))) + return; - const languageMode = this.buffer.getLanguageMode() - const isCommented = languageMode.isRowCommented(bufferRow) + const languageMode = this.buffer.getLanguageMode(); + const isCommented = languageMode.isRowCommented(bufferRow); - let startRow = bufferRow + let startRow = bufferRow; while (startRow > 0) { - if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(startRow - 1))) break - if (languageMode.isRowCommented(startRow - 1) !== isCommented) break - startRow-- + if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(startRow - 1))) + break; + if (languageMode.isRowCommented(startRow - 1) !== isCommented) break; + startRow--; } - let endRow = bufferRow - const rowCount = this.getLineCount() + let endRow = bufferRow; + const rowCount = this.getLineCount(); while (endRow + 1 < rowCount) { - if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(endRow + 1))) break - if (languageMode.isRowCommented(endRow + 1) !== isCommented) break - endRow++ + if (!NON_WHITESPACE_REGEXP.test(this.lineTextForBufferRow(endRow + 1))) + break; + if (languageMode.isRowCommented(endRow + 1) !== isCommented) break; + endRow++; } - return new Range(new Point(startRow, 0), new Point(endRow, this.buffer.lineLengthForRow(endRow))) + return new Range( + new Point(startRow, 0), + new Point(endRow, this.buffer.lineLengthForRow(endRow)) + ); } -} +}; -function columnForIndentLevel (line, indentLevel, tabLength) { - let column = 0 - let indentLength = 0 - const goalIndentLength = indentLevel * tabLength +function columnForIndentLevel(line, indentLevel, tabLength) { + let column = 0; + let indentLength = 0; + const goalIndentLength = indentLevel * tabLength; while (indentLength < goalIndentLength) { - const char = line[column] + const char = line[column]; if (char === '\t') { - indentLength += tabLength - (indentLength % tabLength) + indentLength += tabLength - (indentLength % tabLength); } else if (char === ' ') { - indentLength++ + indentLength++; } else { - break + break; } - column++ + column++; } - return column + return column; } -function columnRangeForStartDelimiter (line, delimiter) { - const startColumn = line.search(NON_WHITESPACE_REGEXP) - if (startColumn === -1) return null - if (!line.startsWith(delimiter, startColumn)) return null +function columnRangeForStartDelimiter(line, delimiter) { + const startColumn = line.search(NON_WHITESPACE_REGEXP); + if (startColumn === -1) return null; + if (!line.startsWith(delimiter, startColumn)) return null; - let endColumn = startColumn + delimiter.length - if (line[endColumn] === ' ') endColumn++ - return [startColumn, endColumn] + let endColumn = startColumn + delimiter.length; + if (line[endColumn] === ' ') endColumn++; + return [startColumn, endColumn]; } -function columnRangeForEndDelimiter (line, delimiter) { - let startColumn = line.lastIndexOf(delimiter) - if (startColumn === -1) return null +function columnRangeForEndDelimiter(line, delimiter) { + let startColumn = line.lastIndexOf(delimiter); + if (startColumn === -1) return null; - const endColumn = startColumn + delimiter.length - if (NON_WHITESPACE_REGEXP.test(line.slice(endColumn))) return null - if (line[startColumn - 1] === ' ') startColumn-- - return [startColumn, endColumn] + const endColumn = startColumn + delimiter.length; + if (NON_WHITESPACE_REGEXP.test(line.slice(endColumn))) return null; + if (line[startColumn - 1] === ' ') startColumn--; + return [startColumn, endColumn]; } class ChangeEvent { - constructor ({oldRange, newRange}) { - this.oldRange = oldRange - this.newRange = newRange + constructor({ oldRange, newRange }) { + this.oldRange = oldRange; + this.newRange = newRange; } - get start () { - return this.newRange.start + get start() { + return this.newRange.start; } - get oldExtent () { - return this.oldRange.getExtent() + get oldExtent() { + return this.oldRange.getExtent(); } - get newExtent () { - return this.newRange.getExtent() + get newExtent() { + return this.newRange.getExtent(); } } diff --git a/src/text-mate-language-mode.js b/src/text-mate-language-mode.js index 8cfd23a34..71f2ee701 100644 --- a/src/text-mate-language-mode.js +++ b/src/text-mate-language-mode.js @@ -1,68 +1,76 @@ -const _ = require('underscore-plus') -const {CompositeDisposable, Emitter} = require('event-kit') -const {Point, Range} = require('text-buffer') -const TokenizedLine = require('./tokenized-line') -const TokenIterator = require('./token-iterator') -const ScopeDescriptor = require('./scope-descriptor') -const NullGrammar = require('./null-grammar') -const {OnigRegExp} = require('oniguruma') -const {toFirstMateScopeId, fromFirstMateScopeId} = require('./first-mate-helpers') -const {selectorMatchesAnyScope} = require('./selectors') +const _ = require('underscore-plus'); +const { CompositeDisposable, Emitter } = require('event-kit'); +const { Point, Range } = require('text-buffer'); +const TokenizedLine = require('./tokenized-line'); +const TokenIterator = require('./token-iterator'); +const ScopeDescriptor = require('./scope-descriptor'); +const NullGrammar = require('./null-grammar'); +const { OnigRegExp } = require('oniguruma'); +const { + toFirstMateScopeId, + fromFirstMateScopeId +} = require('./first-mate-helpers'); +const { selectorMatchesAnyScope } = require('./selectors'); -const NON_WHITESPACE_REGEX = /\S/ +const NON_WHITESPACE_REGEX = /\S/; -let nextId = 0 -const prefixedScopes = new Map() +let nextId = 0; +const prefixedScopes = new Map(); class TextMateLanguageMode { - constructor (params) { - this.emitter = new Emitter() - this.disposables = new CompositeDisposable() - this.tokenIterator = new TokenIterator(this) - this.regexesByPattern = {} + constructor(params) { + this.emitter = new Emitter(); + this.disposables = new CompositeDisposable(); + this.tokenIterator = new TokenIterator(this); + this.regexesByPattern = {}; - this.alive = true - this.tokenizationStarted = false - this.id = params.id != null ? params.id : nextId++ - this.buffer = params.buffer - this.largeFileMode = params.largeFileMode - this.config = params.config - this.largeFileMode = params.largeFileMode != null - ? params.largeFileMode - : this.buffer.buffer.getLength() >= 2 * 1024 * 1024 + this.alive = true; + this.tokenizationStarted = false; + this.id = params.id != null ? params.id : nextId++; + this.buffer = params.buffer; + this.largeFileMode = params.largeFileMode; + this.config = params.config; + this.largeFileMode = + params.largeFileMode != null + ? params.largeFileMode + : this.buffer.buffer.getLength() >= 2 * 1024 * 1024; - this.grammar = params.grammar || NullGrammar - this.rootScopeDescriptor = new ScopeDescriptor({scopes: [this.grammar.scopeName]}) - this.disposables.add(this.grammar.onDidUpdate(() => this.retokenizeLines())) - this.retokenizeLines() + this.grammar = params.grammar || NullGrammar; + this.rootScopeDescriptor = new ScopeDescriptor({ + scopes: [this.grammar.scopeName] + }); + this.disposables.add( + this.grammar.onDidUpdate(() => this.retokenizeLines()) + ); + this.retokenizeLines(); } - destroy () { - if (!this.alive) return - this.alive = false - this.disposables.dispose() - this.tokenizedLines.length = 0 + destroy() { + if (!this.alive) return; + this.alive = false; + this.disposables.dispose(); + this.tokenizedLines.length = 0; } - isAlive () { - return this.alive + isAlive() { + return this.alive; } - isDestroyed () { - return !this.alive + isDestroyed() { + return !this.alive; } - getGrammar () { - return this.grammar + getGrammar() { + return this.grammar; } - getLanguageId () { - return this.grammar.scopeName + getLanguageId() { + return this.grammar.scopeName; } - getNonWordCharacters (position) { - const scope = this.scopeDescriptorForPosition(position) - return this.config.get('editor.nonWordCharacters', {scope}) + getNonWordCharacters(position) { + const scope = this.scopeDescriptorForPosition(position); + return this.config.get('editor.nonWordCharacters', { scope }); } /* @@ -74,19 +82,21 @@ class TextMateLanguageMode { // * bufferRow - A {Number} indicating the buffer row // // Returns a {Number}. - suggestedIndentForBufferRow (bufferRow, tabLength, options) { - const line = this.buffer.lineForRow(bufferRow) - const tokenizedLine = this.tokenizedLineForRow(bufferRow) - const iterator = tokenizedLine.getTokenIterator() - iterator.next() - const scopeDescriptor = new ScopeDescriptor({scopes: iterator.getScopes()}) + suggestedIndentForBufferRow(bufferRow, tabLength, options) { + const line = this.buffer.lineForRow(bufferRow); + const tokenizedLine = this.tokenizedLineForRow(bufferRow); + const iterator = tokenizedLine.getTokenIterator(); + iterator.next(); + const scopeDescriptor = new ScopeDescriptor({ + scopes: iterator.getScopes() + }); return this._suggestedIndentForLineWithScopeAtBufferRow( bufferRow, line, scopeDescriptor, tabLength, options - ) + ); } // Get the suggested indentation level for a given line of text, if it were inserted at the given @@ -95,17 +105,22 @@ class TextMateLanguageMode { // * bufferRow - A {Number} indicating the buffer row // // Returns a {Number}. - suggestedIndentForLineAtBufferRow (bufferRow, line, tabLength) { - const tokenizedLine = this.buildTokenizedLineForRowWithText(bufferRow, line) - const iterator = tokenizedLine.getTokenIterator() - iterator.next() - const scopeDescriptor = new ScopeDescriptor({scopes: iterator.getScopes()}) + suggestedIndentForLineAtBufferRow(bufferRow, line, tabLength) { + const tokenizedLine = this.buildTokenizedLineForRowWithText( + bufferRow, + line + ); + const iterator = tokenizedLine.getTokenIterator(); + iterator.next(); + const scopeDescriptor = new ScopeDescriptor({ + scopes: iterator.getScopes() + }); return this._suggestedIndentForLineWithScopeAtBufferRow( bufferRow, line, scopeDescriptor, tabLength - ) + ); } // Get the suggested indentation level for a line in the buffer on which the user is currently @@ -116,301 +131,381 @@ class TextMateLanguageMode { // * bufferRow - The row {Number} // // Returns a {Number}. - suggestedIndentForEditedBufferRow (bufferRow, tabLength) { - const line = this.buffer.lineForRow(bufferRow) - const currentIndentLevel = this.indentLevelForLine(line, tabLength) - if (currentIndentLevel === 0) return + suggestedIndentForEditedBufferRow(bufferRow, tabLength) { + const line = this.buffer.lineForRow(bufferRow); + const currentIndentLevel = this.indentLevelForLine(line, tabLength); + if (currentIndentLevel === 0) return; - const scopeDescriptor = this.scopeDescriptorForPosition(new Point(bufferRow, 0)) - const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor(scopeDescriptor) - if (!decreaseIndentRegex) return + const scopeDescriptor = this.scopeDescriptorForPosition( + new Point(bufferRow, 0) + ); + const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor( + scopeDescriptor + ); + if (!decreaseIndentRegex) return; - if (!decreaseIndentRegex.testSync(line)) return + if (!decreaseIndentRegex.testSync(line)) return; - const precedingRow = this.buffer.previousNonBlankRow(bufferRow) - if (precedingRow == null) return + const precedingRow = this.buffer.previousNonBlankRow(bufferRow); + if (precedingRow == null) return; - const precedingLine = this.buffer.lineForRow(precedingRow) - let desiredIndentLevel = this.indentLevelForLine(precedingLine, tabLength) + const precedingLine = this.buffer.lineForRow(precedingRow); + let desiredIndentLevel = this.indentLevelForLine(precedingLine, tabLength); - const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor(scopeDescriptor) + const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor( + scopeDescriptor + ); if (increaseIndentRegex) { - if (!increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1 + if (!increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1; } - const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor) + const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor( + scopeDescriptor + ); if (decreaseNextIndentRegex) { - if (decreaseNextIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1 + if (decreaseNextIndentRegex.testSync(precedingLine)) + desiredIndentLevel -= 1; } - if (desiredIndentLevel < 0) return 0 - if (desiredIndentLevel >= currentIndentLevel) return - return desiredIndentLevel + if (desiredIndentLevel < 0) return 0; + if (desiredIndentLevel >= currentIndentLevel) return; + return desiredIndentLevel; } - _suggestedIndentForLineWithScopeAtBufferRow (bufferRow, line, scopeDescriptor, tabLength, options) { - const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor(scopeDescriptor) - const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor(scopeDescriptor) - const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor) + _suggestedIndentForLineWithScopeAtBufferRow( + bufferRow, + line, + scopeDescriptor, + tabLength, + options + ) { + const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor( + scopeDescriptor + ); + const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor( + scopeDescriptor + ); + const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor( + scopeDescriptor + ); - let precedingRow + let precedingRow; if (!options || options.skipBlankLines !== false) { - precedingRow = this.buffer.previousNonBlankRow(bufferRow) - if (precedingRow == null) return 0 + precedingRow = this.buffer.previousNonBlankRow(bufferRow); + if (precedingRow == null) return 0; } else { - precedingRow = bufferRow - 1 - if (precedingRow < 0) return 0 + precedingRow = bufferRow - 1; + if (precedingRow < 0) return 0; } - const precedingLine = this.buffer.lineForRow(precedingRow) - let desiredIndentLevel = this.indentLevelForLine(precedingLine, tabLength) - if (!increaseIndentRegex) return desiredIndentLevel + const precedingLine = this.buffer.lineForRow(precedingRow); + let desiredIndentLevel = this.indentLevelForLine(precedingLine, tabLength); + if (!increaseIndentRegex) return desiredIndentLevel; if (!this.isRowCommented(precedingRow)) { - if (increaseIndentRegex && increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel += 1 - if (decreaseNextIndentRegex && decreaseNextIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1 + if (increaseIndentRegex && increaseIndentRegex.testSync(precedingLine)) + desiredIndentLevel += 1; + if ( + decreaseNextIndentRegex && + decreaseNextIndentRegex.testSync(precedingLine) + ) + desiredIndentLevel -= 1; } if (!this.buffer.isRowBlank(precedingRow)) { - if (decreaseIndentRegex && decreaseIndentRegex.testSync(line)) desiredIndentLevel -= 1 + if (decreaseIndentRegex && decreaseIndentRegex.testSync(line)) + desiredIndentLevel -= 1; } - return Math.max(desiredIndentLevel, 0) + return Math.max(desiredIndentLevel, 0); } /* Section - Comments */ - commentStringsForPosition (position) { - const scope = this.scopeDescriptorForPosition(position) - const commentStartEntries = this.config.getAll('editor.commentStart', {scope}) - const commentEndEntries = this.config.getAll('editor.commentEnd', {scope}) - const commentStartEntry = commentStartEntries[0] - const commentEndEntry = commentEndEntries.find((entry) => { - return entry.scopeSelector === commentStartEntry.scopeSelector - }) + commentStringsForPosition(position) { + const scope = this.scopeDescriptorForPosition(position); + const commentStartEntries = this.config.getAll('editor.commentStart', { + scope + }); + const commentEndEntries = this.config.getAll('editor.commentEnd', { + scope + }); + const commentStartEntry = commentStartEntries[0]; + const commentEndEntry = commentEndEntries.find(entry => { + return entry.scopeSelector === commentStartEntry.scopeSelector; + }); return { commentStartString: commentStartEntry && commentStartEntry.value, commentEndString: commentEndEntry && commentEndEntry.value - } + }; } /* Section - Syntax Highlighting */ - buildHighlightIterator () { - return new TextMateHighlightIterator(this) + buildHighlightIterator() { + return new TextMateHighlightIterator(this); } - classNameForScopeId (id) { - const scope = this.grammar.scopeForId(toFirstMateScopeId(id)) + classNameForScopeId(id) { + const scope = this.grammar.scopeForId(toFirstMateScopeId(id)); if (scope) { - let prefixedScope = prefixedScopes.get(scope) + let prefixedScope = prefixedScopes.get(scope); if (prefixedScope) { - return prefixedScope + return prefixedScope; } else { - prefixedScope = `syntax--${scope.replace(/\./g, ' syntax--')}` - prefixedScopes.set(scope, prefixedScope) - return prefixedScope + prefixedScope = `syntax--${scope.replace(/\./g, ' syntax--')}`; + prefixedScopes.set(scope, prefixedScope); + return prefixedScope; } } else { - return null + return null; } } - getInvalidatedRanges () { - return [] + getInvalidatedRanges() { + return []; } - onDidChangeHighlighting (fn) { - return this.emitter.on('did-change-highlighting', fn) + onDidChangeHighlighting(fn) { + return this.emitter.on('did-change-highlighting', fn); } - onDidTokenize (callback) { - return this.emitter.on('did-tokenize', callback) + onDidTokenize(callback) { + return this.emitter.on('did-tokenize', callback); } - getGrammarSelectionContent () { - return this.buffer.getTextInRange([[0, 0], [10, 0]]) + getGrammarSelectionContent() { + return this.buffer.getTextInRange([[0, 0], [10, 0]]); } - updateForInjection (grammar) { - if (!grammar.injectionSelector) return + updateForInjection(grammar) { + if (!grammar.injectionSelector) return; for (const tokenizedLine of this.tokenizedLines) { if (tokenizedLine) { for (let token of tokenizedLine.tokens) { if (grammar.injectionSelector.matches(token.scopes)) { - this.retokenizeLines() - return + this.retokenizeLines(); + return; } } } } } - retokenizeLines () { - if (!this.alive) return - this.fullyTokenized = false - this.tokenizedLines = new Array(this.buffer.getLineCount()) - this.invalidRows = [] + retokenizeLines() { + if (!this.alive) return; + this.fullyTokenized = false; + this.tokenizedLines = new Array(this.buffer.getLineCount()); + this.invalidRows = []; if (this.largeFileMode || this.grammar.name === 'Null Grammar') { - this.markTokenizationComplete() + this.markTokenizationComplete(); } else { - this.invalidateRow(0) + this.invalidateRow(0); } } - startTokenizing () { - this.tokenizationStarted = true + startTokenizing() { + this.tokenizationStarted = true; if (this.grammar.name !== 'Null Grammar' && !this.largeFileMode) { - this.tokenizeInBackground() + this.tokenizeInBackground(); } } - tokenizeInBackground () { - if (!this.tokenizationStarted || this.pendingChunk || !this.alive) return + tokenizeInBackground() { + if (!this.tokenizationStarted || this.pendingChunk || !this.alive) return; - this.pendingChunk = true + this.pendingChunk = true; _.defer(() => { - this.pendingChunk = false - if (this.isAlive() && this.buffer.isAlive()) this.tokenizeNextChunk() - }) + this.pendingChunk = false; + if (this.isAlive() && this.buffer.isAlive()) this.tokenizeNextChunk(); + }); } - tokenizeNextChunk () { - let rowsRemaining = this.chunkSize + tokenizeNextChunk() { + let rowsRemaining = this.chunkSize; while (this.firstInvalidRow() != null && rowsRemaining > 0) { - var endRow, filledRegion - const startRow = this.invalidRows.shift() - const lastRow = this.buffer.getLastRow() - if (startRow > lastRow) continue + var endRow, filledRegion; + const startRow = this.invalidRows.shift(); + const lastRow = this.buffer.getLastRow(); + if (startRow > lastRow) continue; - let row = startRow + let row = startRow; while (true) { - const previousStack = this.stackForRow(row) - this.tokenizedLines[row] = this.buildTokenizedLineForRow(row, this.stackForRow(row - 1), this.openScopesForRow(row)) + const previousStack = this.stackForRow(row); + this.tokenizedLines[row] = this.buildTokenizedLineForRow( + row, + this.stackForRow(row - 1), + this.openScopesForRow(row) + ); if (--rowsRemaining === 0) { - filledRegion = false - endRow = row - break + filledRegion = false; + endRow = row; + break; } - if (row === lastRow || _.isEqual(this.stackForRow(row), previousStack)) { - filledRegion = true - endRow = row - break + if ( + row === lastRow || + _.isEqual(this.stackForRow(row), previousStack) + ) { + filledRegion = true; + endRow = row; + break; } - row++ + row++; } - this.validateRow(endRow) - if (!filledRegion) this.invalidateRow(endRow + 1) + this.validateRow(endRow); + if (!filledRegion) this.invalidateRow(endRow + 1); - this.emitter.emit('did-change-highlighting', Range(Point(startRow, 0), Point(endRow + 1, 0))) + this.emitter.emit( + 'did-change-highlighting', + Range(Point(startRow, 0), Point(endRow + 1, 0)) + ); } if (this.firstInvalidRow() != null) { - this.tokenizeInBackground() + this.tokenizeInBackground(); } else { - this.markTokenizationComplete() + this.markTokenizationComplete(); } } - markTokenizationComplete () { + markTokenizationComplete() { if (!this.fullyTokenized) { - this.emitter.emit('did-tokenize') + this.emitter.emit('did-tokenize'); } - this.fullyTokenized = true + this.fullyTokenized = true; } - firstInvalidRow () { - return this.invalidRows[0] + firstInvalidRow() { + return this.invalidRows[0]; } - validateRow (row) { - while (this.invalidRows[0] <= row) this.invalidRows.shift() + validateRow(row) { + while (this.invalidRows[0] <= row) this.invalidRows.shift(); } - invalidateRow (row) { - this.invalidRows.push(row) - this.invalidRows.sort((a, b) => a - b) - this.tokenizeInBackground() + invalidateRow(row) { + this.invalidRows.push(row); + this.invalidRows.sort((a, b) => a - b); + this.tokenizeInBackground(); } - updateInvalidRows (start, end, delta) { - this.invalidRows = this.invalidRows.map((row) => { + updateInvalidRows(start, end, delta) { + this.invalidRows = this.invalidRows.map(row => { if (row < start) { - return row + return row; } else if (start <= row && row <= end) { - return end + delta + 1 + return end + delta + 1; } else if (row > end) { - return row + delta + return row + delta; } - }) + }); } - bufferDidChange (e) { - this.changeCount = this.buffer.changeCount + bufferDidChange(e) { + this.changeCount = this.buffer.changeCount; - const {oldRange, newRange} = e - const start = oldRange.start.row - const end = oldRange.end.row - const delta = newRange.end.row - oldRange.end.row - const oldLineCount = (oldRange.end.row - oldRange.start.row) + 1 - const newLineCount = (newRange.end.row - newRange.start.row) + 1 + const { oldRange, newRange } = e; + const start = oldRange.start.row; + const end = oldRange.end.row; + const delta = newRange.end.row - oldRange.end.row; + const oldLineCount = oldRange.end.row - oldRange.start.row + 1; + const newLineCount = newRange.end.row - newRange.start.row + 1; - this.updateInvalidRows(start, end, delta) - const previousEndStack = this.stackForRow(end) // used in spill detection below - if (this.largeFileMode || (this.grammar.name === 'Null Grammar')) { - _.spliceWithArray(this.tokenizedLines, start, oldLineCount, new Array(newLineCount)) + this.updateInvalidRows(start, end, delta); + const previousEndStack = this.stackForRow(end); // used in spill detection below + if (this.largeFileMode || this.grammar.name === 'Null Grammar') { + _.spliceWithArray( + this.tokenizedLines, + start, + oldLineCount, + new Array(newLineCount) + ); } else { - const newTokenizedLines = this.buildTokenizedLinesForRows(start, end + delta, this.stackForRow(start - 1), this.openScopesForRow(start)) - _.spliceWithArray(this.tokenizedLines, start, oldLineCount, newTokenizedLines) - const newEndStack = this.stackForRow(end + delta) + const newTokenizedLines = this.buildTokenizedLinesForRows( + start, + end + delta, + this.stackForRow(start - 1), + this.openScopesForRow(start) + ); + _.spliceWithArray( + this.tokenizedLines, + start, + oldLineCount, + newTokenizedLines + ); + const newEndStack = this.stackForRow(end + delta); if (newEndStack && !_.isEqual(newEndStack, previousEndStack)) { - this.invalidateRow(end + delta + 1) + this.invalidateRow(end + delta + 1); } } } - bufferDidFinishTransaction () {} + bufferDidFinishTransaction() {} - isFoldableAtRow (row) { - return this.endRowForFoldAtRow(row, 1, true) != null + isFoldableAtRow(row) { + return this.endRowForFoldAtRow(row, 1, true) != null; } - buildTokenizedLinesForRows (startRow, endRow, startingStack, startingopenScopes) { - let ruleStack = startingStack - let openScopes = startingopenScopes - const stopTokenizingAt = startRow + this.chunkSize - const tokenizedLines = [] + buildTokenizedLinesForRows( + startRow, + endRow, + startingStack, + startingopenScopes + ) { + let ruleStack = startingStack; + let openScopes = startingopenScopes; + const stopTokenizingAt = startRow + this.chunkSize; + const tokenizedLines = []; for (let row = startRow, end = endRow; row <= end; row++) { - let tokenizedLine - if ((ruleStack || (row === 0)) && row < stopTokenizingAt) { - tokenizedLine = this.buildTokenizedLineForRow(row, ruleStack, openScopes) - ruleStack = tokenizedLine.ruleStack - openScopes = this.scopesFromTags(openScopes, tokenizedLine.tags) + let tokenizedLine; + if ((ruleStack || row === 0) && row < stopTokenizingAt) { + tokenizedLine = this.buildTokenizedLineForRow( + row, + ruleStack, + openScopes + ); + ruleStack = tokenizedLine.ruleStack; + openScopes = this.scopesFromTags(openScopes, tokenizedLine.tags); } - tokenizedLines.push(tokenizedLine) + tokenizedLines.push(tokenizedLine); } if (endRow >= stopTokenizingAt) { - this.invalidateRow(stopTokenizingAt) - this.tokenizeInBackground() + this.invalidateRow(stopTokenizingAt); + this.tokenizeInBackground(); } - return tokenizedLines + return tokenizedLines; } - buildTokenizedLineForRow (row, ruleStack, openScopes) { - return this.buildTokenizedLineForRowWithText(row, this.buffer.lineForRow(row), ruleStack, openScopes) + buildTokenizedLineForRow(row, ruleStack, openScopes) { + return this.buildTokenizedLineForRowWithText( + row, + this.buffer.lineForRow(row), + ruleStack, + openScopes + ); } - buildTokenizedLineForRowWithText (row, text, currentRuleStack = this.stackForRow(row - 1), openScopes = this.openScopesForRow(row)) { - const lineEnding = this.buffer.lineEndingForRow(row) - const {tags, ruleStack} = this.grammar.tokenizeLine(text, currentRuleStack, row === 0, false) + buildTokenizedLineForRowWithText( + row, + text, + currentRuleStack = this.stackForRow(row - 1), + openScopes = this.openScopesForRow(row) + ) { + const lineEnding = this.buffer.lineEndingForRow(row); + const { tags, ruleStack } = this.grammar.tokenizeLine( + text, + currentRuleStack, + row === 0, + false + ); return new TokenizedLine({ openScopes, text, @@ -419,22 +514,22 @@ class TextMateLanguageMode { lineEnding, tokenIterator: this.tokenIterator, grammar: this.grammar - }) + }); } - tokenizedLineForRow (bufferRow) { + tokenizedLineForRow(bufferRow) { if (bufferRow >= 0 && bufferRow <= this.buffer.getLastRow()) { - const tokenizedLine = this.tokenizedLines[bufferRow] + const tokenizedLine = this.tokenizedLines[bufferRow]; if (tokenizedLine) { - return tokenizedLine + return tokenizedLine; } else { - const text = this.buffer.lineForRow(bufferRow) - const lineEnding = this.buffer.lineEndingForRow(bufferRow) + const text = this.buffer.lineForRow(bufferRow); + const lineEnding = this.buffer.lineEndingForRow(bufferRow); const tags = [ this.grammar.startIdForScope(this.grammar.scopeName), text.length, this.grammar.endIdForScope(this.grammar.scopeName) - ] + ]; this.tokenizedLines[bufferRow] = new TokenizedLine({ openScopes: [], text, @@ -442,428 +537,478 @@ class TextMateLanguageMode { lineEnding, tokenIterator: this.tokenIterator, grammar: this.grammar - }) - return this.tokenizedLines[bufferRow] + }); + return this.tokenizedLines[bufferRow]; } } } - tokenizedLinesForRows (startRow, endRow) { - const result = [] + tokenizedLinesForRows(startRow, endRow) { + const result = []; for (let row = startRow, end = endRow; row <= end; row++) { - result.push(this.tokenizedLineForRow(row)) + result.push(this.tokenizedLineForRow(row)); } - return result + return result; } - stackForRow (bufferRow) { - return this.tokenizedLines[bufferRow] && this.tokenizedLines[bufferRow].ruleStack + stackForRow(bufferRow) { + return ( + this.tokenizedLines[bufferRow] && this.tokenizedLines[bufferRow].ruleStack + ); } - openScopesForRow (bufferRow) { - const precedingLine = this.tokenizedLines[bufferRow - 1] + openScopesForRow(bufferRow) { + const precedingLine = this.tokenizedLines[bufferRow - 1]; if (precedingLine) { - return this.scopesFromTags(precedingLine.openScopes, precedingLine.tags) + return this.scopesFromTags(precedingLine.openScopes, precedingLine.tags); } else { - return [] + return []; } } - scopesFromTags (startingScopes, tags) { - const scopes = startingScopes.slice() + scopesFromTags(startingScopes, tags) { + const scopes = startingScopes.slice(); for (const tag of tags) { if (tag < 0) { if (tag % 2 === -1) { - scopes.push(tag) + scopes.push(tag); } else { - const matchingStartTag = tag + 1 + const matchingStartTag = tag + 1; while (true) { - if (scopes.pop() === matchingStartTag) break + if (scopes.pop() === matchingStartTag) break; if (scopes.length === 0) { - break + break; } } } } } - return scopes + return scopes; } - indentLevelForLine (line, tabLength) { - let indentLength = 0 - for (let i = 0, {length} = line; i < length; i++) { - const char = line[i] + indentLevelForLine(line, tabLength) { + let indentLength = 0; + for (let i = 0, { length } = line; i < length; i++) { + const char = line[i]; if (char === '\t') { - indentLength += tabLength - (indentLength % tabLength) + indentLength += tabLength - (indentLength % tabLength); } else if (char === ' ') { - indentLength++ + indentLength++; } else { - break + break; } } - return indentLength / tabLength + return indentLength / tabLength; } - scopeDescriptorForPosition (position) { - let scopes - const {row, column} = this.buffer.clipPosition(Point.fromObject(position)) + scopeDescriptorForPosition(position) { + let scopes; + const { row, column } = this.buffer.clipPosition( + Point.fromObject(position) + ); - const iterator = this.tokenizedLineForRow(row).getTokenIterator() + const iterator = this.tokenizedLineForRow(row).getTokenIterator(); while (iterator.next()) { if (iterator.getBufferEnd() > column) { - scopes = iterator.getScopes() - break + scopes = iterator.getScopes(); + break; } } // rebuild scope of last token if we iterated off the end if (!scopes) { - scopes = iterator.getScopes() - scopes.push(...iterator.getScopeEnds().reverse()) + scopes = iterator.getScopes(); + scopes.push(...iterator.getScopeEnds().reverse()); } - return new ScopeDescriptor({scopes}) + return new ScopeDescriptor({ scopes }); } - tokenForPosition (position) { - const {row, column} = Point.fromObject(position) - return this.tokenizedLineForRow(row).tokenAtBufferColumn(column) + tokenForPosition(position) { + const { row, column } = Point.fromObject(position); + return this.tokenizedLineForRow(row).tokenAtBufferColumn(column); } - tokenStartPositionForPosition (position) { - let {row, column} = Point.fromObject(position) - column = this.tokenizedLineForRow(row).tokenStartColumnForBufferColumn(column) - return new Point(row, column) + tokenStartPositionForPosition(position) { + let { row, column } = Point.fromObject(position); + column = this.tokenizedLineForRow(row).tokenStartColumnForBufferColumn( + column + ); + return new Point(row, column); } - bufferRangeForScopeAtPosition (selector, position) { - let endColumn, tag, tokenIndex - position = Point.fromObject(position) + bufferRangeForScopeAtPosition(selector, position) { + let endColumn, tag, tokenIndex; + position = Point.fromObject(position); - const {openScopes, tags} = this.tokenizedLineForRow(position.row) - const scopes = openScopes.map(tag => this.grammar.scopeForId(tag)) + const { openScopes, tags } = this.tokenizedLineForRow(position.row); + const scopes = openScopes.map(tag => this.grammar.scopeForId(tag)); - let startColumn = 0 + let startColumn = 0; for (tokenIndex = 0; tokenIndex < tags.length; tokenIndex++) { - tag = tags[tokenIndex] + tag = tags[tokenIndex]; if (tag < 0) { - if ((tag % 2) === -1) { - scopes.push(this.grammar.scopeForId(tag)) + if (tag % 2 === -1) { + scopes.push(this.grammar.scopeForId(tag)); } else { - scopes.pop() + scopes.pop(); } } else { - endColumn = startColumn + tag + endColumn = startColumn + tag; if (endColumn >= position.column) { - break + break; } else { - startColumn = endColumn + startColumn = endColumn; } } } - if (!selectorMatchesAnyScope(selector, scopes)) return + if (!selectorMatchesAnyScope(selector, scopes)) return; - const startScopes = scopes.slice() - for (let startTokenIndex = tokenIndex - 1; startTokenIndex >= 0; startTokenIndex--) { - tag = tags[startTokenIndex] + const startScopes = scopes.slice(); + for ( + let startTokenIndex = tokenIndex - 1; + startTokenIndex >= 0; + startTokenIndex-- + ) { + tag = tags[startTokenIndex]; if (tag < 0) { - if ((tag % 2) === -1) { - startScopes.pop() + if (tag % 2 === -1) { + startScopes.pop(); } else { - startScopes.push(this.grammar.scopeForId(tag)) + startScopes.push(this.grammar.scopeForId(tag)); } } else { - if (!selectorMatchesAnyScope(selector, startScopes)) { break } - startColumn -= tag + if (!selectorMatchesAnyScope(selector, startScopes)) { + break; + } + startColumn -= tag; } } - const endScopes = scopes.slice() - for (let endTokenIndex = tokenIndex + 1, end = tags.length; endTokenIndex < end; endTokenIndex++) { - tag = tags[endTokenIndex] + const endScopes = scopes.slice(); + for ( + let endTokenIndex = tokenIndex + 1, end = tags.length; + endTokenIndex < end; + endTokenIndex++ + ) { + tag = tags[endTokenIndex]; if (tag < 0) { - if ((tag % 2) === -1) { - endScopes.push(this.grammar.scopeForId(tag)) + if (tag % 2 === -1) { + endScopes.push(this.grammar.scopeForId(tag)); } else { - endScopes.pop() + endScopes.pop(); } } else { - if (!selectorMatchesAnyScope(selector, endScopes)) { break } - endColumn += tag + if (!selectorMatchesAnyScope(selector, endScopes)) { + break; + } + endColumn += tag; } } - return new Range(new Point(position.row, startColumn), new Point(position.row, endColumn)) + return new Range( + new Point(position.row, startColumn), + new Point(position.row, endColumn) + ); } - isRowCommented (row) { - return this.tokenizedLines[row] && this.tokenizedLines[row].isComment() + isRowCommented(row) { + return this.tokenizedLines[row] && this.tokenizedLines[row].isComment(); } - getFoldableRangeContainingPoint (point, tabLength) { + getFoldableRangeContainingPoint(point, tabLength) { if (point.column >= this.buffer.lineLengthForRow(point.row)) { - const endRow = this.endRowForFoldAtRow(point.row, tabLength) + const endRow = this.endRowForFoldAtRow(point.row, tabLength); if (endRow != null) { - return Range(Point(point.row, Infinity), Point(endRow, Infinity)) + return Range(Point(point.row, Infinity), Point(endRow, Infinity)); } } for (let row = point.row - 1; row >= 0; row--) { - const endRow = this.endRowForFoldAtRow(row, tabLength) + const endRow = this.endRowForFoldAtRow(row, tabLength); if (endRow != null && endRow >= point.row) { - return Range(Point(row, Infinity), Point(endRow, Infinity)) + return Range(Point(row, Infinity), Point(endRow, Infinity)); } } - return null + return null; } - getFoldableRangesAtIndentLevel (indentLevel, tabLength) { - const result = [] - let row = 0 - const lineCount = this.buffer.getLineCount() + getFoldableRangesAtIndentLevel(indentLevel, tabLength) { + const result = []; + let row = 0; + const lineCount = this.buffer.getLineCount(); while (row < lineCount) { - if (this.indentLevelForLine(this.buffer.lineForRow(row), tabLength) === indentLevel) { - const endRow = this.endRowForFoldAtRow(row, tabLength) + if ( + this.indentLevelForLine(this.buffer.lineForRow(row), tabLength) === + indentLevel + ) { + const endRow = this.endRowForFoldAtRow(row, tabLength); if (endRow != null) { - result.push(Range(Point(row, Infinity), Point(endRow, Infinity))) - row = endRow + 1 - continue + result.push(Range(Point(row, Infinity), Point(endRow, Infinity))); + row = endRow + 1; + continue; } } - row++ + row++; } - return result + return result; } - getFoldableRanges (tabLength) { - const result = [] - let row = 0 - const lineCount = this.buffer.getLineCount() + getFoldableRanges(tabLength) { + const result = []; + let row = 0; + const lineCount = this.buffer.getLineCount(); while (row < lineCount) { - const endRow = this.endRowForFoldAtRow(row, tabLength) + const endRow = this.endRowForFoldAtRow(row, tabLength); if (endRow != null) { - result.push(Range(Point(row, Infinity), Point(endRow, Infinity))) + result.push(Range(Point(row, Infinity), Point(endRow, Infinity))); } - row++ + row++; } - return result + return result; } - endRowForFoldAtRow (row, tabLength, existenceOnly = false) { + endRowForFoldAtRow(row, tabLength, existenceOnly = false) { if (this.isRowCommented(row)) { - return this.endRowForCommentFoldAtRow(row, existenceOnly) + return this.endRowForCommentFoldAtRow(row, existenceOnly); } else { - return this.endRowForCodeFoldAtRow(row, tabLength, existenceOnly) + return this.endRowForCodeFoldAtRow(row, tabLength, existenceOnly); } } - endRowForCommentFoldAtRow (row, existenceOnly) { - if (this.isRowCommented(row - 1)) return + endRowForCommentFoldAtRow(row, existenceOnly) { + if (this.isRowCommented(row - 1)) return; - let endRow - for (let nextRow = row + 1, end = this.buffer.getLineCount(); nextRow < end; nextRow++) { - if (!this.isRowCommented(nextRow)) break - endRow = nextRow - if (existenceOnly) break + let endRow; + for ( + let nextRow = row + 1, end = this.buffer.getLineCount(); + nextRow < end; + nextRow++ + ) { + if (!this.isRowCommented(nextRow)) break; + endRow = nextRow; + if (existenceOnly) break; } - return endRow + return endRow; } - endRowForCodeFoldAtRow (row, tabLength, existenceOnly) { - let foldEndRow - const line = this.buffer.lineForRow(row) - if (!NON_WHITESPACE_REGEX.test(line)) return - const startIndentLevel = this.indentLevelForLine(line, tabLength) - const scopeDescriptor = this.scopeDescriptorForPosition([row, 0]) - const foldEndRegex = this.foldEndRegexForScopeDescriptor(scopeDescriptor) - for (let nextRow = row + 1, end = this.buffer.getLineCount(); nextRow < end; nextRow++) { - const line = this.buffer.lineForRow(nextRow) - if (!NON_WHITESPACE_REGEX.test(line)) continue - const indentation = this.indentLevelForLine(line, tabLength) + endRowForCodeFoldAtRow(row, tabLength, existenceOnly) { + let foldEndRow; + const line = this.buffer.lineForRow(row); + if (!NON_WHITESPACE_REGEX.test(line)) return; + const startIndentLevel = this.indentLevelForLine(line, tabLength); + const scopeDescriptor = this.scopeDescriptorForPosition([row, 0]); + const foldEndRegex = this.foldEndRegexForScopeDescriptor(scopeDescriptor); + for ( + let nextRow = row + 1, end = this.buffer.getLineCount(); + nextRow < end; + nextRow++ + ) { + const line = this.buffer.lineForRow(nextRow); + if (!NON_WHITESPACE_REGEX.test(line)) continue; + const indentation = this.indentLevelForLine(line, tabLength); if (indentation < startIndentLevel) { - break + break; } else if (indentation === startIndentLevel) { - if (foldEndRegex && foldEndRegex.searchSync(line)) foldEndRow = nextRow - break + if (foldEndRegex && foldEndRegex.searchSync(line)) foldEndRow = nextRow; + break; } - foldEndRow = nextRow - if (existenceOnly) break + foldEndRow = nextRow; + if (existenceOnly) break; } - return foldEndRow + return foldEndRow; } - increaseIndentRegexForScopeDescriptor (scope) { - return this.regexForPattern(this.config.get('editor.increaseIndentPattern', {scope})) + increaseIndentRegexForScopeDescriptor(scope) { + return this.regexForPattern( + this.config.get('editor.increaseIndentPattern', { scope }) + ); } - decreaseIndentRegexForScopeDescriptor (scope) { - return this.regexForPattern(this.config.get('editor.decreaseIndentPattern', {scope})) + decreaseIndentRegexForScopeDescriptor(scope) { + return this.regexForPattern( + this.config.get('editor.decreaseIndentPattern', { scope }) + ); } - decreaseNextIndentRegexForScopeDescriptor (scope) { - return this.regexForPattern(this.config.get('editor.decreaseNextIndentPattern', {scope})) + decreaseNextIndentRegexForScopeDescriptor(scope) { + return this.regexForPattern( + this.config.get('editor.decreaseNextIndentPattern', { scope }) + ); } - foldEndRegexForScopeDescriptor (scope) { - return this.regexForPattern(this.config.get('editor.foldEndPattern', {scope})) + foldEndRegexForScopeDescriptor(scope) { + return this.regexForPattern( + this.config.get('editor.foldEndPattern', { scope }) + ); } - regexForPattern (pattern) { + regexForPattern(pattern) { if (pattern) { if (!this.regexesByPattern[pattern]) { - this.regexesByPattern[pattern] = new OnigRegExp(pattern) + this.regexesByPattern[pattern] = new OnigRegExp(pattern); } - return this.regexesByPattern[pattern] + return this.regexesByPattern[pattern]; } } - logLines (start = 0, end = this.buffer.getLastRow()) { + logLines(start = 0, end = this.buffer.getLastRow()) { for (let row = start; row <= end; row++) { - const line = this.tokenizedLines[row].text - console.log(row, line, line.length) + const line = this.tokenizedLines[row].text; + console.log(row, line, line.length); } } } -TextMateLanguageMode.prototype.chunkSize = 50 +TextMateLanguageMode.prototype.chunkSize = 50; class TextMateHighlightIterator { - constructor (languageMode) { - this.languageMode = languageMode - this.openScopeIds = null - this.closeScopeIds = null + constructor(languageMode) { + this.languageMode = languageMode; + this.openScopeIds = null; + this.closeScopeIds = null; } - seek (position) { - this.openScopeIds = [] - this.closeScopeIds = [] - this.tagIndex = null + seek(position) { + this.openScopeIds = []; + this.closeScopeIds = []; + this.tagIndex = null; - const currentLine = this.languageMode.tokenizedLineForRow(position.row) - this.currentLineTags = currentLine.tags - this.currentLineLength = currentLine.text.length - const containingScopeIds = currentLine.openScopes.map((id) => fromFirstMateScopeId(id)) + const currentLine = this.languageMode.tokenizedLineForRow(position.row); + this.currentLineTags = currentLine.tags; + this.currentLineLength = currentLine.text.length; + const containingScopeIds = currentLine.openScopes.map(id => + fromFirstMateScopeId(id) + ); - let currentColumn = 0 + let currentColumn = 0; for (let index = 0; index < this.currentLineTags.length; index++) { - const tag = this.currentLineTags[index] + const tag = this.currentLineTags[index]; if (tag >= 0) { if (currentColumn >= position.column) { - this.tagIndex = index - break + this.tagIndex = index; + break; } else { - currentColumn += tag + currentColumn += tag; while (this.closeScopeIds.length > 0) { - this.closeScopeIds.shift() - containingScopeIds.pop() + this.closeScopeIds.shift(); + containingScopeIds.pop(); } while (this.openScopeIds.length > 0) { - const openTag = this.openScopeIds.shift() - containingScopeIds.push(openTag) + const openTag = this.openScopeIds.shift(); + containingScopeIds.push(openTag); } } } else { - const scopeId = fromFirstMateScopeId(tag) + const scopeId = fromFirstMateScopeId(tag); if ((tag & 1) === 0) { if (this.openScopeIds.length > 0) { if (currentColumn >= position.column) { - this.tagIndex = index - break + this.tagIndex = index; + break; } else { while (this.closeScopeIds.length > 0) { - this.closeScopeIds.shift() - containingScopeIds.pop() + this.closeScopeIds.shift(); + containingScopeIds.pop(); } while (this.openScopeIds.length > 0) { - const openTag = this.openScopeIds.shift() - containingScopeIds.push(openTag) + const openTag = this.openScopeIds.shift(); + containingScopeIds.push(openTag); } } } - this.closeScopeIds.push(scopeId) + this.closeScopeIds.push(scopeId); } else { - this.openScopeIds.push(scopeId) + this.openScopeIds.push(scopeId); } } } if (this.tagIndex == null) { - this.tagIndex = this.currentLineTags.length + this.tagIndex = this.currentLineTags.length; } - this.position = Point(position.row, Math.min(this.currentLineLength, currentColumn)) - return containingScopeIds + this.position = Point( + position.row, + Math.min(this.currentLineLength, currentColumn) + ); + return containingScopeIds; } - moveToSuccessor () { - this.openScopeIds = [] - this.closeScopeIds = [] + moveToSuccessor() { + this.openScopeIds = []; + this.closeScopeIds = []; while (true) { if (this.tagIndex === this.currentLineTags.length) { if (this.isAtTagBoundary()) { - break + break; } else if (!this.moveToNextLine()) { - return false + return false; } } else { - const tag = this.currentLineTags[this.tagIndex] + const tag = this.currentLineTags[this.tagIndex]; if (tag >= 0) { if (this.isAtTagBoundary()) { - break + break; } else { - this.position = Point(this.position.row, Math.min( - this.currentLineLength, - this.position.column + this.currentLineTags[this.tagIndex] - )) + this.position = Point( + this.position.row, + Math.min( + this.currentLineLength, + this.position.column + this.currentLineTags[this.tagIndex] + ) + ); } } else { - const scopeId = fromFirstMateScopeId(tag) + const scopeId = fromFirstMateScopeId(tag); if ((tag & 1) === 0) { if (this.openScopeIds.length > 0) { - break + break; } else { - this.closeScopeIds.push(scopeId) + this.closeScopeIds.push(scopeId); } } else { - this.openScopeIds.push(scopeId) + this.openScopeIds.push(scopeId); } } - this.tagIndex++ + this.tagIndex++; } } - return true + return true; } - getPosition () { - return this.position + getPosition() { + return this.position; } - getCloseScopeIds () { - return this.closeScopeIds.slice() + getCloseScopeIds() { + return this.closeScopeIds.slice(); } - getOpenScopeIds () { - return this.openScopeIds.slice() + getOpenScopeIds() { + return this.openScopeIds.slice(); } - moveToNextLine () { - this.position = Point(this.position.row + 1, 0) - const tokenizedLine = this.languageMode.tokenizedLineForRow(this.position.row) + moveToNextLine() { + this.position = Point(this.position.row + 1, 0); + const tokenizedLine = this.languageMode.tokenizedLineForRow( + this.position.row + ); if (tokenizedLine == null) { - return false + return false; } else { - this.currentLineTags = tokenizedLine.tags - this.currentLineLength = tokenizedLine.text.length - this.tagIndex = 0 - return true + this.currentLineTags = tokenizedLine.tags; + this.currentLineLength = tokenizedLine.text.length; + this.tagIndex = 0; + return true; } } - isAtTagBoundary () { - return this.closeScopeIds.length > 0 || this.openScopeIds.length > 0 + isAtTagBoundary() { + return this.closeScopeIds.length > 0 || this.openScopeIds.length > 0; } } -TextMateLanguageMode.TextMateHighlightIterator = TextMateHighlightIterator -module.exports = TextMateLanguageMode +TextMateLanguageMode.TextMateHighlightIterator = TextMateHighlightIterator; +module.exports = TextMateLanguageMode; diff --git a/src/text-utils.js b/src/text-utils.js index 2e89cf2e7..d5f9e23b4 100644 --- a/src/text-utils.js +++ b/src/text-utils.js @@ -1,18 +1,16 @@ -const isHighSurrogate = (charCode) => - charCode >= 0xD800 && charCode <= 0xDBFF +const isHighSurrogate = charCode => charCode >= 0xd800 && charCode <= 0xdbff; -const isLowSurrogate = (charCode) => - charCode >= 0xDC00 && charCode <= 0xDFFF +const isLowSurrogate = charCode => charCode >= 0xdc00 && charCode <= 0xdfff; -const isVariationSelector = (charCode) => - charCode >= 0xFE00 && charCode <= 0xFE0F +const isVariationSelector = charCode => + charCode >= 0xfe00 && charCode <= 0xfe0f; const isCombiningCharacter = charCode => - (charCode >= 0x0300 && charCode <= 0x036F) || - (charCode >= 0x1AB0 && charCode <= 0x1AFF) || - (charCode >= 0x1DC0 && charCode <= 0x1DFF) || - (charCode >= 0x20D0 && charCode <= 0x20FF) || - (charCode >= 0xFE20 && charCode <= 0xFE2F) + (charCode >= 0x0300 && charCode <= 0x036f) || + (charCode >= 0x1ab0 && charCode <= 0x1aff) || + (charCode >= 0x1dc0 && charCode <= 0x1dff) || + (charCode >= 0x20d0 && charCode <= 0x20ff) || + (charCode >= 0xfe20 && charCode <= 0xfe2f); // Are the given character codes a high/low surrogate pair? // @@ -21,7 +19,7 @@ const isCombiningCharacter = charCode => // // Return a {Boolean}. const isSurrogatePair = (charCodeA, charCodeB) => - isHighSurrogate(charCodeA) && isLowSurrogate(charCodeB) + isHighSurrogate(charCodeA) && isLowSurrogate(charCodeB); // Are the given character codes a variation sequence? // @@ -30,7 +28,7 @@ const isSurrogatePair = (charCodeA, charCodeB) => // // Return a {Boolean}. const isVariationSequence = (charCodeA, charCodeB) => - !isVariationSelector(charCodeA) && isVariationSelector(charCodeB) + !isVariationSelector(charCodeA) && isVariationSelector(charCodeB); // Are the given character codes a combined character pair? // @@ -39,7 +37,7 @@ const isVariationSequence = (charCodeA, charCodeB) => // // Return a {Boolean}. const isCombinedCharacter = (charCodeA, charCodeB) => - !isCombiningCharacter(charCodeA) && isCombiningCharacter(charCodeB) + !isCombiningCharacter(charCodeA) && isCombiningCharacter(charCodeB); // Is the character at the given index the start of high/low surrogate pair // a variation sequence, or a combined character? @@ -51,67 +49,70 @@ const isCombinedCharacter = (charCodeA, charCodeB) => // // Return a {Boolean}. const isPairedCharacter = (string, index = 0) => { - const charCodeA = string.charCodeAt(index) - const charCodeB = string.charCodeAt(index + 1) - return isSurrogatePair(charCodeA, charCodeB) || + const charCodeA = string.charCodeAt(index); + const charCodeB = string.charCodeAt(index + 1); + return ( + isSurrogatePair(charCodeA, charCodeB) || isVariationSequence(charCodeA, charCodeB) || isCombinedCharacter(charCodeA, charCodeB) -} + ); +}; const IsJapaneseKanaCharacter = charCode => - charCode >= 0x3000 && charCode <= 0x30FF + charCode >= 0x3000 && charCode <= 0x30ff; const isCJKUnifiedIdeograph = charCode => - charCode >= 0x4E00 && charCode <= 0x9FFF + charCode >= 0x4e00 && charCode <= 0x9fff; const isFullWidthForm = charCode => - (charCode >= 0xFF01 && charCode <= 0xFF5E) || - (charCode >= 0xFFE0 && charCode <= 0xFFE6) + (charCode >= 0xff01 && charCode <= 0xff5e) || + (charCode >= 0xffe0 && charCode <= 0xffe6); -const isDoubleWidthCharacter = (character) => { - const charCode = character.charCodeAt(0) +const isDoubleWidthCharacter = character => { + const charCode = character.charCodeAt(0); - return IsJapaneseKanaCharacter(charCode) || - isCJKUnifiedIdeograph(charCode) || - isFullWidthForm(charCode) -} + return ( + IsJapaneseKanaCharacter(charCode) || + isCJKUnifiedIdeograph(charCode) || + isFullWidthForm(charCode) + ); +}; -const isHalfWidthCharacter = (character) => { - const charCode = character.charCodeAt(0) +const isHalfWidthCharacter = character => { + const charCode = character.charCodeAt(0); - return (charCode >= 0xFF65 && charCode <= 0xFFDC) || - (charCode >= 0xFFE8 && charCode <= 0xFFEE) -} + return ( + (charCode >= 0xff65 && charCode <= 0xffdc) || + (charCode >= 0xffe8 && charCode <= 0xffee) + ); +}; -const isKoreanCharacter = (character) => { - const charCode = character.charCodeAt(0) +const isKoreanCharacter = character => { + const charCode = character.charCodeAt(0); - return (charCode >= 0xAC00 && charCode <= 0xD7A3) || - (charCode >= 0x1100 && charCode <= 0x11FF) || - (charCode >= 0x3130 && charCode <= 0x318F) || - (charCode >= 0xA960 && charCode <= 0xA97F) || - (charCode >= 0xD7B0 && charCode <= 0xD7FF) -} + return ( + (charCode >= 0xac00 && charCode <= 0xd7a3) || + (charCode >= 0x1100 && charCode <= 0x11ff) || + (charCode >= 0x3130 && charCode <= 0x318f) || + (charCode >= 0xa960 && charCode <= 0xa97f) || + (charCode >= 0xd7b0 && charCode <= 0xd7ff) + ); +}; -const isCJKCharacter = (character) => +const isCJKCharacter = character => isDoubleWidthCharacter(character) || isHalfWidthCharacter(character) || - isKoreanCharacter(character) + isKoreanCharacter(character); const isWordStart = (previousCharacter, character) => - ( - previousCharacter === ' ' || + (previousCharacter === ' ' || previousCharacter === '\t' || previousCharacter === '-' || - previousCharacter === '/' - ) && - ( - character !== ' ' && - character !== '\t' - ) + previousCharacter === '/') && + (character !== ' ' && character !== '\t'); const isWrapBoundary = (previousCharacter, character) => - isWordStart(previousCharacter, character) || isCJKCharacter(character) + isWordStart(previousCharacter, character) || isCJKCharacter(character); // Does the given string contain at least surrogate pair, variation sequence, // or combined character? @@ -119,14 +120,16 @@ const isWrapBoundary = (previousCharacter, character) => // * `string` The {String} to check for the presence of paired characters. // // Returns a {Boolean}. -const hasPairedCharacter = (string) => { - let index = 0 +const hasPairedCharacter = string => { + let index = 0; while (index < string.length) { - if (isPairedCharacter(string, index)) { return true } - index++ + if (isPairedCharacter(string, index)) { + return true; + } + index++; } - return false -} + return false; +}; module.exports = { isPairedCharacter, @@ -135,4 +138,4 @@ module.exports = { isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary -} +}; diff --git a/src/theme-manager.js b/src/theme-manager.js index 389dd1bc3..a25d08937 100644 --- a/src/theme-manager.js +++ b/src/theme-manager.js @@ -1,44 +1,53 @@ /* global snapshotAuxiliaryData */ -const path = require('path') -const _ = require('underscore-plus') -const {Emitter, CompositeDisposable} = require('event-kit') -const {File} = require('pathwatcher') -const fs = require('fs-plus') -const LessCompileCache = require('./less-compile-cache') +const path = require('path'); +const _ = require('underscore-plus'); +const { Emitter, CompositeDisposable } = require('event-kit'); +const { File } = require('pathwatcher'); +const fs = require('fs-plus'); +const LessCompileCache = require('./less-compile-cache'); // Extended: Handles loading and activating available themes. // // An instance of this class is always available as the `atom.themes` global. -module.exports = -class ThemeManager { - constructor ({packageManager, config, styleManager, notificationManager, viewRegistry}) { - this.packageManager = packageManager - this.config = config - this.styleManager = styleManager - this.notificationManager = notificationManager - this.viewRegistry = viewRegistry - this.emitter = new Emitter() - this.styleSheetDisposablesBySourcePath = {} - this.lessCache = null - this.initialLoadComplete = false - this.packageManager.registerPackageActivator(this, ['theme']) +module.exports = class ThemeManager { + constructor({ + packageManager, + config, + styleManager, + notificationManager, + viewRegistry + }) { + this.packageManager = packageManager; + this.config = config; + this.styleManager = styleManager; + this.notificationManager = notificationManager; + this.viewRegistry = viewRegistry; + this.emitter = new Emitter(); + this.styleSheetDisposablesBySourcePath = {}; + this.lessCache = null; + this.initialLoadComplete = false; + this.packageManager.registerPackageActivator(this, ['theme']); this.packageManager.onDidActivateInitialPackages(() => { - this.onDidChangeActiveThemes(() => this.packageManager.reloadActivePackageStyleSheets()) - }) + this.onDidChangeActiveThemes(() => + this.packageManager.reloadActivePackageStyleSheets() + ); + }); } - initialize ({resourcePath, configDirPath, safeMode, devMode}) { - this.resourcePath = resourcePath - this.configDirPath = configDirPath - this.safeMode = safeMode - this.lessSourcesByRelativeFilePath = null - if (devMode || (typeof snapshotAuxiliaryData === 'undefined')) { - this.lessSourcesByRelativeFilePath = {} - this.importedFilePathsByRelativeImportPath = {} + initialize({ resourcePath, configDirPath, safeMode, devMode }) { + this.resourcePath = resourcePath; + this.configDirPath = configDirPath; + this.safeMode = safeMode; + this.lessSourcesByRelativeFilePath = null; + if (devMode || typeof snapshotAuxiliaryData === 'undefined') { + this.lessSourcesByRelativeFilePath = {}; + this.importedFilePathsByRelativeImportPath = {}; } else { - this.lessSourcesByRelativeFilePath = snapshotAuxiliaryData.lessSourcesByRelativeFilePath - this.importedFilePathsByRelativeImportPath = snapshotAuxiliaryData.importedFilePathsByRelativeImportPath + this.lessSourcesByRelativeFilePath = + snapshotAuxiliaryData.lessSourcesByRelativeFilePath; + this.importedFilePathsByRelativeImportPath = + snapshotAuxiliaryData.importedFilePathsByRelativeImportPath; } } @@ -52,17 +61,17 @@ class ThemeManager { // * `callback` {Function} // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActiveThemes (callback) { - return this.emitter.on('did-change-active-themes', callback) + onDidChangeActiveThemes(callback) { + return this.emitter.on('did-change-active-themes', callback); } /* Section: Accessing Available Themes */ - getAvailableNames () { + getAvailableNames() { // TODO: Maybe should change to list all the available themes out there? - return this.getLoadedNames() + return this.getLoadedNames(); } /* @@ -70,13 +79,15 @@ class ThemeManager { */ // Public: Returns an {Array} of {String}s of all the loaded theme names. - getLoadedThemeNames () { - return this.getLoadedThemes().map((theme) => theme.name) + getLoadedThemeNames() { + return this.getLoadedThemes().map(theme => theme.name); } // Public: Returns an {Array} of all the loaded themes. - getLoadedThemes () { - return this.packageManager.getLoadedPackages().filter((pack) => pack.isTheme()) + getLoadedThemes() { + return this.packageManager + .getLoadedPackages() + .filter(pack => pack.isTheme()); } /* @@ -84,29 +95,37 @@ class ThemeManager { */ // Public: Returns an {Array} of {String}s of all the active theme names. - getActiveThemeNames () { - return this.getActiveThemes().map((theme) => theme.name) + getActiveThemeNames() { + return this.getActiveThemes().map(theme => theme.name); } // Public: Returns an {Array} of all the active themes. - getActiveThemes () { - return this.packageManager.getActivePackages().filter((pack) => pack.isTheme()) + getActiveThemes() { + return this.packageManager + .getActivePackages() + .filter(pack => pack.isTheme()); } - activatePackages () { - return this.activateThemes() + activatePackages() { + return this.activateThemes(); } /* Section: Managing Enabled Themes */ - warnForNonExistentThemes () { - let themeNames = this.config.get('core.themes') || [] - if (!Array.isArray(themeNames)) { themeNames = [themeNames] } + warnForNonExistentThemes() { + let themeNames = this.config.get('core.themes') || []; + if (!Array.isArray(themeNames)) { + themeNames = [themeNames]; + } for (let themeName of themeNames) { - if (!themeName || (typeof themeName !== 'string') || !this.packageManager.resolvePackagePath(themeName)) { - console.warn(`Enabled theme '${themeName}' is not installed.`) + if ( + !themeName || + typeof themeName !== 'string' || + !this.packageManager.resolvePackagePath(themeName) + ) { + console.warn(`Enabled theme '${themeName}' is not installed.`); } } } @@ -114,12 +133,16 @@ class ThemeManager { // Public: Get the enabled theme names from the config. // // Returns an array of theme names in the order that they should be activated. - getEnabledThemeNames () { - let themeNames = this.config.get('core.themes') || [] - if (!Array.isArray(themeNames)) { themeNames = [themeNames] } - themeNames = themeNames.filter((themeName) => - (typeof themeName === 'string') && this.packageManager.resolvePackagePath(themeName) - ) + getEnabledThemeNames() { + let themeNames = this.config.get('core.themes') || []; + if (!Array.isArray(themeNames)) { + themeNames = [themeNames]; + } + themeNames = themeNames.filter( + themeName => + typeof themeName === 'string' && + this.packageManager.resolvePackagePath(themeName) + ); // Use a built-in syntax and UI theme any time the configured themes are not // available. @@ -133,22 +156,22 @@ class ThemeManager { 'base16-tomorrow-light-theme', 'solarized-dark-syntax', 'solarized-light-syntax' - ] - themeNames = _.intersection(themeNames, builtInThemeNames) + ]; + themeNames = _.intersection(themeNames, builtInThemeNames); if (themeNames.length === 0) { - themeNames = ['one-dark-syntax', 'one-dark-ui'] + themeNames = ['one-dark-syntax', 'one-dark-ui']; } else if (themeNames.length === 1) { if (themeNames[0].endsWith('-ui')) { - themeNames.unshift('one-dark-syntax') + themeNames.unshift('one-dark-syntax'); } else { - themeNames.push('one-dark-ui') + themeNames.push('one-dark-ui'); } } } // Reverse so the first (top) theme is loaded after the others. We want // the first/top theme to override later themes in the stack. - return themeNames.reverse() + return themeNames.reverse(); } /* @@ -164,37 +187,56 @@ class ThemeManager { // // Returns a {Disposable} on which `.dispose()` can be called to remove the // required stylesheet. - requireStylesheet (stylesheetPath, priority, skipDeprecatedSelectorsTransformation) { - let fullPath = this.resolveStylesheet(stylesheetPath) + requireStylesheet( + stylesheetPath, + priority, + skipDeprecatedSelectorsTransformation + ) { + let fullPath = this.resolveStylesheet(stylesheetPath); if (fullPath) { - const content = this.loadStylesheet(fullPath) - return this.applyStylesheet(fullPath, content, priority, skipDeprecatedSelectorsTransformation) + const content = this.loadStylesheet(fullPath); + return this.applyStylesheet( + fullPath, + content, + priority, + skipDeprecatedSelectorsTransformation + ); } else { - throw new Error(`Could not find a file at path '${stylesheetPath}'`) + throw new Error(`Could not find a file at path '${stylesheetPath}'`); } } - unwatchUserStylesheet () { - if (this.userStylesheetSubscriptions != null) this.userStylesheetSubscriptions.dispose() - this.userStylesheetSubscriptions = null - this.userStylesheetFile = null - if (this.userStyleSheetDisposable != null) this.userStyleSheetDisposable.dispose() - this.userStyleSheetDisposable = null + unwatchUserStylesheet() { + if (this.userStylesheetSubscriptions != null) + this.userStylesheetSubscriptions.dispose(); + this.userStylesheetSubscriptions = null; + this.userStylesheetFile = null; + if (this.userStyleSheetDisposable != null) + this.userStyleSheetDisposable.dispose(); + this.userStyleSheetDisposable = null; } - loadUserStylesheet () { - this.unwatchUserStylesheet() + loadUserStylesheet() { + this.unwatchUserStylesheet(); - const userStylesheetPath = this.styleManager.getUserStyleSheetPath() - if (!fs.isFileSync(userStylesheetPath)) { return } + const userStylesheetPath = this.styleManager.getUserStyleSheetPath(); + if (!fs.isFileSync(userStylesheetPath)) { + return; + } try { - this.userStylesheetFile = new File(userStylesheetPath) - this.userStylesheetSubscriptions = new CompositeDisposable() - const reloadStylesheet = () => this.loadUserStylesheet() - this.userStylesheetSubscriptions.add(this.userStylesheetFile.onDidChange(reloadStylesheet)) - this.userStylesheetSubscriptions.add(this.userStylesheetFile.onDidRename(reloadStylesheet)) - this.userStylesheetSubscriptions.add(this.userStylesheetFile.onDidDelete(reloadStylesheet)) + this.userStylesheetFile = new File(userStylesheetPath); + this.userStylesheetSubscriptions = new CompositeDisposable(); + const reloadStylesheet = () => this.loadUserStylesheet(); + this.userStylesheetSubscriptions.add( + this.userStylesheetFile.onDidChange(reloadStylesheet) + ); + this.userStylesheetSubscriptions.add( + this.userStylesheetFile.onDidRename(reloadStylesheet) + ); + this.userStylesheetSubscriptions.add( + this.userStylesheetFile.onDidDelete(reloadStylesheet) + ); } catch (error) { const message = `\ Unable to watch path: \`${path.basename(userStylesheetPath)}\`. Make sure @@ -203,57 +245,63 @@ you have permissions to \`${userStylesheetPath}\`. On linux there are currently problems with watch sizes. See [this document][watches] for more info. [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path\ -` - this.notificationManager.addError(message, {dismissable: true}) +`; + this.notificationManager.addError(message, { dismissable: true }); } - let userStylesheetContents + let userStylesheetContents; try { - userStylesheetContents = this.loadStylesheet(userStylesheetPath, true) + userStylesheetContents = this.loadStylesheet(userStylesheetPath, true); } catch (error) { - return + return; } - this.userStyleSheetDisposable = this.styleManager.addStyleSheet(userStylesheetContents, {sourcePath: userStylesheetPath, priority: 2}) + this.userStyleSheetDisposable = this.styleManager.addStyleSheet( + userStylesheetContents, + { sourcePath: userStylesheetPath, priority: 2 } + ); } - loadBaseStylesheets () { - this.reloadBaseStylesheets() + loadBaseStylesheets() { + this.reloadBaseStylesheets(); } - reloadBaseStylesheets () { - this.requireStylesheet('../static/atom', -2, true) + reloadBaseStylesheets() { + this.requireStylesheet('../static/atom', -2, true); } - stylesheetElementForId (id) { - const escapedId = id.replace(/\\/g, '\\\\') - return document.head.querySelector(`atom-styles style[source-path="${escapedId}"]`) + stylesheetElementForId(id) { + const escapedId = id.replace(/\\/g, '\\\\'); + return document.head.querySelector( + `atom-styles style[source-path="${escapedId}"]` + ); } - resolveStylesheet (stylesheetPath) { + resolveStylesheet(stylesheetPath) { if (path.extname(stylesheetPath).length > 0) { - return fs.resolveOnLoadPath(stylesheetPath) + return fs.resolveOnLoadPath(stylesheetPath); } else { - return fs.resolveOnLoadPath(stylesheetPath, ['css', 'less']) + return fs.resolveOnLoadPath(stylesheetPath, ['css', 'less']); } } - loadStylesheet (stylesheetPath, importFallbackVariables) { + loadStylesheet(stylesheetPath, importFallbackVariables) { if (path.extname(stylesheetPath) === '.less') { - return this.loadLessStylesheet(stylesheetPath, importFallbackVariables) + return this.loadLessStylesheet(stylesheetPath, importFallbackVariables); } else { - return fs.readFileSync(stylesheetPath, 'utf8') + return fs.readFileSync(stylesheetPath, 'utf8'); } } - loadLessStylesheet (lessStylesheetPath, importFallbackVariables = false) { + loadLessStylesheet(lessStylesheetPath, importFallbackVariables = false) { if (this.lessCache == null) { this.lessCache = new LessCompileCache({ resourcePath: this.resourcePath, lessSourcesByRelativeFilePath: this.lessSourcesByRelativeFilePath, - importedFilePathsByRelativeImportPath: this.importedFilePathsByRelativeImportPath, + importedFilePathsByRelativeImportPath: this + .importedFilePathsByRelativeImportPath, importPaths: this.getImportPaths() - }) + }); } try { @@ -261,143 +309,156 @@ On linux there are currently problems with watch sizes. See const baseVarImports = `\ @import "variables/ui-variables"; @import "variables/syntax-variables";\ -` - const relativeFilePath = path.relative(this.resourcePath, lessStylesheetPath) - const lessSource = this.lessSourcesByRelativeFilePath[relativeFilePath] +`; + const relativeFilePath = path.relative( + this.resourcePath, + lessStylesheetPath + ); + const lessSource = this.lessSourcesByRelativeFilePath[relativeFilePath]; - let content, digest + let content, digest; if (lessSource != null) { ({ content } = lessSource); - ({ digest } = lessSource) + ({ digest } = lessSource); } else { - content = baseVarImports + '\n' + fs.readFileSync(lessStylesheetPath, 'utf8') - digest = null + content = + baseVarImports + '\n' + fs.readFileSync(lessStylesheetPath, 'utf8'); + digest = null; } - return this.lessCache.cssForFile(lessStylesheetPath, content, digest) + return this.lessCache.cssForFile(lessStylesheetPath, content, digest); } else { - return this.lessCache.read(lessStylesheetPath) + return this.lessCache.read(lessStylesheetPath); } } catch (error) { - let detail, message - error.less = true + let detail, message; + error.less = true; if (error.line != null) { // Adjust line numbers for import fallbacks - if (importFallbackVariables) { error.line -= 2 } + if (importFallbackVariables) { + error.line -= 2; + } - message = `Error compiling Less stylesheet: \`${lessStylesheetPath}\`` - detail = `Line number: ${error.line}\n${error.message}` + message = `Error compiling Less stylesheet: \`${lessStylesheetPath}\``; + detail = `Line number: ${error.line}\n${error.message}`; } else { - message = `Error loading Less stylesheet: \`${lessStylesheetPath}\`` - detail = error.message + message = `Error loading Less stylesheet: \`${lessStylesheetPath}\``; + detail = error.message; } - this.notificationManager.addError(message, {detail, dismissable: true}) - throw error + this.notificationManager.addError(message, { detail, dismissable: true }); + throw error; } } - removeStylesheet (stylesheetPath) { + removeStylesheet(stylesheetPath) { if (this.styleSheetDisposablesBySourcePath[stylesheetPath] != null) { - this.styleSheetDisposablesBySourcePath[stylesheetPath].dispose() + this.styleSheetDisposablesBySourcePath[stylesheetPath].dispose(); } } - applyStylesheet (path, text, priority, skipDeprecatedSelectorsTransformation) { - this.styleSheetDisposablesBySourcePath[path] = this.styleManager.addStyleSheet( - text, - { - priority, - skipDeprecatedSelectorsTransformation, - sourcePath: path - } - ) + applyStylesheet(path, text, priority, skipDeprecatedSelectorsTransformation) { + this.styleSheetDisposablesBySourcePath[ + path + ] = this.styleManager.addStyleSheet(text, { + priority, + skipDeprecatedSelectorsTransformation, + sourcePath: path + }); - return this.styleSheetDisposablesBySourcePath[path] + return this.styleSheetDisposablesBySourcePath[path]; } - activateThemes () { + activateThemes() { return new Promise(resolve => { // @config.observe runs the callback once, then on subsequent changes. this.config.observe('core.themes', () => { this.deactivateThemes().then(() => { - this.warnForNonExistentThemes() - this.refreshLessCache() // Update cache for packages in core.themes config + this.warnForNonExistentThemes(); + this.refreshLessCache(); // Update cache for packages in core.themes config - const promises = [] + const promises = []; for (const themeName of this.getEnabledThemeNames()) { if (this.packageManager.resolvePackagePath(themeName)) { - promises.push(this.packageManager.activatePackage(themeName)) + promises.push(this.packageManager.activatePackage(themeName)); } else { - console.warn(`Failed to activate theme '${themeName}' because it isn't installed.`) + console.warn( + `Failed to activate theme '${themeName}' because it isn't installed.` + ); } } return Promise.all(promises).then(() => { - this.addActiveThemeClasses() - this.refreshLessCache() // Update cache again now that @getActiveThemes() is populated - this.loadUserStylesheet() - this.reloadBaseStylesheets() - this.initialLoadComplete = true - this.emitter.emit('did-change-active-themes') - resolve() - }) - }) - }) - }) + this.addActiveThemeClasses(); + this.refreshLessCache(); // Update cache again now that @getActiveThemes() is populated + this.loadUserStylesheet(); + this.reloadBaseStylesheets(); + this.initialLoadComplete = true; + this.emitter.emit('did-change-active-themes'); + resolve(); + }); + }); + }); + }); } - deactivateThemes () { - this.removeActiveThemeClasses() - this.unwatchUserStylesheet() - const results = this.getActiveThemes().map(pack => this.packageManager.deactivatePackage(pack.name)) - return Promise.all(results.filter((r) => (r != null) && (typeof r.then === 'function'))) + deactivateThemes() { + this.removeActiveThemeClasses(); + this.unwatchUserStylesheet(); + const results = this.getActiveThemes().map(pack => + this.packageManager.deactivatePackage(pack.name) + ); + return Promise.all( + results.filter(r => r != null && typeof r.then === 'function') + ); } - isInitialLoadComplete () { - return this.initialLoadComplete + isInitialLoadComplete() { + return this.initialLoadComplete; } - addActiveThemeClasses () { - const workspaceElement = this.viewRegistry.getView(this.workspace) + addActiveThemeClasses() { + const workspaceElement = this.viewRegistry.getView(this.workspace); if (workspaceElement) { for (const pack of this.getActiveThemes()) { - workspaceElement.classList.add(`theme-${pack.name}`) + workspaceElement.classList.add(`theme-${pack.name}`); } } } - removeActiveThemeClasses () { - const workspaceElement = this.viewRegistry.getView(this.workspace) + removeActiveThemeClasses() { + const workspaceElement = this.viewRegistry.getView(this.workspace); for (const pack of this.getActiveThemes()) { - workspaceElement.classList.remove(`theme-${pack.name}`) + workspaceElement.classList.remove(`theme-${pack.name}`); } } - refreshLessCache () { - if (this.lessCache) this.lessCache.setImportPaths(this.getImportPaths()) + refreshLessCache() { + if (this.lessCache) this.lessCache.setImportPaths(this.getImportPaths()); } - getImportPaths () { - let themePaths - const activeThemes = this.getActiveThemes() + getImportPaths() { + let themePaths; + const activeThemes = this.getActiveThemes(); if (activeThemes.length > 0) { - themePaths = (activeThemes.filter((theme) => theme).map((theme) => theme.getStylesheetsPath())) + themePaths = activeThemes + .filter(theme => theme) + .map(theme => theme.getStylesheetsPath()); } else { - themePaths = [] + themePaths = []; for (const themeName of this.getEnabledThemeNames()) { - const themePath = this.packageManager.resolvePackagePath(themeName) + const themePath = this.packageManager.resolvePackagePath(themeName); if (themePath) { - const deprecatedPath = path.join(themePath, 'stylesheets') + const deprecatedPath = path.join(themePath, 'stylesheets'); if (fs.isDirectorySync(deprecatedPath)) { - themePaths.push(deprecatedPath) + themePaths.push(deprecatedPath); } else { - themePaths.push(path.join(themePath, 'styles')) + themePaths.push(path.join(themePath, 'styles')); } } } } - return themePaths.filter(themePath => fs.isDirectorySync(themePath)) + return themePaths.filter(themePath => fs.isDirectorySync(themePath)); } -} +}; diff --git a/src/theme-package.js b/src/theme-package.js index 7ac01bd97..b9abb9d92 100644 --- a/src/theme-package.js +++ b/src/theme-package.js @@ -1,55 +1,57 @@ -const path = require('path') -const Package = require('./package') +const path = require('path'); +const Package = require('./package'); -module.exports = -class ThemePackage extends Package { - getType () { - return 'theme' +module.exports = class ThemePackage extends Package { + getType() { + return 'theme'; } - getStyleSheetPriority () { - return 1 + getStyleSheetPriority() { + return 1; } - enable () { - this.config.unshiftAtKeyPath('core.themes', this.name) + enable() { + this.config.unshiftAtKeyPath('core.themes', this.name); } - disable () { - this.config.removeAtKeyPath('core.themes', this.name) + disable() { + this.config.removeAtKeyPath('core.themes', this.name); } - preload () { - this.loadTime = 0 - this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata() + preload() { + this.loadTime = 0; + this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata(); } - finishLoading () { - this.path = path.join(this.packageManager.resourcePath, this.path) + finishLoading() { + this.path = path.join(this.packageManager.resourcePath, this.path); } - load () { - this.loadTime = 0 - this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata() - return this + load() { + this.loadTime = 0; + this.configSchemaRegisteredOnLoad = this.registerConfigSchemaFromMetadata(); + return this; } - activate () { + activate() { if (this.activationPromise == null) { this.activationPromise = new Promise((resolve, reject) => { - this.resolveActivationPromise = resolve - this.rejectActivationPromise = reject + this.resolveActivationPromise = resolve; + this.rejectActivationPromise = reject; this.measure('activateTime', () => { try { - this.loadStylesheets() - this.activateNow() + this.loadStylesheets(); + this.activateNow(); } catch (error) { - this.handleError(`Failed to activate the ${this.name} theme`, error) + this.handleError( + `Failed to activate the ${this.name} theme`, + error + ); } - }) - }) + }); + }); } - return this.activationPromise + return this.activationPromise; } -} +}; diff --git a/src/title-bar.js b/src/title-bar.js index 260266308..267c92356 100644 --- a/src/title-bar.js +++ b/src/title-bar.js @@ -1,47 +1,53 @@ -module.exports = -class TitleBar { - constructor ({workspace, themes, applicationDelegate}) { - this.dblclickHandler = this.dblclickHandler.bind(this) - this.workspace = workspace - this.themes = themes - this.applicationDelegate = applicationDelegate - this.element = document.createElement('div') - this.element.classList.add('title-bar') +module.exports = class TitleBar { + constructor({ workspace, themes, applicationDelegate }) { + this.dblclickHandler = this.dblclickHandler.bind(this); + this.workspace = workspace; + this.themes = themes; + this.applicationDelegate = applicationDelegate; + this.element = document.createElement('div'); + this.element.classList.add('title-bar'); - this.titleElement = document.createElement('div') - this.titleElement.classList.add('title') - this.element.appendChild(this.titleElement) + this.titleElement = document.createElement('div'); + this.titleElement.classList.add('title'); + this.element.appendChild(this.titleElement); - this.element.addEventListener('dblclick', this.dblclickHandler) + this.element.addEventListener('dblclick', this.dblclickHandler); - this.workspace.onDidChangeWindowTitle(() => this.updateTitle()) - this.themes.onDidChangeActiveThemes(() => this.updateWindowSheetOffset()) + this.workspace.onDidChangeWindowTitle(() => this.updateTitle()); + this.themes.onDidChangeActiveThemes(() => this.updateWindowSheetOffset()); - this.updateTitle() - this.updateWindowSheetOffset() + this.updateTitle(); + this.updateWindowSheetOffset(); } - dblclickHandler () { + dblclickHandler() { // User preference deciding which action to take on a title bar double-click - switch (this.applicationDelegate.getUserDefault('AppleActionOnDoubleClick', 'string')) { + switch ( + this.applicationDelegate.getUserDefault( + 'AppleActionOnDoubleClick', + 'string' + ) + ) { case 'Minimize': - this.applicationDelegate.minimizeWindow() - break + this.applicationDelegate.minimizeWindow(); + break; case 'Maximize': if (this.applicationDelegate.isWindowMaximized()) { - this.applicationDelegate.unmaximizeWindow() + this.applicationDelegate.unmaximizeWindow(); } else { - this.applicationDelegate.maximizeWindow() + this.applicationDelegate.maximizeWindow(); } - break + break; } } - updateTitle () { - this.titleElement.textContent = document.title + updateTitle() { + this.titleElement.textContent = document.title; } - updateWindowSheetOffset () { - this.applicationDelegate.getCurrentWindow().setSheetOffset(this.element.offsetHeight) + updateWindowSheetOffset() { + this.applicationDelegate + .getCurrentWindow() + .setSheetOffset(this.element.offsetHeight); } -} +}; diff --git a/src/token-iterator.js b/src/token-iterator.js index 87d41be37..c9fcfe81e 100644 --- a/src/token-iterator.js +++ b/src/token-iterator.js @@ -1,79 +1,80 @@ -module.exports = -class TokenIterator { - constructor (languageMode) { - this.languageMode = languageMode +module.exports = class TokenIterator { + constructor(languageMode) { + this.languageMode = languageMode; } - reset (line) { - this.line = line - this.index = null - this.startColumn = 0 - this.endColumn = 0 - this.scopes = this.line.openScopes.map(id => this.languageMode.grammar.scopeForId(id)) - this.scopeStarts = this.scopes.slice() - this.scopeEnds = [] - return this + reset(line) { + this.line = line; + this.index = null; + this.startColumn = 0; + this.endColumn = 0; + this.scopes = this.line.openScopes.map(id => + this.languageMode.grammar.scopeForId(id) + ); + this.scopeStarts = this.scopes.slice(); + this.scopeEnds = []; + return this; } - next () { - const {tags} = this.line + next() { + const { tags } = this.line; if (this.index != null) { - this.startColumn = this.endColumn - this.scopeEnds.length = 0 - this.scopeStarts.length = 0 - this.index++ + this.startColumn = this.endColumn; + this.scopeEnds.length = 0; + this.scopeStarts.length = 0; + this.index++; } else { - this.index = 0 + this.index = 0; } while (this.index < tags.length) { - const tag = tags[this.index] + const tag = tags[this.index]; if (tag < 0) { - const scope = this.languageMode.grammar.scopeForId(tag) - if ((tag % 2) === 0) { + const scope = this.languageMode.grammar.scopeForId(tag); + if (tag % 2 === 0) { if (this.scopeStarts[this.scopeStarts.length - 1] === scope) { - this.scopeStarts.pop() + this.scopeStarts.pop(); } else { - this.scopeEnds.push(scope) + this.scopeEnds.push(scope); } - this.scopes.pop() + this.scopes.pop(); } else { - this.scopeStarts.push(scope) - this.scopes.push(scope) + this.scopeStarts.push(scope); + this.scopes.push(scope); } - this.index++ + this.index++; } else { - this.endColumn += tag - this.text = this.line.text.substring(this.startColumn, this.endColumn) - return true + this.endColumn += tag; + this.text = this.line.text.substring(this.startColumn, this.endColumn); + return true; } } - return false + return false; } - getScopes () { - return this.scopes + getScopes() { + return this.scopes; } - getScopeStarts () { - return this.scopeStarts + getScopeStarts() { + return this.scopeStarts; } - getScopeEnds () { - return this.scopeEnds + getScopeEnds() { + return this.scopeEnds; } - getText () { - return this.text + getText() { + return this.text; } - getBufferStart () { - return this.startColumn + getBufferStart() { + return this.startColumn; } - getBufferEnd () { - return this.endColumn + getBufferEnd() { + return this.endColumn; } -} +}; diff --git a/src/tooltip-manager.js b/src/tooltip-manager.js index d5fb8b0c0..4d08b6b83 100644 --- a/src/tooltip-manager.js +++ b/src/tooltip-manager.js @@ -1,6 +1,6 @@ -const _ = require('underscore-plus') -const {Disposable, CompositeDisposable} = require('event-kit') -let Tooltip = null +const _ = require('underscore-plus'); +const { Disposable, CompositeDisposable } = require('event-kit'); +let Tooltip = null; // Essential: Associates tooltips with HTML elements. // @@ -44,24 +44,23 @@ let Tooltip = null // keyBindingTarget: this.findEditor.element // }) // ``` -module.exports = -class TooltipManager { - constructor ({keymapManager, viewRegistry}) { +module.exports = class TooltipManager { + constructor({ keymapManager, viewRegistry }) { this.defaults = { trigger: 'hover', container: 'body', html: true, placement: 'auto top', viewportPadding: 2 - } + }; this.hoverDefaults = { - delay: {show: 1000, hide: 100} - } + delay: { show: 1000, hide: 100 } + }; - this.keymapManager = keymapManager - this.viewRegistry = viewRegistry - this.tooltips = new Map() + this.keymapManager = keymapManager; + this.viewRegistry = viewRegistry; + this.tooltips = new Map(); } // Essential: Add a tooltip to the given element. @@ -111,69 +110,74 @@ class TooltipManager { // // Returns a {Disposable} on which `.dispose()` can be called to remove the // tooltip. - add (target, options) { + add(target, options) { if (target.jquery) { - const disposable = new CompositeDisposable() + const disposable = new CompositeDisposable(); for (let i = 0; i < target.length; i++) { - disposable.add(this.add(target[i], options)) + disposable.add(this.add(target[i], options)); } - return disposable + return disposable; } - if (Tooltip == null) { Tooltip = require('./tooltip') } + if (Tooltip == null) { + Tooltip = require('./tooltip'); + } - const {keyBindingCommand, keyBindingTarget} = options + const { keyBindingCommand, keyBindingTarget } = options; if (keyBindingCommand != null) { - const bindings = this.keymapManager.findKeyBindings({command: keyBindingCommand, target: keyBindingTarget}) - const keystroke = getKeystroke(bindings) - if ((options.title != null) && (keystroke != null)) { - options.title += ` ${getKeystroke(bindings)}` + const bindings = this.keymapManager.findKeyBindings({ + command: keyBindingCommand, + target: keyBindingTarget + }); + const keystroke = getKeystroke(bindings); + if (options.title != null && keystroke != null) { + options.title += ` ${getKeystroke(bindings)}`; } else if (keystroke != null) { - options.title = getKeystroke(bindings) + options.title = getKeystroke(bindings); } } - delete options.selector - options = _.defaults(options, this.defaults) + delete options.selector; + options = _.defaults(options, this.defaults); if (options.trigger === 'hover') { - options = _.defaults(options, this.hoverDefaults) + options = _.defaults(options, this.hoverDefaults); } - const tooltip = new Tooltip(target, options, this.viewRegistry) + const tooltip = new Tooltip(target, options, this.viewRegistry); if (!this.tooltips.has(target)) { - this.tooltips.set(target, []) + this.tooltips.set(target, []); } - this.tooltips.get(target).push(tooltip) + this.tooltips.get(target).push(tooltip); - const hideTooltip = function () { - tooltip.leave({currentTarget: target}) - tooltip.hide() - } + const hideTooltip = function() { + tooltip.leave({ currentTarget: target }); + tooltip.hide(); + }; // note: adding a listener here adds a new listener for every tooltip element that's registered. Adding unnecessary listeners is bad for performance. It would be better to add/remove listeners when tooltips are actually created in the dom. - window.addEventListener('resize', hideTooltip) + window.addEventListener('resize', hideTooltip); const disposable = new Disposable(() => { - window.removeEventListener('resize', hideTooltip) + window.removeEventListener('resize', hideTooltip); - hideTooltip() - tooltip.destroy() + hideTooltip(); + tooltip.destroy(); if (this.tooltips.has(target)) { - const tooltipsForTarget = this.tooltips.get(target) - const index = tooltipsForTarget.indexOf(tooltip) + const tooltipsForTarget = this.tooltips.get(target); + const index = tooltipsForTarget.indexOf(tooltip); if (index !== -1) { - tooltipsForTarget.splice(index, 1) + tooltipsForTarget.splice(index, 1); } if (tooltipsForTarget.length === 0) { - this.tooltips.delete(target) + this.tooltips.delete(target); } } - }) + }); - return disposable + return disposable; } // Extended: Find the tooltips that have been applied to the given element. @@ -181,23 +185,25 @@ class TooltipManager { // * `target` The `HTMLElement` to find tooltips on. // // Returns an {Array} of `Tooltip` objects that match the `target`. - findTooltips (target) { + findTooltips(target) { if (this.tooltips.has(target)) { - return this.tooltips.get(target).slice() + return this.tooltips.get(target).slice(); } else { - return [] + return []; } } +}; + +function humanizeKeystrokes(keystroke) { + let keystrokes = keystroke.split(' '); + keystrokes = keystrokes.map(stroke => _.humanizeKeystroke(stroke)); + return keystrokes.join(' '); } -function humanizeKeystrokes (keystroke) { - let keystrokes = keystroke.split(' ') - keystrokes = (keystrokes.map((stroke) => _.humanizeKeystroke(stroke))) - return keystrokes.join(' ') -} - -function getKeystroke (bindings) { +function getKeystroke(bindings) { if (bindings && bindings.length) { - return `${humanizeKeystrokes(bindings[0].keystrokes)}` + return `${humanizeKeystrokes( + bindings[0].keystrokes + )}`; } } diff --git a/src/tooltip.js b/src/tooltip.js index e6960a8ef..48650005e 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -1,35 +1,36 @@ -'use strict' +'use strict'; -const EventKit = require('event-kit') -const tooltipComponentsByElement = new WeakMap() -const listen = require('./delegated-listener') +const EventKit = require('event-kit'); +const tooltipComponentsByElement = new WeakMap(); +const listen = require('./delegated-listener'); // This tooltip class is derived from Bootstrap 3, but modified to not require // jQuery, which is an expensive dependency we want to eliminate. -var followThroughTimer = null +var followThroughTimer = null; -var Tooltip = function (element, options, viewRegistry) { - this.options = null - this.enabled = null - this.timeout = null - this.hoverState = null - this.element = null - this.inState = null - this.viewRegistry = viewRegistry +var Tooltip = function(element, options, viewRegistry) { + this.options = null; + this.enabled = null; + this.timeout = null; + this.hoverState = null; + this.element = null; + this.inState = null; + this.viewRegistry = viewRegistry; - this.init(element, options) -} + this.init(element, options); +}; -Tooltip.VERSION = '3.3.5' +Tooltip.VERSION = '3.3.5'; -Tooltip.FOLLOW_THROUGH_DURATION = 300 +Tooltip.FOLLOW_THROUGH_DURATION = 300; Tooltip.DEFAULTS = { animation: true, placement: 'top', selector: false, - template: '', + template: + '', trigger: 'hover focus', title: '', delay: 0, @@ -39,525 +40,650 @@ Tooltip.DEFAULTS = { selector: 'body', padding: 0 } -} +}; -Tooltip.prototype.init = function (element, options) { - this.enabled = true - this.element = element - this.options = this.getOptions(options) - this.disposables = new EventKit.CompositeDisposable() - this.mutationObserver = new MutationObserver(this.handleMutations.bind(this)) +Tooltip.prototype.init = function(element, options) { + this.enabled = true; + this.element = element; + this.options = this.getOptions(options); + this.disposables = new EventKit.CompositeDisposable(); + this.mutationObserver = new MutationObserver(this.handleMutations.bind(this)); if (this.options.viewport) { if (typeof this.options.viewport === 'function') { - this.viewport = this.options.viewport.call(this, this.element) + this.viewport = this.options.viewport.call(this, this.element); } else { - this.viewport = document.querySelector(this.options.viewport.selector || this.options.viewport) + this.viewport = document.querySelector( + this.options.viewport.selector || this.options.viewport + ); } } - this.inState = {click: false, hover: false, focus: false} + this.inState = { click: false, hover: false, focus: false }; if (this.element instanceof document.constructor && !this.options.selector) { - throw new Error('`selector` option must be specified when initializing tooltip on the window.document object!') + throw new Error( + '`selector` option must be specified when initializing tooltip on the window.document object!' + ); } - var triggers = this.options.trigger.split(' ') + var triggers = this.options.trigger.split(' '); - for (var i = triggers.length; i--;) { - var trigger = triggers[i] + for (var i = triggers.length; i--; ) { + var trigger = triggers[i]; if (trigger === 'click') { - this.disposables.add(listen(this.element, 'click', this.options.selector, this.toggle.bind(this))) - this.hideOnClickOutsideOfTooltip = (event) => { - const tooltipElement = this.getTooltipElement() - if (tooltipElement === event.target) return - if (tooltipElement.contains(event.target)) return - if (this.element === event.target) return - if (this.element.contains(event.target)) return - this.hide() - } + this.disposables.add( + listen( + this.element, + 'click', + this.options.selector, + this.toggle.bind(this) + ) + ); + this.hideOnClickOutsideOfTooltip = event => { + const tooltipElement = this.getTooltipElement(); + if (tooltipElement === event.target) return; + if (tooltipElement.contains(event.target)) return; + if (this.element === event.target) return; + if (this.element.contains(event.target)) return; + this.hide(); + }; } else if (trigger === 'manual') { - this.show() + this.show(); } else { - var eventIn, eventOut + var eventIn, eventOut; if (trigger === 'hover') { - this.hideOnKeydownOutsideOfTooltip = () => this.hide() + this.hideOnKeydownOutsideOfTooltip = () => this.hide(); if (this.options.selector) { - eventIn = 'mouseover' - eventOut = 'mouseout' + eventIn = 'mouseover'; + eventOut = 'mouseout'; } else { - eventIn = 'mouseenter' - eventOut = 'mouseleave' + eventIn = 'mouseenter'; + eventOut = 'mouseleave'; } } else { - eventIn = 'focusin' - eventOut = 'focusout' + eventIn = 'focusin'; + eventOut = 'focusout'; } - this.disposables.add(listen(this.element, eventIn, this.options.selector, this.enter.bind(this))) - this.disposables.add(listen(this.element, eventOut, this.options.selector, this.leave.bind(this))) + this.disposables.add( + listen( + this.element, + eventIn, + this.options.selector, + this.enter.bind(this) + ) + ); + this.disposables.add( + listen( + this.element, + eventOut, + this.options.selector, + this.leave.bind(this) + ) + ); } } this.options.selector - ? (this._options = extend({}, this.options, { trigger: 'manual', selector: '' })) - : this.fixTitle() -} + ? (this._options = extend({}, this.options, { + trigger: 'manual', + selector: '' + })) + : this.fixTitle(); +}; -Tooltip.prototype.startObservingMutations = function () { +Tooltip.prototype.startObservingMutations = function() { this.mutationObserver.observe(this.getTooltipElement(), { - attributes: true, childList: true, characterData: true, subtree: true - }) -} + attributes: true, + childList: true, + characterData: true, + subtree: true + }); +}; -Tooltip.prototype.stopObservingMutations = function () { - this.mutationObserver.disconnect() -} +Tooltip.prototype.stopObservingMutations = function() { + this.mutationObserver.disconnect(); +}; -Tooltip.prototype.handleMutations = function () { - window.requestAnimationFrame(function () { - this.stopObservingMutations() - this.recalculatePosition() - this.startObservingMutations() - }.bind(this)) -} +Tooltip.prototype.handleMutations = function() { + window.requestAnimationFrame( + function() { + this.stopObservingMutations(); + this.recalculatePosition(); + this.startObservingMutations(); + }.bind(this) + ); +}; -Tooltip.prototype.getDefaults = function () { - return Tooltip.DEFAULTS -} +Tooltip.prototype.getDefaults = function() { + return Tooltip.DEFAULTS; +}; -Tooltip.prototype.getOptions = function (options) { - options = extend({}, this.getDefaults(), options) +Tooltip.prototype.getOptions = function(options) { + options = extend({}, this.getDefaults(), options); if (options.delay && typeof options.delay === 'number') { options.delay = { show: options.delay, hide: options.delay - } + }; } - return options -} + return options; +}; -Tooltip.prototype.getDelegateOptions = function () { - var options = {} - var defaults = this.getDefaults() +Tooltip.prototype.getDelegateOptions = function() { + var options = {}; + var defaults = this.getDefaults(); if (this._options) { for (var key of Object.getOwnPropertyNames(this._options)) { - var value = this._options[key] - if (defaults[key] !== value) options[key] = value + var value = this._options[key]; + if (defaults[key] !== value) options[key] = value; } } - return options -} + return options; +}; -Tooltip.prototype.enter = function (event) { +Tooltip.prototype.enter = function(event) { if (event) { if (event.currentTarget !== this.element) { - this.getDelegateComponent(event.currentTarget).enter(event) - return + this.getDelegateComponent(event.currentTarget).enter(event); + return; } - this.inState[event.type === 'focusin' ? 'focus' : 'hover'] = true + this.inState[event.type === 'focusin' ? 'focus' : 'hover'] = true; } - if (this.getTooltipElement().classList.contains('in') || this.hoverState === 'in') { - this.hoverState = 'in' - return + if ( + this.getTooltipElement().classList.contains('in') || + this.hoverState === 'in' + ) { + this.hoverState = 'in'; + return; } - clearTimeout(this.timeout) + clearTimeout(this.timeout); - this.hoverState = 'in' + this.hoverState = 'in'; - if (!this.options.delay || - !this.options.delay.show || - followThroughTimer) { - return this.show() + if (!this.options.delay || !this.options.delay.show || followThroughTimer) { + return this.show(); } - this.timeout = setTimeout(function () { - if (this.hoverState === 'in') this.show() - }.bind(this), this.options.delay.show) -} + this.timeout = setTimeout( + function() { + if (this.hoverState === 'in') this.show(); + }.bind(this), + this.options.delay.show + ); +}; -Tooltip.prototype.isInStateTrue = function () { +Tooltip.prototype.isInStateTrue = function() { for (var key in this.inState) { - if (this.inState[key]) return true + if (this.inState[key]) return true; } - return false -} + return false; +}; -Tooltip.prototype.leave = function (event) { +Tooltip.prototype.leave = function(event) { if (event) { if (event.currentTarget !== this.element) { - this.getDelegateComponent(event.currentTarget).leave(event) - return + this.getDelegateComponent(event.currentTarget).leave(event); + return; } - this.inState[event.type === 'focusout' ? 'focus' : 'hover'] = false + this.inState[event.type === 'focusout' ? 'focus' : 'hover'] = false; } - if (this.isInStateTrue()) return + if (this.isInStateTrue()) return; - clearTimeout(this.timeout) + clearTimeout(this.timeout); - this.hoverState = 'out' + this.hoverState = 'out'; - if (!this.options.delay || !this.options.delay.hide) return this.hide() + if (!this.options.delay || !this.options.delay.hide) return this.hide(); - this.timeout = setTimeout(function () { - if (this.hoverState === 'out') this.hide() - }.bind(this), this.options.delay.hide) -} + this.timeout = setTimeout( + function() { + if (this.hoverState === 'out') this.hide(); + }.bind(this), + this.options.delay.hide + ); +}; -Tooltip.prototype.show = function () { +Tooltip.prototype.show = function() { if (this.hasContent() && this.enabled) { if (this.hideOnClickOutsideOfTooltip) { - window.addEventListener('click', this.hideOnClickOutsideOfTooltip, true) + window.addEventListener('click', this.hideOnClickOutsideOfTooltip, true); } if (this.hideOnKeydownOutsideOfTooltip) { - window.addEventListener('keydown', this.hideOnKeydownOutsideOfTooltip, true) + window.addEventListener( + 'keydown', + this.hideOnKeydownOutsideOfTooltip, + true + ); } - var tip = this.getTooltipElement() - this.startObservingMutations() - var tipId = this.getUID('tooltip') + var tip = this.getTooltipElement(); + this.startObservingMutations(); + var tipId = this.getUID('tooltip'); - this.setContent() - tip.setAttribute('id', tipId) - this.element.setAttribute('aria-describedby', tipId) + this.setContent(); + tip.setAttribute('id', tipId); + this.element.setAttribute('aria-describedby', tipId); - if (this.options.animation) tip.classList.add('fade') + if (this.options.animation) tip.classList.add('fade'); - var placement = typeof this.options.placement === 'function' - ? this.options.placement.call(this, tip, this.element) - : this.options.placement + var placement = + typeof this.options.placement === 'function' + ? this.options.placement.call(this, tip, this.element) + : this.options.placement; - var autoToken = /\s?auto?\s?/i - var autoPlace = autoToken.test(placement) - if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + var autoToken = /\s?auto?\s?/i; + var autoPlace = autoToken.test(placement); + if (autoPlace) placement = placement.replace(autoToken, '') || 'top'; - tip.remove() - tip.style.top = '0px' - tip.style.left = '0px' - tip.style.display = 'block' - tip.classList.add(placement) + tip.remove(); + tip.style.top = '0px'; + tip.style.left = '0px'; + tip.style.display = 'block'; + tip.classList.add(placement); - document.body.appendChild(tip) + document.body.appendChild(tip); - var pos = this.element.getBoundingClientRect() - var actualWidth = tip.offsetWidth - var actualHeight = tip.offsetHeight + var pos = this.element.getBoundingClientRect(); + var actualWidth = tip.offsetWidth; + var actualHeight = tip.offsetHeight; if (autoPlace) { - var orgPlacement = placement - var viewportDim = this.viewport.getBoundingClientRect() + var orgPlacement = placement; + var viewportDim = this.viewport.getBoundingClientRect(); - placement = placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' - : placement === 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' - : placement === 'right' && pos.right + actualWidth > viewportDim.width ? 'left' - : placement === 'left' && pos.left - actualWidth < viewportDim.left ? 'right' - : placement + placement = + placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom + ? 'top' + : placement === 'top' && pos.top - actualHeight < viewportDim.top + ? 'bottom' + : placement === 'right' && pos.right + actualWidth > viewportDim.width + ? 'left' + : placement === 'left' && pos.left - actualWidth < viewportDim.left + ? 'right' + : placement; - tip.classList.remove(orgPlacement) - tip.classList.add(placement) + tip.classList.remove(orgPlacement); + tip.classList.add(placement); } - var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + var calculatedOffset = this.getCalculatedOffset( + placement, + pos, + actualWidth, + actualHeight + ); - this.applyPlacement(calculatedOffset, placement) + this.applyPlacement(calculatedOffset, placement); - var prevHoverState = this.hoverState - this.hoverState = null + var prevHoverState = this.hoverState; + this.hoverState = null; - if (prevHoverState === 'out') this.leave() + if (prevHoverState === 'out') this.leave(); } -} +}; -Tooltip.prototype.applyPlacement = function (offset, placement) { - var tip = this.getTooltipElement() +Tooltip.prototype.applyPlacement = function(offset, placement) { + var tip = this.getTooltipElement(); - var width = tip.offsetWidth - var height = tip.offsetHeight + var width = tip.offsetWidth; + var height = tip.offsetHeight; // manually read margins because getBoundingClientRect includes difference - var computedStyle = window.getComputedStyle(tip) - var marginTop = parseInt(computedStyle.marginTop, 10) - var marginLeft = parseInt(computedStyle.marginLeft, 10) + var computedStyle = window.getComputedStyle(tip); + var marginTop = parseInt(computedStyle.marginTop, 10); + var marginLeft = parseInt(computedStyle.marginLeft, 10); - offset.top += marginTop - offset.left += marginLeft + offset.top += marginTop; + offset.left += marginLeft; - tip.style.top = offset.top + 'px' - tip.style.left = offset.left + 'px' + tip.style.top = offset.top + 'px'; + tip.style.left = offset.left + 'px'; - tip.classList.add('in') + tip.classList.add('in'); // check to see if placing tip in new offset caused the tip to resize itself - var actualWidth = tip.offsetWidth - var actualHeight = tip.offsetHeight + var actualWidth = tip.offsetWidth; + var actualHeight = tip.offsetHeight; if (placement === 'top' && actualHeight !== height) { - offset.top = offset.top + height - actualHeight + offset.top = offset.top + height - actualHeight; } - var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + var delta = this.getViewportAdjustedDelta( + placement, + offset, + actualWidth, + actualHeight + ); - if (delta.left) offset.left += delta.left - else offset.top += delta.top + if (delta.left) offset.left += delta.left; + else offset.top += delta.top; - var isVertical = /top|bottom/.test(placement) - var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight - var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + var isVertical = /top|bottom/.test(placement); + var arrowDelta = isVertical + ? delta.left * 2 - width + actualWidth + : delta.top * 2 - height + actualHeight; + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'; - tip.style.top = offset.top + 'px' - tip.style.left = offset.left + 'px' + tip.style.top = offset.top + 'px'; + tip.style.left = offset.left + 'px'; - this.replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical) -} + this.replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical); +}; -Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { - var arrow = this.getArrowElement() - var amount = 50 * (1 - delta / dimension) + '%' +Tooltip.prototype.replaceArrow = function(delta, dimension, isVertical) { + var arrow = this.getArrowElement(); + var amount = 50 * (1 - delta / dimension) + '%'; if (isVertical) { - arrow.style.left = amount - arrow.style.top = '' + arrow.style.left = amount; + arrow.style.top = ''; } else { - arrow.style.top = amount - arrow.style.left = '' + arrow.style.top = amount; + arrow.style.left = ''; } -} +}; -Tooltip.prototype.setContent = function () { - var tip = this.getTooltipElement() +Tooltip.prototype.setContent = function() { + var tip = this.getTooltipElement(); if (this.options.class) { - tip.classList.add(this.options.class) + tip.classList.add(this.options.class); } - var inner = tip.querySelector('.tooltip-inner') + var inner = tip.querySelector('.tooltip-inner'); if (this.options.item) { - inner.appendChild(this.viewRegistry.getView(this.options.item)) + inner.appendChild(this.viewRegistry.getView(this.options.item)); } else { - var title = this.getTitle() + var title = this.getTitle(); if (this.options.html) { - inner.innerHTML = title + inner.innerHTML = title; } else { - inner.textContent = title + inner.textContent = title; } } - tip.classList.remove('fade', 'in', 'top', 'bottom', 'left', 'right') -} + tip.classList.remove('fade', 'in', 'top', 'bottom', 'left', 'right'); +}; -Tooltip.prototype.hide = function (callback) { - this.inState = {} +Tooltip.prototype.hide = function(callback) { + this.inState = {}; if (this.hideOnClickOutsideOfTooltip) { - window.removeEventListener('click', this.hideOnClickOutsideOfTooltip, true) + window.removeEventListener('click', this.hideOnClickOutsideOfTooltip, true); } if (this.hideOnKeydownOutsideOfTooltip) { - window.removeEventListener('keydown', this.hideOnKeydownOutsideOfTooltip, true) + window.removeEventListener( + 'keydown', + this.hideOnKeydownOutsideOfTooltip, + true + ); } - this.tip && this.tip.classList.remove('in') - this.stopObservingMutations() + this.tip && this.tip.classList.remove('in'); + this.stopObservingMutations(); - if (this.hoverState !== 'in') this.tip && this.tip.remove() + if (this.hoverState !== 'in') this.tip && this.tip.remove(); - this.element.removeAttribute('aria-describedby') + this.element.removeAttribute('aria-describedby'); - callback && callback() + callback && callback(); - this.hoverState = null + this.hoverState = null; - clearTimeout(followThroughTimer) - followThroughTimer = setTimeout( - function () { - followThroughTimer = null - }, - Tooltip.FOLLOW_THROUGH_DURATION - ) + clearTimeout(followThroughTimer); + followThroughTimer = setTimeout(function() { + followThroughTimer = null; + }, Tooltip.FOLLOW_THROUGH_DURATION); - return this -} + return this; +}; -Tooltip.prototype.fixTitle = function () { - if (this.element.getAttribute('title') || typeof this.element.getAttribute('data-original-title') !== 'string') { - this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '') - this.element.setAttribute('title', '') +Tooltip.prototype.fixTitle = function() { + if ( + this.element.getAttribute('title') || + typeof this.element.getAttribute('data-original-title') !== 'string' + ) { + this.element.setAttribute( + 'data-original-title', + this.element.getAttribute('title') || '' + ); + this.element.setAttribute('title', ''); } -} +}; -Tooltip.prototype.hasContent = function () { - return this.getTitle() || this.options.item -} +Tooltip.prototype.hasContent = function() { + return this.getTitle() || this.options.item; +}; -Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { - return placement === 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } - : placement === 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } - : placement === 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } - :/* placement === 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } -} +Tooltip.prototype.getCalculatedOffset = function( + placement, + pos, + actualWidth, + actualHeight +) { + return placement === 'bottom' + ? { + top: pos.top + pos.height, + left: pos.left + pos.width / 2 - actualWidth / 2 + } + : placement === 'top' + ? { + top: pos.top - actualHeight, + left: pos.left + pos.width / 2 - actualWidth / 2 + } + : placement === 'left' + ? { + top: pos.top + pos.height / 2 - actualHeight / 2, + left: pos.left - actualWidth + } + : /* placement === 'right' */ { + top: pos.top + pos.height / 2 - actualHeight / 2, + left: pos.left + pos.width + }; +}; -Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { - var delta = { top: 0, left: 0 } - if (!this.viewport) return delta +Tooltip.prototype.getViewportAdjustedDelta = function( + placement, + pos, + actualWidth, + actualHeight +) { + var delta = { top: 0, left: 0 }; + if (!this.viewport) return delta; - var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 - var viewportDimensions = this.viewport.getBoundingClientRect() + var viewportPadding = + (this.options.viewport && this.options.viewport.padding) || 0; + var viewportDimensions = this.viewport.getBoundingClientRect(); if (/right|left/.test(placement)) { - var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll - var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight - if (topEdgeOffset < viewportDimensions.top) { // top overflow - delta.top = viewportDimensions.top - topEdgeOffset - } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow - delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll; + var bottomEdgeOffset = + pos.top + viewportPadding - viewportDimensions.scroll + actualHeight; + if (topEdgeOffset < viewportDimensions.top) { + // top overflow + delta.top = viewportDimensions.top - topEdgeOffset; + } else if ( + bottomEdgeOffset > + viewportDimensions.top + viewportDimensions.height + ) { + // bottom overflow + delta.top = + viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset; } } else { - var leftEdgeOffset = pos.left - viewportPadding - var rightEdgeOffset = pos.left + viewportPadding + actualWidth - if (leftEdgeOffset < viewportDimensions.left) { // left overflow - delta.left = viewportDimensions.left - leftEdgeOffset - } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow - delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + var leftEdgeOffset = pos.left - viewportPadding; + var rightEdgeOffset = pos.left + viewportPadding + actualWidth; + if (leftEdgeOffset < viewportDimensions.left) { + // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset; + } else if (rightEdgeOffset > viewportDimensions.right) { + // right overflow + delta.left = + viewportDimensions.left + viewportDimensions.width - rightEdgeOffset; } } - return delta -} + return delta; +}; -Tooltip.prototype.getTitle = function () { - var title = this.element.getAttribute('data-original-title') +Tooltip.prototype.getTitle = function() { + var title = this.element.getAttribute('data-original-title'); if (title) { - return title + return title; } else { - return (typeof this.options.title === 'function') + return typeof this.options.title === 'function' ? this.options.title.call(this.element) - : this.options.title + : this.options.title; } -} +}; -Tooltip.prototype.getUID = function (prefix) { - do prefix += ~~(Math.random() * 1000000) - while (document.getElementById(prefix)) - return prefix -} +Tooltip.prototype.getUID = function(prefix) { + do prefix += ~~(Math.random() * 1000000); + while (document.getElementById(prefix)); + return prefix; +}; -Tooltip.prototype.getTooltipElement = function () { +Tooltip.prototype.getTooltipElement = function() { if (!this.tip) { - let div = document.createElement('div') - div.innerHTML = this.options.template + let div = document.createElement('div'); + div.innerHTML = this.options.template; if (div.children.length !== 1) { - throw new Error('Tooltip `template` option must consist of exactly 1 top-level element!') + throw new Error( + 'Tooltip `template` option must consist of exactly 1 top-level element!' + ); } - this.tip = div.firstChild + this.tip = div.firstChild; } - return this.tip -} + return this.tip; +}; -Tooltip.prototype.getArrowElement = function () { - this.arrow = this.arrow || this.getTooltipElement().querySelector('.tooltip-arrow') - return this.arrow -} +Tooltip.prototype.getArrowElement = function() { + this.arrow = + this.arrow || this.getTooltipElement().querySelector('.tooltip-arrow'); + return this.arrow; +}; -Tooltip.prototype.enable = function () { - this.enabled = true -} +Tooltip.prototype.enable = function() { + this.enabled = true; +}; -Tooltip.prototype.disable = function () { - this.enabled = false -} +Tooltip.prototype.disable = function() { + this.enabled = false; +}; -Tooltip.prototype.toggleEnabled = function () { - this.enabled = !this.enabled -} +Tooltip.prototype.toggleEnabled = function() { + this.enabled = !this.enabled; +}; -Tooltip.prototype.toggle = function (event) { +Tooltip.prototype.toggle = function(event) { if (event) { if (event.currentTarget !== this.element) { - this.getDelegateComponent(event.currentTarget).toggle(event) - return + this.getDelegateComponent(event.currentTarget).toggle(event); + return; } - this.inState.click = !this.inState.click - if (this.isInStateTrue()) this.enter() - else this.leave() + this.inState.click = !this.inState.click; + if (this.isInStateTrue()) this.enter(); + else this.leave(); } else { - this.getTooltipElement().classList.contains('in') ? this.leave() : this.enter() + this.getTooltipElement().classList.contains('in') + ? this.leave() + : this.enter(); } -} +}; -Tooltip.prototype.destroy = function () { - clearTimeout(this.timeout) - this.tip && this.tip.remove() - this.disposables.dispose() -} +Tooltip.prototype.destroy = function() { + clearTimeout(this.timeout); + this.tip && this.tip.remove(); + this.disposables.dispose(); +}; -Tooltip.prototype.getDelegateComponent = function (element) { - var component = tooltipComponentsByElement.get(element) +Tooltip.prototype.getDelegateComponent = function(element) { + var component = tooltipComponentsByElement.get(element); if (!component) { - component = new Tooltip(element, this.getDelegateOptions(), this.viewRegistry) - tooltipComponentsByElement.set(element, component) + component = new Tooltip( + element, + this.getDelegateOptions(), + this.viewRegistry + ); + tooltipComponentsByElement.set(element, component); } - return component -} + return component; +}; -Tooltip.prototype.recalculatePosition = function () { - var tip = this.getTooltipElement() +Tooltip.prototype.recalculatePosition = function() { + var tip = this.getTooltipElement(); - var placement = typeof this.options.placement === 'function' - ? this.options.placement.call(this, tip, this.element) - : this.options.placement + var placement = + typeof this.options.placement === 'function' + ? this.options.placement.call(this, tip, this.element) + : this.options.placement; - var autoToken = /\s?auto?\s?/i - var autoPlace = autoToken.test(placement) - if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + var autoToken = /\s?auto?\s?/i; + var autoPlace = autoToken.test(placement); + if (autoPlace) placement = placement.replace(autoToken, '') || 'top'; - tip.classList.add(placement) + tip.classList.add(placement); - var pos = this.element.getBoundingClientRect() - var actualWidth = tip.offsetWidth - var actualHeight = tip.offsetHeight + var pos = this.element.getBoundingClientRect(); + var actualWidth = tip.offsetWidth; + var actualHeight = tip.offsetHeight; if (autoPlace) { - var orgPlacement = placement - var viewportDim = this.viewport.getBoundingClientRect() + var orgPlacement = placement; + var viewportDim = this.viewport.getBoundingClientRect(); - placement = placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' - : placement === 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' - : placement === 'right' && pos.right + actualWidth > viewportDim.width ? 'left' - : placement === 'left' && pos.left - actualWidth < viewportDim.left ? 'right' - : placement + placement = + placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom + ? 'top' + : placement === 'top' && pos.top - actualHeight < viewportDim.top + ? 'bottom' + : placement === 'right' && pos.right + actualWidth > viewportDim.width + ? 'left' + : placement === 'left' && pos.left - actualWidth < viewportDim.left + ? 'right' + : placement; - tip.classList.remove(orgPlacement) - tip.classList.add(placement) + tip.classList.remove(orgPlacement); + tip.classList.add(placement); } - var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) - this.applyPlacement(calculatedOffset, placement) -} + var calculatedOffset = this.getCalculatedOffset( + placement, + pos, + actualWidth, + actualHeight + ); + this.applyPlacement(calculatedOffset, placement); +}; -function extend () { - var args = Array.prototype.slice.apply(arguments) - var target = args.shift() - var source = args.shift() +function extend() { + var args = Array.prototype.slice.apply(arguments); + var target = args.shift(); + var source = args.shift(); while (source) { for (var key of Object.getOwnPropertyNames(source)) { - target[key] = source[key] + target[key] = source[key]; } - source = args.shift() + source = args.shift(); } - return target + return target; } -module.exports = Tooltip +module.exports = Tooltip; diff --git a/src/tree-sitter-grammar.js b/src/tree-sitter-grammar.js index 988a056a7..07be7448c 100644 --- a/src/tree-sitter-grammar.js +++ b/src/tree-sitter-grammar.js @@ -1,49 +1,53 @@ -const path = require('path') -const SyntaxScopeMap = require('./syntax-scope-map') -const Module = require('module') +const path = require('path'); +const SyntaxScopeMap = require('./syntax-scope-map'); +const Module = require('module'); -module.exports = -class TreeSitterGrammar { - constructor (registry, filePath, params) { - this.registry = registry - this.name = params.name - this.scopeName = params.scopeName +module.exports = class TreeSitterGrammar { + constructor(registry, filePath, params) { + this.registry = registry; + this.name = params.name; + this.scopeName = params.scopeName; // TODO - Remove the `RegExp` spelling and only support `Regex`, once all of the existing // Tree-sitter grammars are updated to spell it `Regex`. - this.contentRegex = buildRegex(params.contentRegex || params.contentRegExp) - this.injectionRegex = buildRegex(params.injectionRegex || params.injectionRegExp) - this.firstLineRegex = buildRegex(params.firstLineRegex) + this.contentRegex = buildRegex(params.contentRegex || params.contentRegExp); + this.injectionRegex = buildRegex( + params.injectionRegex || params.injectionRegExp + ); + this.firstLineRegex = buildRegex(params.firstLineRegex); - this.folds = params.folds || [] - this.folds.forEach(normalizeFoldSpecification) + this.folds = params.folds || []; + this.folds.forEach(normalizeFoldSpecification); this.commentStrings = { commentStartString: params.comments && params.comments.start, commentEndString: params.comments && params.comments.end - } + }; - const scopeSelectors = {} + const scopeSelectors = {}; for (const key in params.scopes || {}) { - const classes = preprocessScopes(params.scopes[key]) - const selectors = key.split(/,\s+/) + const classes = preprocessScopes(params.scopes[key]); + const selectors = key.split(/,\s+/); for (let selector of selectors) { - selector = selector.trim() - if (!selector) continue + selector = selector.trim(); + if (!selector) continue; if (scopeSelectors[selector]) { - scopeSelectors[selector] = [].concat(scopeSelectors[selector], classes) + scopeSelectors[selector] = [].concat( + scopeSelectors[selector], + classes + ); } else { - scopeSelectors[selector] = classes + scopeSelectors[selector] = classes; } } } - this.scopeMap = new SyntaxScopeMap(scopeSelectors) - this.fileTypes = params.fileTypes || [] - this.injectionPointsByType = {} + this.scopeMap = new SyntaxScopeMap(scopeSelectors); + this.fileTypes = params.fileTypes || []; + this.injectionPointsByType = {}; for (const injectionPoint of params.injectionPoints || []) { - this.addInjectionPoint(injectionPoint) + this.addInjectionPoint(injectionPoint); } // TODO - When we upgrade to a new enough version of node, use `require.resolve` @@ -52,67 +56,70 @@ class TreeSitterGrammar { id: filePath, filename: filePath, paths: Module._nodeModulePaths(path.dirname(filePath)) - }) + }); - this.languageModule = require(languageModulePath) - this.classNamesById = new Map() - this.scopeNamesById = new Map() - this.idsByScope = Object.create(null) - this.nextScopeId = 256 + 1 - this.registration = null + this.languageModule = require(languageModulePath); + this.classNamesById = new Map(); + this.scopeNamesById = new Map(); + this.idsByScope = Object.create(null); + this.nextScopeId = 256 + 1; + this.registration = null; } - inspect () { - return `TreeSitterGrammar {scopeName: ${this.scopeName}}` + inspect() { + return `TreeSitterGrammar {scopeName: ${this.scopeName}}`; } - idForScope (scopeName) { - let id = this.idsByScope[scopeName] + idForScope(scopeName) { + let id = this.idsByScope[scopeName]; if (!id) { - id = this.nextScopeId += 2 - const className = scopeName.split('.').map(s => `syntax--${s}`).join(' ') - this.idsByScope[scopeName] = id - this.classNamesById.set(id, className) - this.scopeNamesById.set(id, scopeName) + id = this.nextScopeId += 2; + const className = scopeName + .split('.') + .map(s => `syntax--${s}`) + .join(' '); + this.idsByScope[scopeName] = id; + this.classNamesById.set(id, className); + this.scopeNamesById.set(id, scopeName); } - return id + return id; } - classNameForScopeId (id) { - return this.classNamesById.get(id) + classNameForScopeId(id) { + return this.classNamesById.get(id); } - scopeNameForScopeId (id) { - return this.scopeNamesById.get(id) + scopeNameForScopeId(id) { + return this.scopeNamesById.get(id); } - activate () { - this.registration = this.registry.addGrammar(this) + activate() { + this.registration = this.registry.addGrammar(this); } - deactivate () { - if (this.registration) this.registration.dispose() + deactivate() { + if (this.registration) this.registration.dispose(); } - addInjectionPoint (injectionPoint) { - let injectionPoints = this.injectionPointsByType[injectionPoint.type] + addInjectionPoint(injectionPoint) { + let injectionPoints = this.injectionPointsByType[injectionPoint.type]; if (!injectionPoints) { - injectionPoints = this.injectionPointsByType[injectionPoint.type] = [] + injectionPoints = this.injectionPointsByType[injectionPoint.type] = []; } - injectionPoints.push(injectionPoint) + injectionPoints.push(injectionPoint); } - removeInjectionPoint (injectionPoint) { - const injectionPoints = this.injectionPointsByType[injectionPoint.type] + removeInjectionPoint(injectionPoint) { + const injectionPoints = this.injectionPointsByType[injectionPoint.type]; if (injectionPoints) { - const index = injectionPoints.indexOf(injectionPoint) - if (index !== -1) injectionPoints.splice(index, 1) + const index = injectionPoints.indexOf(injectionPoint); + if (index !== -1) injectionPoints.splice(index, 1); if (injectionPoints.length === 0) { - delete this.injectionPointsByType[injectionPoint.type] + delete this.injectionPointsByType[injectionPoint.type]; } } } -} +}; const preprocessScopes = value => typeof value === 'string' @@ -120,46 +127,46 @@ const preprocessScopes = value => : Array.isArray(value) ? value.map(preprocessScopes) : value.match - ? {match: new RegExp(value.match), scopes: preprocessScopes(value.scopes)} - : Object.assign({}, value, {scopes: preprocessScopes(value.scopes)}) + ? { match: new RegExp(value.match), scopes: preprocessScopes(value.scopes) } + : Object.assign({}, value, { scopes: preprocessScopes(value.scopes) }); -const NODE_NAME_REGEX = /[\w_]+/ +const NODE_NAME_REGEX = /[\w_]+/; -function matcherForSpec (spec) { +function matcherForSpec(spec) { if (typeof spec === 'string') { if (spec[0] === '"' && spec[spec.length - 1] === '"') { return { type: spec.substr(1, spec.length - 2), named: false - } + }; } if (!NODE_NAME_REGEX.test(spec)) { - return {type: spec, named: false} + return { type: spec, named: false }; } - return {type: spec, named: true} + return { type: spec, named: true }; } - return spec + return spec; } -function normalizeFoldSpecification (spec) { +function normalizeFoldSpecification(spec) { if (spec.type) { if (Array.isArray(spec.type)) { - spec.matchers = spec.type.map(matcherForSpec) + spec.matchers = spec.type.map(matcherForSpec); } else { - spec.matchers = [matcherForSpec(spec.type)] + spec.matchers = [matcherForSpec(spec.type)]; } } - if (spec.start) normalizeFoldSpecification(spec.start) - if (spec.end) normalizeFoldSpecification(spec.end) + if (spec.start) normalizeFoldSpecification(spec.start); + if (spec.end) normalizeFoldSpecification(spec.end); } -function buildRegex (value) { +function buildRegex(value) { // Allow multiple alternatives to be specified via an array, for // readability of the grammar file - if (Array.isArray(value)) value = value.map(_ => `(${_})`).join('|') - if (typeof value === 'string') return new RegExp(value) - return null + if (Array.isArray(value)) value = value.map(_ => `(${_})`).join('|'); + if (typeof value === 'string') return new RegExp(value); + return null; } diff --git a/src/tree-sitter-language-mode.js b/src/tree-sitter-language-mode.js index d282e083c..4391b02b2 100644 --- a/src/tree-sitter-language-mode.js +++ b/src/tree-sitter-language-mode.js @@ -1,378 +1,420 @@ -const Parser = require('tree-sitter') -const {Point, Range, spliceArray} = require('text-buffer') -const {Patch} = require('superstring') -const {Emitter} = require('event-kit') -const ScopeDescriptor = require('./scope-descriptor') -const Token = require('./token') -const TokenizedLine = require('./tokenized-line') -const TextMateLanguageMode = require('./text-mate-language-mode') -const {matcherForSelector} = require('./selectors') +const Parser = require('tree-sitter'); +const { Point, Range, spliceArray } = require('text-buffer'); +const { Patch } = require('superstring'); +const { Emitter } = require('event-kit'); +const ScopeDescriptor = require('./scope-descriptor'); +const Token = require('./token'); +const TokenizedLine = require('./tokenized-line'); +const TextMateLanguageMode = require('./text-mate-language-mode'); +const { matcherForSelector } = require('./selectors'); -let nextId = 0 -const MAX_RANGE = new Range(Point.ZERO, Point.INFINITY).freeze() -const PARSER_POOL = [] -const WORD_REGEX = /\w/ +let nextId = 0; +const MAX_RANGE = new Range(Point.ZERO, Point.INFINITY).freeze(); +const PARSER_POOL = []; +const WORD_REGEX = /\w/; class TreeSitterLanguageMode { - static _patchSyntaxNode () { + static _patchSyntaxNode() { if (!Parser.SyntaxNode.prototype.hasOwnProperty('range')) { Object.defineProperty(Parser.SyntaxNode.prototype, 'range', { - get () { - return rangeForNode(this) + get() { + return rangeForNode(this); } - }) + }); } } - constructor ({buffer, grammar, config, grammars, syncTimeoutMicros}) { - TreeSitterLanguageMode._patchSyntaxNode() - this.id = nextId++ - this.buffer = buffer - this.grammar = grammar - this.config = config - this.grammarRegistry = grammars - this.parser = new Parser() - this.rootLanguageLayer = new LanguageLayer(this, grammar) - this.injectionsMarkerLayer = buffer.addMarkerLayer() + constructor({ buffer, grammar, config, grammars, syncTimeoutMicros }) { + TreeSitterLanguageMode._patchSyntaxNode(); + this.id = nextId++; + this.buffer = buffer; + this.grammar = grammar; + this.config = config; + this.grammarRegistry = grammars; + this.parser = new Parser(); + this.rootLanguageLayer = new LanguageLayer(this, grammar); + this.injectionsMarkerLayer = buffer.addMarkerLayer(); if (syncTimeoutMicros != null) { - this.syncTimeoutMicros = syncTimeoutMicros + this.syncTimeoutMicros = syncTimeoutMicros; } - this.rootScopeDescriptor = new ScopeDescriptor({scopes: [this.grammar.scopeName]}) - this.emitter = new Emitter() - this.isFoldableCache = [] - this.hasQueuedParse = false + this.rootScopeDescriptor = new ScopeDescriptor({ + scopes: [this.grammar.scopeName] + }); + this.emitter = new Emitter(); + this.isFoldableCache = []; + this.hasQueuedParse = false; - this.grammarForLanguageString = this.grammarForLanguageString.bind(this) + this.grammarForLanguageString = this.grammarForLanguageString.bind(this); - this.rootLanguageLayer.update(null).then(() => - this.emitter.emit('did-tokenize') - ) + this.rootLanguageLayer + .update(null) + .then(() => this.emitter.emit('did-tokenize')); // TODO: Remove this once TreeSitterLanguageMode implements its own auto-indentation system. This // is temporarily needed in order to delegate to the TextMateLanguageMode's auto-indent system. - this.regexesByPattern = {} + this.regexesByPattern = {}; } - async parseCompletePromise () { - let done = false + async parseCompletePromise() { + let done = false; while (!done) { if (this.rootLanguageLayer.currentParsePromise) { - await this.rootLanguageLayer.currentParsePromises + await this.rootLanguageLayer.currentParsePromises; } else { - done = true + done = true; for (const marker of this.injectionsMarkerLayer.getMarkers()) { if (marker.languageLayer.currentParsePromise) { - done = false - await marker.languageLayer.currentParsePromise - break + done = false; + await marker.languageLayer.currentParsePromise; + break; } } } - await new Promise(resolve => setTimeout(resolve, 0)) + await new Promise(resolve => setTimeout(resolve, 0)); } } - destroy () { - this.injectionsMarkerLayer.destroy() - this.rootLanguageLayer = null - this.parser = null + destroy() { + this.injectionsMarkerLayer.destroy(); + this.rootLanguageLayer = null; + this.parser = null; } - getLanguageId () { - return this.grammar.scopeName + getLanguageId() { + return this.grammar.scopeName; } - bufferDidChange ({oldRange, newRange, oldText, newText}) { + bufferDidChange({ oldRange, newRange, oldText, newText }) { const edit = this.rootLanguageLayer._treeEditForBufferChange( - oldRange.start, oldRange.end, newRange.end, oldText, newText - ) - this.rootLanguageLayer.handleTextChange(edit, oldText, newText) + oldRange.start, + oldRange.end, + newRange.end, + oldText, + newText + ); + this.rootLanguageLayer.handleTextChange(edit, oldText, newText); for (const marker of this.injectionsMarkerLayer.getMarkers()) { - marker.languageLayer.handleTextChange(edit, oldText, newText) + marker.languageLayer.handleTextChange(edit, oldText, newText); } } - bufferDidFinishTransaction ({changes}) { - for (let i = 0, {length} = changes; i < length; i++) { - const {oldRange, newRange} = changes[i] + bufferDidFinishTransaction({ changes }) { + for (let i = 0, { length } = changes; i < length; i++) { + const { oldRange, newRange } = changes[i]; spliceArray( this.isFoldableCache, newRange.start.row, oldRange.end.row - oldRange.start.row, - {length: newRange.end.row - newRange.start.row} - ) + { length: newRange.end.row - newRange.start.row } + ); } - this.rootLanguageLayer.update(null) + this.rootLanguageLayer.update(null); } - parse (language, oldTree, ranges) { - const parser = PARSER_POOL.pop() || new Parser() - parser.setLanguage(language) + parse(language, oldTree, ranges) { + const parser = PARSER_POOL.pop() || new Parser(); + parser.setLanguage(language); const result = parser.parseTextBuffer(this.buffer.buffer, oldTree, { syncTimeoutMicros: this.syncTimeoutMicros, includedRanges: ranges - }) + }); if (result.then) { return result.then(tree => { - PARSER_POOL.push(parser) - return tree - }) + PARSER_POOL.push(parser); + return tree; + }); } else { - PARSER_POOL.push(parser) - return result + PARSER_POOL.push(parser); + return result; } } - get tree () { - return this.rootLanguageLayer.tree + get tree() { + return this.rootLanguageLayer.tree; } - updateForInjection (grammar) { - this.rootLanguageLayer.updateInjections(grammar) + updateForInjection(grammar) { + this.rootLanguageLayer.updateInjections(grammar); } /* Section - Highlighting */ - buildHighlightIterator () { - if (!this.rootLanguageLayer) return new NullHighlightIterator() - return new HighlightIterator(this) + buildHighlightIterator() { + if (!this.rootLanguageLayer) return new NullHighlightIterator(); + return new HighlightIterator(this); } - onDidTokenize (callback) { - return this.emitter.on('did-tokenize', callback) + onDidTokenize(callback) { + return this.emitter.on('did-tokenize', callback); } - onDidChangeHighlighting (callback) { - return this.emitter.on('did-change-highlighting', callback) + onDidChangeHighlighting(callback) { + return this.emitter.on('did-change-highlighting', callback); } - classNameForScopeId (scopeId) { - return this.grammar.classNameForScopeId(scopeId) + classNameForScopeId(scopeId) { + return this.grammar.classNameForScopeId(scopeId); } /* Section - Commenting */ - commentStringsForPosition (position) { - const range = this.firstNonWhitespaceRange(position.row) || new Range(position, position) - const {grammar} = this.getSyntaxNodeAndGrammarContainingRange(range) - return grammar.commentStrings + commentStringsForPosition(position) { + const range = + this.firstNonWhitespaceRange(position.row) || + new Range(position, position); + const { grammar } = this.getSyntaxNodeAndGrammarContainingRange(range); + return grammar.commentStrings; } - isRowCommented (row) { - const range = this.firstNonWhitespaceRange(row) + isRowCommented(row) { + const range = this.firstNonWhitespaceRange(row); if (range) { - const firstNode = this.getSyntaxNodeContainingRange(range) - if (firstNode) return firstNode.type.includes('comment') + const firstNode = this.getSyntaxNodeContainingRange(range); + if (firstNode) return firstNode.type.includes('comment'); } - return false + return false; } /* Section - Indentation */ - suggestedIndentForLineAtBufferRow (row, line, tabLength) { + suggestedIndentForLineAtBufferRow(row, line, tabLength) { return this._suggestedIndentForLineWithScopeAtBufferRow( row, line, this.rootScopeDescriptor, tabLength - ) + ); } - suggestedIndentForBufferRow (row, tabLength, options) { + suggestedIndentForBufferRow(row, tabLength, options) { return this._suggestedIndentForLineWithScopeAtBufferRow( row, this.buffer.lineForRow(row), this.rootScopeDescriptor, tabLength, options - ) + ); } - indentLevelForLine (line, tabLength) { - let indentLength = 0 - for (let i = 0, {length} = line; i < length; i++) { - const char = line[i] + indentLevelForLine(line, tabLength) { + let indentLength = 0; + for (let i = 0, { length } = line; i < length; i++) { + const char = line[i]; if (char === '\t') { - indentLength += tabLength - (indentLength % tabLength) + indentLength += tabLength - (indentLength % tabLength); } else if (char === ' ') { - indentLength++ + indentLength++; } else { - break + break; } } - return indentLength / tabLength + return indentLength / tabLength; } /* Section - Folding */ - isFoldableAtRow (row) { - if (this.isFoldableCache[row] != null) return this.isFoldableCache[row] - const result = this.getFoldableRangeContainingPoint(Point(row, Infinity), 0, true) != null - this.isFoldableCache[row] = result - return result + isFoldableAtRow(row) { + if (this.isFoldableCache[row] != null) return this.isFoldableCache[row]; + const result = + this.getFoldableRangeContainingPoint(Point(row, Infinity), 0, true) != + null; + this.isFoldableCache[row] = result; + return result; } - getFoldableRanges () { - return this.getFoldableRangesAtIndentLevel(null) + getFoldableRanges() { + return this.getFoldableRangesAtIndentLevel(null); } /** * TODO: Make this method generate folds for nested languages (currently, * folds are only generated for the root language layer). */ - getFoldableRangesAtIndentLevel (goalLevel) { - let result = [] - let stack = [{node: this.tree.rootNode, level: 0}] + getFoldableRangesAtIndentLevel(goalLevel) { + let result = []; + let stack = [{ node: this.tree.rootNode, level: 0 }]; while (stack.length > 0) { - const {node, level} = stack.pop() + const { node, level } = stack.pop(); - const range = this.getFoldableRangeForNode(node, this.grammar) + const range = this.getFoldableRangeForNode(node, this.grammar); if (range) { if (goalLevel == null || level === goalLevel) { - let updatedExistingRange = false - for (let i = 0, {length} = result; i < length; i++) { - if (result[i].start.row === range.start.row && - result[i].end.row === range.end.row) { - result[i] = range - updatedExistingRange = true - break + let updatedExistingRange = false; + for (let i = 0, { length } = result; i < length; i++) { + if ( + result[i].start.row === range.start.row && + result[i].end.row === range.end.row + ) { + result[i] = range; + updatedExistingRange = true; + break; } } - if (!updatedExistingRange) result.push(range) + if (!updatedExistingRange) result.push(range); } } - const parentStartRow = node.startPosition.row - const parentEndRow = node.endPosition.row - for (let children = node.namedChildren, i = 0, {length} = children; i < length; i++) { - const child = children[i] - const {startPosition: childStart, endPosition: childEnd} = child + const parentStartRow = node.startPosition.row; + const parentEndRow = node.endPosition.row; + for ( + let children = node.namedChildren, i = 0, { length } = children; + i < length; + i++ + ) { + const child = children[i]; + const { startPosition: childStart, endPosition: childEnd } = child; if (childEnd.row > childStart.row) { - if (childStart.row === parentStartRow && childEnd.row === parentEndRow) { - stack.push({node: child, level: level}) + if ( + childStart.row === parentStartRow && + childEnd.row === parentEndRow + ) { + stack.push({ node: child, level: level }); } else { - const childLevel = range && range.containsPoint(childStart) && range.containsPoint(childEnd) - ? level + 1 - : level + const childLevel = + range && + range.containsPoint(childStart) && + range.containsPoint(childEnd) + ? level + 1 + : level; if (childLevel <= goalLevel || goalLevel == null) { - stack.push({node: child, level: childLevel}) + stack.push({ node: child, level: childLevel }); } } } } } - return result.sort((a, b) => a.start.row - b.start.row) + return result.sort((a, b) => a.start.row - b.start.row); } - getFoldableRangeContainingPoint (point, tabLength, existenceOnly = false) { - if (!this.tree) return null + getFoldableRangeContainingPoint(point, tabLength, existenceOnly = false) { + if (!this.tree) return null; - let smallestRange + let smallestRange; this._forEachTreeWithRange(new Range(point, point), (tree, grammar) => { - let node = tree.rootNode.descendantForPosition(this.buffer.clipPosition(point)) + let node = tree.rootNode.descendantForPosition( + this.buffer.clipPosition(point) + ); while (node) { - if (existenceOnly && node.startPosition.row < point.row) return + if (existenceOnly && node.startPosition.row < point.row) return; if (node.endPosition.row > point.row) { - const range = this.getFoldableRangeForNode(node, grammar) + const range = this.getFoldableRangeForNode(node, grammar); if (range && rangeIsSmaller(range, smallestRange)) { - smallestRange = range - return + smallestRange = range; + return; } } - node = node.parent + node = node.parent; } - }) + }); return existenceOnly ? smallestRange && smallestRange.start.row === point.row - : smallestRange + : smallestRange; } - _forEachTreeWithRange (range, callback) { + _forEachTreeWithRange(range, callback) { if (this.rootLanguageLayer.tree) { - callback(this.rootLanguageLayer.tree, this.rootLanguageLayer.grammar) + callback(this.rootLanguageLayer.tree, this.rootLanguageLayer.grammar); } const injectionMarkers = this.injectionsMarkerLayer.findMarkers({ intersectsRange: range - }) + }); for (const injectionMarker of injectionMarkers) { - const {tree, grammar} = injectionMarker.languageLayer - if (tree) callback(tree, grammar) + const { tree, grammar } = injectionMarker.languageLayer; + if (tree) callback(tree, grammar); } } - getFoldableRangeForNode (node, grammar, existenceOnly) { - const {children} = node - const childCount = children.length + getFoldableRangeForNode(node, grammar, existenceOnly) { + const { children } = node; + const childCount = children.length; - for (var i = 0, {length} = grammar.folds; i < length; i++) { - const foldSpec = grammar.folds[i] + for (var i = 0, { length } = grammar.folds; i < length; i++) { + const foldSpec = grammar.folds[i]; - if (foldSpec.matchers && !hasMatchingFoldSpec(foldSpec.matchers, node)) continue + if (foldSpec.matchers && !hasMatchingFoldSpec(foldSpec.matchers, node)) + continue; - let foldStart - const startEntry = foldSpec.start + let foldStart; + const startEntry = foldSpec.start; if (startEntry) { - let foldStartNode + let foldStartNode; if (startEntry.index != null) { - foldStartNode = children[startEntry.index] - if (!foldStartNode || startEntry.matchers && !hasMatchingFoldSpec(startEntry.matchers, foldStartNode)) continue + foldStartNode = children[startEntry.index]; + if ( + !foldStartNode || + (startEntry.matchers && + !hasMatchingFoldSpec(startEntry.matchers, foldStartNode)) + ) + continue; } else { - foldStartNode = children.find(child => hasMatchingFoldSpec(startEntry.matchers, child)) - if (!foldStartNode) continue + foldStartNode = children.find(child => + hasMatchingFoldSpec(startEntry.matchers, child) + ); + if (!foldStartNode) continue; } - foldStart = new Point(foldStartNode.endPosition.row, Infinity) + foldStart = new Point(foldStartNode.endPosition.row, Infinity); } else { - foldStart = new Point(node.startPosition.row, Infinity) + foldStart = new Point(node.startPosition.row, Infinity); } - let foldEnd - const endEntry = foldSpec.end + let foldEnd; + const endEntry = foldSpec.end; if (endEntry) { - let foldEndNode + let foldEndNode; if (endEntry.index != null) { - const index = endEntry.index < 0 ? childCount + endEntry.index : endEntry.index - foldEndNode = children[index] - if (!foldEndNode || (endEntry.type && endEntry.type !== foldEndNode.type)) continue + const index = + endEntry.index < 0 ? childCount + endEntry.index : endEntry.index; + foldEndNode = children[index]; + if ( + !foldEndNode || + (endEntry.type && endEntry.type !== foldEndNode.type) + ) + continue; } else { - foldEndNode = children.find(child => hasMatchingFoldSpec(endEntry.matchers, child)) - if (!foldEndNode) continue + foldEndNode = children.find(child => + hasMatchingFoldSpec(endEntry.matchers, child) + ); + if (!foldEndNode) continue; } - if (foldEndNode.startPosition.row <= foldStart.row) continue + if (foldEndNode.startPosition.row <= foldStart.row) continue; - foldEnd = foldEndNode.startPosition - if (this.buffer.findInRangeSync( - WORD_REGEX, new Range(foldEnd, new Point(foldEnd.row, Infinity)) - )) { - foldEnd = new Point(foldEnd.row - 1, Infinity) + foldEnd = foldEndNode.startPosition; + if ( + this.buffer.findInRangeSync( + WORD_REGEX, + new Range(foldEnd, new Point(foldEnd.row, Infinity)) + ) + ) { + foldEnd = new Point(foldEnd.row - 1, Infinity); } } else { - const {endPosition} = node + const { endPosition } = node; if (endPosition.column === 0) { - foldEnd = Point(endPosition.row - 1, Infinity) + foldEnd = Point(endPosition.row - 1, Infinity); } else if (childCount > 0) { - foldEnd = endPosition + foldEnd = endPosition; } else { - foldEnd = Point(endPosition.row, 0) + foldEnd = Point(endPosition.row, 0); } } - return existenceOnly ? true : new Range(foldStart, foldEnd) + return existenceOnly ? true : new Range(foldStart, foldEnd); } } @@ -380,66 +422,72 @@ class TreeSitterLanguageMode { Section - Syntax Tree APIs */ - getSyntaxNodeContainingRange (range, where = _ => true) { - return this.getSyntaxNodeAndGrammarContainingRange(range, where).node + getSyntaxNodeContainingRange(range, where = _ => true) { + return this.getSyntaxNodeAndGrammarContainingRange(range, where).node; } - getSyntaxNodeAndGrammarContainingRange (range, where = _ => true) { - const startIndex = this.buffer.characterIndexForPosition(range.start) - const endIndex = this.buffer.characterIndexForPosition(range.end) - const searchEndIndex = Math.max(0, endIndex - 1) + getSyntaxNodeAndGrammarContainingRange(range, where = _ => true) { + const startIndex = this.buffer.characterIndexForPosition(range.start); + const endIndex = this.buffer.characterIndexForPosition(range.end); + const searchEndIndex = Math.max(0, endIndex - 1); - let smallestNode = null - let smallestNodeGrammar = this.grammar + let smallestNode = null; + let smallestNodeGrammar = this.grammar; this._forEachTreeWithRange(range, (tree, grammar) => { - let node = tree.rootNode.descendantForIndex(startIndex, searchEndIndex) + let node = tree.rootNode.descendantForIndex(startIndex, searchEndIndex); while (node) { - if (nodeContainsIndices(node, startIndex, endIndex) && where(node, grammar)) { + if ( + nodeContainsIndices(node, startIndex, endIndex) && + where(node, grammar) + ) { if (nodeIsSmaller(node, smallestNode)) { - smallestNode = node - smallestNodeGrammar = grammar + smallestNode = node; + smallestNodeGrammar = grammar; } - break + break; } - node = node.parent + node = node.parent; } - }) + }); - return {node: smallestNode, grammar: smallestNodeGrammar} + return { node: smallestNode, grammar: smallestNodeGrammar }; } - getRangeForSyntaxNodeContainingRange (range, where) { - const node = this.getSyntaxNodeContainingRange(range, where) - return node && node.range + getRangeForSyntaxNodeContainingRange(range, where) { + const node = this.getSyntaxNodeContainingRange(range, where); + return node && node.range; } - getSyntaxNodeAtPosition (position, where) { - return this.getSyntaxNodeContainingRange(new Range(position, position), where) + getSyntaxNodeAtPosition(position, where) { + return this.getSyntaxNodeContainingRange( + new Range(position, position), + where + ); } - bufferRangeForScopeAtPosition (selector, position) { - const nodeCursorAdapter = new NodeCursorAdaptor() + bufferRangeForScopeAtPosition(selector, position) { + const nodeCursorAdapter = new NodeCursorAdaptor(); if (typeof selector === 'string') { - const match = matcherForSelector(selector) + const match = matcherForSelector(selector); selector = (node, grammar) => { - const rules = grammar.scopeMap.get([node.type], [0], node.named) - nodeCursorAdapter.node = node - const scopeName = applyLeafRules(rules, nodeCursorAdapter) + const rules = grammar.scopeMap.get([node.type], [0], node.named); + nodeCursorAdapter.node = node; + const scopeName = applyLeafRules(rules, nodeCursorAdapter); if (scopeName != null) { - return match(scopeName) + return match(scopeName); } - } + }; } - if (selector === null) selector = undefined - const node = this.getSyntaxNodeAtPosition(position, selector) - return node && node.range + if (selector === null) selector = undefined; + const node = this.getSyntaxNodeAtPosition(position, selector); + return node && node.range; } /* Section - Backward compatibility shims */ - tokenizedLineForRow (row) { + tokenizedLineForRow(row) { return new TokenizedLine({ openScopes: [], text: this.buffer.lineForRow(row), @@ -448,133 +496,146 @@ class TreeSitterLanguageMode { lineEnding: this.buffer.lineEndingForRow(row), tokenIterator: null, grammar: this.grammar - }) + }); } - syntaxTreeScopeDescriptorForPosition (point) { - const nodes = [] - point = this.buffer.clipPosition(Point.fromObject(point)) + syntaxTreeScopeDescriptorForPosition(point) { + const nodes = []; + point = this.buffer.clipPosition(Point.fromObject(point)); // If the position is the end of a line, get node of left character instead of newline // This is to match TextMate behaviour, see https://github.com/atom/atom/issues/18463 - if (point.column > 0 && point.column === this.buffer.lineLengthForRow(point.row)) { - point = point.copy() - point.column-- + if ( + point.column > 0 && + point.column === this.buffer.lineLengthForRow(point.row) + ) { + point = point.copy(); + point.column--; } this._forEachTreeWithRange(new Range(point, point), tree => { - let node = tree.rootNode.descendantForPosition(point) + let node = tree.rootNode.descendantForPosition(point); while (node) { - nodes.push(node) - node = node.parent + nodes.push(node); + node = node.parent; } - }) + }); // The nodes are mostly already sorted from smallest to largest, // but for files with multiple syntax trees (e.g. ERB), each tree's // nodes are separate. Sort the nodes from largest to smallest. - nodes.reverse() - nodes.sort((a, b) => - a.startIndex - b.startIndex || b.endIndex - a.endIndex - ) + nodes.reverse(); + nodes.sort( + (a, b) => a.startIndex - b.startIndex || b.endIndex - a.endIndex + ); - const nodeTypes = nodes.map(node => node.type) - nodeTypes.unshift(this.grammar.scopeName) - return new ScopeDescriptor({scopes: nodeTypes}) + const nodeTypes = nodes.map(node => node.type); + nodeTypes.unshift(this.grammar.scopeName); + return new ScopeDescriptor({ scopes: nodeTypes }); } - scopeDescriptorForPosition (point) { - point = this.buffer.clipPosition(Point.fromObject(point)) + scopeDescriptorForPosition(point) { + point = this.buffer.clipPosition(Point.fromObject(point)); // If the position is the end of a line, get scope of left character instead of newline // This is to match TextMate behaviour, see https://github.com/atom/atom/issues/18463 - if (point.column > 0 && point.column === this.buffer.lineLengthForRow(point.row)) { - point = point.copy() - point.column-- + if ( + point.column > 0 && + point.column === this.buffer.lineLengthForRow(point.row) + ) { + point = point.copy(); + point.column--; } - const iterator = this.buildHighlightIterator() - const scopes = [] + const iterator = this.buildHighlightIterator(); + const scopes = []; for (const scope of iterator.seek(point, point.row + 1)) { - scopes.push(this.grammar.scopeNameForScopeId(scope)) + scopes.push(this.grammar.scopeNameForScopeId(scope)); } if (point.isEqual(iterator.getPosition())) { for (const scope of iterator.getOpenScopeIds()) { - scopes.push(this.grammar.scopeNameForScopeId(scope)) + scopes.push(this.grammar.scopeNameForScopeId(scope)); } } if (scopes.length === 0 || scopes[0] !== this.grammar.scopeName) { - scopes.unshift(this.grammar.scopeName) + scopes.unshift(this.grammar.scopeName); } - return new ScopeDescriptor({scopes}) + return new ScopeDescriptor({ scopes }); } - tokenForPosition (point) { - const node = this.getSyntaxNodeAtPosition(point) - const scopes = this.scopeDescriptorForPosition(point).getScopesArray() - return new Token({value: node.text, scopes}) + tokenForPosition(point) { + const node = this.getSyntaxNodeAtPosition(point); + const scopes = this.scopeDescriptorForPosition(point).getScopesArray(); + return new Token({ value: node.text, scopes }); } - getGrammar () { - return this.grammar + getGrammar() { + return this.grammar; } /* Section - Private */ - firstNonWhitespaceRange (row) { - return this.buffer.findInRangeSync(/\S/, new Range(new Point(row, 0), new Point(row, Infinity))) + firstNonWhitespaceRange(row) { + return this.buffer.findInRangeSync( + /\S/, + new Range(new Point(row, 0), new Point(row, Infinity)) + ); } - grammarForLanguageString (languageString) { - return this.grammarRegistry.treeSitterGrammarForLanguageString(languageString) + grammarForLanguageString(languageString) { + return this.grammarRegistry.treeSitterGrammarForLanguageString( + languageString + ); } - emitRangeUpdate (range) { - const startRow = range.start.row - const endRow = range.end.row + emitRangeUpdate(range) { + const startRow = range.start.row; + const endRow = range.end.row; for (let row = startRow; row < endRow; row++) { - this.isFoldableCache[row] = undefined + this.isFoldableCache[row] = undefined; } - this.emitter.emit('did-change-highlighting', range) + this.emitter.emit('did-change-highlighting', range); } } class LanguageLayer { - constructor (languageMode, grammar, contentChildTypes) { - this.languageMode = languageMode - this.grammar = grammar - this.tree = null - this.currentParsePromise = null - this.patchSinceCurrentParseStarted = null - this.contentChildTypes = contentChildTypes + constructor(languageMode, grammar, contentChildTypes) { + this.languageMode = languageMode; + this.grammar = grammar; + this.tree = null; + this.currentParsePromise = null; + this.patchSinceCurrentParseStarted = null; + this.contentChildTypes = contentChildTypes; } - buildHighlightIterator () { + buildHighlightIterator() { if (this.tree) { - return new LayerHighlightIterator(this, this.tree.walk()) + return new LayerHighlightIterator(this, this.tree.walk()); } else { - return new NullHighlightIterator() + return new NullHighlightIterator(); } } - handleTextChange (edit, oldText, newText) { - const {startPosition, oldEndPosition, newEndPosition} = edit + handleTextChange(edit, oldText, newText) { + const { startPosition, oldEndPosition, newEndPosition } = edit; if (this.tree) { - this.tree.edit(edit) + this.tree.edit(edit); if (this.editedRange) { if (startPosition.isLessThan(this.editedRange.start)) { - this.editedRange.start = startPosition + this.editedRange.start = startPosition; } if (oldEndPosition.isLessThan(this.editedRange.end)) { - this.editedRange.end = newEndPosition.traverse(this.editedRange.end.traversalFrom(oldEndPosition)) + this.editedRange.end = newEndPosition.traverse( + this.editedRange.end.traversalFrom(oldEndPosition) + ); } else { - this.editedRange.end = newEndPosition + this.editedRange.end = newEndPosition; } } else { - this.editedRange = new Range(startPosition, newEndPosition) + this.editedRange = new Range(startPosition, newEndPosition); } } @@ -585,202 +646,247 @@ class LanguageLayer { newEndPosition.traversalFrom(startPosition), oldText, newText - ) + ); } } - destroy () { + destroy() { for (const marker of this.languageMode.injectionsMarkerLayer.getMarkers()) { if (marker.parentLanguageLayer === this) { - marker.languageLayer.destroy() - marker.destroy() + marker.languageLayer.destroy(); + marker.destroy(); } } } - async update (nodeRangeSet) { + async update(nodeRangeSet) { if (!this.currentParsePromise) { - while (!this.destroyed && (!this.tree || this.tree.rootNode.hasChanges())) { - const params = {async: false} - this.currentParsePromise = this._performUpdate(nodeRangeSet, params) - if (!params.async) break - await this.currentParsePromise + while ( + !this.destroyed && + (!this.tree || this.tree.rootNode.hasChanges()) + ) { + const params = { async: false }; + this.currentParsePromise = this._performUpdate(nodeRangeSet, params); + if (!params.async) break; + await this.currentParsePromise; } - this.currentParsePromise = null + this.currentParsePromise = null; } } - updateInjections (grammar) { + updateInjections(grammar) { if (grammar.injectionRegex) { - if (!this.currentParsePromise) this.currentParsePromise = Promise.resolve() + if (!this.currentParsePromise) + this.currentParsePromise = Promise.resolve(); this.currentParsePromise = this.currentParsePromise.then(async () => { - await this._populateInjections(MAX_RANGE, null) - this.currentParsePromise = null - }) + await this._populateInjections(MAX_RANGE, null); + this.currentParsePromise = null; + }); } } - async _performUpdate (nodeRangeSet, params) { - let includedRanges = null + async _performUpdate(nodeRangeSet, params) { + let includedRanges = null; if (nodeRangeSet) { - includedRanges = nodeRangeSet.getRanges(this.languageMode.buffer) + includedRanges = nodeRangeSet.getRanges(this.languageMode.buffer); if (includedRanges.length === 0) { - this.tree = null - this.destroyed = true - return + this.tree = null; + this.destroyed = true; + return; } } - let affectedRange = this.editedRange - this.editedRange = null + let affectedRange = this.editedRange; + this.editedRange = null; - this.patchSinceCurrentParseStarted = new Patch() + this.patchSinceCurrentParseStarted = new Patch(); let tree = this.languageMode.parse( this.grammar.languageModule, this.tree, includedRanges - ) + ); if (tree.then) { - params.async = true - tree = await tree + params.async = true; + tree = await tree; } - const changes = this.patchSinceCurrentParseStarted.getChanges() - this.patchSinceCurrentParseStarted = null - for (const {oldStart, newStart, oldEnd, newEnd, oldText, newText} of changes) { - const newExtent = Point.fromObject(newEnd).traversalFrom(newStart) - tree.edit(this._treeEditForBufferChange( - newStart, - oldEnd, - Point.fromObject(oldStart).traverse(newExtent), - oldText, - newText - )) + const changes = this.patchSinceCurrentParseStarted.getChanges(); + this.patchSinceCurrentParseStarted = null; + for (const { + oldStart, + newStart, + oldEnd, + newEnd, + oldText, + newText + } of changes) { + const newExtent = Point.fromObject(newEnd).traversalFrom(newStart); + tree.edit( + this._treeEditForBufferChange( + newStart, + oldEnd, + Point.fromObject(oldStart).traverse(newExtent), + oldText, + newText + ) + ); } if (this.tree) { - const rangesWithSyntaxChanges = this.tree.getChangedRanges(tree) - this.tree = tree + const rangesWithSyntaxChanges = this.tree.getChangedRanges(tree); + this.tree = tree; if (rangesWithSyntaxChanges.length > 0) { for (const range of rangesWithSyntaxChanges) { - this.languageMode.emitRangeUpdate(rangeForNode(range)) + this.languageMode.emitRangeUpdate(rangeForNode(range)); } const combinedRangeWithSyntaxChange = new Range( rangesWithSyntaxChanges[0].startPosition, last(rangesWithSyntaxChanges).endPosition - ) + ); if (affectedRange) { - this.languageMode.emitRangeUpdate(affectedRange) - affectedRange = affectedRange.union(combinedRangeWithSyntaxChange) + this.languageMode.emitRangeUpdate(affectedRange); + affectedRange = affectedRange.union(combinedRangeWithSyntaxChange); } else { - affectedRange = combinedRangeWithSyntaxChange + affectedRange = combinedRangeWithSyntaxChange; } } } else { - this.tree = tree - this.languageMode.emitRangeUpdate(rangeForNode(tree.rootNode)) + this.tree = tree; + this.languageMode.emitRangeUpdate(rangeForNode(tree.rootNode)); if (includedRanges) { - affectedRange = new Range(includedRanges[0].startPosition, last(includedRanges).endPosition) + affectedRange = new Range( + includedRanges[0].startPosition, + last(includedRanges).endPosition + ); } else { - affectedRange = MAX_RANGE + affectedRange = MAX_RANGE; } } if (affectedRange) { - const injectionPromise = this._populateInjections(affectedRange, nodeRangeSet) + const injectionPromise = this._populateInjections( + affectedRange, + nodeRangeSet + ); if (injectionPromise) { - params.async = true - return injectionPromise + params.async = true; + return injectionPromise; } } } - _populateInjections (range, nodeRangeSet) { + _populateInjections(range, nodeRangeSet) { const existingInjectionMarkers = this.languageMode.injectionsMarkerLayer - .findMarkers({intersectsRange: range}) - .filter(marker => marker.parentLanguageLayer === this) + .findMarkers({ intersectsRange: range }) + .filter(marker => marker.parentLanguageLayer === this); if (existingInjectionMarkers.length > 0) { - range = range.union(new Range( - existingInjectionMarkers[0].getRange().start, - last(existingInjectionMarkers).getRange().end - )) + range = range.union( + new Range( + existingInjectionMarkers[0].getRange().start, + last(existingInjectionMarkers).getRange().end + ) + ); } - const markersToUpdate = new Map() + const markersToUpdate = new Map(); const nodes = this.tree.rootNode.descendantsOfType( Object.keys(this.grammar.injectionPointsByType), range.start, range.end - ) + ); - let existingInjectionMarkerIndex = 0 + let existingInjectionMarkerIndex = 0; for (const node of nodes) { - for (const injectionPoint of this.grammar.injectionPointsByType[node.type]) { - const languageName = injectionPoint.language(node) - if (!languageName) continue + for (const injectionPoint of this.grammar.injectionPointsByType[ + node.type + ]) { + const languageName = injectionPoint.language(node); + if (!languageName) continue; - const grammar = this.languageMode.grammarForLanguageString(languageName) - if (!grammar) continue + const grammar = this.languageMode.grammarForLanguageString( + languageName + ); + if (!grammar) continue; - const contentNodes = injectionPoint.content(node) - if (!contentNodes) continue + const contentNodes = injectionPoint.content(node); + if (!contentNodes) continue; - const injectionNodes = [].concat(contentNodes) - if (!injectionNodes.length) continue + const injectionNodes = [].concat(contentNodes); + if (!injectionNodes.length) continue; - const injectionRange = rangeForNode(node) + const injectionRange = rangeForNode(node); - let marker - for (let i = existingInjectionMarkerIndex, n = existingInjectionMarkers.length; i < n; i++) { - const existingMarker = existingInjectionMarkers[i] - const comparison = existingMarker.getRange().compare(injectionRange) + let marker; + for ( + let i = existingInjectionMarkerIndex, + n = existingInjectionMarkers.length; + i < n; + i++ + ) { + const existingMarker = existingInjectionMarkers[i]; + const comparison = existingMarker.getRange().compare(injectionRange); if (comparison > 0) { - break + break; } else if (comparison === 0) { - existingInjectionMarkerIndex = i + existingInjectionMarkerIndex = i; if (existingMarker.languageLayer.grammar === grammar) { - marker = existingMarker - break + marker = existingMarker; + break; } } else { - existingInjectionMarkerIndex = i + existingInjectionMarkerIndex = i; } } if (!marker) { - marker = this.languageMode.injectionsMarkerLayer.markRange(injectionRange) - marker.languageLayer = new LanguageLayer(this.languageMode, grammar, injectionPoint.contentChildTypes) - marker.parentLanguageLayer = this + marker = this.languageMode.injectionsMarkerLayer.markRange( + injectionRange + ); + marker.languageLayer = new LanguageLayer( + this.languageMode, + grammar, + injectionPoint.contentChildTypes + ); + marker.parentLanguageLayer = this; } - markersToUpdate.set(marker, new NodeRangeSet(nodeRangeSet, injectionNodes, injectionPoint.newlinesBetween)) + markersToUpdate.set( + marker, + new NodeRangeSet( + nodeRangeSet, + injectionNodes, + injectionPoint.newlinesBetween + ) + ); } } for (const marker of existingInjectionMarkers) { if (!markersToUpdate.has(marker)) { - marker.languageLayer.destroy() - this.languageMode.emitRangeUpdate(marker.getRange()) - marker.destroy() + marker.languageLayer.destroy(); + this.languageMode.emitRangeUpdate(marker.getRange()); + marker.destroy(); } } if (markersToUpdate.size > 0) { - this.lastUpdateWasAsync = true - const promises = [] + this.lastUpdateWasAsync = true; + const promises = []; for (const [marker, nodeRangeSet] of markersToUpdate) { - promises.push(marker.languageLayer.update(nodeRangeSet)) + promises.push(marker.languageLayer.update(nodeRangeSet)); } - return Promise.all(promises) + return Promise.all(promises); } } - _treeEditForBufferChange (start, oldEnd, newEnd, oldText, newText) { - const startIndex = this.languageMode.buffer.characterIndexForPosition(start) + _treeEditForBufferChange(start, oldEnd, newEnd, oldText, newText) { + const startIndex = this.languageMode.buffer.characterIndexForPosition( + start + ); return { startIndex, oldEndIndex: startIndex + oldText.length, @@ -788,61 +894,71 @@ class LanguageLayer { startPosition: start, oldEndPosition: oldEnd, newEndPosition: newEnd - } + }; } } class HighlightIterator { - constructor (languageMode) { - this.languageMode = languageMode - this.iterators = null + constructor(languageMode) { + this.languageMode = languageMode; + this.iterators = null; } - seek (targetPosition, endRow) { - const injectionMarkers = this.languageMode.injectionsMarkerLayer.findMarkers({ - intersectsRange: new Range(targetPosition, new Point(endRow + 1, 0)) - }) + seek(targetPosition, endRow) { + const injectionMarkers = this.languageMode.injectionsMarkerLayer.findMarkers( + { + intersectsRange: new Range(targetPosition, new Point(endRow + 1, 0)) + } + ); - this.iterators = [this.languageMode.rootLanguageLayer.buildHighlightIterator()] + this.iterators = [ + this.languageMode.rootLanguageLayer.buildHighlightIterator() + ]; for (const marker of injectionMarkers) { - this.iterators.push(marker.languageLayer.buildHighlightIterator()) + this.iterators.push(marker.languageLayer.buildHighlightIterator()); } - this.iterators.sort((a, b) => b.getIndex() - a.getIndex()) + this.iterators.sort((a, b) => b.getIndex() - a.getIndex()); - const containingTags = [] - const containingTagStartIndices = [] - const targetIndex = this.languageMode.buffer.characterIndexForPosition(targetPosition) + const containingTags = []; + const containingTagStartIndices = []; + const targetIndex = this.languageMode.buffer.characterIndexForPosition( + targetPosition + ); for (let i = this.iterators.length - 1; i >= 0; i--) { - this.iterators[i].seek(targetIndex, containingTags, containingTagStartIndices) + this.iterators[i].seek( + targetIndex, + containingTags, + containingTagStartIndices + ); } - this.iterators.sort((a, b) => b.getIndex() - a.getIndex()) - return containingTags + this.iterators.sort((a, b) => b.getIndex() - a.getIndex()); + return containingTags; } - moveToSuccessor () { - const lastIndex = this.iterators.length - 1 - const leader = this.iterators[lastIndex] - leader.moveToSuccessor() - const leaderCharIndex = leader.getIndex() - let i = lastIndex - while (i > 0 && this.iterators[i - 1].getIndex() < leaderCharIndex) i-- - if (i < lastIndex) this.iterators.splice(i, 0, this.iterators.pop()) + moveToSuccessor() { + const lastIndex = this.iterators.length - 1; + const leader = this.iterators[lastIndex]; + leader.moveToSuccessor(); + const leaderCharIndex = leader.getIndex(); + let i = lastIndex; + while (i > 0 && this.iterators[i - 1].getIndex() < leaderCharIndex) i--; + if (i < lastIndex) this.iterators.splice(i, 0, this.iterators.pop()); } - getPosition () { - return last(this.iterators).getPosition() + getPosition() { + return last(this.iterators).getPosition(); } - getCloseScopeIds () { - return last(this.iterators).getCloseScopeIds() + getCloseScopeIds() { + return last(this.iterators).getCloseScopeIds(); } - getOpenScopeIds () { - return last(this.iterators).getOpenScopeIds() + getOpenScopeIds() { + return last(this.iterators).getOpenScopeIds(); } - logState () { - const iterator = last(this.iterators) + logState() { + const iterator = last(this.iterators); if (iterator.treeCursor) { console.log( iterator.getPosition(), @@ -851,281 +967,308 @@ class HighlightIterator { iterator.languageLayer.tree.rootNode.startPosition, iterator.languageLayer.tree.rootNode.endPosition ).toString() - ) - console.log('close', iterator.closeTags.map(id => this.languageMode.grammar.scopeNameForScopeId(id))) - console.log('open', iterator.openTags.map(id => this.languageMode.grammar.scopeNameForScopeId(id))) + ); + console.log( + 'close', + iterator.closeTags.map(id => + this.languageMode.grammar.scopeNameForScopeId(id) + ) + ); + console.log( + 'open', + iterator.openTags.map(id => + this.languageMode.grammar.scopeNameForScopeId(id) + ) + ); } } } class LayerHighlightIterator { - constructor (languageLayer, treeCursor) { - this.languageLayer = languageLayer + constructor(languageLayer, treeCursor) { + this.languageLayer = languageLayer; // The iterator is always positioned at either the start or the end of some node // in the syntax tree. - this.atEnd = false - this.treeCursor = treeCursor + this.atEnd = false; + this.treeCursor = treeCursor; // In order to determine which selectors match its current node, the iterator maintains // a list of the current node's ancestors. Because the selectors can use the `:nth-child` // pseudo-class, each node's child index is also stored. - this.containingNodeTypes = [] - this.containingNodeChildIndices = [] - this.containingNodeEndIndices = [] + this.containingNodeTypes = []; + this.containingNodeChildIndices = []; + this.containingNodeEndIndices = []; // At any given position, the iterator exposes the list of class names that should be // *ended* at its current position and the list of class names that should be *started* // at its current position. - this.closeTags = [] - this.openTags = [] + this.closeTags = []; + this.openTags = []; } - seek (targetIndex, containingTags, containingTagStartIndices) { + seek(targetIndex, containingTags, containingTagStartIndices) { while (this.treeCursor.gotoParent()) {} - this.done = false - this.atEnd = true - this.closeTags.length = 0 - this.openTags.length = 0 - this.containingNodeTypes.length = 0 - this.containingNodeChildIndices.length = 0 - this.containingNodeEndIndices.length = 0 + this.done = false; + this.atEnd = true; + this.closeTags.length = 0; + this.openTags.length = 0; + this.containingNodeTypes.length = 0; + this.containingNodeChildIndices.length = 0; + this.containingNodeEndIndices.length = 0; - const containingTagEndIndices = [] + const containingTagEndIndices = []; if (targetIndex >= this.treeCursor.endIndex) { - this.done = true - return + this.done = true; + return; } - let childIndex = -1 + let childIndex = -1; for (;;) { - this.containingNodeTypes.push(this.treeCursor.nodeType) - this.containingNodeChildIndices.push(childIndex) - this.containingNodeEndIndices.push(this.treeCursor.endIndex) + this.containingNodeTypes.push(this.treeCursor.nodeType); + this.containingNodeChildIndices.push(childIndex); + this.containingNodeEndIndices.push(this.treeCursor.endIndex); - const scopeId = this._currentScopeId() + const scopeId = this._currentScopeId(); if (scopeId) { if (this.treeCursor.startIndex < targetIndex) { insertContainingTag( - scopeId, this.treeCursor.startIndex, - containingTags, containingTagStartIndices - ) - containingTagEndIndices.push(this.treeCursor.endIndex) + scopeId, + this.treeCursor.startIndex, + containingTags, + containingTagStartIndices + ); + containingTagEndIndices.push(this.treeCursor.endIndex); } else { - this.atEnd = false - this.openTags.push(scopeId) - this._moveDown() - break + this.atEnd = false; + this.openTags.push(scopeId); + this._moveDown(); + break; } } - childIndex = this.treeCursor.gotoFirstChildForIndex(targetIndex) - if (childIndex === null) break - if (this.treeCursor.startIndex >= targetIndex) this.atEnd = false + childIndex = this.treeCursor.gotoFirstChildForIndex(targetIndex); + if (childIndex === null) break; + if (this.treeCursor.startIndex >= targetIndex) this.atEnd = false; } if (this.atEnd) { - const currentIndex = this.treeCursor.endIndex - for (let i = 0, {length} = containingTags; i < length; i++) { + const currentIndex = this.treeCursor.endIndex; + for (let i = 0, { length } = containingTags; i < length; i++) { if (containingTagEndIndices[i] === currentIndex) { - this.closeTags.push(containingTags[i]) + this.closeTags.push(containingTags[i]); } } } - return containingTags + return containingTags; } - moveToSuccessor () { - this.closeTags.length = 0 - this.openTags.length = 0 + moveToSuccessor() { + this.closeTags.length = 0; + this.openTags.length = 0; while (!this.done && !this.closeTags.length && !this.openTags.length) { if (this.atEnd) { if (this._moveRight()) { - const scopeId = this._currentScopeId() - if (scopeId) this.openTags.push(scopeId) - this.atEnd = false - this._moveDown() + const scopeId = this._currentScopeId(); + if (scopeId) this.openTags.push(scopeId); + this.atEnd = false; + this._moveDown(); } else if (this._moveUp(true)) { - this.atEnd = true + this.atEnd = true; } else { - this.done = true + this.done = true; } } else if (!this._moveDown()) { - const scopeId = this._currentScopeId() - if (scopeId) this.closeTags.push(scopeId) - this.atEnd = true - this._moveUp(false) + const scopeId = this._currentScopeId(); + if (scopeId) this.closeTags.push(scopeId); + this.atEnd = true; + this._moveUp(false); } } } - getPosition () { + getPosition() { if (this.done) { - return Point.INFINITY + return Point.INFINITY; } else if (this.atEnd) { - return this.treeCursor.endPosition + return this.treeCursor.endPosition; } else { - return this.treeCursor.startPosition + return this.treeCursor.startPosition; } } - getIndex () { + getIndex() { if (this.done) { - return Infinity + return Infinity; } else if (this.atEnd) { - return this.treeCursor.endIndex + return this.treeCursor.endIndex; } else { - return this.treeCursor.startIndex + return this.treeCursor.startIndex; } } - getCloseScopeIds () { - return this.closeTags.slice() + getCloseScopeIds() { + return this.closeTags.slice(); } - getOpenScopeIds () { - return this.openTags.slice() + getOpenScopeIds() { + return this.openTags.slice(); } // Private methods - _moveUp (atLastChild) { - let result = false - const {endIndex} = this.treeCursor - let depth = this.containingNodeEndIndices.length + _moveUp(atLastChild) { + let result = false; + const { endIndex } = this.treeCursor; + let depth = this.containingNodeEndIndices.length; // The iterator should not move up until it has visited all of the children of this node. - while (depth > 1 && (atLastChild || this.containingNodeEndIndices[depth - 2] === endIndex)) { - atLastChild = false - result = true - this.treeCursor.gotoParent() - this.containingNodeTypes.pop() - this.containingNodeChildIndices.pop() - this.containingNodeEndIndices.pop() - --depth - const scopeId = this._currentScopeId() - if (scopeId) this.closeTags.push(scopeId) + while ( + depth > 1 && + (atLastChild || this.containingNodeEndIndices[depth - 2] === endIndex) + ) { + atLastChild = false; + result = true; + this.treeCursor.gotoParent(); + this.containingNodeTypes.pop(); + this.containingNodeChildIndices.pop(); + this.containingNodeEndIndices.pop(); + --depth; + const scopeId = this._currentScopeId(); + if (scopeId) this.closeTags.push(scopeId); } - return result + return result; } - _moveDown () { - let result = false - const {startIndex} = this.treeCursor + _moveDown() { + let result = false; + const { startIndex } = this.treeCursor; // Once the iterator has found a scope boundary, it needs to stay at the same // position, so it should not move down if the first child node starts later than the // current node. while (this.treeCursor.gotoFirstChild()) { - if ((this.closeTags.length || this.openTags.length) && - this.treeCursor.startIndex > startIndex) { - this.treeCursor.gotoParent() - break + if ( + (this.closeTags.length || this.openTags.length) && + this.treeCursor.startIndex > startIndex + ) { + this.treeCursor.gotoParent(); + break; } - result = true - this.containingNodeTypes.push(this.treeCursor.nodeType) - this.containingNodeChildIndices.push(0) - this.containingNodeEndIndices.push(this.treeCursor.endIndex) + result = true; + this.containingNodeTypes.push(this.treeCursor.nodeType); + this.containingNodeChildIndices.push(0); + this.containingNodeEndIndices.push(this.treeCursor.endIndex); - const scopeId = this._currentScopeId() - if (scopeId) this.openTags.push(scopeId) + const scopeId = this._currentScopeId(); + if (scopeId) this.openTags.push(scopeId); } - return result + return result; } - _moveRight () { + _moveRight() { if (this.treeCursor.gotoNextSibling()) { - const depth = this.containingNodeTypes.length - this.containingNodeTypes[depth - 1] = this.treeCursor.nodeType - this.containingNodeChildIndices[depth - 1]++ - this.containingNodeEndIndices[depth - 1] = this.treeCursor.endIndex - return true + const depth = this.containingNodeTypes.length; + this.containingNodeTypes[depth - 1] = this.treeCursor.nodeType; + this.containingNodeChildIndices[depth - 1]++; + this.containingNodeEndIndices[depth - 1] = this.treeCursor.endIndex; + return true; } } - _currentScopeId () { + _currentScopeId() { const value = this.languageLayer.grammar.scopeMap.get( this.containingNodeTypes, this.containingNodeChildIndices, this.treeCursor.nodeIsNamed - ) - const scopeName = applyLeafRules(value, this.treeCursor) + ); + const scopeName = applyLeafRules(value, this.treeCursor); if (scopeName) { - return this.languageLayer.languageMode.grammar.idForScope(scopeName) + return this.languageLayer.languageMode.grammar.idForScope(scopeName); } } } const applyLeafRules = (rules, cursor) => { - if (!rules || typeof rules === 'string') return rules + if (!rules || typeof rules === 'string') return rules; if (Array.isArray(rules)) { - for (let i = 0, {length} = rules; i !== length; ++i) { - const result = applyLeafRules(rules[i], cursor) - if (result) return result + for (let i = 0, { length } = rules; i !== length; ++i) { + const result = applyLeafRules(rules[i], cursor); + if (result) return result; } - return undefined + return undefined; } if (typeof rules === 'object') { if (rules.exact) { return cursor.nodeText === rules.exact ? applyLeafRules(rules.scopes, cursor) - : undefined + : undefined; } if (rules.match) { return rules.match.test(cursor.nodeText) ? applyLeafRules(rules.scopes, cursor) - : undefined + : undefined; } } -} +}; class NodeCursorAdaptor { - get nodeText () { - return this.node.text + get nodeText() { + return this.node.text; } } class NullHighlightIterator { - seek () { return [] } - moveToSuccessor () {} - getIndex () { return Infinity } - getPosition () { return Point.INFINITY } - getOpenScopeIds () { return [] } - getCloseScopeIds () { return [] } + seek() { + return []; + } + moveToSuccessor() {} + getIndex() { + return Infinity; + } + getPosition() { + return Point.INFINITY; + } + getOpenScopeIds() { + return []; + } + getCloseScopeIds() { + return []; + } } class NodeRangeSet { - constructor (previous, nodes, newlinesBetween) { - this.previous = previous - this.nodes = nodes - this.newlinesBetween = newlinesBetween + constructor(previous, nodes, newlinesBetween) { + this.previous = previous; + this.nodes = nodes; + this.newlinesBetween = newlinesBetween; } - getRanges (buffer) { - const previousRanges = this.previous && this.previous.getRanges(buffer) - const result = [] + getRanges(buffer) { + const previousRanges = this.previous && this.previous.getRanges(buffer); + const result = []; for (const node of this.nodes) { - let position = node.startPosition - let index = node.startIndex + let position = node.startPosition; + let index = node.startIndex; for (const child of node.children) { - const nextIndex = child.startIndex + const nextIndex = child.startIndex; if (nextIndex > index) { this._pushRange(buffer, previousRanges, result, { startIndex: index, endIndex: nextIndex, startPosition: position, endPosition: child.startPosition - }) + }); } - position = child.endPosition - index = child.endIndex + position = child.endPosition; + index = child.endIndex; } if (node.endIndex > index) { @@ -1134,60 +1277,72 @@ class NodeRangeSet { endIndex: node.endIndex, startPosition: position, endPosition: node.endPosition - }) + }); } } - return result + return result; } - _pushRange (buffer, previousRanges, newRanges, newRange) { + _pushRange(buffer, previousRanges, newRanges, newRange) { if (!previousRanges) { if (this.newlinesBetween) { - const {startIndex, startPosition} = newRange - this._ensureNewline(buffer, newRanges, startIndex, startPosition) + const { startIndex, startPosition } = newRange; + this._ensureNewline(buffer, newRanges, startIndex, startPosition); } - newRanges.push(newRange) - return + newRanges.push(newRange); + return; } for (const previousRange of previousRanges) { - if (previousRange.endIndex <= newRange.startIndex) continue - if (previousRange.startIndex >= newRange.endIndex) break - const startIndex = Math.max(previousRange.startIndex, newRange.startIndex) - const endIndex = Math.min(previousRange.endIndex, newRange.endIndex) - const startPosition = Point.max(previousRange.startPosition, newRange.startPosition) - const endPosition = Point.min(previousRange.endPosition, newRange.endPosition) + if (previousRange.endIndex <= newRange.startIndex) continue; + if (previousRange.startIndex >= newRange.endIndex) break; + const startIndex = Math.max( + previousRange.startIndex, + newRange.startIndex + ); + const endIndex = Math.min(previousRange.endIndex, newRange.endIndex); + const startPosition = Point.max( + previousRange.startPosition, + newRange.startPosition + ); + const endPosition = Point.min( + previousRange.endPosition, + newRange.endPosition + ); if (this.newlinesBetween) { - this._ensureNewline(buffer, newRanges, startIndex, startPosition) + this._ensureNewline(buffer, newRanges, startIndex, startPosition); } - newRanges.push({startIndex, endIndex, startPosition, endPosition}) + newRanges.push({ startIndex, endIndex, startPosition, endPosition }); } } // For injection points with `newlinesBetween` enabled, ensure that a // newline is included between each disjoint range. - _ensureNewline (buffer, newRanges, startIndex, startPosition) { - const lastRange = newRanges[newRanges.length - 1] + _ensureNewline(buffer, newRanges, startIndex, startPosition) { + const lastRange = newRanges[newRanges.length - 1]; if (lastRange && lastRange.endPosition.row < startPosition.row) { newRanges.push({ - startPosition: new Point(startPosition.row - 1, buffer.lineLengthForRow(startPosition.row - 1)), + startPosition: new Point( + startPosition.row - 1, + buffer.lineLengthForRow(startPosition.row - 1) + ), endPosition: new Point(startPosition.row, 0), startIndex: startIndex - startPosition.column - 1, endIndex: startIndex - startPosition.column - }) + }); } } } -function insertContainingTag (tag, index, tags, indices) { - const i = indices.findIndex(existingIndex => existingIndex > index) +function insertContainingTag(tag, index, tags, indices) { + const i = indices.findIndex(existingIndex => existingIndex > index); if (i === -1) { - tags.push(tag) - indices.push(index) + tags.push(tag); + indices.push(index); } else { - tags.splice(i, 0, tag) - indices.splice(i, 0, index) + tags.splice(i, 0, tag); + indices.splice(i, 0, index); } } @@ -1196,39 +1351,41 @@ function insertContainingTag (tag, index, tags, indices) { // // * `mouse` {Range} // * `house` {Range} -function rangeIsSmaller (mouse, house) { - if (!house) return true - const mvec = vecFromRange(mouse) - const hvec = vecFromRange(house) - return Point.min(mvec, hvec) === mvec +function rangeIsSmaller(mouse, house) { + if (!house) return true; + const mvec = vecFromRange(mouse); + const hvec = vecFromRange(house); + return Point.min(mvec, hvec) === mvec; } -function vecFromRange ({start, end}) { - return end.translate(start.negate()) +function vecFromRange({ start, end }) { + return end.translate(start.negate()); } -function rangeForNode (node) { - return new Range(node.startPosition, node.endPosition) +function rangeForNode(node) { + return new Range(node.startPosition, node.endPosition); } -function nodeContainsIndices (node, start, end) { - if (node.startIndex < start) return node.endIndex >= end - if (node.startIndex === start) return node.endIndex > end - return false +function nodeContainsIndices(node, start, end) { + if (node.startIndex < start) return node.endIndex >= end; + if (node.startIndex === start) return node.endIndex > end; + return false; } -function nodeIsSmaller (left, right) { - if (!left) return false - if (!right) return true - return left.endIndex - left.startIndex < right.endIndex - right.startIndex +function nodeIsSmaller(left, right) { + if (!left) return false; + if (!right) return true; + return left.endIndex - left.startIndex < right.endIndex - right.startIndex; } -function last (array) { - return array[array.length - 1] +function last(array) { + return array[array.length - 1]; } -function hasMatchingFoldSpec (specs, node) { - return specs.some(({type, named}) => type === node.type && named === node.isNamed) +function hasMatchingFoldSpec(specs, node) { + return specs.some( + ({ type, named }) => type === node.type && named === node.isNamed + ); } // TODO: Remove this once TreeSitterLanguageMode implements its own auto-indent system. @@ -1241,10 +1398,11 @@ function hasMatchingFoldSpec (specs, node) { 'regexForPattern', 'getNonWordCharacters' ].forEach(methodName => { - TreeSitterLanguageMode.prototype[methodName] = TextMateLanguageMode.prototype[methodName] -}) + TreeSitterLanguageMode.prototype[methodName] = + TextMateLanguageMode.prototype[methodName]; +}); -TreeSitterLanguageMode.LanguageLayer = LanguageLayer -TreeSitterLanguageMode.prototype.syncTimeoutMicros = 1000 +TreeSitterLanguageMode.LanguageLayer = LanguageLayer; +TreeSitterLanguageMode.prototype.syncTimeoutMicros = 1000; -module.exports = TreeSitterLanguageMode +module.exports = TreeSitterLanguageMode; diff --git a/src/typescript.js b/src/typescript.js index fb2f83670..f10410ea1 100644 --- a/src/typescript.js +++ b/src/typescript.js @@ -1,26 +1,29 @@ -'use strict' +'use strict'; -var _ = require('underscore-plus') -var crypto = require('crypto') -var path = require('path') +var _ = require('underscore-plus'); +var crypto = require('crypto'); +var path = require('path'); var defaultOptions = { target: 1, module: 'commonjs', sourceMap: true -} +}; -var TypeScriptSimple = null -var typescriptVersionDir = null +var TypeScriptSimple = null; +var typescriptVersionDir = null; -exports.shouldCompile = function () { - return true -} +exports.shouldCompile = function() { + return true; +}; -exports.getCachePath = function (sourceCode) { +exports.getCachePath = function(sourceCode) { if (typescriptVersionDir == null) { - var version = require('typescript-simple/package.json').version - typescriptVersionDir = path.join('ts', createVersionAndOptionsDigest(version, defaultOptions)) + var version = require('typescript-simple/package.json').version; + typescriptVersionDir = path.join( + 'ts', + createVersionAndOptionsDigest(version, defaultOptions) + ); } return path.join( @@ -29,23 +32,23 @@ exports.getCachePath = function (sourceCode) { .createHash('sha1') .update(sourceCode, 'utf8') .digest('hex') + '.js' - ) -} + ); +}; -exports.compile = function (sourceCode, filePath) { +exports.compile = function(sourceCode, filePath) { if (!TypeScriptSimple) { - TypeScriptSimple = require('typescript-simple').TypeScriptSimple + TypeScriptSimple = require('typescript-simple').TypeScriptSimple; } if (process.platform === 'win32') { - filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/') + filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/'); } - var options = _.defaults({filename: filePath}, defaultOptions) - return new TypeScriptSimple(options, false).compile(sourceCode, filePath) -} + var options = _.defaults({ filename: filePath }, defaultOptions); + return new TypeScriptSimple(options, false).compile(sourceCode, filePath); +}; -function createVersionAndOptionsDigest (version, options) { +function createVersionAndOptionsDigest(version, options) { return crypto .createHash('sha1') .update('typescript', 'utf8') @@ -53,5 +56,5 @@ function createVersionAndOptionsDigest (version, options) { .update(version, 'utf8') .update('\0', 'utf8') .update(JSON.stringify(options), 'utf8') - .digest('hex') + .digest('hex'); } diff --git a/src/update-process-env.js b/src/update-process-env.js index a7e2622dd..f4dd15851 100644 --- a/src/update-process-env.js +++ b/src/update-process-env.js @@ -1,123 +1,135 @@ -const fs = require('fs') -const childProcess = require('child_process') +const fs = require('fs'); +const childProcess = require('child_process'); const ENVIRONMENT_VARIABLES_TO_PRESERVE = new Set([ 'NODE_ENV', 'NODE_PATH', 'ATOM_HOME', 'ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT' -]) +]); -const PLATFORMS_KNOWN_TO_WORK = new Set([ - 'darwin', - 'linux' -]) +const PLATFORMS_KNOWN_TO_WORK = new Set(['darwin', 'linux']); -async function updateProcessEnv (launchEnv) { - let envToAssign +async function updateProcessEnv(launchEnv) { + let envToAssign; if (launchEnv) { if (shouldGetEnvFromShell(launchEnv)) { - envToAssign = await getEnvFromShell(launchEnv) + envToAssign = await getEnvFromShell(launchEnv); } else if (launchEnv.PWD || launchEnv.PROMPT || launchEnv.PSModulePath) { - envToAssign = launchEnv + envToAssign = launchEnv; } } if (envToAssign) { for (let key in process.env) { if (!ENVIRONMENT_VARIABLES_TO_PRESERVE.has(key)) { - delete process.env[key] + delete process.env[key]; } } for (let key in envToAssign) { - if (!ENVIRONMENT_VARIABLES_TO_PRESERVE.has(key) || (!process.env[key] && envToAssign[key])) { - process.env[key] = envToAssign[key] + if ( + !ENVIRONMENT_VARIABLES_TO_PRESERVE.has(key) || + (!process.env[key] && envToAssign[key]) + ) { + process.env[key] = envToAssign[key]; } } if (envToAssign.ATOM_HOME && fs.existsSync(envToAssign.ATOM_HOME)) { - process.env.ATOM_HOME = envToAssign.ATOM_HOME + process.env.ATOM_HOME = envToAssign.ATOM_HOME; } } } -function shouldGetEnvFromShell (env) { +function shouldGetEnvFromShell(env) { if (!PLATFORMS_KNOWN_TO_WORK.has(process.platform)) { - return false + return false; } if (!env || !env.SHELL || env.SHELL.trim() === '') { - return false + return false; } - const disableSellingOut = env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT || process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT + const disableSellingOut = + env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT || + process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT; if (disableSellingOut === 'true') { - return false + return false; } - return true + return true; } -async function getEnvFromShell (env) { - let {stdout, error} = await new Promise((resolve) => { - let child - let error - let stdout = '' - let done = false +async function getEnvFromShell(env) { + let { stdout, error } = await new Promise(resolve => { + let child; + let error; + let stdout = ''; + let done = false; const cleanup = () => { if (!done && child) { - child.kill() - done = true + child.kill(); + done = true; } - } - process.once('exit', cleanup) + }; + process.once('exit', cleanup); setTimeout(() => { - cleanup() - }, 5000) - child = childProcess.spawn(env.SHELL, ['-ilc', 'command env'], {encoding: 'utf8', detached: true, stdio: ['ignore', 'pipe', process.stderr]}) - const buffers = [] - child.on('error', (e) => { - done = true - error = e - }) - child.stdout.on('data', (data) => { - buffers.push(data) - }) + cleanup(); + }, 5000); + child = childProcess.spawn(env.SHELL, ['-ilc', 'command env'], { + encoding: 'utf8', + detached: true, + stdio: ['ignore', 'pipe', process.stderr] + }); + const buffers = []; + child.on('error', e => { + done = true; + error = e; + }); + child.stdout.on('data', data => { + buffers.push(data); + }); child.on('close', (code, signal) => { - done = true - process.removeListener('exit', cleanup) + done = true; + process.removeListener('exit', cleanup); if (buffers.length) { - stdout = Buffer.concat(buffers).toString('utf8') + stdout = Buffer.concat(buffers).toString('utf8'); } - resolve({stdout, error}) - }) - }) + resolve({ stdout, error }); + }); + }); if (error) { if (error.handle) { - error.handle() + error.handle(); } - console.log('warning: ' + env.SHELL + ' -ilc "command env" failed with signal (' + error.signal + ')') - console.log(error) + console.log( + 'warning: ' + + env.SHELL + + ' -ilc "command env" failed with signal (' + + error.signal + + ')' + ); + console.log(error); } if (!stdout || stdout.trim() === '') { - return null + return null; } - let result = {} + let result = {}; for (let line of stdout.split('\n')) { if (line.includes('=')) { - let components = line.split('=') - let key = components.shift() - let value = components.join('=') - result[key] = value + let components = line.split('='); + let key = components.shift(); + let value = components.join('='); + result[key] = value; } } - return result + return result; } -module.exports = {updateProcessEnv, shouldGetEnvFromShell} +module.exports = { updateProcessEnv, shouldGetEnvFromShell }; diff --git a/src/uri-handler-registry.js b/src/uri-handler-registry.js index 2b36ce906..73dbd946f 100644 --- a/src/uri-handler-registry.js +++ b/src/uri-handler-registry.js @@ -1,5 +1,5 @@ -const url = require('url') -const {Emitter, Disposable} = require('event-kit') +const url = require('url'); +const { Emitter, Disposable } = require('event-kit'); // Private: Associates listener functions with URIs from outside the application. // @@ -62,68 +62,73 @@ const {Emitter, Disposable} = require('event-kit') // } // } // ``` -module.exports = -class URIHandlerRegistry { - constructor (maxHistoryLength = 50) { - this.registrations = new Map() - this.history = [] - this.maxHistoryLength = maxHistoryLength - this._id = 0 +module.exports = class URIHandlerRegistry { + constructor(maxHistoryLength = 50) { + this.registrations = new Map(); + this.history = []; + this.maxHistoryLength = maxHistoryLength; + this._id = 0; - this.emitter = new Emitter() + this.emitter = new Emitter(); } - registerHostHandler (host, callback) { + registerHostHandler(host, callback) { if (typeof callback !== 'function') { - throw new Error('Cannot register a URI host handler with a non-function callback') + throw new Error( + 'Cannot register a URI host handler with a non-function callback' + ); } if (this.registrations.has(host)) { - throw new Error(`There is already a URI host handler for the host ${host}`) + throw new Error( + `There is already a URI host handler for the host ${host}` + ); } else { - this.registrations.set(host, callback) + this.registrations.set(host, callback); } return new Disposable(() => { - this.registrations.delete(host) - }) + this.registrations.delete(host); + }); } - async handleURI (uri) { - const parsed = url.parse(uri, true) - const {protocol, slashes, auth, port, host} = parsed + async handleURI(uri) { + const parsed = url.parse(uri, true); + const { protocol, slashes, auth, port, host } = parsed; if (protocol !== 'atom:' || slashes !== true || auth || port) { - throw new Error(`URIHandlerRegistry#handleURI asked to handle an invalid URI: ${uri}`) + throw new Error( + `URIHandlerRegistry#handleURI asked to handle an invalid URI: ${uri}` + ); } - const registration = this.registrations.get(host) - const historyEntry = {id: ++this._id, uri: uri, handled: false, host} + const registration = this.registrations.get(host); + const historyEntry = { id: ++this._id, uri: uri, handled: false, host }; try { if (registration) { - historyEntry.handled = true - await registration(parsed, uri) + historyEntry.handled = true; + await registration(parsed, uri); } } finally { - this.history.unshift(historyEntry) + this.history.unshift(historyEntry); if (this.history.length > this.maxHistoryLength) { - this.history.length = this.maxHistoryLength + this.history.length = this.maxHistoryLength; } - this.emitter.emit('history-change') + this.emitter.emit('history-change'); } } - getRecentlyHandledURIs () { - return this.history + getRecentlyHandledURIs() { + return this.history; } - onHistoryChange (cb) { - return this.emitter.on('history-change', cb) + onHistoryChange(cb) { + return this.emitter.on('history-change', cb); } - destroy () { - this.emitter.dispose() - this.registrations = new Map() - this.history = [] - this._id = 0 + destroy() { + this.emitter.dispose(); + this.registrations = new Map(); + this.history = []; + this._id = 0; } -} +}; diff --git a/src/view-registry.js b/src/view-registry.js index 87bf8620f..5f70d7d37 100644 --- a/src/view-registry.js +++ b/src/view-registry.js @@ -1,7 +1,7 @@ -const Grim = require('grim') -const {Disposable} = require('event-kit') +const Grim = require('grim'); +const { Disposable } = require('event-kit'); -const AnyConstructor = Symbol('any-constructor') +const AnyConstructor = Symbol('any-constructor'); // Essential: `ViewRegistry` handles the association between model and view // types in Atom. We call this association a View Provider. As in, for a given @@ -22,20 +22,19 @@ const AnyConstructor = Symbol('any-constructor') // an ideal tool for implementing views in Atom. // // You can access the `ViewRegistry` object via `atom.views`. -module.exports = -class ViewRegistry { - constructor (atomEnvironment) { - this.animationFrameRequest = null - this.documentReadInProgress = false - this.performDocumentUpdate = this.performDocumentUpdate.bind(this) - this.atomEnvironment = atomEnvironment - this.clear() +module.exports = class ViewRegistry { + constructor(atomEnvironment) { + this.animationFrameRequest = null; + this.documentReadInProgress = false; + this.performDocumentUpdate = this.performDocumentUpdate.bind(this); + this.atomEnvironment = atomEnvironment; + this.clear(); } - clear () { - this.views = new WeakMap() - this.providers = [] - this.clearDocumentRequests() + clear() { + this.views = new WeakMap(); + this.providers = []; + this.clearDocumentRequests(); } // Essential: Add a provider that will be used to construct views in the @@ -66,32 +65,37 @@ class ViewRegistry { // // Returns a {Disposable} on which `.dispose()` can be called to remove the // added provider. - addViewProvider (modelConstructor, createView) { - let provider + addViewProvider(modelConstructor, createView) { + let provider; if (arguments.length === 1) { switch (typeof modelConstructor) { case 'function': - provider = {createView: modelConstructor, modelConstructor: AnyConstructor} - break + provider = { + createView: modelConstructor, + modelConstructor: AnyConstructor + }; + break; case 'object': - Grim.deprecate('atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.') - provider = modelConstructor - break + Grim.deprecate( + 'atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.' + ); + provider = modelConstructor; + break; default: - throw new TypeError('Arguments to addViewProvider must be functions') + throw new TypeError('Arguments to addViewProvider must be functions'); } } else { - provider = {modelConstructor, createView} + provider = { modelConstructor, createView }; } - this.providers.push(provider) + this.providers.push(provider); return new Disposable(() => { - this.providers = this.providers.filter(p => p !== provider) - }) + this.providers = this.providers.filter(p => p !== provider); + }); } - getViewProviderCount () { - return this.providers.length + getViewProviderCount() { + return this.providers.length; } // Essential: Get the view associated with an object in the workspace. @@ -119,141 +123,165 @@ class ViewRegistry { // If no associated view is returned by the sequence an error is thrown. // // Returns a DOM element. - getView (object) { - if (object == null) { return } - - let view = this.views.get(object) - if (!view) { - view = this.createView(object) - this.views.set(object, view) + getView(object) { + if (object == null) { + return; } - return view + + let view = this.views.get(object); + if (!view) { + view = this.createView(object); + this.views.set(object, view); + } + return view; } - createView (object) { - if (object instanceof HTMLElement) { return object } + createView(object) { + if (object instanceof HTMLElement) { + return object; + } - let element - if (object && (typeof object.getElement === 'function')) { - element = object.getElement() + let element; + if (object && typeof object.getElement === 'function') { + element = object.getElement(); if (element instanceof HTMLElement) { - return element + return element; } } if (object && object.element instanceof HTMLElement) { - return object.element + return object.element; } if (object && object.jquery) { - return object[0] + return object[0]; } for (let provider of this.providers) { if (provider.modelConstructor === AnyConstructor) { - element = provider.createView(object, this.atomEnvironment) - if (element) { return element } - continue + element = provider.createView(object, this.atomEnvironment); + if (element) { + return element; + } + continue; } if (object instanceof provider.modelConstructor) { - element = provider.createView && provider.createView(object, this.atomEnvironment) - if (element) { return element } + element = + provider.createView && + provider.createView(object, this.atomEnvironment); + if (element) { + return element; + } - let ViewConstructor = provider.viewConstructor + let ViewConstructor = provider.viewConstructor; if (ViewConstructor) { - element = new ViewConstructor() + element = new ViewConstructor(); if (element.initialize) { - element.initialize(object) + element.initialize(object); } else if (element.setModel) { - element.setModel(object) + element.setModel(object); } - return element + return element; } } } if (object && object.getViewClass) { - let ViewConstructor = object.getViewClass() + let ViewConstructor = object.getViewClass(); if (ViewConstructor) { - const view = new ViewConstructor(object) - return view[0] + const view = new ViewConstructor(object); + return view[0]; } } - throw new Error(`Can't create a view for ${object.constructor.name} instance. Please register a view provider.`) + throw new Error( + `Can't create a view for ${ + object.constructor.name + } instance. Please register a view provider.` + ); } - updateDocument (fn) { - this.documentWriters.push(fn) - if (!this.documentReadInProgress) { this.requestDocumentUpdate() } + updateDocument(fn) { + this.documentWriters.push(fn); + if (!this.documentReadInProgress) { + this.requestDocumentUpdate(); + } return new Disposable(() => { - this.documentWriters = this.documentWriters.filter(writer => writer !== fn) - }) + this.documentWriters = this.documentWriters.filter( + writer => writer !== fn + ); + }); } - readDocument (fn) { - this.documentReaders.push(fn) - this.requestDocumentUpdate() + readDocument(fn) { + this.documentReaders.push(fn); + this.requestDocumentUpdate(); return new Disposable(() => { - this.documentReaders = this.documentReaders.filter(reader => reader !== fn) - }) + this.documentReaders = this.documentReaders.filter( + reader => reader !== fn + ); + }); } - getNextUpdatePromise () { + getNextUpdatePromise() { if (this.nextUpdatePromise == null) { this.nextUpdatePromise = new Promise(resolve => { - this.resolveNextUpdatePromise = resolve - }) + this.resolveNextUpdatePromise = resolve; + }); } - return this.nextUpdatePromise + return this.nextUpdatePromise; } - clearDocumentRequests () { - this.documentReaders = [] - this.documentWriters = [] - this.nextUpdatePromise = null - this.resolveNextUpdatePromise = null + clearDocumentRequests() { + this.documentReaders = []; + this.documentWriters = []; + this.nextUpdatePromise = null; + this.resolveNextUpdatePromise = null; if (this.animationFrameRequest != null) { - cancelAnimationFrame(this.animationFrameRequest) - this.animationFrameRequest = null + cancelAnimationFrame(this.animationFrameRequest); + this.animationFrameRequest = null; } } - requestDocumentUpdate () { + requestDocumentUpdate() { if (this.animationFrameRequest == null) { - this.animationFrameRequest = requestAnimationFrame(this.performDocumentUpdate) + this.animationFrameRequest = requestAnimationFrame( + this.performDocumentUpdate + ); } } - performDocumentUpdate () { - const { resolveNextUpdatePromise } = this - this.animationFrameRequest = null - this.nextUpdatePromise = null - this.resolveNextUpdatePromise = null + performDocumentUpdate() { + const { resolveNextUpdatePromise } = this; + this.animationFrameRequest = null; + this.nextUpdatePromise = null; + this.resolveNextUpdatePromise = null; - var writer = this.documentWriters.shift() + var writer = this.documentWriters.shift(); while (writer) { - writer() - writer = this.documentWriters.shift() + writer(); + writer = this.documentWriters.shift(); } - var reader = this.documentReaders.shift() - this.documentReadInProgress = true + var reader = this.documentReaders.shift(); + this.documentReadInProgress = true; while (reader) { - reader() - reader = this.documentReaders.shift() + reader(); + reader = this.documentReaders.shift(); } - this.documentReadInProgress = false + this.documentReadInProgress = false; // process updates requested as a result of reads - writer = this.documentWriters.shift() + writer = this.documentWriters.shift(); while (writer) { - writer() - writer = this.documentWriters.shift() + writer(); + writer = this.documentWriters.shift(); } - if (resolveNextUpdatePromise) { resolveNextUpdatePromise() } + if (resolveNextUpdatePromise) { + resolveNextUpdatePromise(); + } } -} +}; diff --git a/src/window-event-handler.js b/src/window-event-handler.js index 6d927e76e..5f7ea9a4f 100644 --- a/src/window-event-handler.js +++ b/src/window-event-handler.js @@ -1,263 +1,319 @@ -const {Disposable, CompositeDisposable} = require('event-kit') -const listen = require('./delegated-listener') +const { Disposable, CompositeDisposable } = require('event-kit'); +const listen = require('./delegated-listener'); // Handles low-level events related to the `window`. -module.exports = -class WindowEventHandler { - constructor ({atomEnvironment, applicationDelegate}) { - this.handleDocumentKeyEvent = this.handleDocumentKeyEvent.bind(this) - this.handleFocusNext = this.handleFocusNext.bind(this) - this.handleFocusPrevious = this.handleFocusPrevious.bind(this) - this.handleWindowBlur = this.handleWindowBlur.bind(this) - this.handleWindowResize = this.handleWindowResize.bind(this) - this.handleEnterFullScreen = this.handleEnterFullScreen.bind(this) - this.handleLeaveFullScreen = this.handleLeaveFullScreen.bind(this) - this.handleWindowBeforeunload = this.handleWindowBeforeunload.bind(this) - this.handleWindowToggleFullScreen = this.handleWindowToggleFullScreen.bind(this) - this.handleWindowClose = this.handleWindowClose.bind(this) - this.handleWindowReload = this.handleWindowReload.bind(this) - this.handleWindowToggleDevTools = this.handleWindowToggleDevTools.bind(this) - this.handleWindowToggleMenuBar = this.handleWindowToggleMenuBar.bind(this) - this.handleLinkClick = this.handleLinkClick.bind(this) - this.handleDocumentContextmenu = this.handleDocumentContextmenu.bind(this) - this.atomEnvironment = atomEnvironment - this.applicationDelegate = applicationDelegate - this.reloadRequested = false - this.subscriptions = new CompositeDisposable() +module.exports = class WindowEventHandler { + constructor({ atomEnvironment, applicationDelegate }) { + this.handleDocumentKeyEvent = this.handleDocumentKeyEvent.bind(this); + this.handleFocusNext = this.handleFocusNext.bind(this); + this.handleFocusPrevious = this.handleFocusPrevious.bind(this); + this.handleWindowBlur = this.handleWindowBlur.bind(this); + this.handleWindowResize = this.handleWindowResize.bind(this); + this.handleEnterFullScreen = this.handleEnterFullScreen.bind(this); + this.handleLeaveFullScreen = this.handleLeaveFullScreen.bind(this); + this.handleWindowBeforeunload = this.handleWindowBeforeunload.bind(this); + this.handleWindowToggleFullScreen = this.handleWindowToggleFullScreen.bind( + this + ); + this.handleWindowClose = this.handleWindowClose.bind(this); + this.handleWindowReload = this.handleWindowReload.bind(this); + this.handleWindowToggleDevTools = this.handleWindowToggleDevTools.bind( + this + ); + this.handleWindowToggleMenuBar = this.handleWindowToggleMenuBar.bind(this); + this.handleLinkClick = this.handleLinkClick.bind(this); + this.handleDocumentContextmenu = this.handleDocumentContextmenu.bind(this); + this.atomEnvironment = atomEnvironment; + this.applicationDelegate = applicationDelegate; + this.reloadRequested = false; + this.subscriptions = new CompositeDisposable(); - this.handleNativeKeybindings() + this.handleNativeKeybindings(); } - initialize (window, document) { - this.window = window - this.document = document - this.subscriptions.add(this.atomEnvironment.commands.add(this.window, { - 'window:toggle-full-screen': this.handleWindowToggleFullScreen, - 'window:close': this.handleWindowClose, - 'window:reload': this.handleWindowReload, - 'window:toggle-dev-tools': this.handleWindowToggleDevTools - })) + initialize(window, document) { + this.window = window; + this.document = document; + this.subscriptions.add( + this.atomEnvironment.commands.add(this.window, { + 'window:toggle-full-screen': this.handleWindowToggleFullScreen, + 'window:close': this.handleWindowClose, + 'window:reload': this.handleWindowReload, + 'window:toggle-dev-tools': this.handleWindowToggleDevTools + }) + ); if (['win32', 'linux'].includes(process.platform)) { - this.subscriptions.add(this.atomEnvironment.commands.add(this.window, - {'window:toggle-menu-bar': this.handleWindowToggleMenuBar}) - ) + this.subscriptions.add( + this.atomEnvironment.commands.add(this.window, { + 'window:toggle-menu-bar': this.handleWindowToggleMenuBar + }) + ); } - this.subscriptions.add(this.atomEnvironment.commands.add(this.document, { - 'core:focus-next': this.handleFocusNext, - 'core:focus-previous': this.handleFocusPrevious - })) + this.subscriptions.add( + this.atomEnvironment.commands.add(this.document, { + 'core:focus-next': this.handleFocusNext, + 'core:focus-previous': this.handleFocusPrevious + }) + ); - this.addEventListener(this.window, 'beforeunload', this.handleWindowBeforeunload) - this.addEventListener(this.window, 'focus', this.handleWindowFocus) - this.addEventListener(this.window, 'blur', this.handleWindowBlur) - this.addEventListener(this.window, 'resize', this.handleWindowResize) + this.addEventListener( + this.window, + 'beforeunload', + this.handleWindowBeforeunload + ); + this.addEventListener(this.window, 'focus', this.handleWindowFocus); + this.addEventListener(this.window, 'blur', this.handleWindowBlur); + this.addEventListener(this.window, 'resize', this.handleWindowResize); - this.addEventListener(this.document, 'keyup', this.handleDocumentKeyEvent) - this.addEventListener(this.document, 'keydown', this.handleDocumentKeyEvent) - this.addEventListener(this.document, 'drop', this.handleDocumentDrop) - this.addEventListener(this.document, 'dragover', this.handleDocumentDragover) - this.addEventListener(this.document, 'contextmenu', this.handleDocumentContextmenu) - this.subscriptions.add(listen(this.document, 'click', 'a', this.handleLinkClick)) - this.subscriptions.add(listen(this.document, 'submit', 'form', this.handleFormSubmit)) + this.addEventListener(this.document, 'keyup', this.handleDocumentKeyEvent); + this.addEventListener( + this.document, + 'keydown', + this.handleDocumentKeyEvent + ); + this.addEventListener(this.document, 'drop', this.handleDocumentDrop); + this.addEventListener( + this.document, + 'dragover', + this.handleDocumentDragover + ); + this.addEventListener( + this.document, + 'contextmenu', + this.handleDocumentContextmenu + ); + this.subscriptions.add( + listen(this.document, 'click', 'a', this.handleLinkClick) + ); + this.subscriptions.add( + listen(this.document, 'submit', 'form', this.handleFormSubmit) + ); - this.subscriptions.add(this.applicationDelegate.onDidEnterFullScreen(this.handleEnterFullScreen)) - this.subscriptions.add(this.applicationDelegate.onDidLeaveFullScreen(this.handleLeaveFullScreen)) + this.subscriptions.add( + this.applicationDelegate.onDidEnterFullScreen(this.handleEnterFullScreen) + ); + this.subscriptions.add( + this.applicationDelegate.onDidLeaveFullScreen(this.handleLeaveFullScreen) + ); } // Wire commands that should be handled by Chromium for elements with the // `.native-key-bindings` class. - handleNativeKeybindings () { + handleNativeKeybindings() { const bindCommandToAction = (command, action) => { this.subscriptions.add( this.atomEnvironment.commands.add( '.native-key-bindings', command, - event => this.applicationDelegate.getCurrentWindow().webContents[action](), + event => + this.applicationDelegate.getCurrentWindow().webContents[action](), false ) - ) - } + ); + }; - bindCommandToAction('core:copy', 'copy') - bindCommandToAction('core:paste', 'paste') - bindCommandToAction('core:undo', 'undo') - bindCommandToAction('core:redo', 'redo') - bindCommandToAction('core:select-all', 'selectAll') - bindCommandToAction('core:cut', 'cut') + bindCommandToAction('core:copy', 'copy'); + bindCommandToAction('core:paste', 'paste'); + bindCommandToAction('core:undo', 'undo'); + bindCommandToAction('core:redo', 'redo'); + bindCommandToAction('core:select-all', 'selectAll'); + bindCommandToAction('core:cut', 'cut'); } - unsubscribe () { - this.subscriptions.dispose() + unsubscribe() { + this.subscriptions.dispose(); } - on (target, eventName, handler) { - target.on(eventName, handler) - this.subscriptions.add(new Disposable(function () { - target.removeListener(eventName, handler) - })) + on(target, eventName, handler) { + target.on(eventName, handler); + this.subscriptions.add( + new Disposable(function() { + target.removeListener(eventName, handler); + }) + ); } - addEventListener (target, eventName, handler) { - target.addEventListener(eventName, handler) - this.subscriptions.add(new Disposable(function () { - target.removeEventListener(eventName, handler) - })) + addEventListener(target, eventName, handler) { + target.addEventListener(eventName, handler); + this.subscriptions.add( + new Disposable(function() { + target.removeEventListener(eventName, handler); + }) + ); } - handleDocumentKeyEvent (event) { - this.atomEnvironment.keymaps.handleKeyboardEvent(event) - event.stopImmediatePropagation() + handleDocumentKeyEvent(event) { + this.atomEnvironment.keymaps.handleKeyboardEvent(event); + event.stopImmediatePropagation(); } - handleDrop (event) { - event.preventDefault() - event.stopPropagation() + handleDrop(event) { + event.preventDefault(); + event.stopPropagation(); } - handleDragover (event) { - event.preventDefault() - event.stopPropagation() - event.dataTransfer.dropEffect = 'none' + handleDragover(event) { + event.preventDefault(); + event.stopPropagation(); + event.dataTransfer.dropEffect = 'none'; } - eachTabIndexedElement (callback) { + eachTabIndexedElement(callback) { for (let element of this.document.querySelectorAll('[tabindex]')) { - if (element.disabled) { continue } - if (!(element.tabIndex >= 0)) { continue } - callback(element, element.tabIndex) + if (element.disabled) { + continue; + } + if (!(element.tabIndex >= 0)) { + continue; + } + callback(element, element.tabIndex); } } - handleFocusNext () { - const focusedTabIndex = this.document.activeElement.tabIndex != null ? this.document.activeElement.tabIndex : -Infinity + handleFocusNext() { + const focusedTabIndex = + this.document.activeElement.tabIndex != null + ? this.document.activeElement.tabIndex + : -Infinity; - let nextElement = null - let nextTabIndex = Infinity - let lowestElement = null - let lowestTabIndex = Infinity - this.eachTabIndexedElement(function (element, tabIndex) { + let nextElement = null; + let nextTabIndex = Infinity; + let lowestElement = null; + let lowestTabIndex = Infinity; + this.eachTabIndexedElement(function(element, tabIndex) { if (tabIndex < lowestTabIndex) { - lowestTabIndex = tabIndex - lowestElement = element + lowestTabIndex = tabIndex; + lowestElement = element; } if (focusedTabIndex < tabIndex && tabIndex < nextTabIndex) { - nextTabIndex = tabIndex - nextElement = element + nextTabIndex = tabIndex; + nextElement = element; } - }) + }); if (nextElement != null) { - nextElement.focus() + nextElement.focus(); } else if (lowestElement != null) { - lowestElement.focus() + lowestElement.focus(); } } - handleFocusPrevious () { - const focusedTabIndex = this.document.activeElement.tabIndex != null ? this.document.activeElement.tabIndex : Infinity + handleFocusPrevious() { + const focusedTabIndex = + this.document.activeElement.tabIndex != null + ? this.document.activeElement.tabIndex + : Infinity; - let previousElement = null - let previousTabIndex = -Infinity - let highestElement = null - let highestTabIndex = -Infinity - this.eachTabIndexedElement(function (element, tabIndex) { + let previousElement = null; + let previousTabIndex = -Infinity; + let highestElement = null; + let highestTabIndex = -Infinity; + this.eachTabIndexedElement(function(element, tabIndex) { if (tabIndex > highestTabIndex) { - highestTabIndex = tabIndex - highestElement = element + highestTabIndex = tabIndex; + highestElement = element; } if (focusedTabIndex > tabIndex && tabIndex > previousTabIndex) { - previousTabIndex = tabIndex - previousElement = element + previousTabIndex = tabIndex; + previousElement = element; } - }) + }); if (previousElement != null) { - previousElement.focus() + previousElement.focus(); } else if (highestElement != null) { - highestElement.focus() + highestElement.focus(); } } - handleWindowFocus () { - this.document.body.classList.remove('is-blurred') + handleWindowFocus() { + this.document.body.classList.remove('is-blurred'); } - handleWindowBlur () { - this.document.body.classList.add('is-blurred') - this.atomEnvironment.storeWindowDimensions() + handleWindowBlur() { + this.document.body.classList.add('is-blurred'); + this.atomEnvironment.storeWindowDimensions(); } - handleWindowResize () { - this.atomEnvironment.storeWindowDimensions() + handleWindowResize() { + this.atomEnvironment.storeWindowDimensions(); } - handleEnterFullScreen () { - this.document.body.classList.add('fullscreen') + handleEnterFullScreen() { + this.document.body.classList.add('fullscreen'); } - handleLeaveFullScreen () { - this.document.body.classList.remove('fullscreen') + handleLeaveFullScreen() { + this.document.body.classList.remove('fullscreen'); } - handleWindowBeforeunload (event) { - if (!this.reloadRequested && !this.atomEnvironment.inSpecMode() && this.atomEnvironment.getCurrentWindow().isWebViewFocused()) { - this.atomEnvironment.hide() + handleWindowBeforeunload(event) { + if ( + !this.reloadRequested && + !this.atomEnvironment.inSpecMode() && + this.atomEnvironment.getCurrentWindow().isWebViewFocused() + ) { + this.atomEnvironment.hide(); } - this.reloadRequested = false - this.atomEnvironment.storeWindowDimensions() - this.atomEnvironment.unloadEditorWindow() - this.atomEnvironment.destroy() + this.reloadRequested = false; + this.atomEnvironment.storeWindowDimensions(); + this.atomEnvironment.unloadEditorWindow(); + this.atomEnvironment.destroy(); } - handleWindowToggleFullScreen () { - this.atomEnvironment.toggleFullScreen() + handleWindowToggleFullScreen() { + this.atomEnvironment.toggleFullScreen(); } - handleWindowClose () { - this.atomEnvironment.close() + handleWindowClose() { + this.atomEnvironment.close(); } - handleWindowReload () { - this.reloadRequested = true - this.atomEnvironment.reload() + handleWindowReload() { + this.reloadRequested = true; + this.atomEnvironment.reload(); } - handleWindowToggleDevTools () { - this.atomEnvironment.toggleDevTools() + handleWindowToggleDevTools() { + this.atomEnvironment.toggleDevTools(); } - handleWindowToggleMenuBar () { - this.atomEnvironment.config.set('core.autoHideMenuBar', !this.atomEnvironment.config.get('core.autoHideMenuBar')) + handleWindowToggleMenuBar() { + this.atomEnvironment.config.set( + 'core.autoHideMenuBar', + !this.atomEnvironment.config.get('core.autoHideMenuBar') + ); if (this.atomEnvironment.config.get('core.autoHideMenuBar')) { - const detail = 'To toggle, press the Alt key or execute the window:toggle-menu-bar command' - this.atomEnvironment.notifications.addInfo('Menu bar hidden', {detail}) + const detail = + 'To toggle, press the Alt key or execute the window:toggle-menu-bar command'; + this.atomEnvironment.notifications.addInfo('Menu bar hidden', { detail }); } } - handleLinkClick (event) { - event.preventDefault() - const uri = event.currentTarget && event.currentTarget.getAttribute('href') + handleLinkClick(event) { + event.preventDefault(); + const uri = event.currentTarget && event.currentTarget.getAttribute('href'); if (uri && uri[0] !== '#') { if (/^https?:\/\//.test(uri)) { - this.applicationDelegate.openExternal(uri) + this.applicationDelegate.openExternal(uri); } else if (uri.startsWith('atom://')) { - this.atomEnvironment.uriHandlerRegistry.handleURI(uri) + this.atomEnvironment.uriHandlerRegistry.handleURI(uri); } } } - handleFormSubmit (event) { + handleFormSubmit(event) { // Prevent form submits from changing the current window's URL - event.preventDefault() + event.preventDefault(); } - handleDocumentContextmenu (event) { - event.preventDefault() - this.atomEnvironment.contextMenu.showForEvent(event) + handleDocumentContextmenu(event) { + event.preventDefault(); + this.atomEnvironment.contextMenu.showForEvent(event); } -} +}; diff --git a/src/window.js b/src/window.js index c4f28ba96..a13a5e648 100644 --- a/src/window.js +++ b/src/window.js @@ -5,13 +5,13 @@ // fn - A {Function} to measure the duration of. // // Returns the value returned by the given function. -window.measure = function (description, fn) { - let start = Date.now() - let value = fn() - let result = Date.now() - start - console.log(description, result) - return value -} +window.measure = function(description, fn) { + let start = Date.now(); + let value = fn(); + let result = Date.now() - start; + console.log(description, result); + return value; +}; // Public: Create a dev tools profile for a function. // @@ -20,11 +20,11 @@ window.measure = function (description, fn) { // fn - A {Function} to profile. // // Returns the value returned by the given function. -window.profile = function (description, fn) { - window.measure(description, function () { - console.profile(description) - let value = fn() - console.profileEnd(description) - return value - }) -} +window.profile = function(description, fn) { + window.measure(description, function() { + console.profile(description); + let value = fn(); + console.profileEnd(description); + return value; + }); +}; diff --git a/src/workspace-center.js b/src/workspace-center.js index 5e47a0e5b..f803c1243 100644 --- a/src/workspace-center.js +++ b/src/workspace-center.js @@ -1,45 +1,47 @@ -'use strict' +'use strict'; -const TextEditor = require('./text-editor') -const PaneContainer = require('./pane-container') +const TextEditor = require('./text-editor'); +const PaneContainer = require('./pane-container'); // Essential: Represents the workspace at the center of the entire window. module.exports = class WorkspaceCenter { - constructor (params) { - params.location = 'center' - this.paneContainer = new PaneContainer(params) - this.didActivate = params.didActivate - this.paneContainer.onDidActivatePane(() => this.didActivate(this)) - this.paneContainer.onDidChangeActivePane((pane) => { - params.didChangeActivePane(this, pane) - }) - this.paneContainer.onDidChangeActivePaneItem((item) => { - params.didChangeActivePaneItem(this, item) - }) - this.paneContainer.onDidDestroyPaneItem((item) => params.didDestroyPaneItem(item)) + constructor(params) { + params.location = 'center'; + this.paneContainer = new PaneContainer(params); + this.didActivate = params.didActivate; + this.paneContainer.onDidActivatePane(() => this.didActivate(this)); + this.paneContainer.onDidChangeActivePane(pane => { + params.didChangeActivePane(this, pane); + }); + this.paneContainer.onDidChangeActivePaneItem(item => { + params.didChangeActivePaneItem(this, item); + }); + this.paneContainer.onDidDestroyPaneItem(item => + params.didDestroyPaneItem(item) + ); } - destroy () { - this.paneContainer.destroy() + destroy() { + this.paneContainer.destroy(); } - serialize () { - return this.paneContainer.serialize() + serialize() { + return this.paneContainer.serialize(); } - deserialize (state, deserializerManager) { - this.paneContainer.deserialize(state, deserializerManager) + deserialize(state, deserializerManager) { + this.paneContainer.deserialize(state, deserializerManager); } - activate () { - this.getActivePane().activate() + activate() { + this.getActivePane().activate(); } - getLocation () { - return 'center' + getLocation() { + return 'center'; } - setDraggingItem () { + setDraggingItem() { // No-op } @@ -55,9 +57,11 @@ module.exports = class WorkspaceCenter { // 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 @@ -68,7 +72,9 @@ module.exports = class WorkspaceCenter { // 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. // @@ -81,8 +87,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -99,8 +105,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -110,8 +116,8 @@ module.exports = class WorkspaceCenter { // * `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); } // Extended: Invoke the given callback when a pane is added to the workspace @@ -122,8 +128,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -134,8 +140,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -146,8 +152,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -158,8 +164,8 @@ module.exports = class WorkspaceCenter { // 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. @@ -168,8 +174,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -180,8 +186,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -194,8 +200,8 @@ module.exports = class WorkspaceCenter { // * `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 @@ -209,8 +215,8 @@ module.exports = class WorkspaceCenter { // 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. @@ -223,8 +229,8 @@ module.exports = class WorkspaceCenter { // 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 @@ -238,12 +244,12 @@ module.exports = class WorkspaceCenter { // pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddTextEditor (callback) { - return this.onDidAddPaneItem(({item, pane, index}) => { + onDidAddTextEditor(callback) { + return this.onDidAddPaneItem(({ item, pane, index }) => { if (item instanceof TextEditor) { - callback({textEditor: item, pane, index}) + callback({ textEditor: item, pane, index }); } - }) + }); } /* @@ -253,40 +259,42 @@ module.exports = class WorkspaceCenter { // Essential: Get all pane items in the workspace center. // // 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 center. // // 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 () { - this.paneContainer.saveAll() + saveAll() { + this.paneContainer.saveAll(); } - confirmClose (options) { - return this.paneContainer.confirmClose(options) + confirmClose(options) { + return this.paneContainer.confirmClose(options); } /* @@ -296,40 +304,40 @@ module.exports = class WorkspaceCenter { // Extended: Get all panes in the workspace center. // // 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(); } - paneForURI (uri) { - return this.paneContainer.paneForURI(uri) + paneForURI(uri) { + return this.paneContainer.paneForURI(uri); } - paneForItem (item) { - return this.paneContainer.paneForItem(item) + paneForItem(item) { + return this.paneContainer.paneForItem(item); } // Destroy (close) the active pane. - destroyActivePane () { - const activePane = this.getActivePane() + destroyActivePane() { + const activePane = this.getActivePane(); if (activePane != null) { - activePane.destroy() + activePane.destroy(); } } -} +}; diff --git a/src/workspace-element.js b/src/workspace-element.js index f94dbd6e9..d84c8a813 100644 --- a/src/workspace-element.js +++ b/src/workspace-element.js @@ -1,116 +1,166 @@ -'use strict' +'use strict'; -const {ipcRenderer} = require('electron') -const path = require('path') -const fs = require('fs-plus') -const {CompositeDisposable, Disposable} = require('event-kit') -const scrollbarStyle = require('scrollbar-style') -const _ = require('underscore-plus') +const { ipcRenderer } = require('electron'); +const path = require('path'); +const fs = require('fs-plus'); +const { CompositeDisposable, Disposable } = require('event-kit'); +const scrollbarStyle = require('scrollbar-style'); +const _ = require('underscore-plus'); class WorkspaceElement extends HTMLElement { - attachedCallback () { - this.focus() - this.htmlElement = document.querySelector('html') - this.htmlElement.addEventListener('mouseleave', this.handleCenterLeave) + attachedCallback() { + this.focus(); + this.htmlElement = document.querySelector('html'); + this.htmlElement.addEventListener('mouseleave', this.handleCenterLeave); } - detachedCallback () { - this.subscriptions.dispose() - this.htmlElement.removeEventListener('mouseleave', this.handleCenterLeave) + detachedCallback() { + this.subscriptions.dispose(); + this.htmlElement.removeEventListener('mouseleave', this.handleCenterLeave); } - 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); - this.appendChild(this.horizontalAxis) + this.appendChild(this.horizontalAxis); } - observeScrollbarStyle () { - this.subscriptions.add(scrollbarStyle.observePreferredScrollbarStyle(style => { - switch (style) { - case 'legacy': - this.classList.remove('scrollbars-visible-when-scrolling') - this.classList.add('scrollbars-visible-always') - break - case 'overlay': - this.classList.remove('scrollbars-visible-always') - this.classList.add('scrollbars-visible-when-scrolling') - break - } - })) + observeScrollbarStyle() { + this.subscriptions.add( + scrollbarStyle.observePreferredScrollbarStyle(style => { + switch (style) { + case 'legacy': + this.classList.remove('scrollbars-visible-when-scrolling'); + this.classList.add('scrollbars-visible-always'); + break; + case 'overlay': + this.classList.remove('scrollbars-visible-always'); + 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))) - 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) + ) + ); + this.subscriptions.add( + this.config.onDidChange( + 'editor.lineHeight', + this.updateGlobalTextEditorStyleSheet.bind(this) + ) + ); } - updateGlobalTextEditorStyleSheet () { + updateGlobalTextEditorStyleSheet() { const styleSheetSource = `atom-workspace { --editor-font-size: ${this.config.get('editor.fontSize')}px; --editor-font-family: ${this.config.get('editor.fontFamily')}; --editor-line-height: ${this.config.get('editor.lineHeight')}; -}` - this.styleManager.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles', priority: -1}) +}`; + this.styleManager.addStyleSheet(styleSheetSource, { + sourcePath: 'global-text-editor-styles', + priority: -1 + }); } - initialize (model, {config, project, styleManager, viewRegistry}) { - this.handleCenterEnter = this.handleCenterEnter.bind(this) - this.handleCenterLeave = this.handleCenterLeave.bind(this) - this.handleEdgesMouseMove = _.throttle(this.handleEdgesMouseMove.bind(this), 100) - this.handleDockDragEnd = this.handleDockDragEnd.bind(this) - this.handleDragStart = this.handleDragStart.bind(this) - this.handleDragEnd = this.handleDragEnd.bind(this) - this.handleDrop = this.handleDrop.bind(this) + initialize(model, { config, project, styleManager, viewRegistry }) { + this.handleCenterEnter = this.handleCenterEnter.bind(this); + this.handleCenterLeave = this.handleCenterLeave.bind(this); + this.handleEdgesMouseMove = _.throttle( + this.handleEdgesMouseMove.bind(this), + 100 + ); + this.handleDockDragEnd = this.handleDockDragEnd.bind(this); + this.handleDragStart = this.handleDragStart.bind(this); + this.handleDragEnd = this.handleDragEnd.bind(this); + this.handleDrop = this.handleDrop.bind(this); - this.model = model - this.viewRegistry = viewRegistry - this.project = project - this.config = config - this.styleManager = styleManager - if (this.viewRegistry == null) { throw new Error('Must pass a viewRegistry parameter when initializing WorkspaceElements') } - if (this.project == null) { throw new Error('Must pass a project parameter when initializing WorkspaceElements') } - if (this.config == null) { throw new Error('Must pass a config parameter when initializing WorkspaceElements') } - if (this.styleManager == null) { throw new Error('Must pass a styleManager parameter when initializing WorkspaceElements') } + this.model = model; + this.viewRegistry = viewRegistry; + this.project = project; + this.config = config; + this.styleManager = styleManager; + if (this.viewRegistry == null) { + throw new Error( + 'Must pass a viewRegistry parameter when initializing WorkspaceElements' + ); + } + if (this.project == null) { + throw new Error( + 'Must pass a project parameter when initializing WorkspaceElements' + ); + } + if (this.config == null) { + throw new Error( + 'Must pass a config parameter when initializing WorkspaceElements' + ); + } + if (this.styleManager == null) { + throw new Error( + 'Must pass a styleManager parameter when initializing WorkspaceElements' + ); + } this.subscriptions = new CompositeDisposable( new Disposable(() => { - this.paneContainer.removeEventListener('mouseenter', this.handleCenterEnter) - this.paneContainer.removeEventListener('mouseleave', this.handleCenterLeave) - window.removeEventListener('mousemove', this.handleEdgesMouseMove) - window.removeEventListener('dragend', this.handleDockDragEnd) - window.removeEventListener('dragstart', this.handleDragStart) - window.removeEventListener('dragend', this.handleDragEnd, true) - window.removeEventListener('drop', this.handleDrop, true) + this.paneContainer.removeEventListener( + 'mouseenter', + this.handleCenterEnter + ); + this.paneContainer.removeEventListener( + 'mouseleave', + this.handleCenterLeave + ); + window.removeEventListener('mousemove', this.handleEdgesMouseMove); + window.removeEventListener('dragend', this.handleDockDragEnd); + window.removeEventListener('dragstart', this.handleDragStart); + window.removeEventListener('dragend', this.handleDragEnd, true); + window.removeEventListener('drop', this.handleDrop, true); }), - ...[this.model.getLeftDock(), this.model.getRightDock(), this.model.getBottomDock()] - .map(dock => dock.onDidChangeHovered(hovered => { - if (hovered) this.hoveredDock = dock - else if (dock === this.hoveredDock) this.hoveredDock = null - this.checkCleanupDockHoverEvents() - })) - ) - this.initializeContent() - this.observeScrollbarStyle() - this.observeTextEditorFontConfig() + ...[ + this.model.getLeftDock(), + this.model.getRightDock(), + this.model.getBottomDock() + ].map(dock => + dock.onDidChangeHovered(hovered => { + if (hovered) this.hoveredDock = dock; + else if (dock === this.hoveredDock) this.hoveredDock = null; + this.checkCleanupDockHoverEvents(); + }) + ) + ); + this.initializeContent(); + this.observeScrollbarStyle(); + this.observeTextEditorFontConfig(); - this.paneContainer = this.model.getCenter().paneContainer.getElement() - this.verticalAxis.appendChild(this.paneContainer) - this.addEventListener('focus', this.handleFocus.bind(this)) + this.paneContainer = this.model.getCenter().paneContainer.getElement(); + this.verticalAxis.appendChild(this.paneContainer); + this.addEventListener('focus', this.handleFocus.bind(this)); - this.addEventListener('mousewheel', this.handleMousewheel.bind(this), true) - window.addEventListener('dragstart', this.handleDragStart) - window.addEventListener('mousemove', this.handleEdgesMouseMove) + this.addEventListener('mousewheel', this.handleMousewheel.bind(this), true); + window.addEventListener('dragstart', this.handleDragStart); + window.addEventListener('mousemove', this.handleEdgesMouseMove); this.panelContainers = { top: this.model.panelContainers.top.getElement(), @@ -120,239 +170,309 @@ class WorkspaceElement extends HTMLElement { header: this.model.panelContainers.header.getElement(), footer: this.model.panelContainers.footer.getElement(), modal: this.model.panelContainers.modal.getElement() - } + }; - 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); - this.paneContainer.addEventListener('mouseenter', this.handleCenterEnter) - this.paneContainer.addEventListener('mouseleave', this.handleCenterLeave) + this.paneContainer.addEventListener('mouseenter', this.handleCenterEnter); + this.paneContainer.addEventListener('mouseleave', this.handleCenterLeave); - return this + return this; } - destroy () { - this.subscriptions.dispose() + destroy() { + this.subscriptions.dispose(); } - getModel () { return this.model } - - handleDragStart (event) { - if (!isTab(event.target)) return - const {item} = event.target - if (!item) return - this.model.setDraggingItem(item) - window.addEventListener('dragend', this.handleDragEnd, true) - window.addEventListener('drop', this.handleDrop, true) + getModel() { + return this.model; } - handleDragEnd (event) { - this.dragEnded() + handleDragStart(event) { + if (!isTab(event.target)) return; + const { item } = event.target; + if (!item) return; + this.model.setDraggingItem(item); + window.addEventListener('dragend', this.handleDragEnd, true); + window.addEventListener('drop', this.handleDrop, true); } - handleDrop (event) { - this.dragEnded() + handleDragEnd(event) { + this.dragEnded(); } - dragEnded () { - this.model.setDraggingItem(null) - window.removeEventListener('dragend', this.handleDragEnd, true) - window.removeEventListener('drop', this.handleDrop, true) + handleDrop(event) { + this.dragEnded(); } - handleCenterEnter (event) { + dragEnded() { + this.model.setDraggingItem(null); + window.removeEventListener('dragend', this.handleDragEnd, true); + window.removeEventListener('drop', this.handleDrop, true); + } + + handleCenterEnter(event) { // Just re-entering the center isn't enough to hide the dock toggle buttons, since they poke // into the center and we want to give an affordance. - this.cursorInCenter = true - this.checkCleanupDockHoverEvents() + this.cursorInCenter = true; + this.checkCleanupDockHoverEvents(); } - handleCenterLeave (event) { + handleCenterLeave(event) { // If the cursor leaves the center, we start listening to determine whether one of the docs is // being hovered. - this.cursorInCenter = false - this.updateHoveredDock({x: event.pageX, y: event.pageY}) - window.addEventListener('dragend', this.handleDockDragEnd) + this.cursorInCenter = false; + this.updateHoveredDock({ x: event.pageX, y: event.pageY }); + window.addEventListener('dragend', this.handleDockDragEnd); } - handleEdgesMouseMove (event) { - this.updateHoveredDock({x: event.pageX, y: event.pageY}) + handleEdgesMouseMove(event) { + this.updateHoveredDock({ x: event.pageX, y: event.pageY }); } - handleDockDragEnd (event) { - this.updateHoveredDock({x: event.pageX, y: event.pageY}) + handleDockDragEnd(event) { + this.updateHoveredDock({ x: event.pageX, y: event.pageY }); } - updateHoveredDock (mousePosition) { + updateHoveredDock(mousePosition) { // If we haven't left the currently hovered dock, don't change anything. - if (this.hoveredDock && this.hoveredDock.pointWithinHoverArea(mousePosition, true)) return + if ( + this.hoveredDock && + this.hoveredDock.pointWithinHoverArea(mousePosition, true) + ) + return; - const docks = [this.model.getLeftDock(), this.model.getRightDock(), this.model.getBottomDock()] - const nextHoveredDock = - docks.find(dock => dock !== this.hoveredDock && dock.pointWithinHoverArea(mousePosition)) - docks.forEach(dock => { dock.setHovered(dock === nextHoveredDock) }) + const docks = [ + this.model.getLeftDock(), + this.model.getRightDock(), + this.model.getBottomDock() + ]; + const nextHoveredDock = docks.find( + dock => + dock !== this.hoveredDock && dock.pointWithinHoverArea(mousePosition) + ); + docks.forEach(dock => { + dock.setHovered(dock === nextHoveredDock); + }); } - checkCleanupDockHoverEvents () { + checkCleanupDockHoverEvents() { if (this.cursorInCenter && !this.hoveredDock) { - window.removeEventListener('dragend', this.handleDockDragEnd) + window.removeEventListener('dragend', this.handleDockDragEnd); } } - handleMousewheel (event) { - if (event.ctrlKey && this.config.get('editor.zoomFontWhenCtrlScrolling') && (event.target.closest('atom-text-editor') != null)) { + 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() - event.stopPropagation() + event.preventDefault(); + event.stopPropagation(); } } - handleFocus (event) { - this.model.getActivePane().activate() + handleFocus(event) { + this.model.getActivePane().activate(); } - focusPaneViewAbove () { this.focusPaneViewInDirection('above') } - - focusPaneViewBelow () { this.focusPaneViewInDirection('below') } - - focusPaneViewOnLeft () { this.focusPaneViewInDirection('left') } - - focusPaneViewOnRight () { this.focusPaneViewInDirection('right') } - - focusPaneViewInDirection (direction, pane) { - const activePane = this.model.getActivePane() - const paneToFocus = this.nearestVisiblePaneInDirection(direction, activePane) - paneToFocus && paneToFocus.focus() + focusPaneViewAbove() { + this.focusPaneViewInDirection('above'); } - moveActiveItemToPaneAbove (params) { - this.moveActiveItemToNearestPaneInDirection('above', params) + focusPaneViewBelow() { + this.focusPaneViewInDirection('below'); } - moveActiveItemToPaneBelow (params) { - this.moveActiveItemToNearestPaneInDirection('below', params) + focusPaneViewOnLeft() { + this.focusPaneViewInDirection('left'); } - moveActiveItemToPaneOnLeft (params) { - this.moveActiveItemToNearestPaneInDirection('left', params) + focusPaneViewOnRight() { + this.focusPaneViewInDirection('right'); } - moveActiveItemToPaneOnRight (params) { - this.moveActiveItemToNearestPaneInDirection('right', params) + focusPaneViewInDirection(direction, pane) { + const activePane = this.model.getActivePane(); + const paneToFocus = this.nearestVisiblePaneInDirection( + direction, + activePane + ); + paneToFocus && paneToFocus.focus(); } - moveActiveItemToNearestPaneInDirection (direction, params) { - const activePane = this.model.getActivePane() - const nearestPaneView = this.nearestVisiblePaneInDirection(direction, activePane) - if (nearestPaneView == null) { return } + moveActiveItemToPaneAbove(params) { + this.moveActiveItemToNearestPaneInDirection('above', params); + } + + moveActiveItemToPaneBelow(params) { + this.moveActiveItemToNearestPaneInDirection('below', params); + } + + moveActiveItemToPaneOnLeft(params) { + this.moveActiveItemToNearestPaneInDirection('left', params); + } + + moveActiveItemToPaneOnRight(params) { + this.moveActiveItemToNearestPaneInDirection('right', params); + } + + moveActiveItemToNearestPaneInDirection(direction, params) { + const activePane = this.model.getActivePane(); + const nearestPaneView = this.nearestVisiblePaneInDirection( + direction, + activePane + ); + if (nearestPaneView == null) { + return; + } if (params && params.keepOriginal) { - activePane.getContainer().copyActiveItemToPane(nearestPaneView.getModel()) + activePane + .getContainer() + .copyActiveItemToPane(nearestPaneView.getModel()); } else { - activePane.getContainer().moveActiveItemToPane(nearestPaneView.getModel()) + activePane + .getContainer() + .moveActiveItemToPane(nearestPaneView.getModel()); } - nearestPaneView.focus() + nearestPaneView.focus(); } - nearestVisiblePaneInDirection (direction, pane) { - const distance = function (pointA, pointB) { - const x = pointB.x - pointA.x - const y = pointB.y - pointA.y - return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) - } + nearestVisiblePaneInDirection(direction, pane) { + const distance = function(pointA, pointB) { + const x = pointB.x - pointA.x; + const y = pointB.y - pointA.y; + return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + }; - const paneView = pane.getElement() - const box = this.boundingBoxForPaneView(paneView) + const paneView = pane.getElement(); + const box = this.boundingBoxForPaneView(paneView); - const paneViews = atom.workspace.getVisiblePanes() + const paneViews = atom.workspace + .getVisiblePanes() .map(otherPane => otherPane.getElement()) .filter(otherPaneView => { - const otherBox = this.boundingBoxForPaneView(otherPaneView) + const otherBox = this.boundingBoxForPaneView(otherPaneView); switch (direction) { - case 'left': return otherBox.right.x <= box.left.x - case 'right': return otherBox.left.x >= box.right.x - case 'above': return otherBox.bottom.y <= box.top.y - case 'below': return otherBox.top.y >= box.bottom.y - } - }).sort((paneViewA, paneViewB) => { - const boxA = this.boundingBoxForPaneView(paneViewA) - const boxB = this.boundingBoxForPaneView(paneViewB) - switch (direction) { - case 'left': return distance(box.left, boxA.right) - distance(box.left, boxB.right) - case 'right': return distance(box.right, boxA.left) - distance(box.right, boxB.left) - case 'above': return distance(box.top, boxA.bottom) - distance(box.top, boxB.bottom) - case 'below': return distance(box.bottom, boxA.top) - distance(box.bottom, boxB.top) + case 'left': + return otherBox.right.x <= box.left.x; + case 'right': + return otherBox.left.x >= box.right.x; + case 'above': + return otherBox.bottom.y <= box.top.y; + case 'below': + return otherBox.top.y >= box.bottom.y; } }) + .sort((paneViewA, paneViewB) => { + const boxA = this.boundingBoxForPaneView(paneViewA); + const boxB = this.boundingBoxForPaneView(paneViewB); + switch (direction) { + case 'left': + return ( + distance(box.left, boxA.right) - distance(box.left, boxB.right) + ); + case 'right': + return ( + distance(box.right, boxA.left) - distance(box.right, boxB.left) + ); + case 'above': + return ( + distance(box.top, boxA.bottom) - distance(box.top, boxB.bottom) + ); + case 'below': + return ( + distance(box.bottom, boxA.top) - distance(box.bottom, boxB.top) + ); + } + }); - return paneViews[0] + return paneViews[0]; } - boundingBoxForPaneView (paneView) { - const boundingBox = paneView.getBoundingClientRect() + boundingBoxForPaneView(paneView) { + const boundingBox = paneView.getBoundingClientRect(); return { - left: {x: boundingBox.left, y: boundingBox.top}, - right: {x: boundingBox.right, y: boundingBox.top}, - top: {x: boundingBox.left, y: boundingBox.top}, - bottom: {x: boundingBox.left, y: boundingBox.bottom} - } + left: { x: boundingBox.left, y: boundingBox.top }, + right: { x: boundingBox.right, y: boundingBox.top }, + top: { x: boundingBox.left, y: boundingBox.top }, + bottom: { x: boundingBox.left, y: boundingBox.bottom } + }; } - runPackageSpecs (options = {}) { - const activePaneItem = this.model.getActivePaneItem() - const activePath = activePaneItem && typeof activePaneItem.getPath === 'function' ? activePaneItem.getPath() : null - let projectPath + runPackageSpecs(options = {}) { + const activePaneItem = this.model.getActivePaneItem(); + const activePath = + activePaneItem && typeof activePaneItem.getPath === 'function' + ? activePaneItem.getPath() + : null; + let projectPath; if (activePath != null) { - [projectPath] = this.project.relativizePath(activePath) + [projectPath] = this.project.relativizePath(activePath); } else { - [projectPath] = this.project.getPaths() + [projectPath] = 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; } - ipcRenderer.send('run-package-specs', specPath, options) + ipcRenderer.send('run-package-specs', specPath, options); } } - runBenchmarks () { - const activePaneItem = this.model.getActivePaneItem() - const activePath = activePaneItem && typeof activePaneItem.getPath === 'function' ? activePaneItem.getPath() : null - let projectPath + runBenchmarks() { + const activePaneItem = this.model.getActivePaneItem(); + const activePath = + activePaneItem && typeof activePaneItem.getPath === 'function' + ? activePaneItem.getPath() + : null; + let projectPath; if (activePath) { - [projectPath] = this.project.relativizePath(activePath) + [projectPath] = this.project.relativizePath(activePath); } else { - [projectPath] = this.project.getPaths() + [projectPath] = this.project.getPaths(); } if (projectPath) { - ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) + ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')); } } } -module.exports = document.registerElement('atom-workspace', {prototype: WorkspaceElement.prototype}) +module.exports = document.registerElement('atom-workspace', { + prototype: WorkspaceElement.prototype +}); -function isTab (element) { - let el = element +function isTab(element) { + let el = element; while (el != null) { - if (el.getAttribute && el.getAttribute('is') === 'tabs-tab') return true - el = el.parentElement + if (el.getAttribute && el.getAttribute('is') === 'tabs-tab') return true; + el = el.parentElement; } - return false + return false; } diff --git a/src/workspace.js b/src/workspace.js index ffb852b85..15a69931a 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -1,24 +1,24 @@ -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 Grim = require('grim') -const DefaultDirectorySearcher = require('./default-directory-searcher') -const RipgrepDirectorySearcher = require('./ripgrep-directory-searcher') -const Dock = require('./dock') -const Model = require('./model') -const StateStore = require('./state-store') -const TextEditor = require('./text-editor') -const Panel = require('./panel') -const PanelContainer = require('./panel-container') -const Task = require('./task') -const WorkspaceCenter = require('./workspace-center') -const WorkspaceElement = require('./workspace-element') +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 Grim = require('grim'); +const DefaultDirectorySearcher = require('./default-directory-searcher'); +const RipgrepDirectorySearcher = require('./ripgrep-directory-searcher'); +const Dock = require('./dock'); +const Model = require('./model'); +const StateStore = require('./state-store'); +const TextEditor = require('./text-editor'); +const Panel = require('./panel'); +const PanelContainer = require('./panel-container'); +const Task = require('./task'); +const WorkspaceCenter = require('./workspace-center'); +const WorkspaceElement = require('./workspace-element'); -const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100 -const ALL_LOCATIONS = ['center', 'left', 'right', 'bottom'] +const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100; +const ALL_LOCATIONS = ['center', 'left', 'right', 'bottom']; // Essential: Represents the state of the user interface for the entire window. // An instance of this class is available via the `atom.workspace` global. @@ -174,82 +174,112 @@ const ALL_LOCATIONS = ['center', 'left', 'right', 'bottom'] // This method indicates whether Atom should prompt the user to save this item // when the user closes or reloads the window. Returns a boolean. module.exports = class Workspace extends Model { - constructor (params) { - super(...arguments) + constructor(params) { + super(...arguments); - this.updateWindowTitle = this.updateWindowTitle.bind(this) - this.updateDocumentEdited = this.updateDocumentEdited.bind(this) - this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this) - this.didChangeActivePaneOnPaneContainer = this.didChangeActivePaneOnPaneContainer.bind(this) - this.didChangeActivePaneItemOnPaneContainer = this.didChangeActivePaneItemOnPaneContainer.bind(this) - this.didActivatePaneContainer = this.didActivatePaneContainer.bind(this) + this.updateWindowTitle = this.updateWindowTitle.bind(this); + this.updateDocumentEdited = this.updateDocumentEdited.bind(this); + this.didDestroyPaneItem = this.didDestroyPaneItem.bind(this); + this.didChangeActivePaneOnPaneContainer = this.didChangeActivePaneOnPaneContainer.bind( + this + ); + this.didChangeActivePaneItemOnPaneContainer = this.didChangeActivePaneItemOnPaneContainer.bind( + this + ); + this.didActivatePaneContainer = this.didActivatePaneContainer.bind(this); - this.enablePersistence = params.enablePersistence - 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.styleManager = params.styleManager - this.draggingItem = false - this.itemLocationStore = new StateStore('AtomPreviousItemLocations', 1) + this.enablePersistence = params.enablePersistence; + 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.styleManager = params.styleManager; + this.draggingItem = false; + this.itemLocationStore = new StateStore('AtomPreviousItemLocations', 1); - this.emitter = new Emitter() - this.openers = [] - this.destroyedItemURIs = [] - this.stoppedChangingActivePaneItemTimeout = null + this.emitter = new Emitter(); + this.openers = []; + this.destroyedItemURIs = []; + this.stoppedChangingActivePaneItemTimeout = null; - this.scandalDirectorySearcher = new DefaultDirectorySearcher() - this.ripgrepDirectorySearcher = new RipgrepDirectorySearcher() - this.consumeServices(this.packageManager) + this.scandalDirectorySearcher = new DefaultDirectorySearcher(); + this.ripgrepDirectorySearcher = new RipgrepDirectorySearcher(); + this.consumeServices(this.packageManager); this.paneContainers = { center: this.createCenter(), left: this.createDock('left'), right: this.createDock('right'), bottom: this.createDock('bottom') - } - this.activePaneContainer = this.paneContainers.center - this.hasActiveTextEditor = false + }; + this.activePaneContainer = this.paneContainers.center; + this.hasActiveTextEditor = false; this.panelContainers = { - top: new PanelContainer({viewRegistry: this.viewRegistry, location: 'top'}), - left: new PanelContainer({viewRegistry: this.viewRegistry, location: 'left', dock: this.paneContainers.left}), - right: new PanelContainer({viewRegistry: this.viewRegistry, location: 'right', dock: this.paneContainers.right}), - bottom: new PanelContainer({viewRegistry: this.viewRegistry, location: 'bottom', dock: this.paneContainers.bottom}), - header: new PanelContainer({viewRegistry: this.viewRegistry, location: 'header'}), - footer: new PanelContainer({viewRegistry: this.viewRegistry, location: 'footer'}), - modal: new PanelContainer({viewRegistry: this.viewRegistry, location: 'modal'}) - } + top: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'top' + }), + left: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'left', + dock: this.paneContainers.left + }), + right: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'right', + dock: this.paneContainers.right + }), + bottom: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'bottom', + dock: this.paneContainers.bottom + }), + header: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'header' + }), + footer: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'footer' + }), + modal: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'modal' + }) + }; - this.incoming = new Map() + this.incoming = new Map(); - this.subscribeToEvents() + this.subscribeToEvents(); } - get paneContainer () { - Grim.deprecate('`atom.workspace.paneContainer` has always been private, but it is now gone. Please use `atom.workspace.getCenter()` instead and consult the workspace API docs for public methods.') - return this.paneContainers.center.paneContainer + get paneContainer() { + Grim.deprecate( + '`atom.workspace.paneContainer` has always been private, but it is now gone. Please use `atom.workspace.getCenter()` instead and consult the workspace API docs for public methods.' + ); + return this.paneContainers.center.paneContainer; } - getElement () { + getElement() { if (!this.element) { this.element = new WorkspaceElement().initialize(this, { config: this.config, project: this.project, viewRegistry: this.viewRegistry, styleManager: this.styleManager - }) + }); } - return this.element + return this.element; } - createCenter () { + createCenter() { return new WorkspaceCenter({ config: this.config, applicationDelegate: this.applicationDelegate, @@ -260,10 +290,10 @@ module.exports = class Workspace extends Model { didChangeActivePane: this.didChangeActivePaneOnPaneContainer, didChangeActivePaneItem: this.didChangeActivePaneItemOnPaneContainer, didDestroyPaneItem: this.didDestroyPaneItem - }) + }); } - createDock (location) { + createDock(location) { return new Dock({ location, config: this.config, @@ -275,237 +305,318 @@ module.exports = class Workspace extends Model { didChangeActivePane: this.didChangeActivePaneOnPaneContainer, didChangeActivePaneItem: this.didChangeActivePaneItemOnPaneContainer, didDestroyPaneItem: this.didDestroyPaneItem - }) + }); } - 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.paneContainers.center.destroy() - this.paneContainers.left.destroy() - this.paneContainers.right.destroy() - this.paneContainers.bottom.destroy() + this.paneContainers.center.destroy(); + this.paneContainers.left.destroy(); + this.paneContainers.right.destroy(); + this.paneContainers.bottom.destroy(); - _.values(this.panelContainers).forEach(panelContainer => { panelContainer.destroy() }) + _.values(this.panelContainers).forEach(panelContainer => { + panelContainer.destroy(); + }); this.paneContainers = { center: this.createCenter(), left: this.createDock('left'), right: this.createDock('right'), bottom: this.createDock('bottom') - } - this.activePaneContainer = this.paneContainers.center - this.hasActiveTextEditor = false + }; + this.activePaneContainer = this.paneContainers.center; + this.hasActiveTextEditor = false; this.panelContainers = { - top: new PanelContainer({viewRegistry: this.viewRegistry, location: 'top'}), - left: new PanelContainer({viewRegistry: this.viewRegistry, location: 'left', dock: this.paneContainers.left}), - right: new PanelContainer({viewRegistry: this.viewRegistry, location: 'right', dock: this.paneContainers.right}), - bottom: new PanelContainer({viewRegistry: this.viewRegistry, location: 'bottom', dock: this.paneContainers.bottom}), - header: new PanelContainer({viewRegistry: this.viewRegistry, location: 'header'}), - footer: new PanelContainer({viewRegistry: this.viewRegistry, location: 'footer'}), - modal: new PanelContainer({viewRegistry: this.viewRegistry, location: 'modal'}) - } + top: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'top' + }), + left: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'left', + dock: this.paneContainers.left + }), + right: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'right', + dock: this.paneContainers.right + }), + bottom: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'bottom', + dock: this.paneContainers.bottom + }), + header: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'header' + }), + footer: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'footer' + }), + modal: new PanelContainer({ + viewRegistry: this.viewRegistry, + location: 'modal' + }) + }; - this.originalFontSize = null - this.openers = [] - this.destroyedItemURIs = [] + this.originalFontSize = null; + this.openers = []; + this.destroyedItemURIs = []; if (this.element) { - this.element.destroy() - this.element = null + this.element.destroy(); + this.element = null; } - this.consumeServices(this.packageManager) + this.consumeServices(this.packageManager); } - subscribeToEvents () { - this.project.onDidChangePaths(this.updateWindowTitle) - this.subscribeToFontSize() - this.subscribeToAddedItems() - this.subscribeToMovedItems() - this.subscribeToDockToggling() + subscribeToEvents() { + this.project.onDidChangePaths(this.updateWindowTitle); + this.subscribeToFontSize(); + this.subscribeToAddedItems(); + this.subscribeToMovedItems(); + this.subscribeToDockToggling(); } - consumeServices ({serviceHub}) { - this.directorySearchers = [] - serviceHub.consume( - 'atom.directory-searcher', - '^0.1.0', - provider => this.directorySearchers.unshift(provider) - ) + consumeServices({ serviceHub }) { + this.directorySearchers = []; + serviceHub.consume('atom.directory-searcher', '^0.1.0', provider => + this.directorySearchers.unshift(provider) + ); } // Called by the Serializable mixin during serialization. - serialize () { + serialize() { return { deserializer: 'Workspace', packagesWithActiveGrammars: this.getPackageNamesWithActiveGrammars(), destroyedItemURIs: this.destroyedItemURIs.slice(), // Ensure deserializing 1.17 state with pre 1.17 Atom does not error // TODO: Remove after 1.17 has been on stable for a while - paneContainer: {version: 2}, + paneContainer: { version: 2 }, paneContainers: { center: this.paneContainers.center.serialize(), left: this.paneContainers.left.serialize(), right: this.paneContainers.right.serialize(), bottom: this.paneContainers.bottom.serialize() } - } + }; } - deserialize (state, deserializerManager) { + deserialize(state, deserializerManager) { const packagesWithActiveGrammars = - state.packagesWithActiveGrammars != null ? state.packagesWithActiveGrammars : [] + state.packagesWithActiveGrammars != null + ? state.packagesWithActiveGrammars + : []; for (let packageName of packagesWithActiveGrammars) { - const pkg = this.packageManager.getLoadedPackage(packageName) + const pkg = this.packageManager.getLoadedPackage(packageName); if (pkg != null) { - pkg.loadGrammarsSync() + pkg.loadGrammarsSync(); } } if (state.destroyedItemURIs != null) { - this.destroyedItemURIs = state.destroyedItemURIs + this.destroyedItemURIs = state.destroyedItemURIs; } if (state.paneContainers) { - this.paneContainers.center.deserialize(state.paneContainers.center, deserializerManager) - this.paneContainers.left.deserialize(state.paneContainers.left, deserializerManager) - this.paneContainers.right.deserialize(state.paneContainers.right, deserializerManager) - this.paneContainers.bottom.deserialize(state.paneContainers.bottom, deserializerManager) + this.paneContainers.center.deserialize( + state.paneContainers.center, + deserializerManager + ); + this.paneContainers.left.deserialize( + state.paneContainers.left, + deserializerManager + ); + this.paneContainers.right.deserialize( + state.paneContainers.right, + deserializerManager + ); + this.paneContainers.bottom.deserialize( + state.paneContainers.bottom, + deserializerManager + ); } else if (state.paneContainer) { // TODO: Remove this fallback once a lot of time has passed since 1.17 was released - this.paneContainers.center.deserialize(state.paneContainer, deserializerManager) + this.paneContainers.center.deserialize( + state.paneContainer, + deserializerManager + ); } - this.hasActiveTextEditor = this.getActiveTextEditor() != null + this.hasActiveTextEditor = this.getActiveTextEditor() != null; - this.updateWindowTitle() + this.updateWindowTitle(); } - getPackageNamesWithActiveGrammars () { - const packageNames = [] - const 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)) + getPackageNamesWithActiveGrammars() { + const packageNames = []; + const addGrammar = ({ includedGrammarScopes, packageName } = {}) => { + if (!packageName) { + return; + } + // Prevent cycles + if (packageNames.indexOf(packageName) !== -1) { + return; } - } - const editors = this.getTextEditors() - for (let editor of editors) { addGrammar(editor.getGrammar()) } + 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) + addGrammar(grammar); } } } - return _.uniq(packageNames) + return _.uniq(packageNames); } - didActivatePaneContainer (paneContainer) { + didActivatePaneContainer(paneContainer) { if (paneContainer !== this.getActivePaneContainer()) { - this.activePaneContainer = paneContainer - this.didChangeActivePaneItem(this.activePaneContainer.getActivePaneItem()) - this.emitter.emit('did-change-active-pane-container', this.activePaneContainer) - this.emitter.emit('did-change-active-pane', this.activePaneContainer.getActivePane()) - this.emitter.emit('did-change-active-pane-item', this.activePaneContainer.getActivePaneItem()) + this.activePaneContainer = paneContainer; + this.didChangeActivePaneItem( + this.activePaneContainer.getActivePaneItem() + ); + this.emitter.emit( + 'did-change-active-pane-container', + this.activePaneContainer + ); + this.emitter.emit( + 'did-change-active-pane', + this.activePaneContainer.getActivePane() + ); + this.emitter.emit( + 'did-change-active-pane-item', + this.activePaneContainer.getActivePaneItem() + ); } } - didChangeActivePaneOnPaneContainer (paneContainer, pane) { + didChangeActivePaneOnPaneContainer(paneContainer, pane) { if (paneContainer === this.getActivePaneContainer()) { - this.emitter.emit('did-change-active-pane', pane) + this.emitter.emit('did-change-active-pane', pane); } } - didChangeActivePaneItemOnPaneContainer (paneContainer, item) { + didChangeActivePaneItemOnPaneContainer(paneContainer, item) { if (paneContainer === this.getActivePaneContainer()) { - this.didChangeActivePaneItem(item) - this.emitter.emit('did-change-active-pane-item', item) + this.didChangeActivePaneItem(item); + this.emitter.emit('did-change-active-pane-item', item); } if (paneContainer === this.getCenter()) { - const hadActiveTextEditor = this.hasActiveTextEditor - this.hasActiveTextEditor = item instanceof TextEditor + const hadActiveTextEditor = this.hasActiveTextEditor; + this.hasActiveTextEditor = item instanceof TextEditor; if (this.hasActiveTextEditor || hadActiveTextEditor) { - const itemValue = this.hasActiveTextEditor ? item : undefined - this.emitter.emit('did-change-active-text-editor', itemValue) + const itemValue = this.hasActiveTextEditor ? item : undefined; + this.emitter.emit('did-change-active-text-editor', itemValue); } } } - didChangeActivePaneItem (item) { - this.updateWindowTitle() - this.updateDocumentEdited() - if (this.activeItemSubscriptions) this.activeItemSubscriptions.dispose() - this.activeItemSubscriptions = new CompositeDisposable() + didChangeActivePaneItem(item) { + this.updateWindowTitle(); + this.updateDocumentEdited(); + if (this.activeItemSubscriptions) this.activeItemSubscriptions.dispose(); + this.activeItemSubscriptions = new CompositeDisposable(); - let modifiedSubscription, titleSubscription + let modifiedSubscription, titleSubscription; if (item != null && typeof item.onDidChangeTitle === 'function') { - titleSubscription = item.onDidChangeTitle(this.updateWindowTitle) + 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 = item.on('title-changed', this.updateWindowTitle); + if ( + titleSubscription == null || + typeof titleSubscription.dispose !== 'function' + ) { titleSubscription = new Disposable(() => { - item.off('title-changed', this.updateWindowTitle) - }) + item.off('title-changed', this.updateWindowTitle); + }); } } if (item != null && typeof item.onDidChangeModified === 'function') { - modifiedSubscription = item.onDidChangeModified(this.updateDocumentEdited) + 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 = item.on( + 'modified-status-changed', + this.updateDocumentEdited + ); + if ( + modifiedSubscription == null || + typeof modifiedSubscription.dispose !== 'function' + ) { modifiedSubscription = new Disposable(() => { - item.off('modified-status-changed', this.updateDocumentEdited) - }) + 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); + } - this.cancelStoppedChangingActivePaneItemTimeout() + this.cancelStoppedChangingActivePaneItemTimeout(); this.stoppedChangingActivePaneItemTimeout = setTimeout(() => { - this.stoppedChangingActivePaneItemTimeout = null - this.emitter.emit('did-stop-changing-active-pane-item', item) - }, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY) + this.stoppedChangingActivePaneItemTimeout = null; + this.emitter.emit('did-stop-changing-active-pane-item', item); + }, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY); } - cancelStoppedChangingActivePaneItemTimeout () { + cancelStoppedChangingActivePaneItemTimeout() { if (this.stoppedChangingActivePaneItemTimeout != null) { - clearTimeout(this.stoppedChangingActivePaneItemTimeout) + clearTimeout(this.stoppedChangingActivePaneItemTimeout); } } - setDraggingItem (draggingItem) { + setDraggingItem(draggingItem) { _.values(this.paneContainers).forEach(dock => { - dock.setDraggingItem(draggingItem) - }) + dock.setDraggingItem(draggingItem); + }); } - subscribeToAddedItems () { - this.onDidAddPaneItem(({item, pane, index}) => { + subscribeToAddedItems() { + this.onDidAddPaneItem(({ item, pane, index }) => { if (item instanceof TextEditor) { const subscriptions = new CompositeDisposable( this.textEditorRegistry.add(item), this.textEditorRegistry.maintainConfig(item) - ) + ); if (!this.project.findBufferForId(item.buffer.id)) { - this.project.addBuffer(item.buffer) + this.project.addBuffer(item.buffer); } - item.onDidDestroy(() => { subscriptions.dispose() }) - 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 + }); // It's important to call handleGrammarUsed after emitting the did-add event: // if we activate a package between adding the editor to the registry and emitting // the package may receive the editor twice from `observeTextEditors`. @@ -513,48 +624,55 @@ module.exports = class Workspace extends Model { if (!item.isDestroyed()) { subscriptions.add( item.observeGrammar(this.handleGrammarUsed.bind(this)) - ) + ); } } - }) + }); } - subscribeToDockToggling () { - const docks = [this.getLeftDock(), this.getRightDock(), this.getBottomDock()] + subscribeToDockToggling() { + const docks = [ + this.getLeftDock(), + this.getRightDock(), + this.getBottomDock() + ]; docks.forEach(dock => { dock.onDidChangeVisible(visible => { - if (visible) return - const {activeElement} = document - const dockElement = dock.getElement() - if (dockElement === activeElement || dockElement.contains(activeElement)) { - this.getCenter().activate() + if (visible) return; + const { activeElement } = document; + const dockElement = dock.getElement(); + if ( + dockElement === activeElement || + dockElement.contains(activeElement) + ) { + this.getCenter().activate(); } - }) - }) + }); + }); } - subscribeToMovedItems () { + subscribeToMovedItems() { for (const paneContainer of this.getPaneContainers()) { paneContainer.observePanes(pane => { - pane.onDidAddItem(({item}) => { + pane.onDidAddItem(({ item }) => { if (typeof item.getURI === 'function' && this.enablePersistence) { - const uri = item.getURI() + const uri = item.getURI(); if (uri) { - const location = paneContainer.getLocation() - let defaultLocation + const location = paneContainer.getLocation(); + let defaultLocation; if (typeof item.getDefaultLocation === 'function') { - defaultLocation = item.getDefaultLocation() + defaultLocation = item.getDefaultLocation(); } - defaultLocation = defaultLocation || 'center' + defaultLocation = defaultLocation || 'center'; if (location === defaultLocation) { - this.itemLocationStore.delete(item.getURI()) + this.itemLocationStore.delete(item.getURI()); } else { - this.itemLocationStore.save(item.getURI(), location) + this.itemLocationStore.save(item.getURI(), location); } } } - }) - }) + }); + }); } } @@ -567,60 +685,75 @@ module.exports = class Workspace extends Model { const projectPaths = left != null ? left : [] const item = this.getActivePaneItem() if (item) { - itemPath = typeof item.getPath === 'function' ? item.getPath() : undefined - const longTitle = typeof item.getLongTitle === 'function' ? item.getLongTitle() : undefined - itemTitle = longTitle == null - ? (typeof item.getTitle === 'function' ? item.getTitle() : undefined) - : longTitle + itemPath = + typeof item.getPath === 'function' ? item.getPath() : 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) - ) + 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 = [] - if ((item != null) && (projectPath != null)) { - titleParts.push(itemTitle, projectPath) - representedPath = itemPath != null ? itemPath : 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 + 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 ') - this.applicationDelegate.setRepresentedFilename(representedPath) - this.emitter.emit('did-change-window-title') + document.title = titleParts.join(' \u2014 '); + this.applicationDelegate.setRepresentedFilename(representedPath); + this.emitter.emit('did-change-window-title'); } // On macOS, fades the application window's proxy icon when the current file // has been modified. - updateDocumentEdited () { - const activePaneItem = this.getActivePaneItem() - const modified = activePaneItem != null && typeof activePaneItem.isModified === 'function' - ? activePaneItem.isModified() || false - : false - this.applicationDelegate.setWindowDocumentEdited(modified) + updateDocumentEdited() { + const activePaneItem = this.getActivePaneItem(); + const modified = + activePaneItem != null && typeof activePaneItem.isModified === 'function' + ? activePaneItem.isModified() || false + : false; + this.applicationDelegate.setWindowDocumentEdited(modified); } /* Section: Event Subscription */ - onDidChangeActivePaneContainer (callback) { - return this.emitter.on('did-change-active-pane-container', callback) + onDidChangeActivePaneContainer(callback) { + return this.emitter.on('did-change-active-pane-container', callback); } // Essential: Invoke the given callback with all current and future text @@ -631,9 +764,11 @@ module.exports = 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 @@ -644,10 +779,12 @@ module.exports = 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) { + observePaneItems(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.observePaneItems(callback)) - ) + ...this.getPaneContainers().map(container => + container.observePaneItems(callback) + ) + ); } // Essential: Invoke the given callback when the active pane item changes. @@ -661,8 +798,8 @@ module.exports = class Workspace extends Model { // * `item` The active pane item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePaneItem (callback) { - return this.emitter.on('did-change-active-pane-item', callback) + onDidChangeActivePaneItem(callback) { + return this.emitter.on('did-change-active-pane-item', callback); } // Essential: Invoke the given callback when the active pane item stops @@ -679,8 +816,8 @@ module.exports = class Workspace extends Model { // * `item` The active pane item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidStopChangingActivePaneItem (callback) { - return this.emitter.on('did-stop-changing-active-pane-item', callback) + onDidStopChangingActivePaneItem(callback) { + return this.emitter.on('did-stop-changing-active-pane-item', callback); } // Essential: Invoke the given callback when a text editor becomes the active @@ -691,8 +828,8 @@ module.exports = class Workspace extends Model { // active text editor. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActiveTextEditor (callback) { - return this.emitter.on('did-change-active-text-editor', callback) + onDidChangeActiveTextEditor(callback) { + return this.emitter.on('did-change-active-text-editor', callback); } // Essential: Invoke the given callback with the current active pane item and @@ -702,9 +839,9 @@ module.exports = class Workspace extends Model { // * `item` The current active pane item. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActivePaneItem (callback) { - callback(this.getActivePaneItem()) - return this.onDidChangeActivePaneItem(callback) + observeActivePaneItem(callback) { + callback(this.getActivePaneItem()); + return this.onDidChangeActivePaneItem(callback); } // Essential: Invoke the given callback with the current active text editor @@ -716,10 +853,10 @@ module.exports = class Workspace extends Model { // active text editor. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeActiveTextEditor (callback) { - callback(this.getActiveTextEditor()) + observeActiveTextEditor(callback) { + callback(this.getActiveTextEditor()); - return this.onDidChangeActiveTextEditor(callback) + return this.onDidChangeActiveTextEditor(callback); } // Essential: Invoke the given callback whenever an item is opened. Unlike @@ -734,8 +871,8 @@ module.exports = 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. @@ -745,10 +882,12 @@ module.exports = class Workspace extends Model { // * `pane` The added pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidAddPane (callback) { + onDidAddPane(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.onDidAddPane(callback)) - ) + ...this.getPaneContainers().map(container => + container.onDidAddPane(callback) + ) + ); } // Extended: Invoke the given callback before a pane is destroyed in the @@ -759,10 +898,12 @@ module.exports = class Workspace extends Model { // * `pane` The pane to be destroyed. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onWillDestroyPane (callback) { + onWillDestroyPane(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.onWillDestroyPane(callback)) - ) + ...this.getPaneContainers().map(container => + container.onWillDestroyPane(callback) + ) + ); } // Extended: Invoke the given callback when a pane is destroyed in the @@ -773,10 +914,12 @@ module.exports = class Workspace extends Model { // * `pane` The destroyed pane. // // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidDestroyPane (callback) { + onDidDestroyPane(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.onDidDestroyPane(callback)) - ) + ...this.getPaneContainers().map(container => + container.onDidDestroyPane(callback) + ) + ); } // Extended: Invoke the given callback with all current and future panes in the @@ -787,10 +930,12 @@ module.exports = 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) { + observePanes(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.observePanes(callback)) - ) + ...this.getPaneContainers().map(container => + container.observePanes(callback) + ) + ); } // Extended: Invoke the given callback when the active pane changes. @@ -799,8 +944,8 @@ module.exports = 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.emitter.on('did-change-active-pane', callback) + onDidChangeActivePane(callback) { + return this.emitter.on('did-change-active-pane', callback); } // Extended: Invoke the given callback with the current active pane and when @@ -811,9 +956,9 @@ module.exports = 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) { - callback(this.getActivePane()) - return this.onDidChangeActivePane(callback) + observeActivePane(callback) { + callback(this.getActivePane()); + return this.onDidChangeActivePane(callback); } // Extended: Invoke the given callback when a pane item is added to the @@ -826,10 +971,12 @@ module.exports = 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) { + onDidAddPaneItem(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.onDidAddPaneItem(callback)) - ) + ...this.getPaneContainers().map(container => + container.onDidAddPaneItem(callback) + ) + ); } // Extended: Invoke the given callback when a pane item is about to be @@ -844,10 +991,12 @@ module.exports = class Workspace extends Model { // its pane. // // Returns a {Disposable} on which `.dispose` can be called to unsubscribe. - onWillDestroyPaneItem (callback) { + onWillDestroyPaneItem(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.onWillDestroyPaneItem(callback)) - ) + ...this.getPaneContainers().map(container => + container.onWillDestroyPaneItem(callback) + ) + ); } // Extended: Invoke the given callback when a pane item is destroyed. @@ -860,10 +1009,12 @@ module.exports = class Workspace extends Model { // pane. // // Returns a {Disposable} on which `.dispose` can be called to unsubscribe. - onDidDestroyPaneItem (callback) { + onDidDestroyPaneItem(callback) { return new CompositeDisposable( - ...this.getPaneContainers().map(container => container.onDidDestroyPaneItem(callback)) - ) + ...this.getPaneContainers().map(container => + container.onDidDestroyPaneItem(callback) + ) + ); } // Extended: Invoke the given callback when a text editor is added to the @@ -877,12 +1028,12 @@ module.exports = 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); } - onDidChangeWindowTitle (callback) { - return this.emitter.on('did-change-window-title', callback) + onDidChangeWindowTitle(callback) { + return this.emitter.on('did-change-window-title', callback); } /* @@ -924,75 +1075,80 @@ module.exports = class Workspace extends Model { // should almost always be omitted to honor user preference. // // Returns a {Promise} that resolves to the {TextEditor} for the file URI. - async open (itemOrURI, options = {}) { - let uri, item + async open(itemOrURI, options = {}) { + let uri, item; if (typeof itemOrURI === 'string') { - uri = this.project.resolvePath(itemOrURI) + uri = this.project.resolvePath(itemOrURI); } else if (itemOrURI) { - item = itemOrURI - if (typeof item.getURI === 'function') uri = item.getURI() + item = itemOrURI; + if (typeof item.getURI === 'function') uri = item.getURI(); } - let resolveItem = () => {} + let resolveItem = () => {}; if (uri) { - const incomingItem = this.incoming.get(uri) + const incomingItem = this.incoming.get(uri); if (!incomingItem) { - this.incoming.set(uri, new Promise(resolve => { resolveItem = resolve })) + this.incoming.set( + uri, + new Promise(resolve => { + resolveItem = resolve; + }) + ); } else { - await incomingItem + await incomingItem; } } try { 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 && (!url.parse(uri).protocol || process.platform === 'win32')) { - this.applicationDelegate.addRecentDocument(uri) + this.applicationDelegate.addRecentDocument(uri); } - let pane, itemExistsInWorkspace + let pane, itemExistsInWorkspace; // Try to find an existing item in the workspace. if (item || uri) { if (options.pane) { - pane = options.pane + pane = options.pane; } else if (options.searchAllPanes) { - pane = item ? this.paneForItem(item) : this.paneForURI(uri) + pane = item ? this.paneForItem(item) : this.paneForURI(uri); } else { // If an item with the given URI is already in the workspace, assume // that item's pane container is the preferred location for that URI. - let container - if (uri) container = this.paneContainerForURI(uri) - if (!container) container = this.getActivePaneContainer() + let container; + if (uri) container = this.paneContainerForURI(uri); + if (!container) container = this.getActivePaneContainer(); // The `split` option affects where we search for the item. - pane = container.getActivePane() + pane = container.getActivePane(); switch (options.split) { case 'left': - pane = pane.findLeftmostSibling() - break + pane = pane.findLeftmostSibling(); + break; case 'right': - pane = pane.findRightmostSibling() - break + pane = pane.findRightmostSibling(); + break; case 'up': - pane = pane.findTopmostSibling() - break + pane = pane.findTopmostSibling(); + break; case 'down': - pane = pane.findBottommostSibling() - break + pane = pane.findBottommostSibling(); + break; } } if (pane) { if (item) { - itemExistsInWorkspace = pane.getItems().includes(item) + itemExistsInWorkspace = pane.getItems().includes(item); } else { - item = pane.itemForURI(uri) - itemExistsInWorkspace = item != null + item = pane.itemForURI(uri); + itemExistsInWorkspace = item != null; } } } @@ -1000,90 +1156,97 @@ module.exports = class Workspace extends Model { // If we already have an item at this stage, we won't need to do an async // lookup of the URI, so we yield the event loop to ensure this method // is consistently asynchronous. - if (item) await Promise.resolve() + if (item) await Promise.resolve(); if (!itemExistsInWorkspace) { - item = item || await this.createItemForURI(uri, options) - if (!item) return + item = item || (await this.createItemForURI(uri, options)); + if (!item) return; if (options.pane) { - pane = options.pane + pane = options.pane; } else { - let location = options.location + let location = options.location; if (!location && !options.split && uri && this.enablePersistence) { - location = await this.itemLocationStore.load(uri) + location = await this.itemLocationStore.load(uri); } if (!location && typeof item.getDefaultLocation === 'function') { - location = item.getDefaultLocation() + location = item.getDefaultLocation(); } - const allowedLocations = typeof item.getAllowedLocations === 'function' ? item.getAllowedLocations() : ALL_LOCATIONS - location = allowedLocations.includes(location) ? location : allowedLocations[0] + const allowedLocations = + typeof item.getAllowedLocations === 'function' + ? item.getAllowedLocations() + : ALL_LOCATIONS; + location = allowedLocations.includes(location) + ? location + : allowedLocations[0]; - const container = this.paneContainers[location] || this.getCenter() - pane = container.getActivePane() + const container = this.paneContainers[location] || this.getCenter(); + pane = container.getActivePane(); switch (options.split) { case 'left': - pane = pane.findLeftmostSibling() - break + pane = pane.findLeftmostSibling(); + break; case 'right': - pane = pane.findOrCreateRightmostSibling() - break + pane = pane.findOrCreateRightmostSibling(); + break; case 'up': - pane = pane.findTopmostSibling() - break + pane = pane.findTopmostSibling(); + break; case 'down': - pane = pane.findOrCreateBottommostSibling() - break + pane = pane.findOrCreateBottommostSibling(); + break; } } } - if (!options.pending && (pane.getPendingItem() === item)) { - pane.clearPendingItem() + if (!options.pending && pane.getPendingItem() === item) { + pane.clearPendingItem(); } - this.itemOpened(item) + this.itemOpened(item); if (options.activateItem === false) { - pane.addItem(item, {pending: options.pending}) + pane.addItem(item, { pending: options.pending }); } else { - pane.activateItem(item, {pending: options.pending}) + pane.activateItem(item, { pending: options.pending }); } if (options.activatePane !== false) { - pane.activate() + pane.activate(); } - let initialColumn = 0 - let initialLine = 0 + let initialColumn = 0; + let initialLine = 0; if (!Number.isNaN(options.initialLine)) { - initialLine = options.initialLine + initialLine = options.initialLine; } if (!Number.isNaN(options.initialColumn)) { - initialColumn = options.initialColumn + initialColumn = options.initialColumn; } if (initialLine >= 0 || initialColumn >= 0) { if (typeof item.setCursorBufferPosition === 'function') { - item.setCursorBufferPosition([initialLine, initialColumn]) + item.setCursorBufferPosition([initialLine, initialColumn]); } if (typeof item.unfoldBufferRow === 'function') { - item.unfoldBufferRow(initialLine) + item.unfoldBufferRow(initialLine); } if (typeof item.scrollToBufferPosition === 'function') { - item.scrollToBufferPosition([initialLine, initialColumn], { center: true }) + item.scrollToBufferPosition([initialLine, initialColumn], { + center: true + }); } } - const index = pane.getActiveItemIndex() - this.emitter.emit('did-open', {uri, pane, item, index}) + const index = pane.getActiveItemIndex(); + this.emitter.emit('did-open', { uri, pane, item, index }); if (uri) { - this.incoming.delete(uri) + this.incoming.delete(uri); } } finally { - resolveItem() + resolveItem(); } - return item + return item; } // Essential: Search the workspace for items matching the given URI and hide them. @@ -1092,35 +1255,34 @@ module.exports = class Workspace extends Model { // of the item to hide. // // Returns a {Boolean} indicating whether any items were found (and hidden). - hide (itemOrURI) { - let foundItems = false + hide(itemOrURI) { + let foundItems = false; // If any visible item has the given URI, hide it for (const container of this.getPaneContainers()) { - const isCenter = container === this.getCenter() + const isCenter = container === this.getCenter(); if (isCenter || container.isVisible()) { for (const pane of container.getPanes()) { - const activeItem = pane.getActiveItem() - const foundItem = ( - activeItem != null && ( - activeItem === itemOrURI || - typeof activeItem.getURI === 'function' && activeItem.getURI() === itemOrURI - ) - ) + const activeItem = pane.getActiveItem(); + const foundItem = + activeItem != null && + (activeItem === itemOrURI || + (typeof activeItem.getURI === 'function' && + activeItem.getURI() === itemOrURI)); if (foundItem) { - foundItems = true + foundItems = true; // We can't really hide the center so we just destroy the item. if (isCenter) { - pane.destroyItem(activeItem) + pane.destroyItem(activeItem); } else { - container.hide() + container.hide(); } } } } } - return foundItems + return foundItems; } // Essential: Search the workspace for items matching the given URI. If any are found, hide them. @@ -1130,17 +1292,17 @@ module.exports = class Workspace extends Model { // of the item to toggle. // // Returns a Promise that resolves when the item is shown or hidden. - toggle (itemOrURI) { + toggle(itemOrURI) { if (this.hide(itemOrURI)) { - return Promise.resolve() + return Promise.resolve(); } else { - return this.open(itemOrURI, {searchAllPanes: true}) + return this.open(itemOrURI, { searchAllPanes: true }); } } // 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 @@ -1157,35 +1319,37 @@ 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 = {}) { - 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; - const uri = this.project.resolvePath(uri_) - let item = this.getActivePane().itemForURI(uri) - if (uri && (item == null)) { + const uri = this.project.resolvePath(uri_); + let item = this.getActivePane().itemForURI(uri); + if (uri && item == null) { for (const opener of this.getOpeners()) { - item = opener(uri, options) - if (item) break + item = opener(uri, options); + if (item) break; } } if (item == null) { - item = this.project.openSync(uri, {initialLine, initialColumn}) + item = this.project.openSync(uri, { initialLine, initialColumn }); } if (activateItem) { - this.getActivePane().activateItem(item) + this.getActivePane().activateItem(item); } - this.itemOpened(item) + this.itemOpened(item); if (activatePane) { - this.getActivePane().activate() + this.getActivePane().activate(); } - return item + return item; } - openURIInPane (uri, pane) { - return this.open(uri, {pane}) + openURIInPane(uri, pane) { + return this.open(uri, { pane }); } // Public: Creates a new item that corresponds to the provided URI. @@ -1196,24 +1360,26 @@ module.exports = class Workspace extends Model { // * `uri` A {String} containing a URI. // // Returns a {Promise} that resolves to the {TextEditor} (or other item) for the given URI. - async createItemForURI (uri, options) { + async createItemForURI(uri, options) { if (uri != null) { for (const opener of this.getOpeners()) { - const item = opener(uri, options) - if (item != null) return item + const item = opener(uri, options); + if (item != null) return item; } } try { - const item = await this.openTextFile(uri, options) - return item + const item = await this.openTextFile(uri, options); + return item; } catch (error) { switch (error.code) { case 'CANCELLED': - return Promise.resolve() + return Promise.resolve(); case 'EACCES': - this.notificationManager.addWarning(`Permission denied '${error.path}'`) - return Promise.resolve() + this.notificationManager.addWarning( + `Permission denied '${error.path}'` + ); + return Promise.resolve(); case 'EPERM': case 'EBUSY': case 'ENXIO': @@ -1227,86 +1393,99 @@ module.exports = class Workspace extends Model { case 'EAGAIN': this.notificationManager.addWarning( `Unable to open '${error.path != null ? error.path : uri}'`, - {detail: error.message} - ) - return Promise.resolve() + { detail: error.message } + ); + return Promise.resolve(); default: - throw error + throw error; } } } - async openTextFile (uri, options) { - const filePath = this.project.resolvePath(uri) + async 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 + throw error; } } } - const fileSize = fs.getSizeSync(filePath) + const fileSize = fs.getSizeSync(filePath); - if (fileSize >= (this.config.get('core.warnOnLargeFileLimit') * 1048576)) { // 40MB by default + if (fileSize >= this.config.get('core.warnOnLargeFileLimit') * 1048576) { + // 40MB by default await new Promise((resolve, reject) => { - this.applicationDelegate.confirm({ - message: 'Atom will be unresponsive during the loading of very large files.', - detail: 'Do you still want to load this file?', - buttons: ['Proceed', 'Cancel'] - }, response => { - if (response === 1) { - const error = new Error() - error.code = 'CANCELLED' - reject(error) - } else { - resolve() + this.applicationDelegate.confirm( + { + message: + 'Atom will be unresponsive during the loading of very large files.', + detail: 'Do you still want to load this file?', + buttons: ['Proceed', 'Cancel'] + }, + response => { + if (response === 1) { + const error = new Error(); + error.code = 'CANCELLED'; + reject(error); + } else { + resolve(); + } } - }) - }) + ); + }); } - const buffer = await this.project.bufferForPath(filePath, options) - return this.textEditorRegistry.build(Object.assign({buffer, autoHeight: false}, options)) + const buffer = await this.project.bufferForPath(filePath, options); + return this.textEditorRegistry.build( + Object.assign({ buffer, autoHeight: false }, options) + ); } - handleGrammarUsed (grammar) { - if (grammar == null) { return } - this.packageManager.triggerActivationHook(`${grammar.scopeName}:root-scope-used`) - this.packageManager.triggerActivationHook(`${grammar.packageName}:grammar-used`) + handleGrammarUsed(grammar) { + if (grammar == null) { + return; + } + this.packageManager.triggerActivationHook( + `${grammar.scopeName}:root-scope-used` + ); + 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) - const subscription = this.textEditorRegistry.maintainConfig(editor) - editor.onDidDestroy(() => subscription.dispose()) - return editor + buildTextEditor(params) { + const editor = this.textEditorRegistry.build(params); + const subscription = this.textEditorRegistry.maintainConfig(editor); + editor.onDidDestroy(() => subscription.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 () { - const uri = this.destroyedItemURIs.pop() + reopenItem() { + const uri = this.destroyedItemURIs.pop(); if (uri) { - return this.open(uri) + return this.open(uri); } else { - return Promise.resolve() + return Promise.resolve(); } } @@ -1339,13 +1518,15 @@ module.exports = 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(() => { _.remove(this.openers, opener) }) + addOpener(opener) { + this.openers.push(opener); + return new Disposable(() => { + _.remove(this.openers, opener); + }); } - getOpeners () { - return this.openers + getOpeners() { + return this.openers; } /* @@ -1355,44 +1536,48 @@ module.exports = class Workspace extends Model { // Essential: Get all pane items in the workspace. // // Returns an {Array} of items. - getPaneItems () { - return _.flatten(this.getPaneContainers().map(container => container.getPaneItems())) + getPaneItems() { + return _.flatten( + this.getPaneContainers().map(container => container.getPaneItems()) + ); } // Essential: Get the active {Pane}'s active item. // // Returns an pane item {Object}. - getActivePaneItem () { - return this.getActivePaneContainer().getActivePaneItem() + getActivePaneItem() { + return this.getActivePaneContainer().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 workspace center's active item if it is a {TextEditor}. // // Returns a {TextEditor} or `undefined` if the workspace center's current // active item is not a {TextEditor}. - getActiveTextEditor () { - const activeItem = this.getCenter().getActivePaneItem() - if (activeItem instanceof TextEditor) { return activeItem } + getActiveTextEditor() { + const activeItem = this.getCenter().getActivePaneItem(); + if (activeItem instanceof TextEditor) { + return activeItem; + } } // Save all pane items. - saveAll () { + saveAll() { this.getPaneContainers().forEach(container => { - container.saveAll() - }) + container.saveAll(); + }); } - confirmClose (options) { - return Promise.all(this.getPaneContainers().map(container => - container.confirmClose(options) - )).then((results) => !results.includes(false)) + confirmClose(options) { + return Promise.all( + this.getPaneContainers().map(container => container.confirmClose(options)) + ).then(results => !results.includes(false)); } // Save the active pane item. @@ -1401,8 +1586,10 @@ module.exports = 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.getCenter().getActivePane().saveActiveItem() + saveActivePaneItem() { + return this.getCenter() + .getActivePane() + .saveActiveItem(); } // Prompt the user for a path and save the active pane item to it. @@ -1410,16 +1597,18 @@ module.exports = 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 () { - this.getCenter().getActivePane().saveActiveItemAs() + saveActivePaneItemAs() { + this.getCenter() + .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(); } /* @@ -1429,36 +1618,40 @@ module.exports = class Workspace extends Model { // Extended: Get the most recently focused pane container. // // Returns a {Dock} or the {WorkspaceCenter}. - getActivePaneContainer () { - return this.activePaneContainer + getActivePaneContainer() { + return this.activePaneContainer; } // Extended: Get all panes in the workspace. // // Returns an {Array} of {Pane}s. - getPanes () { - return _.flatten(this.getPaneContainers().map(container => container.getPanes())) + getPanes() { + return _.flatten( + this.getPaneContainers().map(container => container.getPanes()) + ); } - getVisiblePanes () { - return _.flatten(this.getVisiblePaneContainers().map(container => container.getPanes())) + getVisiblePanes() { + return _.flatten( + this.getVisiblePaneContainers().map(container => container.getPanes()) + ); } // Extended: Get the active {Pane}. // // Returns a {Pane}. - getActivePane () { - return this.getActivePaneContainer().getActivePane() + getActivePane() { + return this.getActivePaneContainer().getActivePane(); } // Extended: Make the next pane active. - activateNextPane () { - return this.getActivePaneContainer().activateNextPane() + activateNextPane() { + return this.getActivePaneContainer().activateNextPane(); } // Extended: Make the previous pane active. - activatePreviousPane () { - return this.getActivePaneContainer().activatePreviousPane() + activatePreviousPane() { + return this.getActivePaneContainer().activatePreviousPane(); } // Extended: Get the first pane container that contains an item with the given @@ -1468,8 +1661,10 @@ module.exports = class Workspace extends Model { // // Returns a {Dock}, the {WorkspaceCenter}, or `undefined` if no item exists // with the given URI. - paneContainerForURI (uri) { - return this.getPaneContainers().find(container => container.paneForURI(uri)) + paneContainerForURI(uri) { + return this.getPaneContainers().find(container => + container.paneForURI(uri) + ); } // Extended: Get the first pane container that contains the given item. @@ -1478,8 +1673,10 @@ module.exports = class Workspace extends Model { // // Returns a {Dock}, the {WorkspaceCenter}, or `undefined` if no item exists // with the given URI. - paneContainerForItem (uri) { - return this.getPaneContainers().find(container => container.paneForItem(uri)) + paneContainerForItem(uri) { + return this.getPaneContainers().find(container => + container.paneForItem(uri) + ); } // Extended: Get the first {Pane} that contains an item with the given URI. @@ -1487,11 +1684,11 @@ module.exports = class Workspace extends Model { // * `uri` {String} uri // // Returns a {Pane} or `undefined` if no item exists with the given URI. - paneForURI (uri) { + paneForURI(uri) { for (let location of this.getPaneContainers()) { - const pane = location.paneForURI(uri) + const pane = location.paneForURI(uri); if (pane != null) { - return pane + return pane; } } } @@ -1501,102 +1698,104 @@ module.exports = class Workspace extends Model { // * `item` the Item that the returned pane must contain. // // Returns a {Pane} or `undefined` if no pane exists for the given item. - paneForItem (item) { + paneForItem(item) { for (let location of this.getPaneContainers()) { - const pane = location.paneForItem(item) + const pane = location.paneForItem(item); if (pane != null) { - return pane + return pane; } } } // Destroy (close) the active pane. - destroyActivePane () { - const activePane = this.getActivePane() + destroyActivePane() { + const activePane = this.getActivePane(); if (activePane != null) { - activePane.destroy() + activePane.destroy(); } } // Close the active center pane item, or the active center pane if it is // empty, or the current window if there is only the empty root pane. - closeActivePaneItemOrEmptyPaneOrWindow () { + closeActivePaneItemOrEmptyPaneOrWindow() { if (this.getCenter().getActivePaneItem() != null) { - this.getCenter().getActivePane().destroyActiveItem() + this.getCenter() + .getActivePane() + .destroyActiveItem(); } else if (this.getCenter().getPanes().length > 1) { - this.getCenter().destroyActivePane() + this.getCenter().destroyActivePane(); } else if (this.config.get('core.closeEmptyWindows')) { - atom.close() + atom.close(); } } // Increase the editor font size by 1px. - increaseFontSize () { - this.config.set('editor.fontSize', this.config.get('editor.fontSize') + 1) + increaseFontSize() { + 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') + decreaseFontSize() { + const fontSize = this.config.get('editor.fontSize'); if (fontSize > 1) { - this.config.set('editor.fontSize', fontSize - 1) + this.config.set('editor.fontSize', fontSize - 1); } } // Restore to the window's original editor font size. - resetFontSize () { + resetFontSize() { if (this.originalFontSize) { - this.config.set('editor.fontSize', this.originalFontSize) + this.config.set('editor.fontSize', this.originalFontSize); } } - subscribeToFontSize () { + subscribeToFontSize() { return this.config.onDidChange('editor.fontSize', () => { if (this.originalFontSize == null) { - this.originalFontSize = this.config.get('editor.fontSize') + this.originalFontSize = this.config.get('editor.fontSize'); } - }) + }); } // 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) { - _.remove(this.destroyedItemURIs, uri) + _.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) { - this.destroyedItemURIs.push(uri) + this.destroyedItemURIs.push(uri); } } // Called by Model superclass when destroyed - destroyed () { - this.paneContainers.center.destroy() - this.paneContainers.left.destroy() - this.paneContainers.right.destroy() - this.paneContainers.bottom.destroy() - this.cancelStoppedChangingActivePaneItemTimeout() + destroyed() { + this.paneContainers.center.destroy(); + this.paneContainers.left.destroy(); + this.paneContainers.right.destroy(); + this.paneContainers.bottom.destroy(); + this.cancelStoppedChangingActivePaneItemTimeout(); if (this.activeItemSubscriptions != null) { - this.activeItemSubscriptions.dispose() + this.activeItemSubscriptions.dispose(); } - if (this.element) this.element.destroy() + if (this.element) this.element.destroy(); } /* @@ -1604,38 +1803,39 @@ module.exports = class Workspace extends Model { */ // Essential: Get the {WorkspaceCenter} at the center of the editor window. - getCenter () { - return this.paneContainers.center + getCenter() { + return this.paneContainers.center; } // Essential: Get the {Dock} to the left of the editor window. - getLeftDock () { - return this.paneContainers.left + getLeftDock() { + return this.paneContainers.left; } // Essential: Get the {Dock} to the right of the editor window. - getRightDock () { - return this.paneContainers.right + getRightDock() { + return this.paneContainers.right; } // Essential: Get the {Dock} below the editor window. - getBottomDock () { - return this.paneContainers.bottom + getBottomDock() { + return this.paneContainers.bottom; } - getPaneContainers () { + getPaneContainers() { return [ this.paneContainers.center, this.paneContainers.left, this.paneContainers.right, this.paneContainers.bottom - ] + ]; } - getVisiblePaneContainers () { - const center = this.getCenter() - return atom.workspace.getPaneContainers() - .filter(container => container === center || container.isVisible()) + getVisiblePaneContainers() { + const center = this.getCenter(); + return atom.workspace + .getPaneContainers() + .filter(container => container === center || container.isVisible()); } /* @@ -1653,8 +1853,8 @@ module.exports = 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. @@ -1669,13 +1869,13 @@ module.exports = 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. @@ -1690,13 +1890,13 @@ module.exports = 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. @@ -1711,13 +1911,13 @@ module.exports = 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. @@ -1732,13 +1932,13 @@ module.exports = 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. @@ -1753,13 +1953,13 @@ module.exports = 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. @@ -1774,13 +1974,13 @@ module.exports = 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. @@ -1800,30 +2000,36 @@ module.exports = class Workspace extends Model { // (default: false) // // 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, this.viewRegistry)) + addPanel(location, options) { + if (options == null) { + options = {}; + } + return this.panelContainers[location].addPanel( + new Panel(options, this.viewRegistry) + ); } /* @@ -1845,54 +2051,56 @@ 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) { + scan(regex, options = {}, iterator) { 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 (const directory of this.project.getDirectories()) { - let searcher = options.ripgrep ? this.ripgrepDirectorySearcher : this.scandalDirectorySearcher + let searcher = options.ripgrep + ? this.ripgrepDirectorySearcher + : this.scandalDirectorySearcher; for (const 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. - let onPathsSearched + 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. - 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 || [], @@ -1904,31 +2112,35 @@ module.exports = class Workspace extends Model { trailingContextLineCount: options.trailingContextLineCount || 0, didMatch: result => { if (!this.project.isPathModified(result.filePath)) { - return iterator(result) + return iterator(result); } }, - didError (error) { - return iterator(null, error) + didError(error) { + return iterator(null, error); }, - didSearchPaths (count) { - return onPathsSearched(searcher, count) + didSearchPaths(count) { + return onPathsSearched(searcher, count); } - } - const directorySearcher = searcher.search(directories, regex, searchOptions) - allSearches.push(directorySearcher) - }) - const searchPromise = Promise.all(allSearches) + }; + 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() + const filePath = buffer.getPath(); if (!this.project.contains(filePath)) { - continue + continue; } - var matches = [] - buffer.scan(regex, match => matches.push(match)) + var matches = []; + buffer.scan(regex, match => matches.push(match)); if (matches.length > 0) { - iterator({ filePath, matches }) + iterator({ filePath, matches }); } } } @@ -1937,32 +2149,34 @@ module.exports = 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 + let isCancelled = false; const cancellablePromise = new Promise((resolve, reject) => { - const onSuccess = function () { + const onSuccess = function() { if (isCancelled) { - resolve('cancelled') + resolve('cancelled'); } else { - resolve(null) + resolve(null); } - } + }; - const onFailure = function (error) { - for (let promise of allSearches) { promise.cancel() } - reject(error) - } + const onFailure = function(error) { + for (let promise of allSearches) { + promise.cancel(); + } + reject(error); + }; - searchPromise.then(onSuccess, onFailure) - }) + searchPromise.then(onSuccess, onFailure); + }); cancellablePromise.cancel = () => { - isCancelled = true + 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. - allSearches.map((promise) => promise.cancel()) - } + allSearches.map(promise => promise.cancel()); + }; - return cancellablePromise + return cancellablePromise; } // Public: Performs a replace across all the specified files in the project. @@ -1974,24 +2188,30 @@ module.exports = class Workspace extends Model { // * `options` {Object} with keys `filePath` and `replacements`. // // Returns a {Promise}. - replace (regex, replacementText, filePaths, iterator) { + replace(regex, replacementText, filePaths, iterator) { return new Promise((resolve, reject) => { - let buffer - const openPaths = this.project.getBuffers().map(buffer => buffer.getPath()) - const outOfProcessPaths = _.difference(filePaths, openPaths) + let buffer; + const openPaths = this.project + .getBuffers() + .map(buffer => buffer.getPath()); + const outOfProcessPaths = _.difference(filePaths, openPaths); - let inProcessFinished = !openPaths.length - let outOfProcessFinished = !outOfProcessPaths.length + let inProcessFinished = !openPaths.length; + let outOfProcessFinished = !outOfProcessPaths.length; const checkFinished = () => { if (outOfProcessFinished && inProcessFinished) { - resolve() + resolve(); } - } + }; if (!outOfProcessFinished.length) { - let flags = 'g' - if (regex.multiline) { flags += 'm' } - if (regex.ignoreCase) { flags += 'i' } + let flags = 'g'; + if (regex.multiline) { + flags += 'm'; + } + if (regex.ignoreCase) { + flags += 'i'; + } const task = Task.once( require.resolve('./replace-handler'), @@ -2000,46 +2220,55 @@ module.exports = class Workspace extends Model { flags, replacementText, () => { - outOfProcessFinished = true - checkFinished() + outOfProcessFinished = true; + checkFinished(); } - ) + ); - task.on('replace:path-replaced', iterator) - task.on('replace:file-error', error => { iterator(null, error) }) + task.on('replace:path-replaced', iterator); + task.on('replace:file-error', error => { + iterator(null, error); + }); } for (buffer of this.project.getBuffers()) { - if (!filePaths.includes(buffer.getPath())) { continue } - const replacements = buffer.replace(regex, replacementText, iterator) + if (!filePaths.includes(buffer.getPath())) { + continue; + } + const replacements = buffer.replace(regex, replacementText, iterator); if (replacements) { - iterator({filePath: buffer.getPath(), replacements}) + iterator({ filePath: buffer.getPath(), replacements }); } } - inProcessFinished = true - checkFinished() - }) + inProcessFinished = true; + checkFinished(); + }); } - checkoutHeadRevision (editor) { + checkoutHeadRevision(editor) { if (editor.getPath()) { const checkoutHead = async () => { - const repository = await this.project.repositoryForDirectory(new Directory(editor.getDirectoryPath())) - if (repository) repository.checkoutHeadForEditor(editor) - } + const repository = await this.project.repositoryForDirectory( + new Directory(editor.getDirectoryPath()) + ); + if (repository) repository.checkoutHeadForEditor(editor); + }; if (this.config.get('editor.confirmCheckoutHeadRevision')) { - this.applicationDelegate.confirm({ - message: 'Confirm Checkout HEAD Revision', - detail: `Are you sure you want to discard all changes to "${editor.getFileName()}" since the last Git commit?`, - buttons: ['OK', 'Cancel'] - }, response => { - if (response === 0) checkoutHead() - }) + this.applicationDelegate.confirm( + { + message: 'Confirm Checkout HEAD Revision', + detail: `Are you sure you want to discard all changes to "${editor.getFileName()}" since the last Git commit?`, + buttons: ['OK', 'Cancel'] + }, + response => { + if (response === 0) checkoutHead(); + } + ); } else { - checkoutHead() + checkoutHead(); } } } -} +}; diff --git a/static/index.js b/static/index.js index 6d4f5e8bf..ffda2e79f 100644 --- a/static/index.js +++ b/static/index.js @@ -1,160 +1,205 @@ -(function () { +(function() { // Define the window start time before the requires so we get a more accurate // window:start marker. - const startWindowTime = Date.now() + const startWindowTime = Date.now(); - const electron = require('electron') - const path = require('path') - const Module = require('module') - const getWindowLoadSettings = require('../src/get-window-load-settings') - const StartupTime = require('../src/startup-time') - const entryPointDirPath = __dirname - let blobStore = null - let useSnapshot = false + const electron = require('electron'); + const path = require('path'); + const Module = require('module'); + const getWindowLoadSettings = require('../src/get-window-load-settings'); + const StartupTime = require('../src/startup-time'); + const entryPointDirPath = __dirname; + let blobStore = null; + let useSnapshot = false; - const startupMarkers = electron.remote.getCurrentWindow().startupMarkers + const startupMarkers = electron.remote.getCurrentWindow().startupMarkers; if (startupMarkers) { - StartupTime.importData(startupMarkers) + StartupTime.importData(startupMarkers); } - StartupTime.addMarker('window:start', startWindowTime) + StartupTime.addMarker('window:start', startWindowTime); - window.onload = function () { + window.onload = function() { try { - StartupTime.addMarker('window:onload:start') - const startTime = Date.now() + StartupTime.addMarker('window:onload:start'); + const startTime = Date.now(); - process.on('unhandledRejection', function (error, promise) { - console.error('Unhandled promise rejection %o with error: %o', promise, error) - }) + process.on('unhandledRejection', function(error, promise) { + console.error( + 'Unhandled promise rejection %o with error: %o', + promise, + error + ); + }); // Normalize to make sure drive letter case is consistent on Windows - process.resourcesPath = path.normalize(process.resourcesPath) + process.resourcesPath = path.normalize(process.resourcesPath); - setupAtomHome() - const devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) - useSnapshot = !devMode && typeof snapshotResult !== 'undefined' + setupAtomHome(); + const devMode = + getWindowLoadSettings().devMode || + !getWindowLoadSettings().resourcePath.startsWith( + process.resourcesPath + path.sep + ); + useSnapshot = !devMode && typeof snapshotResult !== 'undefined'; if (devMode) { - const metadata = require('../package.json') + const metadata = require('../package.json'); if (!metadata._deprecatedPackages) { try { - metadata._deprecatedPackages = require('../script/deprecated-packages.json') + metadata._deprecatedPackages = require('../script/deprecated-packages.json'); } catch (requireError) { - console.error('Failed to setup deprecated packages list', requireError.stack) + console.error( + 'Failed to setup deprecated packages list', + requireError.stack + ); } } } else if (useSnapshot) { - Module.prototype.require = function (module) { - const absoluteFilePath = Module._resolveFilename(module, this, false) - let relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) + Module.prototype.require = function(module) { + const absoluteFilePath = Module._resolveFilename(module, this, false); + let relativeFilePath = path.relative( + entryPointDirPath, + absoluteFilePath + ); if (process.platform === 'win32') { - relativeFilePath = relativeFilePath.replace(/\\/g, '/') + relativeFilePath = relativeFilePath.replace(/\\/g, '/'); } - let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] + let cachedModule = + snapshotResult.customRequire.cache[relativeFilePath]; if (!cachedModule) { - cachedModule = {exports: Module._load(module, this, false)} - snapshotResult.customRequire.cache[relativeFilePath] = cachedModule + cachedModule = { exports: Module._load(module, this, false) }; + snapshotResult.customRequire.cache[relativeFilePath] = cachedModule; } - return cachedModule.exports - } + return cachedModule.exports; + }; - snapshotResult.setGlobals(global, process, window, document, console, require) + snapshotResult.setGlobals( + global, + process, + window, + document, + console, + require + ); } - 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 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() + 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) + profileStartup(Date.now() - startTime); } else { - StartupTime.addMarker('window:setup-window:start') + StartupTime.addMarker('window:setup-window:start'); setupWindow().then(() => { - StartupTime.addMarker('window:setup-window:end') - }) - setLoadTime(Date.now() - startTime) + StartupTime.addMarker('window:setup-window:end'); + }); + setLoadTime(Date.now() - startTime); } } catch (error) { - handleSetupError(error) + handleSetupError(error); } - StartupTime.addMarker('window:onload:end') - } + StartupTime.addMarker('window:onload:end'); + }; - function setLoadTime (loadTime) { + function setLoadTime(loadTime) { if (global.atom) { - global.atom.loadTime = loadTime + global.atom.loadTime = loadTime; } } - function handleSetupError (error) { - const currentWindow = electron.remote.getCurrentWindow() - currentWindow.setSize(800, 600) - currentWindow.center() - currentWindow.show() - currentWindow.openDevTools() - console.error(error.stack || error) + function handleSetupError(error) { + const currentWindow = electron.remote.getCurrentWindow(); + currentWindow.setSize(800, 600); + currentWindow.center(); + currentWindow.show(); + currentWindow.openDevTools(); + console.error(error.stack || error); } - function setupWindow () { - const CompileCache = useSnapshot ? snapshotResult.customRequire('../src/compile-cache.js') : require('../src/compile-cache') - CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) - CompileCache.install(process.resourcesPath, require) + function setupWindow() { + const CompileCache = useSnapshot + ? snapshotResult.customRequire('../src/compile-cache.js') + : require('../src/compile-cache'); + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME); + CompileCache.install(process.resourcesPath, require); - const ModuleCache = useSnapshot ? snapshotResult.customRequire('../src/module-cache.js') : require('../src/module-cache') - ModuleCache.register(getWindowLoadSettings()) + const ModuleCache = useSnapshot + ? snapshotResult.customRequire('../src/module-cache.js') + : require('../src/module-cache'); + ModuleCache.register(getWindowLoadSettings()); - const startCrashReporter = useSnapshot ? snapshotResult.customRequire('../src/crash-reporter-start.js') : require('../src/crash-reporter-start') - startCrashReporter({_version: getWindowLoadSettings().appVersion}) + const startCrashReporter = useSnapshot + ? snapshotResult.customRequire('../src/crash-reporter-start.js') + : require('../src/crash-reporter-start'); + startCrashReporter({ _version: getWindowLoadSettings().appVersion }); - const CSON = useSnapshot ? snapshotResult.customRequire('../node_modules/season/lib/cson.js') : require('season') - CSON.setCacheDir(path.join(CompileCache.getCacheDirectory(), 'cson')) + 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 = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) + const initScriptPath = path.relative( + entryPointDirPath, + getWindowLoadSettings().windowInitializationScript + ); + const initialize = useSnapshot + ? snapshotResult.customRequire(initScriptPath) + : require(initScriptPath); - StartupTime.addMarker('window:initialize:start') + StartupTime.addMarker('window:initialize:start'); - return initialize({blobStore: blobStore}).then(function () { - StartupTime.addMarker('window:initialize:end') - electron.ipcRenderer.send('window-command', 'window:loaded') - }) + return initialize({ blobStore: blobStore }).then(function() { + StartupTime.addMarker('window:initialize:end'); + electron.ipcRenderer.send('window-command', 'window:loaded'); + }); } - function profileStartup (initialTime) { - function profile () { - console.profile('startup') - const 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') - }) + function profileStartup(initialTime) { + function profile() { + console.profile('startup'); + const 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 = electron.remote.getCurrentWindow().webContents + const webContents = electron.remote.getCurrentWindow().webContents; if (webContents.devToolsWebContents) { - profile() + profile(); } else { - webContents.once('devtools-opened', () => { setTimeout(profile, 1000) }) - webContents.openDevTools() + webContents.once('devtools-opened', () => { + setTimeout(profile, 1000); + }); + webContents.openDevTools(); } } - function setupAtomHome () { + function setupAtomHome() { if (process.env.ATOM_HOME) { - return + 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 + process.env.ATOM_HOME = getWindowLoadSettings().atomHome; } } -})() +})(); diff --git a/stylelint.config.js b/stylelint.config.js index a4a755a02..daed8d87b 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -1,28 +1,28 @@ const path = require('path'); module.exports = { - "extends": "stylelint-config-standard", - "ignoreFiles": [path.resolve(__dirname, "static", "atom.less")], - "rules": { - "color-hex-case": null, // TODO: enable? - "max-empty-lines": null, // TODO: enable? - "selector-type-no-unknown": null, - "function-comma-space-after": null, // TODO: enable? - "font-family-no-missing-generic-family-keyword": null, // needed for octicons (no sensible fallback) - "block-opening-brace-space-before": null, - "block-closing-brace-empty-line-before": null, - "declaration-colon-space-after": null, - "declaration-block-single-line-max-declarations": null, - "declaration-empty-line-before": null, // TODO: enable? - "declaration-block-trailing-semicolon": null, // TODO: enable - "no-descending-specificity": null, - "number-leading-zero": null, // TODO: enable? - "no-duplicate-selectors": null, - "selector-pseudo-element-colon-notation": null, // TODO: enable? - "selector-list-comma-newline-after": null, // TODO: enable? - "rule-empty-line-before": null, // TODO: enable? - "at-rule-empty-line-before": null, // TODO: enable? - "font-family-no-duplicate-names": null, // TODO: enable? - "unit-no-unknown": [true, {"ignoreUnits": [ "x" ]}], // Needed for -webkit-image-set 1x/2x units + extends: 'stylelint-config-standard', + ignoreFiles: [path.resolve(__dirname, 'static', 'atom.less')], + rules: { + 'color-hex-case': null, // TODO: enable? + 'max-empty-lines': null, // TODO: enable? + 'selector-type-no-unknown': null, + 'function-comma-space-after': null, // TODO: enable? + 'font-family-no-missing-generic-family-keyword': null, // needed for octicons (no sensible fallback) + 'block-opening-brace-space-before': null, + 'block-closing-brace-empty-line-before': null, + 'declaration-colon-space-after': null, + 'declaration-block-single-line-max-declarations': null, + 'declaration-empty-line-before': null, // TODO: enable? + 'declaration-block-trailing-semicolon': null, // TODO: enable + 'no-descending-specificity': null, + 'number-leading-zero': null, // TODO: enable? + 'no-duplicate-selectors': null, + 'selector-pseudo-element-colon-notation': null, // TODO: enable? + 'selector-list-comma-newline-after': null, // TODO: enable? + 'rule-empty-line-before': null, // TODO: enable? + 'at-rule-empty-line-before': null, // TODO: enable? + 'font-family-no-duplicate-names': null, // TODO: enable? + 'unit-no-unknown': [true, { ignoreUnits: ['x'] }] // Needed for -webkit-image-set 1x/2x units } -} +};