diff --git a/menus/linux.cson b/menus/linux.cson index 94fb90a30..2a1ca47f8 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -137,6 +137,7 @@ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } + { label: 'Run &Benchmarks', command: 'window:run-benchmarks' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/menus/win32.cson b/menus/win32.cson index 70bb1487d..553b6017e 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -136,6 +136,7 @@ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } { label: '&Reload Window', command: 'window:reload' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } + { label: 'Run &Benchmarks', command: 'window:run-benchmarks' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/package.json b/package.json index 71ce9bb07..699559ac7 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,12 @@ "random-words": "0.0.1", "resolve": "^1.1.6", "runas": "^3.1", - "scandal": "^3.0.0", + "scandal": "^3.1.0", "scoped-property-store": "^0.17.0", "scrollbar-style": "^3.2", "season": "^6.0.0", "semver": "^4.3.3", - "service-hub": "^0.7.2", + "service-hub": "^0.7.3", "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", @@ -97,10 +97,10 @@ "solarized-light-syntax": "1.1.2", "about": "1.7.5", "archive-view": "0.63.1", - "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.15.1", - "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.34.2", + "autocomplete-atom-api": "0.10.1", + "autocomplete-css": "0.15.2", + "autocomplete-html": "0.7.3", + "autocomplete-plus": "2.35.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.29.0", "autosave": "0.24.1", @@ -119,7 +119,7 @@ "go-to-line": "0.32.0", "grammar-selector": "0.49.3", "image-view": "0.61.2", - "incompatible-packages": "0.27.1", + "incompatible-packages": "0.27.2", "keybinding-resolver": "0.36.4", "line-ending-selector": "0.6.2", "link": "0.31.3", diff --git a/script/lib/create-windows-installer.js b/script/lib/create-windows-installer.js index 8a0dc0f61..22a22701c 100644 --- a/script/lib/create-windows-installer.js +++ b/script/lib/create-windows-installer.js @@ -10,7 +10,7 @@ const spawnSync = require('./spawn-sync') const CONFIG = require('../config') -module.exports = function (packagedAppPath, codeSign) { +module.exports = (packagedAppPath, codeSign) => { const archSuffix = process.arch === 'ia32' ? '' : '-' + process.arch const options = { appDirectory: packagedAppPath, @@ -23,7 +23,7 @@ module.exports = function (packagedAppPath, codeSign) { } const signing = codeSign && (process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL || process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) - let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH; + let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH if (signing) { if (!certPath) { @@ -42,7 +42,7 @@ module.exports = function (packagedAppPath, codeSign) { console.log('Skipping code-signing. Specify the --code-sign option and provide a ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable to perform code-signing'.gray) } - const cleanUp = function () { + const cleanUp = () => { if (fs.existsSync(certPath) && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) { console.log(`Deleting certificate at ${certPath}`) fs.removeSync(certPath) @@ -57,7 +57,7 @@ module.exports = function (packagedAppPath, codeSign) { } // Squirrel signs its own copy of the executables but we need them for the portable ZIP - const extractSignedExes = function() { + const extractSignedExes = () => { if (signing) { for (let nupkgPath of glob.sync(`${CONFIG.buildOutputPath}/*-full.nupkg`)) { if (nupkgPath.includes(CONFIG.appMetadata.version)) { @@ -73,12 +73,9 @@ module.exports = function (packagedAppPath, codeSign) { console.log(`Creating Windows Installer for ${packagedAppPath}`) return electronInstaller.createWindowsInstaller(options) - .then(extractSignedExes, function (error) { - console.log(`Extracting signed executables failed:\n${error}`) - cleanUp() - }) - .then(cleanUp, function (error) { - console.log(`Windows installer creation failed:\n${error}`) + .then(extractSignedExes) + .then(cleanUp, error => { cleanUp() + return Promise.reject(error) }) } diff --git a/script/lib/download-file-from-github.js b/script/lib/download-file-from-github.js index 2969ea2dc..13e04e99e 100644 --- a/script/lib/download-file-from-github.js +++ b/script/lib/download-file-from-github.js @@ -5,7 +5,7 @@ const path = require('path') const syncRequest = require('sync-request') module.exports = function (downloadURL, destinationPath) { - console.log(`Dowloading file from GitHub Repository to ${destinationPath}`) + console.log(`Downloading file from GitHub Repository to ${destinationPath}`) const response = syncRequest('GET', downloadURL, { 'headers': {'Accept': 'application/vnd.github.v3.raw', 'User-Agent': 'Atom Build'} }) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 81c69f63f..3cb771ec7 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4868,8 +4868,8 @@ describe "TextEditor", -> editor.replaceSelectedText {}, -> '123' expect(buffer.lineForRow(0)).toBe '123var quicksort = function () {' - editor.replaceSelectedText {selectWordIfEmpty: true}, -> 'var' editor.setCursorBufferPosition([0]) + editor.replaceSelectedText {selectWordIfEmpty: true}, -> 'var' expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {' editor.setCursorBufferPosition([10]) @@ -4882,6 +4882,12 @@ describe "TextEditor", -> editor.replaceSelectedText {}, -> 'ia' expect(buffer.lineForRow(0)).toBe 'via quicksort = function () {' + it "replaces the selected text and selects the replacement text", -> + editor.setSelectedBufferRange([[0, 4], [0, 9]]) + editor.replaceSelectedText {}, -> 'whatnot' + expect(buffer.lineForRow(0)).toBe 'var whatnotsort = function () {' + expect(editor.getSelectedBufferRange()).toEqual [[0, 4], [0, 11]] + describe ".transpose()", -> it "swaps two characters", -> editor.buffer.setText("abc") @@ -4902,7 +4908,7 @@ describe "TextEditor", -> editor.setCursorScreenPosition([0, 1]) editor.upperCase() expect(editor.lineTextForBufferRow(0)).toBe 'ABC' - expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]] + expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 3]] describe "when there is a selection", -> it "upper cases the current selection", -> @@ -4919,7 +4925,7 @@ describe "TextEditor", -> editor.setCursorScreenPosition([0, 1]) editor.lowerCase() expect(editor.lineTextForBufferRow(0)).toBe 'abc' - expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]] + expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 3]] describe "when there is a selection", -> it "lower cases the current selection", -> diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 43e4fbae4..b04d8cbd7 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1336,7 +1336,9 @@ i = /test/; #FIXME\ it('calls the callback with all regex results in all files in the project', () => { const results = [] waitsForPromise(() => - atom.workspace.scan(/(a)+/, result => results.push(result)) + atom.workspace.scan( + /(a)+/, {leadingContextLineCount: 1, trailingContextLineCount: 1}, + result => results.push(result)) ) runs(() => { @@ -1349,14 +1351,16 @@ i = /test/; #FIXME\ lineTextOffset: 0, range: [[0, 0], [0, 3]], leadingContextLines: [], - trailingContextLines: [] + trailingContextLines: ['cc aa cc'] }) }) }) it('works with with escaped literals (like $ and ^)', () => { const results = [] - waitsForPromise(() => atom.workspace.scan(/\$\w+/, result => results.push(result))) + waitsForPromise(() => atom.workspace.scan( + /\$\w+/, {leadingContextLineCount: 1, trailingContextLineCount: 1}, + result => results.push(result))) runs(() => { expect(results.length).toBe(1) @@ -1368,7 +1372,7 @@ i = /test/; #FIXME\ lineText: 'dollar$bill', lineTextOffset: 0, range: [[2, 6], [2, 11]], - leadingContextLines: [], + leadingContextLines: ['cc aa cc'], trailingContextLines: [] }) }) diff --git a/src/default-directory-searcher.coffee b/src/default-directory-searcher.coffee index 6b8ffe3e3..3955d38e3 100644 --- a/src/default-directory-searcher.coffee +++ b/src/default-directory-searcher.coffee @@ -11,13 +11,16 @@ class DirectorySearch excludeVcsIgnores: options.excludeVcsIgnores globalExclusions: options.exclusions follow: options.follow + searchOptions = + leadingContextLineCount: options.leadingContextLineCount + trailingContextLineCount: options.trailingContextLineCount @task = new Task(require.resolve('./scan-handler')) @task.on 'scan:result-found', options.didMatch @task.on 'scan:file-error', options.didError @task.on 'scan:paths-searched', options.didSearchPaths @promise = new Promise (resolve, reject) => @task.on('task:cancelled', reject) - @task.start rootPaths, regex.source, scanHandlerOptions, => + @task.start rootPaths, regex.source, scanHandlerOptions, searchOptions, => @task.terminate() resolve() diff --git a/src/scan-handler.coffee b/src/scan-handler.coffee index 8ee8f715e..db2e8299b 100644 --- a/src/scan-handler.coffee +++ b/src/scan-handler.coffee @@ -2,13 +2,13 @@ path = require "path" async = require "async" {PathSearcher, PathScanner, search} = require 'scandal' -module.exports = (rootPaths, regexSource, options) -> +module.exports = (rootPaths, regexSource, options, searchOptions={}) -> callback = @async() PATHS_COUNTER_SEARCHED_CHUNK = 50 pathsSearched = 0 - searcher = new PathSearcher() + searcher = new PathSearcher(searchOptions) searcher.on 'file-error', ({code, path, message}) -> emit('scan:file-error', {code, path, message}) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8095632fd..fc3d9e08e 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1328,12 +1328,12 @@ class TextEditor extends Model replaceSelectedText: (options={}, fn) -> {selectWordIfEmpty} = options @mutateSelectedText (selection) -> - range = selection.getBufferRange() + selection.getBufferRange() if selectWordIfEmpty and selection.isEmpty() selection.selectWord() text = selection.getText() selection.deleteSelectedText() - selection.insertText(fn(text)) + range = selection.insertText(fn(text)) selection.setBufferRange(range) # Split multi-line selections into one selection per line. @@ -2826,6 +2826,11 @@ class TextEditor extends Model # {::backwardsScanInBufferRange} to avoid tripping over your own changes. # # * `regex` A {RegExp} to search for. + # * `options` (optional) {Object} + # * `leadingContextLineCount` {Number} default `0`; The number of lines + # before the matched line to include in the results object. + # * `trailingContextLineCount` {Number} default `0`; The number of lines + # after the matched line to include in the results object. # * `iterator` A {Function} that's called on each match # * `object` {Object} # * `match` The current regular expression match. @@ -2833,7 +2838,12 @@ class TextEditor extends Model # * `range` The {Range} of the match. # * `stop` Call this {Function} to terminate the scan. # * `replace` Call this {Function} with a {String} to replace the match. - scan: (regex, iterator) -> @buffer.scan(regex, iterator) + scan: (regex, options={}, iterator) -> + if _.isFunction(options) + iterator = options + options = {} + + @buffer.scan(regex, options, iterator) # Essential: Scan regular expression matches in a given range, calling the given # iterator function on each match. diff --git a/src/workspace.js b/src/workspace.js index 8ff8aa51d..6c5daba12 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -511,9 +511,8 @@ module.exports = class Workspace extends Model { // // Returns a {Promise} that resolves to the {TextEditor} for the file URI. open (uri_, options = {}) { - const { searchAllPanes } = options - const { split } = options const uri = this.project.resolvePath(uri_) + const {searchAllPanes, split} = options if (!atom.config.get('core.allowPendingPaneItems')) { options.pending = false @@ -526,7 +525,7 @@ module.exports = class Workspace extends Model { } let pane - if (searchAllPanes) { pane = this.paneContainer.paneForURI(uri) } + if (searchAllPanes) { pane = this.paneForURI(uri) } if (pane == null) { switch (split) { case 'left': @@ -547,7 +546,16 @@ module.exports = class Workspace extends Model { } } - return this.openURIInPane(uri, pane, options) + let item + if (uri != null) { + item = pane.itemForURI(uri) + } + if (item == null) { + item = this.createItemForURI(uri, options) + } + + return Promise.resolve(item) + .then(item => this.openItem(item, Object.assign({pane, uri}, options))) } // Open Atom's license in the active pane. @@ -597,26 +605,28 @@ module.exports = class Workspace extends Model { } openURIInPane (uri, pane, options = {}) { - const activatePane = options.activatePane != null ? options.activatePane : true - const activateItem = options.activateItem != null ? options.activateItem : true - let item if (uri != null) { item = pane.itemForURI(uri) - if (item == null) { - for (let opener of this.getOpeners()) { - item = opener(uri, options) - if (item != null) break - } - } else if (!options.pending && (pane.getPendingItem() === item)) { - pane.clearPendingItem() + } + if (item == null) { + item = this.createItemForURI(uri, options) + } + return Promise.resolve(item) + .then(item => this.openItem(item, Object.assign({pane, uri}, options))) + } + + // Returns a {Promise} that resolves to the {TextEditor} (or other item) for the given URI. + createItemForURI (uri, options) { + if (uri != null) { + for (let opener of this.getOpeners()) { + const item = opener(uri, options) + if (item != null) return Promise.resolve(item) } } try { - if (item == null) { - item = this.openTextFile(uri, options) - } + return this.openTextFile(uri, options) } catch (error) { switch (error.code) { case 'CANCELLED': @@ -644,40 +654,46 @@ module.exports = class Workspace extends Model { throw error } } + } - return Promise.resolve(item) - .then(item => { - let initialColumn - if (pane.isDestroyed()) { - return item - } + openItem (item, options = {}) { + const {pane} = options - this.itemOpened(item) - if (activateItem) { - pane.activateItem(item, {pending: options.pending}) - } - if (activatePane) { - pane.activate() - } + if (item == null) return undefined + if (pane.isDestroyed()) return item - let initialLine = initialColumn = 0 - if (!Number.isNaN(options.initialLine)) { - initialLine = options.initialLine - } - if (!Number.isNaN(options.initialColumn)) { - initialColumn = options.initialColumn - } - if ((initialLine >= 0) || (initialColumn >= 0)) { - if (typeof item.setCursorBufferPosition === 'function') { - item.setCursorBufferPosition([initialLine, initialColumn]) - } - } + if (!options.pending && (pane.getPendingItem() === item)) { + pane.clearPendingItem() + } - const index = pane.getActiveItemIndex() - this.emitter.emit('did-open', {uri, pane, item, index}) - return item + const activatePane = options.activatePane != null ? options.activatePane : true + const activateItem = options.activateItem != null ? options.activateItem : true + this.itemOpened(item) + if (activateItem) { + pane.activateItem(item, {pending: options.pending}) + } + if (activatePane) { + pane.activate() + } + + let initialColumn = 0 + let initialLine = 0 + if (!Number.isNaN(options.initialLine)) { + initialLine = options.initialLine + } + if (!Number.isNaN(options.initialColumn)) { + initialColumn = options.initialColumn + } + if ((initialLine >= 0) || (initialColumn >= 0)) { + if (typeof item.setCursorBufferPosition === 'function') { + item.setCursorBufferPosition([initialLine, initialColumn]) } - ) + } + + const index = pane.getActiveItemIndex() + const uri = options.uri == null && typeof item.getURI === 'function' ? item.getURI() : options.uri + this.emitter.emit('did-open', {uri, pane, item, index}) + return item } openTextFile (uri, options) { @@ -1186,6 +1202,10 @@ module.exports = class Workspace extends Model { // * `paths` An {Array} of glob patterns to search within. // * `onPathsSearched` (optional) {Function} to be periodically called // with number of paths searched. + // * `leadingContextLineCount` {Number} default `0`; The number of lines + // before the matched line to include in the results object. + // * `trailingContextLineCount` {Number} default `0`; The number of lines + // after the matched line to include in the results object. // * `iterator` {Function} callback on each file found. // // Returns a {Promise} with a `cancel()` method that will cancel all @@ -1245,6 +1265,8 @@ module.exports = class Workspace extends Model { excludeVcsIgnores: this.config.get('core.excludeVcsIgnoredPaths'), exclusions: this.config.get('core.ignoredNames'), follow: this.config.get('core.followSymlinks'), + leadingContextLineCount: options.leadingContextLineCount || 0, + trailingContextLineCount: options.trailingContextLineCount || 0, didMatch: result => { if (!this.project.isPathModified(result.filePath)) { return iterator(result)