Merge branch 'master' into ns-switch-to-display-layers

# Conflicts:
#	src/text-editor.coffee
This commit is contained in:
Antonio Scandurra 2016-05-04 18:56:45 +02:00
commit 3b46d7f50b
10 changed files with 161 additions and 33 deletions

View File

@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.9.3"
"atom-package-manager": "1.10.0"
}
}

View File

@ -36,7 +36,7 @@
"key-path-helpers": "^0.4.0",
"less-cache": "0.23",
"line-top-index": "0.2.0",
"marked": "^0.3.4",
"marked": "^0.3.5",
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"ohnogit": "0.0.11",
@ -77,7 +77,7 @@
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.11.1",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.30.0",
"autocomplete-plus": "2.31.0",
"autocomplete-snippets": "1.10.0",
"autoflow": "0.27.0",
"autosave": "0.23.1",
@ -87,7 +87,7 @@
"command-palette": "0.38.0",
"deprecation-cop": "0.54.1",
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"encoding-selector": "0.22.0",
"exception-reporting": "0.38.1",
"fuzzy-finder": "1.0.5",
"git-diff": "1.0.1",
@ -97,20 +97,20 @@
"image-view": "0.57.0",
"incompatible-packages": "0.26.1",
"keybinding-resolver": "0.35.0",
"line-ending-selector": "0.4.1",
"line-ending-selector": "0.5.0",
"link": "0.31.1",
"markdown-preview": "0.158.0",
"metrics": "0.53.1",
"notifications": "0.63.2",
"open-on-github": "1.1.0",
"package-generator": "1.0.0",
"settings-view": "0.235.1",
"settings-view": "0.236.0",
"snippets": "1.0.2",
"spell-check": "0.67.1",
"status-bar": "1.2.6",
"styleguide": "0.45.2",
"symbols-view": "0.112.0",
"tabs": "0.93.1",
"symbols-view": "0.113.0",
"tabs": "0.93.2",
"timecop": "0.33.1",
"tree-view": "0.206.2",
"update-package-dependencies": "0.10.0",

View File

@ -74,6 +74,13 @@ describe "CommandRegistry", ->
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['.foo.bar', '.bar', '.foo']
it "orders inline listeners by reverse registration order", ->
calls = []
registry.add child, 'command', -> calls.push('child1')
registry.add child, 'command', -> calls.push('child2')
child.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child2', 'child1']
it "stops bubbling through ancestors when .stopPropagation() is called on the event", ->
calls = []

View File

@ -917,6 +917,82 @@ describe "Pane", ->
expect(item1.save).not.toHaveBeenCalled()
expect(pane.isDestroyed()).toBe false
describe "when item fails to save", ->
[pane, item1, item2] = []
beforeEach ->
pane = new Pane({items: [new Item("A"), new Item("B")], applicationDelegate: atom.applicationDelegate, config: atom.config})
[item1, item2] = pane.getItems()
item1.shouldPromptToSave = -> true
item1.getURI = -> "/test/path"
item1.save = jasmine.createSpy("save").andCallFake ->
error = new Error("EACCES, permission denied '/test/path'")
error.path = '/test/path'
error.code = 'EACCES'
throw error
it "does not destroy the pane if save fails and user clicks cancel", ->
confirmations = 0
confirm.andCallFake ->
confirmations++
if confirmations is 1
return 0 # click save
else
return 1 # click cancel
pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(2)
expect(item1.save).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe false
it "does destroy the pane if the user saves the file under a new name", ->
item1.saveAs = jasmine.createSpy("saveAs").andReturn(true)
confirmations = 0
confirm.andCallFake ->
confirmations++
return 0 # save and then save as
showSaveDialog.andReturn("new/path")
pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(2)
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
expect(item1.save).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe true
it "asks again if the saveAs also fails", ->
item1.saveAs = jasmine.createSpy("saveAs").andCallFake ->
error = new Error("EACCES, permission denied '/test/path'")
error.path = '/test/path'
error.code = 'EACCES'
throw error
confirmations = 0
confirm.andCallFake ->
confirmations++
if confirmations < 3
return 0 # save, save as, save as
return 2 # don't save
showSaveDialog.andReturn("new/path")
pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(3)
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
expect(item1.save).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe true
describe "::destroy()", ->
[container, pane1, pane2] = []

View File

@ -10,6 +10,7 @@ describe "TextEditorRegistry", ->
it "gets added to the list of registered editors", ->
editor = {}
registry.add(editor)
expect(editor.registered).toBe true
expect(registry.editors.size).toBe 1
expect(registry.editors.has(editor)).toBe(true)
@ -19,6 +20,16 @@ describe "TextEditorRegistry", ->
expect(registry.editors.size).toBe 1
disposable.dispose()
expect(registry.editors.size).toBe 0
expect(editor.registered).toBe false
it "can be removed", ->
editor = {}
registry.add(editor)
expect(registry.editors.size).toBe 1
success = registry.remove(editor)
expect(success).toBe true
expect(registry.editors.size).toBe 0
expect(editor.registered).toBe false
describe "when the registry is observed", ->
it "calls the callback for current and future editors until unsubscribed", ->

View File

@ -244,11 +244,14 @@ class CommandRegistry
(@selectorBasedListenersByCommandName[event.type] ? [])
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
.sort (a, b) -> a.compare(b)
listeners = listeners.concat(selectorBasedListeners)
listeners = selectorBasedListeners.concat(listeners)
matched = true if listeners.length > 0
for listener in listeners
# Call inline listeners first in reverse registration order,
# and selector-based listeners by specificity and reverse
# registration order.
for listener in listeners by -1
break if immediatePropagationStopped
listener.callback.call(currentTarget, dispatchedEvent)
@ -271,8 +274,8 @@ class SelectorBasedListener
@sequenceNumber = SequenceCount++
compare: (other) ->
other.specificity - @specificity or
other.sequenceNumber - @sequenceNumber
@specificity - other.specificity or
@sequenceNumber - other.sequenceNumber
class InlineListener
constructor: (@callback) ->

View File

@ -577,15 +577,23 @@ class Pane extends Model
else
return true
chosen = @applicationDelegate.confirm
message: "'#{item.getTitle?() ? uri}' has changes, do you want to save them?"
detailedMessage: "Your changes will be lost if you close this item without saving."
buttons: ["Save", "Cancel", "Don't Save"]
saveDialog = (saveButtonText, saveFn, message) =>
chosen = @applicationDelegate.confirm
message: message
detailedMessage: "Your changes will be lost if you close this item without saving."
buttons: [saveButtonText, "Cancel", "Don't save"]
switch chosen
when 0 then saveFn(item, saveError)
when 1 then false
when 2 then true
switch chosen
when 0 then @saveItem(item, -> true)
when 1 then false
when 2 then true
saveError = (error) =>
if error
saveDialog("Save as", @saveItemAs, "'#{item.getTitle?() ? uri}' could not be saved.\nError: #{@getMessageForErrorCode(error.code)}")
else
true
saveDialog("Save", @saveItem, "'#{item.getTitle?() ? uri}' has changes, do you want to save them?")
# Public: Save the active item.
saveActiveItem: (nextAction) ->
@ -602,9 +610,11 @@ class Pane extends Model
# Public: Save the given item.
#
# * `item` The item to save.
# * `nextAction` (optional) {Function} which will be called after the item is
# successfully saved.
saveItem: (item, nextAction) ->
# * `nextAction` (optional) {Function} which will be called with no argument
# after the item is successfully saved, or with the error if it failed.
# The return value will be that of `nextAction` or `undefined` if it was not
# provided
saveItem: (item, nextAction) =>
if typeof item?.getURI is 'function'
itemURI = item.getURI()
else if typeof item?.getUri is 'function'
@ -613,9 +623,12 @@ class Pane extends Model
if itemURI?
try
item.save?()
nextAction?()
catch error
@handleSaveError(error, item)
nextAction?()
if nextAction
nextAction(error)
else
@handleSaveError(error, item)
else
@saveItemAs(item, nextAction)
@ -623,9 +636,11 @@ class Pane extends Model
# path they select.
#
# * `item` The item to save.
# * `nextAction` (optional) {Function} which will be called after the item is
# successfully saved.
saveItemAs: (item, nextAction) ->
# * `nextAction` (optional) {Function} which will be called with no argument
# after the item is successfully saved, or with the error if it failed.
# The return value will be that of `nextAction` or `undefined` if it was not
# provided
saveItemAs: (item, nextAction) =>
return unless item?.saveAs?
saveOptions = item.getSaveDialogOptions?() ? {}
@ -634,9 +649,12 @@ class Pane extends Model
if newItemPath
try
item.saveAs(newItemPath)
nextAction?()
catch error
@handleSaveError(error, item)
nextAction?()
if nextAction
nextAction(error)
else
@handleSaveError(error, item)
# Public: Save all items.
saveItems: ->

View File

@ -26,8 +26,20 @@ class TextEditorRegistry
# editor is destroyed.
add: (editor) ->
@editors.add(editor)
editor.registered = true
@emitter.emit 'did-add-editor', editor
new Disposable => @editors.delete(editor)
new Disposable => @remove(editor)
# Remove a `TextEditor`.
#
# * `editor` The editor to remove.
#
# Returns a {Boolean} indicating whether the editor was successfully removed.
remove: (editor) ->
removed = @editors.delete(editor)
editor.registered = false
removed
# Invoke the given callback with all the current and future registered
# `TextEditors`.

View File

@ -76,6 +76,7 @@ class TextEditor extends Model
defaultCharWidth: null
height: null
width: null
registered: false
Object.defineProperty @prototype, "element",
get: -> @getElement()
@ -202,7 +203,7 @@ class TextEditor extends Model
tokenizedBuffer: tokenizedBufferState
largeFileMode: @largeFileMode
displayLayerId: @displayLayer.id
registered: atom.textEditors.editors.has this
registered: @registered
subscribeToBuffer: ->
@buffer.retain()

View File

@ -1092,7 +1092,7 @@ class Workspace extends Model
if editor.getPath()
checkoutHead = =>
@project.repositoryForDirectory(new Directory(editor.getDirectoryPath()))
.then (repository) =>
.then (repository) ->
repository?.async.checkoutHeadForEditor(editor)
if @config.get('editor.confirmCheckoutHeadRevision')