mirror of
https://github.com/atom/atom.git
synced 2026-02-14 08:35:11 -05:00
Merge branch 'master' into as-tiled-gutter
This commit is contained in:
@@ -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.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user