Merge remote-tracking branch 'origin/master' into chrome31

Conflicts:
	package.json
This commit is contained in:
Kevin Sawicki
2014-01-13 15:01:52 -08:00
27 changed files with 1472 additions and 862 deletions

View File

@@ -23,7 +23,7 @@
"clear-cut": "0.2.0",
"coffee-script": "1.6.3",
"coffeestack": "0.6.0",
"emissary": "0.19.0",
"emissary": "0.31.0",
"first-mate": "0.17.0",
"fs-plus": "0.14.0",
"fstream": "0.1.24",
@@ -36,7 +36,7 @@
"mkdirp": "0.3.5",
"keytar": "0.15.0",
"less-cache": "0.10.0",
"serializable": "0.1.0",
"serializable": "0.3.0",
"nslog": "0.3.0",
"oniguruma": "0.26.0",
"optimist": "0.4.0",
@@ -46,22 +46,24 @@
"scandal": "0.11.0",
"season": "0.14.0",
"semver": "1.1.4",
"space-pen": "3.0.3",
"space-pen": "3.1.0",
"temp": "0.5.0",
"text-buffer": "0.12.0",
"theorist": "~0.7.0",
"underscore-plus": "0.6.1",
"vm-compatibility-layer": "0.1.0"
"vm-compatibility-layer": "0.1.0",
"theorist": "~0.13.0",
"delegato": "~0.4.0",
"mixto": "~0.4.0"
},
"packageDependencies": {
"atom-dark-syntax": "0.10.0",
"atom-dark-ui": "0.18.0",
"atom-dark-ui": "0.19.0",
"atom-light-syntax": "0.10.0",
"atom-light-ui": "0.17.0",
"atom-light-ui": "0.18.0",
"base16-tomorrow-dark-theme": "0.8.0",
"solarized-dark-syntax": "0.6.0",
"solarized-light-syntax": "0.2.0",
"archive-view": "0.18.0",
"archive-view": "0.19.0",
"autocomplete": "0.19.0",
"autoflow": "0.11.0",
"autosave": "0.10.0",
@@ -74,16 +76,16 @@
"editor-stats": "0.12.0",
"exception-reporting": "0.11.0",
"feedback": "0.22.0",
"find-and-replace": "0.73.0",
"find-and-replace": "0.74.0",
"fuzzy-finder": "0.30.0",
"gists": "0.14.0",
"git-diff": "0.21.0",
"github-sign-in": "0.16.0",
"go-to-line": "0.14.0",
"grammar-selector": "0.16.0",
"image-view": "0.14.0",
"image-view": "0.15.0",
"keybinding-resolver": "0.8.0",
"markdown-preview": "0.23.0",
"markdown-preview": "0.24.0",
"metrics": "0.21.0",
"package-generator": "0.23.0",
"release-notes": "0.15.0",
@@ -93,15 +95,15 @@
"status-bar": "0.31.0",
"styleguide": "0.19.0",
"symbols-view": "0.28.0",
"tabs": "0.16.0",
"tabs": "0.17.0",
"terminal": "0.24.0",
"timecop": "0.12.0",
"timecop": "0.13.0",
"to-the-hubs": "0.17.0",
"tree-view": "0.59.0",
"visual-bell": "0.6.0",
"welcome": "0.4.0",
"whitespace": "0.10.0",
"wrap-guide": "0.10.0",
"wrap-guide": "0.11.0",
"language-c": "0.2.0",
"language-clojure": "0.1.0",
"language-coffee-script": "0.4.0",

View File

@@ -17,6 +17,8 @@ var commands = [
killatom,
[__dirname, '..', 'node_modules'],
[__dirname, '..', 'build', 'node_modules'],
[__dirname, '..', 'apm', 'node_modules'],
[__dirname, '..', 'vendor', 'apm', 'node_modules'],
[__dirname, '..', 'atom-shell'],
[home, '.atom', '.node-gyp'],
[home, '.atom', 'storage'],

View File

@@ -2756,7 +2756,7 @@ describe "EditorView", ->
editorView = atom.workspaceView.getActiveView()
view = $$ -> @div id: 'view', tabindex: -1, 'View'
editorView.getPane().showItem(view)
editorView.getPane().activateItem(view)
expect(editorView.isVisible()).toBeFalsy()
editorView.setText('hidden changes')
@@ -2764,7 +2764,7 @@ describe "EditorView", ->
displayUpdatedHandler = jasmine.createSpy("displayUpdatedHandler")
editorView.on 'editor:display-updated', displayUpdatedHandler
editorView.getPane().showItem(editorView.getModel())
editorView.getPane().activateItem(editorView.getModel())
expect(editorView.isVisible()).toBeTruthy()
waitsFor ->
@@ -2809,7 +2809,7 @@ describe "EditorView", ->
atom.workspaceView.attachToDom()
editorView = atom.workspaceView.getActiveView()
willBeRemovedHandler = jasmine.createSpy('fileChange')
willBeRemovedHandler = jasmine.createSpy('willBeRemovedHandler')
editorView.on 'editor:will-be-removed', willBeRemovedHandler
editorView.getPane().destroyActiveItem()
expect(willBeRemovedHandler).toHaveBeenCalled()

View File

@@ -0,0 +1,77 @@
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
describe "PaneContainer", ->
describe "serialization", ->
[containerA, pane1A, pane2A, pane3A] = []
beforeEach ->
# This is a dummy item to prevent panes from being empty on deserialization
class Item
atom.deserializers.add(this)
@deserialize: -> new this
serialize: -> deserializer: 'Item'
pane1A = new Pane(items: [new Item])
containerA = new PaneContainer(root: pane1A)
pane2A = pane1A.splitRight(items: [new Item])
pane3A = pane2A.splitDown(items: [new Item])
it "preserves the focused pane across serialization", ->
expect(pane3A.focused).toBe true
containerB = containerA.testSerialization()
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(pane3B.focused).toBe true
it "preserves the active pane across serialization, independent of focus", ->
pane3A.activate()
expect(containerA.activePane).toBe pane3A
containerB = containerA.testSerialization()
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(containerB.activePane).toBe pane3B
describe "::activePane", ->
[container, pane1, pane2] = []
beforeEach ->
pane1 = new Pane
container = new PaneContainer(root: pane1)
it "references the first pane if no pane has been made active", ->
expect(container.activePane).toBe pane1
expect(pane1.active).toBe true
it "references the most pane on which ::activate was most recently called", ->
pane2 = pane1.splitRight()
pane2.activate()
expect(container.activePane).toBe pane2
expect(pane1.active).toBe false
expect(pane2.active).toBe true
pane1.activate()
expect(container.activePane).toBe pane1
expect(pane1.active).toBe true
expect(pane2.active).toBe false
it "is reassigned to the next pane if the current active pane is destroyed", ->
pane2 = pane1.splitRight()
pane2.activate()
pane2.destroy()
expect(container.activePane).toBe pane1
expect(pane1.active).toBe true
pane1.destroy()
expect(container.activePane).toBe null
describe "when the last pane is removed", ->
[container, pane, surrenderedFocusHandler] = []
beforeEach ->
pane = new Pane
container = new PaneContainer(root: pane)
container.on 'surrendered-focus', surrenderedFocusHandler = jasmine.createSpy("surrenderedFocusHandler")
it "assigns null to the root and the activePane", ->
pane.destroy()
expect(container.root).toBe null
expect(container.activePane).toBe null

View File

@@ -1,10 +1,10 @@
path = require 'path'
temp = require 'temp'
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
PaneContainerView = require '../src/pane-container-view'
PaneView = require '../src/pane-view'
{_, $, View, $$} = require 'atom'
describe "PaneContainer", ->
describe "PaneContainerView", ->
[TestView, container, pane1, pane2, pane3] = []
beforeEach ->
@@ -16,10 +16,10 @@ describe "PaneContainer", ->
serialize: -> { deserializer: 'TestView', @name }
getUri: -> path.join(temp.dir, @name)
save: -> @saved = true
isEqual: (other) -> @name is other.name
isEqual: (other) -> @name is other?.name
container = new PaneContainer
pane1 = new Pane(new TestView('1'))
container = new PaneContainerView
pane1 = new PaneView(new TestView('1'))
container.setRoot(pane1)
pane2 = pane1.splitRight(new TestView('2'))
pane3 = pane2.splitDown(new TestView('3'))
@@ -42,6 +42,8 @@ describe "PaneContainer", ->
describe ".focusPreviousPane()", ->
it "focuses the pane preceding the focused pane or the last pane if no pane has focus", ->
container.attachToDom()
$(document.body).focus() # clear focus
container.focusPreviousPane()
expect(pane3.activeItem).toMatchSelector ':focus'
container.focusPreviousPane()
@@ -69,10 +71,6 @@ describe "PaneContainer", ->
expect(container.getFocusedPane()).toBe pane3
expect(container.getActivePane()).toBe pane3
# returns the first pane if none have been set to active
container.find('.pane.active').removeClass('active')
expect(container.getActivePane()).toBe pane1
describe ".eachPane(callback)", ->
it "runs the callback with all current and future panes until the subscription is cancelled", ->
panes = []
@@ -90,7 +88,7 @@ describe "PaneContainer", ->
describe ".saveAll()", ->
it "saves all open pane items", ->
pane1.showItem(new TestView('4'))
pane1.activateItem(new TestView('4'))
container.saveAll()
@@ -124,19 +122,19 @@ describe "PaneContainer", ->
describe "serialization", ->
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
newContainer = atom.deserializers.deserialize(container.serialize())
expect(newContainer.find('.row > :contains(1)')).toExist()
expect(newContainer.find('.row > .column > :contains(2)')).toExist()
expect(newContainer.find('.row > .column > :contains(3)')).toExist()
expect(newContainer.find('.pane-row > :contains(1)')).toExist()
expect(newContainer.find('.pane-row > .pane-column > :contains(2)')).toExist()
expect(newContainer.find('.pane-row > .pane-column > :contains(3)')).toExist()
newContainer.height(200).width(300).attachToDom()
expect(newContainer.find('.row > :contains(1)').width()).toBe 150
expect(newContainer.find('.row > .column > :contains(2)').height()).toBe 100
expect(newContainer.find('.pane-row > :contains(1)').width()).toBe 150
expect(newContainer.find('.pane-row > .pane-column > :contains(2)').height()).toBe 100
xit "removes empty panes on deserialization", ->
it "removes empty panes on deserialization", ->
# only deserialize pane 1's view successfully
TestView.deserialize = ({name}) -> new TestView(name) if name is '1'
newContainer = atom.deserializers.deserialize(container.serialize())
expect(newContainer.find('.row, .column')).not.toExist()
expect(newContainer.find('.pane-row, .pane-column')).not.toExist()
expect(newContainer.find('> :contains(1)')).toExist()
describe "pane-container:active-pane-item-changed", ->
@@ -148,9 +146,9 @@ describe "PaneContainer", ->
item2b = new TestView('2b')
item3a = new TestView('3a')
container = new PaneContainer
container = new PaneContainerView
container.attachToDom()
pane1 = new Pane(item1a)
pane1 = new PaneView(item1a)
container.setRoot(pane1)
activeItemChangedHandler = jasmine.createSpy("activeItemChangedHandler")
@@ -162,50 +160,50 @@ describe "PaneContainer", ->
expect(container.getPanes().length).toBe 0
activeItemChangedHandler.reset()
pane = new Pane(item1a)
pane = new PaneView(item1a)
container.setRoot(pane)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
describe "when there is one pane", ->
it "is triggered when a new pane item is added", ->
pane1.showItem(item1b)
pane1.activateItem(item1b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1b
it "is not triggered when the active pane item is shown again", ->
pane1.showItem(item1a)
pane1.activateItem(item1a)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when switching to an existing pane item", ->
pane1.showItem(item1b)
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.showItem(item1a)
pane1.activateItem(item1a)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
it "is triggered when the active pane item is removed", ->
pane1.showItem(item1b)
it "is triggered when the active pane item is destroyed", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.removeItem(item1b)
pane1.destroyItem(item1b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
it "is not triggered when an inactive pane item is removed", ->
pane1.showItem(item1b)
it "is not triggered when an inactive pane item is destroyed", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.removeItem(item1a)
pane1.destroyItem(item1a)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when all pane items are removed", ->
pane1.removeItem(item1a)
it "is triggered when all pane items are destroyed", ->
pane1.destroyItem(item1a)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
it "is triggered when the pane is removed", ->
it "is triggered when the pane is destroyed", ->
pane1.remove()
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
@@ -218,40 +216,40 @@ describe "PaneContainer", ->
activeItemChangedHandler.reset()
it "is triggered when a new pane item is added to the active pane", ->
pane2.showItem(item2b)
pane2.activateItem(item2b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2b
it "is not triggered when a new pane item is added to an inactive pane", ->
pane1.showItem(item1b)
pane1.activateItem(item1b)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when the active pane item removed from the active pane", ->
pane2.showItem(item2b)
it "is triggered when the active pane's active item is destroyed", ->
pane2.activateItem(item2b)
activeItemChangedHandler.reset()
pane2.removeItem(item2b)
pane2.destroyItem(item2b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2a
it "is not triggered when the active pane item removed from an inactive pane", ->
pane1.showItem(item1b)
it "is not triggered when an inactive pane's active item is destroyed", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.removeItem(item1b)
pane1.destroyItem(item1b)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when the active pane is removed", ->
it "is triggered when the active pane is destroyed", ->
pane2.remove()
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
it "is not triggered when an inactive pane is removed", ->
it "is not triggered when an inactive pane is destroyed", ->
pane1.remove()
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when the active pane is changed", ->
pane1.makeActive()
pane1.activate()
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
@@ -265,7 +263,7 @@ describe "PaneContainer", ->
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item3a
it "is not triggered when the non active pane is removed", ->
it "is not triggered when an inactive pane is destroyed", ->
pane3 = pane2.splitDown(item3a)
activeItemChangedHandler.reset()

134
spec/pane-model-spec.coffee Normal file
View File

@@ -0,0 +1,134 @@
{Model} = require 'theorist'
Pane = require '../src/pane'
PaneAxis = require '../src/pane-axis'
PaneContainer = require '../src/pane-container'
describe "Pane", ->
describe "split methods", ->
[pane1, container] = []
beforeEach ->
pane1 = new Pane(items: ["A"])
container = new PaneContainer(root: pane1)
describe "::splitLeft(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a row and inserts a new pane to the left of itself", ->
pane2 = pane1.splitLeft(items: ["B"])
pane3 = pane1.splitLeft(items: ["C"])
expect(container.root.orientation).toBe 'horizontal'
expect(container.root.children).toEqual [pane2, pane3, pane1]
describe "when the parent is a column", ->
it "replaces itself with a row and inserts a new pane to the left of itself", ->
pane1.splitDown()
pane2 = pane1.splitLeft(items: ["B"])
pane3 = pane1.splitLeft(items: ["C"])
row = container.root.children[0]
expect(row.orientation).toBe 'horizontal'
expect(row.children).toEqual [pane2, pane3, pane1]
describe "::splitRight(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a row and inserts a new pane to the right of itself", ->
pane2 = pane1.splitRight(items: ["B"])
pane3 = pane1.splitRight(items: ["C"])
expect(container.root.orientation).toBe 'horizontal'
expect(container.root.children).toEqual [pane1, pane3, pane2]
describe "when the parent is a column", ->
it "replaces itself with a row and inserts a new pane to the right of itself", ->
pane1.splitDown()
pane2 = pane1.splitRight(items: ["B"])
pane3 = pane1.splitRight(items: ["C"])
row = container.root.children[0]
expect(row.orientation).toBe 'horizontal'
expect(row.children).toEqual [pane1, pane3, pane2]
describe "::splitUp(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a column and inserts a new pane above itself", ->
pane2 = pane1.splitUp(items: ["B"])
pane3 = pane1.splitUp(items: ["C"])
expect(container.root.orientation).toBe 'vertical'
expect(container.root.children).toEqual [pane2, pane3, pane1]
describe "when the parent is a row", ->
it "replaces itself with a column and inserts a new pane above itself", ->
pane1.splitRight()
pane2 = pane1.splitUp(items: ["B"])
pane3 = pane1.splitUp(items: ["C"])
column = container.root.children[0]
expect(column.orientation).toBe 'vertical'
expect(column.children).toEqual [pane2, pane3, pane1]
describe "::splitDown(params)", ->
describe "when the parent is the container root", ->
it "replaces itself with a column and inserts a new pane below itself", ->
pane2 = pane1.splitDown(items: ["B"])
pane3 = pane1.splitDown(items: ["C"])
expect(container.root.orientation).toBe 'vertical'
expect(container.root.children).toEqual [pane1, pane3, pane2]
describe "when the parent is a row", ->
it "replaces itself with a column and inserts a new pane below itself", ->
pane1.splitRight()
pane2 = pane1.splitDown(items: ["B"])
pane3 = pane1.splitDown(items: ["C"])
column = container.root.children[0]
expect(column.orientation).toBe 'vertical'
expect(column.children).toEqual [pane1, pane3, pane2]
it "sets up the new pane to be focused", ->
expect(pane1.focused).toBe false
pane2 = pane1.splitRight()
expect(pane2.focused).toBe true
describe "::destroyItem(item)", ->
describe "when the last item is destroyed", ->
it "destroys the pane", ->
pane = new Pane(items: ["A", "B"])
pane.destroyItem("A")
pane.destroyItem("B")
expect(pane.isDestroyed()).toBe true
describe "when an item emits a destroyed event", ->
it "removes it from the list of items", ->
pane = new Pane(items: [new Model, new Model, new Model])
[item1, item2, item3] = pane.items
pane.items[1].destroy()
expect(pane.items).toEqual [item1, item3]
describe "::destroy()", ->
[pane1, container] = []
beforeEach ->
pane1 = new Pane(items: [new Model, new Model])
container = new PaneContainer(root: pane1)
it "destroys the pane's destroyable items", ->
[item1, item2] = pane1.items
pane1.destroy()
expect(item1.isDestroyed()).toBe true
expect(item2.isDestroyed()).toBe true
describe "if the pane's parent has more than two children", ->
it "removes the pane from its parent", ->
pane2 = pane1.splitRight()
pane3 = pane2.splitRight()
expect(container.root.children).toEqual [pane1, pane2, pane3]
pane2.destroy()
expect(container.root.children).toEqual [pane1, pane3]
describe "if the pane's parent has two children", ->
it "replaces the parent with its last remaining child", ->
pane2 = pane1.splitRight()
pane3 = pane2.splitDown()
expect(container.root.children[0]).toBe pane1
expect(container.root.children[1].children).toEqual [pane2, pane3]
pane3.destroy()
expect(container.root.children).toEqual [pane1, pane2]
pane2.destroy()
expect(container.root).toBe pane1

View File

@@ -1,10 +1,10 @@
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
PaneContainerView = require '../src/pane-container-view'
PaneView = require '../src/pane-view'
{fs, $, View} = require 'atom'
path = require 'path'
temp = require 'temp'
describe "Pane", ->
describe "PaneView", ->
[container, view1, view2, editor1, editor2, pane] = []
class TestView extends View
@@ -13,16 +13,16 @@ describe "Pane", ->
initialize: ({@id, @text}) ->
serialize: -> { deserializer: 'TestView', @id, @text }
getUri: -> @id
isEqual: (other) -> @id == other.id and @text == other.text
isEqual: (other) -> other? and @id == other.id and @text == other.text
beforeEach ->
atom.deserializers.add(TestView)
container = new PaneContainer
container = new PaneContainerView
view1 = new TestView(id: 'view-1', text: 'View 1')
view2 = new TestView(id: 'view-2', text: 'View 2')
editor1 = atom.project.openSync('sample.js')
editor2 = atom.project.openSync('sample.txt')
pane = new Pane(view1, editor1, view2, editor2)
pane = new PaneView(view1, editor1, view2, editor2)
container.setRoot(pane)
afterEach ->
@@ -32,49 +32,49 @@ describe "Pane", ->
it "displays the first item in the pane", ->
expect(pane.itemViews.find('#view-1')).toExist()
describe "::showItem(item)", ->
describe "::activateItem(item)", ->
it "hides all item views except the one being shown and sets the activeItem", ->
expect(pane.activeItem).toBe view1
pane.showItem(view2)
pane.activateItem(view2)
expect(view1.css('display')).toBe 'none'
expect(view2.css('display')).not.toBe 'none'
expect(pane.activeItem).toBe view2
it "triggers 'pane:active-item-changed' if the item isn't already the activeItem", ->
pane.makeActive()
pane.activate()
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
container.on 'pane:active-item-changed', itemChangedHandler
expect(pane.activeItem).toBe view1
pane.showItem(view2)
pane.showItem(view2)
pane.activateItem(view2)
pane.activateItem(view2)
expect(itemChangedHandler.callCount).toBe 1
expect(itemChangedHandler.argsForCall[0][1]).toBe view2
itemChangedHandler.reset()
pane.showItem(editor1)
pane.activateItem(editor1)
expect(itemChangedHandler).toHaveBeenCalled()
expect(itemChangedHandler.argsForCall[0][1]).toBe editor1
itemChangedHandler.reset()
describe "if the pane's active view is focused before calling showItem", ->
describe "if the pane's active view is focused before calling activateItem", ->
it "focuses the new active view", ->
container.attachToDom()
pane.focus()
expect(pane.activeView).not.toBe view2
expect(pane.activeView).toMatchSelector ':focus'
pane.showItem(view2)
pane.activateItem(view2)
expect(view2).toMatchSelector ':focus'
describe "when the given item isn't yet in the items list on the pane", ->
view3 = null
beforeEach ->
view3 = new TestView(id: 'view-3', text: "View 3")
pane.showItem(editor1)
pane.activateItem(editor1)
expect(pane.getActiveItemIndex()).toBe 1
it "adds it to the items list after the active item", ->
pane.showItem(view3)
pane.activateItem(view3)
expect(pane.getItems()).toEqual [view1, editor1, view3, view2, editor2]
expect(pane.activeItem).toBe view3
expect(pane.getActiveItemIndex()).toBe 2
@@ -83,21 +83,21 @@ describe "Pane", ->
events = []
container.on 'pane:item-added', (e, item, index) -> events.push(['pane:item-added', item, index])
container.on 'pane:active-item-changed', (e, item) -> events.push(['pane:active-item-changed', item])
pane.showItem(view3)
pane.activateItem(view3)
expect(events).toEqual [['pane:item-added', view3, 2], ['pane:active-item-changed', view3]]
describe "when showing a model item", ->
describe "when no view has yet been appended for that item", ->
it "appends and shows a view to display the item based on its `.getViewClass` method", ->
pane.showItem(editor1)
pane.activateItem(editor1)
editorView = pane.activeView
expect(editorView.css('display')).not.toBe 'none'
expect(editorView.editor).toBe editor1
describe "when a valid view has already been appended for another item", ->
it "multiple views are created for multiple items", ->
pane.showItem(editor1)
pane.showItem(editor2)
pane.activateItem(editor1)
pane.activateItem(editor2)
expect(pane.itemViews.find('.editor').length).toBe 2
editorView = pane.activeView
expect(editorView.css('display')).not.toBe 'none'
@@ -118,23 +118,23 @@ describe "Pane", ->
serialize: -> {@id, @text}
getViewClass: -> TestView
pane.showItem(model1)
pane.showItem(model2)
pane.activateItem(model1)
pane.activateItem(model2)
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
pane.showPreviousItem()
pane.activatePreviousItem()
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
pane.removeItem(model2)
pane.destroyItem(model2)
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
pane.removeItem(model1)
pane.destroyItem(model1)
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
describe "when showing a view item", ->
it "appends it to the itemViews div if it hasn't already been appended and shows it", ->
expect(pane.itemViews.find('#view-2')).not.toExist()
pane.showItem(view2)
pane.activateItem(view2)
expect(pane.itemViews.find('#view-2')).toExist()
expect(pane.activeView).toBe view2
@@ -196,27 +196,31 @@ describe "Pane", ->
expect(pane.getItems().indexOf(editor2)).not.toBe -1
expect(editor2.isDestroyed()).toBe false
describe "::removeItem(item)", ->
it "removes the item's associated view", ->
view1.remove = (selector, keepData) -> @wasRemoved = not keepData
pane.destroyItem(view1)
expect(view1.wasRemoved).toBe true
it "removes the item from the items list and shows the next item if it was showing", ->
pane.removeItem(view1)
pane.destroyItem(view1)
expect(pane.getItems()).toEqual [editor1, view2, editor2]
expect(pane.activeItem).toBe editor1
pane.showItem(editor2)
pane.removeItem(editor2)
pane.activateItem(editor2)
pane.destroyItem(editor2)
expect(pane.getItems()).toEqual [editor1, view2]
expect(pane.activeItem).toBe editor1
it "triggers 'pane:item-removed' with the item and its former index", ->
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
pane.on 'pane:item-removed', itemRemovedHandler
pane.removeItem(editor1)
pane.destroyItem(editor1)
expect(itemRemovedHandler).toHaveBeenCalled()
expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
describe "when removing the last item", ->
it "removes the pane", ->
pane.removeItem(item) for item in pane.getItems()
pane.destroyItem(item) for item in pane.getItems()
expect(pane.hasParent()).toBeFalsy()
describe "when the pane is focused", ->
@@ -226,22 +230,22 @@ describe "Pane", ->
pane2 = pane.splitRight(new TestView(id: 'view-3', text: 'View 3'))
pane.focus()
expect(pane).toMatchSelector(':has(:focus)')
pane.removeItem(item) for item in pane.getItems()
pane.destroyItem(item) for item in pane.getItems()
expect(pane2).toMatchSelector ':has(:focus)'
describe "when the item is a view", ->
it "removes the item from the 'item-views' div", ->
expect(view1.parent()).toMatchSelector pane.itemViews
pane.removeItem(view1)
pane.destroyItem(view1)
expect(view1.parent()).not.toMatchSelector pane.itemViews
describe "when the item is a model", ->
it "removes the associated view only when all items that require it have been removed", ->
pane.showItem(editor1)
pane.showItem(editor2)
pane.removeItem(editor2)
pane.activateItem(editor1)
pane.activateItem(editor2)
pane.destroyItem(editor2)
expect(pane.itemViews.find('.editor')).toExist()
pane.removeItem(editor1)
pane.destroyItem(editor1)
expect(pane.itemViews.find('.editor')).not.toExist()
describe "::moveItem(item, index)", ->
@@ -281,9 +285,9 @@ describe "Pane", ->
describe "when it is the last item on the source pane", ->
it "removes the source pane, but does not destroy the item", ->
pane.removeItem(view1)
pane.removeItem(view2)
pane.removeItem(editor2)
pane.destroyItem(view1)
pane.destroyItem(view2)
pane.destroyItem(editor2)
expect(pane.getItems()).toEqual [editor1]
pane.moveItemToPane(editor1, pane2, 1)
@@ -296,12 +300,12 @@ describe "Pane", ->
it "preserves data by detaching instead of removing", ->
view1.data('preservative', 1234)
pane.moveItemToPane(view1, pane2, 1)
pane2.showItemAtIndex(1)
pane2.activateItemAtIndex(1)
expect(pane2.activeView.data('preservative')).toBe 1234
describe "pane:close", ->
it "destroys all items and removes the pane", ->
pane.showItem(editor1)
pane.activateItem(editor1)
pane.trigger 'pane:close'
expect(pane.hasParent()).toBeFalsy()
expect(editor2.isDestroyed()).toBe true
@@ -309,7 +313,7 @@ describe "Pane", ->
describe "pane:close-other-items", ->
it "destroys all items except the current", ->
pane.showItem(editor1)
pane.activateItem(editor1)
pane.trigger 'pane:close-other-items'
expect(editor2.isDestroyed()).toBe true
expect(pane.getItems()).toEqual [editor1]
@@ -319,7 +323,7 @@ describe "Pane", ->
describe "when the current item has a save method", ->
it "saves the current item", ->
spyOn(editor2, 'save')
pane.showItem(editor2)
pane.activateItem(editor2)
pane.saveActiveItem()
expect(editor2.save).toHaveBeenCalled()
@@ -337,7 +341,7 @@ describe "Pane", ->
it "opens a save dialog and saves the current item as the selected path", ->
newEditor = atom.project.openSync()
spyOn(newEditor, 'saveAs')
pane.showItem(newEditor)
pane.activateItem(newEditor)
pane.saveActiveItem()
@@ -357,7 +361,7 @@ describe "Pane", ->
describe "when the current item has a saveAs method", ->
it "opens the save dialog and calls saveAs on the item with the selected path", ->
spyOn(editor2, 'saveAs')
pane.showItem(editor2)
pane.activateItem(editor2)
pane.saveActiveItemAs()
@@ -405,7 +409,7 @@ describe "Pane", ->
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
activeItemTitleChangedHandler.reset()
pane.showItem(view2)
pane.activateItem(view2)
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
@@ -413,7 +417,7 @@ describe "Pane", ->
it "removes the pane item", ->
filePath = temp.openSync('atom').path
editor = atom.project.openSync(filePath)
pane.showItem(editor)
pane.activateItem(editor)
expect(pane.items).toHaveLength(5)
fs.removeSync(filePath)
@@ -437,29 +441,19 @@ describe "Pane", ->
[paneToLeft, paneToRight] = []
beforeEach ->
pane.showItem(editor1)
pane.activateItem(editor1)
paneToLeft = pane.splitLeft(pane.copyActiveItem())
paneToRight = pane.splitRight(pane.copyActiveItem())
container.attachToDom()
describe "when the removed pane is focused", ->
it "activates and focuses the next pane", ->
pane.focus()
describe "when the removed pane is active", ->
it "makes the next the next pane active and focuses it", ->
pane.activate()
pane.remove()
expect(paneToLeft.isActive()).toBeFalsy()
expect(paneToRight.isActive()).toBeTruthy()
expect(paneToRight).toMatchSelector ':has(:focus)'
describe "when the removed pane is active but not focused", ->
it "activates the next pane, but does not focus it", ->
$(document.activeElement).blur()
expect(pane).not.toMatchSelector ':has(:focus)'
pane.makeActive()
pane.remove()
expect(paneToLeft.isActive()).toBeFalsy()
expect(paneToRight.isActive()).toBeTruthy()
expect(paneToRight).not.toMatchSelector ':has(:focus)'
describe "when the removed pane is not active", ->
it "does not affect the active pane or the focus", ->
paneToLeft.focus()
@@ -491,7 +485,7 @@ describe "Pane", ->
describe "::getNextPane()", ->
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
pane.showItem(editor1)
pane.activateItem(editor1)
expect(pane.getNextPane()).toBeUndefined
pane2 = pane.splitRight(pane.copyActiveItem())
expect(pane.getNextPane()).toBe pane2
@@ -537,7 +531,7 @@ describe "Pane", ->
[pane1, view3, view4] = []
beforeEach ->
pane1 = pane
pane.showItem(editor1)
pane.activateItem(editor1)
view3 = new TestView(id: 'view-3', text: 'View 3')
view4 = new TestView(id: 'view-4', text: 'View 4')
@@ -545,130 +539,61 @@ describe "Pane", ->
it "builds a row if needed, then appends a new pane after itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane1.splitRight(pane1.copyActiveItem())
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(pane2.items).toEqual [editor1]
expect(pane2.activeItem).not.toBe editor1 # it's a copy
pane3 = pane2.splitRight(view3, view4)
expect(pane3.getItems()).toEqual [view3, view4]
expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
expect(container.find('.pane-row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
it "builds a row if needed, then appends a new pane after itself ", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane1.splitRight()
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(pane2.items).toEqual []
expect(pane2.activeItem).toBe null
expect(pane2.activeItem).toBeUndefined()
pane3 = pane2.splitRight()
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]]
expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]]
expect(pane3.items).toEqual []
expect(pane3.activeItem).toBe null
expect(pane3.activeItem).toBeUndefined()
describe "splitLeft(items...)", ->
it "builds a row if needed, then appends a new pane before itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane.splitLeft(pane1.copyActiveItem())
expect(container.find('.row .pane').toArray()).toEqual [pane2[0], pane[0]]
expect(container.find('.pane-row .pane').toArray()).toEqual [pane2[0], pane[0]]
expect(pane2.items).toEqual [editor1]
expect(pane2.activeItem).not.toBe editor1 # it's a copy
pane3 = pane2.splitLeft(view3, view4)
expect(pane3.getItems()).toEqual [view3, view4]
expect(container.find('.row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
expect(container.find('.pane-row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
describe "splitDown(items...)", ->
it "builds a column if needed, then appends a new pane after itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane.splitDown(pane1.copyActiveItem())
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0]]
expect(container.find('.pane-column .pane').toArray()).toEqual [pane[0], pane2[0]]
expect(pane2.items).toEqual [editor1]
expect(pane2.activeItem).not.toBe editor1 # it's a copy
pane3 = pane2.splitDown(view3, view4)
expect(pane3.getItems()).toEqual [view3, view4]
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
expect(container.find('.pane-column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
describe "splitUp(items...)", ->
it "builds a column if needed, then appends a new pane before itself", ->
# creates the new pane with a copy of the active item if none are given
pane2 = pane.splitUp(pane1.copyActiveItem())
expect(container.find('.column .pane').toArray()).toEqual [pane2[0], pane[0]]
expect(container.find('.pane-column .pane').toArray()).toEqual [pane2[0], pane[0]]
expect(pane2.items).toEqual [editor1]
expect(pane2.activeItem).not.toBe editor1 # it's a copy
pane3 = pane2.splitUp(view3, view4)
expect(pane3.getItems()).toEqual [view3, view4]
expect(container.find('.column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
it "lays out nested panes by equally dividing their containing row / column", ->
container.width(520).height(240).attachToDom()
pane1.showItem($("1"))
pane1
.splitLeft($("2"))
.splitUp($("3"))
.splitLeft($("4"))
.splitDown($("5"))
row1 = container.children(':eq(0)')
expect(row1.children().length).toBe 2
column1 = row1.children(':eq(0)').view()
pane1 = row1.children(':eq(1)').view()
expect(column1.outerWidth()).toBe Math.round(2/3 * container.width())
expect(column1.outerHeight()).toBe container.height()
expect(pane1.outerWidth()).toBe Math.round(1/3 * container.width())
expect(pane1.outerHeight()).toBe container.height()
expect(Math.round(pane1.position().left)).toBe column1.outerWidth()
expect(column1.children().length).toBe 2
row2 = column1.children(':eq(0)').view()
pane2 = column1.children(':eq(1)').view()
expect(row2.outerWidth()).toBe column1.outerWidth()
expect(row2.height()).toBe 2/3 * container.height()
expect(pane2.outerWidth()).toBe column1.outerWidth()
expect(pane2.outerHeight()).toBe 1/3 * container.height()
expect(Math.round(pane2.position().top)).toBe row2.height()
expect(row2.children().length).toBe 2
column3 = row2.children(':eq(0)').view()
pane3 = row2.children(':eq(1)').view()
expect(column3.outerWidth()).toBe Math.round(1/3 * container.width())
expect(column3.outerHeight()).toBe row2.outerHeight()
# the built in rounding seems to be rounding x.5 down, but we need to go up. this sucks.
expect(Math.round(pane3.trueWidth())).toBe Math.round(1/3 * container.width())
expect(pane3.height()).toBe row2.outerHeight()
expect(Math.round(pane3.position().left)).toBe column3.width()
expect(column3.children().length).toBe 2
pane4 = column3.children(':eq(0)').view()
pane5 = column3.children(':eq(1)').view()
expect(pane4.outerWidth()).toBe column3.width()
expect(pane4.outerHeight()).toBe 1/3 * container.height()
expect(pane5.outerWidth()).toBe column3.width()
expect(Math.round(pane5.position().top)).toBe pane4.outerHeight()
expect(pane5.outerHeight()).toBe 1/3 * container.height()
pane5.remove()
expect(column3.parent()).not.toExist()
expect(pane2.outerHeight()).toBe Math.floor(1/2 * container.height())
expect(pane3.outerHeight()).toBe Math.floor(1/2 * container.height())
expect(pane4.outerHeight()).toBe Math.floor(1/2 * container.height())
pane4.remove()
expect(row2.parent()).not.toExist()
expect(pane1.outerWidth()).toBe Math.floor(1/2 * container.width())
expect(pane2.outerWidth()).toBe Math.floor(1/2 * container.width())
expect(pane3.outerWidth()).toBe Math.floor(1/2 * container.width())
pane3.remove()
expect(column1.parent()).not.toExist()
expect(pane2.outerHeight()).toBe container.height()
pane2.remove()
expect(row1.parent()).not.toExist()
expect(container.children().length).toBe 1
expect(container.children('.pane').length).toBe 1
expect(pane1.outerWidth()).toBe container.width()
expect(container.find('.pane-column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
describe "::itemForUri(uri)", ->
it "returns the item for which a call to .getUri() returns the given uri", ->
@@ -681,7 +606,7 @@ describe "Pane", ->
expect(newPane.getItems()).toEqual [view1, editor1, view2, editor2]
it "restores the active item on deserialization", ->
pane.showItem(editor2)
pane.activateItem(editor2)
newPane = pane.testSerialization()
expect(newPane.activeItem).toEqual editor2
@@ -691,7 +616,7 @@ describe "Pane", ->
class Unserializable
getViewClass: -> TestView
pane.showItem(new Unserializable)
pane.activateItem(new Unserializable)
newPane = pane.testSerialization()
expect(newPane.activeItem).toEqual pane.items[0]

View File

@@ -2,7 +2,7 @@
Q = require 'q'
path = require 'path'
temp = require 'temp'
Pane = require '../src/pane'
PaneView = require '../src/pane-view'
describe "WorkspaceView", ->
pathToOpen = null
@@ -48,20 +48,20 @@ describe "WorkspaceView", ->
pane2 = pane1.splitRight()
pane3 = pane2.splitRight()
pane4 = pane2.splitDown()
pane2.showItem(atom.project.openSync('b'))
pane3.showItem(atom.project.openSync('../sample.js'))
pane2.activateItem(atom.project.openSync('b'))
pane3.activateItem(atom.project.openSync('../sample.js'))
pane3.activeItem.setCursorScreenPosition([2, 4])
pane4.showItem(atom.project.openSync('../sample.txt'))
pane4.activateItem(atom.project.openSync('../sample.txt'))
pane4.activeItem.setCursorScreenPosition([0, 2])
pane2.focus()
simulateReload()
expect(atom.workspaceView.getEditorViews().length).toBe 4
editor1 = atom.workspaceView.panes.find('.row > .pane .editor:eq(0)').view()
editor3 = atom.workspaceView.panes.find('.row > .pane .editor:eq(1)').view()
editor2 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(0)').view()
editor4 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(1)').view()
editor1 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(0)').view()
editor3 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(1)').view()
editor2 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(0)').view()
editor4 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(1)').view()
expect(editor1.getPath()).toBe atom.project.resolve('a')
expect(editor2.getPath()).toBe atom.project.resolve('b')
@@ -212,7 +212,7 @@ describe "WorkspaceView", ->
describe ".openSync(filePath, options)", ->
describe "when there is no active pane", ->
beforeEach ->
spyOn(Pane.prototype, 'focus')
spyOn(PaneView.prototype, 'focus')
atom.workspaceView.getActivePane().remove()
expect(atom.workspaceView.getActivePane()).toBeUndefined()
@@ -298,20 +298,19 @@ describe "WorkspaceView", ->
expect(pane2[0]).not.toBe pane1[0]
expect(editor.getPath()).toBe require.resolve('./fixtures/dir/b')
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
editor = atom.workspaceView.openSync('file1', split: 'right')
pane3 = atom.workspaceView.getActivePane()
expect(pane3[0]).toBe pane2[0]
expect(editor.getPath()).toBe require.resolve('./fixtures/dir/file1')
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
describe ".openSingletonSync(filePath, options)", ->
describe "when there is an active pane", ->
[pane1] = []
beforeEach ->
spyOn(Pane.prototype, 'focus').andCallFake -> @makeActive()
pane1 = atom.workspaceView.getActivePane()
it "creates a new pane and reuses the file when already open", ->
@@ -320,9 +319,9 @@ describe "WorkspaceView", ->
expect(pane2[0]).not.toBe pane1[0]
expect(pane1.itemForUri('b')).toBeFalsy()
expect(pane2.itemForUri('b')).not.toBeFalsy()
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
pane1.focus()
pane1.activate()
expect(atom.workspaceView.getActivePane()[0]).toBe pane1[0]
atom.workspaceView.openSingletonSync('b', split: 'right')
@@ -330,7 +329,7 @@ describe "WorkspaceView", ->
expect(pane3[0]).toBe pane2[0]
expect(pane1.itemForUri('b')).toBeFalsy()
expect(pane2.itemForUri('b')).not.toBeFalsy()
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
it "handles split: left by opening to the left pane when necessary", ->
atom.workspaceView.openSingletonSync('b', split: 'right')
@@ -344,15 +343,15 @@ describe "WorkspaceView", ->
expect(pane1.itemForUri('file1')).toBeTruthy()
expect(pane2.itemForUri('file1')).toBeFalsy()
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
pane2.focus()
pane2.activate()
expect(atom.workspaceView.getActivePane()[0]).toBe pane2[0]
atom.workspaceView.openSingletonSync('file1', split: 'left')
activePane = atom.workspaceView.getActivePane()
expect(activePane[0]).toBe pane1[0]
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
it "reuses the file when already open", ->
atom.workspaceView.openSync('b')
@@ -361,7 +360,7 @@ describe "WorkspaceView", ->
describe ".open(filePath)", ->
beforeEach ->
spyOn(Pane.prototype, 'focus')
spyOn(PaneView.prototype, 'focus')
describe "when there is no active pane", ->
beforeEach ->
@@ -571,7 +570,7 @@ describe "WorkspaceView", ->
it "saves active editor until there are none", ->
editor = atom.project.openSync('../sample.txt')
spyOn(editor, 'save')
atom.workspaceView.getActivePane().showItem(editor)
atom.workspaceView.getActivePane().activateItem(editor)
atom.workspaceView.trigger('core:save')
expect(editor.save).toHaveBeenCalled()
@@ -582,6 +581,6 @@ describe "WorkspaceView", ->
it "saves active editor until there are none", ->
editor = atom.project.openSync('../sample.txt')
spyOn(editor, 'saveAs')
atom.workspaceView.getActivePane().showItem(editor)
atom.workspaceView.getActivePane().activateItem(editor)
atom.workspaceView.trigger('core:save-as')
expect(editor.saveAs).toHaveBeenCalled()

View File

@@ -35,7 +35,7 @@ class DisplayBuffer extends Model
@subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
@subscribe @buffer, 'marker-created', @handleBufferMarkerCreated
@subscribe @$softWrap, 'value', (softWrap) =>
@subscribe @$softWrap, (softWrap) =>
@emit 'soft-wrap-changed', softWrap
@updateWrappedScreenLines()

View File

@@ -827,6 +827,9 @@ class EditorView extends View
@editor.setVisible(true)
@editor.on "destroyed", =>
@remove()
@editor.on "contents-conflicted.editor", =>
@showBufferConflictAlert(@editor)
@@ -1070,7 +1073,7 @@ class EditorView extends View
#
# Returns a {Pane}.
getPane: ->
@parent('.item-views').parent('.pane').view()
@parent('.item-views').parents('.pane').view()
remove: (selector, keepData) ->
return super if keepData or @removed

View File

@@ -73,8 +73,8 @@ class Editor extends Model
@languageMode = new LanguageMode(this, @buffer.getExtension())
@subscribe @$scrollTop, 'value', (scrollTop) => @emit 'scroll-top-changed', scrollTop
@subscribe @$scrollLeft, 'value', (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
@subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop
@subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
atom.project.addEditor(this) if registerEditor
@@ -174,7 +174,8 @@ class Editor extends Model
# Returns a {Boolean}.
isEqual: (other) ->
return false unless other instanceof Editor
@buffer.getPath() == other.buffer.getPath() and
@isAlive() == other.isAlive() and
@buffer.getPath() == other.buffer.getPath() and
@getScrollTop() == other.getScrollTop() and
@getScrollLeft() == other.getScrollLeft() and
@getCursorScreenPosition().isEqual(other.getCursorScreenPosition())
@@ -313,6 +314,12 @@ class Editor extends Model
# {Delegates to: TextBuffer.setText}
setText: (text) -> @buffer.setText(text)
# {Delegates to: TextBuffer.getTextInRange}
getTextInRange: (range) -> @buffer.getTextInRange(range)
# {Delegates to: TextBuffer.getLineCount}
getLineCount: -> @buffer.getLineCount()
# Private: Retrieves the current {TextBuffer}.
getBuffer: -> @buffer

View File

@@ -230,6 +230,8 @@ class Gutter extends View
@highlightedLineNumbers.push(highlightedLineNumber)
highlightLines: ->
return unless @getEditorView().editor?.isAlive()
if @getEditorView().getSelection().isEmpty()
row = @getEditorView().getCursorScreenPosition().row
rowRange = new Range([row, 0], [row, 0])

34
src/pane-axis-view.coffee Normal file
View File

@@ -0,0 +1,34 @@
{View} = require './space-pen-extensions'
PaneView = null
### Internal ###
module.exports =
class PaneAxisView extends View
initialize: (@model) ->
@onChildAdded(child) for child in @model.children
@subscribe @model.children, 'changed', @onChildrenChanged
viewForModel: (model) ->
viewClass = model.getViewClass()
model._view ?= new viewClass(model)
onChildrenChanged: ({index, removedValues, insertedValues}) =>
focusedElement = document.activeElement if @hasFocus()
@onChildRemoved(child, index) for child in removedValues
@onChildAdded(child, index + i) for child, i in insertedValues
focusedElement?.focus() if document.activeElement is document.body
onChildAdded: (child, index) =>
view = @viewForModel(child)
@insertAt(index, view)
onChildRemoved: (child) =>
view = @viewForModel(child)
view.detach()
PaneView ?= require './pane-view'
if view instanceof PaneView and view.model.isDestroyed()
@getContainer()?.trigger 'pane:removed', [view]
getContainer: ->
@closest('.panes').view()

View File

@@ -1,76 +1,66 @@
{Model, Sequence} = require 'theorist'
{flatten} = require 'underscore-plus'
Serializable = require 'serializable'
{$, View} = require './space-pen-extensions'
### Internal ###
PaneRowView = null
PaneColumnView = null
module.exports =
class PaneAxis extends View
class PaneAxis extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
initialize: ({children}={}) ->
@addChild(child) for child in children ? []
constructor: ({@container, @orientation, children}) ->
@children = Sequence.fromArray(children ? [])
serializeParams: ->
children: @children().views().map (child) -> child.serialize()
@subscribe @children.onEach (child) =>
child.parent = this
child.container = @container
@subscribe child, 'destroyed', => @removeChild(child)
@subscribe @children.onRemoval (child) => @unsubscribe(child)
@when @children.$length.becomesLessThan(2), 'reparentLastChild'
@when @children.$length.becomesLessThan(1), 'destroy'
deserializeParams: (params) ->
params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState)
{container} = params
params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {container})
params
addChild: (child, index=@children().length) ->
@insertAt(index, child)
@getContainer()?.adjustPaneDimensions()
serializeParams: ->
children: @children.map (child) -> child.serialize()
orientation: @orientation
getViewClass: ->
if @orientation is 'vertical'
PaneColumnView ?= require './pane-column-view'
else
PaneRowView ?= require './pane-row-view'
getPanes: ->
flatten(@children.map (child) -> child.getPanes())
addChild: (child, index=@children.length) ->
@children.splice(index, 0, child)
removeChild: (child) ->
parent = @parent().view()
container = @getContainer()
childWasInactive = not child.isActive?()
index = @children.indexOf(child)
throw new Error("Removing non-existent child") if index is -1
@children.splice(index, 1)
primitiveRemove = (child) =>
node = child[0]
$.cleanData(node.getElementsByTagName('*'))
$.cleanData([node])
this[0].removeChild(node)
replaceChild: (oldChild, newChild) ->
index = @children.indexOf(oldChild)
throw new Error("Replacing non-existent child") if index is -1
@children.splice(index, 1, newChild)
# use primitive .removeChild() dom method instead of .remove() to avoid recursive loop
if @children().length == 2
primitiveRemove(child)
sibling = @children().view()
siblingFocused = sibling.is(':has(:focus)')
sibling.detach()
insertChildBefore: (currentChild, newChild) ->
index = @children.indexOf(currentChild)
@children.splice(index, 0, newChild)
if parent.setRoot?
parent.setRoot(sibling, suppressPaneItemChangeEvents: childWasInactive)
else
parent.insertChildBefore(this, sibling)
parent.removeChild(this)
sibling.focus() if siblingFocused
else
primitiveRemove(child)
insertChildAfter: (currentChild, newChild) ->
index = @children.indexOf(currentChild)
@children.splice(index + 1, 0, newChild)
container.adjustPaneDimensions()
Pane = require './pane'
container.trigger 'pane:removed', [child] if child instanceof Pane
detachChild: (child) ->
child.detach()
getContainer: ->
@closest('.panes').view()
getActivePaneItem: ->
@getActivePane()?.activeItem
getActivePane: ->
@find('.pane.active').view() ? @find('.pane:first').view()
insertChildBefore: (child, newChild) ->
newChild.insertBefore(child)
insertChildAfter: (child, newChild) ->
newChild.insertAfter(child)
horizontalChildUnits: ->
$(child).view().horizontalGridUnits() for child in @children()
verticalChildUnits: ->
$(child).view().verticalGridUnits() for child in @children()
reparentLastChild: ->
@parent.replaceChild(this, @children[0])

View File

@@ -0,0 +1,13 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
PaneAxisView = require './pane-axis-view'
# Internal:
module.exports =
class PaneColumnView extends PaneAxisView
@content: ->
@div class: 'pane-column'
className: ->
"PaneColumn"

View File

@@ -1,34 +0,0 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
PaneAxis = require './pane-axis'
# Internal:
module.exports =
class PaneColumn extends PaneAxis
@content: ->
@div class: 'column'
className: ->
"PaneColumn"
adjustDimensions: ->
totalUnits = @verticalGridUnits()
unitsSoFar = 0
for child in @children()
child = $(child).view()
childUnits = child.verticalGridUnits()
child.css
width: '100%'
height: "#{childUnits / totalUnits * 100}%"
top: "#{unitsSoFar / totalUnits * 100}%"
left: 0
child.adjustDimensions()
unitsSoFar += childUnits
horizontalGridUnits: ->
Math.max(@horizontalChildUnits()...)
verticalGridUnits: ->
_.sum(@verticalChildUnits())

View File

@@ -0,0 +1,132 @@
Serializable = require 'serializable'
{$, View} = require './space-pen-extensions'
PaneView = require './pane-view'
PaneContainer = require './pane-container'
# Private: Manages the list of panes within a {WorkspaceView}
module.exports =
class PaneContainerView extends View
atom.deserializers.add(this)
Serializable.includeInto(this)
@deserialize: (state) ->
new this(PaneContainer.deserialize(state.model))
@content: ->
@div class: 'panes'
initialize: (params) ->
if params instanceof PaneContainer
@model = params
else
@model = new PaneContainer({root: params?.root?.model})
@subscribe @model.$root, @onRootChanged
@subscribe @model.$activePaneItem.changes, @onActivePaneItemChanged
viewForModel: (model) ->
if model?
viewClass = model.getViewClass()
model._view ?= new viewClass(model)
serializeParams: ->
model: @model.serialize()
### Public ###
itemDestroyed: (item) ->
@trigger 'item-destroyed', [item]
getRoot: ->
@children().first().view()
setRoot: (root) ->
@model.root = root?.model
onRootChanged: (root) =>
focusedElement = document.activeElement if @hasFocus()
oldRoot = @getRoot()
if oldRoot instanceof PaneView and oldRoot.model.isDestroyed()
@trigger 'pane:removed', [oldRoot]
oldRoot?.detach()
if root?
view = @viewForModel(root)
@append(view)
focusedElement?.focus()
else
atom.workspaceView?.focus() if focusedElement?
onActivePaneItemChanged: (activeItem) =>
@trigger 'pane-container:active-pane-item-changed', [activeItem]
removeChild: (child) ->
throw new Error("Removing non-existant child") unless @getRoot() is child
@setRoot(null)
@trigger 'pane:removed', [child] if child instanceof PaneView
saveAll: ->
pane.saveItems() for pane in @getPanes()
confirmClose: ->
saved = true
for pane in @getPanes()
for item in pane.getItems()
if not pane.promptToSaveItem(item)
saved = false
break
saved
getPanes: ->
@find('.pane').views()
indexOfPane: (pane) ->
@getPanes().indexOf(pane.view())
paneAtIndex: (index) ->
@getPanes()[index]
eachPane: (callback) ->
callback(pane) for pane in @getPanes()
paneAttached = (e) -> callback($(e.target).view())
@on 'pane:attached', paneAttached
off: => @off 'pane:attached', paneAttached
getFocusedPane: ->
@find('.pane:has(:focus)').view()
getActivePane: ->
@viewForModel(@model.activePane)
getActivePaneItem: ->
@model.activePaneItem
getActiveView: ->
@getActivePane()?.activeView
paneForUri: (uri) ->
for pane in @getPanes()
view = pane.itemForUri(uri)
return pane if view?
null
focusNextPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@getFocusedPane())
nextIndex = (currentIndex + 1) % panes.length
panes[nextIndex].focus()
true
else
false
focusPreviousPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@getFocusedPane())
previousIndex = currentIndex - 1
previousIndex = panes.length - 1 if previousIndex < 0
panes[previousIndex].focus()
true
else
false

View File

@@ -1,140 +1,66 @@
{Model} = require 'theorist'
Serializable = require 'serializable'
{$, View} = require './space-pen-extensions'
Pane = require './pane'
# Private: Manages the list of panes within a {WorkspaceView}
module.exports =
class PaneContainer extends View
Serializable.includeInto(this)
class PaneContainer extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@content: ->
@div class: 'panes'
@properties
root: null
activePane: null
initialize: ({root}={}) ->
@setRoot(root)
previousRoot: null
@subscribe this, 'pane:attached', (event, pane) =>
@triggerActiveItemChange() if @getActivePane() is pane
@behavior 'activePaneItem', ->
@$activePane.switch (activePane) -> activePane?.$activeItem
@subscribe this, 'pane:removed', (event, pane) =>
@triggerActiveItemChange() unless @getActivePane()?
@subscribe this, 'pane:became-active', =>
@triggerActiveItemChange()
@subscribe this, 'pane:active-item-changed', (event, item) =>
@triggerActiveItemChange() if @getActivePaneItem() is item
triggerActiveItemChange: ->
@trigger 'pane-container:active-pane-item-changed', [@getActivePaneItem()]
serializeParams: ->
root: @getRoot()?.serialize()
constructor: (params) ->
super
@subscribe @$root, @onRootChanged
@destroyEmptyPanes() if params?.destroyEmptyPanes
deserializeParams: (params) ->
params.root = atom.deserializers.deserialize(params.root)
params.root = atom.deserializers.deserialize(params.root, container: this)
params.destroyEmptyPanes = true
params
### Public ###
serializeParams: (params) ->
root: @root?.serialize()
focusNextPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@getFocusedPane())
nextIndex = (currentIndex + 1) % panes.length
panes[nextIndex].focus()
true
else
false
focusPreviousPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@getFocusedPane())
previousIndex = currentIndex - 1
previousIndex = panes.length - 1 if previousIndex < 0
panes[previousIndex].focus()
true
else
false
makeNextPaneActive: ->
panes = @getPanes()
currentIndex = panes.indexOf(@getActivePane())
nextIndex = (currentIndex + 1) % panes.length
panes[nextIndex].makeActive()
itemDestroyed: (item) ->
@trigger 'item-destroyed', [item]
getRoot: ->
@children().first().view()
setRoot: (root, {suppressPaneItemChangeEvents}={}) ->
@empty()
if root?
@append(root)
root.makeActive?()
removeChild: (child) ->
throw new Error("Removing non-existant child") unless @getRoot() is child
@setRoot(null)
@trigger 'pane:removed', [child] if child instanceof Pane
saveAll: ->
pane.saveItems() for pane in @getPanes()
confirmClose: ->
saved = true
for pane in @getPanes()
for item in pane.getItems()
if not pane.promptToSaveItem(item)
saved = false
break
saved
replaceChild: (oldChild, newChild) ->
throw new Error("Replacing non-existent child") if oldChild isnt @root
@root = newChild
getPanes: ->
@find('.pane').views()
@root?.getPanes() ? []
indexOfPane: (pane) ->
@getPanes().indexOf(pane.view())
activateNextPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@activePane)
nextIndex = (currentIndex + 1) % panes.length
panes[nextIndex].activate()
else
@activePane = null
paneAtIndex: (index) ->
@getPanes()[index]
onRootChanged: (root) =>
@unsubscribe(@previousRoot) if @previousRoot?
@previousRoot = root
eachPane: (callback) ->
callback(pane) for pane in @getPanes()
paneAttached = (e) -> callback($(e.target).view())
@on 'pane:attached', paneAttached
off: => @off 'pane:attached', paneAttached
unless root?
@activePane = null
return
getFocusedPane: ->
@find('.pane:has(:focus)').view()
root.parent = this
root.container = this
getActivePane: ->
@find('.pane.active').view() ? @find('.pane:first').view()
if root instanceof Pane
@activePane ?= root
@subscribe root, 'destroyed', =>
@activePane = null
@root = null
getActivePaneItem: ->
@getActivePane()?.activeItem
getActiveView: ->
@getActivePane()?.activeView
paneForUri: (uri) ->
for pane in @getPanes()
view = pane.itemForUri(uri)
return pane if view?
null
adjustPaneDimensions: ->
if root = @getRoot()
root.css(width: '100%', height: '100%', top: 0, left: 0)
root.adjustDimensions()
removeEmptyPanes: ->
for pane in @getPanes() when pane.getItems().length == 0
pane.remove()
afterAttach: ->
@adjustPaneDimensions()
destroyEmptyPanes: ->
pane.destroy() for pane in @getPanes() when pane.items.length is 0

307
src/pane-model.coffee Normal file
View File

@@ -0,0 +1,307 @@
{find, compact, extend} = require 'underscore-plus'
{dirname} = require 'path'
{Model, Sequence} = require 'theorist'
Serializable = require 'serializable'
PaneAxis = require './pane-axis'
PaneView = null
# Public: A container for multiple items, one of which is *active* at a given
# time. With the default packages, a tab is displayed for each item and the
# active item's view is displayed.
module.exports =
class PaneModel extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@properties
container: null
activeItem: null
focused: false
# Public: Only one pane is considered *active* at a time. A pane is activated
# when it is focused, and when focus returns to the pane container after
# moving to another element such as a panel, it returns to the active pane.
@behavior 'active', ->
@$container
.switch((container) -> container?.$activePane)
.map((activePane) => activePane is this)
.distinctUntilChanged()
# Private:
constructor: (params) ->
super
@items = Sequence.fromArray(params?.items ? [])
@activeItem ?= @items[0]
@subscribe @items.onEach (item) =>
if typeof item.on is 'function'
@subscribe item, 'destroyed', => @removeItem(item)
@subscribe @items.onRemoval (item, index) =>
@unsubscribe item if typeof item.on is 'function'
@activate() if params?.active
# Private: Called by the Serializable mixin during serialization.
serializeParams: ->
items: compact(@items.map((item) -> item.serialize?()))
activeItemUri: @activeItem?.getUri?()
focused: @focused
active: @active
# Private: Called by the Serializable mixin during deserialization.
deserializeParams: (params) ->
{items, activeItemUri} = params
params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri
params
# Private: Called by the view layer to construct a view for this model.
getViewClass: -> PaneView ?= require './pane-view'
isActive: -> @active
# Private: Called by the view layer to indicate that the pane has gained focus.
focus: ->
@focused = true
@activate() unless @isActive()
# Private: Called by the view layer to indicate that the pane has lost focus.
blur: ->
@focused = false
true # if this is called from an event handler, don't cancel it
# Public: Makes this pane the *active* pane, causing it to gain focus
# immediately.
activate: ->
@container?.activePane = this
@emit 'activated'
# Private:
getPanes: -> [this]
# Public:
getItems: ->
@items.slice()
# Public: Returns the item at the specified index.
itemAtIndex: (index) ->
@items[index]
# Public: Makes the next item active.
activateNextItem: ->
index = @getActiveItemIndex()
if index < @items.length - 1
@activateItemAtIndex(index + 1)
else
@activateItemAtIndex(0)
# Public: Makes the previous item active.
activatePreviousItem: ->
index = @getActiveItemIndex()
if index > 0
@activateItemAtIndex(index - 1)
else
@activateItemAtIndex(@items.length - 1)
# Public: Returns the index of the current active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Public: Makes the item at the given index active.
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
# Public: Makes the given item active, adding the item if necessary.
activateItem: (item) ->
if item?
@addItem(item)
@activeItem = item
# Public: Adds the item to the pane.
#
# * item:
# The item to add. It can be a model with an associated view or a view.
# * index:
# An optional index at which to add the item. If omitted, the item is
# added to the end.
#
# Returns the added item
addItem: (item, index=@getActiveItemIndex() + 1) ->
return if item in @items
@items.splice(index, 0, item)
@emit 'item-added', item, index
item
# Private:
removeItem: (item, destroying) ->
index = @items.indexOf(item)
return if index is -1
@activateNextItem() if item is @activeItem and @items.length > 1
@items.splice(index, 1)
@emit 'item-removed', item, index, destroying
@destroy() if @items.length is 0
# Public: Moves the given item to the specified index.
moveItem: (item, newIndex) ->
oldIndex = @items.indexOf(item)
@items.splice(oldIndex, 1)
@items.splice(newIndex, 0, item)
@emit 'item-moved', item, newIndex
# Public: Moves the given item to the given index at another pane.
moveItemToPane: (item, pane, index) ->
pane.addItem(item, index)
@removeItem(item)
# Public: Destroys the currently active item and make the next item active.
destroyActiveItem: ->
@destroyItem(@activeItem)
false
# Public: Destroys the given item. If it is the active item, activate the next
# one. If this is the last item, also destroys the pane.
destroyItem: (item) ->
@emit 'before-item-destroyed', item
if @promptToSaveItem(item)
@emit 'item-destroyed', item
@removeItem(item, true)
item.destroy?()
true
else
false
# Public: Destroys all items and destroys the pane.
destroyItems: ->
@destroyItem(item) for item in @getItems()
# Public: Destroys all items but the active one.
destroyInactiveItems: ->
@destroyItem(item) for item in @getItems() when item isnt @activeItem
# Private: Called by model superclass.
destroyed: ->
@container.activateNextPane() if @isActive()
item.destroy?() for item in @items.slice()
# Public: Prompts the user to save the given item if it can be saved and is
# currently unsaved.
promptToSaveItem: (item) ->
return true unless item.shouldPromptToSave?()
uri = item.getUri()
chosen = atom.confirm
message: "'#{item.getTitle?() ? item.getUri()}' 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"]
switch chosen
when 0 then @saveItem(item, -> true)
when 1 then false
when 2 then true
# Public: Saves the active item.
saveActiveItem: ->
@saveItem(@activeItem)
# Public: Saves the active item at a prompted-for location.
saveActiveItemAs: ->
@saveItemAs(@activeItem)
# Public: Saves the specified item.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
saveItem: (item, nextAction) ->
if item.getUri?()
item.save?()
nextAction?()
else
@saveItemAs(item, nextAction)
# Public: Saves the given item at a prompted-for location.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
saveItemAs: (item, nextAction) ->
return unless item.saveAs?
itemPath = item.getPath?()
itemPath = dirname(itemPath) if itemPath
path = atom.showSaveDialogSync(itemPath)
if path
item.saveAs(path)
nextAction?()
# Public: Saves all items.
saveItems: ->
@saveItem(item) for item in @getItems()
# Public: Returns the first item that matches the given URI or undefined if
# none exists.
itemForUri: (uri) ->
find @items, (item) -> item.getUri?() is uri
# Public: Activates the first item that matches the given URI. Returns a
# boolean indicating whether a matching item was found.
activateItemForUri: (uri) ->
if item = @itemForUri(uri)
@activateItem(item)
true
else
false
# Private:
copyActiveItem: ->
@activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
# Public: Creates a new pane to the left of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {PaneModel}.
splitLeft: (params) ->
@split('horizontal', 'before', params)
# Public: Creates a new pane to the right of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {PaneModel}.
splitRight: (params) ->
@split('horizontal', 'after', params)
# Public: Creates a new pane above the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {PaneModel}.
splitUp: (params) ->
@split('vertical', 'before', params)
# Public: Creates a new pane below the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {PaneModel}.
splitDown: (params) ->
@split('vertical', 'after', params)
# Private:
split: (orientation, side, params) ->
if @parent.orientation isnt orientation
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
newPane = new @constructor(extend({focused: true}, params))
switch side
when 'before' then @parent.insertChildBefore(this, newPane)
when 'after' then @parent.insertChildAfter(this, newPane)
newPane.activate()
newPane

13
src/pane-row-view.coffee Normal file
View File

@@ -0,0 +1,13 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
PaneAxisView = require './pane-axis-view'
### Internal ###
module.exports =
class PaneRowView extends PaneAxisView
@content: ->
@div class: 'pane-row'
className: ->
"PaneRow"

View File

@@ -1,34 +0,0 @@
{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
PaneAxis = require './pane-axis'
### Internal ###
module.exports =
class PaneRow extends PaneAxis
@content: ->
@div class: 'row'
className: ->
"PaneRow"
adjustDimensions: ->
totalUnits = @horizontalGridUnits()
unitsSoFar = 0
for child in @children()
child = $(child).view()
childUnits = child.horizontalGridUnits()
child.css
width: "#{childUnits / totalUnits * 100}%"
height: '100%'
top: 0
left: "#{unitsSoFar / totalUnits * 100}%"
child.adjustDimensions()
unitsSoFar += childUnits
horizontalGridUnits: ->
_.sum(@horizontalChildUnits())
verticalGridUnits: ->
Math.max(@verticalChildUnits()...)

226
src/pane-view.coffee Normal file
View File

@@ -0,0 +1,226 @@
{$, View} = require './space-pen-extensions'
Serializable = require 'serializable'
Delegator = require 'delegato'
Pane = require './pane'
# Public: A container which can contains multiple items to be switched between.
#
# Items can be almost anything however most commonly they're {EditorView}s.
#
# Most packages won't need to use this class, unless you're interested in
# building a package that deals with switching between panes or tiems.
module.exports =
class PaneView extends View
Serializable.includeInto(this)
Delegator.includeInto(this)
@version: 1
@deserialize: (state) ->
new this(Pane.deserialize(state.model))
@content: (wrappedView) ->
@div class: 'pane', tabindex: -1, =>
@div class: 'item-views', outlet: 'itemViews'
@delegatesProperties 'items', 'activeItem', toProperty: 'model'
@delegatesMethods 'getItems', 'activateNextItem', 'activatePreviousItem', 'getActiveItemIndex',
'activateItemAtIndex', 'activateItem', 'addItem', 'itemAtIndex', 'moveItem', 'moveItemToPane',
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
'activate', toProperty: 'model'
previousActiveItem: null
# Private:
initialize: (args...) ->
if args[0] instanceof Pane
@model = args[0]
else
@model = new Pane(items: args)
@model._view = this
@onItemAdded(item) for item in @items
@viewsByItem = new WeakMap()
@handleEvents()
handleEvents: ->
@subscribe @model, 'destroyed', => @remove()
@subscribe @model.$activeItem, @onActiveItemChanged
@subscribe @model, 'item-added', @onItemAdded
@subscribe @model, 'item-removed', @onItemRemoved
@subscribe @model, 'item-moved', @onItemMoved
@subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed
@subscribe @model, 'item-destroyed', @onItemDestroyed
@subscribe @model, 'activated', @onActivated
@subscribe @model.$active, @onActiveStatusChanged
@subscribe this, 'focusin', => @model.focus()
@subscribe this, 'focusout', => @model.blur()
@subscribe this, 'focus', =>
@activeView?.focus()
false
@command 'pane:save-items', => @saveItems()
@command 'pane:show-next-item', => @activateNextItem()
@command 'pane:show-previous-item', => @activatePreviousItem()
@command 'pane:show-item-1', => @activateItemAtIndex(0)
@command 'pane:show-item-2', => @activateItemAtIndex(1)
@command 'pane:show-item-3', => @activateItemAtIndex(2)
@command 'pane:show-item-4', => @activateItemAtIndex(3)
@command 'pane:show-item-5', => @activateItemAtIndex(4)
@command 'pane:show-item-6', => @activateItemAtIndex(5)
@command 'pane:show-item-7', => @activateItemAtIndex(6)
@command 'pane:show-item-8', => @activateItemAtIndex(7)
@command 'pane:show-item-9', => @activateItemAtIndex(8)
@command 'pane:split-left', => @splitLeft(@copyActiveItem())
@command 'pane:split-right', => @splitRight(@copyActiveItem())
@command 'pane:split-up', => @splitUp(@copyActiveItem())
@command 'pane:split-down', => @splitDown(@copyActiveItem())
@command 'pane:close', => @destroyItems()
@command 'pane:close-other-items', => @destroyInactiveItems()
deserializeParams: (params) ->
params.model = Pane.deserialize(params.model)
params
serializeParams: ->
model: @model.serialize()
# Deprecated: Use ::destroyItem
removeItem: (item) -> @destroyItem(item)
# Deprecated: Use ::activateItem
showItem: (item) -> @activateItem(item)
# Deprecated: Use ::activateItemForUri
showItemForUri: (item) -> @activateItemForUri(item)
# Deprecated: Use ::activateItemAtIndex
showItemAtIndex: (index) -> @activateItemAtIndex(index)
# Deprecated: Use ::activateNextItem
showNextItem: -> @activateNextItem()
# Deprecated: Use ::activatePreviousItem
showPreviousItem: -> @activatePreviousItem()
# Private:
afterAttach: (onDom) ->
@focus() if @model.focused and onDom
return if @attached
@attached = true
@trigger 'pane:attached', [this]
onActivated: =>
@focus() unless @hasFocus()
onActiveStatusChanged: (active) =>
if active
@addClass('active')
@trigger 'pane:became-active'
else
@removeClass('active')
@trigger 'pane:became-inactive'
# Public: Returns the next pane, ordered by creation.
getNextPane: ->
panes = @getContainer()?.getPanes()
return unless panes.length > 1
nextIndex = (panes.indexOf(this) + 1) % panes.length
panes[nextIndex]
getActivePaneItem: ->
@activeItem
onActiveItemChanged: (item) =>
@previousActiveItem?.off? 'title-changed', @activeItemTitleChanged
@previousActiveItem = item
return unless item?
hasFocus = @hasFocus()
item.on? 'title-changed', @activeItemTitleChanged
view = @viewForItem(item)
@itemViews.children().not(view).hide()
@itemViews.append(view) unless view.parent().is(@itemViews)
view.show() if @attached
view.focus() if hasFocus
@activeView = view
@trigger 'pane:active-item-changed', [item]
onItemAdded: (item, index) =>
@trigger 'pane:item-added', [item, index]
onItemRemoved: (item, index, destroyed) =>
if item instanceof $
viewToRemove = item
else if viewToRemove = @viewsByItem.get(item)
@viewsByItem.delete(item)
if viewToRemove?
viewToRemove.setModel?(null)
if destroyed
viewToRemove.remove()
else
viewToRemove.detach()
@trigger 'pane:item-removed', [item, index]
onItemMoved: (item, newIndex) =>
@trigger 'pane:item-moved', [item, newIndex]
onBeforeItemDestroyed: (item) =>
@unsubscribe(item) if typeof item.off is 'function'
@trigger 'pane:before-item-destroyed', [item]
onItemDestroyed: (item) =>
@getContainer()?.itemDestroyed(item)
# Private:
activeItemTitleChanged: =>
@trigger 'pane:active-item-title-changed'
# Private:
viewForItem: (item) ->
if item instanceof $
item
else if view = @viewsByItem.get(item)
view
else
viewClass = item.getViewClass()
view = new viewClass(item)
@viewsByItem.set(item, view)
view
# Private:
viewForActiveItem: ->
@viewForItem(@activeItem)
splitLeft: (items...) -> @model.splitLeft({items})._view
splitRight: (items...) -> @model.splitRight({items})._view
splitUp: (items...) -> @model.splitUp({items})._view
splitDown: (items...) -> @model.splitDown({items})._view
# Private:
getContainer: ->
@closest('.panes').view()
beforeRemove: ->
@model.destroy() unless @model.isDestroyed()
# Private:
remove: (selector, keepData) ->
return super if keepData
@unsubscribe()
super

View File

@@ -1,212 +1,193 @@
{find, compact, extend} = require 'underscore-plus'
{dirname} = require 'path'
{$, View} = require './space-pen-extensions'
_ = require 'underscore-plus'
{Model, Sequence} = require 'theorist'
Serializable = require 'serializable'
PaneAxis = require './pane-axis'
PaneView = null
PaneRow = require './pane-row'
PaneColumn = require './pane-column'
# Public: A container which can contains multiple items to be switched between.
#
# Items can be almost anything however most commonly they're {EditorView}s.
#
# Most packages won't need to use this class, unless you're interested in
# building a package that deals with switching between panes or tiems.
# Public: A container for multiple items, one of which is *active* at a given
# time. With the default packages, a tab is displayed for each item and the
# active item's view is displayed.
module.exports =
class Pane extends View
class Pane extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@version: 1
@properties
container: null
activeItem: null
focused: false
@content: (wrappedView) ->
@div class: 'pane', tabindex: -1, =>
@div class: 'item-views', outlet: 'itemViews'
activeItem: null
items: null
viewsByItem: null # Views without a setModel() method are stored here
# Public: Only one pane is considered *active* at a time. A pane is activated
# when it is focused, and when focus returns to the pane container after
# moving to another element such as a panel, it returns to the active pane.
@behavior 'active', ->
@$container
.switch((container) -> container?.$activePane)
.map((activePane) => activePane is this)
.distinctUntilChanged()
# Private:
initialize: (args...) ->
if args[0]?.items # deserializing
{@items, activeItemUri, @focusOnAttach} = args[0]
else
@items = args
constructor: (params) ->
super
@items ?= []
@items = Sequence.fromArray(params?.items ? [])
@activeItem ?= @items[0]
@handleItemEvents(item) for item in @items
@subscribe @items.onEach (item) =>
if typeof item.on is 'function'
@subscribe item, 'destroyed', => @removeItem(item)
@viewsByItem = new WeakMap()
@subscribe @items.onRemoval (item, index) =>
@unsubscribe item if typeof item.on is 'function'
unless activeItemUri? and @showItemForUri(activeItemUri)
@showItem(@items[0]) if @items.length > 0
@activate() if params?.active
@command 'pane:save-items', @saveItems
@command 'pane:show-next-item', @showNextItem
@command 'pane:show-previous-item', @showPreviousItem
@command 'pane:show-item-1', => @showItemAtIndex(0)
@command 'pane:show-item-2', => @showItemAtIndex(1)
@command 'pane:show-item-3', => @showItemAtIndex(2)
@command 'pane:show-item-4', => @showItemAtIndex(3)
@command 'pane:show-item-5', => @showItemAtIndex(4)
@command 'pane:show-item-6', => @showItemAtIndex(5)
@command 'pane:show-item-7', => @showItemAtIndex(6)
@command 'pane:show-item-8', => @showItemAtIndex(7)
@command 'pane:show-item-9', => @showItemAtIndex(8)
@command 'pane:split-left', => @splitLeft(@copyActiveItem())
@command 'pane:split-right', => @splitRight(@copyActiveItem())
@command 'pane:split-up', => @splitUp(@copyActiveItem())
@command 'pane:split-down', => @splitDown(@copyActiveItem())
@command 'pane:close', => @destroyItems()
@command 'pane:close-other-items', => @destroyInactiveItems()
@on 'focus', => @activeView?.focus(); false
@on 'focusin', => @makeActive()
# Private: Called by the Serializable mixin during serialization.
serializeParams: ->
items: compact(@items.map((item) -> item.serialize?()))
activeItemUri: @activeItem?.getUri?()
focused: @focused
active: @active
# Private: Called by the Serializable mixin during deserialization.
deserializeParams: (params) ->
params.items = _.compact(params.items.map (itemState) -> atom.deserializers.deserialize(itemState))
{items, activeItemUri} = params
params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri
params
serializeParams: ->
items: _.compact(@items.map (item) -> item.serialize?())
focusOnAttach: @is(':has(:focus)')
activeItemUri: @getActivePaneItem()?.getUri?()
# Private: Called by the view layer to construct a view for this model.
getViewClass: -> PaneView ?= require './pane-view'
isActive: -> @active
# Private: Called by the view layer to indicate that the pane has gained focus.
focus: ->
@focused = true
@activate() unless @isActive()
# Private: Called by the view layer to indicate that the pane has lost focus.
blur: ->
@focused = false
true # if this is called from an event handler, don't cancel it
# Public: Makes this pane the *active* pane, causing it to gain focus
# immediately.
activate: ->
@container?.activePane = this
@emit 'activated'
# Private:
afterAttach: (onDom) ->
if @focusOnAttach and onDom
@focusOnAttach = null
@focus()
getPanes: -> [this]
return if @attached
@attached = true
@trigger 'pane:attached', [this]
# Public: Focus this pane.
makeActive: ->
wasActive = @isActive()
for pane in @getContainer().getPanes() when pane isnt this
pane.makeInactive()
@addClass('active')
@trigger 'pane:became-active' unless wasActive
# Public: Unfocus this pane.
makeInactive: ->
wasActive = @isActive()
@removeClass('active')
@trigger 'pane:became-inactive' if wasActive
# Public: Returns whether this pane is currently focused.
isActive: ->
@getContainer()?.getActivePane() == this
# Public: Returns the next pane, ordered by creation.
getNextPane: ->
panes = @getContainer()?.getPanes()
return unless panes.length > 1
nextIndex = (panes.indexOf(this) + 1) % panes.length
panes[nextIndex]
# Public: Returns all contained views.
# Public:
getItems: ->
new Array(@items...)
# Public: Switches to the next contained item.
showNextItem: =>
index = @getActiveItemIndex()
if index < @items.length - 1
@showItemAtIndex(index + 1)
else
@showItemAtIndex(0)
# Public: Switches to the previous contained item.
showPreviousItem: =>
index = @getActiveItemIndex()
if index > 0
@showItemAtIndex(index - 1)
else
@showItemAtIndex(@items.length - 1)
getActivePaneItem: ->
@activeItem
# Public: Returns the index of the currently active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Public: Switch to the item associated with the given index.
showItemAtIndex: (index) ->
@showItem(@itemAtIndex(index))
@items.slice()
# Public: Returns the item at the specified index.
itemAtIndex: (index) ->
@items[index]
# Public: Focuses the given item.
showItem: (item) ->
return if !item? or item is @activeItem
# Public: Makes the next item active.
activateNextItem: ->
index = @getActiveItemIndex()
if index < @items.length - 1
@activateItemAtIndex(index + 1)
else
@activateItemAtIndex(0)
if @activeItem
@activeItem.off? 'title-changed', @activeItemTitleChanged
# Public: Makes the previous item active.
activatePreviousItem: ->
index = @getActiveItemIndex()
if index > 0
@activateItemAtIndex(index - 1)
else
@activateItemAtIndex(@items.length - 1)
isFocused = @is(':has(:focus)')
@addItem(item)
item.on? 'title-changed', @activeItemTitleChanged
view = @viewForItem(item)
@itemViews.children().not(view).hide()
@itemViews.append(view) unless view.parent().is(@itemViews)
view.show() if @attached
view.focus() if isFocused
@activeItem = item
@activeView = view
@trigger 'pane:active-item-changed', [item]
# Public: Returns the index of the current active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Private:
activeItemTitleChanged: =>
@trigger 'pane:active-item-title-changed'
# Public: Makes the item at the given index active.
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
# Public: Add an additional item at the specified index.
# Public: Makes the given item active, adding the item if necessary.
activateItem: (item) ->
if item?
@addItem(item)
@activeItem = item
# Public: Adds the item to the pane.
#
# * item:
# The item to add. It can be a model with an associated view or a view.
# * index:
# An optional index at which to add the item. If omitted, the item is
# added to the end.
#
# Returns the added item
addItem: (item, index=@getActiveItemIndex() + 1) ->
return if _.include(@items, item)
return if item in @items
@items.splice(index, 0, item)
@trigger 'pane:item-added', [item, index]
@handleItemEvents(item)
@emit 'item-added', item, index
item
handleItemEvents: (item) ->
if _.isFunction(item.on)
@subscribe item, 'destroyed', => @destroyItem(item)
# Private:
removeItem: (item, destroying) ->
index = @items.indexOf(item)
return if index is -1
@activateNextItem() if item is @activeItem and @items.length > 1
@items.splice(index, 1)
@emit 'item-removed', item, index, destroying
@destroy() if @items.length is 0
# Public: Remove the currently active item.
destroyActiveItem: =>
# Public: Moves the given item to the specified index.
moveItem: (item, newIndex) ->
oldIndex = @items.indexOf(item)
@items.splice(oldIndex, 1)
@items.splice(newIndex, 0, item)
@emit 'item-moved', item, newIndex
# Public: Moves the given item to the given index at another pane.
moveItemToPane: (item, pane, index) ->
pane.addItem(item, index)
@removeItem(item)
# Public: Destroys the currently active item and make the next item active.
destroyActiveItem: ->
@destroyItem(@activeItem)
false
# Public: Remove the specified item.
destroyItem: (item, options) ->
@unsubscribe(item) if _.isFunction(item.off)
@trigger 'pane:before-item-destroyed', [item]
# Public: Destroys the given item. If it is the active item, activate the next
# one. If this is the last item, also destroys the pane.
destroyItem: (item) ->
@emit 'before-item-destroyed', item
if @promptToSaveItem(item)
@getContainer()?.itemDestroyed(item)
@removeItem(item, options)
@emit 'item-destroyed', item
@removeItem(item, true)
item.destroy?()
true
else
false
# Public: Remove and delete all items.
# Public: Destroys all items and destroys the pane.
destroyItems: ->
@destroyItem(item) for item in @getItems()
# Public: Remove and delete all but the currently focused item.
# Public: Destroys all items but the active one.
destroyInactiveItems: ->
@destroyItem(item) for item in @getItems() when item isnt @activeItem
# Public: Prompt the user to save the given item.
# Private: Called by model superclass.
destroyed: ->
@container.activateNextPane() if @isActive()
item.destroy?() for item in @items.slice()
# Public: Prompts the user to save the given item if it can be saved and is
# currently unsaved.
promptToSaveItem: (item) ->
return true unless item.shouldPromptToSave?()
@@ -221,15 +202,18 @@ class Pane extends View
when 1 then false
when 2 then true
# Public: Saves the currently focused item.
saveActiveItem: =>
# Public: Saves the active item.
saveActiveItem: ->
@saveItem(@activeItem)
# Public: Save and prompt for path for the currently focused item.
saveActiveItemAs: =>
# Public: Saves the active item at a prompted-for location.
saveActiveItemAs: ->
@saveItemAs(@activeItem)
# Public: Saves the specified item and call the next action when complete.
# Public: Saves the specified item.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
saveItem: (item, nextAction) ->
if item.getUri?()
item.save?()
@@ -237,8 +221,10 @@ class Pane extends View
else
@saveItemAs(item, nextAction)
# Public: Prompts for path and then saves the specified item. Upon completion
# it also calls the next action.
# Public: Saves the given item at a prompted-for location.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
saveItemAs: (item, nextAction) ->
return unless item.saveAs?
@@ -249,164 +235,73 @@ class Pane extends View
item.saveAs(path)
nextAction?()
# Public: Saves all items in this pane.
saveItems: =>
# Public: Saves all items.
saveItems: ->
@saveItem(item) for item in @getItems()
# Public:
removeItem: (item) ->
index = @items.indexOf(item)
@removeItemAtIndex(index) if index >= 0
# Public: Just remove the item at the given index.
removeItemAtIndex: (index) ->
item = @items[index]
@activeItem.off? 'title-changed', @activeItemTitleChanged if item is @activeItem
@showNextItem() if item is @activeItem and @items.length > 1
_.remove(@items, item)
@cleanupItemView(item)
@trigger 'pane:item-removed', [item, index]
# Public: Moves the given item to a the new index.
moveItem: (item, newIndex) ->
oldIndex = @items.indexOf(item)
@items.splice(oldIndex, 1)
@items.splice(newIndex, 0, item)
@trigger 'pane:item-moved', [item, newIndex]
# Public: Moves the given item to another pane.
moveItemToPane: (item, pane, index) ->
@isMovingItem = true
pane.addItem(item, index)
@removeItem(item)
@isMovingItem = false
# Public: Finds the first item that matches the given uri.
# Public: Returns the first item that matches the given URI or undefined if
# none exists.
itemForUri: (uri) ->
_.detect @items, (item) -> item.getUri?() is uri
find @items, (item) -> item.getUri?() is uri
# Public: Focuses the first item that matches the given uri.
showItemForUri: (uri) ->
# Public: Activates the first item that matches the given URI. Returns a
# boolean indicating whether a matching item was found.
activateItemForUri: (uri) ->
if item = @itemForUri(uri)
@showItem(item)
@activateItem(item)
true
else
false
# Private:
cleanupItemView: (item) ->
if item instanceof $
viewToRemove = item
else if viewToRemove = @viewsByItem.get(item)
@viewsByItem.delete(item)
if @items.length > 0
if @isMovingItem and item is viewToRemove
viewToRemove?.detach()
else if @isMovingItem and viewToRemove?.setModel
viewToRemove.setModel(null) # dont want to destroy the model, so set to null
viewToRemove.remove()
else
viewToRemove?.remove()
else
if @isMovingItem and item is viewToRemove
viewToRemove?.detach()
else if @isMovingItem and viewToRemove?.setModel
viewToRemove.setModel(null) # dont want to destroy the model, so set to null
@parent().view().removeChild(this)
# Private:
viewForItem: (item) ->
if item instanceof $
item
else if view = @viewsByItem.get(item)
view
else
viewClass = item.getViewClass()
view = new viewClass(item)
@viewsByItem.set(item, view)
view
# Private:
viewForActiveItem: ->
@viewForItem(@activeItem)
# Private:
adjustDimensions: -> # do nothing
# Private:
horizontalGridUnits: -> 1
# Private:
verticalGridUnits: -> 1
# Public: Creates a new pane above with a copy of the currently focused item.
splitUp: (items...) ->
@split(items, 'column', 'before')
# Public: Creates a new pane below with a copy of the currently focused item.
splitDown: (items...) ->
@split(items, 'column', 'after')
# Public: Creates a new pane left with a copy of the currently focused item.
splitLeft: (items...) ->
@split(items, 'row', 'before')
# Public: Creates a new pane right with a copy of the currently focused item.
splitRight: (items...) ->
@split(items, 'row', 'after')
# Private:
split: (items, axis, side) ->
PaneContainer = require './pane-container'
parent = @parent().view()
unless parent.hasClass(axis)
axis = @buildPaneAxis(axis)
if parent instanceof PaneContainer
@detach()
axis.addChild(this)
parent.setRoot(axis)
else
parent.insertChildBefore(this, axis)
axis.addChild(this)
parent = axis
newPane = new Pane(items...)
switch side
when 'before' then parent.insertChildBefore(this, newPane)
when 'after' then parent.insertChildAfter(this, newPane)
@getContainer().adjustPaneDimensions()
newPane.makeActive()
newPane.focus()
newPane
# Private:
buildPaneAxis: (axis) ->
switch axis
when 'row' then new PaneRow()
when 'column' then new PaneColumn()
# Private:
getContainer: ->
@closest('.panes').view()
# Private:
copyActiveItem: ->
@activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
# Private:
remove: (selector, keepData) ->
return super if keepData
@parent().view().removeChild(this)
# Public: Creates a new pane to the left of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitLeft: (params) ->
@split('horizontal', 'before', params)
# Public: Creates a new pane to the right of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitRight: (params) ->
@split('horizontal', 'after', params)
# Public: Creates a new pane above the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitUp: (params) ->
@split('vertical', 'before', params)
# Public: Creates a new pane below the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitDown: (params) ->
@split('vertical', 'after', params)
# Private:
beforeRemove: ->
if @is(':has(:focus)')
@getContainer().focusNextPane() or atom.workspaceView?.focus()
else if @isActive()
@getContainer().makeNextPaneActive()
split: (orientation, side, params) ->
if @parent.orientation isnt orientation
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
item.destroy?() for item in @getItems()
newPane = new @constructor(extend({focused: true}, params))
switch side
when 'before' then @parent.insertChildBefore(this, newPane)
when 'after' then @parent.insertChildAfter(this, newPane)
newPane.activate()
newPane

View File

@@ -35,7 +35,7 @@ class TokenizedBuffer extends Model
@subscribe @buffer, "changed", (e) => @handleBufferChange(e)
@subscribe @buffer, "path-changed", => @bufferPath = @buffer.getPath()
@subscribe @$tabLength.changes.onValue (tabLength) =>
@subscribe @$tabLength.changes, (tabLength) =>
lastRow = @buffer.getLastRow()
@tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, lastRow)
@invalidateRow(0)

View File

@@ -6,10 +6,10 @@ _ = require 'underscore-plus'
fs = require 'fs-plus'
Serializable = require 'serializable'
EditorView = require './editor-view'
Pane = require './pane'
PaneColumn = require './pane-column'
PaneRow = require './pane-row'
PaneContainer = require './pane-container'
PaneView = require './pane-view'
PaneColumnView = require './pane-column-view'
PaneRowView = require './pane-row-view'
PaneContainerView = require './pane-container-view'
Editor = require './editor'
# Public: The container for the entire Atom application.
@@ -39,7 +39,7 @@ Editor = require './editor'
module.exports =
class WorkspaceView extends View
Serializable.includeInto(this)
atom.deserializers.add(this, Pane, PaneRow, PaneColumn, EditorView)
atom.deserializers.add(this, PaneView, PaneRowView, PaneColumnView, EditorView)
@version: 2
@@ -60,7 +60,7 @@ class WorkspaceView extends View
# Private:
initialize: ({panes, @fullScreen}={}) ->
panes ?= new PaneContainer
panes ?= new PaneContainerView
@panes.replaceWith(panes)
@panes = panes
@@ -168,12 +168,12 @@ class WorkspaceView extends View
Q(editor ? promise)
.then (editor) =>
if not activePane
activePane = new Pane(editor)
activePane = new PaneView(editor)
@panes.setRoot(activePane)
@itemOpened(editor)
activePane.showItem(editor)
activePane.focus() if changeFocus
activePane.activateItem(editor)
activePane.activate() if changeFocus
@trigger "uri-opened"
editor
.catch (error) ->
@@ -200,15 +200,15 @@ class WorkspaceView extends View
else if split == 'left'
pane = @getPanes()[0]
pane.showItem(paneItem)
pane.activateItem(paneItem)
else
paneItem = atom.project.openSync(uri, {initialLine})
pane = new Pane(paneItem)
pane = new PaneView(paneItem)
@panes.setRoot(pane)
@itemOpened(paneItem)
pane.focus() if changeFocus
pane.activate() if changeFocus
paneItem
openSingletonSync: (uri, {changeFocus, initialLine, split}={}) ->
@@ -218,8 +218,8 @@ class WorkspaceView extends View
if pane
paneItem = pane.itemForUri(uri)
pane.showItem(paneItem)
pane.focus() if changeFocus
pane.activateItem(paneItem)
pane.activate() if changeFocus
paneItem
else
@openSync(uri, {changeFocus, initialLine, split})
@@ -290,11 +290,11 @@ class WorkspaceView extends View
appendToRight: (element) ->
@horizontal.append(element)
# Public: Returns the currently focused {Pane}.
# Public: Returns the currently focused {PaneView}.
getActivePane: ->
@panes.getActivePane()
# Public: Returns the currently focused item from within the focused {Pane}
# Public: Returns the currently focused item from within the focused {PaneView}
getActivePaneItem: ->
@panes.getActivePaneItem()
@@ -329,15 +329,15 @@ class WorkspaceView extends View
saveAll: ->
@panes.saveAll()
# Public: Fires a callback on each open {Pane}.
# Public: Fires a callback on each open {PaneView}.
eachPane: (callback) ->
@panes.eachPane(callback)
# Public: Returns an Array of all open {Pane}s.
# Public: Returns an Array of all open {PaneView}s.
getPanes: ->
@panes.getPanes()
# Public: Return the id of the given a {Pane}
# Public: Return the id of the given a {PaneView}
indexOfPane: (pane) ->
@panes.indexOfPane(pane)

View File

@@ -3,8 +3,47 @@
// Pane-items are things that go inside a pane. Like the UI-Demo, the
// settings-view, the archive-view, the image-view. Etc. Basically a non-
// editor resource with a tab.
.pane-item {
overflow: auto;
color: @text-color;
background-color: @pane-item-background-color;
.panes {
display: -webkit-flex;
-webkit-flex: 1;
.pane-column {
display: -webkit-flex;
-webkit-flex: 1;
-webkit-flex-direction: column;
}
.pane-row {
display: -webkit-flex;
-webkit-flex: 1;
-webkit-flex-direction: row;
}
.pane {
position: relative;
display: -webkit-flex;
-webkit-flex: 1;
-webkit-flex-direction: column;
.item-views {
-webkit-flex: 1;
display: -webkit-flex;
min-height: 0;
min-width: 0;
position: relative;
.pane-item {
color: @text-color;
background-color: @pane-item-background-color;
}
> * {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
}
}

View File

@@ -39,49 +39,3 @@ h6 {
-webkit-flex-flow: column;
}
}
.panes {
position: relative;
-webkit-flex: 1;
.column {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow-y: hidden;
}
.row {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow-x: hidden;
margin: 0;
}
.pane {
position: absolute;
display: -webkit-flex;
-webkit-flex-flow: column;
top: 0;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
}
.pane .item-views {
-webkit-flex: 1;
display: -webkit-flex;
min-height: 0;
}
.pane .item-views > * {
-webkit-flex: 1;
min-width: 0;
}
}