mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Fix handling of .save and .saveAs rejections
* Make Pane.close, Pane.saveActiveItem, and Pane.saveActiveItemAs async. * Refactor the logic for prompting to save on window unload
This commit is contained in:
@@ -477,8 +477,6 @@ describe "AtomEnvironment", ->
|
|||||||
devMode: atom.inDevMode()
|
devMode: atom.inDevMode()
|
||||||
safeMode: atom.inSafeMode()
|
safeMode: atom.inSafeMode()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe "::unloadEditorWindow()", ->
|
describe "::unloadEditorWindow()", ->
|
||||||
it "saves the BlobStore so it can be loaded after reload", ->
|
it "saves the BlobStore so it can be loaded after reload", ->
|
||||||
configDirPath = temp.mkdirSync('atom-spec-environment')
|
configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ describe('AtomApplication', function () {
|
|||||||
sendBackToMainProcess(null)
|
sendBackToMainProcess(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
await window1.saveState()
|
await window1.prepareToUnload()
|
||||||
window1.close()
|
window1.close()
|
||||||
await window1.closedPromise
|
await window1.closedPromise
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ describe('AtomApplication', function () {
|
|||||||
sendBackToMainProcess(textEditor.getText())
|
sendBackToMainProcess(textEditor.getText())
|
||||||
})
|
})
|
||||||
assert.equal(window2Text, 'Hello World! How are you?')
|
assert.equal(window2Text, 'Hello World! How are you?')
|
||||||
await window2.saveState()
|
await window2.prepareToUnload()
|
||||||
window2.close()
|
window2.close()
|
||||||
await window2.closedPromise
|
await window2.closedPromise
|
||||||
|
|
||||||
@@ -354,8 +354,8 @@ describe('AtomApplication', function () {
|
|||||||
])
|
])
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
app1Window1.saveState(),
|
app1Window1.prepareToUnload(),
|
||||||
app1Window2.saveState()
|
app1Window2.prepareToUnload()
|
||||||
])
|
])
|
||||||
|
|
||||||
const atomApplication2 = buildAtomApplication()
|
const atomApplication2 = buildAtomApplication()
|
||||||
@@ -471,7 +471,7 @@ describe('AtomApplication', function () {
|
|||||||
await focusWindow(window2)
|
await focusWindow(window2)
|
||||||
electron.app.quit()
|
electron.app.quit()
|
||||||
assert(!electron.app.hasQuitted())
|
assert(!electron.app.hasQuitted())
|
||||||
await Promise.all([window1.lastSaveStatePromise, window2.lastSaveStatePromise])
|
await Promise.all([window1.lastPrepareToUnloadPromise, window2.lastPrepareToUnloadPromise])
|
||||||
assert(electron.app.hasQuitted())
|
assert(electron.app.hasQuitted())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -149,11 +149,11 @@ describe "PaneContainerElement", ->
|
|||||||
)
|
)
|
||||||
expectPaneScale [leftPane, 0.5], [middlePane, 0.75], [rightPane, 1.75]
|
expectPaneScale [leftPane, 0.5], [middlePane, 0.75], [rightPane, 1.75]
|
||||||
|
|
||||||
middlePane.close()
|
waitsForPromise -> middlePane.close()
|
||||||
expectPaneScale [leftPane, 0.44], [rightPane, 1.55]
|
runs -> expectPaneScale [leftPane, 0.44], [rightPane, 1.55]
|
||||||
|
|
||||||
leftPane.close()
|
waitsForPromise -> leftPane.close()
|
||||||
expectPaneScale [rightPane, 1]
|
runs -> expectPaneScale [rightPane, 1]
|
||||||
|
|
||||||
it "splits or closes panes in orthogonal direction that the pane is being dragged", ->
|
it "splits or closes panes in orthogonal direction that the pane is being dragged", ->
|
||||||
leftPane = container.getActivePane()
|
leftPane = container.getActivePane()
|
||||||
@@ -173,8 +173,8 @@ describe "PaneContainerElement", ->
|
|||||||
expectPaneScale [lowerPane, 1], [leftPane, 1], [leftPane.getParent(), 0.5]
|
expectPaneScale [lowerPane, 1], [leftPane, 1], [leftPane.getParent(), 0.5]
|
||||||
|
|
||||||
# dynamically close pane, the pane's flexscale will recorver to origin value
|
# dynamically close pane, the pane's flexscale will recorver to origin value
|
||||||
lowerPane.close()
|
waitsForPromise -> lowerPane.close()
|
||||||
expectPaneScale [leftPane, 0.5], [rightPane, 1.5]
|
runs -> expectPaneScale [leftPane, 0.5], [rightPane, 1.5]
|
||||||
|
|
||||||
it "unsubscribes from mouse events when the pane is detached", ->
|
it "unsubscribes from mouse events when the pane is detached", ->
|
||||||
container.getActivePane().splitRight()
|
container.getActivePane().splitRight()
|
||||||
|
|||||||
@@ -200,15 +200,17 @@ describe "PaneContainer", ->
|
|||||||
|
|
||||||
it "returns true if the user saves all modified files when prompted", ->
|
it "returns true if the user saves all modified files when prompted", ->
|
||||||
confirm.andReturn(0)
|
confirm.andReturn(0)
|
||||||
saved = container.confirmClose()
|
waitsForPromise ->
|
||||||
expect(saved).toBeTruthy()
|
container.confirmClose().then (saved) ->
|
||||||
expect(confirm).toHaveBeenCalled()
|
expect(confirm).toHaveBeenCalled()
|
||||||
|
expect(saved).toBeTruthy()
|
||||||
|
|
||||||
it "returns false if the user cancels saving any modified file", ->
|
it "returns false if the user cancels saving any modified file", ->
|
||||||
confirm.andReturn(1)
|
confirm.andReturn(1)
|
||||||
saved = container.confirmClose()
|
waitsForPromise ->
|
||||||
expect(saved).toBeFalsy()
|
container.confirmClose().then (saved) ->
|
||||||
expect(confirm).toHaveBeenCalled()
|
expect(confirm).toHaveBeenCalled()
|
||||||
|
expect(saved).toBeFalsy()
|
||||||
|
|
||||||
describe "::onDidAddPane(callback)", ->
|
describe "::onDidAddPane(callback)", ->
|
||||||
it "invokes the given callback when panes are added", ->
|
it "invokes the given callback when panes are added", ->
|
||||||
|
|||||||
@@ -460,11 +460,12 @@ describe "Pane", ->
|
|||||||
it "saves the item before destroying it", ->
|
it "saves the item before destroying it", ->
|
||||||
itemURI = "test"
|
itemURI = "test"
|
||||||
confirm.andReturn(0)
|
confirm.andReturn(0)
|
||||||
pane.destroyItem(item1)
|
|
||||||
|
|
||||||
expect(item1.save).toHaveBeenCalled()
|
waitsForPromise ->
|
||||||
expect(item1 in pane.getItems()).toBe false
|
pane.destroyItem(item1).then ->
|
||||||
expect(item1.isDestroyed()).toBe true
|
expect(item1.save).toHaveBeenCalled()
|
||||||
|
expect(item1 in pane.getItems()).toBe false
|
||||||
|
expect(item1.isDestroyed()).toBe true
|
||||||
|
|
||||||
describe "when the item has no uri", ->
|
describe "when the item has no uri", ->
|
||||||
it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
|
it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
|
||||||
@@ -472,21 +473,23 @@ describe "Pane", ->
|
|||||||
|
|
||||||
showSaveDialog.andReturn("/selected/path")
|
showSaveDialog.andReturn("/selected/path")
|
||||||
confirm.andReturn(0)
|
confirm.andReturn(0)
|
||||||
pane.destroyItem(item1)
|
|
||||||
|
|
||||||
expect(showSaveDialog).toHaveBeenCalled()
|
waitsForPromise ->
|
||||||
expect(item1.saveAs).toHaveBeenCalledWith("/selected/path")
|
pane.destroyItem(item1).then ->
|
||||||
expect(item1 in pane.getItems()).toBe false
|
expect(showSaveDialog).toHaveBeenCalled()
|
||||||
expect(item1.isDestroyed()).toBe true
|
expect(item1.saveAs).toHaveBeenCalledWith("/selected/path")
|
||||||
|
expect(item1 in pane.getItems()).toBe false
|
||||||
|
expect(item1.isDestroyed()).toBe true
|
||||||
|
|
||||||
describe "if the [Don't Save] option is selected", ->
|
describe "if the [Don't Save] option is selected", ->
|
||||||
it "removes and destroys the item without saving it", ->
|
it "removes and destroys the item without saving it", ->
|
||||||
confirm.andReturn(2)
|
confirm.andReturn(2)
|
||||||
pane.destroyItem(item1)
|
|
||||||
|
|
||||||
expect(item1.save).not.toHaveBeenCalled()
|
waitsForPromise ->
|
||||||
expect(item1 in pane.getItems()).toBe false
|
pane.destroyItem(item1).then ->
|
||||||
expect(item1.isDestroyed()).toBe true
|
expect(item1.save).not.toHaveBeenCalled()
|
||||||
|
expect(item1 in pane.getItems()).toBe false
|
||||||
|
expect(item1.isDestroyed()).toBe true
|
||||||
|
|
||||||
describe "if the [Cancel] option is selected", ->
|
describe "if the [Cancel] option is selected", ->
|
||||||
it "does not save, remove, or destroy the item", ->
|
it "does not save, remove, or destroy the item", ->
|
||||||
@@ -550,11 +553,14 @@ describe "Pane", ->
|
|||||||
it "destroys all items", ->
|
it "destroys all items", ->
|
||||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
|
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
|
||||||
[item1, item2, item3] = pane.getItems()
|
[item1, item2, item3] = pane.getItems()
|
||||||
pane.destroyItems()
|
|
||||||
expect(item1.isDestroyed()).toBe true
|
waitsForPromise -> pane.destroyItems()
|
||||||
expect(item2.isDestroyed()).toBe true
|
|
||||||
expect(item3.isDestroyed()).toBe true
|
runs ->
|
||||||
expect(pane.getItems()).toEqual []
|
expect(item1.isDestroyed()).toBe true
|
||||||
|
expect(item2.isDestroyed()).toBe true
|
||||||
|
expect(item3.isDestroyed()).toBe true
|
||||||
|
expect(pane.getItems()).toEqual []
|
||||||
|
|
||||||
describe "::observeItems()", ->
|
describe "::observeItems()", ->
|
||||||
it "invokes the observer with all current and future items", ->
|
it "invokes the observer with all current and future items", ->
|
||||||
@@ -620,24 +626,22 @@ describe "Pane", ->
|
|||||||
pane.saveActiveItem()
|
pane.saveActiveItem()
|
||||||
expect(showSaveDialog).not.toHaveBeenCalled()
|
expect(showSaveDialog).not.toHaveBeenCalled()
|
||||||
|
|
||||||
describe "when the item's saveAs method throws a well-known IO error", ->
|
describe "when the item's saveAs rejects with a well-known IO error", ->
|
||||||
notificationSpy = null
|
|
||||||
beforeEach ->
|
|
||||||
atom.notifications.onDidAddNotification notificationSpy = jasmine.createSpy()
|
|
||||||
|
|
||||||
it "creates a notification", ->
|
it "creates a notification", ->
|
||||||
pane.getActiveItem().saveAs = ->
|
pane.getActiveItem().saveAs = ->
|
||||||
error = new Error("EACCES, permission denied '/foo'")
|
error = new Error("EACCES, permission denied '/foo'")
|
||||||
error.path = '/foo'
|
error.path = '/foo'
|
||||||
error.code = 'EACCES'
|
error.code = 'EACCES'
|
||||||
throw error
|
Promise.reject(error)
|
||||||
|
|
||||||
pane.saveActiveItem()
|
waitsFor (done) ->
|
||||||
expect(notificationSpy).toHaveBeenCalled()
|
subscription = atom.notifications.onDidAddNotification (notification) ->
|
||||||
notification = notificationSpy.mostRecentCall.args[0]
|
expect(notification.getType()).toBe 'warning'
|
||||||
expect(notification.getType()).toBe 'warning'
|
expect(notification.getMessage()).toContain 'Permission denied'
|
||||||
expect(notification.getMessage()).toContain 'Permission denied'
|
expect(notification.getMessage()).toContain '/foo'
|
||||||
expect(notification.getMessage()).toContain '/foo'
|
subscription.dispose()
|
||||||
|
done()
|
||||||
|
pane.saveActiveItem()
|
||||||
|
|
||||||
describe "::saveActiveItemAs()", ->
|
describe "::saveActiveItemAs()", ->
|
||||||
pane = null
|
pane = null
|
||||||
@@ -661,23 +665,21 @@ describe "Pane", ->
|
|||||||
expect(showSaveDialog).not.toHaveBeenCalled()
|
expect(showSaveDialog).not.toHaveBeenCalled()
|
||||||
|
|
||||||
describe "when the item's saveAs method throws a well-known IO error", ->
|
describe "when the item's saveAs method throws a well-known IO error", ->
|
||||||
notificationSpy = null
|
|
||||||
beforeEach ->
|
|
||||||
atom.notifications.onDidAddNotification notificationSpy = jasmine.createSpy()
|
|
||||||
|
|
||||||
it "creates a notification", ->
|
it "creates a notification", ->
|
||||||
pane.getActiveItem().saveAs = ->
|
pane.getActiveItem().saveAs = ->
|
||||||
error = new Error("EACCES, permission denied '/foo'")
|
error = new Error("EACCES, permission denied '/foo'")
|
||||||
error.path = '/foo'
|
error.path = '/foo'
|
||||||
error.code = 'EACCES'
|
error.code = 'EACCES'
|
||||||
throw error
|
Promise.reject(error)
|
||||||
|
|
||||||
pane.saveActiveItemAs()
|
waitsFor (done) ->
|
||||||
expect(notificationSpy).toHaveBeenCalled()
|
subscription = atom.notifications.onDidAddNotification (notification) ->
|
||||||
notification = notificationSpy.mostRecentCall.args[0]
|
expect(notification.getType()).toBe 'warning'
|
||||||
expect(notification.getType()).toBe 'warning'
|
expect(notification.getMessage()).toContain 'Permission denied'
|
||||||
expect(notification.getMessage()).toContain 'Permission denied'
|
expect(notification.getMessage()).toContain '/foo'
|
||||||
expect(notification.getMessage()).toContain '/foo'
|
subscription.dispose()
|
||||||
|
done()
|
||||||
|
pane.saveActiveItemAs()
|
||||||
|
|
||||||
describe "::itemForURI(uri)", ->
|
describe "::itemForURI(uri)", ->
|
||||||
it "returns the item for which a call to .getURI() returns the given uri", ->
|
it "returns the item for which a call to .getURI() returns the given uri", ->
|
||||||
@@ -787,7 +789,6 @@ describe "Pane", ->
|
|||||||
pane2.moveItemToPane(item5, pane1, 0)
|
pane2.moveItemToPane(item5, pane1, 0)
|
||||||
expect(pane1.getPendingItem()).toEqual item6
|
expect(pane1.getPendingItem()).toEqual item6
|
||||||
|
|
||||||
|
|
||||||
describe "split methods", ->
|
describe "split methods", ->
|
||||||
[pane1, item1, container] = []
|
[pane1, item1, container] = []
|
||||||
|
|
||||||
@@ -926,11 +927,10 @@ describe "Pane", ->
|
|||||||
item1.save = jasmine.createSpy("save")
|
item1.save = jasmine.createSpy("save")
|
||||||
|
|
||||||
confirm.andReturn(0)
|
confirm.andReturn(0)
|
||||||
pane.close()
|
pane.close().then ->
|
||||||
|
expect(confirm).toHaveBeenCalled()
|
||||||
expect(confirm).toHaveBeenCalled()
|
expect(item1.save).toHaveBeenCalled()
|
||||||
expect(item1.save).toHaveBeenCalled()
|
expect(pane.isDestroyed()).toBe true
|
||||||
expect(pane.isDestroyed()).toBe true
|
|
||||||
|
|
||||||
it "does not destroy the pane if cancel is called", ->
|
it "does not destroy the pane if cancel is called", ->
|
||||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
|
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
|
||||||
@@ -941,11 +941,12 @@ describe "Pane", ->
|
|||||||
item1.save = jasmine.createSpy("save")
|
item1.save = jasmine.createSpy("save")
|
||||||
|
|
||||||
confirm.andReturn(1)
|
confirm.andReturn(1)
|
||||||
pane.close()
|
|
||||||
|
|
||||||
expect(confirm).toHaveBeenCalled()
|
waitsForPromise ->
|
||||||
expect(item1.save).not.toHaveBeenCalled()
|
pane.close().then ->
|
||||||
expect(pane.isDestroyed()).toBe false
|
expect(confirm).toHaveBeenCalled()
|
||||||
|
expect(item1.save).not.toHaveBeenCalled()
|
||||||
|
expect(pane.isDestroyed()).toBe false
|
||||||
|
|
||||||
describe "when item fails to save", ->
|
describe "when item fails to save", ->
|
||||||
[pane, item1, item2] = []
|
[pane, item1, item2] = []
|
||||||
@@ -972,12 +973,12 @@ describe "Pane", ->
|
|||||||
else
|
else
|
||||||
return 1 # click cancel
|
return 1 # click cancel
|
||||||
|
|
||||||
pane.close()
|
waitsForPromise ->
|
||||||
|
pane.close().then ->
|
||||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||||
expect(confirmations).toBe(2)
|
expect(confirmations).toBe(2)
|
||||||
expect(item1.save).toHaveBeenCalled()
|
expect(item1.save).toHaveBeenCalled()
|
||||||
expect(pane.isDestroyed()).toBe false
|
expect(pane.isDestroyed()).toBe false
|
||||||
|
|
||||||
it "does destroy the pane if the user saves the file under a new name", ->
|
it "does destroy the pane if the user saves the file under a new name", ->
|
||||||
item1.saveAs = jasmine.createSpy("saveAs").andReturn(true)
|
item1.saveAs = jasmine.createSpy("saveAs").andReturn(true)
|
||||||
@@ -989,14 +990,14 @@ describe "Pane", ->
|
|||||||
|
|
||||||
showSaveDialog.andReturn("new/path")
|
showSaveDialog.andReturn("new/path")
|
||||||
|
|
||||||
pane.close()
|
waitsForPromise ->
|
||||||
|
pane.close().then ->
|
||||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||||
expect(confirmations).toBe(2)
|
expect(confirmations).toBe(2)
|
||||||
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
|
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
|
||||||
expect(item1.save).toHaveBeenCalled()
|
expect(item1.save).toHaveBeenCalled()
|
||||||
expect(item1.saveAs).toHaveBeenCalled()
|
expect(item1.saveAs).toHaveBeenCalled()
|
||||||
expect(pane.isDestroyed()).toBe true
|
expect(pane.isDestroyed()).toBe true
|
||||||
|
|
||||||
it "asks again if the saveAs also fails", ->
|
it "asks again if the saveAs also fails", ->
|
||||||
item1.saveAs = jasmine.createSpy("saveAs").andCallFake ->
|
item1.saveAs = jasmine.createSpy("saveAs").andCallFake ->
|
||||||
@@ -1014,14 +1015,14 @@ describe "Pane", ->
|
|||||||
|
|
||||||
showSaveDialog.andReturn("new/path")
|
showSaveDialog.andReturn("new/path")
|
||||||
|
|
||||||
pane.close()
|
waitsForPromise ->
|
||||||
|
pane.close().then ->
|
||||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||||
expect(confirmations).toBe(3)
|
expect(confirmations).toBe(3)
|
||||||
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
|
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
|
||||||
expect(item1.save).toHaveBeenCalled()
|
expect(item1.save).toHaveBeenCalled()
|
||||||
expect(item1.saveAs).toHaveBeenCalled()
|
expect(item1.saveAs).toHaveBeenCalled()
|
||||||
expect(pane.isDestroyed()).toBe true
|
expect(pane.isDestroyed()).toBe true
|
||||||
|
|
||||||
describe "::destroy()", ->
|
describe "::destroy()", ->
|
||||||
[container, pane1, pane2] = []
|
[container, pane1, pane2] = []
|
||||||
|
|||||||
@@ -48,32 +48,6 @@ describe "WindowEventHandler", ->
|
|||||||
window.dispatchEvent(new CustomEvent('window:close'))
|
window.dispatchEvent(new CustomEvent('window:close'))
|
||||||
expect(atom.close).toHaveBeenCalled()
|
expect(atom.close).toHaveBeenCalled()
|
||||||
|
|
||||||
describe "beforeunload event", ->
|
|
||||||
beforeEach ->
|
|
||||||
jasmine.unspy(TextEditor.prototype, "shouldPromptToSave")
|
|
||||||
spyOn(atom, 'destroy')
|
|
||||||
spyOn(ipcRenderer, 'send')
|
|
||||||
|
|
||||||
describe "when pane items are modified", ->
|
|
||||||
editor = null
|
|
||||||
beforeEach ->
|
|
||||||
waitsForPromise -> atom.workspace.open("sample.js").then (o) -> editor = o
|
|
||||||
runs -> editor.insertText("I look different, I feel different.")
|
|
||||||
|
|
||||||
it "prompts the user to save them, and allows the unload to continue if they confirm", ->
|
|
||||||
spyOn(atom.workspace, 'confirmClose').andReturn(true)
|
|
||||||
window.dispatchEvent(new CustomEvent('beforeunload'))
|
|
||||||
expect(atom.workspace.confirmClose).toHaveBeenCalled()
|
|
||||||
expect(ipcRenderer.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
|
|
||||||
expect(atom.destroy).toHaveBeenCalled()
|
|
||||||
|
|
||||||
it "cancels the unload if the user selects cancel", ->
|
|
||||||
spyOn(atom.workspace, 'confirmClose').andReturn(false)
|
|
||||||
window.dispatchEvent(new CustomEvent('beforeunload'))
|
|
||||||
expect(atom.workspace.confirmClose).toHaveBeenCalled()
|
|
||||||
expect(ipcRenderer.send).toHaveBeenCalledWith('did-cancel-window-unload')
|
|
||||||
expect(atom.destroy).not.toHaveBeenCalled()
|
|
||||||
|
|
||||||
describe "when a link is clicked", ->
|
describe "when a link is clicked", ->
|
||||||
it "opens the http/https links in an external application", ->
|
it "opens the http/https links in an external application", ->
|
||||||
{shell} = require 'electron'
|
{shell} = require 'electron'
|
||||||
|
|||||||
@@ -2394,38 +2394,47 @@ i = /test/; #FIXME\
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('::saveActivePaneItem()', () => {
|
describe('::saveActivePaneItem()', () => {
|
||||||
let editor = null
|
let editor, notificationSpy
|
||||||
beforeEach(() =>
|
|
||||||
waitsForPromise(() => atom.workspace.open('sample.js').then(o => { editor = o }))
|
beforeEach(() => {
|
||||||
)
|
waitsForPromise(() => atom.workspace.open('sample.js').then(o => {
|
||||||
|
editor = o
|
||||||
|
}))
|
||||||
|
|
||||||
|
notificationSpy = jasmine.createSpy('did-add-notification')
|
||||||
|
atom.notifications.onDidAddNotification(notificationSpy)
|
||||||
|
})
|
||||||
|
|
||||||
describe('when there is an error', () => {
|
describe('when there is an error', () => {
|
||||||
it('emits a warning notification when the file cannot be saved', () => {
|
it('emits a warning notification when the file cannot be saved', () => {
|
||||||
let addedSpy
|
|
||||||
spyOn(editor, 'save').andCallFake(() => {
|
spyOn(editor, 'save').andCallFake(() => {
|
||||||
throw new Error("'/some/file' is a directory")
|
throw new Error("'/some/file' is a directory")
|
||||||
})
|
})
|
||||||
|
|
||||||
atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy())
|
waitsForPromise(() =>
|
||||||
atom.workspace.saveActivePaneItem()
|
atom.workspace.saveActivePaneItem().then(() => {
|
||||||
expect(addedSpy).toHaveBeenCalled()
|
expect(notificationSpy).toHaveBeenCalled()
|
||||||
expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
expect(notificationSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getMessage()).toContain('Unable to save')
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits a warning notification when the directory cannot be written to', () => {
|
it('emits a warning notification when the directory cannot be written to', () => {
|
||||||
let addedSpy
|
|
||||||
spyOn(editor, 'save').andCallFake(() => {
|
spyOn(editor, 'save').andCallFake(() => {
|
||||||
throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'")
|
throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'")
|
||||||
})
|
})
|
||||||
|
|
||||||
atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy())
|
waitsForPromise(() =>
|
||||||
atom.workspace.saveActivePaneItem()
|
atom.workspace.saveActivePaneItem().then(() => {
|
||||||
expect(addedSpy).toHaveBeenCalled()
|
expect(notificationSpy).toHaveBeenCalled()
|
||||||
expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
expect(notificationSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getMessage()).toContain('Unable to save')
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits a warning notification when the user does not have permission', () => {
|
it('emits a warning notification when the user does not have permission', () => {
|
||||||
let addedSpy
|
|
||||||
spyOn(editor, 'save').andCallFake(() => {
|
spyOn(editor, 'save').andCallFake(() => {
|
||||||
const error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'")
|
const error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'")
|
||||||
error.code = 'EACCES'
|
error.code = 'EACCES'
|
||||||
@@ -2433,10 +2442,13 @@ i = /test/; #FIXME\
|
|||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy())
|
waitsForPromise(() =>
|
||||||
atom.workspace.saveActivePaneItem()
|
atom.workspace.saveActivePaneItem().then(() => {
|
||||||
expect(addedSpy).toHaveBeenCalled()
|
expect(notificationSpy).toHaveBeenCalled()
|
||||||
expect(addedSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
expect(notificationSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getMessage()).toContain('Unable to save')
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits a warning notification when the operation is not permitted', () => {
|
it('emits a warning notification when the operation is not permitted', () => {
|
||||||
@@ -2446,10 +2458,17 @@ i = /test/; #FIXME\
|
|||||||
error.path = '/Some/dir/and-a-file.js'
|
error.path = '/Some/dir/and-a-file.js'
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
|
waitsForPromise(() =>
|
||||||
|
atom.workspace.saveActivePaneItem().then(() => {
|
||||||
|
expect(notificationSpy).toHaveBeenCalled()
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getMessage()).toContain('Unable to save')
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits a warning notification when the file is already open by another app', () => {
|
it('emits a warning notification when the file is already open by another app', () => {
|
||||||
let addedSpy
|
|
||||||
spyOn(editor, 'save').andCallFake(() => {
|
spyOn(editor, 'save').andCallFake(() => {
|
||||||
const error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'")
|
const error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'")
|
||||||
error.code = 'EBUSY'
|
error.code = 'EBUSY'
|
||||||
@@ -2457,17 +2476,16 @@ i = /test/; #FIXME\
|
|||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy())
|
waitsForPromise(() =>
|
||||||
atom.workspace.saveActivePaneItem()
|
atom.workspace.saveActivePaneItem().then(() => {
|
||||||
expect(addedSpy).toHaveBeenCalled()
|
expect(notificationSpy).toHaveBeenCalled()
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
||||||
const notificaiton = addedSpy.mostRecentCall.args[0]
|
expect(notificationSpy.mostRecentCall.args[0].getMessage()).toContain('Unable to save')
|
||||||
expect(notificaiton.getType()).toBe('warning')
|
})
|
||||||
expect(notificaiton.getMessage()).toContain('Unable to save')
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits a warning notification when the file system is read-only', () => {
|
it('emits a warning notification when the file system is read-only', () => {
|
||||||
let addedSpy
|
|
||||||
spyOn(editor, 'save').andCallFake(() => {
|
spyOn(editor, 'save').andCallFake(() => {
|
||||||
const error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'")
|
const error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'")
|
||||||
error.code = 'EROFS'
|
error.code = 'EROFS'
|
||||||
@@ -2475,13 +2493,13 @@ i = /test/; #FIXME\
|
|||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
atom.notifications.onDidAddNotification(addedSpy = jasmine.createSpy())
|
waitsForPromise(() =>
|
||||||
atom.workspace.saveActivePaneItem()
|
atom.workspace.saveActivePaneItem().then(() => {
|
||||||
expect(addedSpy).toHaveBeenCalled()
|
expect(notificationSpy).toHaveBeenCalled()
|
||||||
|
expect(notificationSpy.mostRecentCall.args[0].getType()).toBe('warning')
|
||||||
const notification = addedSpy.mostRecentCall.args[0]
|
expect(notificationSpy.mostRecentCall.args[0].getMessage()).toContain('Unable to save')
|
||||||
expect(notification.getType()).toBe('warning')
|
})
|
||||||
expect(notification.getMessage()).toContain('Unable to save')
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits a warning notification when the file cannot be saved', () => {
|
it('emits a warning notification when the file cannot be saved', () => {
|
||||||
@@ -2489,8 +2507,9 @@ i = /test/; #FIXME\
|
|||||||
throw new Error('no one knows')
|
throw new Error('no one knows')
|
||||||
})
|
})
|
||||||
|
|
||||||
const save = () => atom.workspace.saveActivePaneItem()
|
waitsForPromise({shouldReject: true}, () =>
|
||||||
expect(save).toThrow()
|
atom.workspace.saveActivePaneItem()
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -232,19 +232,14 @@ class ApplicationDelegate
|
|||||||
new Disposable ->
|
new Disposable ->
|
||||||
ipcRenderer.removeListener('context-command', outerCallback)
|
ipcRenderer.removeListener('context-command', outerCallback)
|
||||||
|
|
||||||
onSaveWindowStateRequest: (callback) ->
|
onDidRequestUnload: (callback) ->
|
||||||
outerCallback = (event, message) ->
|
outerCallback = (event, message) ->
|
||||||
callback(event)
|
callback(event).then (shouldUnload) ->
|
||||||
|
ipcRenderer.send('did-prepare-to-unload', shouldUnload)
|
||||||
|
|
||||||
ipcRenderer.on('save-window-state', outerCallback)
|
ipcRenderer.on('prepare-to-unload', outerCallback)
|
||||||
new Disposable ->
|
new Disposable ->
|
||||||
ipcRenderer.removeListener('save-window-state', outerCallback)
|
ipcRenderer.removeListener('prepare-to-unload', outerCallback)
|
||||||
|
|
||||||
didSaveWindowState: ->
|
|
||||||
ipcRenderer.send('did-save-window-state')
|
|
||||||
|
|
||||||
didCancelWindowUnload: ->
|
|
||||||
ipcRenderer.send('did-cancel-window-unload')
|
|
||||||
|
|
||||||
onDidChangeHistoryManager: (callback) ->
|
onDidChangeHistoryManager: (callback) ->
|
||||||
outerCallback = (event, message) ->
|
outerCallback = (event, message) ->
|
||||||
|
|||||||
@@ -694,9 +694,14 @@ class AtomEnvironment extends Model
|
|||||||
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
|
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
|
||||||
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
|
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
|
||||||
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
|
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
|
||||||
@disposables.add @applicationDelegate.onSaveWindowStateRequest =>
|
@disposables.add @applicationDelegate.onDidRequestUnload =>
|
||||||
callback = => @applicationDelegate.didSaveWindowState()
|
@saveState({isUnloading: true})
|
||||||
@saveState({isUnloading: true}).catch(callback).then(callback)
|
.catch(console.error)
|
||||||
|
.then =>
|
||||||
|
@workspace?.confirmClose({
|
||||||
|
windowCloseRequested: true,
|
||||||
|
projectHasPaths: @project.getPaths().length > 0
|
||||||
|
})
|
||||||
|
|
||||||
@listenForUpdates()
|
@listenForUpdates()
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ class AtomApplication
|
|||||||
unless @quitting
|
unless @quitting
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@quitting = true
|
@quitting = true
|
||||||
Promise.all(@windows.map((window) -> window.saveState())).then(-> app.quit())
|
Promise.all(@windows.map((window) -> window.prepareToUnload())).then(-> app.quit())
|
||||||
|
|
||||||
@disposable.add ipcHelpers.on app, 'will-quit', =>
|
@disposable.add ipcHelpers.on app, 'will-quit', =>
|
||||||
@killAllProcesses()
|
@killAllProcesses()
|
||||||
@@ -373,11 +373,6 @@ class AtomApplication
|
|||||||
@disposable.add ipcHelpers.respondTo 'set-temporary-window-state', (win, state) ->
|
@disposable.add ipcHelpers.respondTo 'set-temporary-window-state', (win, state) ->
|
||||||
win.temporaryState = state
|
win.temporaryState = state
|
||||||
|
|
||||||
@disposable.add ipcHelpers.on ipcMain, 'did-cancel-window-unload', =>
|
|
||||||
@quitting = false
|
|
||||||
for window in @windows
|
|
||||||
window.didCancelWindowUnload()
|
|
||||||
|
|
||||||
clipboard = require '../safe-clipboard'
|
clipboard = require '../safe-clipboard'
|
||||||
@disposable.add ipcHelpers.on ipcMain, 'write-text-to-selection-clipboard', (event, selectedText) ->
|
@disposable.add ipcHelpers.on ipcMain, 'write-text-to-selection-clipboard', (event, selectedText) ->
|
||||||
clipboard.writeText(selectedText, 'selection')
|
clipboard.writeText(selectedText, 'selection')
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ class AtomWindow
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@unloading = true
|
@unloading = true
|
||||||
@atomApplication.saveState(false)
|
@atomApplication.saveState(false)
|
||||||
@saveState().then(=> @close())
|
@prepareToUnload().then (result) =>
|
||||||
|
@close() if result
|
||||||
|
|
||||||
@browserWindow.on 'closed', =>
|
@browserWindow.on 'closed', =>
|
||||||
@fileRecoveryService.didCloseWindow(this)
|
@fileRecoveryService.didCloseWindow(this)
|
||||||
@@ -191,21 +192,19 @@ class AtomWindow
|
|||||||
@browserWindow.on 'blur', =>
|
@browserWindow.on 'blur', =>
|
||||||
@browserWindow.focusOnWebView()
|
@browserWindow.focusOnWebView()
|
||||||
|
|
||||||
didCancelWindowUnload: ->
|
prepareToUnload: ->
|
||||||
@unloading = false
|
|
||||||
|
|
||||||
saveState: ->
|
|
||||||
if @isSpecWindow()
|
if @isSpecWindow()
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
@lastPrepareToUnloadPromise = new Promise (resolve) =>
|
||||||
@lastSaveStatePromise = new Promise (resolve) =>
|
callback = (event, result) =>
|
||||||
callback = (event) =>
|
|
||||||
if BrowserWindow.fromWebContents(event.sender) is @browserWindow
|
if BrowserWindow.fromWebContents(event.sender) is @browserWindow
|
||||||
ipcMain.removeListener('did-save-window-state', callback)
|
ipcMain.removeListener('did-prepare-to-unload', callback)
|
||||||
resolve()
|
unless result
|
||||||
ipcMain.on('did-save-window-state', callback)
|
@unloading = false
|
||||||
@browserWindow.webContents.send('save-window-state')
|
@atomApplication.quitting = false
|
||||||
@lastSaveStatePromise
|
resolve(result)
|
||||||
|
ipcMain.on('did-prepare-to-unload', callback)
|
||||||
|
@browserWindow.webContents.send('prepare-to-unload')
|
||||||
|
|
||||||
openPath: (pathToOpen, initialLine, initialColumn) ->
|
openPath: (pathToOpen, initialLine, initialColumn) ->
|
||||||
@openLocations([{pathToOpen, initialLine, initialColumn}])
|
@openLocations([{pathToOpen, initialLine, initialColumn}])
|
||||||
@@ -287,7 +286,8 @@ class AtomWindow
|
|||||||
|
|
||||||
reload: ->
|
reload: ->
|
||||||
@loadedPromise = new Promise((@resolveLoadedPromise) =>)
|
@loadedPromise = new Promise((@resolveLoadedPromise) =>)
|
||||||
@saveState().then => @browserWindow.reload()
|
@prepareToUnload().then (result) =>
|
||||||
|
@browserWindow.reload() if result
|
||||||
@loadedPromise
|
@loadedPromise
|
||||||
|
|
||||||
showSaveDialog: (params) ->
|
showSaveDialog: (params) ->
|
||||||
|
|||||||
@@ -172,18 +172,13 @@ class PaneContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirmClose (options) {
|
confirmClose (options) {
|
||||||
let allSaved = true
|
const promises = []
|
||||||
|
for (const pane of this.getPanes()) {
|
||||||
for (let pane of this.getPanes()) {
|
for (const item of pane.getItems()) {
|
||||||
for (let item of pane.getItems()) {
|
promises.push(pane.promptToSaveItem(item, options))
|
||||||
if (!pane.promptToSaveItem(item, options)) {
|
|
||||||
allSaved = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Promise.all(promises).then((results) => !results.includes(false))
|
||||||
return allSaved
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activateNextPane () {
|
activateNextPane () {
|
||||||
|
|||||||
@@ -602,39 +602,47 @@ class Pane
|
|||||||
# * `force` (optional) {Boolean} Destroy the item without prompting to save
|
# * `force` (optional) {Boolean} Destroy the item without prompting to save
|
||||||
# it, even if the item's `isPermanentDockItem` method returns true.
|
# it, even if the item's `isPermanentDockItem` method returns true.
|
||||||
#
|
#
|
||||||
# Returns a {Boolean} indicating whether or not the item was destroyed.
|
# Returns a {Promise} that resolves with a {Boolean} indicating whether or not
|
||||||
|
# the item was destroyed.
|
||||||
destroyItem: (item, force) ->
|
destroyItem: (item, force) ->
|
||||||
index = @items.indexOf(item)
|
index = @items.indexOf(item)
|
||||||
if index isnt -1
|
if index isnt -1
|
||||||
return false if not force and @getContainer()?.getLocation() isnt 'center' and item.isPermanentDockItem?()
|
return false if not force and @getContainer()?.getLocation() isnt 'center' and item.isPermanentDockItem?()
|
||||||
@emitter.emit 'will-destroy-item', {item, index}
|
@emitter.emit 'will-destroy-item', {item, index}
|
||||||
@container?.willDestroyPaneItem({item, index, pane: this})
|
@container?.willDestroyPaneItem({item, index, pane: this})
|
||||||
if force or @promptToSaveItem(item)
|
if force or not item?.shouldPromptToSave?()
|
||||||
@removeItem(item, false)
|
@removeItem(item, false)
|
||||||
item.destroy?()
|
item.destroy?()
|
||||||
true
|
|
||||||
else
|
else
|
||||||
false
|
@promptToSaveItem(item).then (result) =>
|
||||||
|
if result
|
||||||
|
@removeItem(item, false)
|
||||||
|
item.destroy?()
|
||||||
|
result
|
||||||
|
|
||||||
# Public: Destroy all items.
|
# Public: Destroy all items.
|
||||||
destroyItems: ->
|
destroyItems: ->
|
||||||
@destroyItem(item) for item in @getItems()
|
Promise.all(
|
||||||
return
|
@getItems().map(@destroyItem.bind(this))
|
||||||
|
)
|
||||||
|
|
||||||
# Public: Destroy all items except for the active item.
|
# Public: Destroy all items except for the active item.
|
||||||
destroyInactiveItems: ->
|
destroyInactiveItems: ->
|
||||||
@destroyItem(item) for item in @getItems() when item isnt @activeItem
|
Promise.all(
|
||||||
return
|
@getItems()
|
||||||
|
.filter((item) => item isnt @activeItem)
|
||||||
|
.map(@destroyItem.bind(this))
|
||||||
|
)
|
||||||
|
|
||||||
promptToSaveItem: (item, options={}) ->
|
promptToSaveItem: (item, options={}) ->
|
||||||
return true unless item.shouldPromptToSave?(options)
|
return Promise.resolve(true) unless item.shouldPromptToSave?(options)
|
||||||
|
|
||||||
if typeof item.getURI is 'function'
|
if typeof item.getURI is 'function'
|
||||||
uri = item.getURI()
|
uri = item.getURI()
|
||||||
else if typeof item.getUri is 'function'
|
else if typeof item.getUri is 'function'
|
||||||
uri = item.getUri()
|
uri = item.getUri()
|
||||||
else
|
else
|
||||||
return true
|
return Promise.resolve(true)
|
||||||
|
|
||||||
saveDialog = (saveButtonText, saveFn, message) =>
|
saveDialog = (saveButtonText, saveFn, message) =>
|
||||||
chosen = @applicationDelegate.confirm
|
chosen = @applicationDelegate.confirm
|
||||||
@@ -642,15 +650,21 @@ class Pane
|
|||||||
detailedMessage: "Your changes will be lost if you close this item without saving."
|
detailedMessage: "Your changes will be lost if you close this item without saving."
|
||||||
buttons: [saveButtonText, "Cancel", "Don't Save"]
|
buttons: [saveButtonText, "Cancel", "Don't Save"]
|
||||||
switch chosen
|
switch chosen
|
||||||
when 0 then saveFn(item, saveError)
|
when 0
|
||||||
when 1 then false
|
new Promise (resolve) ->
|
||||||
when 2 then true
|
saveFn item, (error) ->
|
||||||
|
console.log 'error', error
|
||||||
|
saveError(error).then(resolve)
|
||||||
|
when 1
|
||||||
|
Promise.resolve(false)
|
||||||
|
when 2
|
||||||
|
Promise.resolve(true)
|
||||||
|
|
||||||
saveError = (error) =>
|
saveError = (error) =>
|
||||||
if error
|
if error
|
||||||
saveDialog("Save as", @saveItemAs, "'#{item.getTitle?() ? uri}' could not be saved.\nError: #{@getMessageForErrorCode(error.code)}")
|
saveDialog("Save as", @saveItemAs, "'#{item.getTitle?() ? uri}' could not be saved.\nError: #{@getMessageForErrorCode(error.code)}")
|
||||||
else
|
else
|
||||||
true
|
Promise.resolve(true)
|
||||||
|
|
||||||
saveDialog("Save", @saveItem, "'#{item.getTitle?() ? uri}' has changes, do you want to save them?")
|
saveDialog("Save", @saveItem, "'#{item.getTitle?() ? uri}' has changes, do you want to save them?")
|
||||||
|
|
||||||
@@ -663,6 +677,8 @@ class Pane
|
|||||||
#
|
#
|
||||||
# * `nextAction` (optional) {Function} which will be called after the item is
|
# * `nextAction` (optional) {Function} which will be called after the item is
|
||||||
# successfully saved.
|
# successfully saved.
|
||||||
|
#
|
||||||
|
# Returns a {Promise} that resolves when the save is complete
|
||||||
saveActiveItemAs: (nextAction) ->
|
saveActiveItemAs: (nextAction) ->
|
||||||
@saveItemAs(@getActiveItem(), nextAction)
|
@saveItemAs(@getActiveItem(), nextAction)
|
||||||
|
|
||||||
@@ -673,6 +689,8 @@ class Pane
|
|||||||
# after the item is successfully saved, or with the error if it failed.
|
# 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
|
# The return value will be that of `nextAction` or `undefined` if it was not
|
||||||
# provided
|
# provided
|
||||||
|
#
|
||||||
|
# Returns a {Promise} that resolves when the save is complete
|
||||||
saveItem: (item, nextAction) =>
|
saveItem: (item, nextAction) =>
|
||||||
if typeof item?.getURI is 'function'
|
if typeof item?.getURI is 'function'
|
||||||
itemURI = item.getURI()
|
itemURI = item.getURI()
|
||||||
@@ -680,14 +698,16 @@ class Pane
|
|||||||
itemURI = item.getUri()
|
itemURI = item.getUri()
|
||||||
|
|
||||||
if itemURI?
|
if itemURI?
|
||||||
try
|
if item.save?
|
||||||
item.save?()
|
promisify -> item.save()
|
||||||
|
.then -> nextAction?()
|
||||||
|
.catch (error) =>
|
||||||
|
if nextAction
|
||||||
|
nextAction(error)
|
||||||
|
else
|
||||||
|
@handleSaveError(error, item)
|
||||||
|
else
|
||||||
nextAction?()
|
nextAction?()
|
||||||
catch error
|
|
||||||
if nextAction
|
|
||||||
nextAction(error)
|
|
||||||
else
|
|
||||||
@handleSaveError(error, item)
|
|
||||||
else
|
else
|
||||||
@saveItemAs(item, nextAction)
|
@saveItemAs(item, nextAction)
|
||||||
|
|
||||||
@@ -706,14 +726,13 @@ class Pane
|
|||||||
saveOptions.defaultPath ?= item.getPath()
|
saveOptions.defaultPath ?= item.getPath()
|
||||||
newItemPath = @applicationDelegate.showSaveDialog(saveOptions)
|
newItemPath = @applicationDelegate.showSaveDialog(saveOptions)
|
||||||
if newItemPath
|
if newItemPath
|
||||||
try
|
promisify -> item.saveAs(newItemPath)
|
||||||
item.saveAs(newItemPath)
|
.then -> nextAction?()
|
||||||
nextAction?()
|
.catch (error) =>
|
||||||
catch error
|
if nextAction?
|
||||||
if nextAction
|
nextAction(error)
|
||||||
nextAction(error)
|
else
|
||||||
else
|
@handleSaveError(error, item)
|
||||||
@handleSaveError(error, item)
|
|
||||||
|
|
||||||
# Public: Save all items.
|
# Public: Save all items.
|
||||||
saveItems: ->
|
saveItems: ->
|
||||||
@@ -909,13 +928,13 @@ class Pane
|
|||||||
bottommostSibling = @findBottommostSibling()
|
bottommostSibling = @findBottommostSibling()
|
||||||
if bottommostSibling is this then @splitDown() else bottommostSibling
|
if bottommostSibling is this then @splitDown() else bottommostSibling
|
||||||
|
|
||||||
|
# Private: Close the pane unless the user cancels the action via a dialog.
|
||||||
|
#
|
||||||
|
# Returns a {Promise} that resolves once the pane is either closed, or the
|
||||||
|
# closing has been cancelled.
|
||||||
close: ->
|
close: ->
|
||||||
@destroy() if @confirmClose()
|
Promise.all(@getItems().map(@promptToSaveItem.bind(this))).then (results) =>
|
||||||
|
@destroy() unless results.includes(false)
|
||||||
confirmClose: ->
|
|
||||||
for item in @getItems()
|
|
||||||
return false unless @promptToSaveItem(item)
|
|
||||||
true
|
|
||||||
|
|
||||||
handleSaveError: (error, item) ->
|
handleSaveError: (error, item) ->
|
||||||
itemPath = error.path ? item?.getPath?()
|
itemPath = error.path ? item?.getPath?()
|
||||||
@@ -948,3 +967,9 @@ class Pane
|
|||||||
when 'EROFS' then 'Read-only file system'
|
when 'EROFS' then 'Read-only file system'
|
||||||
when 'ESPIPE' then 'Invalid seek'
|
when 'ESPIPE' then 'Invalid seek'
|
||||||
when 'ETIMEDOUT' then 'Connection timed out'
|
when 'ETIMEDOUT' then 'Connection timed out'
|
||||||
|
|
||||||
|
promisify = (callback) ->
|
||||||
|
try
|
||||||
|
Promise.resolve(callback())
|
||||||
|
catch error
|
||||||
|
Promise.reject(error)
|
||||||
@@ -147,19 +147,12 @@ class WindowEventHandler
|
|||||||
@document.body.classList.remove("fullscreen")
|
@document.body.classList.remove("fullscreen")
|
||||||
|
|
||||||
handleWindowBeforeunload: (event) =>
|
handleWindowBeforeunload: (event) =>
|
||||||
projectHasPaths = @atomEnvironment.project.getPaths().length > 0
|
if not @reloadRequested and not @atomEnvironment.inSpecMode() and @atomEnvironment.getCurrentWindow().isWebViewFocused()
|
||||||
confirmed = @atomEnvironment.workspace?.confirmClose(windowCloseRequested: true, projectHasPaths: projectHasPaths)
|
|
||||||
if confirmed and not @reloadRequested and not @atomEnvironment.inSpecMode() and @atomEnvironment.getCurrentWindow().isWebViewFocused()
|
|
||||||
@atomEnvironment.hide()
|
@atomEnvironment.hide()
|
||||||
@reloadRequested = false
|
@reloadRequested = false
|
||||||
|
|
||||||
@atomEnvironment.storeWindowDimensions()
|
@atomEnvironment.storeWindowDimensions()
|
||||||
if confirmed
|
@atomEnvironment.unloadEditorWindow()
|
||||||
@atomEnvironment.unloadEditorWindow()
|
@atomEnvironment.destroy()
|
||||||
@atomEnvironment.destroy()
|
|
||||||
else
|
|
||||||
@applicationDelegate.didCancelWindowUnload()
|
|
||||||
event.returnValue = false
|
|
||||||
|
|
||||||
handleWindowToggleFullScreen: =>
|
handleWindowToggleFullScreen: =>
|
||||||
@atomEnvironment.toggleFullScreen()
|
@atomEnvironment.toggleFullScreen()
|
||||||
|
|||||||
@@ -1299,9 +1299,9 @@ module.exports = class Workspace extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirmClose (options) {
|
confirmClose (options) {
|
||||||
return this.getPaneContainers()
|
return Promise.all(this.getPaneContainers().map(container =>
|
||||||
.map(container => container.confirmClose(options))
|
container.confirmClose(options)
|
||||||
.every(saved => saved)
|
)).then((results) => !results.includes(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the active pane item.
|
// Save the active pane item.
|
||||||
@@ -1311,7 +1311,7 @@ module.exports = class Workspace extends Model {
|
|||||||
// {::saveActivePaneItemAs} # will be called instead. This method does nothing
|
// {::saveActivePaneItemAs} # will be called instead. This method does nothing
|
||||||
// if the active item does not implement a `.save` method.
|
// if the active item does not implement a `.save` method.
|
||||||
saveActivePaneItem () {
|
saveActivePaneItem () {
|
||||||
this.getCenter().getActivePane().saveActiveItem()
|
return this.getCenter().getActivePane().saveActiveItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt the user for a path and save the active pane item to it.
|
// Prompt the user for a path and save the active pane item to it.
|
||||||
|
|||||||
Reference in New Issue
Block a user