diff --git a/packages/tree-view/lib/tree-view.js b/packages/tree-view/lib/tree-view.js index c9d128f1b4..b93825a36e 100644 --- a/packages/tree-view/lib/tree-view.js +++ b/packages/tree-view/lib/tree-view.js @@ -929,7 +929,7 @@ class TreeView { this.emitter.emit('will-delete-entry', meta); - let promise = shell.trashItem(selectedPath).then(() => { + let promise = atom.trashItem(selectedPath).then(() => { this.emitter.emit('entry-deleted', meta); }).catch(() => { this.emitter.emit('delete-entry-failed', meta); diff --git a/src/application-delegate.js b/src/application-delegate.js index 0c68c688f8..bf90d552f6 100644 --- a/src/application-delegate.js +++ b/src/application-delegate.js @@ -34,7 +34,7 @@ module.exports = class ApplicationDelegate { pickFolder(callback) { const responseChannel = 'atom-pick-folder-response'; - ipcRenderer.on(responseChannel, function(event, path) { + ipcRenderer.on(responseChannel, function (event, path) { ipcRenderer.removeAllListeners(responseChannel); return callback(path); }); @@ -132,6 +132,20 @@ module.exports = class ApplicationDelegate { return ipcHelpers.on(ipcRenderer, 'did-leave-full-screen', callback); } + trashItem(filePath) { + // A simple wrapper around `shell.trashItem`, which currently can only be + // called from the main process on Windows. + return ipcRenderer.invoke('trashItem', filePath).then(({ outcome, error, result }) => { + if (outcome === 'success') { + // `result` is undefined, but we might as well guard against an + // Electron API change in the future. + return result; + } else if (outcome === 'failure') { + return Promise.reject(error); + } + }); + } + async openWindowDevTools() { // Defer DevTools interaction to the next tick, because using them during // event handling causes some wrong input events to be triggered on diff --git a/src/atom-environment.js b/src/atom-environment.js index a507739133..a8829868fe 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -776,6 +776,16 @@ class AtomEnvironment { return this.setFullScreen(!this.isFullScreen()); } + // A proxy to `shell.trashItem` — which ought to work in the renderer, but + // suffers from a bug on Windows. We work around it by delegating to the main + // process. + // + // Undocumented for now, but may eventually be an official part of the API + // for when community packages need to delete files. + trashItem(filePath) { + return this.applicationDelegate.trashItem(filePath); + } + // Restore the window to its previous dimensions and show it. // // Restores the full screen and maximized state after the window has resized to diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index fd6ae5006b..0946ac8a41 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -135,6 +135,30 @@ ipcMain.handle('isDefaultProtocolClient', (_, { protocol, path, args }) => { ipcMain.handle('setAsDefaultProtocolClient', (_, { protocol, path, args }) => { return app.setAsDefaultProtocolClient(protocol, path, args); }); + +// Handle file deletion requests. +// +// Works around https://github.com/electron/electron/issues/29598, which seems +// to be the cause of failed deletion attempts on Windows. +ipcMain.handle('trashItem', async (_, filePath) => { + // We can't toss a promise over the wall, so we'll `await` it on our side and + // report the progress back to the renderer. + // + // If we return an `Error` object from this handler in the case of error, + // `ipcRenderer.invoke` will detect it and wrap it with its own explanation. + // We want to preserve the original error and hide the implementation + // details, so we instead return an object with an explicit `outcome` + // property to avoid this behavior. + try { + // `shell.trashItem` resolves with an empty value on success… + let result = await shell.trashItem(filePath); + return { outcome: 'success', result }; + } catch (error) { + // …and rejects on failure. + return { outcome: 'failure', error } + } +}); + // The application's singleton class. // // It's the entry point into the Pulsar application and maintains the global state