Merge pull request #14695 from atom/jr-editors-live-in-workspace-center

Provide API for observing the active text editor
This commit is contained in:
Jason Rudolph 2017-06-06 10:09:52 -04:00 committed by GitHub
commit 268f94b89d
5 changed files with 221 additions and 112 deletions

View File

@ -1,5 +1,7 @@
/** @babel */
const Grim = require('grim')
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
describe('Dock', () => {
@ -329,4 +331,13 @@ describe('Dock', () => {
expect(() => atom.workspace.getElement().handleDragStart(dragEvent)).not.toThrow()
})
})
describe('::getActiveTextEditor()', () => {
it('is deprecated', () => {
spyOn(Grim, 'deprecate')
atom.workspace.getLeftDock().getActiveTextEditor()
expect(Grim.deprecate.callCount).toBe(1)
})
})
})

View File

@ -31,35 +31,35 @@ describe('Workspace', () => {
afterEach(() => temp.cleanupSync())
describe('serialization', () => {
const simulateReload = () => {
const workspaceState = atom.workspace.serialize()
const projectState = atom.project.serialize({isUnloading: true})
atom.workspace.destroy()
atom.project.destroy()
atom.project = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm.bind(atom),
applicationDelegate: atom.applicationDelegate
})
atom.project.deserialize(projectState)
atom.workspace = new Workspace({
config: atom.config,
project: atom.project,
packageManager: atom.packages,
grammarRegistry: atom.grammars,
styleManager: atom.styles,
deserializerManager: atom.deserializers,
notificationManager: atom.notifications,
applicationDelegate: atom.applicationDelegate,
viewRegistry: atom.views,
assert: atom.assert.bind(atom),
textEditorRegistry: atom.textEditors
})
return atom.workspace.deserialize(workspaceState, atom.deserializers)
}
const simulateReload = () => {
const workspaceState = workspace.serialize()
const projectState = atom.project.serialize({isUnloading: true})
workspace.destroy()
atom.project.destroy()
atom.project = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm.bind(atom),
applicationDelegate: atom.applicationDelegate
})
atom.project.deserialize(projectState)
workspace = atom.workspace = new Workspace({
config: atom.config,
project: atom.project,
packageManager: atom.packages,
grammarRegistry: atom.grammars,
styleManager: atom.styles,
deserializerManager: atom.deserializers,
notificationManager: atom.notifications,
applicationDelegate: atom.applicationDelegate,
viewRegistry: atom.views,
assert: atom.assert.bind(atom),
textEditorRegistry: atom.textEditors
})
return workspace.deserialize(workspaceState, atom.deserializers)
}
describe('serialization', () => {
describe('when the workspace contains text editors', () => {
it('constructs the view with the same panes', () => {
const pane1 = atom.workspace.getActivePane()
@ -120,62 +120,6 @@ describe('Workspace', () => {
expect(atom.workspace.getTextEditors().length).toBe(0)
})
})
describe('where a dock contains an editor', () => {
afterEach(() => {
atom.workspace.getRightDock().paneContainer.destroy()
})
it('constructs the view with the same panes', () => {
const pane1 = atom.workspace.getRightDock().getActivePane()
const pane2 = pane1.splitRight({copyActiveItem: true})
const pane3 = pane2.splitRight({copyActiveItem: true})
let pane4 = null
waitsForPromise(() =>
atom.workspace.open(null, {location: 'right'}).then(editor => editor.setText('An untitled editor.'))
)
waitsForPromise(() =>
atom.workspace.open('b', {location: 'right'}).then(editor => pane2.activateItem(editor.copy()))
)
waitsForPromise(() =>
atom.workspace.open('../sample.js', {location: 'right'}).then(editor => pane3.activateItem(editor))
)
runs(() => {
pane3.activeItem.setCursorScreenPosition([2, 4])
pane4 = pane2.splitDown()
})
waitsForPromise(() =>
atom.workspace.open('../sample.txt', {location: 'right'}).then(editor => pane4.activateItem(editor))
)
runs(() => {
pane4.getActiveItem().setCursorScreenPosition([0, 2])
pane2.activate()
simulateReload()
expect(atom.workspace.getTextEditors().length).toBe(5)
const [editor1, editor2, untitledEditor, editor3, editor4] = atom.workspace.getTextEditors()
const firstDirectory = atom.project.getDirectories()[0]
expect(firstDirectory).toBeDefined()
expect(editor1.getPath()).toBe(firstDirectory.resolve('b'))
expect(editor2.getPath()).toBe(firstDirectory.resolve('../sample.txt'))
expect(editor2.getCursorScreenPosition()).toEqual([0, 2])
expect(editor3.getPath()).toBe(firstDirectory.resolve('b'))
expect(editor4.getPath()).toBe(firstDirectory.resolve('../sample.js'))
expect(editor4.getCursorScreenPosition()).toEqual([2, 4])
expect(untitledEditor.getPath()).toBeUndefined()
expect(untitledEditor.getText()).toBe('An untitled editor.')
expect(atom.workspace.getRightDock().getActiveTextEditor().getPath()).toBe(editor3.getPath())
})
})
})
})
describe('::open(itemOrURI, options)', () => {
@ -429,6 +373,13 @@ describe('Workspace', () => {
})
})
describe('when attempting to open an editor in a dock', () => {
it('opens the editor in the workspace center', async () => {
await atom.workspace.open('sample.txt', {location: 'right'})
expect(atom.workspace.getCenter().getActivePaneItem().getFileName()).toEqual('sample.txt')
})
})
describe('when called with an item rather than a URI', () => {
it('adds the item itself to the workspace', async () => {
const item = document.createElement('div')
@ -1419,6 +1370,42 @@ describe('Workspace', () => {
})
})
describe('::getActiveTextEditor()', () => {
describe("when the workspace center's active pane item is a text editor", () => {
describe('when the workspace center has focus', function () {
it('returns the text editor', () => {
const workspaceCenter = workspace.getCenter()
const editor = new TextEditor()
workspaceCenter.getActivePane().activateItem(editor)
workspaceCenter.activate()
expect(workspace.getActiveTextEditor()).toBe(editor)
})
})
describe('when a dock has focus', function () {
it('returns the text editor', () => {
const workspaceCenter = workspace.getCenter()
const editor = new TextEditor()
workspaceCenter.getActivePane().activateItem(editor)
workspace.getLeftDock().activate()
expect(workspace.getActiveTextEditor()).toBe(editor)
})
})
})
describe("when the workspace center's active pane item is not a text editor", () => {
it('returns undefined', () => {
const workspaceCenter = workspace.getCenter()
const nonEditorItem = document.createElement('div')
workspaceCenter.getActivePane().activateItem(nonEditorItem)
expect(workspace.getActiveTextEditor()).toBeUndefined()
})
})
})
describe('::observeTextEditors()', () => {
it('invokes the observer with current and future text editors', () => {
const observed = []
@ -1435,6 +1422,92 @@ describe('Workspace', () => {
})
})
describe('::observeActiveTextEditor()', () => {
it('invokes the observer with current active text editor and each time a different text editor becomes active', () => {
const pane = workspace.getCenter().getActivePane()
observed = []
const inactiveEditorBeforeRegisteringObserver = new TextEditor()
const activeEditorBeforeRegisteringObserver = new TextEditor()
pane.activateItem(inactiveEditorBeforeRegisteringObserver)
pane.activateItem(activeEditorBeforeRegisteringObserver)
workspace.observeActiveTextEditor(editor => observed.push(editor))
const editorAddedAfterRegisteringObserver = new TextEditor()
const nonEditorItemAddedAfterRegisteringObserver = document.createElement('div')
pane.activateItem(editorAddedAfterRegisteringObserver)
expect(observed).toEqual(
[activeEditorBeforeRegisteringObserver, editorAddedAfterRegisteringObserver]
)
})
})
describe('::onDidChangeActiveTextEditor()', () => {
let center, pane, observed
beforeEach(() => {
center = workspace.getCenter()
pane = center.getActivePane()
observed = []
})
it("invokes the observer when a text editor becomes the workspace center's active pane item while a dock has focus", () => {
workspace.onDidChangeActiveTextEditor(editor => observed.push(editor))
const dock = workspace.getLeftDock()
dock.activate()
expect(atom.workspace.getActivePaneContainer()).toBe(dock)
const editor = new TextEditor()
center.getActivePane().activateItem(editor)
expect(atom.workspace.getActivePaneContainer()).toBe(dock)
expect(observed).toEqual([editor])
})
it('invokes the observer when the last text editor is closed', () => {
const editor = new TextEditor()
pane.activateItem(editor)
workspace.onDidChangeActiveTextEditor(editor => observed.push(editor))
pane.destroyItem(editor)
expect(observed).toEqual([undefined])
})
it("invokes the observer when the workspace center's active pane item changes from an editor item to a non-editor item", () => {
const editor = new TextEditor()
const nonEditorItem = document.createElement('div')
pane.activateItem(editor)
workspace.onDidChangeActiveTextEditor(editor => observed.push(editor))
pane.activateItem(nonEditorItem)
expect(observed).toEqual([undefined])
})
it("does not invoke the observer when the workspace center's active pane item changes from a non-editor item to another non-editor item", () => {
workspace.onDidChangeActiveTextEditor(editor => observed.push(editor))
const nonEditorItem1 = document.createElement('div')
const nonEditorItem2 = document.createElement('div')
pane.activateItem(nonEditorItem1)
pane.activateItem(nonEditorItem1)
expect(observed).toEqual([])
})
it('invokes the observer when closing the one and only text editor after deserialization', async () => {
pane.activateItem(new TextEditor())
simulateReload()
workspace.onDidChangeActiveTextEditor(editor => observed.push(editor))
workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(observed).toEqual([undefined])
})
})
describe('when an editor is destroyed', () => {
it('removes the editor', () => {
let editor = null

View File

@ -4,6 +4,7 @@ const _ = require('underscore-plus')
const {CompositeDisposable} = require('event-kit')
const PaneContainer = require('./pane-container')
const TextEditor = require('./text-editor')
const Grim = require('grim')
const MINIMUM_SIZE = 100
const DEFAULT_INITIAL_SIZE = 300
@ -384,21 +385,6 @@ module.exports = class Dock {
Section: Event Subscription
*/
// Essential: Invoke the given callback with all current and future text
// editors in the dock.
//
// * `callback` {Function} to be called with current and future text editors.
// * `editor` An {TextEditor} that is present in {::getTextEditors} at the time
// of subscription or that is added at some later time.
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
observeTextEditors (callback) {
for (const textEditor of this.getTextEditors()) {
callback(textEditor)
}
return this.onDidAddTextEditor(({textEditor}) => callback(textEditor))
}
// Essential: Invoke the given callback with all current and future panes items
// in the dock.
//
@ -583,18 +569,13 @@ module.exports = class Dock {
return this.paneContainer.getActivePaneItem()
}
// Essential: Get all text editors in the dock.
// Deprecated: Get the active item if it is a {TextEditor}.
//
// Returns an {Array} of {TextEditor}s.
getTextEditors () {
return this.paneContainer.getTextEditors()
}
// Essential: Get the active item if it is an {TextEditor}.
//
// Returns an {TextEditor} or `undefined` if the current active item is not an
// Returns a {TextEditor} or `undefined` if the current active item is not a
// {TextEditor}.
getActiveTextEditor () {
Grim.deprecate('Text editors are not allowed in docks. Use atom.workspace.getActiveTextEditor() instead.')
const activeItem = this.getActivePaneItem()
if (activeItem instanceof TextEditor) { return activeItem }
}

View File

@ -3628,6 +3628,9 @@ class TextEditor extends Model
})
@component.element
getAllowedLocations: ->
['center']
# Essential: Retrieves the greyed out placeholder of a mini editor.
#
# Returns a {String}.

View File

@ -216,6 +216,7 @@ module.exports = class Workspace extends Model {
bottom: this.createDock('bottom')
}
this.activePaneContainer = this.paneContainers.center
this.hasActiveTextEditor = false
this.panelContainers = {
top: new PanelContainer({viewRegistry: this.viewRegistry, location: 'top'}),
@ -296,6 +297,7 @@ module.exports = class Workspace extends Model {
bottom: this.createDock('bottom')
}
this.activePaneContainer = this.paneContainers.center
this.hasActiveTextEditor = false
this.panelContainers = {
top: new PanelContainer({viewRegistry: this.viewRegistry, location: 'top'}),
@ -371,6 +373,8 @@ module.exports = class Workspace extends Model {
this.paneContainers.center.deserialize(state.paneContainer, deserializerManager)
}
this.hasActiveTextEditor = this.getActiveTextEditor() != null
this.updateWindowTitle()
}
@ -422,6 +426,16 @@ module.exports = class Workspace extends Model {
this.didChangeActivePaneItem(item)
this.emitter.emit('did-change-active-pane-item', item)
}
if (paneContainer === this.getCenter()) {
const hadActiveTextEditor = this.hasActiveTextEditor
this.hasActiveTextEditor = item instanceof TextEditor
if (this.hasActiveTextEditor || hadActiveTextEditor) {
const itemValue = this.hasActiveTextEditor ? item : undefined
this.emitter.emit('did-change-active-text-editor', itemValue)
}
}
}
didChangeActivePaneItem (item) {
@ -648,6 +662,18 @@ module.exports = class Workspace extends Model {
return this.emitter.on('did-stop-changing-active-pane-item', callback)
}
// Essential: Invoke the given callback when a text editor becomes the active
// text editor and when there is no longer an active text editor.
//
// * `callback` {Function} to be called when the active text editor changes.
// * `editor` The active {TextEditor} or undefined if there is no longer an
// active text editor.
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeActiveTextEditor (callback) {
return this.emitter.on('did-change-active-text-editor', callback)
}
// Essential: Invoke the given callback with the current active pane item and
// with all future active pane items in the workspace.
//
@ -660,6 +686,21 @@ module.exports = class Workspace extends Model {
return this.onDidChangeActivePaneItem(callback)
}
// Essential: Invoke the given callback with the current active text editor
// (if any), with all future active text editors, and when there is no longer
// an active text editor.
//
// * `callback` {Function} to be called when the active text editor changes.
// * `editor` The active {TextEditor} or undefined if there is not an
// active text editor.
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
observeActiveTextEditor (callback) {
callback(this.getActiveTextEditor())
return this.onDidChangeActiveTextEditor(callback)
}
// Essential: Invoke the given callback whenever an item is opened. Unlike
// {::onDidAddPaneItem}, observers will be notified for items that are already
// present in the workspace when they are reopened.
@ -1282,12 +1323,12 @@ module.exports = class Workspace extends Model {
return this.getPaneItems().filter(item => item instanceof TextEditor)
}
// Essential: Get the active item if it is an {TextEditor}.
// Essential: Get the workspace center's active item if it is a {TextEditor}.
//
// Returns an {TextEditor} or `undefined` if the current active item is not an
// {TextEditor}.
// Returns a {TextEditor} or `undefined` if the workspace center's current
// active item is not a {TextEditor}.
getActiveTextEditor () {
const activeItem = this.getActivePaneItem()
const activeItem = this.getCenter().getActivePaneItem()
if (activeItem instanceof TextEditor) { return activeItem }
}