diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 4b115e594..8c526ed78 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -274,6 +274,21 @@ describe('Workspace', () => { }) }) + it('discovers existing editors that are still opening', () => { + let editor0 = null + let editor1 = null + + waitsForPromise(() => Promise.all([ + workspace.open('spartacus.txt').then(o0 => { editor0 = o0 }), + workspace.open('spartacus.txt').then(o1 => { editor1 = o1 }), + ])) + + runs(() => { + expect(editor0).toEqual(editor1) + expect(workspace.getActivePane().items).toEqual([editor0]) + }) + }) + it("uses the location specified by the model's `getDefaultLocation()` method", () => { const item = { getDefaultLocation: jasmine.createSpy().andReturn('right'), @@ -361,6 +376,28 @@ describe('Workspace', () => { }) }) + it('discovers existing editors that are still opening in an inactive pane', () => { + let editor0 = null + let editor1 = null + const pane0 = workspace.getActivePane() + const pane1 = workspace.getActivePane().splitRight() + + pane0.activate() + const promise0 = workspace.open('spartacus.txt', {searchAllPanes: true}).then(o0 => { editor0 = o0 }) + pane1.activate() + const promise1 = workspace.open('spartacus.txt', {searchAllPanes: true}).then(o1 => { editor1 = o1 }) + + waitsForPromise(() => Promise.all([promise0, promise1])) + + runs(() => { + expect(editor0).toBeDefined() + expect(editor1).toBeDefined() + + expect(editor0).toEqual(editor1) + expect(workspace.getActivePane().items).toEqual([editor0]) + }) + }) + it('activates the pane in the dock with the matching item', () => { const dock = atom.workspace.getRightDock() const ITEM_URI = 'atom://test' diff --git a/src/workspace.js b/src/workspace.js index de51651ec..3f858ddac 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -225,6 +225,8 @@ module.exports = class Workspace extends Model { modal: new PanelContainer({viewRegistry: this.viewRegistry, location: 'modal'}) } + this.incoming = new Map() + this.subscribeToEvents() } @@ -921,133 +923,150 @@ module.exports = class Workspace extends Model { if (typeof item.getURI === 'function') uri = item.getURI() } - if (!atom.config.get('core.allowPendingPaneItems')) { - options.pending = false - } - - // Avoid adding URLs as recent documents to work-around this Spotlight crash: - // https://github.com/atom/atom/issues/10071 - if (uri && (!url.parse(uri).protocol || process.platform === 'win32')) { - this.applicationDelegate.addRecentDocument(uri) - } - - let pane, itemExistsInWorkspace - - // Try to find an existing item in the workspace. - if (item || uri) { - if (options.pane) { - pane = options.pane - } else if (options.searchAllPanes) { - pane = item ? this.paneForItem(item) : this.paneForURI(uri) + let resolveItem = () => {} + if (uri) { + const incomingItem = this.incoming.get(uri) + if (!incomingItem) { + this.incoming.set(uri, new Promise(resolve => { resolveItem = resolve })) } else { - // If an item with the given URI is already in the workspace, assume - // that item's pane container is the preferred location for that URI. - let container - if (uri) container = this.paneContainerForURI(uri) - if (!container) container = this.getActivePaneContainer() + await incomingItem + } + } - // The `split` option affects where we search for the item. - pane = container.getActivePane() - switch (options.split) { - case 'left': - pane = pane.findLeftmostSibling() - break - case 'right': - pane = pane.findRightmostSibling() - break - case 'up': - pane = pane.findTopmostSibling() - break - case 'down': - pane = pane.findBottommostSibling() - break - } + try { + if (!atom.config.get('core.allowPendingPaneItems')) { + options.pending = false } - if (pane) { - if (item) { - itemExistsInWorkspace = pane.getItems().includes(item) + // Avoid adding URLs as recent documents to work-around this Spotlight crash: + // https://github.com/atom/atom/issues/10071 + if (uri && (!url.parse(uri).protocol || process.platform === 'win32')) { + this.applicationDelegate.addRecentDocument(uri) + } + + let pane, itemExistsInWorkspace + + // Try to find an existing item in the workspace. + if (item || uri) { + if (options.pane) { + pane = options.pane + } else if (options.searchAllPanes) { + pane = item ? this.paneForItem(item) : this.paneForURI(uri) } else { - item = pane.itemForURI(uri) - itemExistsInWorkspace = item != null + // If an item with the given URI is already in the workspace, assume + // that item's pane container is the preferred location for that URI. + let container + if (uri) container = this.paneContainerForURI(uri) + if (!container) container = this.getActivePaneContainer() + + // The `split` option affects where we search for the item. + pane = container.getActivePane() + switch (options.split) { + case 'left': + pane = pane.findLeftmostSibling() + break + case 'right': + pane = pane.findRightmostSibling() + break + case 'up': + pane = pane.findTopmostSibling() + break + case 'down': + pane = pane.findBottommostSibling() + break + } + } + + if (pane) { + if (item) { + itemExistsInWorkspace = pane.getItems().includes(item) + } else { + item = pane.itemForURI(uri) + itemExistsInWorkspace = item != null + } } } - } - // If we already have an item at this stage, we won't need to do an async - // lookup of the URI, so we yield the event loop to ensure this method - // is consistently asynchronous. - if (item) await Promise.resolve() + // If we already have an item at this stage, we won't need to do an async + // lookup of the URI, so we yield the event loop to ensure this method + // is consistently asynchronous. + if (item) await Promise.resolve() - if (!itemExistsInWorkspace) { - item = item || await this.createItemForURI(uri, options) - if (!item) return + if (!itemExistsInWorkspace) { + item = item || await this.createItemForURI(uri, options) + if (!item) return - if (options.pane) { - pane = options.pane + if (options.pane) { + pane = options.pane + } else { + let location = options.location + if (!location && !options.split && uri && this.enablePersistence) { + location = await this.itemLocationStore.load(uri) + } + if (!location && typeof item.getDefaultLocation === 'function') { + location = item.getDefaultLocation() + } + + const allowedLocations = typeof item.getAllowedLocations === 'function' ? item.getAllowedLocations() : ALL_LOCATIONS + location = allowedLocations.includes(location) ? location : allowedLocations[0] + + const container = this.paneContainers[location] || this.getCenter() + pane = container.getActivePane() + switch (options.split) { + case 'left': + pane = pane.findLeftmostSibling() + break + case 'right': + pane = pane.findOrCreateRightmostSibling() + break + case 'up': + pane = pane.findTopmostSibling() + break + case 'down': + pane = pane.findOrCreateBottommostSibling() + break + } + } + } + + if (!options.pending && (pane.getPendingItem() === item)) { + pane.clearPendingItem() + } + + this.itemOpened(item) + + if (options.activateItem === false) { + pane.addItem(item, {pending: options.pending}) } else { - let location = options.location - if (!location && !options.split && uri && this.enablePersistence) { - location = await this.itemLocationStore.load(uri) - } - if (!location && typeof item.getDefaultLocation === 'function') { - location = item.getDefaultLocation() - } + pane.activateItem(item, {pending: options.pending}) + } - const allowedLocations = typeof item.getAllowedLocations === 'function' ? item.getAllowedLocations() : ALL_LOCATIONS - location = allowedLocations.includes(location) ? location : allowedLocations[0] + if (options.activatePane !== false) { + pane.activate() + } - const container = this.paneContainers[location] || this.getCenter() - pane = container.getActivePane() - switch (options.split) { - case 'left': - pane = pane.findLeftmostSibling() - break - case 'right': - pane = pane.findOrCreateRightmostSibling() - break - case 'up': - pane = pane.findTopmostSibling() - break - case 'down': - pane = pane.findOrCreateBottommostSibling() - break + let initialColumn = 0 + let initialLine = 0 + if (!Number.isNaN(options.initialLine)) { + initialLine = options.initialLine + } + if (!Number.isNaN(options.initialColumn)) { + initialColumn = options.initialColumn + } + if (initialLine >= 0 || initialColumn >= 0) { + if (typeof item.setCursorBufferPosition === 'function') { + item.setCursorBufferPosition([initialLine, initialColumn]) } } - } - if (!options.pending && (pane.getPendingItem() === item)) { - pane.clearPendingItem() - } - - this.itemOpened(item) - - if (options.activateItem === false) { - pane.addItem(item, {pending: options.pending}) - } else { - pane.activateItem(item, {pending: options.pending}) - } - - if (options.activatePane !== false) { - pane.activate() - } - - let initialColumn = 0 - let initialLine = 0 - if (!Number.isNaN(options.initialLine)) { - initialLine = options.initialLine - } - if (!Number.isNaN(options.initialColumn)) { - initialColumn = options.initialColumn - } - if (initialLine >= 0 || initialColumn >= 0) { - if (typeof item.setCursorBufferPosition === 'function') { - item.setCursorBufferPosition([initialLine, initialColumn]) + const index = pane.getActiveItemIndex() + this.emitter.emit('did-open', {uri, pane, item, index}) + if (uri) { + this.incoming.delete(uri) } + } finally { + resolveItem() } - - const index = pane.getActiveItemIndex() - this.emitter.emit('did-open', {uri, pane, item, index}) return item }