mirror of
https://github.com/atom/atom.git
synced 2026-01-24 14:28:14 -05:00
Merge branch 'master' into as-tiled-gutter
This commit is contained in:
14
package.json
14
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.208.0",
|
||||
"version": "0.209.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -32,7 +32,7 @@
|
||||
"delegato": "^1",
|
||||
"emissary": "^1.3.3",
|
||||
"event-kit": "^1.2.0",
|
||||
"first-mate": "^4.1.6",
|
||||
"first-mate": "^4.1.7",
|
||||
"fs-plus": "^2.8.0",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
@@ -57,14 +57,14 @@
|
||||
"scandal": "2.0.3",
|
||||
"scoped-property-store": "^0.17.0",
|
||||
"scrollbar-style": "^3.1",
|
||||
"season": "^5.1.4",
|
||||
"season": "^5.3",
|
||||
"semver": "^4.3.3",
|
||||
"serializable": "^1",
|
||||
"service-hub": "^0.5.0",
|
||||
"space-pen": "3.8.2",
|
||||
"stacktrace-parser": "0.1.1",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "6.3.2",
|
||||
"text-buffer": "6.3.6",
|
||||
"theorist": "^1.0.2",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
@@ -120,17 +120,17 @@
|
||||
"status-bar": "0.74.0",
|
||||
"styleguide": "0.44.0",
|
||||
"symbols-view": "0.98.0",
|
||||
"tabs": "0.76.0",
|
||||
"tabs": "0.80.0",
|
||||
"timecop": "0.31.0",
|
||||
"tree-view": "0.172.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.28.0",
|
||||
"welcome": "0.29.0",
|
||||
"whitespace": "0.30.0",
|
||||
"wrap-guide": "0.35.0",
|
||||
"language-c": "0.45.0",
|
||||
"language-clojure": "0.15.0",
|
||||
"language-coffee-script": "0.41.0",
|
||||
"language-csharp": "0.5.0",
|
||||
"language-csharp": "0.6.0",
|
||||
"language-css": "0.30.0",
|
||||
"language-gfm": "0.77.0",
|
||||
"language-git": "0.10.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
"$0/../../app/apm/bin/node.exe" "$0/../../app/apm/lib/cli.js" "$@"
|
||||
directory=$(dirname "$0")
|
||||
"$directory/../app/apm/bin/node.exe" "$directory/../app/apm/lib/cli.js" "$@"
|
||||
|
||||
@@ -15,8 +15,10 @@ while getopts ":fhtvw-:" opt; do
|
||||
esac
|
||||
done
|
||||
|
||||
directory=$(dirname "$0")
|
||||
|
||||
if [ $EXPECT_OUTPUT ]; then
|
||||
"$0/../../../atom.exe" "$@"
|
||||
"$directory/../../atom.exe" "$@"
|
||||
else
|
||||
"$0/../../app/apm/bin/node.exe" "$0/../atom.js" "$@"
|
||||
"$directory/../app/apm/bin/node.exe" "$directory/atom.js" "$@"
|
||||
fi
|
||||
|
||||
4
spec/fixtures/packages/package-with-different-directory-name/package.json
vendored
Normal file
4
spec/fixtures/packages/package-with-different-directory-name/package.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "package-with-a-totally-different-name",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "package-with-invalid-selectors",
|
||||
"name": "package-with-invalid-activation-commands",
|
||||
"version": "1.0.0",
|
||||
"activationCommands": {
|
||||
"<>": [
|
||||
|
||||
@@ -1 +1 @@
|
||||
'main': 'main-module.coffee'
|
||||
'main': 'main-module.coffee'
|
||||
|
||||
@@ -105,3 +105,13 @@ describe "Package", ->
|
||||
theme.onDidDeactivate spy = jasmine.createSpy()
|
||||
theme.deactivate()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
|
||||
describe ".loadMetadata()", ->
|
||||
[packagePath, pack, metadata] = []
|
||||
|
||||
beforeEach ->
|
||||
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-different-directory-name')
|
||||
metadata = Package.loadMetadata(packagePath, true)
|
||||
|
||||
it "uses the package name defined in package.json", ->
|
||||
expect(metadata.name).toBe 'package-with-a-totally-different-name'
|
||||
|
||||
@@ -154,3 +154,9 @@ describe "PaneContainerElement", ->
|
||||
container.destroy()
|
||||
expect(element.resizeStopped.callCount).toBe 1
|
||||
expect(document.removeEventListener.callCount).toBe 2
|
||||
|
||||
it "does not throw an error when resized to fit content in a detached state", ->
|
||||
container.getActivePane().splitRight()
|
||||
element = getResizeElement(0)
|
||||
element.remove()
|
||||
expect(-> element.resizeToFitContent()).not.toThrow()
|
||||
|
||||
@@ -70,3 +70,29 @@ describe "Task", ->
|
||||
task.terminate()
|
||||
expect(stdout.listeners('data').length).toBe 0
|
||||
expect(stderr.listeners('data').length).toBe 0
|
||||
|
||||
describe "::cancel()", ->
|
||||
it "dispatches 'task:cancelled' when invoked on an active task", ->
|
||||
task = new Task(require.resolve('./fixtures/task-spec-handler'))
|
||||
cancelledEventSpy = jasmine.createSpy('eventSpy')
|
||||
task.on('task:cancelled', cancelledEventSpy)
|
||||
completedEventSpy = jasmine.createSpy('eventSpy')
|
||||
task.on('task:completed', completedEventSpy)
|
||||
|
||||
expect(task.cancel()).toBe(true)
|
||||
expect(cancelledEventSpy).toHaveBeenCalled()
|
||||
expect(completedEventSpy).not.toHaveBeenCalled()
|
||||
|
||||
it "does not dispatch 'task:cancelled' when invoked on an inactive task", ->
|
||||
handlerResult = null
|
||||
task = Task.once require.resolve('./fixtures/task-spec-handler'), (result) ->
|
||||
handlerResult = result
|
||||
|
||||
waitsFor ->
|
||||
handlerResult?
|
||||
|
||||
runs ->
|
||||
cancelledEventSpy = jasmine.createSpy('eventSpy')
|
||||
task.on('task:cancelled', cancelledEventSpy)
|
||||
expect(task.cancel()).toBe(false)
|
||||
expect(cancelledEventSpy).not.toHaveBeenCalled()
|
||||
|
||||
@@ -1327,6 +1327,10 @@ describe "TextEditorPresenter", ->
|
||||
marker8 = editor.markBufferRange([[2, 2], [2, 2]])
|
||||
highlight8 = editor.decorateMarker(marker8, type: 'highlight', class: 'h')
|
||||
|
||||
# partially off-screen above, empty
|
||||
marker9 = editor.markBufferRange([[0, 0], [2, 0]], invalidate: 'touch')
|
||||
highlight9 = editor.decorateMarker(marker9, type: 'highlight', class: 'h')
|
||||
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
|
||||
|
||||
expectUndefinedStateForHighlight(presenter, highlight1)
|
||||
@@ -1388,6 +1392,7 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectUndefinedStateForHighlight(presenter, highlight7)
|
||||
expectUndefinedStateForHighlight(presenter, highlight8)
|
||||
expectUndefinedStateForHighlight(presenter, highlight9)
|
||||
|
||||
it "is empty until all of the required measurements are assigned", ->
|
||||
editor.setSelectedBufferRanges([
|
||||
|
||||
@@ -938,6 +938,92 @@ describe "Workspace", ->
|
||||
.then ->
|
||||
expect(resultPaths).toEqual([file2])
|
||||
|
||||
describe "when a custom directory searcher is registered", ->
|
||||
fakeSearch = null
|
||||
# Function that is invoked once all of the fields on fakeSearch are set.
|
||||
onFakeSearchCreated = null
|
||||
|
||||
class FakeSearch
|
||||
constructor: (@options) ->
|
||||
# Note that hoisting resolve and reject in this way is generally frowned upon.
|
||||
@promise = new Promise (resolve, reject) =>
|
||||
@hoistedResolve = resolve
|
||||
@hoistedReject = reject
|
||||
onFakeSearchCreated?(this)
|
||||
then: (args...) ->
|
||||
@promise.then.apply(@promise, args)
|
||||
cancel: ->
|
||||
@cancelled = true
|
||||
# According to the spec for a DirectorySearcher, invoking `cancel()` should
|
||||
# resolve the thenable rather than reject it.
|
||||
@hoistedResolve()
|
||||
|
||||
beforeEach ->
|
||||
fakeSearch = null
|
||||
onFakeSearchCreated = null
|
||||
atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', {
|
||||
canSearchDirectory: (directory) -> directory.getPath() is dir1
|
||||
search: (directory, regex, options) -> fakeSearch = new FakeSearch(options)
|
||||
})
|
||||
|
||||
it "can override the DefaultDirectorySearcher on a per-directory basis", ->
|
||||
foreignFilePath = 'ssh://foreign-directory:8080/hello.txt'
|
||||
numPathsSearchedInDir2 = 1
|
||||
numPathsToPretendToSearchInCustomDirectorySearcher = 10
|
||||
searchResult =
|
||||
filePath: foreignFilePath,
|
||||
matches: [
|
||||
{
|
||||
lineText: 'Hello world',
|
||||
lineTextOffset: 0,
|
||||
matchText: 'Hello',
|
||||
range: [[0, 0], [0, 5]],
|
||||
},
|
||||
]
|
||||
onFakeSearchCreated = (fakeSearch) ->
|
||||
fakeSearch.options.didMatch(searchResult)
|
||||
fakeSearch.options.didSearchPaths(numPathsToPretendToSearchInCustomDirectorySearcher)
|
||||
fakeSearch.hoistedResolve()
|
||||
|
||||
resultPaths = []
|
||||
onPathsSearched = jasmine.createSpy('onPathsSearched')
|
||||
waitsForPromise ->
|
||||
atom.workspace.scan /aaaa/, {onPathsSearched}, ({filePath}) ->
|
||||
resultPaths.push(filePath)
|
||||
|
||||
runs ->
|
||||
expect(resultPaths.sort()).toEqual([foreignFilePath, file2].sort())
|
||||
# onPathsSearched should be called once by each DirectorySearcher. The order is not
|
||||
# guaranteed, so we can only verify the total number of paths searched is correct
|
||||
# after the second call.
|
||||
expect(onPathsSearched.callCount).toBe(2)
|
||||
expect(onPathsSearched.mostRecentCall.args[0]).toBe(
|
||||
numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2)
|
||||
|
||||
it "can be cancelled when the object returned by scan() has its cancel() method invoked", ->
|
||||
thenable = atom.workspace.scan /aaaa/, ->
|
||||
expect(fakeSearch.cancelled).toBe(undefined)
|
||||
thenable.cancel()
|
||||
expect(fakeSearch.cancelled).toBe(true)
|
||||
|
||||
resultOfPromiseSearch = null
|
||||
waitsForPromise ->
|
||||
thenable.then (promiseResult) -> resultOfPromiseSearch = promiseResult
|
||||
|
||||
runs ->
|
||||
expect(resultOfPromiseSearch).toBe('cancelled')
|
||||
|
||||
it "will have the side-effect of failing the overall search if it fails", ->
|
||||
cancelableSearch = atom.workspace.scan /aaaa/, ->
|
||||
fakeSearch.hoistedReject()
|
||||
|
||||
didReject = false
|
||||
waitsForPromise ->
|
||||
cancelableSearch.catch -> didReject = true
|
||||
|
||||
runs ->
|
||||
expect(didReject).toBe(true)
|
||||
|
||||
describe "::replace(regex, replacementText, paths, iterator)", ->
|
||||
[filePath, commentFilePath, sampleContent, sampleCommentContent] = []
|
||||
|
||||
|
||||
@@ -472,9 +472,13 @@ class Atom extends Model
|
||||
ipc.send('call-window-method', 'restart')
|
||||
|
||||
# Extended: Returns a {Boolean} true when the current window is maximized.
|
||||
isMaximixed: ->
|
||||
isMaximized: ->
|
||||
@getCurrentWindow().isMaximized()
|
||||
|
||||
isMaximixed: ->
|
||||
deprecate "Use atom.isMaximized() instead"
|
||||
@isMaximized()
|
||||
|
||||
maximize: ->
|
||||
ipc.send('call-window-method', 'maximize')
|
||||
|
||||
@@ -501,9 +505,9 @@ class Atom extends Model
|
||||
displayWindow: ->
|
||||
dimensions = @restoreWindowDimensions()
|
||||
@show()
|
||||
@focus()
|
||||
|
||||
setImmediate =>
|
||||
@focus()
|
||||
@setFullScreen(true) if @workspace?.fullScreen
|
||||
@maximize() if dimensions?.maximized and process.platform isnt 'darwin'
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class AtomWindow
|
||||
hash: encodeURIComponent(JSON.stringify(loadSettings))
|
||||
|
||||
getLoadSettings: ->
|
||||
if @browserWindow.webContents.loaded
|
||||
if @browserWindow.webContents?.loaded
|
||||
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
|
||||
JSON.parse(decodeURIComponent(hash))
|
||||
|
||||
@@ -166,7 +166,6 @@ class AtomWindow
|
||||
|
||||
openLocations: (locationsToOpen) ->
|
||||
if @loaded
|
||||
@focus()
|
||||
@sendMessage 'open-locations', locationsToOpen
|
||||
else
|
||||
@browserWindow.once 'window:loaded', => @openLocations(locationsToOpen)
|
||||
|
||||
@@ -139,7 +139,7 @@ addCommandsToPath = (callback) ->
|
||||
|
||||
atomShCommandPath = path.join(binFolder, 'atom')
|
||||
relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
|
||||
atomShCommand = "#!/bin/sh\r\n\"$0/../#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\""
|
||||
atomShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\""
|
||||
|
||||
apmCommandPath = path.join(binFolder, 'apm.cmd')
|
||||
relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
|
||||
@@ -147,7 +147,7 @@ addCommandsToPath = (callback) ->
|
||||
|
||||
apmShCommandPath = path.join(binFolder, 'apm')
|
||||
relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
|
||||
apmShCommand = "#!/bin/sh\r\n\"$0/../#{relativeApmShPath.replace(/\\/g, '/')}\" \"$@\""
|
||||
apmShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeApmShPath.replace(/\\/g, '/')}\" \"$@\""
|
||||
|
||||
fs.writeFile atomCommandPath, atomCommand, ->
|
||||
fs.writeFile atomShCommandPath, atomShCommand, ->
|
||||
|
||||
95
src/default-directory-searcher.coffee
Normal file
95
src/default-directory-searcher.coffee
Normal file
@@ -0,0 +1,95 @@
|
||||
Task = require './task'
|
||||
|
||||
# Public: Searches local files for lines matching a specified regex.
|
||||
#
|
||||
# Implements thenable so it can be used with `Promise.all()`.
|
||||
class DirectorySearch
|
||||
constructor: (rootPaths, regex, options) ->
|
||||
scanHandlerOptions =
|
||||
ignoreCase: regex.ignoreCase
|
||||
inclusions: options.inclusions
|
||||
includeHidden: options.includeHidden
|
||||
excludeVcsIgnores: options.excludeVcsIgnores
|
||||
exclusions: options.exclusions
|
||||
follow: options.follow
|
||||
@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, resolve)
|
||||
|
||||
# Public: Implementation of `then()` to satisfy the *thenable* contract.
|
||||
# This makes it possible to use a `DirectorySearch` with `Promise.all()`.
|
||||
#
|
||||
# Returns `Promise`.
|
||||
then: (args...) ->
|
||||
@promise.then.apply(@promise, args)
|
||||
|
||||
# Public: Cancels the search.
|
||||
cancel: ->
|
||||
# This will cause @promise to reject.
|
||||
@task.cancel()
|
||||
null
|
||||
|
||||
|
||||
# Default provider for the `atom.directory-searcher` service.
|
||||
module.exports =
|
||||
class DefaultDirectorySearcher
|
||||
# Public: Determines whether this object supports search for a `Directory`.
|
||||
#
|
||||
# * `directory` {Directory} whose search needs might be supported by this object.
|
||||
#
|
||||
# Returns a `boolean` indicating whether this object can search this `Directory`.
|
||||
canSearchDirectory: (directory) -> true
|
||||
|
||||
# Public: Performs a text search for files in the specified `Directory`, subject to the
|
||||
# specified parameters.
|
||||
#
|
||||
# Results are streamed back to the caller by invoking methods on the specified `options`,
|
||||
# such as `didMatch` and `didError`.
|
||||
#
|
||||
# * `directories` {Array} of {Directory} objects to search, all of which have been accepted by
|
||||
# this searcher's `canSearchDirectory()` predicate.
|
||||
# * `regex` {RegExp} to search with.
|
||||
# * `options` {Object} with the following properties:
|
||||
# * `didMatch` {Function} call with a search result structured as follows:
|
||||
# * `searchResult` {Object} with the following keys:
|
||||
# * `filePath` {String} absolute path to the matching file.
|
||||
# * `matches` {Array} with object elements with the following keys:
|
||||
# * `lineText` {String} The full text of the matching line (without a line terminator character).
|
||||
# * `lineTextOffset` {Number} (This always seems to be 0?)
|
||||
# * `matchText` {String} The text that matched the `regex` used for the search.
|
||||
# * `range` {Range} Identifies the matching region in the file. (Likely as an array of numeric arrays.)
|
||||
# * `didError` {Function} call with an Error if there is a problem during the search.
|
||||
# * `didSearchPaths` {Function} periodically call with the number of paths searched thus far.
|
||||
# * `inclusions` {Array} of glob patterns (as strings) to search within. Note that this
|
||||
# array may be empty, indicating that all files should be searched.
|
||||
#
|
||||
# Each item in the array is a file/directory pattern, e.g., `src` to search in the "src"
|
||||
# directory or `*.js` to search all JavaScript files. In practice, this often comes from the
|
||||
# comma-delimited list of patterns in the bottom text input of the ProjectFindView dialog.
|
||||
# * `ignoreHidden` {boolean} whether to ignore hidden files.
|
||||
# * `excludeVcsIgnores` {boolean} whether to exclude VCS ignored paths.
|
||||
# * `exclusions` {Array} similar to inclusions
|
||||
# * `follow` {boolean} whether symlinks should be followed.
|
||||
#
|
||||
# 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) ->
|
||||
rootPaths = directories.map (directory) -> directory.getPath()
|
||||
isCancelled = false
|
||||
directorySearch = new DirectorySearch(rootPaths, regex, options)
|
||||
promise = new Promise (resolve, reject) ->
|
||||
directorySearch.then resolve, ->
|
||||
if isCancelled
|
||||
resolve()
|
||||
else
|
||||
reject()
|
||||
return {
|
||||
then: promise.then.bind(promise)
|
||||
cancel: ->
|
||||
isCancelled = true
|
||||
directorySearch.cancel()
|
||||
}
|
||||
@@ -45,7 +45,8 @@ class Package
|
||||
throw error unless ignoreErrors
|
||||
|
||||
metadata ?= {}
|
||||
metadata.name = packageName
|
||||
unless typeof metadata.name is 'string' and metadata.name.length > 0
|
||||
metadata.name = packageName
|
||||
|
||||
if includeDeprecatedAPIs and metadata.stylesheetMain?
|
||||
deprecate("Use the `mainStyleSheet` key instead of `stylesheetMain` in the `package.json` of `#{packageName}`", {packageName})
|
||||
|
||||
@@ -17,8 +17,8 @@ class PaneResizeHandleElement extends HTMLElement
|
||||
|
||||
resizeToFitContent: ->
|
||||
# clear flex-grow css style of both pane
|
||||
@previousSibling.model.setFlexScale(1)
|
||||
@nextSibling.model.setFlexScale(1)
|
||||
@previousSibling?.model.setFlexScale(1)
|
||||
@nextSibling?.model.setFlexScale(1)
|
||||
|
||||
resizeStarted: (e) ->
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -84,7 +84,7 @@ class Selection extends Model
|
||||
|
||||
# Public: Modifies the buffer {Range} for the selection.
|
||||
#
|
||||
# * `screenRange` The new {Range} to select.
|
||||
# * `bufferRange` The new {Range} to select.
|
||||
# * `options` (optional) {Object} with the keys:
|
||||
# * `preserveFolds` if `true`, the fold settings are preserved after the
|
||||
# selection moves.
|
||||
|
||||
@@ -150,7 +150,7 @@ class Task
|
||||
#
|
||||
# No more events are emitted once this method is called.
|
||||
terminate: ->
|
||||
return unless @childProcess?
|
||||
return false unless @childProcess?
|
||||
|
||||
@childProcess.removeAllListeners()
|
||||
@childProcess.stdout.removeAllListeners()
|
||||
@@ -158,4 +158,10 @@ class Task
|
||||
@childProcess.kill()
|
||||
@childProcess = null
|
||||
|
||||
undefined
|
||||
true
|
||||
|
||||
cancel: ->
|
||||
didForcefullyTerminate = @terminate()
|
||||
if didForcefullyTerminate
|
||||
@emit('task:cancelled')
|
||||
didForcefullyTerminate
|
||||
|
||||
@@ -1255,13 +1255,6 @@ class TextEditorPresenter
|
||||
range = marker.getScreenRange()
|
||||
|
||||
if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1)
|
||||
tileStartRow = @tileForRow(range.start.row)
|
||||
tileEndRow = @tileForRow(range.end.row)
|
||||
|
||||
for tile in [tileStartRow..tileEndRow] by @tileSize
|
||||
delete @state.content.tiles[tile]?.highlights[decoration.id]
|
||||
|
||||
@emitDidUpdateState()
|
||||
return
|
||||
|
||||
if range.start.row < @startRow
|
||||
@@ -1271,11 +1264,7 @@ class TextEditorPresenter
|
||||
range.end.row = @endRow
|
||||
range.end.column = 0
|
||||
|
||||
if range.isEmpty()
|
||||
tileState = @state.content.tiles[@tileForRow(range.start.row)]
|
||||
delete tileState.highlights[decoration.id]
|
||||
@emitDidUpdateState()
|
||||
return
|
||||
return if range.isEmpty()
|
||||
|
||||
flash = decoration.consumeNextFlash()
|
||||
|
||||
@@ -1309,8 +1298,6 @@ class TextEditorPresenter
|
||||
@visibleHighlights[tileStartRow] ?= {}
|
||||
@visibleHighlights[tileStartRow][decoration.id] = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
true
|
||||
|
||||
repositionRegionWithinTile: (region, tileStartRow) ->
|
||||
|
||||
@@ -7,6 +7,7 @@ Serializable = require 'serializable'
|
||||
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
fs = require 'fs-plus'
|
||||
DefaultDirectorySearcher = require './default-directory-searcher'
|
||||
Model = require './model'
|
||||
TextEditor = require './text-editor'
|
||||
PaneContainer = require './pane-container'
|
||||
@@ -46,6 +47,13 @@ class Workspace extends Model
|
||||
@paneContainer ?= new PaneContainer()
|
||||
@paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem)
|
||||
|
||||
@directorySearchers = []
|
||||
@defaultDirectorySearcher = new DefaultDirectorySearcher()
|
||||
atom.packages.serviceHub.consume(
|
||||
'atom.directory-searcher',
|
||||
'^0.1.0',
|
||||
(provider) => @directorySearchers.unshift(provider))
|
||||
|
||||
@panelContainers =
|
||||
top: new PanelContainer({location: 'top'})
|
||||
left: new PanelContainer({location: 'left'})
|
||||
@@ -791,36 +799,65 @@ class Workspace extends Model
|
||||
# * `regex` {RegExp} to search with.
|
||||
# * `options` (optional) {Object} (default: {})
|
||||
# * `paths` An {Array} of glob patterns to search within
|
||||
# * `onPathsSearched` (optional) {Function}
|
||||
# * `iterator` {Function} callback on each file found
|
||||
#
|
||||
# Returns a `Promise`.
|
||||
# Returns a `Promise` with a `cancel()` method that will cancel all
|
||||
# of the underlying searches that were started as part of this scan.
|
||||
scan: (regex, options={}, iterator) ->
|
||||
if _.isFunction(options)
|
||||
iterator = options
|
||||
options = {}
|
||||
|
||||
deferred = Q.defer()
|
||||
|
||||
searchOptions =
|
||||
ignoreCase: regex.ignoreCase
|
||||
inclusions: options.paths
|
||||
includeHidden: true
|
||||
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
|
||||
exclusions: atom.config.get('core.ignoredNames')
|
||||
follow: atom.config.get('core.followSymlinks')
|
||||
|
||||
task = Task.once require.resolve('./scan-handler'), atom.project.getPaths(), regex.source, searchOptions, ->
|
||||
deferred.resolve()
|
||||
|
||||
task.on 'scan:result-found', (result) ->
|
||||
iterator(result) unless atom.project.isPathModified(result.filePath)
|
||||
|
||||
task.on 'scan:file-error', (error) ->
|
||||
iterator(null, error)
|
||||
# Find a searcher for every Directory in the project. Each searcher that is matched
|
||||
# will be associated with an Array of Directory objects in the Map.
|
||||
directoriesForSearcher = new Map()
|
||||
for directory in atom.project.getDirectories()
|
||||
searcher = @defaultDirectorySearcher
|
||||
for directorySearcher in @directorySearchers
|
||||
if directorySearcher.canSearchDirectory(directory)
|
||||
searcher = directorySearcher
|
||||
break
|
||||
directories = directoriesForSearcher.get(searcher)
|
||||
unless directories
|
||||
directories = []
|
||||
directoriesForSearcher.set(searcher, directories)
|
||||
directories.push(directory)
|
||||
|
||||
# Define the onPathsSearched callback.
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
# Maintain a map of directories to the number of search results. When notified of a new count,
|
||||
# replace the entry in the map and update the total.
|
||||
onPathsSearchedOption = options.onPathsSearched
|
||||
totalNumberOfPathsSearched = 0
|
||||
numberOfPathsSearchedForSearcher = new Map()
|
||||
onPathsSearched = (searcher, numberOfPathsSearched) ->
|
||||
oldValue = numberOfPathsSearchedForSearcher.get(searcher)
|
||||
if oldValue
|
||||
totalNumberOfPathsSearched -= oldValue
|
||||
numberOfPathsSearchedForSearcher.set(searcher, numberOfPathsSearched)
|
||||
totalNumberOfPathsSearched += numberOfPathsSearched
|
||||
onPathsSearchedOption(totalNumberOfPathsSearched)
|
||||
else
|
||||
onPathsSearched = ->
|
||||
|
||||
# Kick off all of the searches and unify them into one Promise.
|
||||
allSearches = []
|
||||
directoriesForSearcher.forEach (directories, searcher) ->
|
||||
searchOptions =
|
||||
inclusions: options.paths or []
|
||||
includeHidden: true
|
||||
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
|
||||
exclusions: atom.config.get('core.ignoredNames')
|
||||
follow: atom.config.get('core.followSymlinks')
|
||||
didMatch: (result) ->
|
||||
iterator(result) unless atom.project.isPathModified(result.filePath)
|
||||
didError: (error) ->
|
||||
iterator(null, error)
|
||||
didSearchPaths: (count) -> onPathsSearched(searcher, count)
|
||||
directorySearcher = searcher.search(directories, regex, searchOptions)
|
||||
allSearches.push(directorySearcher)
|
||||
searchPromise = Promise.all(allSearches)
|
||||
|
||||
for buffer in atom.project.getBuffers() when buffer.isModified()
|
||||
filePath = buffer.getPath()
|
||||
@@ -829,11 +866,31 @@ class Workspace extends Model
|
||||
buffer.scan regex, (match) -> matches.push match
|
||||
iterator {filePath, matches} if matches.length > 0
|
||||
|
||||
promise = deferred.promise
|
||||
promise.cancel = ->
|
||||
task.terminate()
|
||||
deferred.resolve('cancelled')
|
||||
promise
|
||||
# Make sure the Promise that is returned to the client is cancelable. To be consistent
|
||||
# with the existing behavior, instead of cancel() rejecting the promise, it should
|
||||
# resolve it with the special value 'cancelled'. At least the built-in find-and-replace
|
||||
# package relies on this behavior.
|
||||
isCancelled = false
|
||||
cancellablePromise = new Promise (resolve, reject) ->
|
||||
onSuccess = ->
|
||||
if isCancelled
|
||||
resolve('cancelled')
|
||||
else
|
||||
resolve(null)
|
||||
searchPromise.then(onSuccess, reject)
|
||||
cancellablePromise.cancel = ->
|
||||
isCancelled = true
|
||||
# Note that cancelling all of the members of allSearches will cause all of the searches
|
||||
# to resolve, which causes searchPromise to resolve, which is ultimately what causes
|
||||
# cancellablePromise to resolve.
|
||||
promise.cancel() for promise in allSearches
|
||||
|
||||
# Although this method claims to return a `Promise`, the `ResultsPaneView.onSearch()`
|
||||
# method in the find-and-replace package expects the object returned by this method to have a
|
||||
# `done()` method. Include a done() method until find-and-replace can be updated.
|
||||
cancellablePromise.done = (onSuccessOrFailure) ->
|
||||
cancellablePromise.then(onSuccessOrFailure, onSuccessOrFailure)
|
||||
cancellablePromise
|
||||
|
||||
# Public: Performs a replace across all the specified files in the project.
|
||||
#
|
||||
|
||||
@@ -25,7 +25,7 @@ atom-pane-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin-top: -4px;
|
||||
cursor: ns-resize;
|
||||
cursor: row-resize;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ atom-pane-container {
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
margin-left: -4px;
|
||||
cursor: ew-resize;
|
||||
cursor: col-resize;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user