Merge branch 'master' into as-tiled-gutter

This commit is contained in:
Antonio Scandurra
2015-06-11 18:38:31 +02:00
22 changed files with 355 additions and 66 deletions

View File

@@ -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'

View File

@@ -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)

View File

@@ -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, ->

View 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()
}

View File

@@ -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})

View File

@@ -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()

View File

@@ -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.

View File

@@ -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

View File

@@ -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) ->

View File

@@ -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.
#