diff --git a/.gitmodules b/.gitmodules
index d843b2761..5e7fd303a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -51,7 +51,7 @@
url = https://github.com/mmcgrana/textmate-clojure
[submodule "prebuilt-cef"]
path = prebuilt-cef
- url = git@github.com:github/prebuilt-cef.git
+ url = https://github.com/github/prebuilt-cef
[submodule "vendor/packages/yaml.tmbundle"]
path = vendor/packages/yaml.tmbundle
url = https://github.com/textmate/yaml.tmbundle.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/Rakefile b/Rakefile
index 8e07e69e0..403cc250a 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,7 +3,7 @@ BUILD_DIR = '/tmp/atom-build'
desc "Build Atom via `xcodebuild`"
task :build => "create-xcode-project" do
- command = "xcodebuild -target Atom -configuration Release SYMROOT=#{BUILD_DIR}"
+ command = "xcodebuild -target Atom SYMROOT=#{BUILD_DIR}"
output = `#{command}`
if $?.exitstatus != 0
$stderr.puts "Error #{$?.exitstatus}:\n#{output}"
@@ -64,18 +64,6 @@ task :install => [:clean, :build] do
puts "\033[32mAtom is installed at `#{dest_path}`. Atom cli is installed at `#{cli_path}`\033[0m"
end
-desc "Package up the app for speakeasy"
-task :package => ["setup-codesigning", "build"] do
- path = application_path()
- exit 1 if not path
-
- dest_path = '/tmp/atom-for-speakeasy/Atom.tar.bz2'
- `mkdir -p $(dirname #{dest_path})`
- `rm -rf #{dest_path}`
- `tar --directory $(dirname #{path}) -jcf #{dest_path} $(basename #{path})`
- `open $(dirname #{dest_path})`
-end
-
task "setup-codesigning" do
ENV['CODE_SIGN'] = "Developer ID Application: GitHub"
end
diff --git a/atom.gyp b/atom.gyp
index 93be1d16f..068a571f9 100644
--- a/atom.gyp
+++ b/atom.gyp
@@ -36,7 +36,7 @@
'sources.gypi',
],
'target_defaults': {
- 'default_configuration': 'Debug',
+ 'default_configuration': 'Release',
'configurations': {
'Debug': {
'defines': ['DEBUG=1'],
@@ -71,7 +71,7 @@
'native/mac/speakeasy.pem',
],
'xcode_settings': {
- 'INFOPLIST_FILE': 'native/mac/info.plist',
+ 'INFOPLIST_FILE': 'native/mac/Atom-Info.plist',
'LD_RUNPATH_SEARCH_PATHS': '@executable_path/../Frameworks',
},
'conditions': [
@@ -178,6 +178,12 @@
'Atom',
],
},
+ {
+ 'postbuild_name': 'Print env for Constructicon',
+ 'action': [
+ 'env',
+ ],
+ },
],
'link_settings': {
'libraries': [
diff --git a/benchmark/benchmark-suite.coffee b/benchmark/benchmark-suite.coffee
index d77958db7..c0d142a6d 100644
--- a/benchmark/benchmark-suite.coffee
+++ b/benchmark/benchmark-suite.coffee
@@ -107,7 +107,7 @@ describe "TokenizedBuffer.", ->
[languageMode, buffer] = []
beforeEach ->
- editSession = benchmarkFixturesProject.buildEditSessionForPath('medium.coffee')
+ editSession = benchmarkFixturesProject.buildEditSession('medium.coffee')
{ languageMode, buffer } = editSession
benchmark "construction", 20, ->
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 969272d2f..507b6c653 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -157,10 +157,10 @@ its own namespace.
- hideGitIgnoredFiles: Whether files in the .gitignore should be hidden
- ignoredNames: File names to ignore across all of atom (not fully implemented)
- themes: An array of theme names to load, in cascading order
+ - autosave: Save a resource when its view loses focus
- editor
- autoIndent: Enable/disable basic auto-indent (defaults to true)
- autoIndentOnPaste: Enable/disable auto-indented pasted text (defaults to false)
- - autosave: Save a file when an editor loses focus
- nonWordCharacters: A string of non-word characters to define word boundaries
- fontSize
- fontFamily
diff --git a/docs/internals/configuration.md b/docs/internals/configuration.md
index c25b0155d..d1df1d358 100644
--- a/docs/internals/configuration.md
+++ b/docs/internals/configuration.md
@@ -7,7 +7,7 @@ read config settings. You can read a value from `config` with `config.get`:
```coffeescript
# read a value with `config.get`
-@autosave() if config.get "editor.autosave"
+@autosave() if config.get "core.autosave"
```
Or you can use `observeConfig` to track changes from a view object.
@@ -47,7 +47,7 @@ the following way:
```coffeescript
# basic key update
-config.set("editor.autosave", true)
+config.set("core.autosave", true)
# if you mutate a config key, you'll need to call `config.update` to inform
# observers of the change
diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm
index 5e0375a35..7a74fdf22 100644
--- a/native/atom_window_controller.mm
+++ b/native/atom_window_controller.mm
@@ -46,6 +46,11 @@
if (alwaysUseBundleResourcePath || !_resourcePath) {
_resourcePath = [[NSBundle bundleForClass:self.class] resourcePath];
}
+
+ if ([self isDevMode]) {
+ [self displayDevIcon];
+ }
+
_resourcePath = [_resourcePath stringByStandardizingPath];
[_resourcePath retain];
@@ -72,7 +77,6 @@
- (id)initDevWithPath:(NSString *)path {
_pathToOpen = [path retain];
- AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication];
return [self initWithBootstrapScript:@"window-bootstrap" background:NO alwaysUseBundleResourcePath:false];
}
@@ -127,6 +131,8 @@
[urlString appendString:[[url URLByAppendingPathComponent:@"static/index.html"] absoluteString]];
[urlString appendFormat:@"?bootstrapScript=%@", [self encodeUrlParam:_bootstrapScript]];
[urlString appendFormat:@"&resourcePath=%@", [self encodeUrlParam:_resourcePath]];
+ if ([self isDevMode])
+ [urlString appendFormat:@"&devMode=1"];
if (_exitWhenDone)
[urlString appendString:@"&exitWhenDone=1"];
if (_pathToOpen)
@@ -205,6 +211,33 @@
return YES;
}
+- (bool)isDevMode {
+ NSString *bundleResourcePath = [[NSBundle bundleForClass:self.class] resourcePath];
+ return ![_resourcePath isEqualToString:bundleResourcePath];
+}
+
+- (void)displayDevIcon {
+ NSView *themeFrame = [self.window.contentView superview];
+ NSButton *fullScreenButton = nil;
+ for (NSView *view in themeFrame.subviews) {
+ if (![view isKindOfClass:NSButton.class]) continue;
+ NSButton *button = (NSButton *)view;
+ if (button.action != @selector(toggleFullScreen:)) continue;
+ fullScreenButton = button;
+ break;
+ }
+
+ NSButton *devButton = [[NSButton alloc] init];
+ [devButton setTitle:@"\xF0\x9F\x92\x80"];
+ devButton.autoresizingMask = NSViewMinXMargin | NSViewMinYMargin;
+ devButton.buttonType = NSMomentaryChangeButton;
+ devButton.bordered = NO;
+ [devButton sizeToFit];
+ devButton.frame = NSMakeRect(fullScreenButton.frame.origin.x - devButton.frame.size.width - 5, fullScreenButton.frame.origin.y, devButton.frame.size.width, devButton.frame.size.height);
+
+ [[self.window.contentView superview] addSubview:devButton];
+}
+
- (void)populateBrowserSettings:(CefBrowserSettings &)settings {
CefString(&settings.default_encoding) = "UTF-8";
settings.remote_fonts = STATE_ENABLED;
diff --git a/native/mac/info.plist b/native/mac/Atom-Info.plist
similarity index 100%
rename from native/mac/info.plist
rename to native/mac/Atom-Info.plist
diff --git a/native/v8_extensions/git.mm b/native/v8_extensions/git.mm
index 36229345e..49aa98bf1 100644
--- a/native/v8_extensions/git.mm
+++ b/native/v8_extensions/git.mm
@@ -106,7 +106,7 @@ namespace v8_extensions {
return;
int shortNameLength = branchNameLength - 11;
- char* shortName = (char*) malloc(sizeof(char) * shortNameLength + 1);
+ char* shortName = (char*) malloc(sizeof(char) * (shortNameLength + 1));
shortName[shortNameLength] = '\0';
strncpy(shortName, &branchName[11], shortNameLength);
*out = shortName;
@@ -122,15 +122,18 @@ namespace v8_extensions {
return;
int shortBranchNameLength = strlen(shortBranchName);
- char* remoteKey = (char*) malloc(sizeof(char) * shortBranchNameLength + 15);
+ char* remoteKey = (char*) malloc(sizeof(char) * (shortBranchNameLength + 15));
sprintf(remoteKey, "branch.%s.remote", shortBranchName);
- char* mergeKey = (char*) malloc(sizeof(char) * shortBranchNameLength + 14);
+ char* mergeKey = (char*) malloc(sizeof(char) * (shortBranchNameLength + 14));
sprintf(mergeKey, "branch.%s.merge", shortBranchName);
free((char*)shortBranchName);
git_config *config;
- if (git_repository_config(&config, repo) != GIT_OK)
+ if (git_repository_config(&config, repo) != GIT_OK) {
+ free(remoteKey);
+ free(mergeKey);
return;
+ }
const char *remote;
const char *merge;
diff --git a/script/compile-coffee b/script/compile-coffee
index 298d9f53c..f3d19392c 100755
--- a/script/compile-coffee
+++ b/script/compile-coffee
@@ -6,7 +6,14 @@ set -e
# The Setup's environment ourselves. If this isn't done, things like the
# node shim won't be able to find the stuff they need.
-node --version > /dev/null 2>&1 || source /opt/github/env.sh
+node --version > /dev/null 2>&1 || {
+ if [ -e /opt/github/env.sh ]; then
+ source /opt/github/env.sh
+ else
+ # Try Constructicon's PATH.
+ export PATH="/usr/local/Cellar/node/0.8.21/bin:${PATH}"
+ fi
+}
INPUT_FILE="${1}"
OUTPUT_FILE="${2}"
diff --git a/script/compile-cson b/script/compile-cson
index bc3a79ff3..7168fd19d 100755
--- a/script/compile-cson
+++ b/script/compile-cson
@@ -6,7 +6,14 @@ set -e
# The Setup's environment ourselves. If this isn't done, things like the
# node shim won't be able to find the stuff they need.
-node --version > /dev/null 2>&1 || source /opt/github/env.sh
+node --version > /dev/null 2>&1 || {
+ if [ -e /opt/github/env.sh ]; then
+ source /opt/github/env.sh
+ else
+ # Try Constructicon's PATH.
+ export PATH="/usr/local/Cellar/node/0.8.21/bin:${PATH}"
+ fi
+}
INPUT_FILE="${1}"
OUTPUT_FILE="${2}"
diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild
new file mode 100755
index 000000000..734ca7c58
--- /dev/null
+++ b/script/constructicon/prebuild
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -ex
+
+cd "$(dirname "$0")/../.."
+
+export PATH="/usr/local/Cellar/node/0.8.21/bin:/usr/local/bin:${PATH}"
+
+rake setup-codesigning create-xcode-project
diff --git a/spec/app/atom-package-spec.coffee b/spec/app/atom-package-spec.coffee
index 067f2e0e4..7b75b8ede 100644
--- a/spec/app/atom-package-spec.coffee
+++ b/spec/app/atom-package-spec.coffee
@@ -23,7 +23,7 @@ describe "AtomPackage", ->
it "triggers the activation event on all handlers registered during activation", ->
rootView.open('sample.js')
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
eventHandler = jasmine.createSpy("activation-event")
editor.command 'activation-event', eventHandler
editor.trigger 'activation-event'
diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee
index 261c86d4f..e614a2ff7 100644
--- a/spec/app/atom-spec.coffee
+++ b/spec/app/atom-spec.coffee
@@ -141,3 +141,74 @@ describe "the `atom` global", ->
runs ->
expect(versionHandler.argsForCall[0][0]).toMatch /^\d+\.\d+(\.\d+)?$/
+
+ describe "modal native dialogs", ->
+ beforeEach ->
+ spyOn(atom, 'sendMessageToBrowserProcess')
+ atom.sendMessageToBrowserProcess.simulateConfirmation = (buttonText) ->
+ labels = @argsForCall[0][1][2...]
+ callbacks = @argsForCall[0][2]
+ @reset()
+ callbacks[labels.indexOf(buttonText)]()
+ advanceClock 50
+
+ atom.sendMessageToBrowserProcess.simulatePathSelection = (path) ->
+ callback = @argsForCall[0][2]
+ @reset()
+ callback(path)
+ advanceClock 50
+
+ it "only presents one native dialog at a time", ->
+ confirmHandler = jasmine.createSpy("confirmHandler")
+ selectPathHandler = jasmine.createSpy("selectPathHandler")
+
+ atom.confirm "Are you happy?", "really, truly happy?", "Yes", confirmHandler, "No"
+ atom.confirm "Are you happy?", "really, truly happy?", "Yes", confirmHandler, "No"
+ atom.showSaveDialog(selectPathHandler)
+ atom.showSaveDialog(selectPathHandler)
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ atom.sendMessageToBrowserProcess.simulateConfirmation("Yes")
+ expect(confirmHandler).toHaveBeenCalled()
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ atom.sendMessageToBrowserProcess.simulateConfirmation("No")
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ atom.sendMessageToBrowserProcess.simulatePathSelection('/selected/path')
+ expect(selectPathHandler).toHaveBeenCalledWith('/selected/path')
+ selectPathHandler.reset()
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+
+ it "prioritizes dialogs presented as the result of dismissing other dialogs before any previously deferred dialogs", ->
+ atom.confirm "A1", "", "Next", ->
+ atom.confirm "B1", "", "Next", ->
+ atom.confirm "C1", "", "Next", ->
+ atom.confirm "C2", "", "Next", ->
+ atom.confirm "B2", "", "Next", ->
+ atom.confirm "A2", "", "Next", ->
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A1"
+ atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "B1"
+ atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "C1"
+ atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "C2"
+ atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "B2"
+ atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
+
+ expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
+ expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A2"
+ atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee
index 3510d46f4..84b58ddeb 100644
--- a/spec/app/buffer-spec.coffee
+++ b/spec/app/buffer-spec.coffee
@@ -180,24 +180,88 @@ describe 'Buffer', ->
waitsFor 'change event', ->
changeHandler.callCount > 0
- describe ".isModified()", ->
- it "returns true when user changes buffer", ->
+ describe "modified status", ->
+ it "reports the modified status changing to true or false after the user changes buffer", ->
+ modifiedHandler = jasmine.createSpy("modifiedHandler")
+ buffer.on 'modified-status-changed', modifiedHandler
+
expect(buffer.isModified()).toBeFalsy()
buffer.insert([0,0], "hi")
expect(buffer.isModified()).toBe true
- it "returns false after modified buffer is saved", ->
+ advanceClock(buffer.stoppedChangingDelay)
+ expect(modifiedHandler).toHaveBeenCalledWith(true)
+
+ modifiedHandler.reset()
+ buffer.insert([0,2], "ho")
+ advanceClock(buffer.stoppedChangingDelay)
+ expect(modifiedHandler).not.toHaveBeenCalled()
+
+ modifiedHandler.reset()
+ buffer.undo()
+ buffer.undo()
+ advanceClock(buffer.stoppedChangingDelay)
+ expect(modifiedHandler).toHaveBeenCalledWith(false)
+
+ it "reports the modified status changing to true after the underlying file is deleted", ->
+ buffer.release()
+ filePath = "/tmp/atom-tmp-file"
+ fs.write(filePath, 'delete me')
+ buffer = new Buffer(filePath)
+ modifiedHandler = jasmine.createSpy("modifiedHandler")
+ buffer.on 'modified-status-changed', modifiedHandler
+
+ fs.remove(filePath)
+
+ waitsFor "modified status to change", -> modifiedHandler.callCount
+ runs -> expect(buffer.isModified()).toBe true
+
+ it "reports the modified status changing to false after a modified buffer is saved", ->
filePath = "/tmp/atom-tmp-file"
fs.write(filePath, '')
buffer.release()
buffer = new Buffer(filePath)
- expect(buffer.isModified()).toBe false
+ modifiedHandler = jasmine.createSpy("modifiedHandler")
+ buffer.on 'modified-status-changed', modifiedHandler
buffer.insert([0,0], "hi")
+ advanceClock(buffer.stoppedChangingDelay)
expect(buffer.isModified()).toBe true
+ modifiedHandler.reset()
buffer.save()
+
+ expect(modifiedHandler).toHaveBeenCalledWith(false)
expect(buffer.isModified()).toBe false
+ modifiedHandler.reset()
+
+ buffer.insert([0, 0], 'x')
+ advanceClock(buffer.stoppedChangingDelay)
+ expect(modifiedHandler).toHaveBeenCalledWith(true)
+ expect(buffer.isModified()).toBe true
+
+ it "reports the modified status changing to false after a modified buffer is reloaded", ->
+ filePath = "/tmp/atom-tmp-file"
+ fs.write(filePath, '')
+ buffer.release()
+ buffer = new Buffer(filePath)
+ modifiedHandler = jasmine.createSpy("modifiedHandler")
+ buffer.on 'modified-status-changed', modifiedHandler
+
+ buffer.insert([0,0], "hi")
+ advanceClock(buffer.stoppedChangingDelay)
+ expect(buffer.isModified()).toBe true
+ modifiedHandler.reset()
+
+ buffer.reload()
+ expect(modifiedHandler).toHaveBeenCalledWith(false)
+ expect(buffer.isModified()).toBe false
+ modifiedHandler.reset()
+
+ buffer.insert([0, 0], 'x')
+ advanceClock(buffer.stoppedChangingDelay)
+ expect(modifiedHandler).toHaveBeenCalledWith(true)
+ expect(buffer.isModified()).toBe true
it "returns false for an empty buffer with no path", ->
buffer.release()
@@ -1056,61 +1120,30 @@ describe 'Buffer', ->
expect(buffer.isEmpty()).toBeFalsy()
describe "'contents-modified' event", ->
- describe "when the buffer is deleted", ->
- it "triggers the contents-modified event", ->
- delay = buffer.stoppedChangingDelay
- path = "/tmp/atom-file-to-delete.txt"
- fs.write(path, 'delete me')
- bufferToDelete = new Buffer(path)
- contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
- bufferToDelete.on 'contents-modified', contentsModifiedHandler
+ it "triggers the 'contents-modified' event with the current modified status when the buffer changes, rate-limiting events with a delay", ->
+ delay = buffer.stoppedChangingDelay
+ contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
+ buffer.on 'contents-modified', contentsModifiedHandler
- expect(bufferToDelete.getPath()).toBe path
- expect(bufferToDelete.isModified()).toBeFalsy()
- expect(contentsModifiedHandler).not.toHaveBeenCalled()
+ buffer.insert([0, 0], 'a')
+ expect(contentsModifiedHandler).not.toHaveBeenCalled()
- removeHandler = jasmine.createSpy('removeHandler')
- bufferToDelete.file.on 'removed', removeHandler
- fs.remove(path)
- waitsFor "file to be removed", ->
- removeHandler.callCount > 0
+ advanceClock(delay / 2)
- runs ->
- expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:true)
- bufferToDelete.destroy()
+ buffer.insert([0, 0], 'b')
+ expect(contentsModifiedHandler).not.toHaveBeenCalled()
- describe "when the buffer text has been changed", ->
- it "triggers the contents-modified event 'stoppedChangingDelay' ms after the last buffer change", ->
- delay = buffer.stoppedChangingDelay
- contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
- buffer.on 'contents-modified', contentsModifiedHandler
+ advanceClock(delay / 2)
+ expect(contentsModifiedHandler).not.toHaveBeenCalled()
- buffer.insert([0, 0], 'a')
- expect(contentsModifiedHandler).not.toHaveBeenCalled()
+ advanceClock(delay / 2)
+ expect(contentsModifiedHandler).toHaveBeenCalledWith(true)
- advanceClock(delay / 2)
-
- buffer.insert([0, 0], 'b')
- expect(contentsModifiedHandler).not.toHaveBeenCalled()
-
- advanceClock(delay / 2)
- expect(contentsModifiedHandler).not.toHaveBeenCalled()
-
- advanceClock(delay / 2)
- expect(contentsModifiedHandler).toHaveBeenCalled()
-
- it "triggers the contents-modified event with data about whether its contents differ from the contents on disk", ->
- delay = buffer.stoppedChangingDelay
- contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
- buffer.on 'contents-modified', contentsModifiedHandler
-
- buffer.insert([0, 0], 'a')
- advanceClock(delay)
- expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:true)
-
- buffer.delete([[0, 0], [0, 1]], '')
- advanceClock(delay)
- expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:false)
+ contentsModifiedHandler.reset()
+ buffer.undo()
+ buffer.undo()
+ advanceClock(delay)
+ expect(contentsModifiedHandler).toHaveBeenCalledWith(false)
describe ".append(text)", ->
it "adds text to the end of the buffer", ->
diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee
index fe5dc83f7..2381d76e2 100644
--- a/spec/app/display-buffer-spec.coffee
+++ b/spec/app/display-buffer-spec.coffee
@@ -6,7 +6,7 @@ describe "DisplayBuffer", ->
[editSession, displayBuffer, buffer, changeHandler, tabLength] = []
beforeEach ->
tabLength = 2
- editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength })
+ editSession = project.buildEditSession('sample.js', { tabLength })
{ buffer, displayBuffer } = editSession
changeHandler = jasmine.createSpy 'changeHandler'
displayBuffer.on 'changed', changeHandler
@@ -228,7 +228,7 @@ describe "DisplayBuffer", ->
editSession2 = null
beforeEach ->
- editSession2 = fixturesProject.buildEditSessionForPath('two-hundred.txt')
+ editSession2 = project.buildEditSession('two-hundred.txt')
{ buffer, displayBuffer } = editSession2
displayBuffer.on 'changed', changeHandler
diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee
index fce910c73..1a4d3754d 100644
--- a/spec/app/edit-session-spec.coffee
+++ b/spec/app/edit-session-spec.coffee
@@ -9,12 +9,30 @@ describe "EditSession", ->
buffer.setText(buffer.getText().replace(/[ ]{2}/g, "\t"))
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
+ editSession = project.buildEditSession('sample.js', autoIndent: false)
buffer = editSession.buffer
lineLengths = buffer.getLines().map (line) -> line.length
- afterEach ->
- fixturesProject.destroy()
+ describe "title", ->
+ describe ".getTitle()", ->
+ it "uses the basename of the buffer's path as its title, or 'untitled' if the path is undefined", ->
+ expect(editSession.getTitle()).toBe 'sample.js'
+ buffer.setPath(undefined)
+ expect(editSession.getTitle()).toBe 'untitled'
+
+ describe ".getLongTitle()", ->
+ it "appends the name of the containing directory to the basename of the file", ->
+ expect(editSession.getLongTitle()).toBe 'sample.js - fixtures'
+ buffer.setPath(undefined)
+ expect(editSession.getLongTitle()).toBe 'untitled'
+
+ it "emits 'title-changed' events when the underlying buffer path", ->
+ titleChangedHandler = jasmine.createSpy("titleChangedHandler")
+ editSession.on 'title-changed', titleChangedHandler
+
+ buffer.setPath('/foo/bar/baz.txt')
+ buffer.setPath(undefined)
+ expect(titleChangedHandler.callCount).toBe 2
describe "cursor", ->
describe ".getCursor()", ->
@@ -1715,7 +1733,7 @@ describe "EditSession", ->
it "does not explode if the current language mode has no comment regex", ->
editSession.destroy()
- editSession = fixturesProject.buildEditSessionForPath(null, autoIndent: false)
+ editSession = project.buildEditSession(null, autoIndent: false)
editSession.setSelectedBufferRange([[4, 5], [4, 5]])
editSession.toggleLineCommentsInSelection()
expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
@@ -1793,7 +1811,7 @@ describe "EditSession", ->
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
it "restores selected ranges even when the change occurred in another edit session", ->
- otherEditSession = fixturesProject.buildEditSessionForPath(editSession.getPath())
+ otherEditSession = project.buildEditSession(editSession.getPath())
otherEditSession.setSelectedBufferRange([[2, 2], [3, 3]])
otherEditSession.delete()
@@ -1986,13 +2004,13 @@ describe "EditSession", ->
describe "soft-tabs detection", ->
it "assign soft / hard tabs based on the contents of the buffer, or uses the default if unknown", ->
- editSession = fixturesProject.buildEditSessionForPath('sample.js', softTabs: false)
+ editSession = project.buildEditSession('sample.js', softTabs: false)
expect(editSession.softTabs).toBeTruthy()
- editSession = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', softTabs: true)
+ editSession = project.buildEditSession('sample-with-tabs.coffee', softTabs: true)
expect(editSession.softTabs).toBeFalsy()
- editSession = fixturesProject.buildEditSessionForPath(null, softTabs: false)
+ editSession = project.buildEditSession(null, softTabs: false)
expect(editSession.softTabs).toBeFalsy()
describe ".indentLevelForLine(line)", ->
diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee
index 0d46cfba9..04e6c9b57 100644
--- a/spec/app/editor-spec.coffee
+++ b/spec/app/editor-spec.coffee
@@ -1,4 +1,3 @@
-RootView = require 'root-view'
EditSession = require 'edit-session'
Buffer = require 'buffer'
Editor = require 'editor'
@@ -10,75 +9,41 @@ _ = require 'underscore'
fs = require 'fs'
describe "Editor", ->
- [buffer, editor, cachedLineHeight] = []
+ [buffer, editor, editSession, cachedLineHeight, cachedCharWidth] = []
+
+ beforeEach ->
+ editSession = project.buildEditSession('sample.js')
+ buffer = editSession.buffer
+ editor = new Editor(editSession)
+ editor.lineOverdraw = 2
+ editor.isFocused = true
+ editor.enableKeymap()
+ editor.attachToDom = ({ heightInLines, widthInChars } = {}) ->
+ heightInLines ?= this.getBuffer().getLineCount()
+ this.height(getLineHeight() * heightInLines)
+ this.width(getCharWidth() * widthInChars) if widthInChars
+ $('#jasmine-content').append(this)
getLineHeight = ->
return cachedLineHeight if cachedLineHeight?
- editorForMeasurement = new Editor(editSession: project.buildEditSessionForPath('sample.js'))
- editorForMeasurement.attachToDom()
- cachedLineHeight = editorForMeasurement.lineHeight
- editorForMeasurement.remove()
+ calcDimensions()
cachedLineHeight
- beforeEach ->
- window.rootView = new RootView
- rootView.open('sample.js')
- editor = rootView.getActiveEditor()
- buffer = editor.getBuffer()
+ getCharWidth = ->
+ return cachedCharWidth if cachedCharWidth?
+ calcDimensions()
+ cachedCharWidth
- editor.attachToDom = ({ heightInLines } = {}) ->
- heightInLines ?= this.getBuffer().getLineCount()
- this.height(getLineHeight() * heightInLines)
- $('#jasmine-content').append(this)
-
- editor.lineOverdraw = 2
- editor.enableKeymap()
- editor.isFocused = true
+ calcDimensions = ->
+ editorForMeasurement = new Editor(editSession: project.buildEditSession('sample.js'))
+ editorForMeasurement.attachToDom()
+ cachedLineHeight = editorForMeasurement.lineHeight
+ cachedCharWidth = editorForMeasurement.charWidth
+ editorForMeasurement.remove()
describe "construction", ->
- it "throws an error if no editor session is given unless deserializing", ->
+ it "throws an error if no edit session is given", ->
expect(-> new Editor).toThrow()
- expect(-> new Editor(deserializing: true)).not.toThrow()
-
- describe ".copy()", ->
- it "builds a new editor with the same edit sessions, cursor position, and scroll position as the receiver", ->
- rootView.attachToDom()
- rootView.height(8 * editor.lineHeight)
- rootView.width(50 * editor.charWidth)
-
- editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
- editor.setCursorScreenPosition([5, 1])
- editor.scrollTop(1.5 * editor.lineHeight)
- editor.scrollView.scrollLeft(44)
-
- # proves this test covers serialization and deserialization
- spyOn(editor, 'serialize').andCallThrough()
- spyOn(Editor, 'deserialize').andCallThrough()
-
- newEditor = editor.copy()
- expect(editor.serialize).toHaveBeenCalled()
- expect(Editor.deserialize).toHaveBeenCalled()
-
- expect(newEditor.getBuffer()).toBe editor.getBuffer()
- expect(newEditor.getCursorScreenPosition()).toEqual editor.getCursorScreenPosition()
- expect(newEditor.editSessions).toEqual(editor.editSessions)
- expect(newEditor.activeEditSession).toEqual(editor.activeEditSession)
- expect(newEditor.getActiveEditSessionIndex()).toEqual(editor.getActiveEditSessionIndex())
-
- newEditor.height(editor.height())
- newEditor.width(editor.width())
-
- newEditor.attachToDom()
- expect(newEditor.scrollTop()).toBe editor.scrollTop()
- expect(newEditor.scrollView.scrollLeft()).toBe 44
-
- it "does not blow up if no file exists for a previous edit session, but prints a warning", ->
- spyOn(console, 'warn')
- fs.write('/tmp/delete-me')
- editor.edit(project.buildEditSessionForPath('/tmp/delete-me'))
- fs.remove('/tmp/delete-me')
- newEditor = editor.copy()
- expect(console.warn).toHaveBeenCalled()
describe "when the editor is attached to the dom", ->
it "calculates line height and char width and updates the pixel position of the cursor", ->
@@ -117,7 +82,7 @@ describe "Editor", ->
it "triggers an alert", ->
path = "/tmp/atom-changed-file.txt"
fs.write(path, "")
- editSession = project.buildEditSessionForPath(path)
+ editSession = project.buildEditSession(path)
editor.edit(editSession)
editor.insertText("now the buffer is modified")
@@ -135,274 +100,66 @@ describe "Editor", ->
expect(atom.confirm).toHaveBeenCalled()
describe ".remove()", ->
- it "removes subscriptions from all edit session buffers", ->
- editSession1 = editor.activeEditSession
- subscriberCount1 = editSession1.buffer.subscriptionCount()
- editSession2 = project.buildEditSessionForPath(project.resolve('sample.txt'))
- expect(subscriberCount1).toBeGreaterThan 1
-
- editor.edit(editSession2)
- subscriberCount2 = editSession2.buffer.subscriptionCount()
- expect(subscriberCount2).toBeGreaterThan 1
-
+ it "destroys the edit session", ->
editor.remove()
- expect(editSession1.buffer.subscriptionCount()).toBeLessThan subscriberCount1
- expect(editSession2.buffer.subscriptionCount()).toBeLessThan subscriberCount2
-
- describe "when 'close' is triggered", ->
- it "adds a closed session path to the array", ->
- editor.edit(project.buildEditSessionForPath())
- editSession = editor.activeEditSession
- expect(editor.closedEditSessions.length).toBe 0
- editor.trigger "core:close"
- expect(editor.closedEditSessions.length).toBe 0
- editor.edit(project.buildEditSessionForPath(project.resolve('sample.txt')))
- editor.trigger "core:close"
- expect(editor.closedEditSessions.length).toBe 1
-
- it "closes the active edit session and loads next edit session", ->
- editor.edit(project.buildEditSessionForPath())
- editSession = editor.activeEditSession
- spyOn(editSession.buffer, 'isModified').andReturn false
- spyOn(editSession, 'destroy').andCallThrough()
- spyOn(editor, "remove").andCallThrough()
- editor.trigger "core:close"
- expect(editSession.destroy).toHaveBeenCalled()
- expect(editor.remove).not.toHaveBeenCalled()
- expect(editor.getBuffer()).toBe buffer
-
- it "triggers the 'editor:edit-session-removed' event with the edit session and its former index", ->
- editor.edit(project.buildEditSessionForPath())
- editSession = editor.activeEditSession
- index = editor.getActiveEditSessionIndex()
- spyOn(editSession.buffer, 'isModified').andReturn false
-
- editSessionRemovedHandler = jasmine.createSpy('editSessionRemovedHandler')
- editor.on 'editor:edit-session-removed', editSessionRemovedHandler
- editor.trigger "core:close"
-
- expect(editSessionRemovedHandler).toHaveBeenCalled()
- expect(editSessionRemovedHandler.argsForCall[0][1..2]).toEqual [editSession, index]
-
- it "calls remove on the editor if there is one edit session and mini is false", ->
- editSession = editor.activeEditSession
- expect(editor.mini).toBeFalsy()
- expect(editor.editSessions.length).toBe 1
- spyOn(editor, 'remove').andCallThrough()
- editor.trigger 'core:close'
- spyOn(editSession, 'destroy').andCallThrough()
- expect(editor.remove).toHaveBeenCalled()
-
- miniEditor = new Editor(mini: true)
- spyOn(miniEditor, 'remove').andCallThrough()
- miniEditor.trigger 'core:close'
- expect(miniEditor.remove).not.toHaveBeenCalled()
-
- describe "when buffer is modified", ->
- it "triggers an alert and does not close the session", ->
- spyOn(editor, 'remove').andCallThrough()
- spyOn(atom, 'confirm')
- editor.insertText("I AM CHANGED!")
- editor.trigger "core:close"
- expect(editor.remove).not.toHaveBeenCalled()
- expect(atom.confirm).toHaveBeenCalled()
-
- it "doesn't trigger an alert if the buffer is opened in multiple sessions", ->
- spyOn(editor, 'remove').andCallThrough()
- spyOn(atom, 'confirm')
- editor.insertText("I AM CHANGED!")
- editor.splitLeft()
- editor.trigger "core:close"
- expect(editor.remove).toHaveBeenCalled()
- expect(atom.confirm).not.toHaveBeenCalled()
+ expect(editor.activeEditSession.destroyed).toBeTruthy()
describe ".edit(editSession)", ->
- otherEditSession = null
+ [newEditSession, newBuffer] = []
beforeEach ->
- otherEditSession = project.buildEditSessionForPath()
+ newEditSession = project.buildEditSession('two-hundred.txt')
+ newBuffer = newEditSession.buffer
- describe "when the edit session wasn't previously assigned to this editor", ->
- it "adds edit session to editor and triggers the 'editor:edit-session-added' event", ->
- editSessionAddedHandler = jasmine.createSpy('editSessionAddedHandler')
- editor.on 'editor:edit-session-added', editSessionAddedHandler
+ it "updates the rendered lines, cursors, selections, scroll position, and event subscriptions to match the given edit session", ->
+ editor.attachToDom(heightInLines: 5, widthInChars: 30)
+ editor.setCursorBufferPosition([3, 5])
+ editor.scrollToBottom()
+ editor.scrollView.scrollLeft(150)
+ previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
+ previousScrollTop = editor.scrollTop()
+ previousScrollLeft = editor.scrollView.scrollLeft()
- originalEditSessionCount = editor.editSessions.length
- editor.edit(otherEditSession)
- expect(editor.activeEditSession).toBe otherEditSession
- expect(editor.editSessions.length).toBe originalEditSessionCount + 1
+ newEditSession.scrollTop = 120
+ newEditSession.setSelectedBufferRange([[40, 0], [43, 1]])
- expect(editSessionAddedHandler).toHaveBeenCalled()
- expect(editSessionAddedHandler.argsForCall[0][1..2]).toEqual [otherEditSession, originalEditSessionCount]
+ editor.edit(newEditSession)
+ { firstRenderedScreenRow, lastRenderedScreenRow } = editor
+ expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe newBuffer.lineForRow(firstRenderedScreenRow)
+ expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe newBuffer.lineForRow(editor.lastRenderedScreenRow)
+ expect(editor.scrollTop()).toBe 120
+ expect(editor.getSelectionView().regions[0].position().top).toBe 40 * editor.lineHeight
+ editor.insertText("hello")
+ expect(editor.lineElementForScreenRow(40).text()).toBe "hello3"
- describe "when the edit session was previously assigned to this editor", ->
- it "restores the previous edit session associated with the editor", ->
- previousEditSession = editor.activeEditSession
+ editor.edit(editSession)
+ { firstRenderedScreenRow, lastRenderedScreenRow } = editor
+ expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe buffer.lineForRow(firstRenderedScreenRow)
+ expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe buffer.lineForRow(editor.lastRenderedScreenRow)
+ expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
+ expect(editor.scrollTop()).toBe previousScrollTop
+ expect(editor.scrollView.scrollLeft()).toBe previousScrollLeft
+ expect(editor.getCursorView().position()).toEqual { top: 3 * editor.lineHeight, left: 5 * editor.charWidth }
+ editor.insertText("goodbye")
+ expect(editor.lineElementForScreenRow(3).text()).toMatch /^ vgoodbyear/
- editor.edit(otherEditSession)
- expect(editor.activeEditSession).not.toBe previousEditSession
+ it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
+ path = "/tmp/atom-changed-file.txt"
+ fs.write(path, "")
+ tempEditSession = project.buildEditSession(path)
+ editor.edit(tempEditSession)
+ tempEditSession.insertText("a buffer change")
- editor.edit(previousEditSession)
- expect(editor.activeEditSession).toBe previousEditSession
+ spyOn(atom, "confirm")
- it "handles buffer manipulation correctly after switching to a new edit session", ->
- editor.attachToDom()
- editor.insertText("abc\n")
- expect(editor.lineElementForScreenRow(0).text()).toBe 'abc'
+ contentsConflictedHandler = jasmine.createSpy("contentsConflictedHandler")
+ tempEditSession.on 'contents-conflicted', contentsConflictedHandler
+ fs.write(path, "a file change")
+ waitsFor ->
+ contentsConflictedHandler.callCount > 0
- editor.edit(otherEditSession)
- expect(editor.lineElementForScreenRow(0).html()).toBe ' '
-
- editor.insertText("def\n")
- expect(editor.lineElementForScreenRow(0).text()).toBe 'def'
-
- it "removes the opened session from the closed sessions array", ->
- editor.edit(project.buildEditSessionForPath('sample.txt'))
- expect(editor.closedEditSessions.length).toBe 0
- editor.trigger "core:close"
- expect(editor.closedEditSessions.length).toBe 1
- editor.edit(project.buildEditSessionForPath('sample.txt'))
- expect(editor.closedEditSessions.length).toBe 0
-
- describe "switching edit sessions", ->
- [session0, session1, session2] = []
-
- beforeEach ->
- session0 = editor.activeEditSession
-
- editor.edit(project.buildEditSessionForPath('sample.txt'))
- session1 = editor.activeEditSession
-
- editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
- session2 = editor.activeEditSession
-
- describe ".setActiveEditSessionIndex(index)", ->
- it "restores the buffer, cursors, selections, and scroll position of the edit session associated with the index", ->
- editor.attachToDom(heightInLines: 10)
- editor.setSelectedBufferRange([[40, 0], [43, 1]])
- expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
- previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
-
- editor.scrollTop(750)
- expect(editor.scrollTop()).toBe 750
-
- editor.setActiveEditSessionIndex(0)
- expect(editor.getBuffer()).toBe session0.buffer
-
- editor.setActiveEditSessionIndex(2)
- expect(editor.getBuffer()).toBe session2.buffer
- expect(editor.getCursorScreenPosition()).toEqual [43, 1]
- expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
- expect(editor.scrollTop()).toBe 750
- expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
- expect(editor.getSelectionView().find('.region')).toExist()
-
- editor.setActiveEditSessionIndex(0)
- editor.activeEditSession.selectToEndOfLine()
- expect(editor.getSelectionView().find('.region')).toExist()
-
- it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
- path = "/tmp/atom-changed-file.txt"
- fs.write(path, "")
- editSession = project.buildEditSessionForPath(path)
- editor.edit editSession
- editSession.insertText("a buffer change")
-
- spyOn(atom, "confirm")
-
- contentsConflictedHandler = jasmine.createSpy("contentsConflictedHandler")
- editSession.on 'contents-conflicted', contentsConflictedHandler
- fs.write(path, "a file change")
- waitsFor ->
- contentsConflictedHandler.callCount > 0
-
- runs ->
- expect(atom.confirm).toHaveBeenCalled()
-
- it "emits an editor:active-edit-session-changed event with the edit session and its index", ->
- activeEditSessionChangeHandler = jasmine.createSpy('activeEditSessionChangeHandler')
- editor.on 'editor:active-edit-session-changed', activeEditSessionChangeHandler
-
- editor.setActiveEditSessionIndex(2)
- expect(activeEditSessionChangeHandler).toHaveBeenCalled()
- expect(activeEditSessionChangeHandler.argsForCall[0][1..2]).toEqual [editor.activeEditSession, 2]
- activeEditSessionChangeHandler.reset()
-
- editor.setActiveEditSessionIndex(0)
- expect(activeEditSessionChangeHandler.argsForCall[0][1..2]).toEqual [editor.activeEditSession, 0]
- activeEditSessionChangeHandler.reset()
-
- describe ".loadNextEditSession()", ->
- it "loads the next editor state and wraps to beginning when end is reached", ->
- expect(editor.activeEditSession).toBe session2
- editor.loadNextEditSession()
- expect(editor.activeEditSession).toBe session0
- editor.loadNextEditSession()
- expect(editor.activeEditSession).toBe session1
- editor.loadNextEditSession()
- expect(editor.activeEditSession).toBe session2
-
- describe ".loadPreviousEditSession()", ->
- it "loads the next editor state and wraps to beginning when end is reached", ->
- expect(editor.activeEditSession).toBe session2
- editor.loadPreviousEditSession()
- expect(editor.activeEditSession).toBe session1
- editor.loadPreviousEditSession()
- expect(editor.activeEditSession).toBe session0
- editor.loadPreviousEditSession()
- expect(editor.activeEditSession).toBe session2
-
- describe ".save()", ->
- describe "when the current buffer has a path", ->
- tempFilePath = null
-
- beforeEach ->
- project.setPath('/tmp')
- tempFilePath = '/tmp/atom-temp.txt'
- fs.write(tempFilePath, "")
- rootView.open(tempFilePath)
- editor = rootView.getActiveEditor()
- expect(editor.getPath()).toBe tempFilePath
-
- afterEach ->
- expect(fs.remove(tempFilePath))
-
- it "saves the current buffer to disk", ->
- editor.getBuffer().setText 'Edited!'
- expect(fs.read(tempFilePath)).not.toBe "Edited!"
-
- editor.save()
-
- expect(fs.exists(tempFilePath)).toBeTruthy()
- expect(fs.read(tempFilePath)).toBe 'Edited!'
-
- describe "when the current buffer has no path", ->
- selectedFilePath = null
- beforeEach ->
- editor.edit(project.buildEditSessionForPath())
-
- expect(editor.getPath()).toBeUndefined()
- editor.getBuffer().setText 'Save me to a new path'
- spyOn(atom, 'showSaveDialog').andCallFake (callback) -> callback(selectedFilePath)
-
- it "presents a 'save as' dialog", ->
- editor.save()
- expect(atom.showSaveDialog).toHaveBeenCalled()
-
- describe "when a path is chosen", ->
- it "saves the buffer to the chosen path", ->
- selectedFilePath = '/tmp/temp.txt'
-
- editor.save()
-
- expect(fs.exists(selectedFilePath)).toBeTruthy()
- expect(fs.read(selectedFilePath)).toBe 'Save me to a new path'
-
- describe "when dialog is cancelled", ->
- it "does not save the buffer", ->
- selectedFilePath = null
- editor.save()
- expect(fs.exists(selectedFilePath)).toBeFalsy()
+ runs ->
+ expect(atom.confirm).toHaveBeenCalled()
describe ".scrollTop(n)", ->
beforeEach ->
@@ -451,29 +208,6 @@ describe "Editor", ->
editor.scrollTop(50)
expect(editor.scrollTop()).toBe 50
- describe "split methods", ->
- describe "when inside a pane", ->
- fakePane = null
- beforeEach ->
- fakePane = { splitUp: jasmine.createSpy('splitUp').andReturn({}), remove: -> }
- spyOn(editor, 'pane').andReturn(fakePane)
-
- it "calls the corresponding split method on the containing pane with a new editor containing a copy of the active edit session", ->
- editor.edit project.buildEditSessionForPath("sample.txt")
- editor.splitUp()
- expect(fakePane.splitUp).toHaveBeenCalled()
- [newEditor] = fakePane.splitUp.argsForCall[0]
- expect(newEditor.editSessions.length).toEqual 1
- expect(newEditor.activeEditSession.buffer).toBe editor.activeEditSession.buffer
- newEditor.remove()
-
- describe "when not inside a pane", ->
- it "does not split the editor, but doesn't throw an exception", ->
- editor.splitUp().remove()
- editor.splitDown().remove()
- editor.splitLeft().remove()
- editor.splitRight().remove()
-
describe "editor:attached event", ->
it 'only triggers an editor:attached event when it is first added to the DOM', ->
openHandler = jasmine.createSpy('openHandler')
@@ -488,7 +222,7 @@ describe "Editor", ->
editor.attachToDom()
expect(openHandler).not.toHaveBeenCalled()
- describe "editor-path-changed event", ->
+ describe "editor:path-changed event", ->
path = null
beforeEach ->
path = "/tmp/something.txt"
@@ -506,7 +240,7 @@ describe "Editor", ->
it "emits event when editor receives a new buffer", ->
eventHandler = jasmine.createSpy('eventHandler')
editor.on 'editor:path-changed', eventHandler
- editor.edit(project.buildEditSessionForPath(path))
+ editor.edit(project.buildEditSession(path))
expect(eventHandler).toHaveBeenCalled()
it "stops listening to events on previously set buffers", ->
@@ -514,7 +248,7 @@ describe "Editor", ->
oldBuffer = editor.getBuffer()
editor.on 'editor:path-changed', eventHandler
- editor.edit(project.buildEditSessionForPath(path))
+ editor.edit(project.buildEditSession(path))
expect(eventHandler).toHaveBeenCalled()
eventHandler.reset()
@@ -541,30 +275,21 @@ describe "Editor", ->
afterEach ->
editor.clearFontFamily()
- it "updates the font family on new and existing editors", ->
- rootView.attachToDom()
- rootView.height(200)
- rootView.width(200)
-
- config.set("editor.fontFamily", "Courier")
- newEditor = editor.splitRight()
-
- expect($("head style.editor-font-family").text()).toMatch "{font-family: Courier}"
- expect(editor.css('font-family')).toBe 'Courier'
- expect(newEditor.css('font-family')).toBe 'Courier'
-
it "updates the font family of editors and recalculates dimensions critical to cursor positioning", ->
- rootView.attachToDom()
- rootView.height(200)
- rootView.width(200)
-
+ editor.attachToDom(12)
lineHeightBefore = editor.lineHeight
charWidthBefore = editor.charWidth
- config.set("editor.fontFamily", "Consolas")
editor.setCursorScreenPosition [5, 6]
+
+ config.set("editor.fontFamily", "PCMyungjo")
+ expect(editor.css('font-family')).toBe 'PCMyungjo'
+ expect($("head style.editor-font-family").text()).toMatch "{font-family: PCMyungjo}"
expect(editor.charWidth).not.toBe charWidthBefore
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
- expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight
+
+ newEditor = new Editor(editor.activeEditSession.copy())
+ newEditor.attachToDom()
+ expect(newEditor.css('font-family')).toBe 'PCMyungjo'
describe "font size", ->
beforeEach ->
@@ -576,24 +301,9 @@ describe "Editor", ->
expect($("head style.font-size").text()).toMatch "{font-size: #{config.get('editor.fontSize')}px}"
describe "when the font size changes", ->
- it "updates the font family on new and existing editors", ->
- rootView.attachToDom()
- rootView.height(200)
- rootView.width(200)
-
- config.set("editor.fontSize", 20)
- newEditor = editor.splitRight()
-
- expect($("head style.font-size").text()).toMatch "{font-size: 20px}"
- expect(editor.css('font-size')).toBe '20px'
- expect(newEditor.css('font-size')).toBe '20px'
-
it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", ->
- rootView.attachToDom()
- rootView.height(200)
- rootView.width(200)
-
config.set("editor.fontSize", 10)
+ editor.attachToDom()
lineHeightBefore = editor.lineHeight
charWidthBefore = editor.charWidth
editor.setCursorScreenPosition [5, 6]
@@ -606,10 +316,14 @@ describe "Editor", ->
expect(editor.renderedLines.outerHeight()).toBe buffer.getLineCount() * editor.lineHeight
expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight
+ newEditor = new Editor(editor.activeEditSession.copy())
+ newEditor.attachToDom()
+ expect(editor.css('font-size')).toBe '30px'
+
it "updates the position and size of selection regions", ->
- rootView.attachToDom()
config.set("editor.fontSize", 10)
editor.setSelectedBufferRange([[5, 2], [5, 7]])
+ editor.attachToDom()
config.set("editor.fontSize", 30)
selectionRegion = editor.find('.region')
@@ -619,7 +333,7 @@ describe "Editor", ->
expect(selectionRegion.width()).toBe 5 * editor.charWidth
it "updates the gutter width and font size", ->
- rootView.attachToDom()
+ editor.attachToDom()
config.set("editor.fontSize", 20)
expect(editor.gutter.css('font-size')).toBe "20px"
expect(editor.gutter.width()).toBe(editor.charWidth * 2 + editor.gutter.calculateLineNumberPadding())
@@ -632,22 +346,25 @@ describe "Editor", ->
config.set("editor.fontSize", 10)
expect(editor.renderedLines.find(".line").length).toBeGreaterThan originalLineCount
- describe "when the editor is detached", ->
+ describe "when the font size changes while editor is detached", ->
it "redraws the editor according to the new font size when it is reattached", ->
- rootView.attachToDom()
- rootView.height(200)
- rootView.width(200)
+ editor.setCursorScreenPosition([4, 2])
+ editor.attachToDom()
+ initialLineHeight = editor.lineHeight
+ initialCharWidth = editor.charWidth
+ initialCursorPosition = editor.getCursorView().position()
+ initialScrollbarHeight = editor.verticalScrollbarContent.height()
+ editor.detach()
- newEditor = editor.splitRight()
- newEditorParent = newEditor.parent()
- newEditor.detach()
config.set("editor.fontSize", 10)
- newEditorParent.append(newEditor)
+ expect(editor.lineHeight).toBe initialLineHeight
+ expect(editor.charWidth).toBe initialCharWidth
- expect(newEditor.lineHeight).toBe editor.lineHeight
- expect(newEditor.charWidth).toBe editor.charWidth
- expect(newEditor.getCursorView().position()).toEqual editor.getCursorView().position()
- expect(newEditor.verticalScrollbarContent.height()).toBe editor.verticalScrollbarContent.height()
+ editor.attachToDom()
+ expect(editor.lineHeight).not.toBe initialLineHeight
+ expect(editor.charWidth).not.toBe initialCharWidth
+ expect(editor.getCursorView().position()).not.toEqual initialCursorPosition
+ expect(editor.verticalScrollbarContent.height()).not.toBe initialScrollbarHeight
describe "mouse events", ->
beforeEach ->
@@ -1375,7 +1092,7 @@ describe "Editor", ->
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
it "does not wrap the lines of any newly assigned buffers", ->
- otherEditSession = project.buildEditSessionForPath()
+ otherEditSession = project.buildEditSession()
otherEditSession.buffer.setText([1..100].join(''))
editor.edit(otherEditSession)
expect(editor.renderedLines.find('.line').length).toBe(1)
@@ -1411,7 +1128,7 @@ describe "Editor", ->
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
it "calls .setSoftWrapColumn() when the editor is attached because now its dimensions are available to calculate it", ->
- otherEditor = new Editor(editSession: project.buildEditSessionForPath('sample.js'))
+ otherEditor = new Editor(editSession: project.buildEditSession('sample.js'))
spyOn(otherEditor, 'setSoftWrapColumn')
otherEditor.setSoftWrap(true)
@@ -1419,6 +1136,7 @@ describe "Editor", ->
otherEditor.simulateDomAttachment()
expect(otherEditor.setSoftWrapColumn).toHaveBeenCalled()
+ otherEditor.remove()
describe "when some lines at the end of the buffer are not visible on screen", ->
beforeEach ->
@@ -1707,7 +1425,7 @@ describe "Editor", ->
describe "when autoscrolling at the end of the document", ->
it "renders lines properly", ->
- editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
+ editor.edit(project.buildEditSession('two-hundred.txt'))
editor.attachToDom(heightInLines: 5.5)
expect(editor.renderedLines.find('.line').length).toBe 8
@@ -1718,7 +1436,7 @@ describe "Editor", ->
describe "when line has a character that could push it to be too tall (regression)", ->
it "does renders the line at a consistent height", ->
- rootView.attachToDom()
+ editor.attachToDom()
buffer.insert([0, 0], "–")
expect(editor.find('.line:eq(0)').outerHeight()).toBe editor.find('.line:eq(1)').outerHeight()
@@ -1749,21 +1467,11 @@ describe "Editor", ->
expect(editor.find('.line').html()).toBe 'var¬'
it "allows invisible glyphs to be customized via config.editor.invisibles", ->
- rootView.height(200)
- rootView.attachToDom()
- rightEditor = rootView.getActiveEditor()
- rightEditor.setText(" \t ")
- leftEditor = rightEditor.splitLeft()
-
- config.set "editor.showInvisibles", true
- config.set "editor.invisibles",
- eol: ";"
- space: "_"
- tab: "tab"
- config.update()
-
- expect(rightEditor.find(".line:first").text()).toBe "_tab _;"
- expect(leftEditor.find(".line:first").text()).toBe "_tab _;"
+ editor.setText(" \t ")
+ editor.attachToDom()
+ config.set("editor.showInvisibles", true)
+ config.set("editor.invisibles", eol: ";", space: "_", tab: "tab")
+ expect(editor.find(".line:first").text()).toBe "_tab _;"
it "displays trailing carriage return using a visible non-empty value", ->
editor.setText "a line that ends with a carriage return\r\n"
@@ -1988,11 +1696,14 @@ describe "Editor", ->
describe "when the switching from an edit session for a long buffer to an edit session for a short buffer", ->
it "updates the line numbers to reflect the shorter buffer", ->
- editor.edit(fixturesProject.buildEditSessionForPath(null))
+ emptyEditSession = project.buildEditSession(null)
+ editor.edit(emptyEditSession)
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1
- editor.setActiveEditSessionIndex(0)
- editor.setActiveEditSessionIndex(1)
+ editor.edit(editSession)
+ expect(editor.gutter.lineNumbers.find('.line-number').length).toBeGreaterThan 1
+
+ editor.edit(emptyEditSession)
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1
describe "when the editor is mini", ->
@@ -2122,7 +1833,7 @@ describe "Editor", ->
describe "folding", ->
beforeEach ->
- editSession = project.buildEditSessionForPath('two-hundred.txt')
+ editSession = project.buildEditSession('two-hundred.txt')
buffer = editSession.buffer
editor.edit(editSession)
editor.attachToDom()
@@ -2211,14 +1922,6 @@ describe "Editor", ->
editor.scrollTop(0)
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
- describe ".getOpenBufferPaths()", ->
- it "returns the paths of all non-anonymous buffers with edit sessions on this editor", ->
- editor.edit(project.buildEditSessionForPath('sample.txt'))
- editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
- editor.edit(project.buildEditSessionForPath())
- paths = editor.getOpenBufferPaths().map (path) -> project.relativize(path)
- expect(paths).toEqual = ['sample.js', 'sample.txt', 'two-hundred.txt']
-
describe "paging up and down", ->
beforeEach ->
editor.attachToDom()
@@ -2260,37 +1963,20 @@ describe "Editor", ->
expect(editor.getCursor().getScreenPosition().row).toBe(0)
expect(editor.getFirstVisibleScreenRow()).toBe(0)
- describe "when autosave is enabled", ->
- it "autosaves the current buffer when the editor loses focus or switches edit sessions", ->
- config.set "editor.autosave", true
- rootView.attachToDom()
- editor2 = editor.splitRight()
- spyOn(editor2.activeEditSession, 'save')
-
- editor.focus()
- expect(editor2.activeEditSession.save).toHaveBeenCalled()
-
- editSession = editor.activeEditSession
- spyOn(editSession, 'save')
- rootView.open('sample.txt')
- expect(editSession.save).toHaveBeenCalled()
-
describe ".checkoutHead()", ->
[path, originalPathText] = []
beforeEach ->
- path = require.resolve('fixtures/git/working-dir/file.txt')
+ path = project.resolve('git/working-dir/file.txt')
originalPathText = fs.read(path)
- rootView.open(path)
- editor = rootView.getActiveEditor()
- editor.attachToDom()
+ editor.edit(project.buildEditSession(path))
afterEach ->
fs.write(path, originalPathText)
it "restores the contents of the editor to the HEAD revision", ->
editor.setText('')
- editor.save()
+ editor.getBuffer().save()
fileChangeHandler = jasmine.createSpy('fileChange')
editor.getBuffer().file.on 'contents-changed', fileChangeHandler
@@ -2344,7 +2030,7 @@ describe "Editor", ->
describe "when clicking below the last line", ->
beforeEach ->
- rootView.attachToDom()
+ editor.attachToDom()
it "move the cursor to the end of the file", ->
expect(editor.getCursorScreenPosition()).toEqual [0,0]
@@ -2361,68 +2047,19 @@ describe "Editor", ->
editor.underlayer.trigger event
expect(editor.getSelection().getScreenRange()).toEqual [[0,0], [12,2]]
- describe ".destroyEditSessionIndex(index)", ->
- it "prompts to save dirty buffers before closing", ->
- editor.setText("I'm dirty")
- rootView.open('sample.txt')
- expect(editor.getEditSessions().length).toBe 2
- spyOn(atom, "confirm")
- editor.destroyEditSessionIndex(0)
- expect(atom.confirm).toHaveBeenCalled()
- expect(editor.getEditSessions().length).toBe 2
- expect(editor.getEditSessions()[0].buffer.isModified()).toBeTruthy()
-
- describe ".destroyInactiveEditSessions()", ->
- it "destroys every edit session except the active one", ->
- rootView.open('sample.txt')
- cssSession = rootView.open('css.css')
- rootView.open('coffee.coffee')
- rootView.open('hello.rb')
- expect(editor.getEditSessions().length).toBe 5
- editor.setActiveEditSessionIndex(2)
- editor.destroyInactiveEditSessions()
- expect(editor.getActiveEditSessionIndex()).toBe 0
- expect(editor.getEditSessions().length).toBe 1
- expect(editor.getEditSessions()[0]).toBe cssSession
-
- it "prompts to save dirty buffers before destroying", ->
- editor.setText("I'm dirty")
- dirtySession = editor.activeEditSession
- rootView.open('sample.txt')
- expect(editor.getEditSessions().length).toBe 2
- spyOn(atom, "confirm")
- editor.destroyInactiveEditSessions()
- expect(atom.confirm).toHaveBeenCalled()
- expect(editor.getEditSessions().length).toBe 2
- expect(editor.getEditSessions()[0].buffer.isModified()).toBeTruthy()
-
- describe ".destroyAllEditSessions()", ->
- it "destroys every edit session", ->
- rootView.open('sample.txt')
- rootView.open('css.css')
- rootView.open('coffee.coffee')
- rootView.open('hello.rb')
- expect(editor.getEditSessions().length).toBe 5
- editor.setActiveEditSessionIndex(2)
- editor.destroyAllEditSessions()
- expect(editor.pane()).toBeUndefined()
- expect(editor.getEditSessions().length).toBe 0
-
describe ".reloadGrammar()", ->
[path] = []
beforeEach ->
path = "/tmp/grammar-change.txt"
fs.write(path, "var i;")
- rootView.attachToDom()
afterEach ->
- project.removeGrammarOverrideForPath(path)
fs.remove(path) if fs.exists(path)
it "updates all the rendered lines when the grammar changes", ->
- rootView.open(path)
- editor = rootView.getActiveEditor()
+ editor.edit(project.buildEditSession(path))
+
expect(editor.getGrammar().name).toBe 'Plain Text'
jsGrammar = syntax.grammarForFilePath('/tmp/js.js')
expect(jsGrammar.name).toBe 'JavaScript'
@@ -2436,12 +2073,6 @@ describe "Editor", ->
expect(line0.tokens.length).toBe 3
expect(line0.tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
- line0 = editor.renderedLines.find('.line:first')
- span0 = line0.children('span:eq(0)')
- expect(span0).toMatchSelector '.source.js'
- expect(span0.children('span:eq(0)')).toMatchSelector '.storage.modifier.js'
- expect(span0.children('span:eq(0)').text()).toBe 'var'
-
it "doesn't update the rendered lines when the grammar doesn't change", ->
expect(editor.getGrammar().name).toBe 'JavaScript'
spyOn(editor, 'updateDisplay').andCallThrough()
@@ -2451,8 +2082,8 @@ describe "Editor", ->
expect(editor.getGrammar().name).toBe 'JavaScript'
it "emits an editor:grammar-changed event when updated", ->
- rootView.open(path)
- editor = rootView.getActiveEditor()
+ editor.edit(project.buildEditSession(path))
+
eventHandler = jasmine.createSpy('eventHandler')
editor.on('editor:grammar-changed', eventHandler)
editor.reloadGrammar()
@@ -2770,104 +2401,6 @@ describe "Editor", ->
expect(buffer.lineForRow(15)).toBeUndefined()
expect(editor.getCursorBufferPosition()).toEqual [13, 0]
- describe ".moveEditSessionToIndex(fromIndex, toIndex)", ->
- describe "when the edit session moves to a later index", ->
- it "updates the edit session order", ->
- jsPath = editor.getPath()
- rootView.open("sample.txt")
- txtPath = editor.getPath()
- expect(editor.editSessions[0].getPath()).toBe jsPath
- expect(editor.editSessions[1].getPath()).toBe txtPath
- editor.moveEditSessionToIndex(0, 1)
- expect(editor.editSessions[0].getPath()).toBe txtPath
- expect(editor.editSessions[1].getPath()).toBe jsPath
-
- it "fires an editor:edit-session-order-changed event", ->
- eventHandler = jasmine.createSpy("eventHandler")
- rootView.open("sample.txt")
- editor.on "editor:edit-session-order-changed", eventHandler
- editor.moveEditSessionToIndex(0, 1)
- expect(eventHandler).toHaveBeenCalled()
-
- it "sets the moved session as the editor's active session", ->
- jsPath = editor.getPath()
- rootView.open("sample.txt")
- txtPath = editor.getPath()
- expect(editor.activeEditSession.getPath()).toBe txtPath
- editor.moveEditSessionToIndex(0, 1)
- expect(editor.activeEditSession.getPath()).toBe jsPath
-
- describe "when the edit session moves to an earlier index", ->
- it "updates the edit session order", ->
- jsPath = editor.getPath()
- rootView.open("sample.txt")
- txtPath = editor.getPath()
- expect(editor.editSessions[0].getPath()).toBe jsPath
- expect(editor.editSessions[1].getPath()).toBe txtPath
- editor.moveEditSessionToIndex(1, 0)
- expect(editor.editSessions[0].getPath()).toBe txtPath
- expect(editor.editSessions[1].getPath()).toBe jsPath
-
- it "fires an editor:edit-session-order-changed event", ->
- eventHandler = jasmine.createSpy("eventHandler")
- rootView.open("sample.txt")
- editor.on "editor:edit-session-order-changed", eventHandler
- editor.moveEditSessionToIndex(1, 0)
- expect(eventHandler).toHaveBeenCalled()
-
- it "sets the moved session as the editor's active session", ->
- jsPath = editor.getPath()
- rootView.open("sample.txt")
- txtPath = editor.getPath()
- expect(editor.activeEditSession.getPath()).toBe txtPath
- editor.moveEditSessionToIndex(1, 0)
- expect(editor.activeEditSession.getPath()).toBe txtPath
-
- describe ".moveEditSessionToEditor(fromIndex, toEditor, toIndex)", ->
- it "closes the edit session in the source editor", ->
- jsPath = editor.getPath()
- rootView.open("sample.txt")
- txtPath = editor.getPath()
- rightEditor = editor.splitRight()
- expect(editor.editSessions[0].getPath()).toBe jsPath
- expect(editor.editSessions[1].getPath()).toBe txtPath
- editor.moveEditSessionToEditor(0, rightEditor, 1)
- expect(editor.editSessions[0].getPath()).toBe txtPath
- expect(editor.editSessions[1]).toBeUndefined()
-
- it "opens the edit session in the destination editor at the target index", ->
- jsPath = editor.getPath()
- rootView.open("sample.txt")
- txtPath = editor.getPath()
- rightEditor = editor.splitRight()
- expect(rightEditor.editSessions[0].getPath()).toBe txtPath
- expect(rightEditor.editSessions[1]).toBeUndefined()
- editor.moveEditSessionToEditor(0, rightEditor, 0)
- expect(rightEditor.editSessions[0].getPath()).toBe jsPath
- expect(rightEditor.editSessions[1].getPath()).toBe txtPath
-
- describe "when editor:undo-close-session is triggered", ->
- describe "when an edit session is opened back up after it is closed", ->
- it "is removed from the undo stack and not reopened when the event is triggered", ->
- rootView.open('sample.txt')
- expect(editor.getPath()).toBe fixturesProject.resolve('sample.txt')
- editor.trigger "core:close"
- expect(editor.closedEditSessions.length).toBe 1
- rootView.open('sample.txt')
- expect(editor.closedEditSessions.length).toBe 0
- editor.trigger 'editor:undo-close-session'
- expect(editor.getPath()).toBe fixturesProject.resolve('sample.txt')
-
- it "opens the closed session back up at the previous index", ->
- rootView.open('sample.txt')
- editor.loadPreviousEditSession()
- expect(editor.getPath()).toBe fixturesProject.resolve('sample.js')
- editor.trigger "core:close"
- expect(editor.getPath()).toBe fixturesProject.resolve('sample.txt')
- editor.trigger 'editor:undo-close-session'
- expect(editor.getPath()).toBe fixturesProject.resolve('sample.js')
- expect(editor.getActiveEditSessionIndex()).toBe 0
-
describe "editor:save-debug-snapshot", ->
it "saves the state of the rendered lines, the display buffer, and the buffer to a file of the user's choosing", ->
saveDialogCallback = null
diff --git a/spec/app/git-spec.coffee b/spec/app/git-spec.coffee
index 36ca195e1..fcdb120d8 100644
--- a/spec/app/git-spec.coffee
+++ b/spec/app/git-spec.coffee
@@ -1,5 +1,6 @@
Git = require 'git'
fs = require 'fs'
+Task = require 'task'
describe "Git", ->
repo = null
@@ -188,10 +189,10 @@ describe "Git", ->
beforeEach ->
repo = new Git(require.resolve('fixtures/git/working-dir'))
- modifiedPath = fixturesProject.resolve('git/working-dir/file.txt')
+ modifiedPath = project.resolve('git/working-dir/file.txt')
originalModifiedPathText = fs.read(modifiedPath)
- newPath = fixturesProject.resolve('git/working-dir/untracked.txt')
- cleanPath = fixturesProject.resolve('git/working-dir/other.txt')
+ newPath = project.resolve('git/working-dir/untracked.txt')
+ cleanPath = project.resolve('git/working-dir/other.txt')
fs.write(newPath, '')
afterEach ->
@@ -212,3 +213,22 @@ describe "Git", ->
expect(statuses[cleanPath]).toBeUndefined()
expect(repo.isStatusNew(statuses[newPath])).toBeTruthy()
expect(repo.isStatusModified(statuses[modifiedPath])).toBeTruthy()
+
+ it "only starts a single web worker at a time and schedules a restart if one is already running", =>
+ fs.write(modifiedPath, 'making this path modified')
+ statusHandler = jasmine.createSpy('statusHandler')
+ repo.on 'statuses-changed', statusHandler
+
+ spyOn(Task.prototype, "start").andCallThrough()
+ repo.refreshStatus()
+ expect(Task.prototype.start.callCount).toBe 1
+ repo.refreshStatus()
+ expect(Task.prototype.start.callCount).toBe 1
+ repo.refreshStatus()
+ expect(Task.prototype.start.callCount).toBe 1
+
+ waitsFor ->
+ statusHandler.callCount > 0
+
+ runs ->
+ expect(Task.prototype.start.callCount).toBe 2
diff --git a/spec/app/grammar-view-spec.coffee b/spec/app/grammar-view-spec.coffee
index afa03839d..45157aab0 100644
--- a/spec/app/grammar-view-spec.coffee
+++ b/spec/app/grammar-view-spec.coffee
@@ -7,10 +7,8 @@ describe "GrammarView", ->
beforeEach ->
window.rootView = new RootView
- project.removeGrammarOverrideForPath('sample.js')
rootView.open('sample.js')
- editor = rootView.getActiveEditor()
- rootView.attachToDom()
+ editor = rootView.getActiveView()
textGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'Plain Text'
expect(textGrammar).toBeTruthy()
jsGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'JavaScript'
diff --git a/spec/app/language-mode-spec.coffee b/spec/app/language-mode-spec.coffee
index a68f778dc..5b2704947 100644
--- a/spec/app/language-mode-spec.coffee
+++ b/spec/app/language-mode-spec.coffee
@@ -10,18 +10,18 @@ describe "LanguageMode", ->
describe "common behavior", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
+ editSession = project.buildEditSession('sample.js', autoIndent: false)
{ buffer, languageMode } = editSession
describe "language detection", ->
it "uses the file name as the file type if it has no extension", ->
- jsEditSession = fixturesProject.buildEditSessionForPath('js', autoIndent: false)
+ jsEditSession = project.buildEditSession('js', autoIndent: false)
expect(jsEditSession.languageMode.grammar.name).toBe "JavaScript"
jsEditSession.destroy()
describe "javascript", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
+ editSession = project.buildEditSession('sample.js', autoIndent: false)
{ buffer, languageMode } = editSession
describe ".toggleLineCommentsForBufferRows(start, end)", ->
@@ -63,7 +63,7 @@ describe "LanguageMode", ->
describe "coffeescript", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('coffee.coffee', autoIndent: false)
+ editSession = project.buildEditSession('coffee.coffee', autoIndent: false)
{ buffer, languageMode } = editSession
describe ".toggleLineCommentsForBufferRows(start, end)", ->
@@ -98,7 +98,7 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('css.css', autoIndent: false)
+ editSession = project.buildEditSession('css.css', autoIndent: false)
{ buffer, languageMode } = editSession
describe ".toggleLineCommentsForBufferRows(start, end)", ->
diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee
new file mode 100644
index 000000000..56ea53336
--- /dev/null
+++ b/spec/app/pane-container-spec.coffee
@@ -0,0 +1,148 @@
+PaneContainer = require 'pane-container'
+Pane = require 'pane'
+{View, $$} = require 'space-pen'
+_ = require 'underscore'
+$ = require 'jquery'
+
+describe "PaneContainer", ->
+ [TestView, container, pane1, pane2, pane3] = []
+
+ beforeEach ->
+ class TestView extends View
+ registerDeserializer(this)
+ @deserialize: ({name}) -> new TestView(name)
+ @content: -> @div tabindex: -1
+ initialize: (@name) -> @text(@name)
+ serialize: -> { deserializer: 'TestView', @name }
+ getUri: -> "/tmp/#{@name}"
+ save: -> @saved = true
+ isEqual: (other) -> @name is other.name
+
+ container = new PaneContainer
+ pane1 = new Pane(new TestView('1'))
+ container.append(pane1)
+ pane2 = pane1.splitRight(new TestView('2'))
+ pane3 = pane2.splitDown(new TestView('3'))
+
+ afterEach ->
+ unregisterDeserializer(TestView)
+
+ describe ".focusNextPane()", ->
+ it "focuses the pane following the focused pane or the first pane if no pane has focus", ->
+ container.attachToDom()
+ container.focusNextPane()
+ expect(pane1.activeItem).toMatchSelector ':focus'
+ container.focusNextPane()
+ expect(pane2.activeItem).toMatchSelector ':focus'
+ container.focusNextPane()
+ expect(pane3.activeItem).toMatchSelector ':focus'
+ container.focusNextPane()
+ expect(pane1.activeItem).toMatchSelector ':focus'
+
+ describe ".getActivePane()", ->
+ it "returns the most-recently focused pane", ->
+ focusStealer = $$ -> @div tabindex: -1, "focus stealer"
+ focusStealer.attachToDom()
+ container.attachToDom()
+
+ pane2.focus()
+ expect(container.getFocusedPane()).toBe pane2
+ expect(container.getActivePane()).toBe pane2
+
+ focusStealer.focus()
+ expect(container.getFocusedPane()).toBeUndefined()
+ expect(container.getActivePane()).toBe pane2
+
+ pane3.focus()
+ 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 = []
+ subscription = container.eachPane (pane) -> panes.push(pane)
+ expect(panes).toEqual [pane1, pane2, pane3]
+
+ panes = []
+ pane4 = pane3.splitRight()
+ expect(panes).toEqual [pane4]
+
+ panes = []
+ subscription.cancel()
+ pane4.splitDown()
+ expect(panes).toEqual []
+
+ describe ".reopenItem()", ->
+ describe "when there is an active pane", ->
+ it "reconstructs and shows the last-closed pane item", ->
+ expect(container.getActivePane()).toBe pane3
+ item3 = pane3.activeItem
+ item4 = new TestView('4')
+ pane3.showItem(item4)
+
+ pane3.destroyItem(item3)
+ pane3.destroyItem(item4)
+ expect(container.getActivePane()).toBe pane1
+
+ expect(container.reopenItem()).toBeTruthy()
+ expect(pane1.activeItem).toEqual item4
+
+ expect(container.reopenItem()).toBeTruthy()
+ expect(pane1.activeItem).toEqual item3
+
+ expect(container.reopenItem()).toBeFalsy()
+ expect(pane1.activeItem).toEqual item3
+
+ describe "when there is no active pane", ->
+ it "attaches a new pane with the reconstructed last pane item", ->
+ pane1.remove()
+ pane2.remove()
+ item3 = pane3.activeItem
+ pane3.destroyItem(item3)
+ expect(container.getActivePane()).toBeUndefined()
+
+ container.reopenItem()
+
+ expect(container.getActivePane().activeItem).toEqual item3
+
+ it "does not reopen an item that is already open", ->
+ item3 = pane3.activeItem
+ item4 = new TestView('4')
+ pane3.showItem(item4)
+ pane3.destroyItem(item3)
+ pane3.destroyItem(item4)
+
+ expect(container.getActivePane()).toBe pane1
+ pane1.showItem(new TestView('4'))
+
+ expect(container.reopenItem()).toBeTruthy()
+ expect(_.pluck(pane1.getItems(), 'name')).toEqual ['1', '4', '3']
+ expect(pane1.activeItem).toEqual item3
+
+ expect(container.reopenItem()).toBeFalsy()
+ expect(pane1.activeItem).toEqual item3
+
+ describe ".saveAll()", ->
+ it "saves all open pane items", ->
+ pane1.showItem(new TestView('4'))
+
+ container.saveAll()
+
+ for pane in container.getPanes()
+ for item in pane.getItems()
+ expect(item.saved).toBeTruthy()
+
+ describe "serialization", ->
+ it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
+ newContainer = 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()
+
+ 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
diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee
new file mode 100644
index 000000000..3c62fc797
--- /dev/null
+++ b/spec/app/pane-spec.coffee
@@ -0,0 +1,693 @@
+PaneContainer = require 'pane-container'
+Pane = require 'pane'
+{$$} = require 'space-pen'
+$ = require 'jquery'
+
+describe "Pane", ->
+ [container, view1, view2, editSession1, editSession2, pane] = []
+
+ beforeEach ->
+ container = new PaneContainer
+ view1 = $$ -> @div id: 'view-1', tabindex: -1, 'View 1'
+ view2 = $$ -> @div id: 'view-2', tabindex: -1, 'View 2'
+ editSession1 = project.buildEditSession('sample.js')
+ editSession2 = project.buildEditSession('sample.txt')
+ pane = new Pane(view1, editSession1, view2, editSession2)
+ container.append(pane)
+
+ describe ".initialize(items...)", ->
+ it "displays the first item in the pane", ->
+ expect(pane.itemViews.find('#view-1')).toExist()
+
+ describe ".showItem(item)", ->
+ it "hides all item views except the one being shown and sets the activeItem", ->
+ expect(pane.activeItem).toBe view1
+ pane.showItem(view2)
+ expect(view1.css('display')).toBe 'none'
+ expect(view2.css('display')).toBe ''
+ expect(pane.activeItem).toBe view2
+
+ it "triggers 'pane:active-item-changed' if the item isn't already the activeItem", ->
+ pane.makeActive()
+ itemChangedHandler = jasmine.createSpy("itemChangedHandler")
+ container.on 'pane:active-item-changed', itemChangedHandler
+
+ expect(pane.activeItem).toBe view1
+ pane.showItem(view2)
+ pane.showItem(view2)
+ expect(itemChangedHandler.callCount).toBe 1
+ expect(itemChangedHandler.argsForCall[0][1]).toBe view2
+ itemChangedHandler.reset()
+
+ pane.showItem(editSession1)
+ expect(itemChangedHandler).toHaveBeenCalled()
+ expect(itemChangedHandler.argsForCall[0][1]).toBe editSession1
+ itemChangedHandler.reset()
+
+ describe "if the pane's active view is focused before calling showItem", ->
+ it "focuses the new active view", ->
+ container.attachToDom()
+ pane.focus()
+ expect(pane.activeView).not.toBe view2
+ expect(pane.activeView).toMatchSelector ':focus'
+ pane.showItem(view2)
+ expect(view2).toMatchSelector ':focus'
+
+ describe "when the given item isn't yet in the items list on the pane", ->
+ view3 = null
+ beforeEach ->
+ view3 = $$ -> @div id: 'view-3', "View 3"
+ pane.showItem(editSession1)
+ expect(pane.getActiveItemIndex()).toBe 1
+
+ it "adds it to the items list after the active item", ->
+ pane.showItem(view3)
+ expect(pane.getItems()).toEqual [view1, editSession1, view3, view2, editSession2]
+ expect(pane.activeItem).toBe view3
+ expect(pane.getActiveItemIndex()).toBe 2
+
+ it "triggers the 'item-added' event with the item and its index before the 'active-item-changed' event", ->
+ 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)
+ 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(editSession1)
+ editor = pane.activeView
+ expect(editor.css('display')).toBe ''
+ expect(editor.activeEditSession).toBe editSession1
+
+ describe "when a valid view has already been appended for another item", ->
+ it "recycles the existing view by assigning the selected item to it", ->
+ pane.showItem(editSession1)
+ pane.showItem(editSession2)
+ expect(pane.itemViews.find('.editor').length).toBe 1
+ editor = pane.activeView
+ expect(editor.css('display')).toBe ''
+ expect(editor.activeEditSession).toBe editSession2
+
+ 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)
+ expect(pane.itemViews.find('#view-2')).toExist()
+ expect(pane.activeView).toBe view2
+
+ describe ".destroyItem(item)", ->
+ describe "if the item is not modified", ->
+ it "removes the item and tries to call destroy on it", ->
+ pane.destroyItem(editSession2)
+ expect(pane.getItems().indexOf(editSession2)).toBe -1
+ expect(editSession2.destroyed).toBeTruthy()
+
+ describe "if the item is modified", ->
+ beforeEach ->
+ spyOn(atom, 'confirm')
+ spyOn(atom, 'showSaveDialog')
+ spyOn(editSession2, 'save')
+ spyOn(editSession2, 'saveAs')
+
+ atom.confirm.selectOption = (buttonText) ->
+ for arg, i in @argsForCall[0] when arg is buttonText
+ @argsForCall[0][i + 1]?()
+
+ editSession2.insertText('a')
+ expect(editSession2.isModified()).toBeTruthy()
+ pane.destroyItem(editSession2)
+
+ it "presents a dialog with the option to save the item first", ->
+ expect(atom.confirm).toHaveBeenCalled()
+ expect(pane.getItems().indexOf(editSession2)).not.toBe -1
+ expect(editSession2.destroyed).toBeFalsy()
+
+ describe "if the [Save] option is selected", ->
+ describe "when the item has a uri", ->
+ it "saves the item before removing and destroying it", ->
+ atom.confirm.selectOption('Save')
+
+ expect(editSession2.save).toHaveBeenCalled()
+ expect(pane.getItems().indexOf(editSession2)).toBe -1
+ expect(editSession2.destroyed).toBeTruthy()
+
+ describe "when the item has no uri", ->
+ it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
+ editSession2.buffer.setPath(undefined)
+
+ atom.confirm.selectOption('Save')
+
+ expect(atom.showSaveDialog).toHaveBeenCalled()
+
+ atom.showSaveDialog.argsForCall[0][0]("/selected/path")
+
+ expect(editSession2.saveAs).toHaveBeenCalledWith("/selected/path")
+ expect(pane.getItems().indexOf(editSession2)).toBe -1
+ expect(editSession2.destroyed).toBeTruthy()
+
+ describe "if the [Don't Save] option is selected", ->
+ it "removes and destroys the item without saving it", ->
+ atom.confirm.selectOption("Don't Save")
+
+ expect(editSession2.save).not.toHaveBeenCalled()
+ expect(pane.getItems().indexOf(editSession2)).toBe -1
+ expect(editSession2.destroyed).toBeTruthy()
+
+ describe "if the [Cancel] option is selected", ->
+ it "does not save, remove, or destroy the item", ->
+ atom.confirm.selectOption("Cancel")
+
+ expect(editSession2.save).not.toHaveBeenCalled()
+ expect(pane.getItems().indexOf(editSession2)).not.toBe -1
+ expect(editSession2.destroyed).toBeFalsy()
+
+ describe ".removeItem(item)", ->
+ it "removes the item from the items list and shows the next item if it was showing", ->
+ pane.removeItem(view1)
+ expect(pane.getItems()).toEqual [editSession1, view2, editSession2]
+ expect(pane.activeItem).toBe editSession1
+
+ pane.showItem(editSession2)
+ pane.removeItem(editSession2)
+ expect(pane.getItems()).toEqual [editSession1, view2]
+ expect(pane.activeItem).toBe editSession1
+
+ it "triggers 'pane:item-removed' with the item and its former index", ->
+ itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
+ pane.on 'pane:item-removed', itemRemovedHandler
+ pane.removeItem(editSession1)
+ expect(itemRemovedHandler).toHaveBeenCalled()
+ expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 1]
+
+ describe "when removing the last item", ->
+ it "removes the pane", ->
+ pane.removeItem(item) for item in pane.getItems()
+ expect(pane.hasParent()).toBeFalsy()
+
+ describe "when the pane is focused", ->
+ it "shifts focus to the next pane", ->
+ container.attachToDom()
+ pane2 = pane.splitRight($$ -> @div class: 'view-3', tabindex: -1, 'View 3')
+ pane.focus()
+ expect(pane).toMatchSelector(':has(:focus)')
+ pane.removeItem(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)
+ 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(editSession2)
+ pane.removeItem(editSession2)
+ expect(pane.itemViews.find('.editor')).toExist()
+ pane.removeItem(editSession1)
+ expect(pane.itemViews.find('.editor')).not.toExist()
+
+ describe ".moveItem(item, index)", ->
+ it "moves the item to the given index and emits a 'pane:item-moved' event with the item and the new index", ->
+ itemMovedHandler = jasmine.createSpy("itemMovedHandler")
+ pane.on 'pane:item-moved', itemMovedHandler
+
+ pane.moveItem(view1, 2)
+ expect(pane.getItems()).toEqual [editSession1, view2, view1, editSession2]
+ expect(itemMovedHandler).toHaveBeenCalled()
+ expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [view1, 2]
+ itemMovedHandler.reset()
+
+ pane.moveItem(editSession1, 3)
+ expect(pane.getItems()).toEqual [view2, view1, editSession2, editSession1]
+ expect(itemMovedHandler).toHaveBeenCalled()
+ expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 3]
+ itemMovedHandler.reset()
+
+ pane.moveItem(editSession1, 1)
+ expect(pane.getItems()).toEqual [view2, editSession1, view1, editSession2]
+ expect(itemMovedHandler).toHaveBeenCalled()
+ expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 1]
+ itemMovedHandler.reset()
+
+ describe ".moveItemToPane(item, pane, index)", ->
+ [pane2, view3] = []
+
+ beforeEach ->
+ view3 = $$ -> @div id: 'view-3', "View 3"
+ pane2 = pane.splitRight(view3)
+
+ it "moves the item to the given pane at the given index", ->
+ pane.moveItemToPane(view1, pane2, 1)
+ expect(pane.getItems()).toEqual [editSession1, view2, editSession2]
+ expect(pane2.getItems()).toEqual [view3, view1]
+
+ 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(editSession2)
+
+ expect(pane.getItems()).toEqual [editSession1]
+ pane.moveItemToPane(editSession1, pane2, 1)
+
+ expect(pane.hasParent()).toBeFalsy()
+ expect(pane2.getItems()).toEqual [view3, editSession1]
+ expect(editSession1.destroyed).toBeFalsy()
+
+ describe "core:close", ->
+ it "destroys the active item and does not bubble the event", ->
+ containerCloseHandler = jasmine.createSpy("containerCloseHandler")
+ container.on 'core:close', containerCloseHandler
+
+ pane.showItem(editSession1)
+ initialItemCount = pane.getItems().length
+ pane.trigger 'core:close'
+ expect(pane.getItems().length).toBe initialItemCount - 1
+ expect(editSession1.destroyed).toBeTruthy()
+
+ expect(containerCloseHandler).not.toHaveBeenCalled()
+
+ describe "pane:close", ->
+ it "destroys all items and removes the pane", ->
+ pane.showItem(editSession1)
+ pane.trigger 'pane:close'
+ expect(pane.hasParent()).toBeFalsy()
+ expect(editSession2.destroyed).toBeTruthy()
+ expect(editSession1.destroyed).toBeTruthy()
+
+ describe "pane:close-other-items", ->
+ it "destroys all items except the current", ->
+ pane.showItem(editSession1)
+ pane.trigger 'pane:close-other-items'
+ expect(editSession2.destroyed).toBeTruthy()
+ expect(pane.getItems()).toEqual [editSession1]
+
+ describe "core:save", ->
+ describe "when the current item has a uri", ->
+ describe "when the current item has a save method", ->
+ it "saves the current item", ->
+ spyOn(editSession2, 'save')
+ pane.showItem(editSession2)
+ pane.trigger 'core:save'
+ expect(editSession2.save).toHaveBeenCalled()
+
+ describe "when the current item has no save method", ->
+ it "does nothing", ->
+ expect(pane.activeItem.save).toBeUndefined()
+ pane.trigger 'core:save'
+
+ describe "when the current item has no uri", ->
+ beforeEach ->
+ spyOn(atom, 'showSaveDialog')
+
+ describe "when the current item has a saveAs method", ->
+ it "opens a save dialog and saves the current item as the selected path", ->
+ spyOn(editSession2, 'saveAs')
+ editSession2.buffer.setPath(undefined)
+ pane.showItem(editSession2)
+
+ pane.trigger 'core:save'
+
+ expect(atom.showSaveDialog).toHaveBeenCalled()
+ atom.showSaveDialog.argsForCall[0][0]('/selected/path')
+ expect(editSession2.saveAs).toHaveBeenCalledWith('/selected/path')
+
+ describe "when the current item has no saveAs method", ->
+ it "does nothing", ->
+ expect(pane.activeItem.saveAs).toBeUndefined()
+ pane.trigger 'core:save'
+ expect(atom.showSaveDialog).not.toHaveBeenCalled()
+
+ describe "core:save-as", ->
+ beforeEach ->
+ spyOn(atom, 'showSaveDialog')
+
+ 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(editSession2, 'saveAs')
+ pane.showItem(editSession2)
+
+ pane.trigger 'core:save-as'
+
+ expect(atom.showSaveDialog).toHaveBeenCalled()
+ atom.showSaveDialog.argsForCall[0][0]('/selected/path')
+ expect(editSession2.saveAs).toHaveBeenCalledWith('/selected/path')
+
+ describe "when the current item does not have a saveAs method", ->
+ it "does nothing", ->
+ expect(pane.activeItem.saveAs).toBeUndefined()
+ pane.trigger 'core:save-as'
+ expect(atom.showSaveDialog).not.toHaveBeenCalled()
+
+ describe "pane:show-next-item and pane:show-previous-item", ->
+ it "advances forward/backward through the pane's items, looping around at either end", ->
+ expect(pane.activeItem).toBe view1
+ pane.trigger 'pane:show-previous-item'
+ expect(pane.activeItem).toBe editSession2
+ pane.trigger 'pane:show-previous-item'
+ expect(pane.activeItem).toBe view2
+ pane.trigger 'pane:show-next-item'
+ expect(pane.activeItem).toBe editSession2
+ pane.trigger 'pane:show-next-item'
+ expect(pane.activeItem).toBe view1
+
+ describe "pane:show-item-N events", ->
+ it "shows the (n-1)th item if it exists", ->
+ pane.trigger 'pane:show-item-2'
+ expect(pane.activeItem).toBe pane.itemAtIndex(1)
+ pane.trigger 'pane:show-item-1'
+ expect(pane.activeItem).toBe pane.itemAtIndex(0)
+ pane.trigger 'pane:show-item-9' # don't fail on out-of-bounds indices
+ expect(pane.activeItem).toBe pane.itemAtIndex(0)
+
+ describe "when the title of the active item changes", ->
+ it "emits pane:active-item-title-changed", ->
+ activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
+ pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
+
+ expect(pane.activeItem).toBe view1
+
+ view2.trigger 'title-changed'
+ expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
+
+ view1.trigger 'title-changed'
+ expect(activeItemTitleChangedHandler).toHaveBeenCalled()
+ activeItemTitleChangedHandler.reset()
+
+ pane.showItem(view2)
+ view2.trigger 'title-changed'
+ expect(activeItemTitleChangedHandler).toHaveBeenCalled()
+
+ describe ".remove()", ->
+ it "destroys all the pane's items", ->
+ pane.remove()
+ expect(editSession1.destroyed).toBeTruthy()
+ expect(editSession2.destroyed).toBeTruthy()
+
+ it "triggers a 'pane:removed' event with the pane", ->
+ removedHandler = jasmine.createSpy("removedHandler")
+ container.on 'pane:removed', removedHandler
+ pane.remove()
+ expect(removedHandler).toHaveBeenCalled()
+ expect(removedHandler.argsForCall[0][1]).toBe pane
+
+ describe "when there are other panes", ->
+ [paneToLeft, paneToRight] = []
+
+ beforeEach ->
+ pane.showItem(editSession1)
+ paneToLeft = pane.splitLeft()
+ paneToRight = pane.splitRight()
+ container.attachToDom()
+
+ describe "when the removed pane is focused", ->
+ it "activates and focuses the next pane", ->
+ pane.focus()
+ 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()
+ expect(paneToLeft.isActive()).toBeTruthy()
+ expect(paneToRight.isActive()).toBeFalsy()
+
+ pane.remove()
+ expect(paneToLeft.isActive()).toBeTruthy()
+ expect(paneToRight.isActive()).toBeFalsy()
+ expect(paneToLeft).toMatchSelector ':has(:focus)'
+
+ describe "when it is the last pane", ->
+ beforeEach ->
+ expect(container.getPanes().length).toBe 1
+ window.rootView = focus: jasmine.createSpy("rootView.focus")
+
+ describe "when the removed pane is focused", ->
+ it "calls focus on rootView so we don't lose focus", ->
+ container.attachToDom()
+ pane.focus()
+ pane.remove()
+ expect(rootView.focus).toHaveBeenCalled()
+
+ describe "when the removed pane is not focused", ->
+ it "does not call focus on root view", ->
+ expect(pane).not.toMatchSelector ':has(:focus)'
+ pane.remove()
+ expect(rootView.focus).not.toHaveBeenCalled()
+
+ describe "when the pane is focused", ->
+ it "focuses the active item view", ->
+ focusHandler = jasmine.createSpy("focusHandler")
+ pane.activeItem.on 'focus', focusHandler
+ pane.focus()
+ expect(focusHandler).toHaveBeenCalled()
+
+ it "triggers 'pane:became-active' if it was not previously active", ->
+ becameActiveHandler = jasmine.createSpy("becameActiveHandler")
+ container.on 'pane:became-active', becameActiveHandler
+
+ expect(pane.isActive()).toBeFalsy()
+ pane.focusin()
+ expect(pane.isActive()).toBeTruthy()
+ pane.focusin()
+
+ expect(becameActiveHandler.callCount).toBe 1
+
+ describe "split methods", ->
+ [pane1, view3, view4] = []
+ beforeEach ->
+ pane1 = pane
+ pane.showItem(editSession1)
+ view3 = $$ -> @div id: 'view-3', 'View 3'
+ view4 = $$ -> @div id: 'view-4', 'View 4'
+
+ describe "splitRight(items...)", ->
+ 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(pane2.items).toEqual [editSession1]
+ expect(pane2.activeItem).not.toBe editSession1 # 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]]
+
+ describe "splitRight(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()
+ expect(container.find('.row .pane').toArray()).toEqual [pane2[0], pane[0]]
+ expect(pane2.items).toEqual [editSession1]
+ expect(pane2.activeItem).not.toBe editSession1 # 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]]
+
+ 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()
+ expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0]]
+ expect(pane2.items).toEqual [editSession1]
+ expect(pane2.activeItem).not.toBe editSession1 # 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]]
+
+ 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()
+ expect(container.find('.column .pane').toArray()).toEqual [pane2[0], pane[0]]
+ expect(pane2.items).toEqual [editSession1]
+ expect(pane2.activeItem).not.toBe editSession1 # 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(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(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()
+
+ describe "autosave", ->
+ [initialActiveItem, initialActiveItemUri] = []
+
+ beforeEach ->
+ initialActiveItem = pane.activeItem
+ initialActiveItemUri = null
+ pane.activeItem.getUri = -> initialActiveItemUri
+ pane.activeItem.save = jasmine.createSpy("activeItem.save")
+ spyOn(pane, 'saveItem').andCallThrough()
+
+ describe "when the active view loses focus", ->
+ it "saves the item if core.autosave is true and the item has a uri", ->
+ pane.activeView.trigger 'focusout'
+ expect(pane.saveItem).not.toHaveBeenCalled()
+ expect(pane.activeItem.save).not.toHaveBeenCalled()
+
+ config.set('core.autosave', true)
+ pane.activeView.trigger 'focusout'
+ expect(pane.saveItem).not.toHaveBeenCalled()
+ expect(pane.activeItem.save).not.toHaveBeenCalled()
+
+ initialActiveItemUri = '/tmp/hi'
+ pane.activeView.trigger 'focusout'
+ expect(pane.activeItem.save).toHaveBeenCalled()
+
+ describe "when an item becomes inactive", ->
+ it "saves the item if core.autosave is true and the item has a uri", ->
+ expect(view2).not.toBe pane.activeItem
+ expect(pane.saveItem).not.toHaveBeenCalled()
+ expect(initialActiveItem.save).not.toHaveBeenCalled()
+ pane.showItem(view2)
+
+ pane.showItem(initialActiveItem)
+ config.set('core.autosave', true)
+ pane.showItem(view2)
+ expect(pane.saveItem).not.toHaveBeenCalled()
+ expect(initialActiveItem.save).not.toHaveBeenCalled()
+
+ pane.showItem(initialActiveItem)
+ initialActiveItemUri = '/tmp/hi'
+ pane.showItem(view2)
+ expect(initialActiveItem.save).toHaveBeenCalled()
+
+ describe "when an item is destroyed", ->
+ it "saves the item if core.autosave is true and the item has a uri", ->
+ # doesn't have to be the active item
+ expect(view2).not.toBe pane.activeItem
+ pane.showItem(view2)
+
+ pane.destroyItem(editSession1)
+ expect(pane.saveItem).not.toHaveBeenCalled()
+
+ config.set("core.autosave", true)
+ view2.getUri = -> undefined
+ view2.save = ->
+ pane.destroyItem(view2)
+ expect(pane.saveItem).not.toHaveBeenCalled()
+
+ initialActiveItemUri = '/tmp/hi'
+ pane.destroyItem(initialActiveItem)
+ expect(initialActiveItem.save).toHaveBeenCalled()
+
+ describe ".itemForUri(uri)", ->
+ it "returns the item for which a call to .getUri() returns the given uri", ->
+ expect(pane.itemForUri(editSession1.getUri())).toBe editSession1
+ expect(pane.itemForUri(editSession2.getUri())).toBe editSession2
+
+ describe "serialization", ->
+ it "can serialize and deserialize the pane and all its serializable items", ->
+ newPane = deserialize(pane.serialize())
+ expect(newPane.getItems()).toEqual [editSession1, editSession2]
+
+ it "restores the active item on deserialization if it serializable", ->
+ pane.showItem(editSession2)
+ newPane = deserialize(pane.serialize())
+ expect(newPane.activeItem).toEqual editSession2
+
+ it "defaults to the first item on deserialization if the active item was not serializable", ->
+ expect(view2.serialize?()).toBeFalsy()
+ pane.showItem(view2)
+ newPane = deserialize(pane.serialize())
+ expect(newPane.activeItem).toEqual editSession1
+
+ it "focuses the pane after attach only if had focus when serialized", ->
+ container.attachToDom()
+
+ pane.focus()
+ state = pane.serialize()
+ pane.remove()
+ newPane = deserialize(state)
+ container.append(newPane)
+ expect(newPane).toMatchSelector(':has(:focus)')
+
+ $(document.activeElement).blur()
+ state = newPane.serialize()
+ newPane.remove()
+ newerPane = deserialize(state)
+ expect(newerPane).not.toMatchSelector(':has(:focus)')
diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee
index 09549eb98..136e61a85 100644
--- a/spec/app/project-spec.coffee
+++ b/spec/app/project-spec.coffee
@@ -3,17 +3,13 @@ fs = require 'fs'
_ = require 'underscore'
describe "Project", ->
- project = null
beforeEach ->
- project = new Project(require.resolve('fixtures/dir'))
+ project.setPath(project.resolve('dir'))
- afterEach ->
- project.destroy()
-
- describe "when editSession is destroyed", ->
+ describe "when an edit session is destroyed", ->
it "removes edit session and calls destroy on buffer (if buffer is not referenced by other edit sessions)", ->
- editSession = project.buildEditSessionForPath("a")
- anotherEditSession = project.buildEditSessionForPath("a")
+ editSession = project.buildEditSession("a")
+ anotherEditSession = project.buildEditSession("a")
expect(project.editSessions.length).toBe 2
expect(editSession.buffer).toBe anotherEditSession.buffer
@@ -24,7 +20,17 @@ describe "Project", ->
anotherEditSession.destroy()
expect(project.editSessions.length).toBe 0
- describe ".buildEditSessionForPath(path)", ->
+ describe "when an edit session is saved and the project has no path", ->
+ it "sets the project's path to the saved file's parent directory", ->
+ path = project.resolve('a')
+ project.setPath(undefined)
+ expect(project.getPath()).toBeUndefined()
+ editSession = project.buildEditSession()
+ editSession.saveAs('/tmp/atom-test-save-sets-project-path')
+ expect(project.getPath()).toBe '/tmp'
+ fs.remove('/tmp/atom-test-save-sets-project-path')
+
+ describe ".buildEditSession(path)", ->
[absolutePath, newBufferHandler, newEditSessionHandler] = []
beforeEach ->
absolutePath = require.resolve('fixtures/dir/a')
@@ -35,30 +41,30 @@ describe "Project", ->
describe "when given an absolute path that hasn't been opened previously", ->
it "returns a new edit session for the given path and emits 'buffer-created' and 'edit-session-created' events", ->
- editSession = project.buildEditSessionForPath(absolutePath)
+ editSession = project.buildEditSession(absolutePath)
expect(editSession.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe "when given a relative path that hasn't been opened previously", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'edit-session-created' events", ->
- editSession = project.buildEditSessionForPath('a')
+ editSession = project.buildEditSession('a')
expect(editSession.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe "when passed the path to a buffer that has already been opened", ->
it "returns a new edit session containing previously opened buffer and emits a 'edit-session-created' event", ->
- editSession = project.buildEditSessionForPath(absolutePath)
+ editSession = project.buildEditSession(absolutePath)
newBufferHandler.reset()
- expect(project.buildEditSessionForPath(absolutePath).buffer).toBe editSession.buffer
- expect(project.buildEditSessionForPath('a').buffer).toBe editSession.buffer
+ expect(project.buildEditSession(absolutePath).buffer).toBe editSession.buffer
+ expect(project.buildEditSession('a').buffer).toBe editSession.buffer
expect(newBufferHandler).not.toHaveBeenCalled()
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe "when not passed a path", ->
it "returns a new edit session and emits 'buffer-created' and 'edit-session-created' events", ->
- editSession = project.buildEditSessionForPath()
+ editSession = project.buildEditSession()
expect(editSession.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer)
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee
index 15205bbc1..89c8ef8c1 100644
--- a/spec/app/root-view-spec.coffee
+++ b/spec/app/root-view-spec.coffee
@@ -4,13 +4,13 @@ Project = require 'project'
RootView = require 'root-view'
Buffer = require 'buffer'
Editor = require 'editor'
+Pane = require 'pane'
{View, $$} = require 'space-pen'
describe "RootView", ->
pathToOpen = null
beforeEach ->
- project.destroy()
project.setPath(project.resolve('dir'))
pathToOpen = project.resolve('a')
window.rootView = new RootView
@@ -23,37 +23,39 @@ describe "RootView", ->
describe "when the serialized RootView has an unsaved buffer", ->
it "constructs the view with the same panes", ->
+ rootView.attachToDom()
rootView.open()
- editor1 = rootView.getActiveEditor()
+ editor1 = rootView.getActiveView()
buffer = editor1.getBuffer()
editor1.splitRight()
viewState = rootView.serialize()
rootView.deactivate()
- window.rootView = RootView.deserialize(viewState)
- rootView.focus()
+ window.rootView = deserialize(viewState)
+ rootView.attachToDom()
+
expect(rootView.getEditors().length).toBe 2
- expect(rootView.getActiveEditor().getText()).toBe buffer.getText()
- expect(rootView.getTitle()).toBe "untitled – #{project.getPath()}"
+ expect(rootView.getActiveView().getText()).toBe buffer.getText()
+ expect(rootView.title).toBe "untitled - #{project.getPath()}"
describe "when the serialized RootView has a project", ->
describe "when there are open editors", ->
it "constructs the view with the same panes", ->
- editor1 = rootView.getActiveEditor()
- editor2 = editor1.splitRight()
- editor3 = editor2.splitRight()
- editor4 = editor2.splitDown()
- editor2.edit(project.buildEditSessionForPath('b'))
- editor3.edit(project.buildEditSessionForPath('../sample.js'))
- editor3.setCursorScreenPosition([2, 4])
- editor4.edit(project.buildEditSessionForPath('../sample.txt'))
- editor4.setCursorScreenPosition([0, 2])
rootView.attachToDom()
- editor2.focus()
+ pane1 = rootView.getActivePane()
+ pane2 = pane1.splitRight()
+ pane3 = pane2.splitRight()
+ pane4 = pane2.splitDown()
+ pane2.showItem(project.buildEditSession('b'))
+ pane3.showItem(project.buildEditSession('../sample.js'))
+ pane3.activeItem.setCursorScreenPosition([2, 4])
+ pane4.showItem(project.buildEditSession('../sample.txt'))
+ pane4.activeItem.setCursorScreenPosition([0, 2])
+ pane2.focus()
viewState = rootView.serialize()
rootView.deactivate()
- window.rootView = RootView.deserialize(viewState)
+ window.rootView = deserialize(viewState)
rootView.attachToDom()
expect(rootView.getEditors().length).toBe 4
@@ -81,276 +83,54 @@ describe "RootView", ->
expect(editor3.isFocused).toBeFalsy()
expect(editor4.isFocused).toBeFalsy()
- expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}"
+ expect(rootView.title).toBe "#{fs.base(editor2.getPath())} - #{project.getPath()}"
describe "where there are no open editors", ->
it "constructs the view with no open editors", ->
- rootView.getActiveEditor().remove()
+ rootView.getActivePane().remove()
expect(rootView.getEditors().length).toBe 0
viewState = rootView.serialize()
rootView.deactivate()
- window.rootView = RootView.deserialize(viewState)
+ window.rootView = deserialize(viewState)
rootView.attachToDom()
expect(rootView.getEditors().length).toBe 0
- describe "when a pane's wrapped view cannot be deserialized", ->
- it "renders an empty pane", ->
- viewState =
- panesViewState:
- deserializer: "Pane",
- wrappedView:
- deserializer: "BogusView"
-
- rootView.deactivate()
- window.rootView = RootView.deserialize(viewState)
- expect(rootView.find('.pane').length).toBe 1
- expect(rootView.find('.pane').children().length).toBe 0
-
describe "focus", ->
- describe "when there is an active editor", ->
- it "hands off focus to the active editor", ->
- rootView.attachToDom()
-
- rootView.open() # create an editor
- expect(rootView).not.toMatchSelector(':focus')
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
-
+ describe "when there is an active view", ->
+ it "hands off focus to the active view", ->
+ editor = rootView.getActiveView()
+ editor.isFocused = false
rootView.focus()
- expect(rootView).not.toMatchSelector(':focus')
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(editor.isFocused).toBeTruthy()
- describe "when there is no active editor", ->
+ describe "when there is no active view", ->
beforeEach ->
- rootView.getActiveEditor().remove()
+ rootView.getActivePane().remove()
+ expect(rootView.getActiveView()).toBeUndefined()
rootView.attachToDom()
+ expect(document.activeElement).toBe document.body
describe "when are visible focusable elements (with a -1 tabindex)", ->
it "passes focus to the first focusable element", ->
- rootView.horizontal.append $$ ->
- @div "One", id: 'one', tabindex: -1
- @div "Two", id: 'two', tabindex: -1
+ focusable1 = $$ -> @div "One", id: 'one', tabindex: -1
+ focusable2 = $$ -> @div "Two", id: 'two', tabindex: -1
+ rootView.horizontal.append(focusable1, focusable2)
+ expect(document.activeElement).toBe document.body
rootView.focus()
- expect(rootView).not.toMatchSelector(':focus')
- expect(rootView.find('#one')).toMatchSelector(':focus')
- expect(rootView.find('#two')).not.toMatchSelector(':focus')
+ expect(document.activeElement).toBe focusable1[0]
describe "when there are no visible focusable elements", ->
it "surrenders focus to the body", ->
- expect(document.activeElement).toBe $('body')[0]
+ focusable = $$ -> @div "One", id: 'one', tabindex: -1
+ rootView.horizontal.append(focusable)
+ focusable.hide()
+ expect(document.activeElement).toBe document.body
- describe "panes", ->
- [pane1, newPaneContent] = []
-
- beforeEach ->
- rootView.attachToDom()
- rootView.width(800)
- rootView.height(600)
- pane1 = rootView.find('.pane').view()
- pane1.attr('id', 'pane-1')
- newPaneContent = $("
New pane content
")
- spyOn(newPaneContent, 'focus')
-
- describe "vertical splits", ->
- describe "when .splitRight(view) is called on a pane", ->
- it "places a new pane to the right of the current pane in a .row div", ->
- expect(rootView.panes.find('.row')).not.toExist()
-
- pane2 = pane1.splitRight(newPaneContent)
- expect(newPaneContent.focus).toHaveBeenCalled()
-
- expect(rootView.panes.find('.row')).toExist()
- expect(rootView.panes.find('.row .pane').length).toBe 2
- [leftPane, rightPane] = rootView.panes.find('.row .pane').map -> $(this)
- expect(rightPane[0]).toBe pane2[0]
- expect(leftPane.attr('id')).toBe 'pane-1'
- expect(rightPane.html()).toBe "New pane content
"
-
- expectedColumnWidth = Math.floor(rootView.panes.width() / 2)
- expect(leftPane.outerWidth()).toBe expectedColumnWidth
- expect(rightPane.position().left).toBe expectedColumnWidth
- expect(rightPane.outerWidth()).toBe expectedColumnWidth
-
- pane2.remove()
-
- expect(rootView.panes.find('.row')).not.toExist()
- expect(rootView.panes.find('.pane').length).toBe 1
- expect(pane1.outerWidth()).toBe rootView.panes.width()
-
- describe "when splitLeft(view) is called on a pane", ->
- it "places a new pane to the left of the current pane in a .row div", ->
- expect(rootView.find('.row')).not.toExist()
-
- pane2 = pane1.splitLeft(newPaneContent)
- expect(newPaneContent.focus).toHaveBeenCalled()
-
- expect(rootView.find('.row')).toExist()
- expect(rootView.find('.row .pane').length).toBe 2
- [leftPane, rightPane] = rootView.find('.row .pane').map -> $(this)
- expect(leftPane[0]).toBe pane2[0]
- expect(rightPane.attr('id')).toBe 'pane-1'
- expect(leftPane.html()).toBe "New pane content
"
-
- expectedColumnWidth = Math.floor(rootView.panes.width() / 2)
- expect(leftPane.outerWidth()).toBe expectedColumnWidth
- expect(rightPane.position().left).toBe expectedColumnWidth
- expect(rightPane.outerWidth()).toBe expectedColumnWidth
-
- pane2.remove()
-
- expect(rootView.panes.find('.row')).not.toExist()
- expect(rootView.panes.find('.pane').length).toBe 1
- expect(pane1.outerWidth()).toBe rootView.panes.width()
- expect(pane1.position().left).toBe 0
-
- describe "horizontal splits", ->
- describe "when splitUp(view) is called on a pane", ->
- it "places a new pane above the current pane in a .column div", ->
- expect(rootView.find('.column')).not.toExist()
-
- pane2 = pane1.splitUp(newPaneContent)
- expect(newPaneContent.focus).toHaveBeenCalled()
-
- expect(rootView.find('.column')).toExist()
- expect(rootView.find('.column .pane').length).toBe 2
- [topPane, bottomPane] = rootView.find('.column .pane').map -> $(this)
- expect(topPane[0]).toBe pane2[0]
- expect(bottomPane.attr('id')).toBe 'pane-1'
- expect(topPane.html()).toBe "New pane content
"
-
- expectedRowHeight = Math.floor(rootView.panes.height() / 2)
- expect(topPane.outerHeight()).toBe expectedRowHeight
- expect(bottomPane.position().top).toBe expectedRowHeight
- expect(bottomPane.outerHeight()).toBe expectedRowHeight
-
- pane2.remove()
-
- expect(rootView.panes.find('.column')).not.toExist()
- expect(rootView.panes.find('.pane').length).toBe 1
- expect(pane1.outerHeight()).toBe rootView.panes.height()
- expect(pane1.position().top).toBe 0
-
- describe "when splitDown(view) is called on a pane", ->
- it "places a new pane below the current pane in a .column div", ->
- expect(rootView.find('.column')).not.toExist()
-
- pane2 = pane1.splitDown(newPaneContent)
- expect(newPaneContent.focus).toHaveBeenCalled()
-
- expect(rootView.find('.column')).toExist()
- expect(rootView.find('.column .pane').length).toBe 2
- [topPane, bottomPane] = rootView.find('.column .pane').map -> $(this)
- expect(bottomPane[0]).toBe pane2[0]
- expect(topPane.attr('id')).toBe 'pane-1'
- expect(bottomPane.html()).toBe "New pane content
"
-
- expectedRowHeight = Math.floor(rootView.panes.height() / 2)
- expect(topPane.outerHeight()).toBe expectedRowHeight
- expect(bottomPane.position().top).toBe expectedRowHeight
- expect(bottomPane.outerHeight()).toBe expectedRowHeight
-
- pane2.remove()
-
- expect(rootView.panes.find('.column')).not.toExist()
- expect(rootView.panes.find('.pane').length).toBe 1
- expect(pane1.outerHeight()).toBe rootView.panes.height()
-
- describe "layout of nested vertical and horizontal splits", ->
- it "lays out rows and columns with a consistent width", ->
- pane1.html("1")
-
- pane1
- .splitLeft("2")
- .splitUp("3")
- .splitLeft("4")
- .splitDown("5")
-
- row1 = rootView.panes.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 * rootView.panes.width())
- expect(column1.outerHeight()).toBe rootView.height()
- expect(pane1.outerWidth()).toBe Math.round(1/3 * rootView.panes.width())
- expect(pane1.outerHeight()).toBe rootView.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 * rootView.panes.height()
- expect(pane2.outerWidth()).toBe column1.outerWidth()
- expect(pane2.outerHeight()).toBe 1/3 * rootView.panes.height()
- expect(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 * rootView.panes.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 * rootView.panes.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 * rootView.panes.height()
- expect(pane5.outerWidth()).toBe column3.width()
- expect(pane5.position().top).toBe pane4.outerHeight()
- expect(pane5.outerHeight()).toBe 1/3 * rootView.panes.height()
-
- pane5.remove()
-
- expect(column3.parent()).not.toExist()
- expect(pane2.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
- expect(pane3.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
- expect(pane4.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
-
- pane4.remove()
- expect(row2.parent()).not.toExist()
- expect(pane1.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
- expect(pane2.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
- expect(pane3.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
-
- pane3.remove()
- expect(column1.parent()).not.toExist()
- expect(pane2.outerHeight()).toBe rootView.panes.height()
-
- pane2.remove()
- expect(row1.parent()).not.toExist()
- expect(rootView.panes.children().length).toBe 1
- expect(rootView.panes.children('.pane').length).toBe 1
- expect(pane1.outerWidth()).toBe rootView.panes.width()
-
- describe ".focusNextPane()", ->
- it "focuses the wrapped view of the pane after the currently focused pane", ->
- class DummyView extends View
- @content: (number) -> @div(number, tabindex: -1)
-
- view1 = pane1.wrappedView
- view2 = new DummyView(2)
- view3 = new DummyView(3)
- pane2 = pane1.splitDown(view2)
- pane3 = pane2.splitRight(view3)
- rootView.attachToDom()
- view1.focus()
-
- spyOn(view1, 'focus').andCallThrough()
- spyOn(view2, 'focus').andCallThrough()
- spyOn(view3, 'focus').andCallThrough()
-
- rootView.focusNextPane()
- expect(view2.focus).toHaveBeenCalled()
- rootView.focusNextPane()
- expect(view3.focus).toHaveBeenCalled()
- rootView.focusNextPane()
- expect(view1.focus).toHaveBeenCalled()
+ rootView.focus()
+ expect(document.activeElement).toBe document.body
describe "keymap wiring", ->
commandHandler = null
@@ -360,249 +140,144 @@ describe "RootView", ->
window.keymap.bindKeys('*', 'x': 'foo-command')
- describe "when a keydown event is triggered on the RootView (not originating from Ace)", ->
+ describe "when a keydown event is triggered on the RootView", ->
it "triggers matching keybindings for that event", ->
event = keydownEvent 'x', target: rootView[0]
rootView.trigger(event)
expect(commandHandler).toHaveBeenCalled()
- describe ".activeKeybindings()", ->
- originalKeymap = null
- keymap = null
- editor = null
+ describe "window title", ->
+ describe "when the project has no path", ->
+ it "sets the title to 'untitled'", ->
+ project.setPath(undefined)
+ expect(rootView.title).toBe 'untitled'
+ describe "when the project has a path", ->
beforeEach ->
- rootView.attachToDom()
- editor = rootView.getActiveEditor()
- keymap = new (require 'keymap')
- originalKeymap = window.keymap
- window.keymap = keymap
+ rootView.open('b')
- afterEach ->
- window.keymap = originalKeymap
+ describe "when there is an active pane item", ->
+ it "sets the title to the pane item's title plus the project path", ->
+ item = rootView.getActivePaneItem()
+ expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}"
- it "returns all keybindings available for focused element", ->
- editor.on 'test-event-a', => # nothing
+ describe "when the title of the active pane item changes", ->
+ it "updates the window title based on the item's new title", ->
+ editSession = rootView.getActivePaneItem()
+ editSession.buffer.setPath('/tmp/hi')
+ expect(rootView.title).toBe "#{editSession.getTitle()} - #{project.getPath()}"
- keymap.bindKeys ".editor",
- "meta-a": "test-event-a"
- "meta-b": "test-event-b"
+ describe "when the active pane's item changes", ->
+ it "updates the title to the new item's title plus the project path", ->
+ rootView.getActivePane().showNextItem()
+ item = rootView.getActivePaneItem()
+ expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}"
- keybindings = rootView.activeKeybindings()
- expect(Object.keys(keybindings).length).toBe 2
- expect(keybindings["meta-a"]).toEqual "test-event-a"
+ describe "when the last pane item is removed", ->
+ it "sets the title to the project's path", ->
+ rootView.getActivePane().remove()
+ expect(rootView.getActivePaneItem()).toBeUndefined()
+ expect(rootView.title).toBe project.getPath()
- describe "when the path of the active editor changes", ->
- it "changes the title and emits an root-view:active-path-changed event", ->
- pathChangeHandler = jasmine.createSpy 'pathChangeHandler'
- rootView.on 'root-view:active-path-changed', pathChangeHandler
-
- editor1 = rootView.getActiveEditor()
- expect(rootView.getTitle()).toBe "#{fs.base(editor1.getPath())} – #{project.getPath()}"
-
- editor2 = rootView.getActiveEditor().splitLeft()
-
- path = project.resolve('b')
- editor2.edit(project.buildEditSessionForPath(path))
- expect(pathChangeHandler).toHaveBeenCalled()
- expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}"
-
- pathChangeHandler.reset()
- editor1.getBuffer().saveAs("/tmp/should-not-be-title.txt")
- expect(pathChangeHandler).not.toHaveBeenCalled()
- expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}"
-
- it "sets the project path to the directory of the editor if it was previously unassigned", ->
- project.setPath(undefined)
- window.rootView = new RootView
- rootView.open()
- expect(project.getPath()?).toBeFalsy()
- rootView.getActiveEditor().getBuffer().saveAs('/tmp/ignore-me')
- expect(project.getPath()).toBe '/tmp'
-
- describe "when editors are focused", ->
- it "triggers 'root-view:active-path-changed' events if the path of the active editor actually changes", ->
- pathChangeHandler = jasmine.createSpy 'pathChangeHandler'
- rootView.on 'root-view:active-path-changed', pathChangeHandler
-
- editor1 = rootView.getActiveEditor()
- editor2 = rootView.getActiveEditor().splitLeft()
-
- rootView.open(require.resolve('fixtures/sample.txt'))
- expect(pathChangeHandler).toHaveBeenCalled()
- pathChangeHandler.reset()
-
- editor1.focus()
- expect(pathChangeHandler).toHaveBeenCalled()
- pathChangeHandler.reset()
-
- rootView.focus()
- expect(pathChangeHandler).not.toHaveBeenCalled()
-
- editor2.edit(editor1.activeEditSession.copy())
- editor2.focus()
- expect(pathChangeHandler).not.toHaveBeenCalled()
-
- describe "when the last editor is removed", ->
- it "updates the title to the project path", ->
- rootView.getEditors()[0].remove()
- expect(rootView.getTitle()).toBe project.getPath()
+ describe "when an inactive pane's item changes", ->
+ it "does not update the title", ->
+ pane = rootView.getActivePane()
+ pane.splitRight()
+ initialTitle = rootView.title
+ pane.showNextItem()
+ expect(rootView.title).toBe initialTitle
describe "font size adjustment", ->
- editor = null
- beforeEach ->
- editor = rootView.getActiveEditor()
- editor.attachToDom()
-
it "increases/decreases font size when increase/decrease-font-size events are triggered", ->
- fontSizeBefore = editor.getFontSize()
+ fontSizeBefore = config.get('editor.fontSize')
rootView.trigger 'window:increase-font-size'
- expect(editor.getFontSize()).toBe fontSizeBefore + 1
+ expect(config.get('editor.fontSize')).toBe fontSizeBefore + 1
rootView.trigger 'window:increase-font-size'
- expect(editor.getFontSize()).toBe fontSizeBefore + 2
+ expect(config.get('editor.fontSize')).toBe fontSizeBefore + 2
rootView.trigger 'window:decrease-font-size'
- expect(editor.getFontSize()).toBe fontSizeBefore + 1
+ expect(config.get('editor.fontSize')).toBe fontSizeBefore + 1
rootView.trigger 'window:decrease-font-size'
- expect(editor.getFontSize()).toBe fontSizeBefore
+ expect(config.get('editor.fontSize')).toBe fontSizeBefore
it "does not allow the font size to be less than 1", ->
config.set("editor.fontSize", 1)
rootView.trigger 'window:decrease-font-size'
- expect(editor.getFontSize()).toBe 1
+ expect(config.get('editor.fontSize')).toBe 1
describe ".open(path, options)", ->
- describe "when there is no active editor", ->
+ describe "when there is no active pane", ->
beforeEach ->
- rootView.getActiveEditor().destroyActiveEditSession()
- expect(rootView.getActiveEditor()).toBeUndefined()
+ spyOn(Pane.prototype, 'focus')
+ rootView.getActivePane().remove()
+ expect(rootView.getActivePane()).toBeUndefined()
describe "when called with no path", ->
- it "opens / returns an edit session for an empty buffer in a new editor", ->
+ it "creates a empty edit session as an item on a new pane, and focuses the pane", ->
editSession = rootView.open()
- expect(rootView.getActiveEditor()).toBeDefined()
- expect(rootView.getActiveEditor().getPath()).toBeUndefined()
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
+ expect(rootView.getActivePane().activeItem).toBe editSession
+ expect(editSession.getPath()).toBeUndefined()
+ expect(rootView.getActivePane().focus).toHaveBeenCalled()
describe "when called with a path", ->
- it "opens a buffer with the given path in a new editor", ->
+ it "creates an edit session for the given path as an item on a new pane, and focuses the pane", ->
editSession = rootView.open('b')
- expect(rootView.getActiveEditor()).toBeDefined()
- expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/dir/b')
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
+ expect(rootView.getActivePane().activeItem).toBe editSession
+ expect(editSession.getPath()).toBe require.resolve('fixtures/dir/b')
+ expect(rootView.getActivePane().focus).toHaveBeenCalled()
- describe "when there is an active editor", ->
+ describe "when the changeFocus option is false", ->
+ it "does not focus the new pane", ->
+ editSession = rootView.open('b', changeFocus: false)
+ expect(rootView.getActivePane().focus).not.toHaveBeenCalled()
+
+ describe "when there is an active pane", ->
+ [activePane, initialItemCount] = []
beforeEach ->
- expect(rootView.getActiveEditor()).toBeDefined()
+ activePane = rootView.getActivePane()
+ spyOn(activePane, 'focus')
+ initialItemCount = activePane.getItems().length
describe "when called with no path", ->
- it "opens an empty buffer in the active editor", ->
+ it "opens an edit session with an empty buffer as an item on the active pane and focuses it", ->
editSession = rootView.open()
- expect(rootView.getActiveEditor().getPath()).toBeUndefined()
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
+ expect(activePane.getItems().length).toBe initialItemCount + 1
+ expect(activePane.activeItem).toBe editSession
+ expect(editSession.getPath()).toBeUndefined()
+ expect(activePane.focus).toHaveBeenCalled()
describe "when called with a path", ->
- [editor1, editor2] = []
- beforeEach ->
- rootView.attachToDom()
- editor1 = rootView.getActiveEditor()
- editor2 = editor1.splitRight()
- rootView.open('b')
- editor2.loadPreviousEditSession()
- editor1.focus()
+ describe "when the active pane already has an edit session item for the path being opened", ->
+ it "shows the existing edit session on the pane", ->
+ previousEditSession = activePane.activeItem
- describe "when allowActiveEditorChange is false (the default)", ->
- activeEditor = null
- beforeEach ->
- activeEditor = rootView.getActiveEditor()
+ editSession = rootView.open('b')
+ expect(activePane.activeItem).toBe editSession
+ expect(editSession).not.toBe previousEditSession
- describe "when the active editor has an edit session for the given path", ->
- it "re-activates the existing edit session", ->
- expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
- previousEditSession = activeEditor.activeEditSession
+ editSession = rootView.open(previousEditSession.getPath())
+ expect(editSession).toBe previousEditSession
+ expect(activePane.activeItem).toBe editSession
- editSession = rootView.open('b')
- expect(activeEditor.activeEditSession).not.toBe previousEditSession
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
+ expect(activePane.focus).toHaveBeenCalled()
- editSession = rootView.open('a')
- expect(activeEditor.activeEditSession).toBe previousEditSession
- expect(editSession).toBe previousEditSession
+ describe "when the active pane does not have an edit session item for the path being opened", ->
+ it "creates a new edit session for the given path in the active editor", ->
+ editSession = rootView.open('b')
+ expect(activePane.items.length).toBe 2
+ expect(activePane.activeItem).toBe editSession
+ expect(activePane.focus).toHaveBeenCalled()
- describe "when the active editor does not have an edit session for the given path", ->
- it "creates a new edit session for the given path in the active editor", ->
- editSession = rootView.open('b')
- expect(activeEditor.editSessions.length).toBe 2
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
-
- describe "when the 'allowActiveEditorChange' option is true", ->
- describe "when the active editor has an edit session for the given path", ->
- it "re-activates the existing edit session regardless of whether any other editor also has an edit session for the path", ->
- activeEditor = rootView.getActiveEditor()
- expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
- previousEditSession = activeEditor.activeEditSession
-
- editSession = rootView.open('b')
- expect(activeEditor.activeEditSession).not.toBe previousEditSession
- expect(editSession).toBe activeEditor.activeEditSession
-
- editSession = rootView.open('a', allowActiveEditorChange: true)
- expect(activeEditor.activeEditSession).toBe previousEditSession
- expect(editSession).toBe activeEditor.activeEditSession
-
- describe "when the active editor does *not* have an edit session for the given path", ->
- describe "when another editor has an edit session for the path", ->
- it "focuses the other editor and activates its edit session for the path", ->
- expect(rootView.getActiveEditor()).toBe editor1
- editSession = rootView.open('b', allowActiveEditorChange: true)
- expect(rootView.getActiveEditor()).toBe editor2
- expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
-
- describe "when no other editor has an edit session for the path either", ->
- it "creates a new edit session for the path on the current active editor", ->
- path = require.resolve('fixtures/sample.js')
- editSession = rootView.open(path, allowActiveEditorChange: true)
- expect(rootView.getActiveEditor()).toBe editor1
- expect(editor1.getPath()).toBe path
- expect(editSession).toBe rootView.getActiveEditor().activeEditSession
-
- describe ".saveAll()", ->
- it "saves all open editors", ->
- project.setPath('/tmp')
- file1 = '/tmp/atom-temp1.txt'
- file2 = '/tmp/atom-temp2.txt'
- fs.write(file1, "file1")
- fs.write(file2, "file2")
- rootView.open(file1)
-
- editor1 = rootView.getActiveEditor()
- buffer1 = editor1.activeEditSession.buffer
- expect(buffer1.getText()).toBe("file1")
- expect(buffer1.isModified()).toBe(false)
- buffer1.setText('edited1')
- expect(buffer1.isModified()).toBe(true)
-
- editor2 = editor1.splitRight()
- editor2.edit(project.buildEditSessionForPath('atom-temp2.txt'))
- buffer2 = editor2.activeEditSession.buffer
- expect(buffer2.getText()).toBe("file2")
- expect(buffer2.isModified()).toBe(false)
- buffer2.setText('edited2')
- expect(buffer2.isModified()).toBe(true)
-
- rootView.saveAll()
-
- expect(buffer1.isModified()).toBe(false)
- expect(fs.read(buffer1.getPath())).toBe("edited1")
- expect(buffer2.isModified()).toBe(false)
- expect(fs.read(buffer2.getPath())).toBe("edited2")
+ describe "when the changeFocus option is false", ->
+ it "does not focus the active pane", ->
+ editSession = rootView.open('b', changeFocus: false)
+ expect(activePane.focus).not.toHaveBeenCalled()
describe "window:toggle-invisibles event", ->
it "shows/hides invisibles in all open and future editors", ->
rootView.height(200)
rootView.attachToDom()
- rightEditor = rootView.getActiveEditor()
+ rightEditor = rootView.getActiveView()
rightEditor.setText(" \t ")
leftEditor = rightEditor.splitLeft()
expect(rightEditor.find(".line:first").text()).toBe " "
@@ -636,7 +311,7 @@ describe "RootView", ->
count++
rootView.eachEditor(callback)
expect(count).toBe 1
- expect(callbackEditor).toBe rootView.getActiveEditor()
+ expect(callbackEditor).toBe rootView.getActiveView()
it "invokes the callback for new editor", ->
count = 0
@@ -648,9 +323,9 @@ describe "RootView", ->
rootView.eachEditor(callback)
count = 0
callbackEditor = null
- rootView.getActiveEditor().splitRight()
+ rootView.getActiveView().splitRight()
expect(count).toBe 1
- expect(callbackEditor).toBe rootView.getActiveEditor()
+ expect(callbackEditor).toBe rootView.getActiveView()
describe ".eachBuffer(callback)", ->
beforeEach ->
@@ -664,7 +339,7 @@ describe "RootView", ->
count++
rootView.eachBuffer(callback)
expect(count).toBe 1
- expect(callbackBuffer).toBe rootView.getActiveEditor().getBuffer()
+ expect(callbackBuffer).toBe rootView.getActiveView().getBuffer()
it "invokes the callback for new buffer", ->
count = 0
@@ -678,4 +353,4 @@ describe "RootView", ->
callbackBuffer = null
rootView.open(require.resolve('fixtures/sample.txt'))
expect(count).toBe 1
- expect(callbackBuffer).toBe rootView.getActiveEditor().getBuffer()
+ expect(callbackBuffer).toBe rootView.getActiveView().getBuffer()
diff --git a/spec/app/text-mate-grammar-spec.coffee b/spec/app/text-mate-grammar-spec.coffee
index 91f220ecb..07d06ca02 100644
--- a/spec/app/text-mate-grammar-spec.coffee
+++ b/spec/app/text-mate-grammar-spec.coffee
@@ -262,7 +262,7 @@ describe "TextMateGrammar", ->
describe "when the grammar is CSON", ->
it "loads the grammar and correctly parses a keyword", ->
spyOn(syntax, 'addGrammar')
- pack = new TextMatePackage(fixturesProject.resolve("packages/package-with-a-cson-grammar.tmbundle"))
+ pack = new TextMatePackage(project.resolve("packages/package-with-a-cson-grammar.tmbundle"))
pack.load()
grammar = pack.grammars[0]
expect(grammar).toBeTruthy()
diff --git a/spec/app/theme-spec.coffee b/spec/app/theme-spec.coffee
index 5afebc983..518482292 100644
--- a/spec/app/theme-spec.coffee
+++ b/spec/app/theme-spec.coffee
@@ -26,7 +26,7 @@ describe "@load(name)", ->
expect($(".editor").css("padding-right")).not.toBe("102px")
expect($(".editor").css("padding-bottom")).not.toBe("103px")
- themePath = fixturesProject.resolve('themes/theme-with-package-file')
+ themePath = project.resolve('themes/theme-with-package-file')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe("101px")
expect($(".editor").css("padding-right")).toBe("102px")
@@ -36,7 +36,7 @@ describe "@load(name)", ->
it "loads and applies the stylesheet", ->
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
- themePath = fixturesProject.resolve('themes/theme-stylesheet.css')
+ themePath = project.resolve('themes/theme-stylesheet.css')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe "1234px"
@@ -46,7 +46,7 @@ describe "@load(name)", ->
expect($(".editor").css("padding-right")).not.toBe "20px"
expect($(".editor").css("padding-bottom")).not.toBe "30px"
- themePath = fixturesProject.resolve('themes/theme-without-package-file')
+ themePath = project.resolve('themes/theme-without-package-file')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe "10px"
expect($(".editor").css("padding-right")).toBe "20px"
diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee
index 944628904..86bd50520 100644
--- a/spec/app/tokenized-buffer-spec.coffee
+++ b/spec/app/tokenized-buffer-spec.coffee
@@ -18,7 +18,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains soft-tabs", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
+ editSession = project.buildEditSession('sample.js', autoIndent: false)
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
editSession.setVisible(true)
@@ -299,7 +299,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains hard-tabs", ->
beforeEach ->
tabLength = 2
- editSession = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', { tabLength })
+ editSession = project.buildEditSession('sample-with-tabs.coffee', { tabLength })
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
editSession.setVisible(true)
@@ -328,7 +328,7 @@ describe "TokenizedBuffer", ->
describe "when a Git commit message file is tokenized", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('COMMIT_EDITMSG', autoIndent: false)
+ editSession = project.buildEditSession('COMMIT_EDITMSG', autoIndent: false)
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
editSession.setVisible(true)
@@ -355,7 +355,7 @@ describe "TokenizedBuffer", ->
describe "when a C++ source file is tokenized", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('includes.cc', autoIndent: false)
+ editSession = project.buildEditSession('includes.cc', autoIndent: false)
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
editSession.setVisible(true)
@@ -386,7 +386,7 @@ describe "TokenizedBuffer", ->
describe "when a Ruby source file is tokenized", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('hello.rb', autoIndent: false)
+ editSession = project.buildEditSession('hello.rb', autoIndent: false)
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
editSession.setVisible(true)
@@ -403,7 +403,7 @@ describe "TokenizedBuffer", ->
describe "when an Objective-C source file is tokenized", ->
beforeEach ->
- editSession = fixturesProject.buildEditSessionForPath('function.mm', autoIndent: false)
+ editSession = project.buildEditSession('function.mm', autoIndent: false)
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
editSession.setVisible(true)
diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee
index 32f9397f1..12f79248f 100644
--- a/spec/app/window-spec.coffee
+++ b/spec/app/window-spec.coffee
@@ -46,16 +46,17 @@ describe "Window", ->
expect(window.close).toHaveBeenCalled()
describe ".reload()", ->
- it "returns false when no buffers are modified", ->
+ beforeEach ->
spyOn($native, "reload")
+
+ it "returns false when no buffers are modified", ->
window.reload()
expect($native.reload).toHaveBeenCalled()
- it "shows alert when a modifed buffer exists", ->
+ it "shows an alert when a modifed buffer exists", ->
rootView.open('sample.js')
- rootView.getActiveEditor().insertText("hi")
+ rootView.getActiveView().insertText("hi")
spyOn(atom, "confirm")
- spyOn($native, "reload")
window.reload()
expect($native.reload).not.toHaveBeenCalled()
expect(atom.confirm).toHaveBeenCalled()
@@ -103,13 +104,13 @@ describe "Window", ->
it "unsubscribes from all buffers", ->
rootView.open('sample.js')
- editor1 = rootView.getActiveEditor()
- editor2 = editor1.splitRight()
- expect(window.rootView.getEditors().length).toBe 2
+ buffer = rootView.getActivePaneItem().buffer
+ rootView.getActivePane().splitRight()
+ expect(window.rootView.find('.editor').length).toBe 2
window.shutdown()
- expect(editor1.getBuffer().subscriptionCount()).toBe 0
+ expect(buffer.subscriptionCount()).toBe 0
it "only serializes window state the first time it is called", ->
deactivateSpy = spyOn(atom, "setRootViewStateForPath").andCallThrough()
@@ -129,3 +130,34 @@ describe "Window", ->
window.installAtomCommand(commandPath)
expect(fs.exists(commandPath)).toBeTruthy()
expect(fs.read(commandPath).length).toBeGreaterThan 1
+
+ describe ".deserialize(state)", ->
+ class Foo
+ @deserialize: ({name}) -> new Foo(name)
+ constructor: (@name) ->
+
+ beforeEach ->
+ registerDeserializer(Foo)
+
+ afterEach ->
+ unregisterDeserializer(Foo)
+
+ it "calls deserialize on the deserializer for the given state object, or returns undefined if one can't be found", ->
+ object = deserialize({ deserializer: 'Foo', name: 'Bar' })
+ expect(object.name).toBe 'Bar'
+ expect(deserialize({ deserializer: 'Bogus' })).toBeUndefined()
+
+ describe "when the deserializer has a version", ->
+ beforeEach ->
+ Foo.version = 2
+
+ describe "when the deserialized state has a matching version", ->
+ it "attempts to deserialize the state", ->
+ object = deserialize({ deserializer: 'Foo', version: 2, name: 'Bar' })
+ expect(object.name).toBe 'Bar'
+
+ describe "when the deserialized state has a non-matching version", ->
+ it "returns undefined", ->
+ expect(deserialize({ deserializer: 'Foo', version: 3, name: 'Bar' })).toBeUndefined()
+ expect(deserialize({ deserializer: 'Foo', version: 1, name: 'Bar' })).toBeUndefined()
+ expect(deserialize({ deserializer: 'Foo', name: 'Bar' })).toBeUndefined()
diff --git a/spec/fixtures/packages/package-with-activation-events/main.coffee b/spec/fixtures/packages/package-with-activation-events/main.coffee
index a591812bd..a860be2bb 100644
--- a/spec/fixtures/packages/package-with-activation-events/main.coffee
+++ b/spec/fixtures/packages/package-with-activation-events/main.coffee
@@ -2,7 +2,7 @@ module.exports =
activationEventCallCount: 0
activate: ->
- rootView.getActiveEditor()?.command 'activation-event', =>
+ rootView.getActiveView()?.command 'activation-event', =>
@activationEventCallCount++
serialize: ->
diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee
index 910c8d238..4632db296 100644
--- a/spec/spec-helper.coffee
+++ b/spec/spec-helper.coffee
@@ -30,9 +30,8 @@ jasmine.getEnv().defaultTimeoutInterval = 5000
beforeEach ->
jQuery.fx.off = true
- window.fixturesProject = new Project(require.resolve('fixtures'))
- window.project = fixturesProject
- window.git = Git.open(fixturesProject.getPath())
+ window.project = new Project(require.resolve('fixtures'))
+ window.git = Git.open(project.getPath())
window.project.on 'path-changed', ->
window.git?.destroy()
window.git = Git.open(window.project.getPath())
@@ -56,7 +55,7 @@ beforeEach ->
# make editor display updates synchronous
spyOn(Editor.prototype, 'requestDisplayUpdate').andCallFake -> @updateDisplay()
- spyOn(RootView.prototype, 'updateWindowTitle').andCallFake ->
+ spyOn(RootView.prototype, 'setTitle').andCallFake (@title) ->
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
spyOn(File.prototype, "detectResurrectionAfterDelay").andCallFake -> @detectResurrection()
@@ -73,7 +72,7 @@ afterEach ->
keymap.bindingSets = bindingSetsToRestore
keymap.bindingSetsByFirstKeystrokeToRestore = bindingSetsByFirstKeystrokeToRestore
if rootView?
- rootView.deactivate()
+ rootView.deactivate?()
window.rootView = null
if project?
project.destroy()
@@ -83,6 +82,8 @@ afterEach ->
window.git = null
$('#jasmine-content').empty()
ensureNoPathSubscriptions()
+ atom.pendingModals = [[]]
+ atom.presentingModal = false
waits(0) # yield to ui thread to make screen update more frequently
window.loadPackage = (name, options) ->
diff --git a/src/app/atom.coffee b/src/app/atom.coffee
index b5149622e..2e4635c18 100644
--- a/src/app/atom.coffee
+++ b/src/app/atom.coffee
@@ -10,11 +10,14 @@ originalSendMessageToBrowserProcess = atom.sendMessageToBrowserProcess
_.extend atom,
exitWhenDone: window.location.params.exitWhenDone
+ devMode: window.location.params.devMode
loadedThemes: []
pendingBrowserProcessCallbacks: {}
loadedPackages: []
activatedAtomPackages: []
atomPackageStates: {}
+ presentingModal: false
+ pendingModals: [[]]
getPathToOpen: ->
@getWindowState('pathToOpen') ? window.location.params.pathToOpen
@@ -101,15 +104,50 @@ _.extend atom,
@sendMessageToBrowserProcess('newWindow', args)
confirm: (message, detailedMessage, buttonLabelsAndCallbacks...) ->
- args = [message, detailedMessage]
- callbacks = []
- while buttonLabelsAndCallbacks.length
- args.push(buttonLabelsAndCallbacks.shift())
- callbacks.push(buttonLabelsAndCallbacks.shift())
- @sendMessageToBrowserProcess('confirm', args, callbacks)
+ wrapCallback = (callback) => => @dismissModal(callback)
+ @presentModal =>
+ args = [message, detailedMessage]
+ callbacks = []
+ while buttonLabelsAndCallbacks.length
+ do =>
+ buttonLabel = buttonLabelsAndCallbacks.shift()
+ buttonCallback = buttonLabelsAndCallbacks.shift()
+ args.push(buttonLabel)
+ callbacks.push(=> @dismissModal(buttonCallback))
+ @sendMessageToBrowserProcess('confirm', args, callbacks)
showSaveDialog: (callback) ->
- @sendMessageToBrowserProcess('showSaveDialog', [], callback)
+ @presentModal =>
+ @sendMessageToBrowserProcess('showSaveDialog', [], (path) => @dismissModal(callback, path))
+
+ presentModal: (fn) ->
+ if @presentingModal
+ @pushPendingModal(fn)
+ else
+ @presentingModal = true
+ fn()
+
+ dismissModal: (fn, args...) ->
+ @pendingModals.push([]) # prioritize any modals presented during dismiss callback
+ fn?(args...)
+ @presentingModal = false
+ if fn = @shiftPendingModal()
+ _.delay (=> @presentModal(fn)), 50 # let view update before next dialog
+
+ pushPendingModal: (fn) ->
+ # pendingModals is a stack of queues. enqueue to top of stack.
+ stackSize = @pendingModals.length
+ @pendingModals[stackSize - 1].push(fn)
+
+ shiftPendingModal: ->
+ # pop pendingModals stack if its top queue is empty, otherwise shift off the topmost queue
+ stackSize = @pendingModals.length
+ currentQueueSize = @pendingModals[stackSize - 1].length
+ if stackSize > 1 and currentQueueSize == 0
+ @pendingModals.pop()
+ @shiftPendingModal()
+ else
+ @pendingModals[stackSize - 1].shift()
toggleDevTools: ->
@sendMessageToBrowserProcess('toggleDevTools')
diff --git a/src/app/buffer-change-operation.coffee b/src/app/buffer-change-operation.coffee
index b3ffda35e..cbbf68b0a 100644
--- a/src/app/buffer-change-operation.coffee
+++ b/src/app/buffer-change-operation.coffee
@@ -73,7 +73,7 @@ class BufferChangeOperation
event = { oldRange, newRange, oldText, newText }
@updateMarkers(event)
@buffer.trigger 'changed', event
- @buffer.scheduleStoppedChangingEvent()
+ @buffer.scheduleModifiedEvents()
@resumeMarkerObservation()
@buffer.trigger 'markers-updated'
diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee
index 092f0d797..00d0483e4 100644
--- a/src/app/buffer.coffee
+++ b/src/app/buffer.coffee
@@ -69,7 +69,7 @@ class Buffer
@file.on "removed", =>
@updateCachedDiskContents()
- @trigger "contents-modified", {differsFromDisk: true}
+ @triggerModifiedStatusChanged(@isModified())
@file.on "moved", =>
@trigger "path-changed", this
@@ -78,6 +78,7 @@ class Buffer
@trigger 'will-reload'
@updateCachedDiskContents()
@setText(@cachedDiskContents)
+ @triggerModifiedStatusChanged(false)
@trigger 'reloaded'
updateCachedDiskContents: ->
@@ -252,6 +253,7 @@ class Buffer
@setPath(path)
@cachedDiskContents = @getText()
@file.write(@getText())
+ @triggerModifiedStatusChanged(false)
@trigger 'saved'
isModified: ->
@@ -424,13 +426,20 @@ class Buffer
return unless path
git?.checkoutHead(path)
- scheduleStoppedChangingEvent: ->
+ scheduleModifiedEvents: ->
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
stoppedChangingCallback = =>
@stoppedChangingTimeout = null
- @trigger 'contents-modified', {differsFromDisk: @isModified()}
+ modifiedStatus = @isModified()
+ @trigger 'contents-modified', modifiedStatus
+ @triggerModifiedStatusChanged(modifiedStatus)
@stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay)
+ triggerModifiedStatusChanged: (modifiedStatus) ->
+ return if modifiedStatus is @previousModifiedStatus
+ @previousModifiedStatus = modifiedStatus
+ @trigger 'modified-status-changed', modifiedStatus
+
fileExists: ->
@file.exists()
diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee
index b160d5468..0d48a1f4e 100644
--- a/src/app/edit-session.coffee
+++ b/src/app/edit-session.coffee
@@ -14,17 +14,22 @@ module.exports =
class EditSession
registerDeserializer(this)
- @deserialize: (state, project) ->
+ @deserialize: (state) ->
if fs.exists(state.buffer)
- session = project.buildEditSessionForPath(state.buffer)
+ session = project.buildEditSession(state.buffer)
else
console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists" if state.buffer
- session = project.buildEditSessionForPath(null)
+ session = project.buildEditSession(null)
session.setScrollTop(state.scrollTop)
session.setScrollLeft(state.scrollLeft)
session.setCursorScreenPosition(state.cursorScreenPosition)
session
+ @identifiedBy: 'path'
+
+ @deserializesToSameObject: (state, editSession) ->
+ state.path
+
scrollTop: 0
scrollLeft: 0
languageMode: null
@@ -43,17 +48,38 @@ class EditSession
@addCursorAtScreenPosition([0, 0])
@buffer.retain()
- @subscribe @buffer, "path-changed", => @trigger "path-changed"
+ @subscribe @buffer, "path-changed", =>
+ @project.setPath(fs.directory(@getPath())) unless @project.getPath()?
+ @trigger "title-changed"
+ @trigger "path-changed"
@subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted"
@subscribe @buffer, "markers-updated", => @mergeCursors()
+ @subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed"
@preserveCursorPositionOnBufferReload()
@subscribe @displayBuffer, "changed", (e) =>
@trigger 'screen-lines-changed', e
+ getViewClass: ->
+ require 'editor'
+
+ getTitle: ->
+ if path = @getPath()
+ fs.base(path)
+ else
+ 'untitled'
+
+ getLongTitle: ->
+ if path = @getPath()
+ fileName = fs.base(path)
+ directory = fs.base(fs.directory(path))
+ "#{fileName} - #{directory}"
+ else
+ 'untitled'
+
destroy: ->
- throw new Error("Edit session already destroyed") if @destroyed
+ return if @destroyed
@destroyed = true
@unsubscribe()
@buffer.release()
@@ -130,6 +156,7 @@ class EditSession
saveAs: (path) -> @buffer.saveAs(path)
getFileExtension: -> @buffer.getExtension()
getPath: -> @buffer.getPath()
+ getUri: -> @getPath()
isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
getEofBufferPosition: -> @buffer.getEofPosition()
diff --git a/src/app/editor.coffee b/src/app/editor.coffee
index 5b5bc6502..1e2c0b464 100644
--- a/src/app/editor.coffee
+++ b/src/app/editor.coffee
@@ -16,7 +16,6 @@ class Editor extends View
fontSize: 20
showInvisibles: false
showIndentGuide: false
- autosave: false
autoIndent: true
autoIndentOnPaste: false
nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-"
@@ -49,8 +48,6 @@ class Editor extends View
lineCache: null
isFocused: false
activeEditSession: null
- closedEditSessions: null
- editSessions: null
attached: false
lineOverdraw: 10
pendingChanges: null
@@ -58,15 +55,12 @@ class Editor extends View
newSelections: null
redrawOnReattach: false
- @deserialize: (state) ->
- editor = new Editor(mini: state.mini, deserializing: true)
- editSessions = state.editSessions.map (state) -> EditSession.deserialize(state, project)
- editor.pushEditSession(editSession) for editSession in editSessions
- editor.setActiveEditSessionIndex(state.activeEditSessionIndex)
- editor.isFocused = state.isFocused
- editor
+ initialize: (editSessionOrOptions) ->
+ if editSessionOrOptions instanceof EditSession
+ editSession = editSessionOrOptions
+ else
+ {editSession, @mini} = (editSessionOrOptions ? {})
- initialize: ({editSession, @mini, deserializing} = {}) ->
requireStylesheet 'editor.css'
@id = Editor.nextEditorId++
@@ -76,8 +70,6 @@ class Editor extends View
@handleEvents()
@cursorViews = []
@selectionViews = []
- @editSessions = []
- @closedEditSessions = []
@pendingChanges = []
@newCursors = []
@newSelections = []
@@ -91,18 +83,8 @@ class Editor extends View
tabLength: 2
softTabs: true
)
- else if not deserializing
- throw new Error("Editor must be constructed with an 'editSession' or 'mini: true' param")
-
- serialize: ->
- @saveScrollPositionForActiveEditSession()
- deserializer: "Editor"
- editSessions: @editSessions.map (session) -> session.serialize()
- activeEditSessionIndex: @getActiveEditSessionIndex()
- isFocused: @isFocused
-
- copy: ->
- Editor.deserialize(@serialize(), rootView)
+ else
+ throw new Error("Must supply an EditSession or mini: true")
bindKeys: ->
editorBindings =
@@ -155,9 +137,6 @@ class Editor extends View
'core:select-down': @selectDown
'core:select-to-top': @selectToTop
'core:select-to-bottom': @selectToBottom
- 'core:close': @destroyActiveEditSession
- 'editor:save': @save
- 'editor:save-as': @saveAs
'editor:newline-below': @insertNewlineBelow
'editor:newline-above': @insertNewlineAbove
'editor:toggle-soft-tabs': @toggleSoftTabs
@@ -167,32 +146,14 @@ class Editor extends View
'editor:fold-current-row': @foldCurrentRow
'editor:unfold-current-row': @unfoldCurrentRow
'editor:fold-selection': @foldSelection
- 'editor:split-left': @splitLeft
- 'editor:split-right': @splitRight
- 'editor:split-up': @splitUp
- 'editor:split-down': @splitDown
- 'editor:show-next-buffer': @loadNextEditSession
- 'editor:show-buffer-1': => @setActiveEditSessionIndex(0) if @editSessions[0]
- 'editor:show-buffer-2': => @setActiveEditSessionIndex(1) if @editSessions[1]
- 'editor:show-buffer-3': => @setActiveEditSessionIndex(2) if @editSessions[2]
- 'editor:show-buffer-4': => @setActiveEditSessionIndex(3) if @editSessions[3]
- 'editor:show-buffer-5': => @setActiveEditSessionIndex(4) if @editSessions[4]
- 'editor:show-buffer-6': => @setActiveEditSessionIndex(5) if @editSessions[5]
- 'editor:show-buffer-7': => @setActiveEditSessionIndex(6) if @editSessions[6]
- 'editor:show-buffer-8': => @setActiveEditSessionIndex(7) if @editSessions[7]
- 'editor:show-buffer-9': => @setActiveEditSessionIndex(8) if @editSessions[8]
- 'editor:show-previous-buffer': @loadPreviousEditSession
'editor:toggle-line-comments': @toggleLineCommentsInSelection
'editor:log-cursor-scope': @logCursorScope
'editor:checkout-head-revision': @checkoutHead
- 'editor:close-other-edit-sessions': @destroyInactiveEditSessions
- 'editor:close-all-edit-sessions': @destroyAllEditSessions
'editor:select-grammar': @selectGrammar
'editor:copy-path': @copyPathToPasteboard
'editor:move-line-up': @moveLineUp
'editor:move-line-down': @moveLineDown
'editor:duplicate-line': @duplicateLine
- 'editor:undo-close-session': @undoDestroySession
'editor:toggle-indent-guide': => config.set('editor.showIndentGuide', !config.get('editor.showIndentGuide'))
'editor:save-debug-snapshot': @saveDebugSnapshot
@@ -343,7 +304,7 @@ class Editor extends View
checkoutHead: -> @getBuffer().checkoutHead()
setText: (text) -> @getBuffer().setText(text)
getText: -> @getBuffer().getText()
- getPath: -> @getBuffer().getPath()
+ getPath: -> @activeEditSession?.getPath()
getLineCount: -> @getBuffer().getLineCount()
getLastBufferRow: -> @getBuffer().getLastRow()
getTextInRange: (range) -> @getBuffer().getTextInRange(range)
@@ -367,13 +328,11 @@ class Editor extends View
false
@hiddenInput.on 'focus', =>
- rootView?.editorFocused(this)
@isFocused = true
@addClass 'is-focused'
@hiddenInput.on 'focusout', =>
@isFocused = false
- @autosave() if config.get "editor.autosave"
@removeClass 'is-focused'
@underlayer.on 'click', (e) =>
@@ -436,10 +395,7 @@ class Editor extends View
e.pageX = @renderedLines.offset().left
onMouseDown(e)
- @subscribe syntax, 'grammars-loaded', =>
- @reloadGrammar()
- for session in @editSessions
- session.reloadGrammar() unless session is @activeEditSession
+ @subscribe syntax, 'grammars-loaded', => @reloadGrammar()
@scrollView.on 'scroll', =>
if @scrollView.scrollLeft() == 0
@@ -481,86 +437,16 @@ class Editor extends View
@trigger 'editor:attached', [this]
edit: (editSession) ->
- index = @editSessions.indexOf(editSession)
- index = @pushEditSession(editSession) if index == -1
- @setActiveEditSessionIndex(index)
-
- pushEditSession: (editSession) ->
- index = @editSessions.length
- @editSessions.push(editSession)
- @closedEditSessions = @closedEditSessions.filter ({path})->
- path isnt editSession.getPath()
- editSession.on 'destroyed', => @editSessionDestroyed(editSession)
- @trigger 'editor:edit-session-added', [editSession, index]
- index
-
- getBuffer: -> @activeEditSession.buffer
-
- undoDestroySession: ->
- return unless @closedEditSessions.length > 0
-
- {path, index} = @closedEditSessions.pop()
- rootView.open(path)
- activeIndex = @getActiveEditSessionIndex()
- @moveEditSessionToIndex(activeIndex, index) if index < activeIndex
-
- destroyActiveEditSession: ->
- @destroyEditSessionIndex(@getActiveEditSessionIndex())
-
- destroyEditSessionIndex: (index, callback) ->
- return if @mini
-
- editSession = @editSessions[index]
- destroySession = =>
- path = editSession.getPath()
- @closedEditSessions.push({path, index}) if path
- editSession.destroy()
- callback?(index)
-
- if editSession.isModified() and not editSession.hasEditors()
- @promptToSaveDirtySession(editSession, destroySession)
- else
- destroySession()
-
- destroyInactiveEditSessions: ->
- destroyIndex = (index) =>
- index++ if index is @getActiveEditSessionIndex()
- @destroyEditSessionIndex(index, destroyIndex) if @editSessions[index]
- destroyIndex(0)
-
- destroyAllEditSessions: ->
- destroyIndex = (index) =>
- @destroyEditSessionIndex(index, destroyIndex) if @editSessions[index]
- destroyIndex(0)
-
- editSessionDestroyed: (editSession) ->
- index = @editSessions.indexOf(editSession)
- @loadPreviousEditSession() if index is @getActiveEditSessionIndex() and @editSessions.length > 1
- _.remove(@editSessions, editSession)
- @trigger 'editor:edit-session-removed', [editSession, index]
- @remove() if @editSessions.length is 0
-
- loadNextEditSession: ->
- nextIndex = (@getActiveEditSessionIndex() + 1) % @editSessions.length
- @setActiveEditSessionIndex(nextIndex)
-
- loadPreviousEditSession: ->
- previousIndex = @getActiveEditSessionIndex() - 1
- previousIndex = @editSessions.length - 1 if previousIndex < 0
- @setActiveEditSessionIndex(previousIndex)
-
- getActiveEditSessionIndex: ->
- return index for session, index in @editSessions when session == @activeEditSession
-
- setActiveEditSessionIndex: (index) ->
- throw new Error("Edit session not found") unless @editSessions[index]
+ return if editSession is @activeEditSession
if @activeEditSession
- @autosave() if config.get "editor.autosave"
@saveScrollPositionForActiveEditSession()
@activeEditSession.off(".editor")
- @activeEditSession = @editSessions[index]
+ @activeEditSession = editSession
+
+ return unless @activeEditSession?
+
@activeEditSession.setVisible(true)
@activeEditSession.on "contents-conflicted.editor", =>
@@ -571,11 +457,18 @@ class Editor extends View
@trigger 'editor:path-changed'
@trigger 'editor:path-changed'
- @trigger 'editor:active-edit-session-changed', [@activeEditSession, index]
@resetDisplay()
if @attached and @activeEditSession.buffer.isInConflict()
- setTimeout(( =>@showBufferConflictAlert(@activeEditSession)), 0) # Display after editSession has a chance to display
+ _.defer => @showBufferConflictAlert(@activeEditSession) # Display after editSession has a chance to display
+
+ getModel: ->
+ @activeEditSession
+
+ setModel: (editSession) ->
+ @edit(editSession)
+
+ getBuffer: -> @activeEditSession.buffer
showBufferConflictAlert: (editSession) ->
atom.confirm(
@@ -585,30 +478,6 @@ class Editor extends View
"Cancel"
)
- moveEditSessionToIndex: (fromIndex, toIndex) ->
- return if fromIndex is toIndex
- editSession = @editSessions.splice(fromIndex, 1)
- @editSessions.splice(toIndex, 0, editSession[0])
- @trigger 'editor:edit-session-order-changed', [editSession, fromIndex, toIndex]
- @setActiveEditSessionIndex(toIndex)
-
- moveEditSessionToEditor: (fromIndex, toEditor, toIndex) ->
- fromEditSession = @editSessions[fromIndex]
- toEditSession = fromEditSession.copy()
- @destroyEditSessionIndex(fromIndex)
- toEditor.edit(toEditSession)
- toEditor.moveEditSessionToIndex(toEditor.getActiveEditSessionIndex(), toIndex)
-
- activateEditSessionForPath: (path) ->
- for editSession, index in @editSessions
- if editSession.buffer.getPath() == path
- @setActiveEditSessionIndex(index)
- return @activeEditSession
- false
-
- getOpenBufferPaths: ->
- editSession.buffer.getPath() for editSession in @editSessions when editSession.buffer.getPath()?
-
scrollTop: (scrollTop, options={}) ->
return @cachedScrollTop or 0 unless scrollTop?
maxScrollTop = @verticalScrollbar.prop('scrollHeight') - @verticalScrollbar.height()
@@ -723,22 +592,6 @@ class Editor extends View
@removeClass 'soft-wrap'
$(window).off 'resize', @_setSoftWrapColumn
- save: (session=@activeEditSession, onSuccess) ->
- if @getPath()
- session.save()
- onSuccess?()
- else
- @saveAs(session, onSuccess)
-
- saveAs: (session=@activeEditSession, onSuccess) ->
- atom.showSaveDialog (path) =>
- if path
- session.saveAs(path)
- onSuccess?()
-
- autosave: ->
- @save() if @getPath()?
-
setFontSize: (fontSize) ->
headTag = $("head")
styleTag = headTag.find("style.font-size")
@@ -781,54 +634,33 @@ class Editor extends View
@updateLayerDimensions()
@requestDisplayUpdate()
- newSplitEditor: (editSession) ->
- new Editor { editSession: editSession ? @activeEditSession.copy() }
+ splitLeft: (items...) ->
+ @pane()?.splitLeft(items...).activeView
- splitLeft: (editSession) ->
- @pane()?.splitLeft(@newSplitEditor(editSession)).wrappedView
+ splitRight: (items...) ->
+ @pane()?.splitRight(items...).activeView
- splitRight: (editSession) ->
- @pane()?.splitRight(@newSplitEditor(editSession)).wrappedView
+ splitUp: (items...) ->
+ @pane()?.splitUp(items...).activeView
- splitUp: (editSession) ->
- @pane()?.splitUp(@newSplitEditor(editSession)).wrappedView
-
- splitDown: (editSession) ->
- @pane()?.splitDown(@newSplitEditor(editSession)).wrappedView
+ splitDown: (items...) ->
+ @pane()?.splitDown(items...).activeView
pane: ->
- @parent('.pane').view()
-
- promptToSaveDirtySession: (session, callback) ->
- path = session.getPath()
- filename = if path then fs.base(path) else "untitled buffer"
- atom.confirm(
- "'#{filename}' has changes, do you want to save them?"
- "Your changes will be lost if you don't save them"
- "Save", => @save(session, callback),
- "Cancel", null
- "Don't Save", callback
- )
+ @closest('.pane').view()
remove: (selector, keepData) ->
return super if keepData or @removed
@trigger 'editor:will-be-removed'
- if @pane() then @pane().remove() else super
+ super
rootView?.focus()
afterRemove: ->
@removed = true
- @destroyEditSessions()
+ @activeEditSession?.destroy()
$(window).off(".editor-#{@id}")
$(document).off(".editor-#{@id}")
- getEditSessions: ->
- new Array(@editSessions...)
-
- destroyEditSessions: ->
- for session in @getEditSessions()
- session.destroy()
-
getCursorView: (index) ->
index ?= @cursorViews.length - 1
@cursorViews[index]
@@ -933,7 +765,8 @@ class Editor extends View
@pendingDisplayUpdate = false
updateDisplay: (options={}) ->
- return unless @attached
+ return unless @attached and @activeEditSession
+ return if @activeEditSession.destroyed
@updateRenderedLines()
@highlightCursorLine()
@updateCursorViews()
diff --git a/src/app/git.coffee b/src/app/git.coffee
index 761084c57..b9f469b1c 100644
--- a/src/app/git.coffee
+++ b/src/app/git.coffee
@@ -28,6 +28,7 @@ class Git
statuses: null
upstream: null
+ statusTask: null
constructor: (path, options={}) ->
@statuses = {}
@@ -58,7 +59,11 @@ class Git
@path ?= fs.absolute(@getRepo().getPath())
destroy: ->
- @statusTask?.abort()
+ if @statusTask?
+ @statusTask.abort()
+ @statusTask.off()
+ @statusTask = null
+
@getRepo().destroy()
@repo = null
@unsubscribe()
@@ -130,8 +135,16 @@ class Git
@getRepo().isSubmodule(@relativize(path))
refreshStatus: ->
- @statusTask = new RepositoryStatusTask(this)
- @statusTask.start()
+ if @statusTask?
+ @statusTask.off()
+ @statusTask.one 'task-completed', =>
+ @statusTask = null
+ @refreshStatus()
+ else
+ @statusTask = new RepositoryStatusTask(this)
+ @statusTask.one 'task-completed', =>
+ @statusTask = null
+ @statusTask.start()
getDirectoryStatus: (directoryPath) ->
directoryPath = "#{directoryPath}/"
diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson
index 680f25c17..04cc02488 100644
--- a/src/app/keymaps/atom.cson
+++ b/src/app/keymaps/atom.cson
@@ -1,4 +1,6 @@
'body':
+ 'meta-s': 'core:save'
+ 'meta-S': 'core:save-as'
'enter': 'core:confirm'
'escape': 'core:cancel'
'meta-w': 'core:close'
@@ -30,6 +32,26 @@
'ctrl-tab': 'window:focus-next-pane'
'ctrl-meta-f': 'window:toggle-full-screen'
+ 'ctrl-|': 'pane:split-right'
+ 'ctrl-w v': 'pane:split-right'
+ 'ctrl--': 'pane:split-down'
+ 'ctrl-w s': 'pane:split-down'
+
+ 'meta-{': 'pane:show-previous-item'
+ 'meta-}': 'pane:show-next-item'
+ 'alt-meta-left': 'pane:show-previous-item'
+ 'alt-meta-right': 'pane:show-next-item'
+ 'meta-1': 'pane:show-item-1'
+ 'meta-2': 'pane:show-item-2'
+ 'meta-3': 'pane:show-item-3'
+ 'meta-4': 'pane:show-item-4'
+ 'meta-5': 'pane:show-item-5'
+ 'meta-6': 'pane:show-item-6'
+ 'meta-7': 'pane:show-item-7'
+ 'meta-8': 'pane:show-item-8'
+ 'meta-9': 'pane:show-item-9'
+ 'meta-T': 'pane:reopen-closed-item'
+
'.tool-panel':
'meta-escape': 'tool-panel:unfocus'
'escape': 'core:close'
diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson
index 467119136..8b192fece 100644
--- a/src/app/keymaps/editor.cson
+++ b/src/app/keymaps/editor.cson
@@ -1,9 +1,4 @@
-'body':
- 'meta-T': 'editor:undo-close-session'
-
'.editor':
- 'meta-s': 'editor:save'
- 'meta-S': 'editor:save-as'
'enter': 'editor:newline'
'meta-enter': 'editor:newline-below'
'meta-shift-enter': 'editor:newline-above'
@@ -15,26 +10,9 @@
'ctrl-{': 'editor:fold-all'
'ctrl-}': 'editor:unfold-all'
'alt-meta-ctrl-f': 'editor:fold-selection'
- 'ctrl-|': 'editor:split-right'
- 'ctrl-w v': 'editor:split-right'
- 'ctrl--': 'editor:split-down'
- 'ctrl-w s': 'editor:split-down'
'shift-tab': 'editor:outdent-selected-rows'
'meta-[': 'editor:outdent-selected-rows'
'meta-]': 'editor:indent-selected-rows'
- 'meta-{': 'editor:show-previous-buffer'
- 'meta-}': 'editor:show-next-buffer'
- 'alt-meta-left': 'editor:show-previous-buffer'
- 'alt-meta-right': 'editor:show-next-buffer'
- 'meta-1': 'editor:show-buffer-1'
- 'meta-2': 'editor:show-buffer-2'
- 'meta-3': 'editor:show-buffer-3'
- 'meta-4': 'editor:show-buffer-4'
- 'meta-5': 'editor:show-buffer-5'
- 'meta-6': 'editor:show-buffer-6'
- 'meta-7': 'editor:show-buffer-7'
- 'meta-8': 'editor:show-buffer-8'
- 'meta-9': 'editor:show-buffer-9'
'meta-/': 'editor:toggle-line-comments'
'ctrl-W': 'editor:select-word'
'meta-alt-p': 'editor:log-cursor-scope'
diff --git a/src/app/pane-grid.coffee b/src/app/pane-axis.coffee
similarity index 95%
rename from src/app/pane-grid.coffee
rename to src/app/pane-axis.coffee
index 3d54fd64d..9aa4fda81 100644
--- a/src/app/pane-grid.coffee
+++ b/src/app/pane-axis.coffee
@@ -2,7 +2,7 @@ $ = require 'jquery'
{View} = require 'space-pen'
module.exports =
-class PaneGrid extends View
+class PaneAxis extends View
@deserialize: ({children}) ->
childViews = children.map (child) -> deserialize(child)
new this(childViews)
diff --git a/src/app/pane-column.coffee b/src/app/pane-column.coffee
index f00c7ed23..43ba40cbb 100644
--- a/src/app/pane-column.coffee
+++ b/src/app/pane-column.coffee
@@ -1,9 +1,9 @@
$ = require 'jquery'
_ = require 'underscore'
-PaneGrid = require 'pane-grid'
+PaneAxis = require 'pane-axis'
module.exports =
-class PaneColumn extends PaneGrid
+class PaneColumn extends PaneAxis
@content: ->
@div class: 'column'
diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee
new file mode 100644
index 000000000..6fc367e56
--- /dev/null
+++ b/src/app/pane-container.coffee
@@ -0,0 +1,97 @@
+{View} = require 'space-pen'
+Pane = require 'pane'
+$ = require 'jquery'
+
+module.exports =
+class PaneContainer extends View
+ registerDeserializer(this)
+
+ @deserialize: ({root}) ->
+ container = new PaneContainer
+ container.append(deserialize(root)) if root
+ container
+
+ @content: ->
+ @div id: 'panes'
+
+ initialize: ->
+ @destroyedItemStates = []
+
+ serialize: ->
+ deserializer: 'PaneContainer'
+ root: @getRoot()?.serialize()
+
+ focusNextPane: ->
+ panes = @getPanes()
+ if panes.length > 1
+ currentIndex = panes.indexOf(@getFocusedPane())
+ nextIndex = (currentIndex + 1) % panes.length
+ panes[nextIndex].focus()
+ true
+ else
+ false
+
+ makeNextPaneActive: ->
+ panes = @getPanes()
+ currentIndex = panes.indexOf(@getActivePane())
+ nextIndex = (currentIndex + 1) % panes.length
+ panes[nextIndex].makeActive()
+
+ reopenItem: ->
+ if lastItemState = @destroyedItemStates.pop()
+ if activePane = @getActivePane()
+ activePane.showItem(deserialize(lastItemState))
+ true
+ else
+ @append(new Pane(deserialize(lastItemState)))
+
+ itemDestroyed: (item) ->
+ state = item.serialize?()
+ state.uri ?= item.getUri?()
+ @destroyedItemStates.push(state) if state?
+
+ itemAdded: (item) ->
+ itemUri = item.getUri?()
+ @destroyedItemStates = @destroyedItemStates.filter (itemState) ->
+ itemState.uri isnt itemUri
+
+ getRoot: ->
+ @children().first().view()
+
+ saveAll: ->
+ pane.saveItems() for pane in @getPanes()
+
+ 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
+ cancel: => @off 'pane:attached', paneAttached
+
+ getFocusedPane: ->
+ @find('.pane:has(:focus)').view()
+
+ getActivePane: ->
+ @find('.pane.active').view() ? @find('.pane:first').view()
+
+ getActivePaneItem: ->
+ @getActivePane()?.activeItem
+
+ getActiveView: ->
+ @getActivePane()?.activeView
+
+ adjustPaneDimensions: ->
+ if root = @getRoot()
+ root.css(width: '100%', height: '100%', top: 0, left: 0)
+ root.adjustDimensions()
+
+ afterAttach: ->
+ @adjustPaneDimensions()
diff --git a/src/app/pane-row.coffee b/src/app/pane-row.coffee
index c729e0b9a..ce7a09f82 100644
--- a/src/app/pane-row.coffee
+++ b/src/app/pane-row.coffee
@@ -1,9 +1,9 @@
$ = require 'jquery'
_ = require 'underscore'
-PaneGrid = require 'pane-grid'
+PaneAxis = require 'pane-axis'
module.exports =
-class PaneRow extends PaneGrid
+class PaneRow extends PaneAxis
@content: ->
@div class: 'row'
diff --git a/src/app/pane.coffee b/src/app/pane.coffee
index 5b0e630f6..ef29ce72f 100644
--- a/src/app/pane.coffee
+++ b/src/app/pane.coffee
@@ -1,4 +1,6 @@
{View} = require 'space-pen'
+$ = require 'jquery'
+_ = require 'underscore'
PaneRow = require 'pane-row'
PaneColumn = require 'pane-column'
@@ -6,58 +8,314 @@ module.exports =
class Pane extends View
@content: (wrappedView) ->
@div class: 'pane', =>
- @subview 'wrappedView', wrappedView if wrappedView
+ @div class: 'item-views', outlet: 'itemViews'
- @deserialize: ({wrappedView}) ->
- new Pane(deserialize(wrappedView))
+ @deserialize: ({items, focused, activeItemUri}) ->
+ pane = new Pane(items.map((item) -> deserialize(item))...)
+ pane.showItemForUri(activeItemUri) if activeItemUri
+ pane.focusOnAttach = true if focused
+ pane
+
+ activeItem: null
+ items: null
+
+ initialize: (@items...) ->
+ @viewsByClassName = {}
+ @showItem(@items[0])
+
+ @command 'core:close', @destroyActiveItem
+ @command 'core:save', @saveActiveItem
+ @command 'core:save-as', @saveActiveItemAs
+ @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()
+ @command 'pane:split-right', => @splitRight()
+ @command 'pane:split-up', => @splitUp()
+ @command 'pane:split-down', => @splitDown()
+ @command 'pane:close', => @destroyItems()
+ @command 'pane:close-other-items', => @destroyInactiveItems()
+ @on 'focus', => @activeView.focus(); false
+ @on 'focusin', => @makeActive()
+ @on 'focusout', => @autosaveActiveItem()
+
+ afterAttach: (onDom) ->
+ if @focusOnAttach and onDom
+ @focusOnAttach = null
+ @focus()
+
+ return if @attached
+ @attached = true
+ @trigger 'pane:attached'
+
+ makeActive: ->
+ for pane in @getContainer().getPanes() when pane isnt this
+ pane.makeInactive()
+ wasActive = @isActive()
+ @addClass('active')
+ @trigger 'pane:became-active' unless wasActive
+
+ makeInactive: ->
+ @removeClass('active')
+
+ isActive: ->
+ @hasClass('active')
+
+ getItems: ->
+ new Array(@items...)
+
+ showNextItem: =>
+ index = @getActiveItemIndex()
+ if index < @items.length - 1
+ @showItemAtIndex(index + 1)
+ else
+ @showItemAtIndex(0)
+
+ showPreviousItem: =>
+ index = @getActiveItemIndex()
+ if index > 0
+ @showItemAtIndex(index - 1)
+ else
+ @showItemAtIndex(@items.length - 1)
+
+ getActiveItemIndex: ->
+ @items.indexOf(@activeItem)
+
+ showItemAtIndex: (index) ->
+ @showItem(@itemAtIndex(index))
+
+ itemAtIndex: (index) ->
+ @items[index]
+
+ showItem: (item) ->
+ return if !item? or item is @activeItem
+
+ if @activeItem
+ @activeItem.off? 'title-changed', @activeItemTitleChanged
+ @autosaveActiveItem()
+
+ 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()
+ view.focus() if isFocused
+ @activeItem = item
+ @activeView = view
+ @trigger 'pane:active-item-changed', [item]
+
+ activeItemTitleChanged: =>
+ @trigger 'pane:active-item-title-changed'
+
+ addItem: (item) ->
+ return if _.include(@items, item)
+ index = @getActiveItemIndex() + 1
+ @items.splice(index, 0, item)
+ @getContainer().itemAdded(item)
+ @trigger 'pane:item-added', [item, index]
+ item
+
+ destroyActiveItem: =>
+ @destroyItem(@activeItem)
+ false
+
+ destroyItem: (item) ->
+ container = @getContainer()
+ reallyDestroyItem = =>
+ @removeItem(item)
+ container.itemDestroyed(item)
+ item.destroy?()
+
+ @autosaveItem(item)
+
+ if item.isModified?()
+ @promptToSaveItem(item, reallyDestroyItem)
+ else
+ reallyDestroyItem()
+
+ destroyItems: ->
+ @destroyItem(item) for item in @getItems()
+
+ destroyInactiveItems: ->
+ @destroyItem(item) for item in @getItems() when item isnt @activeItem
+
+ promptToSaveItem: (item, nextAction) ->
+ uri = item.getUri()
+ atom.confirm(
+ "'#{item.getTitle()}' has changes, do you want to save them?"
+ "Your changes will be lost if close this item without saving."
+ "Save", => @saveItem(item, nextAction)
+ "Cancel", null
+ "Don't Save", nextAction
+ )
+
+ saveActiveItem: =>
+ @saveItem(@activeItem)
+
+ saveActiveItemAs: =>
+ @saveItemAs(@activeItem)
+
+ saveItem: (item, nextAction) ->
+ if item.getUri?()
+ item.save()
+ nextAction?()
+ else
+ @saveItemAs(item, nextAction)
+
+ saveItemAs: (item, nextAction) ->
+ return unless item.saveAs?
+ atom.showSaveDialog (path) =>
+ if path
+ item.saveAs(path)
+ nextAction?()
+
+ saveItems: =>
+ @saveItem(item) for item in @getItems()
+
+ autosaveActiveItem: ->
+ @autosaveItem(@activeItem)
+
+ autosaveItem: (item) ->
+ @saveItem(item) if config.get('core.autosave') and item.getUri?()
+
+ removeItem: (item) ->
+ index = @items.indexOf(item)
+ return if index == -1
+
+ @showNextItem() if item is @activeItem and @items.length > 1
+ _.remove(@items, item)
+ @cleanupItemView(item)
+ @trigger 'pane:item-removed', [item, index]
+
+ moveItem: (item, newIndex) ->
+ oldIndex = @items.indexOf(item)
+ @items.splice(oldIndex, 1)
+ @items.splice(newIndex, 0, item)
+ @trigger 'pane:item-moved', [item, newIndex]
+
+ moveItemToPane: (item, pane, index) ->
+ @removeItem(item)
+ pane.addItem(item, index)
+
+ itemForUri: (uri) ->
+ _.detect @items, (item) -> item.getUri?() is uri
+
+ showItemForUri: (uri) ->
+ @showItem(@itemForUri(uri))
+
+ cleanupItemView: (item) ->
+ if item instanceof $
+ viewToRemove = item
+ else
+ viewClass = item.getViewClass()
+ otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass
+ unless otherItemsForView.length
+ viewToRemove = @viewsByClassName[viewClass.name]
+ viewToRemove?.setModel(null)
+ delete @viewsByClassName[viewClass.name]
+
+ if @items.length > 0
+ viewToRemove?.remove()
+ else
+ @remove()
+
+ viewForItem: (item) ->
+ if item instanceof $
+ item
+ else
+ viewClass = item.getViewClass()
+ if view = @viewsByClassName[viewClass.name]
+ view.setModel(item)
+ else
+ view = @viewsByClassName[viewClass.name] = new viewClass(item)
+ view
+
+ viewForActiveItem: ->
+ @viewForItem(@activeItem)
serialize: ->
deserializer: "Pane"
- wrappedView: @wrappedView?.serialize()
+ focused: @is(':has(:focus)')
+ activeItemUri: @activeItem.getUri?() if typeof @activeItem.serialize is 'function'
+ items: _.compact(@getItems().map (item) -> item.serialize?())
adjustDimensions: -> # do nothing
- horizontalGridUnits: ->
- 1
+ horizontalGridUnits: -> 1
- verticalGridUnits: ->
- 1
+ verticalGridUnits: -> 1
- splitUp: (view) ->
- @split(view, 'column', 'before')
+ splitUp: (items...) ->
+ @split(items, 'column', 'before')
- splitDown: (view) ->
- @split(view, 'column', 'after')
+ splitDown: (items...) ->
+ @split(items, 'column', 'after')
- splitLeft: (view) ->
- @split(view, 'row', 'before')
+ splitLeft: (items...) ->
+ @split(items, 'row', 'before')
- splitRight: (view) ->
- @split(view, 'row', 'after')
+ splitRight: (items...) ->
+ @split(items, 'row', 'after')
- split: (view, axis, side) ->
+ split: (items, axis, side) ->
unless @parent().hasClass(axis)
@buildPaneAxis(axis)
.insertBefore(this)
.append(@detach())
- pane = new Pane(view)
+ items = [@copyActiveItem()] unless items.length
+ pane = new Pane(items...)
this[side](pane)
- rootView.adjustPaneDimensions()
- view.focus?()
+ @getContainer().adjustPaneDimensions()
+ pane.focus()
pane
- remove: (selector, keepData) ->
- return super if keepData
- # find parent elements before removing from dom
- parentAxis = @parent('.row, .column')
- super
- if parentAxis.children().length == 1
- sibling = parentAxis.children().detach()
- parentAxis.replaceWith(sibling)
- rootView.adjustPaneDimensions()
-
buildPaneAxis: (axis) ->
switch axis
when 'row' then new PaneRow
when 'column' then new PaneColumn
+
+ getContainer: ->
+ @closest('#panes').view()
+
+ copyActiveItem: ->
+ deserialize(@activeItem.serialize())
+
+ remove: (selector, keepData) ->
+ return super if keepData
+
+ # find parent elements before removing from dom
+ container = @getContainer()
+ parentAxis = @parent('.row, .column')
+
+ if @is(':has(:focus)')
+ container.focusNextPane() or rootView?.focus()
+ else if @isActive()
+ container.makeNextPaneActive()
+
+ super
+
+ if parentAxis.children().length == 1
+ sibling = parentAxis.children()
+ siblingFocused = sibling.is(':has(:focus)')
+ sibling.detach()
+ parentAxis.replaceWith(sibling)
+ sibling.focus() if siblingFocused
+ container.adjustPaneDimensions()
+ container.trigger 'pane:removed', [this]
+
+ afterRemove: ->
+ item.destroy?() for item in @getItems()
diff --git a/src/app/project.coffee b/src/app/project.coffee
index d31c12238..fac99ce8d 100644
--- a/src/app/project.coffee
+++ b/src/app/project.coffee
@@ -95,10 +95,10 @@ class Project
getSoftWrap: -> @softWrap
setSoftWrap: (@softWrap) ->
- buildEditSessionForPath: (filePath, editSessionOptions={}) ->
- @buildEditSession(@bufferForPath(filePath), editSessionOptions)
+ buildEditSession: (filePath, editSessionOptions={}) ->
+ @buildEditSessionForBuffer(@bufferForPath(filePath), editSessionOptions)
- buildEditSession: (buffer, editSessionOptions) ->
+ buildEditSessionForBuffer: (buffer, editSessionOptions) ->
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
options.project = this
options.buffer = buffer
diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee
index 558143889..2d3b8fa83 100644
--- a/src/app/root-view.coffee
+++ b/src/app/root-view.coffee
@@ -10,28 +10,29 @@ Project = require 'project'
Pane = require 'pane'
PaneColumn = require 'pane-column'
PaneRow = require 'pane-row'
+PaneContainer = require 'pane-container'
+EditSession = require 'edit-session'
module.exports =
class RootView extends View
registerDeserializers(this, Pane, PaneRow, PaneColumn, Editor)
+ @version: 1
+
@configDefaults:
ignoredNames: [".git", ".svn", ".DS_Store"]
disabledPackages: []
- @content: ->
+ @content: ({panes}={}) ->
@div id: 'root-view', =>
@div id: 'horizontal', outlet: 'horizontal', =>
@div id: 'vertical', outlet: 'vertical', =>
- @div id: 'panes', outlet: 'panes'
+ @subview 'panes', panes ? new PaneContainer
- @deserialize: ({ panesViewState, packageStates, projectPath }) ->
- atom.atomPackageStates = packageStates ? {}
- rootView = new RootView
- rootView.setRootPane(deserialize(panesViewState)) if panesViewState
- rootView
-
- title: null
+ @deserialize: ({ panes, packages, projectPath }) ->
+ atom.atomPackageStates = packages ? {}
+ panes = deserialize(panes) if panes?.deserializer is 'PaneContainer'
+ new RootView({panes})
initialize: ->
@command 'toggle-dev-tools', => atom.toggleDevTools()
@@ -39,12 +40,11 @@ class RootView extends View
@subscribe $(window), 'focus', (e) =>
@handleFocus(e) if document.activeElement is document.body
- @on 'root-view:active-path-changed', (e, path) =>
- if path
- project.setPath(path) unless project.getRootDirectory()
- @setTitle(fs.base(path))
- else
- @setTitle("untitled")
+ project.on 'path-changed', => @updateTitle()
+ @on 'pane:became-active', => @updateTitle()
+ @on 'pane:active-item-changed', '.active.pane', => @updateTitle()
+ @on 'pane:removed', => @updateTitle() unless @getActivePane()
+ @on 'pane:active-item-title-changed', '.active.pane', => @updateTitle()
@command 'window:increase-font-size', =>
config.set("editor.fontSize", config.get("editor.fontSize") + 1)
@@ -53,26 +53,31 @@ class RootView extends View
fontSize = config.get "editor.fontSize"
config.set("editor.fontSize", fontSize - 1) if fontSize > 1
-
@command 'window:focus-next-pane', => @focusNextPane()
@command 'window:save-all', => @saveAll()
@command 'window:toggle-invisibles', =>
config.set("editor.showInvisibles", !config.get("editor.showInvisibles"))
@command 'window:toggle-ignored-files', =>
config.set("core.hideGitIgnoredFiles", not config.core.hideGitIgnoredFiles)
+
@command 'window:toggle-auto-indent', =>
config.set("editor.autoIndent", !config.get("editor.autoIndent"))
+
@command 'window:toggle-auto-indent-on-paste', =>
config.set("editor.autoIndentOnPaste", !config.get("editor.autoIndentOnPaste"))
+ @command 'pane:reopen-closed-item', =>
+ @panes.reopenItem()
+
serialize: ->
+ version: RootView.version
deserializer: 'RootView'
- panesViewState: @panes.children().view()?.serialize()
- packageStates: atom.serializeAtomPackages()
+ panes: @panes.serialize()
+ packages: atom.serializeAtomPackages()
handleFocus: (e) ->
- if @getActiveEditor()
- @getActiveEditor().focus()
+ if @getActivePane()
+ @getActivePane().focus()
false
else
@setTitle(null)
@@ -92,118 +97,57 @@ class RootView extends View
open: (path, options = {}) ->
changeFocus = options.changeFocus ? true
- allowActiveEditorChange = options.allowActiveEditorChange ? false
-
- unless editSession = @openInExistingEditor(path, allowActiveEditorChange, changeFocus)
- editSession = project.buildEditSessionForPath(path)
- editor = new Editor({editSession})
- pane = new Pane(editor)
- @panes.append(pane)
- if changeFocus
- editor.focus()
+ path = project.resolve(path) if path?
+ if activePane = @getActivePane()
+ if editSession = activePane.itemForUri(path)
+ activePane.showItem(editSession)
else
- @makeEditorActive(editor, changeFocus)
+ editSession = project.buildEditSession(path)
+ activePane.showItem(editSession)
+ else
+ editSession = project.buildEditSession(path)
+ activePane = new Pane(editSession)
+ @panes.append(activePane)
+ activePane.focus() if changeFocus
editSession
- openInExistingEditor: (path, allowActiveEditorChange, changeFocus) ->
- if activeEditor = @getActiveEditor()
- activeEditor.focus() if changeFocus
-
- path = project.resolve(path) if path
-
- if editSession = activeEditor.activateEditSessionForPath(path)
- return editSession
-
- if allowActiveEditorChange
- for editor in @getEditors()
- if editSession = editor.activateEditSessionForPath(path)
- @makeEditorActive(editor, changeFocus)
- return editSession
-
- editSession = project.buildEditSessionForPath(path)
- activeEditor.edit(editSession)
- editSession
-
- editorFocused: (editor) ->
- @makeEditorActive(editor) if @panes.containsElement(editor)
-
- makeEditorActive: (editor, focus) ->
- if focus
- editor.focus()
- return
-
- previousActiveEditor = @panes.find('.editor.active').view()
- previousActiveEditor?.removeClass('active').off('.root-view')
- editor.addClass('active')
-
- if not editor.mini
- editor.on 'editor:path-changed.root-view', =>
- @trigger 'root-view:active-path-changed', editor.getPath()
- if not previousActiveEditor or editor.getPath() != previousActiveEditor.getPath()
- @trigger 'root-view:active-path-changed', editor.getPath()
-
- activeKeybindings: ->
- keymap.bindingsForElement(document.activeElement)
-
- getTitle: ->
- @title or "untitled"
+ updateTitle: ->
+ if projectPath = project.getPath()
+ if item = @getActivePaneItem()
+ @setTitle("#{item.getTitle()} - #{projectPath}")
+ else
+ @setTitle(projectPath)
+ else
+ @setTitle('untitled')
setTitle: (title) ->
- projectPath = project.getPath()
- if not projectPath
- @title = "untitled"
- else if title
- @title = "#{title} – #{projectPath}"
- else
- @title = projectPath
-
- @updateWindowTitle()
-
- updateWindowTitle: ->
- document.title = @title
+ document.title = title
getEditors: ->
- @panes.find('.pane > .editor').map(-> $(this).view()).toArray()
+ @panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
getModifiedBuffers: ->
modifiedBuffers = []
- for editor in @getEditors()
- for session in editor.editSessions
- modifiedBuffers.push session.buffer if session.buffer.isModified()
-
+ for pane in @getPanes()
+ for item in pane.getItems() when item instanceof EditSession
+ modifiedBuffers.push item.buffer if item.buffer.isModified()
modifiedBuffers
getOpenBufferPaths: ->
_.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths()))
- getActiveEditor: ->
- if (editor = @panes.find('.editor.active')).length
- editor.view()
- else
- @panes.find('.editor:first').view()
+ getActivePane: ->
+ @panes.getActivePane()
- getActiveEditSession: ->
- @getActiveEditor()?.activeEditSession
+ getActivePaneItem: ->
+ @panes.getActivePaneItem()
- focusNextPane: ->
- panes = @panes.find('.pane')
- currentIndex = panes.toArray().indexOf(@getFocusedPane()[0])
- nextIndex = (currentIndex + 1) % panes.length
- panes.eq(nextIndex).view().wrappedView.focus()
+ getActiveView: ->
+ @panes.getActiveView()
- getFocusedPane: ->
- @panes.find('.pane:has(:focus)')
-
- setRootPane: (pane) ->
- @panes.empty()
- @panes.append(pane)
- @adjustPaneDimensions()
-
- adjustPaneDimensions: ->
- rootPane = @panes.children().first().view()
- rootPane?.css(width: '100%', height: '100%', top: 0, left: 0)
- rootPane?.adjustDimensions()
+ focusNextPane: -> @panes.focusNextPane()
+ getFocusedPane: -> @panes.getFocusedPane()
remove: ->
editor.remove() for editor in @getEditors()
@@ -211,7 +155,16 @@ class RootView extends View
super
saveAll: ->
- editor.save() for editor in @getEditors()
+ @panes.saveAll()
+
+ eachPane: (callback) ->
+ @panes.eachPane(callback)
+
+ getPanes: ->
+ @panes.getPanes()
+
+ indexOfPane: (pane) ->
+ @panes.indexOfPane(pane)
eachEditor: (callback) ->
callback(editor) for editor in @getEditors()
@@ -223,10 +176,3 @@ class RootView extends View
eachBuffer: (callback) ->
project.eachBuffer(callback)
- indexOfPane: (pane) ->
- index = -1
- for p, idx in @panes.find('.pane')
- if pane.is(p)
- index = idx
- break
- index
diff --git a/src/app/sortable-list.coffee b/src/app/sortable-list.coffee
index 08d727646..ebdc675a1 100644
--- a/src/app/sortable-list.coffee
+++ b/src/app/sortable-list.coffee
@@ -14,7 +14,9 @@ class SortableList extends View
@on 'drop', '.sortable', @onDrop
onDragStart: (event) =>
- return false if !@shouldAllowDrag(event)
+ unless @shouldAllowDrag(event)
+ event.preventDefault()
+ return
el = @getSortableElement(event)
el.addClass 'is-dragging'
@@ -45,9 +47,8 @@ class SortableList extends View
true
getDroppedElement: (event) ->
- idx = event.originalEvent.dataTransfer.getData 'sortable-index'
- @find ".sortable:eq(#{idx})"
+ index = event.originalEvent.dataTransfer.getData('sortable-index')
+ @find(".sortable:eq(#{index})")
getSortableElement: (event) ->
- el = $(event.target)
- if !el.hasClass('sortable') then el.closest('.sortable') else el
+ $(event.target).closest('.sortable')
diff --git a/src/app/window.coffee b/src/app/window.coffee
index a4c514b15..321a60f56 100644
--- a/src/app/window.coffee
+++ b/src/app/window.coffee
@@ -151,8 +151,16 @@ window.registerDeserializers = (args...) ->
window.registerDeserializer = (klass) ->
deserializers[klass.name] = klass
+window.unregisterDeserializer = (klass) ->
+ delete deserializers[klass.name]
+
window.deserialize = (state) ->
- deserializers[state?.deserializer]?.deserialize(state)
+ if deserializer = getDeserializer(state)
+ return if deserializer.version? and deserializer.version isnt state.version
+ deserializer.deserialize(state)
+
+window.getDeserializer = (state) ->
+ deserializers[state?.deserializer]
window.measure = (description, fn) ->
start = new Date().getTime()
diff --git a/src/packages/autocomplete/spec/autocomplete-spec.coffee b/src/packages/autocomplete/spec/autocomplete-spec.coffee
index 8d019d69b..9bfa9af7f 100644
--- a/src/packages/autocomplete/spec/autocomplete-spec.coffee
+++ b/src/packages/autocomplete/spec/autocomplete-spec.coffee
@@ -17,8 +17,8 @@ describe "Autocomplete", ->
autocompletePackage = window.loadPackage("autocomplete")
expect(AutocompleteView.prototype.initialize).not.toHaveBeenCalled()
- leftEditor = rootView.getActiveEditor()
- rightEditor = rootView.getActiveEditor().splitRight()
+ leftEditor = rootView.getActiveView()
+ rightEditor = leftEditor.splitRight()
leftEditor.trigger 'autocomplete:attach'
expect(leftEditor.find('.autocomplete')).toExist()
@@ -40,7 +40,7 @@ describe "AutocompleteView", ->
beforeEach ->
window.rootView = new RootView
- editor = new Editor(editSession: fixturesProject.buildEditSessionForPath('sample.js'))
+ editor = new Editor(editSession: project.buildEditSession('sample.js'))
window.loadPackage('autocomplete')
autocomplete = new AutocompleteView(editor)
miniEditor = autocomplete.miniEditor
diff --git a/src/packages/autoflow/spec/autoflow-spec.coffee b/src/packages/autoflow/spec/autoflow-spec.coffee
index d717bcf85..190ac238c 100644
--- a/src/packages/autoflow/spec/autoflow-spec.coffee
+++ b/src/packages/autoflow/spec/autoflow-spec.coffee
@@ -7,7 +7,7 @@ describe "Autoflow package", ->
window.rootView = new RootView
rootView.open()
window.loadPackage 'autoflow'
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
config.set('editor.preferredLineLength', 30)
describe "autoflow:reflow-paragraph", ->
diff --git a/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee
index ff56fec57..099be0926 100644
--- a/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee
+++ b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee
@@ -8,7 +8,7 @@ describe "bracket matching", ->
rootView.open('sample.js')
window.loadPackage('bracket-matcher')
rootView.attachToDom()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
editSession = editor.activeEditSession
buffer = editSession.buffer
diff --git a/src/packages/command-logger/spec/command-logger-spec.coffee b/src/packages/command-logger/spec/command-logger-spec.coffee
index 7210c171f..4305f9ada 100644
--- a/src/packages/command-logger/spec/command-logger-spec.coffee
+++ b/src/packages/command-logger/spec/command-logger-spec.coffee
@@ -9,7 +9,7 @@ describe "CommandLogger", ->
rootView.open('sample.js')
commandLogger = window.loadPackage('command-logger').packageMain
commandLogger.eventLog = {}
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
describe "when a command is triggered", ->
it "records the number of times the command is triggered", ->
diff --git a/src/packages/command-palette/spec/command-palette-spec.coffee b/src/packages/command-palette/spec/command-palette-spec.coffee
index 2b115bc68..414c53996 100644
--- a/src/packages/command-palette/spec/command-palette-spec.coffee
+++ b/src/packages/command-palette/spec/command-palette-spec.coffee
@@ -19,8 +19,8 @@ describe "CommandPalette", ->
describe "when command-palette:toggle is triggered on the root view", ->
it "shows a list of all valid command descriptions, names, and keybindings for the previously focused element", ->
- keyBindings = _.losslessInvert(keymap.bindingsForElement(rootView.getActiveEditor()))
- for eventName, description of rootView.getActiveEditor().events()
+ keyBindings = _.losslessInvert(keymap.bindingsForElement(rootView.getActiveView()))
+ for eventName, description of rootView.getActiveView().events()
eventLi = palette.list.children("[data-event-name='#{eventName}']")
if description
expect(eventLi).toExist()
@@ -32,7 +32,7 @@ describe "CommandPalette", ->
expect(eventLi).not.toExist()
it "displays all commands registerd on the window", ->
- editorEvents = rootView.getActiveEditor().events()
+ editorEvents = rootView.getActiveView().events()
windowEvents = $(window).events()
expect(_.isEmpty(windowEvents)).toBeFalsy()
for eventName, description of windowEvents
@@ -60,19 +60,19 @@ describe "CommandPalette", ->
expect(palette.hasParent()).toBeTruthy()
palette.trigger 'command-palette:toggle'
expect(palette.hasParent()).toBeFalsy()
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when the command palette is cancelled", ->
it "focuses the root view and detaches the command palette", ->
expect(palette.hasParent()).toBeTruthy()
palette.cancel()
expect(palette.hasParent()).toBeFalsy()
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when an command selection is confirmed", ->
it "detaches the palette, then focuses the previously focused element and emits the selected command on it", ->
eventHandler = jasmine.createSpy 'eventHandler'
- activeEditor = rootView.getActiveEditor()
+ activeEditor = rootView.getActiveView()
{eventName} = palette.array[5]
activeEditor.preempt eventName, eventHandler
diff --git a/src/packages/command-panel/lib/command-panel-view.coffee b/src/packages/command-panel/lib/command-panel-view.coffee
index 3ede83809..2430c7ae0 100644
--- a/src/packages/command-panel/lib/command-panel-view.coffee
+++ b/src/packages/command-panel/lib/command-panel-view.coffee
@@ -120,7 +120,7 @@ class CommandPanelView extends View
@errorMessages.empty()
try
- @commandInterpreter.eval(command, rootView.getActiveEditSession()).done ({operationsToPreview, errorMessages}) =>
+ @commandInterpreter.eval(command, rootView.getActivePaneItem()).done ({operationsToPreview, errorMessages}) =>
@loadingMessage.hide()
@history.push(command)
@historyIndex = @history.length
@@ -155,12 +155,12 @@ class CommandPanelView extends View
@miniEditor.setText(@history[@historyIndex] or '')
repeatRelativeAddress: ->
- @commandInterpreter.repeatRelativeAddress(rootView.getActiveEditSession())
+ @commandInterpreter.repeatRelativeAddress(rootView.getActivePaneItem())
repeatRelativeAddressInReverse: ->
- @commandInterpreter.repeatRelativeAddressInReverse(rootView.getActiveEditSession())
+ @commandInterpreter.repeatRelativeAddressInReverse(rootView.getActivePaneItem())
setSelectionAsLastRelativeAddress: ->
- selection = rootView.getActiveEditor().getSelectedText()
+ selection = rootView.getActiveView().getSelectedText()
regex = _.escapeRegExp(selection)
@commandInterpreter.lastRelativeAddress = new CompositeCommand([new RegexAddress(regex)])
diff --git a/src/packages/command-panel/spec/command-interpreter-spec.coffee b/src/packages/command-panel/spec/command-interpreter-spec.coffee
index 289d87709..c4602b59f 100644
--- a/src/packages/command-panel/spec/command-interpreter-spec.coffee
+++ b/src/packages/command-panel/spec/command-interpreter-spec.coffee
@@ -6,12 +6,11 @@ EditSession = require 'edit-session'
_ = require 'underscore'
describe "CommandInterpreter", ->
- [project, interpreter, editSession, buffer] = []
+ [interpreter, editSession, buffer] = []
beforeEach ->
- project = new Project(fixturesProject.resolve('dir/'))
- interpreter = new CommandInterpreter(fixturesProject)
- editSession = fixturesProject.buildEditSessionForPath('sample.js')
+ interpreter = new CommandInterpreter(project)
+ editSession = project.buildEditSession('sample.js')
buffer = editSession.buffer
afterEach ->
@@ -418,7 +417,7 @@ describe "CommandInterpreter", ->
describe "X x/regex/", ->
it "returns selection operations for all regex matches in all the project's files", ->
editSession.destroy()
- project = new Project(fixturesProject.resolve('dir/'))
+ project.setPath(project.resolve('dir'))
interpreter = new CommandInterpreter(project)
operationsToPreview = null
@@ -428,7 +427,7 @@ describe "CommandInterpreter", ->
runs ->
expect(operationsToPreview.length).toBeGreaterThan 3
for operation in operationsToPreview
- editSession = project.buildEditSessionForPath(operation.getPath())
+ editSession = project.buildEditSession(operation.getPath())
editSession.setSelectedBufferRange(operation.execute(editSession))
expect(editSession.getSelectedText()).toMatch /a+/
editSession.destroy()
diff --git a/src/packages/command-panel/spec/command-panel-spec.coffee b/src/packages/command-panel/spec/command-panel-spec.coffee
index e6005c4be..213d14191 100644
--- a/src/packages/command-panel/spec/command-panel-spec.coffee
+++ b/src/packages/command-panel/spec/command-panel-spec.coffee
@@ -3,14 +3,14 @@ CommandPanelView = require 'command-panel/lib/command-panel-view'
_ = require 'underscore'
describe "CommandPanel", ->
- [editor, buffer, commandPanel] = []
+ [editSession, buffer, commandPanel] = []
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
rootView.enableKeymap()
- editor = rootView.getActiveEditor()
- buffer = editor.activeEditSession.buffer
+ editSession = rootView.getActivePaneItem()
+ buffer = editSession.buffer
commandPanelMain = window.loadPackage('command-panel', activateImmediately: true).packageMain
commandPanel = commandPanelMain.commandPanelView
commandPanel.history = []
@@ -219,41 +219,41 @@ describe "CommandPanel", ->
it "repeats the last search command if there is one", ->
rootView.trigger 'command-panel:repeat-relative-address'
- editor.setCursorScreenPosition([4, 0])
+ editSession.setCursorScreenPosition([4, 0])
commandPanel.execute("/current")
- expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[5,6], [5,13]]
rootView.trigger 'command-panel:repeat-relative-address'
- expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[6,6], [6,13]]
commandPanel.execute('s/r/R/g')
rootView.trigger 'command-panel:repeat-relative-address'
- expect(editor.getSelection().getBufferRange()).toEqual [[6,34], [6,41]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[6,34], [6,41]]
commandPanel.execute('0')
commandPanel.execute('/sort/ s/r/R/') # this contains a substitution... won't be repeated
rootView.trigger 'command-panel:repeat-relative-address'
- expect(editor.getSelection().getBufferRange()).toEqual [[3,31], [3,38]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[3,31], [3,38]]
describe "when command-panel:repeat-relative-address-in-reverse is triggered on the root view", ->
it "it repeats the last relative address in the reverse direction", ->
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
- editor.setCursorScreenPosition([6, 0])
+ editSession.setCursorScreenPosition([6, 0])
commandPanel.execute("/current")
- expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[6,6], [6,13]]
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
- expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[5,6], [5,13]]
describe "when command-panel:set-selection-as-regex-address is triggered on the root view", ->
it "sets the @lastRelativeAddress to a RegexAddress of the current selection", ->
rootView.open(require.resolve('fixtures/sample.js'))
- rootView.getActiveEditor().setSelectedBufferRange([[1,21],[1,28]])
+ rootView.getActivePaneItem().setSelectedBufferRange([[1,21],[1,28]])
commandInterpreter = commandPanel.commandInterpreter
expect(commandInterpreter.lastRelativeAddress).toBeUndefined()
@@ -267,7 +267,7 @@ describe "CommandPanel", ->
commandPanel.miniEditor.setText("foo")
commandPanel.miniEditor.setCursorBufferPosition([0, 0])
- rootView.getActiveEditor().trigger "command-panel:find-in-file"
+ rootView.getActiveView().trigger "command-panel:find-in-file"
expect(commandPanel.attach).toHaveBeenCalled()
expect(commandPanel.parent).not.toBeEmpty()
expect(commandPanel.miniEditor.getText()).toBe "/"
@@ -297,8 +297,8 @@ describe "CommandPanel", ->
describe "when the command returns operations to be previewed", ->
beforeEach ->
+ rootView.getActivePane().remove()
rootView.attachToDom()
- editor.remove()
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/quicksort/')
@@ -350,16 +350,14 @@ describe "CommandPanel", ->
expect(commandPanel).toBeVisible()
expect(commandPanel.errorMessages).not.toBeVisible()
-
describe "when the command contains an escaped character", ->
it "executes the command with the escaped character (instead of as a backslash followed by the character)", ->
rootView.trigger 'command-panel:toggle'
editSession = rootView.open(require.resolve 'fixtures/sample-with-tabs.coffee')
- editor.edit(editSession)
commandPanel.miniEditor.setText "/\\tsell"
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
- expect(editor.getSelectedBufferRange()).toEqual [[3,1],[3,6]]
+ expect(editSession.getSelectedBufferRange()).toEqual [[3,1],[3,6]]
describe "when move-up and move-down are triggerred on the editor", ->
it "navigates forward and backward through the command history", ->
@@ -470,11 +468,11 @@ describe "CommandPanel", ->
previewList.trigger 'core:confirm'
- editSession = rootView.getActiveEditSession()
+ editSession = rootView.getActivePaneItem()
expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
- expect(editor.isScreenRowVisible(editor.getCursorScreenRow())).toBeTruthy()
+ expect(rootView.getActiveView().isScreenRowVisible(editSession.getCursorScreenRow())).toBeTruthy()
expect(previewList.focus).toHaveBeenCalled()
expect(executeHandler).not.toHaveBeenCalled()
@@ -496,7 +494,7 @@ describe "CommandPanel", ->
previewList.find('li.operation:eq(4) span').mousedown()
expect(previewList.getSelectedOperation()).toBe operation
- editSession = rootView.getActiveEditSession()
+ editSession = rootView.getActivePaneItem()
expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(previewList.focus).toHaveBeenCalled()
diff --git a/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee
index 3eddc0c8e..d12f6f29a 100644
--- a/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee
+++ b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee
@@ -22,19 +22,18 @@ class FuzzyFinderView extends SelectList
@subscribe $(window), 'focus', => @reloadProjectPaths = true
@observeConfig 'fuzzy-finder.ignoredNames', => @reloadProjectPaths = true
- rootView.eachEditor (editor) ->
- editor.activeEditSession.lastOpened = (new Date) - 1
- editor.on 'editor:active-edit-session-changed', (e, editSession, index) ->
- editSession.lastOpened = (new Date) - 1
+ rootView.eachPane (pane) ->
+ pane.activeItem.lastOpened = (new Date) - 1
+ pane.on 'pane:active-item-changed', (e, item) -> item.lastOpened = (new Date) - 1
- @miniEditor.command 'editor:split-left', =>
- @splitOpenPath (editor, session) -> editor.splitLeft(session)
- @miniEditor.command 'editor:split-right', =>
- @splitOpenPath (editor, session) -> editor.splitRight(session)
- @miniEditor.command 'editor:split-down', =>
- @splitOpenPath (editor, session) -> editor.splitDown(session)
- @miniEditor.command 'editor:split-up', =>
- @splitOpenPath (editor, session) -> editor.splitUp(session)
+ @miniEditor.command 'pane:split-left', =>
+ @splitOpenPath (pane, session) -> pane.splitLeft(session)
+ @miniEditor.command 'pane:split-right', =>
+ @splitOpenPath (pane, session) -> pane.splitRight(session)
+ @miniEditor.command 'pane:split-down', =>
+ @splitOpenPath (pane, session) -> pane.splitDown(session)
+ @miniEditor.command 'pane:split-up', =>
+ @splitOpenPath (pane, session) -> pane.splitUp(session)
itemForElement: (path) ->
$$ ->
@@ -70,10 +69,8 @@ class FuzzyFinderView extends SelectList
splitOpenPath: (fn) ->
path = @getSelectedElement()
return unless path
-
- editor = rootView.getActiveEditor()
- if editor
- fn(editor, project.buildEditSessionForPath(path))
+ if pane = rootView.getActivePane()
+ fn(pane, project.buildEditSession(path))
else
@openPath(path)
@@ -118,7 +115,7 @@ class FuzzyFinderView extends SelectList
else
return unless project.getPath()?
@allowActiveEditorChange = false
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
currentWord = editor.getWordUnderCursor(wordRegex: @filenameRegex)
if currentWord.length == 0
@@ -177,7 +174,7 @@ class FuzzyFinderView extends SelectList
editSession.getPath()?
editSessions = _.sortBy editSessions, (editSession) =>
- if editSession is rootView.getActiveEditSession()
+ if editSession is rootView.getActivePaneItem()
0
else
-(editSession.lastOpened or 1)
diff --git a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
index f322d4613..ffaae8cb0 100644
--- a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
+++ b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
@@ -21,8 +21,8 @@ describe 'FuzzyFinder', ->
it "shows the FuzzyFinder or hides it and returns focus to the active editor if it already showing", ->
rootView.attachToDom()
expect(rootView.find('.fuzzy-finder')).not.toExist()
- rootView.find('.editor').trigger 'editor:split-right'
- [editor1, editor2] = rootView.find('.editor').map -> $(this).view()
+ rootView.getActiveView().splitRight()
+ [editor1, editor2] = rootView.getEditors()
expect(rootView.find('.fuzzy-finder')).not.toExist()
rootView.trigger 'fuzzy-finder:toggle-file-finder'
@@ -72,13 +72,13 @@ describe 'FuzzyFinder', ->
describe "when a path selection is confirmed", ->
it "opens the file associated with that path in the editor", ->
rootView.attachToDom()
- editor1 = rootView.getActiveEditor()
+ editor1 = rootView.getActiveView()
editor2 = editor1.splitRight()
- expect(rootView.getActiveEditor()).toBe editor2
+ expect(rootView.getActiveView()).toBe editor2
rootView.trigger 'fuzzy-finder:toggle-file-finder'
finderView.confirmed('dir/a')
- expectedPath = fixturesProject.resolve('dir/a')
+ expectedPath = project.resolve('dir/a')
expect(finderView.hasParent()).toBeFalsy()
expect(editor1.getPath()).not.toBe expectedPath
@@ -88,26 +88,26 @@ describe 'FuzzyFinder', ->
describe "when the selected path isn't a file that exists", ->
it "leaves the the tree view open, doesn't open the path in the editor, and displays an error", ->
rootView.attachToDom()
- path = rootView.getActiveEditor().getPath()
+ path = rootView.getActiveView().getPath()
rootView.trigger 'fuzzy-finder:toggle-file-finder'
finderView.confirmed('dir/this/is/not/a/file.txt')
expect(finderView.hasParent()).toBeTruthy()
- expect(rootView.getActiveEditor().getPath()).toBe path
+ expect(rootView.getActiveView().getPath()).toBe path
expect(finderView.find('.error').text().length).toBeGreaterThan 0
advanceClock(2000)
expect(finderView.find('.error').text().length).toBe 0
describe "buffer-finder behavior", ->
describe "toggling", ->
- describe "when the active editor contains edit sessions for buffers with paths", ->
+ describe "when there are pane items with paths", ->
beforeEach ->
rootView.open('sample.txt')
- it "shows the FuzzyFinder or hides it, returning focus to the active editor if", ->
+ it "shows the FuzzyFinder if it isn't showing, or hides it and returns focus to the active editor", ->
rootView.attachToDom()
expect(rootView.find('.fuzzy-finder')).not.toExist()
- rootView.find('.editor').trigger 'editor:split-right'
- [editor1, editor2] = rootView.find('.editor').map -> $(this).view()
+ rootView.getActiveView().splitRight()
+ [editor1, editor2] = rootView.getEditors()
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
expect(rootView.find('.fuzzy-finder')).toExist()
@@ -122,26 +122,17 @@ describe 'FuzzyFinder', ->
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
expect(finderView.miniEditor.getText()).toBe ''
- it "lists the paths of the current open buffers by most recently modified", ->
+ it "lists the paths of the current items, sorted by most recently opened but with the current item last", ->
rootView.attachToDom()
rootView.open 'sample-with-tabs.coffee'
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- children = finderView.list.children('li')
- expect(children.get(0).outerText).toBe "sample.txt"
- expect(children.get(1).outerText).toBe "sample.js"
- expect(children.get(2).outerText).toBe "sample-with-tabs.coffee"
+ expect(_.pluck(finderView.list.children('li'), 'outerText')).toEqual ['sample.txt', 'sample.js', 'sample-with-tabs.coffee']
+ rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
rootView.open 'sample.txt'
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- children = finderView.list.children('li')
- expect(children.get(0).outerText).toBe "sample-with-tabs.coffee"
- expect(children.get(1).outerText).toBe "sample.js"
- expect(children.get(2).outerText).toBe "sample.txt"
- expect(finderView.list.children('li').length).toBe 3
- expect(finderView.list.find("li:contains(sample.js)")).toExist()
- expect(finderView.list.find("li:contains(sample.txt)")).toExist()
- expect(finderView.list.find("li:contains(sample-with-tabs.coffee)")).toExist()
+ expect(_.pluck(finderView.list.children('li'), 'outerText')).toEqual ['sample-with-tabs.coffee', 'sample.js', 'sample.txt']
expect(finderView.list.children().first()).toHaveClass 'selected'
it "serializes the list of paths and their last opened time", ->
@@ -151,29 +142,26 @@ describe 'FuzzyFinder', ->
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
rootView.open()
- states = rootView.serialize().packageStates
+ states = rootView.serialize().packages
states = _.map states['fuzzy-finder'], (path, time) -> [ path, time ]
states = _.sortBy states, (path, time) -> -time
paths = [ 'sample-with-tabs.coffee', 'sample.txt', 'sample.js' ]
+
for [time, path] in states
expect(_.last path.split '/').toBe paths.shift()
expect(time).toBeGreaterThan 50000
- describe "when the active editor only contains edit sessions for anonymous buffers", ->
+ describe "when there are only panes with anonymous items", ->
it "does not open", ->
- editor = rootView.getActiveEditor()
- editor.edit(project.buildEditSessionForPath())
- editor.loadPreviousEditSession()
- editor.destroyActiveEditSession()
- expect(editor.getOpenBufferPaths().length).toBe 0
+ rootView.getActivePane().remove()
+ rootView.open()
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
expect(rootView.find('.fuzzy-finder')).not.toExist()
- describe "when there is no active editor", ->
+ describe "when there are no pane items", ->
it "does not open", ->
- rootView.getActiveEditor().destroyActiveEditSession()
- expect(rootView.getActiveEditor()).toBeUndefined()
+ rootView.getActivePane().remove()
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
expect(rootView.find('.fuzzy-finder')).not.toExist()
@@ -182,16 +170,16 @@ describe 'FuzzyFinder', ->
beforeEach ->
rootView.attachToDom()
- editor1 = rootView.getActiveEditor()
+ editor1 = rootView.getActiveView()
editor2 = editor1.splitRight()
- expect(rootView.getActiveEditor()).toBe editor2
+ expect(rootView.getActiveView()).toBe editor2
rootView.open('sample.txt')
- editor2.loadPreviousEditSession()
+ editor2.trigger 'pane:show-previous-item'
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- describe "when there is an edit session for the confirmed path in the active editor", ->
- it "switches the active editor to the edit session for the selected path", ->
- expectedPath = fixturesProject.resolve('sample.txt')
+ describe "when the active pane has an item for the selected path", ->
+ it "switches to the item for the selected path", ->
+ expectedPath = project.resolve('sample.txt')
finderView.confirmed('sample.txt')
expect(finderView.hasParent()).toBeFalsy()
@@ -199,27 +187,26 @@ describe 'FuzzyFinder', ->
expect(editor2.getPath()).toBe expectedPath
expect(editor2.isFocused).toBeTruthy()
- describe "when there is NO edit session for the confirmed path on the active editor, but there is one on another editor", ->
- it "focuses the editor that contains an edit session for the selected path", ->
+ describe "when the active pane does not have an item for the selected path", ->
+ it "adds a new item to the active pane for the selcted path", ->
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
editor1.focus()
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- expect(rootView.getActiveEditor()).toBe editor1
+ expect(rootView.getActiveView()).toBe editor1
- expectedPath = fixturesProject.resolve('sample.txt')
+ expectedPath = project.resolve('sample.txt')
finderView.confirmed('sample.txt')
expect(finderView.hasParent()).toBeFalsy()
- expect(editor1.getPath()).not.toBe expectedPath
- expect(editor2.getPath()).toBe expectedPath
- expect(editor2.isFocused).toBeTruthy()
+ expect(editor1.getPath()).toBe expectedPath
+ expect(editor1.isFocused).toBeTruthy()
describe "git-status-finder behavior", ->
[originalText, originalPath, newPath] = []
beforeEach ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
originalText = editor.getText()
originalPath = editor.getPath()
fs.write(originalPath, 'making a change for the better')
@@ -248,7 +235,7 @@ describe 'FuzzyFinder', ->
describe "when an editor is open", ->
it "detaches the finder and focuses the previously focused element", ->
rootView.attachToDom()
- activeEditor = rootView.getActiveEditor()
+ activeEditor = rootView.getActiveView()
activeEditor.focus()
rootView.trigger 'fuzzy-finder:toggle-file-finder'
@@ -265,7 +252,7 @@ describe 'FuzzyFinder', ->
describe "when no editors are open", ->
it "detaches the finder and focuses the previously focused element", ->
rootView.attachToDom()
- rootView.getActiveEditor().destroyActiveEditSession()
+ rootView.getActivePane().remove()
inputView = $$ -> @input()
rootView.append(inputView)
@@ -351,7 +338,7 @@ describe 'FuzzyFinder', ->
editor = null
beforeEach ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
rootView.attachToDom()
it "opens the fuzzy finder window when there are multiple matches", ->
@@ -405,59 +392,63 @@ describe 'FuzzyFinder', ->
expect(finderView.find('.error').text().length).toBeGreaterThan 0
describe "opening a path into a split", ->
- beforeEach ->
- rootView.attachToDom()
+ it "opens the path by splitting the active editor left", ->
+ expect(rootView.getPanes().length).toBe 1
+ pane = rootView.getActivePane()
+ spyOn(pane, "splitLeft").andCallThrough()
- describe "when an editor is active", ->
- it "opens the path by splitting the active editor left", ->
- editor = rootView.getActiveEditor()
- spyOn(editor, "splitLeft").andCallThrough()
- expect(rootView.find('.editor').length).toBe 1
- rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- finderView.miniEditor.trigger 'editor:split-left'
- expect(rootView.find('.editor').length).toBe 2
- expect(editor.splitLeft).toHaveBeenCalled()
- expect(rootView.getActiveEditor()).not.toBe editor
- expect(rootView.getActiveEditor().getPath()).toBe editor.getPath()
+ rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
+ path = finderView.getSelectedElement()
+ finderView.miniEditor.trigger 'pane:split-left'
- it "opens the path by splitting the active editor right", ->
- editor = rootView.getActiveEditor()
- spyOn(editor, "splitRight").andCallThrough()
- expect(rootView.find('.editor').length).toBe 1
- rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- finderView.miniEditor.trigger 'editor:split-right'
- expect(rootView.find('.editor').length).toBe 2
- expect(editor.splitRight).toHaveBeenCalled()
- expect(rootView.getActiveEditor()).not.toBe editor
- expect(rootView.getActiveEditor().getPath()).toBe editor.getPath()
+ expect(rootView.getPanes().length).toBe 2
+ expect(pane.splitLeft).toHaveBeenCalled()
+ expect(rootView.getActiveView().getPath()).toBe project.resolve(path)
- it "opens the path by splitting the active editor down", ->
- editor = rootView.getActiveEditor()
- spyOn(editor, "splitDown").andCallThrough()
- expect(rootView.find('.editor').length).toBe 1
- rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- finderView.miniEditor.trigger 'editor:split-down'
- expect(rootView.find('.editor').length).toBe 2
- expect(editor.splitDown).toHaveBeenCalled()
- expect(rootView.getActiveEditor()).not.toBe editor
- expect(rootView.getActiveEditor().getPath()).toBe editor.getPath()
+ it "opens the path by splitting the active editor right", ->
+ expect(rootView.getPanes().length).toBe 1
+ pane = rootView.getActivePane()
+ spyOn(pane, "splitRight").andCallThrough()
- it "opens the path by splitting the active editor up", ->
- editor = rootView.getActiveEditor()
- spyOn(editor, "splitUp").andCallThrough()
- expect(rootView.find('.editor').length).toBe 1
- rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
- finderView.miniEditor.trigger 'editor:split-up'
- expect(rootView.find('.editor').length).toBe 2
- expect(editor.splitUp).toHaveBeenCalled()
- expect(rootView.getActiveEditor()).not.toBe editor
- expect(rootView.getActiveEditor().getPath()).toBe editor.getPath()
+ rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
+ path = finderView.getSelectedElement()
+ finderView.miniEditor.trigger 'pane:split-right'
+
+ expect(rootView.getPanes().length).toBe 2
+ expect(pane.splitRight).toHaveBeenCalled()
+ expect(rootView.getActiveView().getPath()).toBe project.resolve(path)
+
+ it "opens the path by splitting the active editor up", ->
+ expect(rootView.getPanes().length).toBe 1
+ pane = rootView.getActivePane()
+ spyOn(pane, "splitUp").andCallThrough()
+
+ rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
+ path = finderView.getSelectedElement()
+ finderView.miniEditor.trigger 'pane:split-up'
+
+ expect(rootView.getPanes().length).toBe 2
+ expect(pane.splitUp).toHaveBeenCalled()
+ expect(rootView.getActiveView().getPath()).toBe project.resolve(path)
+
+ it "opens the path by splitting the active editor down", ->
+ expect(rootView.getPanes().length).toBe 1
+ pane = rootView.getActivePane()
+ spyOn(pane, "splitDown").andCallThrough()
+
+ rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
+ path = finderView.getSelectedElement()
+ finderView.miniEditor.trigger 'pane:split-down'
+
+ expect(rootView.getPanes().length).toBe 2
+ expect(pane.splitDown).toHaveBeenCalled()
+ expect(rootView.getActiveView().getPath()).toBe project.resolve(path)
describe "git status decorations", ->
[originalText, originalPath, editor, newPath] = []
beforeEach ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
originalText = editor.getText()
originalPath = editor.getPath()
newPath = project.resolve('newsample.js')
@@ -470,7 +461,7 @@ describe 'FuzzyFinder', ->
describe "when a modified file is shown in the list", ->
it "displays the modified icon", ->
editor.setText('modified')
- editor.save()
+ editor.activeEditSession.save()
git.getPathStatus(editor.getPath())
rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
diff --git a/src/packages/gfm.tmbundle/spec/gfm-spec.coffee b/src/packages/gfm.tmbundle/spec/gfm-spec.coffee
index db307ab65..97bc98300 100644
--- a/src/packages/gfm.tmbundle/spec/gfm-spec.coffee
+++ b/src/packages/gfm.tmbundle/spec/gfm-spec.coffee
@@ -136,6 +136,6 @@ describe "GitHub Flavored Markdown grammar", ->
describe "auto indent", ->
it "indents newlines entered after list lines", ->
config.set('editor.autoIndent', true)
- editSession = fixturesProject.buildEditSessionForPath('gfm.md')
+ editSession = project.buildEditSession('gfm.md')
editSession.insertNewlineBelow()
expect(editSession.buffer.lineForRow(1)).toBe ' '
diff --git a/src/packages/gists/lib/gists.coffee b/src/packages/gists/lib/gists.coffee
index 4cc241fcd..6df96a01d 100644
--- a/src/packages/gists/lib/gists.coffee
+++ b/src/packages/gists/lib/gists.coffee
@@ -9,7 +9,7 @@ class Gists
rootView.command 'gist:create', '.editor', => @createGist()
createGist: (editor) ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
return unless editor?
gist = { public: false, files: {} }
diff --git a/src/packages/gists/spec/gists-spec.coffee b/src/packages/gists/spec/gists-spec.coffee
index 430b727ad..3091f8ed6 100644
--- a/src/packages/gists/spec/gists-spec.coffee
+++ b/src/packages/gists/spec/gists-spec.coffee
@@ -8,7 +8,7 @@ describe "Gists package", ->
window.rootView = new RootView
rootView.open('sample.js')
window.loadPackage('gists')
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
spyOn($, 'ajax')
describe "when gist:create is triggered on an editor", ->
diff --git a/src/packages/go-to-line/lib/go-to-line-view.coffee b/src/packages/go-to-line/lib/go-to-line-view.coffee
index 1f24afd39..d1da9b764 100644
--- a/src/packages/go-to-line/lib/go-to-line-view.coffee
+++ b/src/packages/go-to-line/lib/go-to-line-view.coffee
@@ -38,7 +38,7 @@ class GoToLineView extends View
confirm: ->
lineNumber = @miniEditor.getText()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
@detach()
@@ -51,5 +51,5 @@ class GoToLineView extends View
attach: ->
@previouslyFocusedElement = $(':focus')
rootView.append(this)
- @message.text("Enter a line number 1-#{rootView.getActiveEditor().getLineCount()}")
+ @message.text("Enter a line number 1-#{rootView.getActiveView().getLineCount()}")
@miniEditor.focus()
diff --git a/src/packages/go-to-line/spec/go-to-line-spec.coffee b/src/packages/go-to-line/spec/go-to-line-spec.coffee
index fe0c8b7df..bddef9426 100644
--- a/src/packages/go-to-line/spec/go-to-line-spec.coffee
+++ b/src/packages/go-to-line/spec/go-to-line-spec.coffee
@@ -8,7 +8,7 @@ describe 'GoToLine', ->
window.rootView = new RootView
rootView.open('sample.js')
rootView.enableKeymap()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
goToLine = GoToLineView.activate()
editor.setCursorBufferPosition([1,0])
diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee
index 0560030a0..b78827a83 100644
--- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee
+++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee
@@ -40,7 +40,7 @@ class MarkdownPreviewView extends ScrollView
@detaching = false
getActiveText: ->
- rootView.getActiveEditor()?.getText()
+ rootView.getActiveView()?.getText()
getErrorHtml: (error) ->
$$$ ->
@@ -74,7 +74,7 @@ class MarkdownPreviewView extends ScrollView
@markdownBody.html(html) if @hasParent()
isMarkdownEditor: (path) ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
return unless editor?
return true if editor.getGrammar().scopeName is 'source.gfm'
path and fs.isMarkdownExtension(fs.extension(path))
diff --git a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee
index a195f907d..7704768ee 100644
--- a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee
+++ b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee
@@ -13,7 +13,7 @@ describe "MarkdownPreview", ->
describe "markdown-preview:toggle event", ->
it "toggles on/off a preview for a .md file", ->
rootView.open('file.md')
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
expect(rootView.find('.markdown-preview')).not.toExist()
editor.trigger('markdown-preview:toggle')
@@ -25,7 +25,7 @@ describe "MarkdownPreview", ->
it "displays a preview for a .markdown file", ->
rootView.open('file.markdown')
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
expect(rootView.find('.markdown-preview')).not.toExist()
editor.trigger('markdown-preview:toggle')
expect(rootView.find('.markdown-preview')).toExist()
@@ -35,7 +35,7 @@ describe "MarkdownPreview", ->
it "displays a preview for a file with the source.gfm grammar scope", ->
gfmGrammar = _.find syntax.grammars, (grammar) -> grammar.scopeName is 'source.gfm'
rootView.open('file.js')
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
project.addGrammarOverrideForPath(editor.getPath(), gfmGrammar)
editor.reloadGrammar()
expect(rootView.find('.markdown-preview')).not.toExist()
@@ -46,39 +46,39 @@ describe "MarkdownPreview", ->
it "does not display a preview for non-markdown file", ->
rootView.open('file.js')
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
expect(rootView.find('.markdown-preview')).not.toExist()
editor.trigger('markdown-preview:toggle')
expect(rootView.find('.markdown-preview')).not.toExist()
expect(MarkdownPreview.prototype.loadHtml).not.toHaveBeenCalled()
- describe "core:cancel event", ->
- it "removes markdown preview", ->
- rootView.open('file.md')
- editor = rootView.getActiveEditor()
- expect(rootView.find('.markdown-preview')).not.toExist()
- editor.trigger('markdown-preview:toggle')
+ describe "core:cancel event", ->
+ it "removes markdown preview", ->
+ rootView.open('file.md')
+ editor = rootView.getActiveView()
+ expect(rootView.find('.markdown-preview')).not.toExist()
+ editor.trigger('markdown-preview:toggle')
- markdownPreviewView = rootView.find('.markdown-preview')?.view()
- expect(markdownPreviewView).toExist()
- markdownPreviewView.trigger('core:cancel')
- expect(rootView.find('.markdown-preview')).not.toExist()
+ markdownPreviewView = rootView.find('.markdown-preview')?.view()
+ expect(markdownPreviewView).toExist()
+ markdownPreviewView.trigger('core:cancel')
+ expect(rootView.find('.markdown-preview')).not.toExist()
- describe "when the editor receives focus", ->
- it "removes the markdown preview view", ->
- rootView.attachToDom()
- rootView.open('file.md')
- editor = rootView.getActiveEditor()
- expect(rootView.find('.markdown-preview')).not.toExist()
- editor.trigger('markdown-preview:toggle')
+ describe "when the editor receives focus", ->
+ it "removes the markdown preview view", ->
+ rootView.attachToDom()
+ rootView.open('file.md')
+ editor = rootView.getActiveView()
+ expect(rootView.find('.markdown-preview')).not.toExist()
+ editor.trigger('markdown-preview:toggle')
- markdownPreviewView = rootView.find('.markdown-preview')
- editor.focus()
- expect(markdownPreviewView).toExist()
- expect(rootView.find('.markdown-preview')).not.toExist()
+ markdownPreviewView = rootView.find('.markdown-preview')
+ editor.focus()
+ expect(markdownPreviewView).toExist()
+ expect(rootView.find('.markdown-preview')).not.toExist()
- describe "when no editor is open", ->
- it "does not attach", ->
- expect(rootView.getActiveEditor()).toBeFalsy()
- rootView.trigger('markdown-preview:toggle')
- expect(rootView.find('.markdown-preview')).not.toExist()
+ describe "when no editor is open", ->
+ it "does not attach", ->
+ expect(rootView.getActiveView()).toBeFalsy()
+ rootView.trigger('markdown-preview:toggle')
+ expect(rootView.find('.markdown-preview')).not.toExist()
diff --git a/src/packages/package-generator/spec/package-generator-spec.coffee b/src/packages/package-generator/spec/package-generator-spec.coffee
index 690443457..cae5d5d54 100644
--- a/src/packages/package-generator/spec/package-generator-spec.coffee
+++ b/src/packages/package-generator/spec/package-generator-spec.coffee
@@ -21,11 +21,11 @@ describe 'Package Generator', ->
rootView.trigger("package-generator:generate")
packageGeneratorView = rootView.find(".package-generator").view()
expect(packageGeneratorView.miniEditor.isFocused).toBeTruthy()
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
packageGeneratorView.trigger("core:cancel")
expect(packageGeneratorView.hasParent()).toBeFalsy()
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when a package is generated", ->
[packageName, packagePath] = []
@@ -58,6 +58,7 @@ describe 'Package Generator', ->
expect(fs.join(fs.directory(packagePath), "camel-case-is-for-the-birds")).toExistOnDisk()
it "correctly lays out the package files and closes the package generator view", ->
+ rootView.attachToDom()
rootView.trigger("package-generator:generate")
packageGeneratorView = rootView.find(".package-generator").view()
expect(packageGeneratorView.hasParent()).toBeTruthy()
@@ -73,7 +74,7 @@ describe 'Package Generator', ->
expect("#{packagePath}/stylesheets/#{packageName}.css").toExistOnDisk()
expect(packageGeneratorView.hasParent()).toBeFalsy()
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
it "replaces instances of packageName placeholders in template files", ->
rootView.trigger("package-generator:generate")
diff --git a/src/packages/snippets/spec/snippets-spec.coffee b/src/packages/snippets/spec/snippets-spec.coffee
index c6e997d35..b77b2d80b 100644
--- a/src/packages/snippets/spec/snippets-spec.coffee
+++ b/src/packages/snippets/spec/snippets-spec.coffee
@@ -21,8 +21,8 @@ describe "Snippets extension", ->
window.loadPackage("snippets")
- editor = rootView.getActiveEditor()
- editSession = rootView.getActiveEditSession()
+ editor = rootView.getActiveView()
+ editSession = rootView.getActivePaneItem()
buffer = editor.getBuffer()
rootView.simulateDomAttachment()
rootView.enableKeymap()
@@ -300,7 +300,7 @@ describe "Snippets extension", ->
jasmine.unspy(LoadSnippetsTask.prototype, 'loadTextMateSnippets')
snippets.loaded = false
task = new LoadSnippetsTask(snippets)
- task.packages = [Package.build(fixturesProject.resolve('packages/package-with-a-cson-grammar.tmbundle'))]
+ task.packages = [Package.build(project.resolve('packages/package-with-a-cson-grammar.tmbundle'))]
task.start()
waitsFor "CSON snippets to load", 5000, -> snippets.loaded
diff --git a/src/packages/spell-check/spec/spell-check-spec.coffee b/src/packages/spell-check/spec/spell-check-spec.coffee
index bf3fba917..f2b4f4f3a 100644
--- a/src/packages/spell-check/spec/spell-check-spec.coffee
+++ b/src/packages/spell-check/spec/spell-check-spec.coffee
@@ -9,7 +9,7 @@ describe "Spell check", ->
config.set('spell-check.grammars', [])
window.loadPackage('spell-check')
rootView.attachToDom()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
it "decorates all misspelled words", ->
editor.setText("This middle of thiss sentencts has issues.")
@@ -110,5 +110,5 @@ describe "Spell check", ->
view = editor.find('.misspelling').view()
buffer = editor.getBuffer()
expect(buffer.getMarkerPosition(view.marker)).not.toBeUndefined()
- editor.destroyEditSessions()
+ editor.remove()
expect(buffer.getMarkerPosition(view.marker)).toBeUndefined()
diff --git a/src/packages/status-bar/lib/status-bar-view.coffee b/src/packages/status-bar/lib/status-bar-view.coffee
index c681fe4ea..f58d4dba0 100644
--- a/src/packages/status-bar/lib/status-bar-view.coffee
+++ b/src/packages/status-bar/lib/status-bar-view.coffee
@@ -47,7 +47,7 @@ class StatusBarView extends View
subscribeToBuffer: ->
@buffer?.off '.status-bar'
@buffer = @editor.getBuffer()
- @buffer.on 'contents-modified.status-bar', (e) => @updateBufferHasModifiedText(e.differsFromDisk)
+ @buffer.on 'modified-status-changed.status-bar', (isModified) => @updateBufferHasModifiedText(isModified)
@buffer.on 'saved.status-bar', => @updateStatusBar()
@updateStatusBar()
@@ -60,8 +60,8 @@ class StatusBarView extends View
updateGrammarText: ->
@grammarName.text(@editor.getGrammar().name)
- updateBufferHasModifiedText: (differsFromDisk)->
- if differsFromDisk
+ updateBufferHasModifiedText: (isModified)->
+ if isModified
@bufferModified.text('*') unless @isModified
@isModified = true
else
diff --git a/src/packages/status-bar/spec/status-bar-spec.coffee b/src/packages/status-bar/spec/status-bar-spec.coffee
index f2d73b07b..f7a88cf4d 100644
--- a/src/packages/status-bar/spec/status-bar-spec.coffee
+++ b/src/packages/status-bar/spec/status-bar-spec.coffee
@@ -12,7 +12,7 @@ describe "StatusBar", ->
rootView.open('sample.js')
rootView.simulateDomAttachment()
StatusBar.activate()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
statusBar = rootView.find('.status-bar').view()
buffer = editor.getBuffer()
@@ -63,7 +63,7 @@ describe "StatusBar", ->
editor.insertText("\n")
advanceClock(buffer.stoppedChangingDelay)
expect(statusBar.bufferModified.text()).toBe '*'
- editor.save()
+ editor.getBuffer().save()
expect(statusBar.bufferModified.text()).toBe ''
it "disables the buffer modified indicator if the content matches again", ->
diff --git a/src/packages/strip-trailing-whitespace/spec/strip-trailing-whitespace-spec.coffee b/src/packages/strip-trailing-whitespace/spec/strip-trailing-whitespace-spec.coffee
index f76f5b38d..17277b059 100644
--- a/src/packages/strip-trailing-whitespace/spec/strip-trailing-whitespace-spec.coffee
+++ b/src/packages/strip-trailing-whitespace/spec/strip-trailing-whitespace-spec.coffee
@@ -12,7 +12,7 @@ describe "StripTrailingWhitespace", ->
window.loadPackage('strip-trailing-whitespace')
rootView.focus()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
afterEach ->
fs.remove(path) if fs.exists(path)
@@ -23,7 +23,7 @@ describe "StripTrailingWhitespace", ->
# works for buffers that are already open when extension is initialized
editor.insertText("foo \nbar\t \n\nbaz")
- editor.save()
+ editor.getBuffer().save()
expect(editor.getText()).toBe "foo\nbar\n\nbaz"
# works for buffers that are opened after extension is initialized
@@ -47,25 +47,25 @@ describe "StripTrailingWhitespace", ->
it "adds a trailing newline when there is no trailing newline", ->
editor.insertText "foo"
- editor.save()
+ editor.getBuffer().save()
expect(editor.getText()).toBe "foo\n"
it "removes extra trailing newlines and only keeps one", ->
editor.insertText "foo\n\n\n\n"
- editor.save()
+ editor.getBuffer().save()
expect(editor.getText()).toBe "foo\n"
it "leaves a buffer with a single trailing newline untouched", ->
editor.insertText "foo\nbar\n"
- editor.save()
+ editor.getBuffer().save()
expect(editor.getText()).toBe "foo\nbar\n"
it "leaves an empty buffer untouched", ->
editor.insertText ""
- editor.save()
+ editor.getBuffer().save()
expect(editor.getText()).toBe ""
it "leaves a buffer that is a single newline untouched", ->
editor.insertText "\n"
- editor.save()
+ editor.getBuffer().save()
expect(editor.getText()).toBe "\n"
diff --git a/src/packages/symbols-view/lib/symbols-view.coffee b/src/packages/symbols-view/lib/symbols-view.coffee
index fb87d78de..10f347b66 100644
--- a/src/packages/symbols-view/lib/symbols-view.coffee
+++ b/src/packages/symbols-view/lib/symbols-view.coffee
@@ -43,7 +43,7 @@ class SymbolsView extends SelectList
populateFileSymbols: ->
tags = []
callback = (tag) -> tags.push tag
- path = rootView.getActiveEditor().getPath()
+ path = rootView.getActiveView().getPath()
@list.empty()
@setLoading("Generating symbols...")
new TagGenerator(path, callback).generate().done =>
@@ -91,7 +91,7 @@ class SymbolsView extends SelectList
@moveToPosition(position) if position
moveToPosition: (position) ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
editor.scrollToBufferPosition(position, center: true)
editor.setCursorBufferPosition(position)
editor.moveCursorToFirstCharacterOfLine()
@@ -111,7 +111,7 @@ class SymbolsView extends SelectList
return new Point(index, 0) if pattern is $.trim(line)
goToDeclaration: ->
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
matches = TagReader.find(editor)
return unless matches.length
diff --git a/src/packages/symbols-view/spec/symbols-view-spec.coffee b/src/packages/symbols-view/spec/symbols-view-spec.coffee
index dcc5c5556..30d9f69be 100644
--- a/src/packages/symbols-view/spec/symbols-view-spec.coffee
+++ b/src/packages/symbols-view/spec/symbols-view-spec.coffee
@@ -19,7 +19,7 @@ describe "SymbolsView", ->
describe "when tags can be generated for a file", ->
it "initially displays all JavaScript functions with line numbers", ->
rootView.open('sample.js')
- rootView.getActiveEditor().trigger "symbols-view:toggle-file-symbols"
+ rootView.getActiveView().trigger "symbols-view:toggle-file-symbols"
symbolsView = rootView.find('.symbols-view').view()
expect(symbolsView.find('.loading')).toHaveText 'Generating symbols...'
@@ -39,7 +39,7 @@ describe "SymbolsView", ->
it "displays error when no tags match text in mini-editor", ->
rootView.open('sample.js')
- rootView.getActiveEditor().trigger "symbols-view:toggle-file-symbols"
+ rootView.getActiveView().trigger "symbols-view:toggle-file-symbols"
symbolsView = rootView.find('.symbols-view').view()
waitsFor ->
@@ -66,7 +66,7 @@ describe "SymbolsView", ->
describe "when tags can't be generated for a file", ->
it "shows an error message when no matching tags are found", ->
rootView.open('sample.txt')
- rootView.getActiveEditor().trigger "symbols-view:toggle-file-symbols"
+ rootView.getActiveView().trigger "symbols-view:toggle-file-symbols"
symbolsView = rootView.find('.symbols-view').view()
setErrorSpy = spyOn(symbolsView, "setError").andCallThrough()
@@ -93,14 +93,14 @@ describe "SymbolsView", ->
runs ->
rootView.open('sample.js')
- expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [0,0]
+ expect(rootView.getActiveView().getCursorBufferPosition()).toEqual [0,0]
expect(rootView.find('.symbols-view')).not.toExist()
symbolsView = SymbolsView.activate()
symbolsView.setArray(tags)
symbolsView.attach()
expect(rootView.find('.symbols-view')).toExist()
symbolsView.confirmed(tags[1])
- expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [1,2]
+ expect(rootView.getActiveView().getCursorBufferPosition()).toEqual [1,2]
describe "TagGenerator", ->
it "generates tags for all JavaScript functions", ->
@@ -136,29 +136,29 @@ describe "SymbolsView", ->
describe "go to declaration", ->
it "doesn't move the cursor when no declaration is found", ->
rootView.open("tagged.js")
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
editor.setCursorBufferPosition([0,2])
editor.trigger 'symbols-view:go-to-declaration'
expect(editor.getCursorBufferPosition()).toEqual [0,2]
it "moves the cursor to the declaration", ->
rootView.open("tagged.js")
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
editor.setCursorBufferPosition([6,24])
editor.trigger 'symbols-view:go-to-declaration'
expect(editor.getCursorBufferPosition()).toEqual [2,0]
it "displays matches when more than one exists and opens the selected match", ->
rootView.open("tagged.js")
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
editor.setCursorBufferPosition([8,14])
editor.trigger 'symbols-view:go-to-declaration'
symbolsView = rootView.find('.symbols-view').view()
expect(symbolsView.list.children('li').length).toBe 2
expect(symbolsView).toBeVisible()
symbolsView.confirmed(symbolsView.array[0])
- expect(rootView.getActiveEditor().getPath()).toBe project.resolve("tagged-duplicate.js")
- expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [0,4]
+ expect(rootView.getActiveView().getPath()).toBe project.resolve("tagged-duplicate.js")
+ expect(rootView.getActiveView().getCursorBufferPosition()).toEqual [0,4]
describe "when the tag is in a file that doesn't exist", ->
renamedPath = null
@@ -173,7 +173,7 @@ describe "SymbolsView", ->
it "doesn't display the tag", ->
rootView.open("tagged.js")
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
editor.setCursorBufferPosition([8,14])
editor.trigger 'symbols-view:go-to-declaration'
symbolsView = rootView.find('.symbols-view').view()
diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee
new file mode 100644
index 000000000..fbbab1330
--- /dev/null
+++ b/src/packages/tabs/lib/tab-bar-view.coffee
@@ -0,0 +1,98 @@
+$ = require 'jquery'
+_ = require 'underscore'
+SortableList = require 'sortable-list'
+TabView = require './tab-view'
+
+module.exports =
+class TabBarView extends SortableList
+ @content: ->
+ @ul class: "tabs #{@viewClass()}"
+
+ initialize: (@pane) ->
+ super
+
+ @paneContainer = @pane.getContainer()
+ @addTabForItem(item) for item in @pane.getItems()
+
+ @pane.on 'pane:item-added', (e, item, index) => @addTabForItem(item, index)
+ @pane.on 'pane:item-moved', (e, item, index) => @moveItemTabToIndex(item, index)
+ @pane.on 'pane:item-removed', (e, item) => @removeTabForItem(item)
+ @pane.on 'pane:active-item-changed', => @updateActiveTab()
+
+ @updateActiveTab()
+
+ @on 'click', '.tab', (e) =>
+ tab = $(e.target).closest('.tab').view()
+ @pane.showItem(tab.item)
+ @pane.focus()
+
+ @on 'click', '.tab .close-icon', (e) =>
+ tab = $(e.target).closest('.tab').view()
+ @pane.destroyItem(tab.item)
+ false
+
+ @pane.prepend(this)
+
+ addTabForItem: (item, index) ->
+ @insertTabAtIndex(new TabView(item, @pane), index)
+
+ moveItemTabToIndex: (item, index) ->
+ tab = @tabForItem(item)
+ tab.detach()
+ @insertTabAtIndex(tab, index)
+
+ insertTabAtIndex: (tab, index) ->
+ followingTab = @tabAtIndex(index) if index?
+ if followingTab
+ tab.insertBefore(followingTab)
+ else
+ @append(tab)
+
+ removeTabForItem: (item) ->
+ @tabForItem(item).remove()
+
+ getTabs: ->
+ @children('.tab').toArray().map (elt) -> $(elt).view()
+
+ tabAtIndex: (index) ->
+ @children(".tab:eq(#{index})").view()
+
+ tabForItem: (item) ->
+ _.detect @getTabs(), (tab) -> tab.item is item
+
+ setActiveTab: (tabView) ->
+ unless tabView.hasClass('active')
+ @find(".tab.active").removeClass('active')
+ tabView.addClass('active')
+
+ updateActiveTab: ->
+ @setActiveTab(@tabForItem(@pane.activeItem))
+
+ shouldAllowDrag: ->
+ (@paneContainer.getPanes().length > 1) or (@pane.getItems().length > 1)
+
+ onDragStart: (event) =>
+ super
+ pane = $(event.target).closest('.pane')
+ paneIndex = @paneContainer.indexOfPane(pane)
+ event.originalEvent.dataTransfer.setData 'from-pane-index', paneIndex
+
+ onDrop: (event) =>
+ super
+
+ dataTransfer = event.originalEvent.dataTransfer
+ fromIndex = parseInt(dataTransfer.getData('sortable-index'))
+ fromPaneIndex = parseInt(dataTransfer.getData('from-pane-index'))
+ fromPane = @paneContainer.paneAtIndex(fromPaneIndex)
+ toIndex = @getSortableElement(event).index()
+ toPane = $(event.target).closest('.pane').view()
+ draggedTab = fromPane.find(".tabs .sortable:eq(#{fromIndex})").view()
+ item = draggedTab.item
+
+ if toPane is fromPane
+ toIndex++ if fromIndex > toIndex
+ toPane.moveItem(item, toIndex)
+ else
+ fromPane.moveItemToPane(item, toPane, toIndex)
+ toPane.showItem(item)
+ toPane.focus()
diff --git a/src/packages/tabs/lib/tab-view.coffee b/src/packages/tabs/lib/tab-view.coffee
index 166233094..9bfee397e 100644
--- a/src/packages/tabs/lib/tab-view.coffee
+++ b/src/packages/tabs/lib/tab-view.coffee
@@ -1,100 +1,55 @@
$ = require 'jquery'
-SortableList = require 'sortable-list'
-Tab = require './tab'
+{View} = require 'space-pen'
+fs = require 'fs'
module.exports =
-class TabView extends SortableList
- @activate: ->
- rootView.eachEditor (editor) =>
- @prependToEditorPane(editor) if editor.attached
-
- @prependToEditorPane: (editor) ->
- if pane = editor.pane()
- pane.prepend(new TabView(editor))
-
+class TabView extends View
@content: ->
- @ul class: "tabs #{@viewClass()}"
+ @li class: 'tab sortable', =>
+ @span class: 'title', outlet: 'title'
+ @span class: 'close-icon'
- initialize: (@editor) ->
- super
+ initialize: (@item, @pane) ->
+ @item.on? 'title-changed', => @updateTitle()
+ @item.on? 'modified-status-changed', => @updateModifiedStatus()
+ @updateTitle()
+ @updateModifiedStatus()
- @addTabForEditSession(editSession) for editSession in @editor.editSessions
+ updateTitle: ->
+ return if @updatingTitle
+ @updatingTitle = true
- @setActiveTab(@editor.getActiveEditSessionIndex())
- @editor.on 'editor:active-edit-session-changed', (e, editSession, index) => @setActiveTab(index)
- @editor.on 'editor:edit-session-added', (e, editSession) => @addTabForEditSession(editSession)
- @editor.on 'editor:edit-session-removed', (e, editSession, index) => @removeTabAtIndex(index)
- @editor.on 'editor:edit-session-order-changed', (e, editSession, fromIndex, toIndex) =>
- fromTab = @find(".tab:eq(#{fromIndex})")
- toTab = @find(".tab:eq(#{toIndex})")
- fromTab.detach()
- if fromIndex < toIndex
- fromTab.insertAfter(toTab)
- else
- fromTab.insertBefore(toTab)
+ title = @item.getTitle()
+ useLongTitle = false
+ for tab in @getSiblingTabs()
+ if tab.item.getTitle() is title
+ tab.updateTitle()
+ useLongTitle = true
+ title = @item.getLongTitle?() ? title if useLongTitle
- @on 'click', '.tab', (e) =>
- @editor.setActiveEditSessionIndex($(e.target).closest('.tab').index())
- @editor.focus()
+ @title.text(title)
+ @updatingTitle = false
- @on 'click', '.tab .close-icon', (e) =>
- index = $(e.target).closest('.tab').index()
- @editor.destroyEditSessionIndex(index)
- false
+ getSiblingTabs: ->
+ @siblings('.tab').views()
- addTabForEditSession: (editSession) ->
- @append(new Tab(editSession, @editor))
-
- setActiveTab: (index) ->
- @find(".tab.active").removeClass('active')
- @find(".tab:eq(#{index})").addClass('active')
-
- removeTabAtIndex: (index) ->
- @find(".tab:eq(#{index})").remove()
-
- containsEditSession: (editor, editSession) ->
- for session in editor.editSessions
- return true if editSession.getPath() is session.getPath()
-
- shouldAllowDrag: (event) ->
- panes = rootView.find('.pane')
- !(panes.length == 1 && panes.find('.sortable').length == 1)
-
- onDragStart: (event) =>
- super
-
- pane = $(event.target).closest('.pane')
- paneIndex = rootView.indexOfPane(pane)
- event.originalEvent.dataTransfer.setData 'from-pane-index', paneIndex
-
- onDrop: (event) =>
- super
-
- droppedNearTab = @getSortableElement(event)
- transfer = event.originalEvent.dataTransfer
- previousDraggedTabIndex = transfer.getData 'sortable-index'
-
- fromPaneIndex = ~~transfer.getData 'from-pane-index'
- toPaneIndex = rootView.indexOfPane($(event.target).closest('.pane'))
- fromPane = $(rootView.find('.pane')[fromPaneIndex])
- fromEditor = fromPane.find('.editor').view()
- draggedTab = fromPane.find(".#{TabView.viewClass()} .sortable:eq(#{previousDraggedTabIndex})")
-
- if draggedTab.is(droppedNearTab)
- fromEditor.focus()
- return
-
- if fromPaneIndex == toPaneIndex
- droppedNearTab = @getSortableElement(event)
- fromIndex = draggedTab.index()
- toIndex = droppedNearTab.index()
- toIndex++ if fromIndex > toIndex
- fromEditor.moveEditSessionToIndex(fromIndex, toIndex)
- fromEditor.focus()
+ updateModifiedStatus: ->
+ if @item.isModified?()
+ @addClass('modified') unless @isModified
+ @isModified = true
else
- toEditor = rootView.find(".pane:eq(#{toPaneIndex}) > .editor").view()
- if @containsEditSession(toEditor, fromEditor.editSessions[draggedTab.index()])
- fromEditor.focus()
- else
- fromEditor.moveEditSessionToEditor(draggedTab.index(), toEditor, droppedNearTab.index() + 1)
- toEditor.focus()
+ @removeClass('modified') if @isModified
+ @isModified = false
+
+ updateFileName: ->
+ fileNameText = @editSession.buffer.getBaseName()
+ if fileNameText?
+ duplicates = @editor.getEditSessions().filter (session) -> fileNameText is session.buffer.getBaseName()
+ if duplicates.length > 1
+ directory = fs.base(fs.directory(@editSession.getPath()))
+ fileNameText = "#{fileNameText} - #{directory}" if directory
+ else
+ fileNameText = 'untitled'
+
+ @fileName.text(fileNameText)
+ @fileName.attr('title', @editSession.getPath())
diff --git a/src/packages/tabs/lib/tab.coffee b/src/packages/tabs/lib/tab.coffee
deleted file mode 100644
index 9a7e8e3ab..000000000
--- a/src/packages/tabs/lib/tab.coffee
+++ /dev/null
@@ -1,40 +0,0 @@
-{View} = require 'space-pen'
-fs = require 'fs'
-
-module.exports =
-class Tab extends View
- @content: (editSession) ->
- @li class: 'tab sortable', =>
- @span class: 'file-name', outlet: 'fileName'
- @span class: 'close-icon'
-
- initialize: (@editSession, @editor) ->
- @buffer = @editSession.buffer
- @subscribe @buffer, 'path-changed', => @updateFileName()
- @subscribe @buffer, 'contents-modified', => @updateModifiedStatus()
- @subscribe @buffer, 'saved', => @updateModifiedStatus()
- @subscribe @editor, 'editor:edit-session-added', => @updateFileName()
- @subscribe @editor, 'editor:edit-session-removed', => @updateFileName()
- @updateFileName()
- @updateModifiedStatus()
-
- updateModifiedStatus: ->
- if @buffer.isModified()
- @toggleClass('file-modified') unless @isModified
- @isModified = true
- else
- @removeClass('file-modified') if @isModified
- @isModified = false
-
- updateFileName: ->
- fileNameText = @editSession.buffer.getBaseName()
- if fileNameText?
- duplicates = @editor.getEditSessions().filter (session) -> fileNameText is session.buffer.getBaseName()
- if duplicates.length > 1
- directory = fs.base(fs.directory(@editSession.getPath()))
- fileNameText = "#{fileNameText} - #{directory}" if directory
- else
- fileNameText = 'untitled'
-
- @fileName.text(fileNameText)
- @fileName.attr('title', @editSession.getPath())
diff --git a/src/packages/tabs/lib/tabs.coffee b/src/packages/tabs/lib/tabs.coffee
new file mode 100644
index 000000000..ba2da6b3a
--- /dev/null
+++ b/src/packages/tabs/lib/tabs.coffee
@@ -0,0 +1,5 @@
+TabBarView = require './tab-bar-view'
+
+module.exports =
+ activate: ->
+ rootView.eachPane (pane) => new TabBarView(pane)
diff --git a/src/packages/tabs/package.cson b/src/packages/tabs/package.cson
index 0e40dfd74..1c24d65ba 100644
--- a/src/packages/tabs/package.cson
+++ b/src/packages/tabs/package.cson
@@ -1 +1 @@
-'main': 'lib/tab-view'
+'main': 'lib/tabs'
diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee
index 00efe0324..d8d32e390 100644
--- a/src/packages/tabs/spec/tabs-spec.coffee
+++ b/src/packages/tabs/spec/tabs-spec.coffee
@@ -1,231 +1,256 @@
$ = require 'jquery'
_ = require 'underscore'
RootView = require 'root-view'
+Pane = require 'pane'
+PaneContainer = require 'pane-container'
+TabBarView = require 'tabs/lib/tab-bar-view'
fs = require 'fs'
+{View} = require 'space-pen'
-describe "TabView", ->
- [editor, buffer, tabs] = []
-
+describe "Tabs package main", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
- rootView.open('sample.txt')
- rootView.simulateDomAttachment()
window.loadPackage("tabs")
- editor = rootView.getActiveEditor()
- tabs = rootView.find('.tabs').view()
- describe "@activate", ->
- it "appends a status bear to all existing and new editors", ->
+ describe ".activate()", ->
+ it "appends a tab bar all existing and new panes", ->
expect(rootView.panes.find('.pane').length).toBe 1
expect(rootView.panes.find('.pane > .tabs').length).toBe 1
- editor.splitRight()
+ rootView.getActivePane().splitRight()
expect(rootView.find('.pane').length).toBe 2
expect(rootView.panes.find('.pane > .tabs').length).toBe 2
- describe ".initialize()", ->
- it "creates a tab for each edit session on the editor to which the tab-strip belongs", ->
- expect(editor.editSessions.length).toBe 2
- expect(tabs.find('.tab').length).toBe 2
+describe "TabBarView", ->
+ [item1, item2, editSession1, pane, tabBar] = []
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe editor.editSessions[0].buffer.getBaseName()
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe editor.editSessions[1].buffer.getBaseName()
+ class TestView extends View
+ @deserialize: ({title, longTitle}) -> new TestView(title, longTitle)
+ @content: (title) -> @div title
+ initialize: (@title, @longTitle) ->
+ getTitle: -> @title
+ getLongTitle: -> @longTitle
+ serialize: -> { deserializer: 'TestView', @title, @longTitle }
- it "highlights the tab for the current active edit session", ->
- expect(editor.getActiveEditSessionIndex()).toBe 1
- expect(tabs.find('.tab:eq(1)')).toHaveClass 'active'
+ beforeEach ->
+ registerDeserializer(TestView)
+ item1 = new TestView('Item 1')
+ item2 = new TestView('Item 2')
+ editSession1 = project.buildEditSession('sample.js')
+ paneContainer = new PaneContainer
+ pane = new Pane(item1, editSession1, item2)
+ pane.showItem(item2)
+ paneContainer.append(pane)
+ tabBar = new TabBarView(pane)
- it "sets the title on each tab to be the full path of the edit session", ->
- expect(tabs.find('.tab:eq(0) .file-name').attr('title')).toBe editor.editSessions[0].getPath()
- expect(tabs.find('.tab:eq(1) .file-name').attr('title')).toBe editor.editSessions[1].getPath()
+ afterEach ->
+ unregisterDeserializer(TestView)
- describe "when the active edit session changes", ->
- it "highlights the tab for the newly-active edit session", ->
- editor.setActiveEditSessionIndex(0)
- expect(tabs.find('.active').length).toBe 1
- expect(tabs.find('.tab:eq(0)')).toHaveClass 'active'
+ describe ".initialize(pane)", ->
+ it "creates a tab for each item on the tab bar's parent pane", ->
+ expect(pane.getItems().length).toBe 3
+ expect(tabBar.find('.tab').length).toBe 3
- editor.setActiveEditSessionIndex(1)
- expect(tabs.find('.active').length).toBe 1
- expect(tabs.find('.tab:eq(1)')).toHaveClass 'active'
+ expect(tabBar.find('.tab:eq(0) .title').text()).toBe item1.getTitle()
+ expect(tabBar.find('.tab:eq(1) .title').text()).toBe editSession1.getTitle()
+ expect(tabBar.find('.tab:eq(2) .title').text()).toBe item2.getTitle()
- describe "when a new edit session is created", ->
- it "adds a tab for the new edit session", ->
- rootView.open('two-hundred.txt')
- expect(tabs.find('.tab').length).toBe 3
- expect(tabs.find('.tab:eq(2) .file-name').text()).toBe 'two-hundred.txt'
+ it "highlights the tab for the active pane item", ->
+ expect(tabBar.find('.tab:eq(2)')).toHaveClass 'active'
- describe "when the edit session's buffer has an undefined path", ->
- it "makes the tab text 'untitled'", ->
- rootView.open()
- expect(tabs.find('.tab').length).toBe 3
- expect(tabs.find('.tab:eq(2) .file-name').text()).toBe 'untitled'
+ describe "when the active pane item changes", ->
+ it "highlights the tab for the new active pane item", ->
+ pane.showItem(item1)
+ expect(tabBar.find('.active').length).toBe 1
+ expect(tabBar.find('.tab:eq(0)')).toHaveClass 'active'
- it "removes the tab's title", ->
- rootView.open()
- expect(tabs.find('.tab').length).toBe 3
- expect(tabs.find('.tab:eq(2) .file-name').attr('title')).toBeUndefined()
+ pane.showItem(item2)
+ expect(tabBar.find('.active').length).toBe 1
+ expect(tabBar.find('.tab:eq(2)')).toHaveClass 'active'
- describe "when an edit session is removed", ->
- it "removes the tab for the removed edit session", ->
- editor.setActiveEditSessionIndex(0)
- editor.destroyActiveEditSession()
- expect(tabs.find('.tab').length).toBe 1
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.txt'
+ describe "when a new item is added to the pane", ->
+ it "adds a tab for the new item at the same index as the item in the pane", ->
+ pane.showItem(item1)
+ item3 = new TestView('Item 3')
+ pane.showItem(item3)
+ expect(tabBar.find('.tab').length).toBe 4
+ expect(tabBar.tabAtIndex(1).find('.title')).toHaveText 'Item 3'
+
+ it "adds the 'modified' class to the new tab if the item is initially modified", ->
+ editSession2 = project.buildEditSession('sample.txt')
+ editSession2.insertText('x')
+ pane.showItem(editSession2)
+ expect(tabBar.tabForItem(editSession2)).toHaveClass 'modified'
+
+ describe "when an item is removed from the pane", ->
+ it "removes the item's tab from the tab bar", ->
+ pane.removeItem(item2)
+ expect(tabBar.getTabs().length).toBe 2
+ expect(tabBar.find('.tab:contains(Item 2)')).not.toExist()
describe "when a tab is clicked", ->
- it "activates the associated edit session", ->
- expect(editor.getActiveEditSessionIndex()).toBe 1
- tabs.find('.tab:eq(0)').click()
- expect(editor.getActiveEditSessionIndex()).toBe 0
- tabs.find('.tab:eq(1)').click()
- expect(editor.getActiveEditSessionIndex()).toBe 1
+ it "shows the associated item on the pane and focuses the pane", ->
+ spyOn(pane, 'focus')
- it "focuses the associated editor", ->
- rootView.attachToDom()
- expect(editor).toMatchSelector ":has(:focus)"
- editor.splitRight()
- expect(editor).not.toMatchSelector ":has(:focus)"
- tabs.find('.tab:eq(0)').click()
- expect(editor).toMatchSelector ":has(:focus)"
+ tabBar.tabAtIndex(0).click()
+ expect(pane.activeItem).toBe pane.getItems()[0]
- describe "when a file name associated with a tab changes", ->
- [buffer, oldPath, newPath] = []
+ tabBar.tabAtIndex(2).click()
+ expect(pane.activeItem).toBe pane.getItems()[2]
- beforeEach ->
- buffer = editor.editSessions[0].buffer
- oldPath = "/tmp/file-to-rename.txt"
- newPath = "/tmp/renamed-file.txt"
- fs.write(oldPath, "this old path")
- rootView.open(oldPath)
+ expect(pane.focus.callCount).toBe 2
- afterEach ->
- fs.remove(newPath) if fs.exists(newPath)
+ describe "when a tab's close icon is clicked", ->
+ it "destroys the tab's item on the pane", ->
+ tabBar.tabForItem(editSession1).find('.close-icon').click()
+ expect(pane.getItems().length).toBe 2
+ expect(pane.getItems().indexOf(editSession1)).toBe -1
+ expect(editSession1.destroyed).toBeTruthy()
+ expect(tabBar.getTabs().length).toBe 2
+ expect(tabBar.find('.tab:contains(sample.js)')).not.toExist()
- it "updates the file name in the tab", ->
- tabFileName = tabs.find('.tab:eq(2) .file-name')
- expect(tabFileName).toExist()
- editor.setActiveEditSessionIndex(0)
- fs.move(oldPath, newPath)
- waitsFor "file to be renamed", ->
- tabFileName.text() == "renamed-file.txt"
+ describe "when a tab item's title changes", ->
+ it "updates the title of the item's tab", ->
+ editSession1.buffer.setPath('/this/is-a/test.txt')
+ expect(tabBar.tabForItem(editSession1)).toHaveText 'test.txt'
- describe "when the close icon is clicked", ->
- it "closes the selected non-active edit session", ->
- activeSession = editor.activeEditSession
- expect(editor.getActiveEditSessionIndex()).toBe 1
- tabs.find('.tab .close-icon:eq(0)').click()
- expect(editor.getActiveEditSessionIndex()).toBe 0
- expect(editor.activeEditSession).toBe activeSession
+ describe "when two tabs have the same title", ->
+ it "displays the long title on the tab if it's available from the item", ->
+ item1.title = "Old Man"
+ item1.longTitle = "Grumpy Old Man"
+ item1.trigger 'title-changed'
+ item2.title = "Old Man"
+ item2.longTitle = "Jolly Old Man"
+ item2.trigger 'title-changed'
- it "closes the selected active edit session", ->
- firstSession = editor.getEditSessions()[0]
- expect(editor.getActiveEditSessionIndex()).toBe 1
- tabs.find('.tab .close-icon:eq(1)').click()
- expect(editor.getActiveEditSessionIndex()).toBe 0
- expect(editor.activeEditSession).toBe firstSession
+ expect(tabBar.tabForItem(item1)).toHaveText "Grumpy Old Man"
+ expect(tabBar.tabForItem(item2)).toHaveText "Jolly Old Man"
- describe "when two tabs have the same file name", ->
- [tempPath] = []
+ item2.longTitle = undefined
+ item2.trigger 'title-changed'
- beforeEach ->
- tempPath = '/tmp/sample.js'
- fs.write(tempPath, 'sample')
+ expect(tabBar.tabForItem(item1)).toHaveText "Grumpy Old Man"
+ expect(tabBar.tabForItem(item2)).toHaveText "Old Man"
- afterEach ->
- fs.remove(tempPath) if fs.exists(tempPath)
+ describe "when a tab item's modified status changes", ->
+ it "adds or removes the 'modified' class to the tab based on the status", ->
+ tab = tabBar.tabForItem(editSession1)
+ expect(editSession1.isModified()).toBeFalsy()
+ expect(tab).not.toHaveClass 'modified'
- it "displays the parent folder name after the file name", ->
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js'
- rootView.open(tempPath)
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js - fixtures'
- expect(tabs.find('.tab:last .file-name').text()).toBe 'sample.js - tmp'
- editor.destroyActiveEditSession()
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js'
+ editSession1.insertText('x')
+ advanceClock(editSession1.buffer.stoppedChangingDelay)
+ expect(editSession1.isModified()).toBeTruthy()
+ expect(tab).toHaveClass 'modified'
- describe "when an editor:edit-session-order-changed event is triggered", ->
- it "updates the order of the tabs to match the new edit session order", ->
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
+ editSession1.undo()
+ advanceClock(editSession1.buffer.stoppedChangingDelay)
+ expect(editSession1.isModified()).toBeFalsy()
+ expect(tab).not.toHaveClass 'modified'
- editor.moveEditSessionToIndex(0, 1)
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.txt"
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.js"
-
- editor.moveEditSessionToIndex(1, 0)
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
+ describe "when a pane item moves to a new index", ->
+ it "updates the order of the tabs to match the new item order", ->
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
+ pane.moveItem(item2, 1)
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "Item 2", "sample.js"]
+ pane.moveItem(editSession1, 0)
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["sample.js", "Item 1", "Item 2"]
+ pane.moveItem(item1, 2)
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["sample.js", "Item 2", "Item 1"]
describe "dragging and dropping tabs", ->
- describe "when the tab is dropped onto itself", ->
- it "doesn't move the edit session and focuses the editor", ->
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
+ buildDragEvents = (dragged, dropTarget) ->
+ dataTransfer =
+ data: {}
+ setData: (key, value) -> @data[key] = value
+ getData: (key) -> @data[key]
- sortableElement = [tabs.find('.tab:eq(0)')]
- spyOn(tabs, 'getSortableElement').andCallFake -> sortableElement[0]
- event = $.Event()
- event.target = tabs[0]
- event.originalEvent =
- dataTransfer:
- data: {}
- setData: (key, value) -> @data[key] = value
- getData: (key) -> @data[key]
+ dragStartEvent = $.Event()
+ dragStartEvent.target = dragged[0]
+ dragStartEvent.originalEvent = { dataTransfer }
- editor.hiddenInput.focusout()
- tabs.onDragStart(event)
- tabs.onDrop(event)
+ dropEvent = $.Event()
+ dropEvent.target = dropTarget[0]
+ dropEvent.originalEvent = { dataTransfer }
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
- expect(editor.isFocused).toBeTruthy()
+ [dragStartEvent, dropEvent]
- describe "when a tab is dragged from and dropped onto the same editor", ->
- it "moves the edit session, updates the order of the tabs, and focuses the editor", ->
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
+ describe "when a tab is dragged within the same pane", ->
+ describe "when it is dropped on tab that's later in the list", ->
+ it "moves the tab and its item, shows the tab's item, and focuses the pane", ->
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
+ expect(pane.getItems()).toEqual [item1, editSession1, item2]
+ expect(pane.activeItem).toBe item2
+ spyOn(pane, 'focus')
- sortableElement = [tabs.find('.tab:eq(0)')]
- spyOn(tabs, 'getSortableElement').andCallFake -> sortableElement[0]
- event = $.Event()
- event.target = tabs[0]
- event.originalEvent =
- dataTransfer:
- data: {}
- setData: (key, value) -> @data[key] = value
- getData: (key) -> @data[key]
+ [dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(0), tabBar.tabAtIndex(1))
+ tabBar.onDragStart(dragStartEvent)
+ tabBar.onDrop(dropEvent)
- editor.hiddenInput.focusout()
- tabs.onDragStart(event)
- sortableElement = [tabs.find('.tab:eq(1)')]
- tabs.onDrop(event)
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["sample.js", "Item 1", "Item 2"]
+ expect(pane.getItems()).toEqual [editSession1, item1, item2]
+ expect(pane.activeItem).toBe item1
+ expect(pane.focus).toHaveBeenCalled()
- expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.txt"
- expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.js"
- expect(editor.isFocused).toBeTruthy()
+ describe "when it is dropped on a tab that's earlier in the list", ->
+ it "moves the tab and its item, shows the tab's item, and focuses the pane", ->
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
+ expect(pane.getItems()).toEqual [item1, editSession1, item2]
+ expect(pane.activeItem).toBe item2
+ spyOn(pane, 'focus')
- describe "when a tab is dragged from one editor and dropped onto another editor", ->
- it "moves the edit session, updates the order of the tabs, and focuses the destination editor", ->
- leftTabs = tabs
- rightEditor = editor.splitRight()
- rightTabs = rootView.find('.tabs:last').view()
+ [dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(2), tabBar.tabAtIndex(0))
+ tabBar.onDragStart(dragStartEvent)
+ tabBar.onDrop(dropEvent)
- sortableElement = [leftTabs.find('.tab:eq(0)')]
- spyOn(tabs, 'getSortableElement').andCallFake -> sortableElement[0]
- event = $.Event()
- event.target = leftTabs
- event.originalEvent =
- dataTransfer:
- data: {}
- setData: (key, value) -> @data[key] = value
- getData: (key) -> @data[key]
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "Item 2", "sample.js"]
+ expect(pane.getItems()).toEqual [item1, item2, editSession1]
+ expect(pane.activeItem).toBe item2
+ expect(pane.focus).toHaveBeenCalled()
- rightEditor.hiddenInput.focusout()
- tabs.onDragStart(event)
+ describe "when it is dropped on itself", ->
+ it "doesn't move the tab or item, but does make it the active item and focuses the pane", ->
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
+ expect(pane.getItems()).toEqual [item1, editSession1, item2]
+ expect(pane.activeItem).toBe item2
+ spyOn(pane, 'focus')
- event.target = rightTabs
- sortableElement = [rightTabs.find('.tab:eq(0)')]
- tabs.onDrop(event)
+ [dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(0), tabBar.tabAtIndex(0))
+ tabBar.onDragStart(dragStartEvent)
+ tabBar.onDrop(dropEvent)
- expect(rightTabs.find('.tab:eq(0) .file-name').text()).toBe "sample.txt"
- expect(rightTabs.find('.tab:eq(1) .file-name').text()).toBe "sample.js"
- expect(rightEditor.isFocused).toBeTruthy()
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
+ expect(pane.getItems()).toEqual [item1, editSession1, item2]
+ expect(pane.activeItem).toBe item1
+ expect(pane.focus).toHaveBeenCalled()
+
+ describe "when a tab is dragged to a different pane", ->
+ [pane2, tabBar2, item2b] = []
+
+ beforeEach ->
+ pane2 = pane.splitRight()
+ [item2b] = pane2.getItems()
+ tabBar2 = new TabBarView(pane2)
+
+ it "removes the tab and item from their original pane and moves them to the target pane", ->
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
+ expect(pane.getItems()).toEqual [item1, editSession1, item2]
+ expect(pane.activeItem).toBe item2
+
+ expect(tabBar2.getTabs().map (tab) -> tab.text()).toEqual ["Item 2"]
+ expect(pane2.getItems()).toEqual [item2b]
+ expect(pane2.activeItem).toBe item2b
+ spyOn(pane2, 'focus')
+
+ [dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(0), tabBar2.tabAtIndex(0))
+ tabBar.onDragStart(dragStartEvent)
+ tabBar.onDrop(dropEvent)
+
+ expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["sample.js", "Item 2"]
+ expect(pane.getItems()).toEqual [editSession1, item2]
+ expect(pane.activeItem).toBe item2
+
+ expect(tabBar2.getTabs().map (tab) -> tab.text()).toEqual ["Item 2", "Item 1"]
+ expect(pane2.getItems()).toEqual [item2b, item1]
+ expect(pane2.activeItem).toBe item1
+ expect(pane2.focus).toHaveBeenCalled()
diff --git a/src/packages/tree-view/lib/tree-view.coffee b/src/packages/tree-view/lib/tree-view.coffee
index ee4ebf0c3..bf1df2b2e 100644
--- a/src/packages/tree-view/lib/tree-view.coffee
+++ b/src/packages/tree-view/lib/tree-view.coffee
@@ -40,7 +40,7 @@ class TreeView extends ScrollView
else
@selectActiveFile()
- rootView.on 'root-view:active-path-changed', => @selectActiveFile()
+ rootView.on 'pane:active-item-changed pane:became-active', => @selectActiveFile()
project.on 'path-changed', => @updateRoot()
@observeConfig 'core.hideGitIgnoredFiles', => @updateRoot()
@@ -98,7 +98,7 @@ class TreeView extends ScrollView
@openSelectedEntry(false) if entry instanceof FileView
when 2
if entry.is('.selected.file')
- rootView.getActiveEditor().focus()
+ rootView.getActiveView().focus()
else if entry.is('.selected.directory')
entry.toggleExpansion()
@@ -119,6 +119,7 @@ class TreeView extends ScrollView
updateRoot: ->
@root?.remove()
+
if rootDirectory = project.getRootDirectory()
@root = new DirectoryView(directory: rootDirectory, isExpanded: true, project: project)
@treeViewList.append(@root)
@@ -126,14 +127,14 @@ class TreeView extends ScrollView
@root = null
selectActiveFile: ->
- activeFilePath = rootView.getActiveEditor()?.getPath()
+ activeFilePath = rootView.getActiveView()?.getPath()
@selectEntryForPath(activeFilePath) if activeFilePath
revealActiveFile: ->
@attach()
@focus()
- return unless activeFilePath = rootView.getActiveEditor()?.getPath()
+ return unless activeFilePath = rootView.getActiveView()?.getPath()
activePathComponents = project.relativize(activeFilePath).split('/')
currentPath = project.getPath().replace(/\/$/, '')
diff --git a/src/packages/tree-view/lib/tree.coffee b/src/packages/tree-view/lib/tree.coffee
index cd43e4467..df3cb45c3 100644
--- a/src/packages/tree-view/lib/tree.coffee
+++ b/src/packages/tree-view/lib/tree.coffee
@@ -2,7 +2,7 @@ module.exports =
treeView: null
activate: (@state) ->
- @state.attached ?= true unless rootView.getActiveEditSession()
+ @state.attached ?= true unless rootView.getActivePaneItem()
@createView() if @state.attached
rootView.command 'tree-view:toggle', => @createView().toggle()
diff --git a/src/packages/tree-view/spec/tree-view-spec.coffee b/src/packages/tree-view/spec/tree-view-spec.coffee
index c364bf527..fc2f9fe81 100644
--- a/src/packages/tree-view/spec/tree-view-spec.coffee
+++ b/src/packages/tree-view/spec/tree-view-spec.coffee
@@ -65,7 +65,7 @@ describe "TreeView", ->
describe "when the project is assigned a path because a new buffer is saved", ->
it "creates a root directory view but does not attach to the root view", ->
- rootView.getActiveEditSession().saveAs("/tmp/test.txt")
+ rootView.getActivePaneItem().saveAs("/tmp/test.txt")
expect(treeView.hasParent()).toBeFalsy()
expect(treeView.root.getPath()).toBe require.resolve('/tmp')
expect(treeView.root.parent()).toMatchSelector(".tree-view")
@@ -174,14 +174,14 @@ describe "TreeView", ->
describe "if the current file has no path", ->
it "shows and focuses the tree view, but does not attempt to select a specific file", ->
rootView.open()
- expect(rootView.getActiveEditSession().getPath()).toBeUndefined()
+ expect(rootView.getActivePaneItem().getPath()).toBeUndefined()
rootView.trigger 'tree-view:reveal-active-file'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView.focus).toHaveBeenCalled()
describe "if there is no editor open", ->
it "shows and focuses the tree view, but does not attempt to select a specific file", ->
- expect(rootView.getActiveEditSession()).toBeUndefined()
+ expect(rootView.getActivePaneItem()).toBeUndefined()
rootView.trigger 'tree-view:reveal-active-file'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView.focus).toHaveBeenCalled()
@@ -195,7 +195,7 @@ describe "TreeView", ->
treeView.trigger 'tool-panel:unfocus'
expect(treeView).toBeVisible()
expect(treeView.find(".tree-view")).not.toMatchSelector(':focus')
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when core:close is triggered on the tree view", ->
it "detaches the TreeView, focuses the RootView and does not bubble the core:close event", ->
@@ -262,28 +262,28 @@ describe "TreeView", ->
describe "when a file is single-clicked", ->
it "selects the files and opens it in the active editor, without changing focus", ->
- expect(rootView.getActiveEditor()).toBeUndefined()
+ expect(rootView.getActiveView()).toBeUndefined()
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
- expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
sampleTxt.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
- expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.txt')
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.txt')
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
describe "when a file is double-clicked", ->
it "selects the file and opens it in the active editor on the first click, then changes focus to the active editor on the second", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
- expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
sampleJs.trigger clickEvent(originalEvent: { detail: 2 })
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when a directory is single-clicked", ->
it "is selected", ->
@@ -299,26 +299,25 @@ describe "TreeView", ->
expect(subdir).toHaveClass 'selected'
subdir.trigger clickEvent(originalEvent: { detail: 2 })
expect(subdir).toHaveClass 'expanded'
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
describe "when a new file is opened in the active editor", ->
- it "is selected in the tree view if the file's entry visible", ->
+ it "selects the file in the tree view if the file's entry visible", ->
sampleJs.click()
rootView.open(require.resolve('fixtures/tree-view/tree-view.txt'))
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
- it "selected a file's parent dir if the file's entry is not visible", ->
- rootView.open(require.resolve('fixtures/tree-view/dir1/sub-dir1/sub-file1'))
-
+ it "selects the file's parent dir if the file's entry is not visible", ->
+ rootView.open('dir1/sub-dir1/sub-file1')
dirView = treeView.root.find('.directory:contains(dir1)').view()
expect(dirView).toHaveClass 'selected'
describe "when a different editor becomes active", ->
it "selects the file in that is open in that editor", ->
sampleJs.click()
- leftEditor = rootView.getActiveEditor()
+ leftEditor = rootView.getActiveView()
rightEditor = leftEditor.splitRight()
sampleTxt.click()
@@ -569,8 +568,8 @@ describe "TreeView", ->
it "opens the file in the editor and focuses it", ->
treeView.root.find('.file:contains(tree-view.js)').click()
treeView.root.trigger 'tree-view:open-selected-entry'
- expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when a directory is selected", ->
it "expands or collapses the directory", ->
@@ -586,7 +585,7 @@ describe "TreeView", ->
describe "when nothing is selected", ->
it "does nothing", ->
treeView.root.trigger 'tree-view:open-selected-entry'
- expect(rootView.getActiveEditor()).toBeUndefined()
+ expect(rootView.getActiveView()).toBeUndefined()
describe "file modification", ->
[dirView, fileView, rootDirPath, dirPath, filePath] = []
@@ -650,7 +649,7 @@ describe "TreeView", ->
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isFile(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
- expect(rootView.getActiveEditor().getPath()).toBe newPath
+ expect(rootView.getActiveView().getPath()).toBe newPath
waitsFor "tree view to be updated", ->
dirView.entries.find("> .file").length > 1
@@ -680,9 +679,9 @@ describe "TreeView", ->
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isDirectory(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
- expect(rootView.getActiveEditor().getPath()).not.toBe newPath
+ expect(rootView.getActiveView().getPath()).not.toBe newPath
expect(treeView.find(".tree-view")).toMatchSelector(':focus')
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
expect(dirView.find('.directory.selected:contains(new)').length).toBe(1)
it "selects the created directory", ->
@@ -693,9 +692,9 @@ describe "TreeView", ->
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isDirectory(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
- expect(rootView.getActiveEditor().getPath()).not.toBe newPath
+ expect(rootView.getActiveView().getPath()).not.toBe newPath
expect(treeView.find(".tree-view")).toMatchSelector(':focus')
- expect(rootView.getActiveEditor().isFocused).toBeFalsy()
+ expect(rootView.getActiveView().isFocused).toBeFalsy()
expect(dirView.find('.directory.selected:contains(new2)').length).toBe(1)
describe "when a file or directory already exists at the given path", ->
@@ -722,7 +721,7 @@ describe "TreeView", ->
rootView.attachToDom()
rootView.focus()
expect(addDialog.parent()).not.toExist()
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when a directory is selected", ->
it "opens an add dialog with the directory's path populated", ->
@@ -839,7 +838,7 @@ describe "TreeView", ->
rootView.attachToDom()
rootView.focus()
expect(moveDialog.parent()).not.toExist()
- expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+ expect(rootView.getActiveView().isFocused).toBeTruthy()
describe "when a file is selected that's name starts with a '.'", ->
[dotFilePath, dotFileView, moveDialog] = []
diff --git a/src/packages/wrap-guide/spec/wrap-guide-spec.coffee b/src/packages/wrap-guide/spec/wrap-guide-spec.coffee
index 5944b7692..ff14f7c27 100644
--- a/src/packages/wrap-guide/spec/wrap-guide-spec.coffee
+++ b/src/packages/wrap-guide/spec/wrap-guide-spec.coffee
@@ -8,7 +8,7 @@ describe "WrapGuide", ->
rootView.open('sample.js')
window.loadPackage('wrap-guide')
rootView.attachToDom()
- editor = rootView.getActiveEditor()
+ editor = rootView.getActiveView()
wrapGuide = rootView.find('.wrap-guide').view()
editor.width(editor.charWidth * wrapGuide.getDefaultColumn() * 2)
editor.trigger 'resize'
diff --git a/src/stdlib/task.coffee b/src/stdlib/task.coffee
index 3177e50be..eb3e2f3f5 100644
--- a/src/stdlib/task.coffee
+++ b/src/stdlib/task.coffee
@@ -1,3 +1,6 @@
+_ = require 'underscore'
+EventEmitter = require 'event-emitter'
+
module.exports =
class Task
aborted: false
@@ -49,3 +52,6 @@ class Task
@abort()
@worker?.terminate()
@worker = null
+ @trigger 'task-completed'
+
+_.extend Task.prototype, EventEmitter
diff --git a/static/atom.css b/static/atom.css
index bfe56f43b..6cfb79269 100644
--- a/static/atom.css
+++ b/static/atom.css
@@ -21,12 +21,12 @@ html, body {
-webkit-flex-flow: column;
}
-#root-view #panes {
+#panes {
position: relative;
-webkit-flex: 1;
}
-#root-view #panes .column {
+#panes .column {
position: absolute;
top: 0;
bottom: 0;
@@ -35,7 +35,7 @@ html, body {
overflow-y: hidden;
}
-#root-view #panes .row {
+#panes .row {
position: absolute;
top: 0;
bottom: 0;
@@ -44,7 +44,7 @@ html, body {
overflow-x: hidden;
}
-#root-view #panes .pane {
+#panes .pane {
position: absolute;
display: -webkit-flex;
-webkit-flex-flow: column;
@@ -55,6 +55,12 @@ html, body {
box-sizing: border-box;
}
+#panes .pane .item-views {
+ -webkit-flex: 1;
+ display: -webkit-flex;
+ -webkit-flex-flow: column;
+}
+
@font-face {
font-family: 'Octicons Regular';
src: url("octicons-regular-webfont.woff") format("woff");
@@ -72,4 +78,4 @@ html, body {
position: relative;
display: inline-block;
padding-left: 19px;
-}
\ No newline at end of file
+}
diff --git a/static/tabs.css b/static/tabs.css
index 29133287a..bb262bba1 100644
--- a/static/tabs.css
+++ b/static/tabs.css
@@ -23,7 +23,7 @@
-webkit-flex: 2;
}
-.tab .file-name {
+.tab .title {
display: block;
overflow: hidden;
white-space: nowrap;
@@ -52,7 +52,7 @@
color: #fff;
}
-.tab.file-modified .close-icon {
+.tab.modified .close-icon {
top: 11px;
width: 5px;
height: 5px;
@@ -61,11 +61,11 @@
border-radius: 12px;
}
-.tab.file-modified .close-icon:before {
+.tab.modified .close-icon:before {
content: "";
}
-.tab.file-modified:hover .close-icon {
+.tab.modified:hover .close-icon {
border: none;
width: 12px;
height: 12px;
@@ -73,7 +73,7 @@
top: 5px;
}
-.tab.file-modified:hover .close-icon:before {
+.tab.modified:hover .close-icon:before {
content: "\f081";
color: #66a6ff;
}
diff --git a/vendor/space-pen.coffee b/vendor/space-pen.coffee
index 08b715960..f734350e0 100644
--- a/vendor/space-pen.coffee
+++ b/vendor/space-pen.coffee
@@ -162,7 +162,8 @@ class Builder
options.attributes = arg
options
-jQuery.fn.view = -> this.data('view')
+jQuery.fn.view = -> @data('view')
+jQuery.fn.views = -> @toArray().map (elt) -> jQuery(elt).view()
# Trigger attach event when views are added to the DOM
callAttachHook = (element) ->