Merge branch 'master' into dh-async-repo

This commit is contained in:
joshaber
2015-11-17 11:39:22 -08:00
36 changed files with 609 additions and 176 deletions

View File

@@ -117,7 +117,7 @@ class AtomEnvironment extends Model
# Call .loadOrCreate instead
constructor: (params={}) ->
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence} = params
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
@state = {version: @constructor.version}
@@ -183,7 +183,7 @@ class AtomEnvironment extends Model
@themes.loadBaseStylesheets()
@initialStyleElements = @styles.getSnapshot()
@themes.initialLoadComplete = true
@themes.initialLoadComplete = true if onlyLoadBaseStyleSheets
@setBodyPlatformClass()
@stylesElement = @styles.buildStylesElement()

View File

@@ -35,7 +35,7 @@ class CommandInstaller
process.resourcesPath
installShellCommandsInteractively: ->
showErrorDialog = (error) ->
showErrorDialog = (error) =>
@applicationDelegate.confirm
message: "Failed to install shell commands"
detailedMessage: error.message

View File

@@ -671,17 +671,47 @@ class Config
#
# * `callback` {Function} to execute while suppressing calls to handlers.
transact: (callback) ->
@transactDepth++
@beginTransaction()
try
callback()
finally
@transactDepth--
@emitChangeEvent()
@endTransaction()
###
Section: Internal methods used by core
###
# Private: Suppress calls to handler functions registered with {::onDidChange}
# and {::observe} for the duration of the {Promise} returned by `callback`.
# After the {Promise} is either resolved or rejected, handlers will be called
# once if the value for their key-path has changed.
#
# * `callback` {Function} that returns a {Promise}, which will be executed
# while suppressing calls to handlers.
#
# 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) ->
@beginTransaction()
try
endTransaction = (fn) => (args...) =>
@endTransaction()
fn(args...)
result = callback()
new Promise (resolve, reject) =>
result.then(endTransaction(resolve)).catch(endTransaction(reject))
catch error
@endTransaction()
Promise.reject(error)
beginTransaction: ->
@transactDepth++
endTransaction: ->
@transactDepth--
@emitChangeEvent()
pushAtKeyPath: (keyPath, value) ->
arrayValue = @get(keyPath) ? []
result = arrayValue.push(value)

View File

@@ -36,21 +36,21 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
if score > highestScore or not bestMatch?
bestMatch = grammar
highestScore = score
else if score is highestScore and bestMatch?.bundledPackage
bestMatch = grammar unless grammar.bundledPackage
bestMatch
# Extended: Returns a {Number} representing how well the grammar matches the
# `filePath` and `contents`.
getGrammarScore: (grammar, filePath, contents) ->
return Infinity if @grammarOverrideForPath(filePath) is grammar.scopeName
contents = fs.readFileSync(filePath, 'utf8') if not contents? and fs.isFileSync(filePath)
if @grammarOverrideForPath(filePath) is grammar.scopeName
2 + (filePath?.length ? 0)
else if @grammarMatchesContents(grammar, contents)
1 + (filePath?.length ? 0)
else
@getGrammarPathScore(grammar, filePath)
score = @getGrammarPathScore(grammar, filePath)
if score > 0 and not grammar.bundledPackage
score += 0.25
if @grammarMatchesContents(grammar, contents)
score += 0.125
score
getGrammarPathScore: (grammar, filePath) ->
return -1 unless filePath

View File

@@ -58,6 +58,7 @@ module.exports = ({blobStore}) ->
document.title = "Spec Suite"
# Avoid throttling of test window by playing silence
# See related discussion in https://github.com/atom/atom/pull/9485
context = new AudioContext()
source = context.createBufferSource()
source.connect(context.destination)
@@ -69,6 +70,7 @@ module.exports = ({blobStore}) ->
buildAtomEnvironment = (params) ->
params = cloneObject(params)
params.blobStore = blobStore unless params.hasOwnProperty("blobStore")
params.onlyLoadBaseStyleSheets = true unless params.hasOwnProperty("onlyLoadBaseStyleSheets")
new AtomEnvironment(params)
promise = testRunner({

View File

@@ -37,6 +37,7 @@ class PackageManager
@emitter = new Emitter
@activationHookEmitter = new Emitter
@packageDirPaths = []
@deferredActivationHooks = []
if configDirPath? and not safeMode
if @devMode
@packageDirPaths.push(path.join(configDirPath, "dev", "packages"))
@@ -336,8 +337,10 @@ class PackageManager
keymapsToEnable = _.difference(oldValue, newValue)
keymapsToDisable = _.difference(newValue, oldValue)
@getLoadedPackage(packageName).deactivateKeymaps() for packageName in keymapsToDisable when not @isPackageDisabled(packageName)
@getLoadedPackage(packageName).activateKeymaps() for packageName in keymapsToEnable when not @isPackageDisabled(packageName)
for packageName in keymapsToDisable when not @isPackageDisabled(packageName)
@getLoadedPackage(packageName)?.deactivateKeymaps()
for packageName in keymapsToEnable when not @isPackageDisabled(packageName)
@getLoadedPackage(packageName)?.activateKeymaps()
null
loadPackages: ->
@@ -407,6 +410,7 @@ class PackageManager
packages = @getLoadedPackagesForTypes(types)
promises = promises.concat(activator.activatePackages(packages))
Promise.all(promises).then =>
@triggerDeferredActivationHooks()
@emitter.emit 'did-activate-initial-packages'
# another type of package manager can handle other package types.
@@ -416,11 +420,11 @@ class PackageManager
activatePackages: (packages) ->
promises = []
@config.transact =>
@config.transactAsync =>
for pack in packages
promise = @activatePackage(pack.name)
promises.push(promise) unless pack.hasActivationCommands()
return
promises.push(promise) unless pack.activationShouldBeDeferred()
Promise.all(promises)
@observeDisabledPackages()
@observePackagesWithKeymapsDisabled()
promises
@@ -437,9 +441,17 @@ class PackageManager
else
Promise.reject(new Error("Failed to load package '#{name}'"))
triggerDeferredActivationHooks: ->
return unless @deferredActivationHooks?
@activationHookEmitter.emit(hook) for hook in @deferredActivationHooks
@deferredActivationHooks = null
triggerActivationHook: (hook) ->
return new Error("Cannot trigger an empty activation hook") unless hook? and _.isString(hook) and hook.length > 0
@activationHookEmitter.emit(hook)
if @deferredActivationHooks?
@deferredActivationHooks.push hook
else
@activationHookEmitter.emit(hook)
onDidTriggerActivationHook: (hook, callback) ->
return unless hook? and _.isString(hook) and hook.length > 0

View File

@@ -312,6 +312,9 @@ class Pane extends Model
else
@activateItemAtIndex(@items.length - 1)
activateLastItem: ->
@activateItemAtIndex(@items.length - 1)
# Public: Move the active tab to the right.
moveItemRight: ->
index = @getActiveItemIndex()
@@ -722,7 +725,7 @@ class Pane extends Model
@notificationManager.addWarning("Unable to save file: #{error.message}")
else if error.code is 'EACCES'
addWarningWithPath('Unable to save file: Permission denied')
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST']
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN']
addWarningWithPath('Unable to save file', detail: error.message)
else if error.code is 'EROFS'
addWarningWithPath('Unable to save file: Read-only file system')

View File

@@ -55,7 +55,7 @@ class Project extends Model
###
deserialize: (state, deserializerManager) ->
states.paths = [state.path] if state.path? # backward compatibility
state.paths = [state.path] if state.path? # backward compatibility
@buffers = _.compact state.buffers.map (bufferState) ->
# Check that buffer's file path is accessible

View File

@@ -12,7 +12,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5)
'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6)
'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7)
'pane:show-item-9': -> @getModel().getActivePane().activateItemAtIndex(8)
'pane:show-item-9': -> @getModel().getActivePane().activateLastItem()
'pane:move-item-right': -> @getModel().getActivePane().moveItemRight()
'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft()
'window:increase-font-size': -> @getModel().increaseFontSize()

View File

@@ -50,10 +50,6 @@ class TextEditorComponent
@presenter = new TextEditorPresenter
model: @editor
scrollTop: 0
scrollLeft: 0
scrollRow: @editor.getScrollRow()
scrollColumn: @editor.getScrollColumn()
tileSize: tileSize
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay

View File

@@ -13,15 +13,12 @@ class TextEditorPresenter
minimumReflowInterval: 200
constructor: (params) ->
{@model, @config, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 6
{@model, @config} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
{@contentFrameWidth} = params
@gutterWidth = 0
@tileSize ?= 6
@realScrollTop = @scrollTop
@realScrollLeft = @scrollLeft
@disposables = new CompositeDisposable
@@ -77,7 +74,6 @@ class TextEditorPresenter
@updateVerticalDimensions()
@updateScrollbarDimensions()
@restoreScrollPosition()
@commitPendingLogicalScrollTopPosition()
@commitPendingScrollTopPosition()
@@ -218,6 +214,7 @@ class TextEditorPresenter
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
@disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this))
@disposables.add @model.onDidChangeFirstVisibleScreenRow(@didChangeFirstVisibleScreenRow.bind(this))
@observeCursor(cursor) for cursor in @model.getCursors()
@disposables.add @model.onDidAddGutter(@didAddGutter.bind(this))
return
@@ -778,8 +775,7 @@ class TextEditorPresenter
if scrollTop isnt @realScrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@scrollRow = Math.round(@scrollTop / @lineHeight)
@model.setScrollRow(@scrollRow)
@model.setFirstVisibleScreenRow(Math.round(@scrollTop / @lineHeight), true)
@updateStartRow()
@updateEndRow()
@@ -795,8 +791,7 @@ class TextEditorPresenter
if scrollLeft isnt @realScrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth)
@model.setScrollColumn(@scrollColumn)
@model.setFirstVisibleScreenColumn(Math.round(@scrollLeft / @baseCharacterWidth))
@emitter.emit 'did-change-scroll-left', @scrollLeft
@@ -1095,6 +1090,7 @@ class TextEditorPresenter
setLineHeight: (lineHeight) ->
unless @lineHeight is lineHeight
@lineHeight = lineHeight
@restoreScrollTopIfNeeded()
@model.setLineHeightInPixels(lineHeight)
@shouldUpdateHeightState = true
@shouldUpdateHorizontalScrollState = true
@@ -1122,6 +1118,7 @@ class TextEditorPresenter
@halfWidthCharWidth = halfWidthCharWidth
@koreanCharWidth = koreanCharWidth
@model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
@restoreScrollLeftIfNeeded()
@characterWidthsChanged()
characterWidthsChanged: ->
@@ -1433,6 +1430,9 @@ class TextEditorPresenter
@emitDidUpdateState()
didChangeFirstVisibleScreenRow: (screenRow) ->
@updateScrollTop(screenRow * @lineHeight)
getVerticalScrollMarginInPixels: ->
Math.round(@model.getVerticalScrollMargin() * @lineHeight)
@@ -1512,14 +1512,6 @@ class TextEditorPresenter
@updateScrollTop(@pendingScrollTop)
@pendingScrollTop = null
restoreScrollPosition: ->
return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements()
@setScrollTop(@scrollRow * @lineHeight) if @scrollRow?
@setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn?
@hasRestoredScrollPosition = true
clearPendingScrollPosition: ->
@pendingScrollLogicalPosition = null
@pendingScrollTop = null
@@ -1531,6 +1523,14 @@ class TextEditorPresenter
canScrollTopTo: (scrollTop) ->
@scrollTop isnt @constrainScrollTop(scrollTop)
restoreScrollTopIfNeeded: ->
unless @scrollTop?
@updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight)
restoreScrollLeftIfNeeded: ->
unless @scrollLeft?
@updateScrollLeft(@model.getFirstVisibleScreenColumn() * @baseCharacterWidth)
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback

View File

@@ -54,13 +54,11 @@ GutterContainer = require './gutter-container'
# soft wraps and folds to ensure your code interacts with them correctly.
module.exports =
class TextEditor extends Model
callDisplayBufferCreatedHook: false
buffer: null
languageMode: null
cursors: null
selections: null
suppressSelectionMerging: false
updateBatchDepth: 0
selectionFlashDuration: 500
gutterContainer: null
@@ -90,7 +88,7 @@ class TextEditor extends Model
super
{
@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength,
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength,
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
@@ -106,6 +104,8 @@ class TextEditor extends Model
throw new Error("Must pass a project parameter when constructing TextEditors") unless @project?
throw new Error("Must pass an assert parameter when constructing TextEditors") unless @assert?
@firstVisibleScreenRow ?= 0
@firstVisibleScreenColumn ?= 0
@emitter = new Emitter
@disposables = new CompositeDisposable
@cursors = []
@@ -146,8 +146,8 @@ class TextEditor extends Model
deserializer: 'TextEditor'
id: @id
softTabs: @softTabs
scrollRow: @getScrollRow()
scrollColumn: @getScrollColumn()
firstVisibleScreenRow: @getFirstVisibleScreenRow()
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
displayBuffer: @displayBuffer.serialize()
selectionsMarkerLayerId: @selectionsMarkerLayer.id
@@ -453,6 +453,9 @@ class TextEditor extends Model
onDidChangeCharacterWidths: (callback) ->
@displayBuffer.onDidChangeCharacterWidths(callback)
onDidChangeFirstVisibleScreenRow: (callback, fromView) ->
@emitter.on 'did-change-first-visible-screen-row', callback
onDidChangeScrollTop: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
@@ -583,18 +586,18 @@ class TextEditor extends Model
else
'untitled'
# Essential: Get unique title for display in other parts of the UI
# such as the window title.
# Essential: Get unique title for display in other parts of the UI, such as
# the window title.
#
# If the editor's buffer is unsaved, its title is "untitled"
# If the editor's buffer is saved, its unique title is formatted as one
# of the following,
# * "<filename>" when it is the only editing buffer with this file name.
# * "<unique-dir-prefix>/.../<filename>", where the "..." may be omitted
# if the the direct parent directory is already different.
# if the the direct parent directory is already different.
#
# Returns a {String}
getUniqueTitle: ->
getLongTitle: ->
if sessionPath = @getPath()
title = @getTitle()
@@ -622,22 +625,6 @@ class TextEditor extends Model
else
'untitled'
# Essential: Get the editor's long title for display in other parts of the UI
# such as the window title.
#
# If the editor's buffer is saved, its long title is formatted as
# "<filename> - <directory>". If it is unsaved, its title is "untitled"
#
# Returns a {String}.
getLongTitle: ->
if sessionPath = @getPath()
fileName = path.basename(sessionPath)
directory = @project.relativize(path.dirname(sessionPath))
directory = if directory.length > 0 then directory else path.basename(path.dirname(sessionPath))
"#{fileName} - #{directory}"
else
'untitled'
# Essential: Returns the {String} path of this editor's text buffer.
getPath: -> @buffer.getPath()
@@ -3130,14 +3117,6 @@ class TextEditor extends Model
@placeholderText = placeholderText
@emitter.emit 'did-change-placeholder-text', @placeholderText
getFirstVisibleScreenRow: ->
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
@viewRegistry.getView(this).getVisibleRowRange()[0]
getLastVisibleScreenRow: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
@viewRegistry.getView(this).getVisibleRowRange()[1]
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
@viewRegistry.getView(this).pixelPositionForBufferPosition(bufferPosition)
@@ -3192,11 +3171,40 @@ class TextEditor extends Model
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@displayBuffer.getWidth()
getScrollRow: -> @scrollRow
setScrollRow: (@scrollRow) ->
# Experimental: Scroll the editor such that the given screen row is at the
# top of the visible area.
setFirstVisibleScreenRow: (screenRow, fromView) ->
unless fromView
maxScreenRow = @getLineCount() - 1
unless @config.get('editor.scrollPastEnd')
height = @displayBuffer.getHeight()
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
if height? and lineHeightInPixels?
maxScreenRow -= Math.floor(height / lineHeightInPixels)
screenRow = Math.max(Math.min(screenRow, maxScreenRow), 0)
getScrollColumn: -> @scrollColumn
setScrollColumn: (@scrollColumn) ->
unless screenRow is @firstVisibleScreenRow
@firstVisibleScreenRow = screenRow
@emitter.emit 'did-change-first-visible-screen-row', screenRow unless fromView
getFirstVisibleScreenRow: -> @firstVisibleScreenRow
getLastVisibleScreenRow: ->
height = @displayBuffer.getHeight()
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
if height? and lineHeightInPixels?
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getLineCount() - 1)
else
null
getVisibleRowRange: ->
if lastVisibleScreenRow = @getLastVisibleScreenRow()
[@firstVisibleScreenRow, lastVisibleScreenRow]
else
null
setFirstVisibleScreenColumn: (@firstVisibleScreenColumn) ->
getFirstVisibleScreenColumn: -> @firstVisibleScreenColumn
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
@@ -3253,11 +3261,6 @@ class TextEditor extends Model
@viewRegistry.getView(this).getMaxScrollTop()
getVisibleRowRange: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.")
@viewRegistry.getView(this).getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")

View File

@@ -468,7 +468,7 @@ class Workspace extends Model
when 'EACCES'
@notificationManager.addWarning("Permission denied '#{error.path}'")
return Promise.resolve()
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR'
@notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
return Promise.resolve()
else