From 5f2bd9edd7cf1c381d898faa74cf87f7b70a5b72 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 15:13:00 -0500 Subject: [PATCH 001/160] Add a prebuild script for Constructicon Constructicon will run this just before building the project, so this is our chance to install the node modules we need and create the .xcodeproj. --- script/constructicon/prebuild | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 script/constructicon/prebuild diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild new file mode 100755 index 000000000..23ac36fb5 --- /dev/null +++ b/script/constructicon/prebuild @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +cd "$(dirname "$0")/../.." + +npm install +rake create-xcode-project From caf0dec598f132006c40e4bf8797db12eadc92f9 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 15:21:21 -0500 Subject: [PATCH 002/160] Make the prebuild script noisy while we debug --- script/constructicon/prebuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild index 23ac36fb5..2e7417cf7 100755 --- a/script/constructicon/prebuild +++ b/script/constructicon/prebuild @@ -1,6 +1,6 @@ #!/bin/sh -set -e +set -ex cd "$(dirname "$0")/../.." From ebaa344164cd7109d5a7f06b54fd61413828b6e9 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 15:38:39 -0500 Subject: [PATCH 003/160] Put Constructicon's node in PATH --- script/constructicon/prebuild | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild index 2e7417cf7..477bf609a 100755 --- a/script/constructicon/prebuild +++ b/script/constructicon/prebuild @@ -4,5 +4,7 @@ set -ex cd "$(dirname "$0")/../.." +export PATH="/usr/local/Cellar/node/0.8.21/bin:${PATH}" + npm install rake create-xcode-project From aae5ebc81041b131c2e9756c0fc0a697710af8fb Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 15:52:58 -0500 Subject: [PATCH 004/160] Ensure gyp is in Constructicon's PATH --- script/constructicon/prebuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild index 477bf609a..f0bffa13b 100755 --- a/script/constructicon/prebuild +++ b/script/constructicon/prebuild @@ -4,7 +4,7 @@ set -ex cd "$(dirname "$0")/../.." -export PATH="/usr/local/Cellar/node/0.8.21/bin:${PATH}" +export PATH="/usr/local/Cellar/node/0.8.21/bin:/usr/local/bin:${PATH}" npm install rake create-xcode-project From 01e0e886e60b875b6df1ce60b4ffe253594333d7 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 15:53:12 -0500 Subject: [PATCH 005/160] Turn on code signing in Constructicon --- script/constructicon/prebuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild index f0bffa13b..d6338333a 100755 --- a/script/constructicon/prebuild +++ b/script/constructicon/prebuild @@ -7,4 +7,4 @@ cd "$(dirname "$0")/../.." export PATH="/usr/local/Cellar/node/0.8.21/bin:/usr/local/bin:${PATH}" npm install -rake create-xcode-project +rake setup-codesigning create-xcode-project From 28d4ea0456d711017208071dcc966d3915fb8568 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 16:38:30 -0500 Subject: [PATCH 006/160] Don't source env.sh unless it exists --- script/compile-coffee | 6 +++++- script/compile-cson | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/script/compile-coffee b/script/compile-coffee index 298d9f53c..4ec30577b 100755 --- a/script/compile-coffee +++ b/script/compile-coffee @@ -6,7 +6,11 @@ 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 +if node --version > /dev/null 2>&1; then + # cool +elif [ -e /opt/github/env.sh ]; then + source /opt/github/env.sh +fi INPUT_FILE="${1}" OUTPUT_FILE="${2}" diff --git a/script/compile-cson b/script/compile-cson index bc3a79ff3..20f8f7534 100755 --- a/script/compile-cson +++ b/script/compile-cson @@ -6,7 +6,11 @@ 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 +if node --version > /dev/null 2>&1; then + # cool +elif [ -e /opt/github/env.sh ]; then + source /opt/github/env.sh +fi INPUT_FILE="${1}" OUTPUT_FILE="${2}" From 7b32560ce2f0ba6d53d130195ef4cb1009eeb7e4 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 16:41:36 -0500 Subject: [PATCH 007/160] Fix syntax errors --- script/compile-coffee | 10 +++++----- script/compile-cson | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/script/compile-coffee b/script/compile-coffee index 4ec30577b..cb70ce654 100755 --- a/script/compile-coffee +++ b/script/compile-coffee @@ -6,11 +6,11 @@ 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. -if node --version > /dev/null 2>&1; then - # cool -elif [ -e /opt/github/env.sh ]; then - source /opt/github/env.sh -fi +node --version > /dev/null 2>&1 || { + if [ -e /opt/github/env.sh ]; then + source /opt/github/env.sh + fi +} INPUT_FILE="${1}" OUTPUT_FILE="${2}" diff --git a/script/compile-cson b/script/compile-cson index 20f8f7534..6f7cad53d 100755 --- a/script/compile-cson +++ b/script/compile-cson @@ -6,11 +6,11 @@ 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. -if node --version > /dev/null 2>&1; then - # cool -elif [ -e /opt/github/env.sh ]; then - source /opt/github/env.sh -fi +node --version > /dev/null 2>&1 || { + if [ -e /opt/github/env.sh ]; then + source /opt/github/env.sh + fi +} INPUT_FILE="${1}" OUTPUT_FILE="${2}" From 9e1b97577341a7e65e98e00e96756f63d78c02cf Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 16:45:40 -0500 Subject: [PATCH 008/160] Try finding node where it's installed on Constructicon --- script/compile-coffee | 3 +++ script/compile-cson | 3 +++ 2 files changed, 6 insertions(+) diff --git a/script/compile-coffee b/script/compile-coffee index cb70ce654..f3d19392c 100755 --- a/script/compile-coffee +++ b/script/compile-coffee @@ -9,6 +9,9 @@ set -e 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 } diff --git a/script/compile-cson b/script/compile-cson index 6f7cad53d..7168fd19d 100755 --- a/script/compile-cson +++ b/script/compile-cson @@ -9,6 +9,9 @@ set -e 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 } From 5421dddec69f629c15b9138fe1a9e033d693c356 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 16:51:10 -0500 Subject: [PATCH 009/160] info.plist -> Atom-Info.plist for Constructicon --- atom.gyp | 2 +- native/mac/{info.plist => Atom-Info.plist} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename native/mac/{info.plist => Atom-Info.plist} (100%) diff --git a/atom.gyp b/atom.gyp index 1c8f40997..2e0a2ba90 100644 --- a/atom.gyp +++ b/atom.gyp @@ -74,7 +74,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': [ 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 From 9b2468a48458dba0196a1a24fba211641e4ae95f Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 16:52:19 -0500 Subject: [PATCH 010/160] Update prebuilt-cef * prebuilt-cef c24e35c...3ced0be (1): > Use [[:space:]] instead of \s --- prebuilt-cef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prebuilt-cef b/prebuilt-cef index c24e35c3e..3ced0be18 160000 --- a/prebuilt-cef +++ b/prebuilt-cef @@ -1 +1 @@ -Subproject commit c24e35c3ed60952f9e3ed6b3a73e17f0714d147b +Subproject commit 3ced0be18717e65fcf4d832bded37156cd0710e7 From cea04758a58da61baf42c39898030342b025ff58 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 16:56:24 -0500 Subject: [PATCH 011/160] Print the environment for Constructicon --- atom.gyp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/atom.gyp b/atom.gyp index 2e0a2ba90..8a13a0ec6 100644 --- a/atom.gyp +++ b/atom.gyp @@ -182,6 +182,12 @@ 'Atom', ], }, + { + 'postbuild_name': 'Print env for Constructicon', + 'action': [ + 'env', + ], + }, ], 'link_settings': { 'libraries': [ From 8394852f07c40673fdcc7ffe7d32b34bc3a83017 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 17:07:08 -0500 Subject: [PATCH 012/160] Remove the :package rake task We don't need this anymore since Constructicon takes care of packaging the app. --- Rakefile | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Rakefile b/Rakefile index 704999599..310a11104 100644 --- a/Rakefile +++ b/Rakefile @@ -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 From 192f8841bb933a2626c91895c8433897cb6ae791 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 18:00:11 -0500 Subject: [PATCH 013/160] Let Rakefile take care of running npm --- script/constructicon/prebuild | 1 - 1 file changed, 1 deletion(-) diff --git a/script/constructicon/prebuild b/script/constructicon/prebuild index d6338333a..734ca7c58 100755 --- a/script/constructicon/prebuild +++ b/script/constructicon/prebuild @@ -6,5 +6,4 @@ cd "$(dirname "$0")/../.." export PATH="/usr/local/Cellar/node/0.8.21/bin:/usr/local/bin:${PATH}" -npm install rake setup-codesigning create-xcode-project From ae1757aa4aa4e17c04cf3545dac32326dcd0de23 Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Mon, 4 Mar 2013 18:08:16 -0500 Subject: [PATCH 014/160] Add an empty changelog for Constructicon --- CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb From 716a78a77425c8beff5b9e83716f83507e416fe4 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Mon, 4 Mar 2013 15:36:07 -0800 Subject: [PATCH 015/160] Default all targets to Release --- atom.gyp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom.gyp b/atom.gyp index 8a13a0ec6..417fe2de1 100644 --- a/atom.gyp +++ b/atom.gyp @@ -39,7 +39,7 @@ 'sources.gypi', ], 'target_defaults': { - 'default_configuration': 'Debug', + 'default_configuration': 'Release', 'configurations': { 'Debug': { 'defines': ['DEBUG=1'], From 0c067b55ba2e9fb8de77aea3b17402b2e95fce8c Mon Sep 17 00:00:00 2001 From: probablycorey Date: Mon, 4 Mar 2013 15:38:06 -0800 Subject: [PATCH 016/160] Make rake install build with default configuration --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 310a11104..e26ae7a7b 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}" From 081e3a459fd08c75093df1f2abb043dc7a17dbca Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Mar 2013 15:55:38 -0800 Subject: [PATCH 017/160] Replace Consolas with PCMyungjo in spec --- spec/app/editor-spec.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 0d46cfba9..1f37a838e 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -554,13 +554,12 @@ describe "Editor", -> 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") + config.set("editor.fontFamily", "PCMyungjo") + editor.setCursorScreenPosition [5, 6] expect(editor.charWidth).not.toBe charWidthBefore expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth } From 0a21ef4a181859dce496d9de9d9e6c0e177f7873 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Kevin Sawicki Date: Tue, 5 Mar 2013 10:58:37 -0800 Subject: [PATCH 018/160] Remove unused code --- native/atom_window_controller.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index eaf40f2c5..f81331743 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -69,7 +69,6 @@ - (id)initDevWithPath:(NSString *)path { _pathToOpen = [path retain]; - AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication]; return [self initWithBootstrapScript:@"window-bootstrap" background:NO alwaysUseBundleResourcePath:false]; } From ad3782753bd71a838206a639c92f5c63be4c7f55 Mon Sep 17 00:00:00 2001 From: "Corey Johnson, Kevin Sawicki & Nathan Sobo" Date: Tue, 5 Mar 2013 13:58:58 -0800 Subject: [PATCH 019/160] Display :skull: in window bar when Atom is in dev mode Closes #350 --- native/atom_window_controller.mm | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index f81331743..89f6d17cd 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -43,9 +43,15 @@ } } + NSString *bundleResourcePath = [[NSBundle bundleForClass:self.class] resourcePath]; if (alwaysUseBundleResourcePath || !_resourcePath) { - _resourcePath = [[NSBundle bundleForClass:self.class] resourcePath]; + _resourcePath = bundleResourcePath; } + + if (![_resourcePath isEqualToString:bundleResourcePath]) { + [self displayDevIcon]; + } + _resourcePath = [_resourcePath stringByStandardizingPath]; [_resourcePath retain]; @@ -201,6 +207,28 @@ return YES; } +- (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_disabled = false; From 8e11ca58e525f6d51747e6a903df218913b7014a Mon Sep 17 00:00:00 2001 From: "Corey Johnson, Kevin Sawicki & Nathan Sobo" Date: Tue, 5 Mar 2013 14:03:23 -0800 Subject: [PATCH 020/160] Add isDevMode method --- native/atom_window_controller.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index 89f6d17cd..eed179a3c 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -43,12 +43,11 @@ } } - NSString *bundleResourcePath = [[NSBundle bundleForClass:self.class] resourcePath]; if (alwaysUseBundleResourcePath || !_resourcePath) { - _resourcePath = bundleResourcePath; + _resourcePath = [[NSBundle bundleForClass:self.class] resourcePath]; } - if (![_resourcePath isEqualToString:bundleResourcePath]) { + if (![self isDevMode]) { [self displayDevIcon]; } @@ -207,6 +206,11 @@ 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; From 0dfd3597fb31db5a3ced93fb4c5ae07f8a767dc6 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Mar 2013 12:48:45 -0800 Subject: [PATCH 021/160] add visual indicator for dev mode --- static/atom.css | 10 +++++++++- static/index.html | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/static/atom.css b/static/atom.css index bfe56f43b..187f53b74 100644 --- a/static/atom.css +++ b/static/atom.css @@ -10,6 +10,14 @@ html, body { position: relative; } +.is-dev-mode #root-view:before { + content: ""; + height: 3px; + display: block; + background-image: -webkit-linear-gradient(#ffc833, #ebac00); + border-bottom: 1px solid #000; +} + #root-view #horizontal { display: -webkit-flex; height: 100%; @@ -72,4 +80,4 @@ html, body { position: relative; display: inline-block; padding-left: 19px; -} \ No newline at end of file +} diff --git a/static/index.html b/static/index.html index 0e27bf115..75da64b8a 100644 --- a/static/index.html +++ b/static/index.html @@ -23,6 +23,11 @@ console.error(error.stack || error); } } + + document.addEventListener('DOMContentLoaded', function() { + if(window.location.params.devMode == "true") + document.body.setAttribute('class', 'is-dev-mode') + }) From d6ae5a17783f183640457ad1f8c057256235a642 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Mar 2013 14:10:01 -0800 Subject: [PATCH 022/160] Set atom.devMode --- native/atom_window_controller.mm | 4 +++- src/app/atom.coffee | 1 + static/index.html | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index eed179a3c..90bd4af8a 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -128,6 +128,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) @@ -208,7 +210,7 @@ - (bool)isDevMode { NSString *bundleResourcePath = [[NSBundle bundleForClass:self.class] resourcePath]; - return [_resourcePath isEqualToString:bundleResourcePath]; + return ![_resourcePath isEqualToString:bundleResourcePath]; } - (void)displayDevIcon { diff --git a/src/app/atom.coffee b/src/app/atom.coffee index b5149622e..6939a071f 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -10,6 +10,7 @@ originalSendMessageToBrowserProcess = atom.sendMessageToBrowserProcess _.extend atom, exitWhenDone: window.location.params.exitWhenDone + devMode: window.location.params.devMode loadedThemes: [] pendingBrowserProcessCallbacks: {} loadedPackages: [] diff --git a/static/index.html b/static/index.html index 75da64b8a..a78a5a823 100644 --- a/static/index.html +++ b/static/index.html @@ -25,8 +25,9 @@ } document.addEventListener('DOMContentLoaded', function() { - if(window.location.params.devMode == "true") + if (window.location.params.devMode) { document.body.setAttribute('class', 'is-dev-mode') + } }) From 9331b3beed7c387e33d329fe798764f1eaba6624 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Mar 2013 14:16:30 -0800 Subject: [PATCH 023/160] Add .dev-mode class to root view --- src/app/root-view.coffee | 1 + static/atom.css | 2 +- static/index.html | 6 ------ 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 558143889..4ef9662b8 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -34,6 +34,7 @@ class RootView extends View title: null initialize: -> + @addClass('dev-mode') if atom.devMode @command 'toggle-dev-tools', => atom.toggleDevTools() @on 'focus', (e) => @handleFocus(e) @subscribe $(window), 'focus', (e) => diff --git a/static/atom.css b/static/atom.css index 187f53b74..f029ddc77 100644 --- a/static/atom.css +++ b/static/atom.css @@ -10,7 +10,7 @@ html, body { position: relative; } -.is-dev-mode #root-view:before { +#root-view.dev-mode:before { content: ""; height: 3px; display: block; diff --git a/static/index.html b/static/index.html index a78a5a823..0e27bf115 100644 --- a/static/index.html +++ b/static/index.html @@ -23,12 +23,6 @@ console.error(error.stack || error); } } - - document.addEventListener('DOMContentLoaded', function() { - if (window.location.params.devMode) { - document.body.setAttribute('class', 'is-dev-mode') - } - }) From 1b403d2920e066485124d97e85427efb348cd55d Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Mar 2013 14:20:34 -0800 Subject: [PATCH 024/160] Fix logic mistake --- native/atom_window_controller.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index 90bd4af8a..1d9acd5d2 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -47,7 +47,7 @@ _resourcePath = [[NSBundle bundleForClass:self.class] resourcePath]; } - if (![self isDevMode]) { + if ([self isDevMode]) { [self displayDevIcon]; } From 57b0151cd28cd5f33a987bafded62d5cb9a623be Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Mar 2013 16:08:22 -0800 Subject: [PATCH 025/160] Remove the dev-mode css style, for now :soon: --- src/app/root-view.coffee | 1 - static/atom.css | 8 -------- 2 files changed, 9 deletions(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 4ef9662b8..558143889 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -34,7 +34,6 @@ class RootView extends View title: null initialize: -> - @addClass('dev-mode') if atom.devMode @command 'toggle-dev-tools', => atom.toggleDevTools() @on 'focus', (e) => @handleFocus(e) @subscribe $(window), 'focus', (e) => diff --git a/static/atom.css b/static/atom.css index f029ddc77..f16aa95be 100644 --- a/static/atom.css +++ b/static/atom.css @@ -10,14 +10,6 @@ html, body { position: relative; } -#root-view.dev-mode:before { - content: ""; - height: 3px; - display: block; - background-image: -webkit-linear-gradient(#ffc833, #ebac00); - border-bottom: 1px solid #000; -} - #root-view #horizontal { display: -webkit-flex; height: 100%; From b76ab87a96ac3e4ab8a08e3e312f0858306390ee Mon Sep 17 00:00:00 2001 From: Adam Roben Date: Wed, 6 Mar 2013 08:29:55 -0500 Subject: [PATCH 026/160] Use HTTPS for prebuilt-cef Fixes #361. --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 8af55a04d8529b1c0095fb08ce2a3e11a06603bb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 6 Mar 2013 14:59:12 -0800 Subject: [PATCH 027/160] Use a single worker for status refreshes There still appear to be crashes occurring when using libgit2 from multiple workers at the same time. So only start a new status worker once the current one completes if a refresh was requested while a worker was running. Closes #367 --- spec/app/git-spec.coffee | 20 ++++++++++++++++++++ src/app/git.coffee | 19 ++++++++++++++++--- src/stdlib/task.coffee | 6 ++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/spec/app/git-spec.coffee b/spec/app/git-spec.coffee index 36ca195e1..04c5596fc 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 @@ -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/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/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 From d509195aab449798219ee8b0f1fabe262a61474c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 7 Mar 2013 08:34:05 -0800 Subject: [PATCH 028/160] Free keys when config open fails --- native/v8_extensions/git.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/native/v8_extensions/git.mm b/native/v8_extensions/git.mm index 36229345e..f7804d7ee 100644 --- a/native/v8_extensions/git.mm +++ b/native/v8_extensions/git.mm @@ -129,8 +129,11 @@ namespace v8_extensions { 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; From 9fe1be7fe0fc081dcf66757d148cf395b2cde598 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 7 Mar 2013 08:54:29 -0800 Subject: [PATCH 029/160] Add parens around string length --- native/v8_extensions/git.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/v8_extensions/git.mm b/native/v8_extensions/git.mm index f7804d7ee..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,9 +122,9 @@ 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); From beaeac4425f05533519ac98539441178c3312ed5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 16 Feb 2013 15:00:29 -0700 Subject: [PATCH 030/160] Rename `buildEditSessionForPath` to `buildEditSession` Since this is the more external method, having a shorter name is more convenient. The former `buildEditSession` method took a Buffer, and is now called `buildEditSessionForBuffer`. --- benchmark/benchmark-suite.coffee | 2 +- spec/app/display-buffer-spec.coffee | 4 +- spec/app/edit-session-spec.coffee | 12 ++--- spec/app/editor-spec.coffee | 54 +++++++++---------- spec/app/language-mode-spec.coffee | 10 ++-- spec/app/project-spec.coffee | 18 +++---- spec/app/root-view-spec.coffee | 10 ++-- spec/app/tokenized-buffer-spec.coffee | 12 ++--- src/app/edit-session.coffee | 4 +- src/app/project.coffee | 6 +-- src/app/root-view.coffee | 4 +- .../spec/autocomplete-spec.coffee | 2 +- .../spec/command-interpreter-spec.coffee | 4 +- .../fuzzy-finder/lib/fuzzy-finder-view.coffee | 2 +- .../spec/fuzzy-finder-spec.coffee | 2 +- 15 files changed, 73 insertions(+), 73 deletions(-) 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/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index fe5dc83f7..de7404eee 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 = fixturesProject.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 = fixturesProject.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..cbd7a90e9 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -9,7 +9,7 @@ describe "EditSession", -> buffer.setText(buffer.getText().replace(/[ ]{2}/g, "\t")) beforeEach -> - editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false) + editSession = fixturesProject.buildEditSession('sample.js', autoIndent: false) buffer = editSession.buffer lineLengths = buffer.getLines().map (line) -> line.length @@ -1715,7 +1715,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 = fixturesProject.buildEditSession(null, autoIndent: false) editSession.setSelectedBufferRange([[4, 5], [4, 5]]) editSession.toggleLineCommentsInSelection() expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {" @@ -1793,7 +1793,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 = fixturesProject.buildEditSession(editSession.getPath()) otherEditSession.setSelectedBufferRange([[2, 2], [3, 3]]) otherEditSession.delete() @@ -1986,13 +1986,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 = fixturesProject.buildEditSession('sample.js', softTabs: false) expect(editSession.softTabs).toBeTruthy() - editSession = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', softTabs: true) + editSession = fixturesProject.buildEditSession('sample-with-tabs.coffee', softTabs: true) expect(editSession.softTabs).toBeFalsy() - editSession = fixturesProject.buildEditSessionForPath(null, softTabs: false) + editSession = fixturesProject.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 1f37a838e..c8b0572ff 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -14,7 +14,7 @@ describe "Editor", -> getLineHeight = -> return cachedLineHeight if cachedLineHeight? - editorForMeasurement = new Editor(editSession: project.buildEditSessionForPath('sample.js')) + editorForMeasurement = new Editor(editSession: project.buildEditSession('sample.js')) editorForMeasurement.attachToDom() cachedLineHeight = editorForMeasurement.lineHeight editorForMeasurement.remove() @@ -46,7 +46,7 @@ describe "Editor", -> rootView.height(8 * editor.lineHeight) rootView.width(50 * editor.charWidth) - editor.edit(project.buildEditSessionForPath('two-hundred.txt')) + editor.edit(project.buildEditSession('two-hundred.txt')) editor.setCursorScreenPosition([5, 1]) editor.scrollTop(1.5 * editor.lineHeight) editor.scrollView.scrollLeft(44) @@ -75,7 +75,7 @@ describe "Editor", -> 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')) + editor.edit(project.buildEditSession('/tmp/delete-me')) fs.remove('/tmp/delete-me') newEditor = editor.copy() expect(console.warn).toHaveBeenCalled() @@ -117,7 +117,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") @@ -138,7 +138,7 @@ describe "Editor", -> it "removes subscriptions from all edit session buffers", -> editSession1 = editor.activeEditSession subscriberCount1 = editSession1.buffer.subscriptionCount() - editSession2 = project.buildEditSessionForPath(project.resolve('sample.txt')) + editSession2 = project.buildEditSession(project.resolve('sample.txt')) expect(subscriberCount1).toBeGreaterThan 1 editor.edit(editSession2) @@ -151,17 +151,17 @@ describe "Editor", -> describe "when 'close' is triggered", -> it "adds a closed session path to the array", -> - editor.edit(project.buildEditSessionForPath()) + editor.edit(project.buildEditSession()) 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.edit(project.buildEditSession(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()) + editor.edit(project.buildEditSession()) editSession = editor.activeEditSession spyOn(editSession.buffer, 'isModified').andReturn false spyOn(editSession, 'destroy').andCallThrough() @@ -172,7 +172,7 @@ describe "Editor", -> 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()) + editor.edit(project.buildEditSession()) editSession = editor.activeEditSession index = editor.getActiveEditSessionIndex() spyOn(editSession.buffer, 'isModified').andReturn false @@ -220,7 +220,7 @@ describe "Editor", -> otherEditSession = null beforeEach -> - otherEditSession = project.buildEditSessionForPath() + otherEditSession = project.buildEditSession() 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", -> @@ -257,11 +257,11 @@ describe "Editor", -> expect(editor.lineElementForScreenRow(0).text()).toBe 'def' it "removes the opened session from the closed sessions array", -> - editor.edit(project.buildEditSessionForPath('sample.txt')) + editor.edit(project.buildEditSession('sample.txt')) expect(editor.closedEditSessions.length).toBe 0 editor.trigger "core:close" expect(editor.closedEditSessions.length).toBe 1 - editor.edit(project.buildEditSessionForPath('sample.txt')) + editor.edit(project.buildEditSession('sample.txt')) expect(editor.closedEditSessions.length).toBe 0 describe "switching edit sessions", -> @@ -270,10 +270,10 @@ describe "Editor", -> beforeEach -> session0 = editor.activeEditSession - editor.edit(project.buildEditSessionForPath('sample.txt')) + editor.edit(project.buildEditSession('sample.txt')) session1 = editor.activeEditSession - editor.edit(project.buildEditSessionForPath('two-hundred.txt')) + editor.edit(project.buildEditSession('two-hundred.txt')) session2 = editor.activeEditSession describe ".setActiveEditSessionIndex(index)", -> @@ -304,7 +304,7 @@ describe "Editor", -> 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) + editSession = project.buildEditSession(path) editor.edit editSession editSession.insertText("a buffer change") @@ -379,7 +379,7 @@ describe "Editor", -> describe "when the current buffer has no path", -> selectedFilePath = null beforeEach -> - editor.edit(project.buildEditSessionForPath()) + editor.edit(project.buildEditSession()) expect(editor.getPath()).toBeUndefined() editor.getBuffer().setText 'Save me to a new path' @@ -459,7 +459,7 @@ describe "Editor", -> 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.edit project.buildEditSession("sample.txt") editor.splitUp() expect(fakePane.splitUp).toHaveBeenCalled() [newEditor] = fakePane.splitUp.argsForCall[0] @@ -506,7 +506,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 +514,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() @@ -1374,7 +1374,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) @@ -1410,7 +1410,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) @@ -1706,7 +1706,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 @@ -1987,7 +1987,7 @@ 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)) + editor.edit(fixturesProject.buildEditSession(null)) expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1 editor.setActiveEditSessionIndex(0) @@ -2121,7 +2121,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() @@ -2212,9 +2212,9 @@ describe "Editor", -> 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()) + editor.edit(project.buildEditSession('sample.txt')) + editor.edit(project.buildEditSession('two-hundred.txt')) + editor.edit(project.buildEditSession()) paths = editor.getOpenBufferPaths().map (path) -> project.relativize(path) expect(paths).toEqual = ['sample.js', 'sample.txt', 'two-hundred.txt'] diff --git a/spec/app/language-mode-spec.coffee b/spec/app/language-mode-spec.coffee index a68f778dc..1d53922f2 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 = fixturesProject.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 = fixturesProject.buildEditSession('js', autoIndent: false) expect(jsEditSession.languageMode.grammar.name).toBe "JavaScript" jsEditSession.destroy() describe "javascript", -> beforeEach -> - editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false) + editSession = fixturesProject.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 = fixturesProject.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 = fixturesProject.buildEditSession('css.css', autoIndent: false) { buffer, languageMode } = editSession describe ".toggleLineCommentsForBufferRows(start, end)", -> diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index 09549eb98..db5eaaf92 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -12,8 +12,8 @@ describe "Project", -> describe "when editSession 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 +24,7 @@ describe "Project", -> anotherEditSession.destroy() expect(project.editSessions.length).toBe 0 - describe ".buildEditSessionForPath(path)", -> + describe ".buildEditSession(path)", -> [absolutePath, newBufferHandler, newEditSessionHandler] = [] beforeEach -> absolutePath = require.resolve('fixtures/dir/a') @@ -35,30 +35,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..62c9dfaea 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -43,10 +43,10 @@ describe "RootView", -> editor2 = editor1.splitRight() editor3 = editor2.splitRight() editor4 = editor2.splitDown() - editor2.edit(project.buildEditSessionForPath('b')) - editor3.edit(project.buildEditSessionForPath('../sample.js')) + editor2.edit(project.buildEditSession('b')) + editor3.edit(project.buildEditSession('../sample.js')) editor3.setCursorScreenPosition([2, 4]) - editor4.edit(project.buildEditSessionForPath('../sample.txt')) + editor4.edit(project.buildEditSession('../sample.txt')) editor4.setCursorScreenPosition([0, 2]) rootView.attachToDom() editor2.focus() @@ -404,7 +404,7 @@ describe "RootView", -> editor2 = rootView.getActiveEditor().splitLeft() path = project.resolve('b') - editor2.edit(project.buildEditSessionForPath(path)) + editor2.edit(project.buildEditSession(path)) expect(pathChangeHandler).toHaveBeenCalled() expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}" @@ -584,7 +584,7 @@ describe "RootView", -> expect(buffer1.isModified()).toBe(true) editor2 = editor1.splitRight() - editor2.edit(project.buildEditSessionForPath('atom-temp2.txt')) + editor2.edit(project.buildEditSession('atom-temp2.txt')) buffer2 = editor2.activeEditSession.buffer expect(buffer2.getText()).toBe("file2") expect(buffer2.isModified()).toBe(false) diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index 944628904..52e48cf58 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 = fixturesProject.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 = fixturesProject.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 = fixturesProject.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 = fixturesProject.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 = fixturesProject.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 = fixturesProject.buildEditSession('function.mm', autoIndent: false) buffer = editSession.buffer tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer editSession.setVisible(true) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index b160d5468..4e06fde11 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -16,10 +16,10 @@ class EditSession @deserialize: (state, project) -> 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) 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..d01ac9c03 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -95,7 +95,7 @@ class RootView extends View allowActiveEditorChange = options.allowActiveEditorChange ? false unless editSession = @openInExistingEditor(path, allowActiveEditorChange, changeFocus) - editSession = project.buildEditSessionForPath(path) + editSession = project.buildEditSession(path) editor = new Editor({editSession}) pane = new Pane(editor) @panes.append(pane) @@ -121,7 +121,7 @@ class RootView extends View @makeEditorActive(editor, changeFocus) return editSession - editSession = project.buildEditSessionForPath(path) + editSession = project.buildEditSession(path) activeEditor.edit(editSession) editSession diff --git a/src/packages/autocomplete/spec/autocomplete-spec.coffee b/src/packages/autocomplete/spec/autocomplete-spec.coffee index 8d019d69b..6912fd5b4 100644 --- a/src/packages/autocomplete/spec/autocomplete-spec.coffee +++ b/src/packages/autocomplete/spec/autocomplete-spec.coffee @@ -40,7 +40,7 @@ describe "AutocompleteView", -> beforeEach -> window.rootView = new RootView - editor = new Editor(editSession: fixturesProject.buildEditSessionForPath('sample.js')) + editor = new Editor(editSession: fixturesProject.buildEditSession('sample.js')) window.loadPackage('autocomplete') autocomplete = new AutocompleteView(editor) miniEditor = autocomplete.miniEditor diff --git a/src/packages/command-panel/spec/command-interpreter-spec.coffee b/src/packages/command-panel/spec/command-interpreter-spec.coffee index 289d87709..c9f590346 100644 --- a/src/packages/command-panel/spec/command-interpreter-spec.coffee +++ b/src/packages/command-panel/spec/command-interpreter-spec.coffee @@ -11,7 +11,7 @@ describe "CommandInterpreter", -> beforeEach -> project = new Project(fixturesProject.resolve('dir/')) interpreter = new CommandInterpreter(fixturesProject) - editSession = fixturesProject.buildEditSessionForPath('sample.js') + editSession = fixturesProject.buildEditSession('sample.js') buffer = editSession.buffer afterEach -> @@ -428,7 +428,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/fuzzy-finder/lib/fuzzy-finder-view.coffee b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee index 3eddc0c8e..480ecab33 100644 --- a/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee +++ b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee @@ -73,7 +73,7 @@ class FuzzyFinderView extends SelectList editor = rootView.getActiveEditor() if editor - fn(editor, project.buildEditSessionForPath(path)) + fn(editor, project.buildEditSession(path)) else @openPath(path) diff --git a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee index f322d4613..469b0fe96 100644 --- a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee +++ b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee @@ -163,7 +163,7 @@ describe 'FuzzyFinder', -> describe "when the active editor only contains edit sessions for anonymous buffers", -> it "does not open", -> editor = rootView.getActiveEditor() - editor.edit(project.buildEditSessionForPath()) + editor.edit(project.buildEditSession()) editor.loadPreviousEditSession() editor.destroyActiveEditSession() expect(editor.getOpenBufferPaths().length).toBe 0 From 5b58751a149a3bcf8b2375bc22b5cb4456fbc30c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 16 Feb 2013 16:02:43 -0700 Subject: [PATCH 031/160] :lipstick: --- src/app/pane.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 5b0e630f6..b44e317a5 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -17,11 +17,10 @@ class Pane extends View adjustDimensions: -> # do nothing - horizontalGridUnits: -> - 1 + horizontalGridUnits: -> 1 + + verticalGridUnits: -> 1 - verticalGridUnits: -> - 1 splitUp: (view) -> @split(view, 'column', 'before') From 68b05a5d8df3f79654d3533a455332624fa2c4f7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 16 Feb 2013 16:03:51 -0700 Subject: [PATCH 032/160] Allow for panes to exist without a rootView (for testing purposes) --- src/app/pane.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index b44e317a5..df096af7b 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -42,7 +42,7 @@ class Pane extends View pane = new Pane(view) this[side](pane) - rootView.adjustPaneDimensions() + rootView?.adjustPaneDimensions() view.focus?() pane @@ -54,7 +54,7 @@ class Pane extends View if parentAxis.children().length == 1 sibling = parentAxis.children().detach() parentAxis.replaceWith(sibling) - rootView.adjustPaneDimensions() + rootView?.adjustPaneDimensions() buildPaneAxis: (axis) -> switch axis From 2bdc077d2a783fc92e66083cd7c89dba85dc12d7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 10:45:02 -0700 Subject: [PATCH 033/160] Construct Pane w/ multiple items. Show first item on construction. --- spec/app/pane-spec.coffee | 17 +++++++++++++++++ src/app/pane.coffee | 11 ++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 spec/app/pane-spec.coffee diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee new file mode 100644 index 000000000..8693d67fc --- /dev/null +++ b/spec/app/pane-spec.coffee @@ -0,0 +1,17 @@ +Editor = require 'editor' +Pane = require 'pane' +{$$} = require 'space-pen' + +describe "Pane", -> + [view1, view2, editSession1, editSession2, pane] = [] + + beforeEach -> + view1 = $$ -> @div id: 'view-1', 'View 1' + view2 = $$ -> @div id: 'view-1', 'View 1' + editSession1 = project.buildEditSession('sample.js') + editSession2 = project.buildEditSession('sample.txt') + pane = new Pane(view1, editSession1, view2, editSession2) + + describe ".initialize(items...)", -> + it "displays the first item in the pane", -> + expect(pane.itemViews.find(view1)).toExist() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index df096af7b..7ed290cd7 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -6,11 +6,20 @@ 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)) + initialize: (@items...) -> + @viewsByItem = new WeakMap + @showItem(@items[0]) + + showItem: (item) -> + @itemViews.children().hide() + @itemViews.append(item) unless @itemViews.children(item).length + item.show() + serialize: -> deserializer: "Pane" wrappedView: @wrappedView?.serialize() From 372393d9ca5fdca1e0d523024c99916f08f0380e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 11:15:33 -0700 Subject: [PATCH 034/160] Allow panes to have model objects as items in addition to views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem I've been struggling with is that we need to potentially assign tabs both to EditSessions and also to other views added by extensions, like a markdown preview view. EditSessions are however not actually views… instead they plug into editors. The solution is to have the pane ask a model object what view should be used to render it. When asked to show a non-view item, the pane constructs and appends a view for that item or recycles an appropriate view that it has already appended. --- spec/app/pane-spec.coffee | 31 +++++++++++++++++++++++++++++-- src/app/edit-session.coffee | 3 +++ src/app/editor.coffee | 10 +++++++++- src/app/pane.coffee | 21 ++++++++++++++++++--- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 8693d67fc..7286be09c 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -7,11 +7,38 @@ describe "Pane", -> beforeEach -> view1 = $$ -> @div id: 'view-1', 'View 1' - view2 = $$ -> @div id: 'view-1', 'View 1' + view2 = $$ -> @div id: 'view-2', 'View 2' editSession1 = project.buildEditSession('sample.js') editSession2 = project.buildEditSession('sample.txt') pane = new Pane(view1, editSession1, view2, editSession2) describe ".initialize(items...)", -> it "displays the first item in the pane", -> - expect(pane.itemViews.find(view1)).toExist() + expect(pane.itemViews.find('#view-1')).toExist() + + describe ".showItem(item)", -> + it "hides all item views except the one being shown", -> + pane.showItem(view2) + expect(view1.css('display')).toBe 'none' + expect(view2.css('display')).toBe '' + + 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.itemViews.find('.editor').view() + 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.itemViews.find('.editor').view() + 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 show it", -> + expect(pane.itemViews.find('#view-2')).not.toExist() + pane.showItem(view2) + expect(pane.itemViews.find('#view-2')).toExist() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 4e06fde11..eaedcfcdd 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -52,6 +52,9 @@ class EditSession @subscribe @displayBuffer, "changed", (e) => @trigger 'screen-lines-changed', e + getViewClass: -> + require 'editor' + destroy: -> throw new Error("Edit session already destroyed") if @destroyed @destroyed = true diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 5b5bc6502..03f7766ed 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -66,7 +66,12 @@ class Editor extends View editor.isFocused = state.isFocused editor - initialize: ({editSession, @mini, deserializing} = {}) -> + initialize: (editSessionOrOptions) -> + if editSessionOrOptions instanceof EditSession + editSession = editSessionOrOptions + else + {editSession, @mini, deserializing} = (options ? {}) + requireStylesheet 'editor.css' @id = Editor.nextEditorId++ @@ -485,6 +490,9 @@ class Editor extends View index = @pushEditSession(editSession) if index == -1 @setActiveEditSessionIndex(index) + setModel: (editSession) -> + @edit(editSession) + pushEditSession: (editSession) -> index = @editSessions.length @editSessions.push(editSession) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 7ed290cd7..a6e983936 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -1,4 +1,5 @@ {View} = require 'space-pen' +$ = require 'jquery' PaneRow = require 'pane-row' PaneColumn = require 'pane-column' @@ -12,13 +13,27 @@ class Pane extends View new Pane(deserialize(wrappedView)) initialize: (@items...) -> - @viewsByItem = new WeakMap + @viewsByClassName = {} @showItem(@items[0]) showItem: (item) -> @itemViews.children().hide() - @itemViews.append(item) unless @itemViews.children(item).length - item.show() + view = @viewForItem(item) + unless view.parent().is(@itemViews) + @itemViews.append(view) + view.show() + + viewForItem: (item) -> + if item instanceof $ + item + else + viewClass = item.getViewClass() + if view = @viewsByClassName[viewClass.name] + view.setModel(item) + view + else + @viewsByClassName[viewClass.name] = new viewClass(item) + serialize: -> deserializer: "Pane" From ef0c62f532420ef3b4c8dbaaf5744222bfe24ede Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 11:44:51 -0700 Subject: [PATCH 035/160] Add show next / previous item. --- spec/app/pane-spec.coffee | 16 +++++++++++++++- src/app/pane.coffee | 28 +++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 7286be09c..9edd5216a 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -17,10 +17,12 @@ describe "Pane", -> expect(pane.itemViews.find('#view-1')).toExist() describe ".showItem(item)", -> - it "hides all item views except the one being shown", -> + it "hides all item views except the one being shown and sets the currentItem", -> + expect(pane.currentItem).toBe view1 pane.showItem(view2) expect(view1.css('display')).toBe 'none' expect(view2.css('display')).toBe '' + expect(pane.currentItem).toBe view2 describe "when showing a model item", -> describe "when no view has yet been appended for that item", -> @@ -42,3 +44,15 @@ describe "Pane", -> expect(pane.itemViews.find('#view-2')).not.toExist() pane.showItem(view2) expect(pane.itemViews.find('#view-2')).toExist() + + describe "pane:show-next-item and pane:show-preview-item", -> + it "advances forward/backward through the pane's items, looping around at either end", -> + expect(pane.currentItem).toBe view1 + pane.trigger 'pane:show-previous-item' + expect(pane.currentItem).toBe editSession2 + pane.trigger 'pane:show-previous-item' + expect(pane.currentItem).toBe view2 + pane.trigger 'pane:show-next-item' + expect(pane.currentItem).toBe editSession2 + pane.trigger 'pane:show-next-item' + expect(pane.currentItem).toBe view1 diff --git a/src/app/pane.coffee b/src/app/pane.coffee index a6e983936..b5b6cccaa 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -12,15 +12,42 @@ class Pane extends View @deserialize: ({wrappedView}) -> new Pane(deserialize(wrappedView)) + currentItem: null + items: null + initialize: (@items...) -> @viewsByClassName = {} @showItem(@items[0]) + @command 'pane:show-next-item', @showNextItem + @command 'pane:show-previous-item', @showPreviousItem + + showNextItem: => + index = @getCurrentItemIndex() + if index < @items.length - 1 + @showItemAtIndex(index + 1) + else + @showItemAtIndex(0) + + showPreviousItem: => + index = @getCurrentItemIndex() + if index > 0 + @showItemAtIndex(index - 1) + else + @showItemAtIndex(@items.length - 1) + + getCurrentItemIndex: -> + @items.indexOf(@currentItem) + + showItemAtIndex: (index) -> + @showItem(@items[index]) + showItem: (item) -> @itemViews.children().hide() view = @viewForItem(item) unless view.parent().is(@itemViews) @itemViews.append(view) + @currentItem = item view.show() viewForItem: (item) -> @@ -34,7 +61,6 @@ class Pane extends View else @viewsByClassName[viewClass.name] = new viewClass(item) - serialize: -> deserializer: "Pane" wrappedView: @wrappedView?.serialize() From 41f18ee6a2754009057c12372ea775cdc5d3b801 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 15:23:23 -0700 Subject: [PATCH 036/160] Add `Pane.removeItem` --- spec/app/pane-spec.coffee | 25 +++++++++++++++++++++++++ src/app/pane.coffee | 23 +++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 9edd5216a..6d090371b 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -45,6 +45,31 @@ describe "Pane", -> pane.showItem(view2) expect(pane.itemViews.find('#view-2')).toExist() + 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.currentItem).toBe editSession1 + + pane.showItem(editSession2) + pane.removeItem(editSession2) + expect(pane.getItems()).toEqual [editSession1, view2] + expect(pane.currentItem).toBe editSession1 + + 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 "pane:show-next-item and pane:show-preview-item", -> it "advances forward/backward through the pane's items, looping around at either end", -> expect(pane.currentItem).toBe view1 diff --git a/src/app/pane.coffee b/src/app/pane.coffee index b5b6cccaa..f94779bd5 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -1,5 +1,6 @@ {View} = require 'space-pen' $ = require 'jquery' +_ = require 'underscore' PaneRow = require 'pane-row' PaneColumn = require 'pane-column' @@ -22,6 +23,9 @@ class Pane extends View @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem + getItems: -> + new Array(@items...) + showNextItem: => index = @getCurrentItemIndex() if index < @items.length - 1 @@ -50,6 +54,21 @@ class Pane extends View @currentItem = item view.show() + removeItem: (item) -> + @showNextItem() if item is @currentItem and @items.length > 1 + _.remove(@items, item) + @cleanupItemView(item) + + cleanupItemView: (item) -> + if item instanceof $ + item.remove() + else + viewClass = item.getViewClass() + otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass + unless otherItemsForView.length + @viewsByClassName[viewClass.name].remove() + delete @viewsByClassName[viewClass.name] + viewForItem: (item) -> if item instanceof $ item @@ -57,9 +76,9 @@ class Pane extends View viewClass = item.getViewClass() if view = @viewsByClassName[viewClass.name] view.setModel(item) - view else - @viewsByClassName[viewClass.name] = new viewClass(item) + view = @viewsByClassName[viewClass.name] = new viewClass(item) + view serialize: -> deserializer: "Pane" From d89a7eb52241f56bad1886d6581feaba9c7b57b9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 15:38:43 -0700 Subject: [PATCH 037/160] When showing an item on a pane, add it to the items list if needed --- spec/app/pane-spec.coffee | 10 ++++++++++ src/app/pane.coffee | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 6d090371b..3921542ef 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -24,6 +24,16 @@ describe "Pane", -> expect(view2.css('display')).toBe '' expect(pane.currentItem).toBe view2 + describe "when the given item isn't yet in the items list on the pane", -> + it "adds it to the items list after the current item", -> + view3 = $$ -> @div id: 'view-3', "View 3" + pane.showItem(editSession1) + expect(pane.getCurrentItemIndex()).toBe 1 + pane.showItem(view3) + expect(pane.getItems()).toEqual [view1, editSession1, view3, view2, editSession2] + expect(pane.currentItem).toBe view3 + expect(pane.getCurrentItemIndex()).toBe 2 + 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", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index f94779bd5..2d121f7ff 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -47,6 +47,7 @@ class Pane extends View @showItem(@items[index]) showItem: (item) -> + @addItem(item) @itemViews.children().hide() view = @viewForItem(item) unless view.parent().is(@itemViews) @@ -54,6 +55,11 @@ class Pane extends View @currentItem = item view.show() + addItem: (item) -> + return if _.include(@items, item) + @items.splice(@getCurrentItemIndex() + 1, 0, item) + item + removeItem: (item) -> @showNextItem() if item is @currentItem and @items.length > 1 _.remove(@items, item) From 77bf3e4d7445f93f75211379cf2768a270aed93a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 16:12:06 -0700 Subject: [PATCH 038/160] Get root view pane specs passing --- spec/app/root-view-spec.coffee | 369 +++++++++++++++++---------------- src/app/pane.coffee | 6 +- src/app/root-view.coffee | 44 ++-- 3 files changed, 203 insertions(+), 216 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 62c9dfaea..43bd19a9f 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -141,199 +141,18 @@ describe "RootView", -> it "surrenders focus to the body", -> expect(document.activeElement).toBe $('body')[0] - describe "panes", -> + fdescribe "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 + view1 = pane1.find('.editor').view() view2 = new DummyView(2) view3 = new DummyView(3) pane2 = pane1.splitDown(view2) @@ -352,6 +171,190 @@ describe "RootView", -> rootView.focusNextPane() expect(view1.focus).toHaveBeenCalled() + describe "pane layout", -> + beforeEach -> + rootView.attachToDom() + rootView.width(800) + rootView.height(600) + 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).view() + expect(rightPane[0]).toBe pane2[0] + expect(leftPane.attr('id')).toBe 'pane-1' + expect(rightPane.currentItem).toBe newPaneContent + + 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).view() + expect(leftPane[0]).toBe pane2[0] + expect(rightPane.attr('id')).toBe 'pane-1' + expect(leftPane.currentItem).toBe + + 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).view() + expect(topPane[0]).toBe pane2[0] + expect(bottomPane.attr('id')).toBe 'pane-1' + expect(topPane.currentItem).toBe newPaneContent + + 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).view() + expect(bottomPane[0]).toBe pane2[0] + expect(topPane.attr('id')).toBe 'pane-1' + expect(bottomPane.currentItem).toBe newPaneContent + + 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.showItem($("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 "keymap wiring", -> commandHandler = null beforeEach -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 2d121f7ff..dd9a4ac10 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -22,6 +22,7 @@ class Pane extends View @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem + @on 'focus', => @viewForCurrentItem().focus() getItems: -> new Array(@items...) @@ -86,6 +87,9 @@ class Pane extends View view = @viewsByClassName[viewClass.name] = new viewClass(item) view + viewForCurrentItem: -> + @viewForItem(@currentItem) + serialize: -> deserializer: "Pane" wrappedView: @wrappedView?.serialize() @@ -118,7 +122,7 @@ class Pane extends View pane = new Pane(view) this[side](pane) rootView?.adjustPaneDimensions() - view.focus?() + pane.focus() pane remove: (selector, keepData) -> diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index d01ac9c03..5449f4925 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -91,39 +91,16 @@ class RootView extends View @remove() open: (path, options = {}) -> - changeFocus = options.changeFocus ? true - allowActiveEditorChange = options.allowActiveEditorChange ? false - - unless editSession = @openInExistingEditor(path, allowActiveEditorChange, changeFocus) - editSession = project.buildEditSession(path) - editor = new Editor({editSession}) - pane = new Pane(editor) - @panes.append(pane) - if changeFocus - editor.focus() + if activePane = @getActivePane() + if existingItem = activePane.itemForPath(path) + activePane.showItem(existingItem) else - @makeEditorActive(editor, changeFocus) + activePane.showItem(project.buildEditSession(path)) + else + activePane = new Pane(project.buildEditSession(path)) + @panes.append(activePane) - 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.buildEditSession(path) - activeEditor.edit(editSession) - editSession + activePane.focus() if options.changeFocus editorFocused: (editor) -> @makeEditorActive(editor) if @panes.containsElement(editor) @@ -177,6 +154,9 @@ class RootView extends View getOpenBufferPaths: -> _.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths())) + getActivePane: -> + @panes.find('.pane.active').view() ? @panes.find('.pane:first').view() + getActiveEditor: -> if (editor = @panes.find('.editor.active')).length editor.view() @@ -190,7 +170,7 @@ class RootView extends View panes = @panes.find('.pane') currentIndex = panes.toArray().indexOf(@getFocusedPane()[0]) nextIndex = (currentIndex + 1) % panes.length - panes.eq(nextIndex).view().wrappedView.focus() + panes.eq(nextIndex).view().focus() getFocusedPane: -> @panes.find('.pane:has(:focus)') From 62729c42eeb81e021a4b3145754d75366477d2cc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 16:22:09 -0700 Subject: [PATCH 039/160] Panes destroy their items when they are removed As a consequence of these changes, editors will no longer need to listen for destruction of their edit sessions. An editor will eventually only ever be displaying a single edit session, and the editor will destroy that edit session when it is removed. Panes will be responsible for supporting multiple edit sessions, and they will automatically remove the editor when they have no more edit session items. --- spec/app/pane-spec.coffee | 10 ++++++++++ src/app/editor.coffee | 2 +- src/app/pane.coffee | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 3921542ef..e4a551906 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -80,6 +80,10 @@ describe "Pane", -> pane.removeItem(editSession1) expect(pane.itemViews.find('.editor')).not.toExist() + it "calls destroy on the model", -> + pane.removeItem(editSession2) + expect(editSession2.destroyed).toBeTruthy() + describe "pane:show-next-item and pane:show-preview-item", -> it "advances forward/backward through the pane's items, looping around at either end", -> expect(pane.currentItem).toBe view1 @@ -91,3 +95,9 @@ describe "Pane", -> expect(pane.currentItem).toBe editSession2 pane.trigger 'pane:show-next-item' expect(pane.currentItem).toBe view1 + + describe ".remove()", -> + it "destroys all the pane's items", -> + pane.remove() + expect(editSession1.destroyed).toBeTruthy() + expect(editSession2.destroyed).toBeTruthy() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 03f7766ed..dc4bddd85 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -498,7 +498,7 @@ class Editor extends View @editSessions.push(editSession) @closedEditSessions = @closedEditSessions.filter ({path})-> path isnt editSession.getPath() - editSession.on 'destroyed', => @editSessionDestroyed(editSession) +# editSession.on 'destroyed', => @editSessionDestroyed(editSession) @trigger 'editor:edit-session-added', [editSession, index] index diff --git a/src/app/pane.coffee b/src/app/pane.coffee index dd9a4ac10..774654b76 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -64,6 +64,7 @@ class Pane extends View removeItem: (item) -> @showNextItem() if item is @currentItem and @items.length > 1 _.remove(@items, item) + item.destroy?() @cleanupItemView(item) cleanupItemView: (item) -> @@ -135,6 +136,9 @@ class Pane extends View parentAxis.replaceWith(sibling) rootView?.adjustPaneDimensions() + afterRemove: -> + item.destroy?() for item in @getItems() + buildPaneAxis: (axis) -> switch axis when 'row' then new PaneRow From c6729e9df1709229a92c4cbad54b5485db1111ad Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 16:22:44 -0700 Subject: [PATCH 040/160] Ignore redundant destructions of EditSessions --- src/app/edit-session.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index eaedcfcdd..e0c771135 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -56,7 +56,7 @@ class EditSession require 'editor' destroy: -> - throw new Error("Edit session already destroyed") if @destroyed + return if @destroyed @destroyed = true @unsubscribe() @buffer.release() From 829bfa0a102fa96389c751e2ba83ba0047791d5b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 16:37:13 -0700 Subject: [PATCH 041/160] Add `Pane.itemForPath` --- spec/app/pane-spec.coffee | 5 +++++ src/app/pane.coffee | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index e4a551906..e928bdd45 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -101,3 +101,8 @@ describe "Pane", -> pane.remove() expect(editSession1.destroyed).toBeTruthy() expect(editSession2.destroyed).toBeTruthy() + + describe ".itemForPath(path)", -> + it "returns the item for which a call to .getPath() returns the given path", -> + expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 + expect(pane.itemForPath(editSession2.getPath())).toBe editSession2 \ No newline at end of file diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 774654b76..006fd2d2a 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -67,6 +67,9 @@ class Pane extends View item.destroy?() @cleanupItemView(item) + itemForPath: (path) -> + _.detect @items, (item) -> item.getPath?() is path + cleanupItemView: (item) -> if item instanceof $ item.remove() @@ -101,7 +104,6 @@ class Pane extends View verticalGridUnits: -> 1 - splitUp: (view) -> @split(view, 'column', 'before') From bee1efed5c8cc7fae2402c60c4309cea1ce3d5f8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 18 Feb 2013 17:28:29 -0700 Subject: [PATCH 042/160] Make `RootView.open` work with new Pane behavior Still a bit of a WIP. Panes don't yet take the "active" class correctly when focused. --- spec/app/root-view-spec.coffee | 105 +++++++++------------------------ src/app/root-view.coffee | 11 ++-- 2 files changed, 36 insertions(+), 80 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 43bd19a9f..c13a8aa47 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -474,101 +474,54 @@ describe "RootView", -> rootView.trigger 'window:decrease-font-size' expect(editor.getFontSize()).toBe 1 - describe ".open(path, options)", -> - describe "when there is no active editor", -> + fdescribe ".open(path, options)", -> + describe "when there is no active pane", -> beforeEach -> - rootView.getActiveEditor().destroyActiveEditSession() - expect(rootView.getActiveEditor()).toBeUndefined() + 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", -> editSession = rootView.open() - expect(rootView.getActiveEditor()).toBeDefined() - expect(rootView.getActiveEditor().getPath()).toBeUndefined() - expect(editSession).toBe rootView.getActiveEditor().activeEditSession + expect(rootView.getActivePane().currentItem).toBe editSession + expect(editSession.getPath()).toBeUndefined() describe "when called with a path", -> it "opens a buffer with the given path in a new editor", -> 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().currentItem).toBe editSession + expect(editSession.getPath()).toBe require.resolve('fixtures/dir/b') - describe "when there is an active editor", -> + describe "when there is an active pane", -> + [activePane, initialItemCount] = [] beforeEach -> - expect(rootView.getActiveEditor()).toBeDefined() + activePane = rootView.getActivePane() + 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 in the active pane", -> editSession = rootView.open() - expect(rootView.getActiveEditor().getPath()).toBeUndefined() - expect(editSession).toBe rootView.getActiveEditor().activeEditSession + expect(activePane.getItems().length).toBe initialItemCount + 1 + expect(activePane.currentItem).toBe editSession + expect(editSession.getPath()).toBeUndefined() 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.currentItem - describe "when allowActiveEditorChange is false (the default)", -> - activeEditor = null - beforeEach -> - activeEditor = rootView.getActiveEditor() + editSession = rootView.open('b') + expect(activePane.currentItem).toBe editSession - 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('a') + expect(editSession).not.toBe previousEditSession + expect(activePane.currentItem).toBe editSession - editSession = rootView.open('b') - expect(activeEditor.activeEditSession).not.toBe previousEditSession - expect(editSession).toBe rootView.getActiveEditor().activeEditSession - - editSession = rootView.open('a') - expect(activeEditor.activeEditSession).toBe previousEditSession - expect(editSession).toBe previousEditSession - - 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 "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.currentItem).toBe editSession describe ".saveAll()", -> it "saves all open editors", -> diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 5449f4925..a1914bdfe 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -92,15 +92,18 @@ class RootView extends View open: (path, options = {}) -> if activePane = @getActivePane() - if existingItem = activePane.itemForPath(path) - activePane.showItem(existingItem) + if editSession = activePane.itemForPath(path) + activePane.showItem(editSession) else - activePane.showItem(project.buildEditSession(path)) + editSession = project.buildEditSession(path) + activePane.showItem(editSession) else - activePane = new Pane(project.buildEditSession(path)) + editSession = project.buildEditSession(path) + activePane = new Pane(editSession) @panes.append(activePane) activePane.focus() if options.changeFocus + editSession editorFocused: (editor) -> @makeEditorActive(editor) if @panes.containsElement(editor) From 568fcf441e9a3023a97efbe0c632eb695760e2ec Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 16:01:08 -0700 Subject: [PATCH 043/160] Pane serializes its serializable items --- spec/app/pane-spec.coffee | 7 ++++++- src/app/pane.coffee | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index e928bdd45..2ada044da 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -105,4 +105,9 @@ describe "Pane", -> describe ".itemForPath(path)", -> it "returns the item for which a call to .getPath() returns the given path", -> expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 - expect(pane.itemForPath(editSession2.getPath())).toBe editSession2 \ No newline at end of file + expect(pane.itemForPath(editSession2.getPath())).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] diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 006fd2d2a..64c186093 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -10,8 +10,8 @@ class Pane extends View @div class: 'pane', => @div class: 'item-views', outlet: 'itemViews' - @deserialize: ({wrappedView}) -> - new Pane(deserialize(wrappedView)) + @deserialize: ({items}) -> + new Pane(items.map((item) -> deserialize(item))...) currentItem: null items: null @@ -96,7 +96,7 @@ class Pane extends View serialize: -> deserializer: "Pane" - wrappedView: @wrappedView?.serialize() + items: _.compact(@getItems().map (item) -> item.serialize?()) adjustDimensions: -> # do nothing From 281a28bb0e451ad73d35da1b6aa584dd8595ba2d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 16:42:16 -0700 Subject: [PATCH 044/160] Add spec for pane focusing the its current item view when it's focused --- spec/app/pane-spec.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 2ada044da..d856b14df 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -102,6 +102,13 @@ describe "Pane", -> expect(editSession1.destroyed).toBeTruthy() expect(editSession2.destroyed).toBeTruthy() + describe ".focus()", -> + it "focuses the current item", -> + focusHandler = jasmine.createSpy("focusHandler") + pane.currentItem.on 'focus', focusHandler + pane.focus() + expect(focusHandler).toHaveBeenCalled() + describe ".itemForPath(path)", -> it "returns the item for which a call to .getPath() returns the given path", -> expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 From 45eec6a8ff0bed5ab49b7ff67f71dd9b9a5c9795 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 16:43:29 -0700 Subject: [PATCH 045/160] Get more specs passing. Failing specs due to missing features. --- spec/app/editor-spec.coffee | 213 +-------------------------------- spec/app/pane-spec.coffee | 20 ++++ spec/app/root-view-spec.coffee | 10 +- src/app/edit-session.coffee | 2 +- src/app/editor.coffee | 14 +-- src/app/root-view.coffee | 3 +- static/atom.css | 6 + 7 files changed, 47 insertions(+), 221 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index c8b0572ff..1aa43f983 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -149,73 +149,6 @@ describe "Editor", -> 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.buildEditSession()) - editSession = editor.activeEditSession - expect(editor.closedEditSessions.length).toBe 0 - editor.trigger "core:close" - expect(editor.closedEditSessions.length).toBe 0 - editor.edit(project.buildEditSession(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.buildEditSession()) - 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.buildEditSession()) - 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() - describe ".edit(editSession)", -> otherEditSession = null @@ -469,10 +402,10 @@ describe "Editor", -> 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() + editor.splitUp() + editor.splitDown() + editor.splitLeft() + editor.splitRight() describe "editor:attached event", -> it 'only triggers an editor:attached event when it is first added to the DOM', -> @@ -545,10 +478,8 @@ describe "Editor", -> 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' @@ -602,6 +533,7 @@ describe "Editor", -> expect(editor.lineHeight).toBeGreaterThan lineHeightBefore expect(editor.charWidth).toBeGreaterThan charWidthBefore expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth } + expect(editor.activeEditSession.buffer).toBe buffer expect(editor.renderedLines.outerHeight()).toBe buffer.getLineCount() * editor.lineHeight expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight @@ -1418,6 +1350,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 -> @@ -2371,42 +2304,6 @@ describe "Editor", -> 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] = [] @@ -2769,104 +2666,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/pane-spec.coffee b/spec/app/pane-spec.coffee index d856b14df..17073177e 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -118,3 +118,23 @@ describe "Pane", -> it "can serialize and deserialize the pane and all its serializable items", -> newPane = deserialize(pane.serialize()) expect(newPane.getItems()).toEqual [editSession1, editSession2] + +# This relates to confirming the closing of a tab +# +# 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() diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index c13a8aa47..5be73e8e9 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -10,7 +10,6 @@ describe "RootView", -> pathToOpen = null beforeEach -> - project.destroy() project.setPath(project.resolve('dir')) pathToOpen = project.resolve('a') window.rootView = new RootView @@ -18,7 +17,7 @@ describe "RootView", -> rootView.open(pathToOpen) rootView.focus() - describe "@deserialize()", -> + xdescribe "@deserialize()", -> viewState = null describe "when the serialized RootView has an unsaved buffer", -> @@ -27,10 +26,11 @@ describe "RootView", -> editor1 = rootView.getActiveEditor() buffer = editor1.getBuffer() editor1.splitRight() + viewState = rootView.serialize() rootView.deactivate() - window.rootView = RootView.deserialize(viewState) + rootView.focus() expect(rootView.getEditors().length).toBe 2 expect(rootView.getActiveEditor().getText()).toBe buffer.getText() @@ -141,7 +141,7 @@ describe "RootView", -> it "surrenders focus to the body", -> expect(document.activeElement).toBe $('body')[0] - fdescribe "panes", -> + describe "panes", -> [pane1, newPaneContent] = [] beforeEach -> @@ -474,7 +474,7 @@ describe "RootView", -> rootView.trigger 'window:decrease-font-size' expect(editor.getFontSize()).toBe 1 - fdescribe ".open(path, options)", -> + describe ".open(path, options)", -> describe "when there is no active pane", -> beforeEach -> rootView.getActivePane().remove() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index e0c771135..14a3b11e2 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -14,7 +14,7 @@ module.exports = class EditSession registerDeserializer(this) - @deserialize: (state, project) -> + @deserialize: (state) -> if fs.exists(state.buffer) session = project.buildEditSession(state.buffer) else diff --git a/src/app/editor.coffee b/src/app/editor.coffee index dc4bddd85..9187e8d8f 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -70,7 +70,7 @@ class Editor extends View if editSessionOrOptions instanceof EditSession editSession = editSessionOrOptions else - {editSession, @mini, deserializing} = (options ? {}) + {editSession, @mini, deserializing} = (editSessionOrOptions ? {}) requireStylesheet 'editor.css' @@ -793,19 +793,19 @@ class Editor extends View new Editor { editSession: editSession ? @activeEditSession.copy() } splitLeft: (editSession) -> - @pane()?.splitLeft(@newSplitEditor(editSession)).wrappedView + @pane()?.splitLeft(@newSplitEditor(editSession)).currentItem splitRight: (editSession) -> - @pane()?.splitRight(@newSplitEditor(editSession)).wrappedView + @pane()?.splitRight(@newSplitEditor(editSession)).currentItem splitUp: (editSession) -> - @pane()?.splitUp(@newSplitEditor(editSession)).wrappedView + @pane()?.splitUp(@newSplitEditor(editSession)).currentItem splitDown: (editSession) -> - @pane()?.splitDown(@newSplitEditor(editSession)).wrappedView + @pane()?.splitDown(@newSplitEditor(editSession)).currentItem pane: -> - @parent('.pane').view() + @closest('.pane').view() promptToSaveDirtySession: (session, callback) -> path = session.getPath() @@ -821,7 +821,7 @@ class Editor extends 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: -> diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index a1914bdfe..7f8a72858 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -120,6 +120,7 @@ class RootView extends View 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() @@ -144,7 +145,7 @@ class RootView extends View document.title = @title getEditors: -> - @panes.find('.pane > .editor').map(-> $(this).view()).toArray() + @panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray() getModifiedBuffers: -> modifiedBuffers = [] diff --git a/static/atom.css b/static/atom.css index f16aa95be..003390b38 100644 --- a/static/atom.css +++ b/static/atom.css @@ -55,6 +55,12 @@ html, body { box-sizing: border-box; } +#root-view #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"); From 0c2a5f273c48c3efef630773f4e510d39abbe51e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 20:53:34 -0700 Subject: [PATCH 046/160] Enhance pane split methods. Spec them in pane-spec. When a pane is split, it attempts to make a copy of its current item if no items are passed to the split method. When splitting, multiple items can also be passed to the constructor of the new pane. --- spec/app/pane-spec.coffee | 59 ++++++++++++++++++++++++++++++++++++++- src/app/pane.coffee | 24 +++++++++------- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 17073177e..f00bcfd2d 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -3,14 +3,16 @@ Pane = require 'pane' {$$} = require 'space-pen' describe "Pane", -> - [view1, view2, editSession1, editSession2, pane] = [] + [container, view1, view2, editSession1, editSession2, pane] = [] beforeEach -> + container = $$ -> @div id: 'panes' view1 = $$ -> @div id: 'view-1', 'View 1' view2 = $$ -> @div id: 'view-2', '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", -> @@ -109,6 +111,61 @@ describe "Pane", -> pane.focus() expect(focusHandler).toHaveBeenCalled() + describe "split methods", -> + [view3, view4] = [] + beforeEach -> + 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 current item if none are given + pane2 = pane.splitRight() + expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0]] + expect(pane2.items).toEqual [editSession1] + expect(pane2.currentItem).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 current 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.currentItem).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 current 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.currentItem).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 current 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.currentItem).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]] + describe ".itemForPath(path)", -> it "returns the item for which a call to .getPath() returns the given path", -> expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 64c186093..b2c1ca5e1 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -104,30 +104,34 @@ class Pane extends View 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 = [@copyCurrentItem()] unless items.length + pane = new Pane(items...) this[side](pane) rootView?.adjustPaneDimensions() pane.focus() pane + copyCurrentItem: -> + deserialize(@currentItem.serialize()) + remove: (selector, keepData) -> return super if keepData # find parent elements before removing from dom From 19e2cab920ba868f5d377e6879813a08d07c2209 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 20:55:38 -0700 Subject: [PATCH 047/160] :lipstick: --- spec/app/pane-spec.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index f00bcfd2d..bdcc1d246 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -86,7 +86,7 @@ describe "Pane", -> pane.removeItem(editSession2) expect(editSession2.destroyed).toBeTruthy() - describe "pane:show-next-item and pane:show-preview-item", -> + 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.currentItem).toBe view1 pane.trigger 'pane:show-previous-item' @@ -104,8 +104,8 @@ describe "Pane", -> expect(editSession1.destroyed).toBeTruthy() expect(editSession2.destroyed).toBeTruthy() - describe ".focus()", -> - it "focuses the current item", -> + describe "when the pane is focused", -> + it "focuses the current item view", -> focusHandler = jasmine.createSpy("focusHandler") pane.currentItem.on 'focus', focusHandler pane.focus() From 9ecb03e470e41c9439e936a2a4dfa3aca214d2ae Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 20:59:25 -0700 Subject: [PATCH 048/160] Rename PaneGrid to PaneAxis PaneGrid is a superclass of PaneRow and PaneColumn. These are both a type of axis for the pane layout system. --- spec/app/pane-container-spec.coffee | 0 src/app/{pane-grid.coffee => pane-axis.coffee} | 2 +- src/app/pane-column.coffee | 4 ++-- src/app/pane-row.coffee | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 spec/app/pane-container-spec.coffee rename src/app/{pane-grid.coffee => pane-axis.coffee} (95%) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee new file mode 100644 index 000000000..e69de29bb 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-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' From fee835f8995236112f5d64c3f2c86aea4c08d0f0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Feb 2013 22:28:43 -0700 Subject: [PATCH 049/160] Add a PaneContainer subview for RootView PaneContainer is responsible for all pane-related logic. Laying them out, switching focus between them, etc. This should help make RootView simpler and keep pane-layout related tests in their own focused area. --- spec/app/pane-container-spec.coffee | 47 +++++++ spec/app/pane-spec.coffee | 83 ++++++++++++- spec/app/root-view-spec.coffee | 184 ---------------------------- src/app/pane-container.coffee | 41 +++++++ src/app/pane.coffee | 18 +-- src/app/root-view.coffee | 32 ++--- src/app/window.coffee | 3 + static/atom.css | 10 +- 8 files changed, 192 insertions(+), 226 deletions(-) create mode 100644 src/app/pane-container.coffee diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index e69de29bb..8d2bf21e1 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -0,0 +1,47 @@ +PaneContainer = require 'pane-container' +Pane = require 'pane' +{View} = require 'space-pen' +$ = require 'jquery' + +describe "PaneContainer", -> + [TestView, container, pane1, pane2, pane3] = [] + + beforeEach -> + class TestView extends View + registerDeserializer(this) + @deserialize: ({myText}) -> new TestView(myText) + @content: -> @div tabindex: -1 + initialize: (@myText) -> @text(@myText) + serialize: -> deserializer: 'TestView', myText: @myText + + 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.currentItem).toMatchSelector ':focus' + container.focusNextPane() + expect(pane2.currentItem).toMatchSelector ':focus' + container.focusNextPane() + expect(pane3.currentItem).toMatchSelector ':focus' + container.focusNextPane() + expect(pane1.currentItem).toMatchSelector ':focus' + + 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 index bdcc1d246..a856fcc82 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -1,12 +1,13 @@ -Editor = require 'editor' +PaneContainer = require 'pane-container' Pane = require 'pane' {$$} = require 'space-pen' +$ = require 'jquery' describe "Pane", -> [container, view1, view2, editSession1, editSession2, pane] = [] beforeEach -> - container = $$ -> @div id: 'panes' + container = new PaneContainer view1 = $$ -> @div id: 'view-1', 'View 1' view2 = $$ -> @div id: 'view-2', 'View 2' editSession1 = project.buildEditSession('sample.js') @@ -52,7 +53,7 @@ describe "Pane", -> 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 show it", -> + 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() @@ -112,8 +113,9 @@ describe "Pane", -> expect(focusHandler).toHaveBeenCalled() describe "split methods", -> - [view3, view4] = [] + [pane1, view3, view4] = [] beforeEach -> + pane1 = pane pane.showItem(editSession1) view3 = $$ -> @div id: 'view-3', 'View 3' view4 = $$ -> @div id: 'view-4', 'View 4' @@ -121,8 +123,8 @@ describe "Pane", -> 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 current item if none are given - pane2 = pane.splitRight() - expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0]] + pane2 = pane1.splitRight() + expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] expect(pane2.items).toEqual [editSession1] expect(pane2.currentItem).not.toBe editSession1 # it's a copy @@ -166,6 +168,75 @@ describe "Pane", -> 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 ".itemForPath(path)", -> it "returns the item for which a call to .getPath() returns the given path", -> expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 5be73e8e9..8b1d6f691 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -171,190 +171,6 @@ describe "RootView", -> rootView.focusNextPane() expect(view1.focus).toHaveBeenCalled() - describe "pane layout", -> - beforeEach -> - rootView.attachToDom() - rootView.width(800) - rootView.height(600) - 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).view() - expect(rightPane[0]).toBe pane2[0] - expect(leftPane.attr('id')).toBe 'pane-1' - expect(rightPane.currentItem).toBe newPaneContent - - 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).view() - expect(leftPane[0]).toBe pane2[0] - expect(rightPane.attr('id')).toBe 'pane-1' - expect(leftPane.currentItem).toBe - - 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).view() - expect(topPane[0]).toBe pane2[0] - expect(bottomPane.attr('id')).toBe 'pane-1' - expect(topPane.currentItem).toBe newPaneContent - - 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).view() - expect(bottomPane[0]).toBe pane2[0] - expect(topPane.attr('id')).toBe 'pane-1' - expect(bottomPane.currentItem).toBe newPaneContent - - 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.showItem($("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 "keymap wiring", -> commandHandler = null beforeEach -> diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee new file mode 100644 index 000000000..87bc09f98 --- /dev/null +++ b/src/app/pane-container.coffee @@ -0,0 +1,41 @@ +{View} = require 'space-pen' +$ = 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' + + serialize: -> + deserializer: 'PaneContainer' + root: @getRoot()?.serialize() + + focusNextPane: -> + panes = @getPanes() + currentIndex = panes.indexOf(@getFocusedPane()) + nextIndex = (currentIndex + 1) % panes.length + panes[nextIndex].focus() + + getRoot: -> + @children().first().view() + + getPanes: -> + @find('.pane').toArray().map (node)-> $(node).view() + + getFocusedPane: -> + @find('.pane:has(:focus)').view() + + adjustPaneDimensions: -> + if root = @getRoot() + root.css(width: '100%', height: '100%', top: 0, left: 0) + root.adjustDimensions() + + afterAttach: -> + @adjustPaneDimensions() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index b2c1ca5e1..ebad81482 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -125,27 +125,31 @@ class Pane extends View items = [@copyCurrentItem()] unless items.length pane = new Pane(items...) this[side](pane) - rootView?.adjustPaneDimensions() + @getContainer().adjustPaneDimensions() pane.focus() pane + buildPaneAxis: (axis) -> + switch axis + when 'row' then new PaneRow + when 'column' then new PaneColumn + + getContainer: -> + @closest('#panes').view() + copyCurrentItem: -> deserialize(@currentItem.serialize()) remove: (selector, keepData) -> return super if keepData # find parent elements before removing from dom + container = @getContainer() parentAxis = @parent('.row, .column') super if parentAxis.children().length == 1 sibling = parentAxis.children().detach() parentAxis.replaceWith(sibling) - rootView?.adjustPaneDimensions() + container.adjustPaneDimensions() afterRemove: -> item.destroy?() for item in @getItems() - - buildPaneAxis: (axis) -> - switch axis - when 'row' then new PaneRow - when 'column' then new PaneColumn diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 7f8a72858..a557fa866 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -10,6 +10,7 @@ Project = require 'project' Pane = require 'pane' PaneColumn = require 'pane-column' PaneRow = require 'pane-row' +PaneContainer = require 'pane-container' module.exports = class RootView extends View @@ -19,17 +20,16 @@ class RootView extends View 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 + panes = deserialize(panesViewState) if panesViewState?.deserializer is 'PaneContainer' + new RootView({panes}) title: null @@ -67,7 +67,7 @@ class RootView extends View serialize: -> deserializer: 'RootView' - panesViewState: @panes.children().view()?.serialize() + panesViewState: @panes.serialize() packageStates: atom.serializeAtomPackages() handleFocus: (e) -> @@ -170,24 +170,8 @@ class RootView extends View getActiveEditSession: -> @getActiveEditor()?.activeEditSession - focusNextPane: -> - panes = @panes.find('.pane') - currentIndex = panes.toArray().indexOf(@getFocusedPane()[0]) - nextIndex = (currentIndex + 1) % panes.length - panes.eq(nextIndex).view().focus() - - 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() diff --git a/src/app/window.coffee b/src/app/window.coffee index a4c514b15..2ff52830c 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -151,6 +151,9 @@ 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) diff --git a/static/atom.css b/static/atom.css index 003390b38..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,7 +55,7 @@ html, body { box-sizing: border-box; } -#root-view #panes .pane .item-views { +#panes .pane .item-views { -webkit-flex: 1; display: -webkit-flex; -webkit-flex-flow: column; From 7d147dd2ce891aac35156b2d53168c5661e66150 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 10:52:21 -0700 Subject: [PATCH 050/160] Make Pane handle split commands instead of Editor --- spec/app/editor-spec.coffee | 23 ----------------------- src/app/editor.coffee | 15 ++++----------- src/app/keymaps/atom.cson | 6 ++++++ src/app/keymaps/editor.cson | 4 ---- src/app/pane.coffee | 4 ++++ 5 files changed, 14 insertions(+), 38 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 1aa43f983..edcb4d13c 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -384,29 +384,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.buildEditSession("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() - editor.splitDown() - editor.splitLeft() - editor.splitRight() - describe "editor:attached event", -> it 'only triggers an editor:attached event when it is first added to the DOM', -> openHandler = jasmine.createSpy('openHandler') diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 9187e8d8f..1bc4ed8db 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -172,10 +172,6 @@ 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] @@ -789,20 +785,17 @@ class Editor extends View @updateLayerDimensions() @requestDisplayUpdate() - newSplitEditor: (editSession) -> - new Editor { editSession: editSession ? @activeEditSession.copy() } - splitLeft: (editSession) -> - @pane()?.splitLeft(@newSplitEditor(editSession)).currentItem + @pane()?.splitLeft() splitRight: (editSession) -> - @pane()?.splitRight(@newSplitEditor(editSession)).currentItem + @pane()?.splitRight() splitUp: (editSession) -> - @pane()?.splitUp(@newSplitEditor(editSession)).currentItem + @pane()?.splitUp() splitDown: (editSession) -> - @pane()?.splitDown(@newSplitEditor(editSession)).currentItem + @pane()?.splitDown() pane: -> @closest('.pane').view() diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index 680f25c17..d2a16ebff 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -30,6 +30,12 @@ 'ctrl-tab': 'window:focus-next-pane' 'ctrl-meta-f': 'window:toggle-full-screen' +'.pane': + 'ctrl-|': 'pane:split-right' + 'ctrl-w v': 'pane:split-right' + 'ctrl--': 'pane:split-down' + 'ctrl-w s': 'pane:split-down' + '.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..b072a462b 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -15,10 +15,6 @@ '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' diff --git a/src/app/pane.coffee b/src/app/pane.coffee index ebad81482..2cf3f7ef5 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -22,6 +22,10 @@ class Pane extends View @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem + @command 'pane:split-left', => @splitLeft() + @command 'pane:split-right', => @splitRight() + @command 'pane:split-up', => @splitUp() + @command 'pane:split-down', => @splitDown() @on 'focus', => @viewForCurrentItem().focus() getItems: -> From 11a702a2a62549aa5ef0aebd143dad2a2aaf12ae Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 11:12:36 -0700 Subject: [PATCH 051/160] Remove pane when its last item is removed --- spec/app/pane-spec.coffee | 4 ++++ src/app/pane.coffee | 1 + 2 files changed, 5 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index a856fcc82..bb5c0b430 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -69,6 +69,10 @@ describe "Pane", -> expect(pane.getItems()).toEqual [editSession1, view2] expect(pane.currentItem).toBe editSession1 + it "removes the pane when its last item is removed", -> + pane.removeItem(item) for item in pane.getItems() + expect(pane.hasParent()).toBeFalsy() + describe "when the item is a view", -> it "removes the item from the 'item-views' div", -> expect(view1.parent()).toMatchSelector pane.itemViews diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 2cf3f7ef5..3f6a8c2ba 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -70,6 +70,7 @@ class Pane extends View _.remove(@items, item) item.destroy?() @cleanupItemView(item) + @remove() unless @items.length itemForPath: (path) -> _.detect @items, (item) -> item.getPath?() is path From bd8ec81b1ec86da953a6dd729a6606ba5624adbb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 11:13:10 -0700 Subject: [PATCH 052/160] Make Pane close the current item when handling 'core:close' event --- spec/app/pane-spec.coffee | 6 ++++++ src/app/pane.coffee | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index bb5c0b430..6004ced05 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -91,6 +91,12 @@ describe "Pane", -> pane.removeItem(editSession2) expect(editSession2.destroyed).toBeTruthy() + describe "core:close", -> + it "removes the current item", -> + initialItemCount = pane.getItems().length + pane.trigger 'core:close' + expect(pane.getItems().length).toBe initialItemCount - 1 + 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.currentItem).toBe view1 diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 3f6a8c2ba..4b9a20357 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -20,6 +20,7 @@ class Pane extends View @viewsByClassName = {} @showItem(@items[0]) + @command 'core:close', @removeCurrentItem @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem @command 'pane:split-left', => @splitLeft() @@ -65,6 +66,9 @@ class Pane extends View @items.splice(@getCurrentItemIndex() + 1, 0, item) item + removeCurrentItem: => + @removeItem(@currentItem) + removeItem: (item) -> @showNextItem() if item is @currentItem and @items.length > 1 _.remove(@items, item) From 8f980a0f203217de90a646745d0f444e6b3cd8a8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 11:28:55 -0700 Subject: [PATCH 053/160] Replace Editor's next/previous edit session bindings w/ pane bindings --- spec/app/pane-spec.coffee | 1 + src/app/editor.coffee | 3 --- src/app/keymaps/atom.cson | 4 ++++ src/app/keymaps/editor.cson | 4 ---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 6004ced05..3c249e749 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -50,6 +50,7 @@ describe "Pane", -> pane.showItem(editSession2) expect(pane.itemViews.find('.editor').length).toBe 1 editor = pane.itemViews.find('.editor').view() + expect(editor.css('display')).toBe '' expect(editor.activeEditSession).toBe editSession2 describe "when showing a view item", -> diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 1bc4ed8db..fc69b0c37 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -160,7 +160,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 @@ -172,7 +171,6 @@ class Editor extends View 'editor:fold-current-row': @foldCurrentRow 'editor:unfold-current-row': @unfoldCurrentRow 'editor:fold-selection': @foldSelection - '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] @@ -182,7 +180,6 @@ class Editor extends View '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 diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index d2a16ebff..d3427fb45 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -31,6 +31,10 @@ 'ctrl-meta-f': 'window:toggle-full-screen' '.pane': + 'meta-{': 'pane:show-previous-item' + 'meta-}': 'pane:show-next-item' + 'alt-meta-left': 'pane:show-previous-item' + 'alt-meta-right': 'pane:show-next-item' 'ctrl-|': 'pane:split-right' 'ctrl-w v': 'pane:split-right' 'ctrl--': 'pane:split-down' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index b072a462b..e30e84e9b 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -18,10 +18,6 @@ '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' From 2ba63e608f5524d37c68c2db808e732762793a32 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 11:30:48 -0700 Subject: [PATCH 054/160] Don't allow core:close event to bubble out of Pane --- spec/app/pane-spec.coffee | 7 ++++++- src/app/pane.coffee | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 3c249e749..499452b31 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -93,11 +93,16 @@ describe "Pane", -> expect(editSession2.destroyed).toBeTruthy() describe "core:close", -> - it "removes the current item", -> + it "removes the current item and does not bubble the event", -> + containerCloseHandler = jasmine.createSpy("containerCloseHandler") + container.on 'core:close', containerCloseHandler + initialItemCount = pane.getItems().length pane.trigger 'core:close' expect(pane.getItems().length).toBe initialItemCount - 1 + expect(containerCloseHandler).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.currentItem).toBe view1 diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 4b9a20357..14a1158bc 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -68,6 +68,7 @@ class Pane extends View removeCurrentItem: => @removeItem(@currentItem) + false removeItem: (item) -> @showNextItem() if item is @currentItem and @items.length > 1 From 486baa393b74191ed471e423d45a26433078923a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 11:56:47 -0700 Subject: [PATCH 055/160] PaneContainer.getActivePane returns the most recently focused pane --- spec/app/pane-container-spec.coffee | 24 +++++++++++++++++++++++- src/app/pane-container.coffee | 12 ++++++++++++ src/app/pane.coffee | 4 +++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index 8d2bf21e1..bca9ed793 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -1,6 +1,6 @@ PaneContainer = require 'pane-container' Pane = require 'pane' -{View} = require 'space-pen' +{View, $$} = require 'space-pen' $ = require 'jquery' describe "PaneContainer", -> @@ -35,6 +35,28 @@ describe "PaneContainer", -> container.focusNextPane() expect(pane1.currentItem).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 "serialization", -> it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", -> newContainer = deserialize(container.serialize()) diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 87bc09f98..4e91b3b35 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -13,6 +13,11 @@ class PaneContainer extends View @content: -> @div id: 'panes' + initialize: -> + @on 'focusin', (e) => + focusedPane = $(e.target).closest('.pane').view() + @setActivePane(focusedPane) + serialize: -> deserializer: 'PaneContainer' root: @getRoot()?.serialize() @@ -32,6 +37,13 @@ class PaneContainer extends View getFocusedPane: -> @find('.pane:has(:focus)').view() + getActivePane: -> + @find('.pane.active').view() ? @find('.pane:first').view() + + setActivePane: (pane) -> + @find('.pane').removeClass('active') + pane.addClass('active') + adjustPaneDimensions: -> if root = @getRoot() root.css(width: '100%', height: '100%', top: 0, left: 0) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 14a1158bc..acafb25a1 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -27,7 +27,9 @@ class Pane extends View @command 'pane:split-right', => @splitRight() @command 'pane:split-up', => @splitUp() @command 'pane:split-down', => @splitDown() - @on 'focus', => @viewForCurrentItem().focus() + @on 'focus', => + @viewForCurrentItem().focus() + false getItems: -> new Array(@items...) From 4e12882478c78c9367f2489ed7fa3d2d35cadcea Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Feb 2013 11:59:00 -0700 Subject: [PATCH 056/160] Delegate getActivePane to the PaneContainer in RootView --- src/app/root-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index a557fa866..4fa03aefb 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -159,7 +159,7 @@ class RootView extends View _.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths())) getActivePane: -> - @panes.find('.pane.active').view() ? @panes.find('.pane:first').view() + @panes.getActivePane() getActiveEditor: -> if (editor = @panes.find('.editor.active')).length From d310fb366f32bd0324039ea176ede0d0d5eb7464 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 14:38:03 -0700 Subject: [PATCH 057/160] Remove references to RootView from editor spec --- spec/app/editor-spec.coffee | 230 +++++++++--------------------------- 1 file changed, 57 insertions(+), 173 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index edcb4d13c..a5cf751cb 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' @@ -12,6 +11,19 @@ fs = require 'fs' describe "Editor", -> [buffer, editor, cachedLineHeight] = [] + beforeEach -> + editor = new Editor(project.buildEditSession('sample.js')) + buffer = editor.getBuffer() + editor.lineOverdraw = 2 + editor.isFocused = true + editor.enableKeymap() + editor.attachToDom = ({ heightInLines, widthInChars } = {}) -> + heightInLines ?= this.getBuffer().getLineCount() + this.height(getLineHeight() * heightInLines) + this.width(@charWidth * widthInChars) if widthInChars + $('#jasmine-content').append(this) + + getLineHeight = -> return cachedLineHeight if cachedLineHeight? editorForMeasurement = new Editor(editSession: project.buildEditSession('sample.js')) @@ -20,66 +32,11 @@ describe "Editor", -> editorForMeasurement.remove() cachedLineHeight - beforeEach -> - window.rootView = new RootView - rootView.open('sample.js') - editor = rootView.getActiveEditor() - buffer = editor.getBuffer() - - editor.attachToDom = ({ heightInLines } = {}) -> - heightInLines ?= this.getBuffer().getLineCount() - this.height(getLineHeight() * heightInLines) - $('#jasmine-content').append(this) - - editor.lineOverdraw = 2 - editor.enableKeymap() - editor.isFocused = true - describe "construction", -> it "throws an error if no editor session is given unless deserializing", -> 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.buildEditSession('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.buildEditSession('/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", -> expect(editor.lineHeight).toBeNull() @@ -189,14 +146,6 @@ describe "Editor", -> editor.insertText("def\n") expect(editor.lineElementForScreenRow(0).text()).toBe 'def' - it "removes the opened session from the closed sessions array", -> - editor.edit(project.buildEditSession('sample.txt')) - expect(editor.closedEditSessions.length).toBe 0 - editor.trigger "core:close" - expect(editor.closedEditSessions.length).toBe 1 - editor.edit(project.buildEditSession('sample.txt')) - expect(editor.closedEditSessions.length).toBe 0 - describe "switching edit sessions", -> [session0, session1, session2] = [] @@ -293,9 +242,7 @@ describe "Editor", -> project.setPath('/tmp') tempFilePath = '/tmp/atom-temp.txt' fs.write(tempFilePath, "") - rootView.open(tempFilePath) - editor = rootView.getActiveEditor() - expect(editor.getPath()).toBe tempFilePath + editor.edit(project.buildEditSession(tempFilePath)) afterEach -> expect(fs.remove(tempFilePath)) @@ -313,8 +260,6 @@ describe "Editor", -> selectedFilePath = null beforeEach -> editor.edit(project.buildEditSession()) - - expect(editor.getPath()).toBeUndefined() editor.getBuffer().setText 'Save me to a new path' spyOn(atom, 'showSaveDialog').andCallFake (callback) -> callback(selectedFilePath) @@ -451,27 +396,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", -> editor.attachToDom(12) - lineHeightBefore = editor.lineHeight charWidthBefore = editor.charWidth - config.set("editor.fontFamily", "PCMyungjo") - 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 -> @@ -483,24 +422,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] @@ -510,14 +434,17 @@ describe "Editor", -> expect(editor.lineHeight).toBeGreaterThan lineHeightBefore expect(editor.charWidth).toBeGreaterThan charWidthBefore expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth } - expect(editor.activeEditSession.buffer).toBe buffer 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') @@ -527,7 +454,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()) @@ -540,22 +467,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 -> @@ -1627,7 +1557,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() @@ -1658,21 +1588,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" @@ -2169,30 +2089,13 @@ 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) @@ -2253,7 +2156,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] @@ -2270,32 +2173,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 ".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' @@ -2309,12 +2199,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() @@ -2324,8 +2208,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() From ad62f896bc1833d51a90a69dbe796adc501e260f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 14:47:26 -0700 Subject: [PATCH 058/160] Make Pane maintain a currentView pointer based on its current item --- spec/app/pane-spec.coffee | 6 ++++-- src/app/pane.coffee | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 499452b31..f0f0d99aa 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -41,7 +41,8 @@ describe "Pane", -> 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.itemViews.find('.editor').view() + editor = pane.currentView + expect(editor.css('display')).toBe '' expect(editor.activeEditSession).toBe editSession1 describe "when a valid view has already been appended for another item", -> @@ -49,7 +50,7 @@ describe "Pane", -> pane.showItem(editSession1) pane.showItem(editSession2) expect(pane.itemViews.find('.editor').length).toBe 1 - editor = pane.itemViews.find('.editor').view() + editor = pane.currentView expect(editor.css('display')).toBe '' expect(editor.activeEditSession).toBe editSession2 @@ -58,6 +59,7 @@ describe "Pane", -> expect(pane.itemViews.find('#view-2')).not.toExist() pane.showItem(view2) expect(pane.itemViews.find('#view-2')).toExist() + expect(pane.currentView).toBe view2 describe ".removeItem(item)", -> it "removes the item from the items list and shows the next item if it was showing", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index acafb25a1..69ce2af62 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -58,10 +58,10 @@ class Pane extends View @addItem(item) @itemViews.children().hide() view = @viewForItem(item) - unless view.parent().is(@itemViews) - @itemViews.append(view) + @itemViews.append(view) unless view.parent().is(@itemViews) @currentItem = item - view.show() + @currentView = view + @currentView.show() addItem: (item) -> return if _.include(@items, item) From 4a6f05ae4ed5ade66549fb0093372a4a0ec42714 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 16:22:19 -0700 Subject: [PATCH 059/160] Trigger 'pane:active-item-changed' on Pane This event is triggered when the item changes on the active pane, or when a different pane becomes active. Also: Pane now sets itself as the active pane, rather than letting PaneContainer handle the focusin event. --- spec/app/pane-spec.coffee | 33 +++++++++++++++++++++++++++++++++ src/app/pane-container.coffee | 8 -------- src/app/pane.coffee | 20 +++++++++++++++++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index f0f0d99aa..331e414e4 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -27,6 +27,27 @@ describe "Pane", -> expect(view2.css('display')).toBe '' expect(pane.currentItem).toBe view2 + it "triggers 'pane:active-item-changed' if the pane is active and the item isn't already the currentItem", -> + pane.makeActive() + itemChangedHandler = jasmine.createSpy("itemChangedHandler") + container.on 'pane:active-item-changed', itemChangedHandler + + expect(pane.currentItem).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() + + pane.makeInactive() + pane.showItem(editSession2) + expect(itemChangedHandler).not.toHaveBeenCalled() + describe "when the given item isn't yet in the items list on the pane", -> it "adds it to the items list after the current item", -> view3 = $$ -> @div id: 'view-3', "View 3" @@ -130,6 +151,18 @@ describe "Pane", -> pane.focus() expect(focusHandler).toHaveBeenCalled() + it "triggers 'pane:active-item-changed' if it was not previously active", -> + itemChangedHandler = jasmine.createSpy("itemChangedHandler") + container.on 'pane:active-item-changed', itemChangedHandler + + expect(pane.isActive()).toBeFalsy() + pane.focusin() + expect(pane.isActive()).toBeTruthy() + pane.focusin() + + expect(itemChangedHandler.callCount).toBe 1 + expect(itemChangedHandler.argsForCall[0][1]).toBe pane.currentItem + describe "split methods", -> [pane1, view3, view4] = [] beforeEach -> diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 4e91b3b35..ca4c2f254 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -13,11 +13,6 @@ class PaneContainer extends View @content: -> @div id: 'panes' - initialize: -> - @on 'focusin', (e) => - focusedPane = $(e.target).closest('.pane').view() - @setActivePane(focusedPane) - serialize: -> deserializer: 'PaneContainer' root: @getRoot()?.serialize() @@ -40,9 +35,6 @@ class PaneContainer extends View getActivePane: -> @find('.pane.active').view() ? @find('.pane:first').view() - setActivePane: (pane) -> - @find('.pane').removeClass('active') - pane.addClass('active') adjustPaneDimensions: -> if root = @getRoot() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 69ce2af62..cb5e3636d 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -27,9 +27,21 @@ class Pane extends View @command 'pane:split-right', => @splitRight() @command 'pane:split-up', => @splitUp() @command 'pane:split-down', => @splitDown() - @on 'focus', => - @viewForCurrentItem().focus() - false + @on 'focus', => @currentView.focus(); false + @on 'focusin', => @makeActive() + + makeActive: -> + for pane in @getContainer().getPanes() when pane isnt this + pane.makeInactive() + wasActive = @isActive() + @addClass('active') + @trigger 'pane:active-item-changed', [@currentItem] unless wasActive + + makeInactive: -> + @removeClass('active') + + isActive: -> + @hasClass('active') getItems: -> new Array(@items...) @@ -55,6 +67,7 @@ class Pane extends View @showItem(@items[index]) showItem: (item) -> + return if item is @currentItem @addItem(item) @itemViews.children().hide() view = @viewForItem(item) @@ -62,6 +75,7 @@ class Pane extends View @currentItem = item @currentView = view @currentView.show() + @trigger 'pane:active-item-changed', [item] if @isActive() addItem: (item) -> return if _.include(@items, item) From 58228f7ff7054c5346eeae91034cf989c5c44a15 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 16:23:16 -0700 Subject: [PATCH 060/160] Remove RootView.activeKeybindings method. It was dead code. --- spec/app/root-view-spec.coffee | 25 ------------------------- src/app/root-view.coffee | 3 --- 2 files changed, 28 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 8b1d6f691..e3b05e280 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -186,31 +186,6 @@ describe "RootView", -> rootView.trigger(event) expect(commandHandler).toHaveBeenCalled() - describe ".activeKeybindings()", -> - originalKeymap = null - keymap = null - editor = null - - beforeEach -> - rootView.attachToDom() - editor = rootView.getActiveEditor() - keymap = new (require 'keymap') - originalKeymap = window.keymap - window.keymap = keymap - - afterEach -> - window.keymap = originalKeymap - - it "returns all keybindings available for focused element", -> - editor.on 'test-event-a', => # nothing - - keymap.bindKeys ".editor", - "meta-a": "test-event-a" - "meta-b": "test-event-b" - - keybindings = rootView.activeKeybindings() - expect(Object.keys(keybindings).length).toBe 2 - expect(keybindings["meta-a"]).toEqual "test-event-a" describe "when the path of the active editor changes", -> it "changes the title and emits an root-view:active-path-changed event", -> diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 4fa03aefb..c462de3a0 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -124,9 +124,6 @@ class RootView extends View 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" From 80e736d4ee7150ea3a62d222ef22b86852a63f92 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 16:25:51 -0700 Subject: [PATCH 061/160] Add RootView.getActiveView and .getActivePaneItem They will replace getActiveEditor/getActiveEditSession --- spec/app/grammar-view-spec.coffee | 4 +--- src/app/pane-container.coffee | 5 +++++ src/app/root-view.coffee | 8 ++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) 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/src/app/pane-container.coffee b/src/app/pane-container.coffee index ca4c2f254..aae999567 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -35,6 +35,11 @@ class PaneContainer extends View getActivePane: -> @find('.pane.active').view() ? @find('.pane:first').view() + getActivePaneItem: -> + @getActivePane()?.currentItem + + getActiveView: -> + @getActivePane()?.currentView adjustPaneDimensions: -> if root = @getRoot() diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index c462de3a0..d1f49294f 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -71,8 +71,8 @@ class RootView extends View packageStates: atom.serializeAtomPackages() handleFocus: (e) -> - if @getActiveEditor() - @getActiveEditor().focus() + if @getActivePane() + @getActivePane().focus() false else @setTitle(null) @@ -163,9 +163,13 @@ class RootView extends View editor.view() else @panes.find('.editor:first').view() + getActivePaneItem: -> + @panes.getActivePaneItem() getActiveEditSession: -> @getActiveEditor()?.activeEditSession + getActiveView: -> + @panes.getActiveView() focusNextPane: -> @panes.focusNextPane() getFocusedPane: -> @panes.getFocusedPane() From 517c5022d3b40568864ca264728ed058bd54a87a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 16:26:31 -0700 Subject: [PATCH 062/160] Provide a default param for RootView@content when not deserializing --- src/app/root-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index d1f49294f..fc67bad4b 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -20,7 +20,7 @@ class RootView extends View ignoredNames: [".git", ".svn", ".DS_Store"] disabledPackages: [] - @content: ({panes}) -> + @content: ({panes}={}) -> @div id: 'root-view', => @div id: 'horizontal', outlet: 'horizontal', => @div id: 'vertical', outlet: 'vertical', => From 75229808de2f7f65e927a07c3daffcb0b9f8bdab Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 16:27:41 -0700 Subject: [PATCH 063/160] Add Editor.getModel --- src/app/editor.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index fc69b0c37..6b3912414 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -483,6 +483,9 @@ class Editor extends View index = @pushEditSession(editSession) if index == -1 @setActiveEditSessionIndex(index) + getModel: -> + @activeEditSession + setModel: (editSession) -> @edit(editSession) From 161ed69ef0b8057c0370ff3898635ad9a83a8a8f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 17:42:51 -0700 Subject: [PATCH 064/160] When a pane is removed, focus/activate the next pane --- spec/app/pane-spec.coffee | 60 +++++++++++++++++++++++++++++++++-- spec/spec-helper.coffee | 2 +- src/app/pane-container.coffee | 14 ++++++-- src/app/pane.coffee | 6 ++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 331e414e4..b24f9540c 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -8,8 +8,8 @@ describe "Pane", -> beforeEach -> container = new PaneContainer - view1 = $$ -> @div id: 'view-1', 'View 1' - view2 = $$ -> @div id: 'view-2', 'View 2' + 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) @@ -144,6 +144,62 @@ describe "Pane", -> expect(editSession1.destroyed).toBeTruthy() expect(editSession2.destroyed).toBeTruthy() + 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 current item view", -> focusHandler = jasmine.createSpy("focusHandler") diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 910c8d238..485521129 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -73,7 +73,7 @@ afterEach -> keymap.bindingSets = bindingSetsToRestore keymap.bindingSetsByFirstKeystrokeToRestore = bindingSetsByFirstKeystrokeToRestore if rootView? - rootView.deactivate() + rootView.deactivate?() window.rootView = null if project? project.destroy() diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index aae999567..f9471d0b8 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -19,9 +19,19 @@ class PaneContainer extends View focusNextPane: -> panes = @getPanes() - currentIndex = panes.indexOf(@getFocusedPane()) + 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].focus() + panes[nextIndex].makeActive() getRoot: -> @children().first().view() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index cb5e3636d..99f3d6d32 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -171,7 +171,13 @@ class Pane extends View # find parent elements before removing from dom container = @getContainer() parentAxis = @parent('.row, .column') + if @is(':has(:focus)') + rootView?.focus() unless container.focusNextPane() + else if @isActive() + container.makeNextPaneActive() + super + if parentAxis.children().length == 1 sibling = parentAxis.children().detach() parentAxis.replaceWith(sibling) From 3ae9c10ff51a0d6fcd2cc8c0643f4821f37d6754 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 17:43:18 -0700 Subject: [PATCH 065/160] Trigger 'pane:active-item-changed' w/ null when last pane is removed --- spec/app/pane-spec.coffee | 7 +++++++ src/app/pane.coffee | 1 + 2 files changed, 8 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index b24f9540c..d761c364c 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -187,6 +187,13 @@ describe "Pane", -> expect(container.getPanes().length).toBe 1 window.rootView = focus: jasmine.createSpy("rootView.focus") + it "triggers a 'pane:active-item-changed' event with null", -> + itemChangedHandler = jasmine.createSpy("itemChangedHandler") + container.on 'pane:active-item-changed', itemChangedHandler + pane.remove() + expect(itemChangedHandler).toHaveBeenCalled() + expect(itemChangedHandler.argsForCall[0][1]).toBeNull() + describe "when the removed pane is focused", -> it "calls focus on rootView so we don't lose focus", -> container.attachToDom() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 99f3d6d32..cad027c9b 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -182,6 +182,7 @@ class Pane extends View sibling = parentAxis.children().detach() parentAxis.replaceWith(sibling) container.adjustPaneDimensions() + container.trigger 'pane:active-item-changed', [null] unless container.getActivePaneItem() afterRemove: -> item.destroy?() for item in @getItems() From d6b85cf7e87690fe7625fee60b368b51d072af8e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 18:22:55 -0700 Subject: [PATCH 066/160] Base title updates on pane:active-item-changed events --- spec/app/root-view-spec.coffee | 71 ++++++++-------------------------- spec/spec-helper.coffee | 2 +- src/app/edit-session.coffee | 3 ++ src/app/root-view.coffee | 32 ++++++--------- 4 files changed, 32 insertions(+), 76 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index e3b05e280..bb78737ed 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -179,69 +179,30 @@ 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 "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 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 + describe "when the project has a path", -> + describe "when there is no active pane item", -> + it "sets the title to the project's path", -> + rootView.getActivePane().remove() + expect(rootView.getActivePaneItem()).toBeUndefined() + expect(rootView.title).toBe project.getPath() - editor1 = rootView.getActiveEditor() - expect(rootView.getTitle()).toBe "#{fs.base(editor1.getPath())} – #{project.getPath()}" - - editor2 = rootView.getActiveEditor().splitLeft() - - path = project.resolve('b') - editor2.edit(project.buildEditSession(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 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()}" describe "font size adjustment", -> editor = null diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 485521129..92c752871 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -56,7 +56,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() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 14a3b11e2..5bc18faba 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -55,6 +55,9 @@ class EditSession getViewClass: -> require 'editor' + getTitle: -> + fs.base(@getPath()) + destroy: -> return if @destroyed @destroyed = true diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index fc67bad4b..cc0682e93 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -39,12 +39,8 @@ 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:active-item-changed', => @updateTitle() @command 'window:increase-font-size', => config.set("editor.fontSize", config.get("editor.fontSize") + 1) @@ -124,22 +120,18 @@ class RootView extends View if not previousActiveEditor or editor.getPath() != previousActiveEditor.getPath() @trigger 'root-view:active-path-changed', editor.getPath() - 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 > .item-views > .editor').map(-> $(this).view()).toArray() From 26c63edf3388e124d738a56d1c2433b52d83331a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 18:30:25 -0700 Subject: [PATCH 067/160] Assert against config for font-size changing events instead of editor --- spec/app/root-view-spec.coffee | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index bb78737ed..b676435cd 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -205,26 +205,21 @@ describe "RootView", -> expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}" 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 pane", -> From 2bf51637983668e8a2b8e6ad39a1c3b1e82fb6ec Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 18:31:00 -0700 Subject: [PATCH 068/160] Kill pane specs on root view --- spec/app/root-view-spec.coffee | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index b676435cd..3c467ec4f 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -141,35 +141,6 @@ describe "RootView", -> it "surrenders focus to the body", -> expect(document.activeElement).toBe $('body')[0] - describe "panes", -> - [pane1, newPaneContent] = [] - - beforeEach -> - pane1 = rootView.find('.pane').view() - - 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.find('.editor').view() - 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() describe "keymap wiring", -> commandHandler = null From 6304bac23367cab01ac0c27f4f32884a85f46be2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 18:32:03 -0700 Subject: [PATCH 069/160] Remove RootView.getActiveEditor / getActiveEditSession MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's a bunch broken because of this… to be continued. --- spec/app/root-view-spec.coffee | 39 +++++++++++++++++----------------- src/app/root-view.coffee | 8 ------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 3c467ec4f..3e9ec8fa1 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -109,38 +109,39 @@ describe "RootView", -> 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 + rootView.focus() + expect(document.activeElement).toBe document.body describe "keymap wiring", -> commandHandler = null diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index cc0682e93..371f49fa8 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -49,7 +49,6 @@ 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', => @@ -150,16 +149,9 @@ class RootView extends View getActivePane: -> @panes.getActivePane() - getActiveEditor: -> - if (editor = @panes.find('.editor.active')).length - editor.view() - else - @panes.find('.editor:first').view() getActivePaneItem: -> @panes.getActivePaneItem() - getActiveEditSession: -> - @getActiveEditor()?.activeEditSession getActiveView: -> @panes.getActiveView() From 106c6c395816e127ac3ee42543210989fa7b19d3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 18:34:33 -0700 Subject: [PATCH 070/160] Return 'untitled' from EditSession.getPath if its path is null --- src/app/edit-session.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 5bc18faba..5ba7eda49 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -56,7 +56,10 @@ class EditSession require 'editor' getTitle: -> - fs.base(@getPath()) + if path = @getPath() + fs.base(path) + else + 'untitled' destroy: -> return if @destroyed From 062adae714afef594a3cacd667a43716b65f7d5f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki & Nathan Sobo Date: Thu, 21 Feb 2013 18:38:31 -0700 Subject: [PATCH 071/160] Return the new pane's currentView when splitting the editor --- spec/app/root-view-spec.coffee | 2 +- src/app/editor.coffee | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 3e9ec8fa1..43c89cec0 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -251,7 +251,7 @@ describe "RootView", -> fs.write(file2, "file2") rootView.open(file1) - editor1 = rootView.getActiveEditor() + editor1 = rootView.getActiveView() buffer1 = editor1.activeEditSession.buffer expect(buffer1.getText()).toBe("file1") expect(buffer1.isModified()).toBe(false) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 6b3912414..d2ece3c63 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -786,16 +786,16 @@ class Editor extends View @requestDisplayUpdate() splitLeft: (editSession) -> - @pane()?.splitLeft() + @pane()?.splitLeft().currentView splitRight: (editSession) -> - @pane()?.splitRight() + @pane()?.splitRight().currentView splitUp: (editSession) -> - @pane()?.splitUp() + @pane()?.splitUp().currentView splitDown: (editSession) -> - @pane()?.splitDown() + @pane()?.splitDown().currentView pane: -> @closest('.pane').view() From ff899e9c1b9fb01ef6f754d3fa7d62c167145ab0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 21 Feb 2013 17:49:42 -0800 Subject: [PATCH 072/160] Replace RootView.getActiveEditor() with getActiveView() --- spec/app/root-view-spec.coffee | 12 ++++++------ src/packages/wrap-guide/spec/wrap-guide-spec.coffee | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 43c89cec0..85be2c4e0 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -277,7 +277,7 @@ describe "RootView", -> 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 " " @@ -311,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 @@ -323,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 -> @@ -339,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 @@ -353,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/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' From 8660670ae3bd16c02e4f465df17d900bb8475045 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 21 Feb 2013 18:01:02 -0800 Subject: [PATCH 073/160] Replace RootView.getActiveEditor() with getActiveView() --- .../autocomplete/spec/autocomplete-spec.coffee | 4 ++-- src/packages/autoflow/spec/autoflow-spec.coffee | 2 +- .../spec/bracket-matcher-spec.coffee | 2 +- .../command-logger/spec/command-logger-spec.coffee | 2 +- src/packages/gists/lib/gists.coffee | 2 +- src/packages/gists/spec/gists-spec.coffee | 2 +- src/packages/go-to-line/lib/go-to-line-view.coffee | 4 ++-- .../go-to-line/spec/go-to-line-spec.coffee | 2 +- .../lib/markdown-preview-view.coffee | 4 ++-- .../spec/markdown-preview-spec.coffee | 14 +++++++------- .../spec/strip-trailing-whitespace-spec.coffee | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/packages/autocomplete/spec/autocomplete-spec.coffee b/src/packages/autocomplete/spec/autocomplete-spec.coffee index 6912fd5b4..d85b8b507 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() 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/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..6e5ca6f0e 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,7 +46,7 @@ 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() @@ -55,7 +55,7 @@ describe "MarkdownPreview", -> describe "core:cancel event", -> it "removes markdown preview", -> rootView.open('file.md') - editor = rootView.getActiveEditor() + editor = rootView.getActiveView() expect(rootView.find('.markdown-preview')).not.toExist() editor.trigger('markdown-preview:toggle') @@ -68,7 +68,7 @@ describe "MarkdownPreview", -> it "removes the markdown preview view", -> rootView.attachToDom() rootView.open('file.md') - editor = rootView.getActiveEditor() + editor = rootView.getActiveView() expect(rootView.find('.markdown-preview')).not.toExist() editor.trigger('markdown-preview:toggle') @@ -79,6 +79,6 @@ describe "MarkdownPreview", -> describe "when no editor is open", -> it "does not attach", -> - expect(rootView.getActiveEditor()).toBeFalsy() + expect(rootView.getActiveView()).toBeFalsy() rootView.trigger('markdown-preview:toggle') expect(rootView.find('.markdown-preview')).not.toExist() 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..e1fc838de 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) From a6bf7f876d5df28b7b2fb2413ad1a9df2c86482f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 21 Feb 2013 18:07:04 -0800 Subject: [PATCH 074/160] Replace RootView.getActiveEditor() with getActiveView() --- spec/app/atom-package-spec.coffee | 2 +- .../packages/package-with-activation-events/main.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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: -> From 20590f590e6ef3276a2ca9726a3838f775a04d96 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 11:36:24 -0700 Subject: [PATCH 075/160] Make window specs pass without getActiveEditor/getEditors --- spec/app/window-spec.coffee | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index 32f9397f1..787a80734 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() From 1902a0c5530e82db0242806b6d0a55270e94d87a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 11:52:53 -0700 Subject: [PATCH 076/160] Get CommandPanel specs passing without getActiveEditor/EditSession --- .../lib/command-panel-view.coffee | 8 ++-- .../spec/command-panel-spec.coffee | 38 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) 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-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() From 3382a542b3d032f000b465afb8015f16b1e3f0f4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 11:55:53 -0700 Subject: [PATCH 077/160] Get CommandPalette specs to pass without getActiveEditor --- .../command-palette/spec/command-palette-spec.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 From 892ff0c51fb4fc4b642798acba6bf98a07972399 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 15:27:09 -0700 Subject: [PATCH 078/160] Add PaneContainer.eachPane It calls the given callback with all current and future panes --- spec/app/pane-container-spec.coffee | 15 +++++++++++++++ src/app/pane-container.coffee | 6 ++++++ src/app/pane.coffee | 5 +++++ src/app/root-view.coffee | 3 +++ 4 files changed, 29 insertions(+) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index bca9ed793..3a20ee3e4 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -57,6 +57,21 @@ describe "PaneContainer", -> 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 "serialization", -> it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", -> newContainer = deserialize(container.serialize()) diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index f9471d0b8..adac275b6 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -39,6 +39,12 @@ class PaneContainer extends View getPanes: -> @find('.pane').toArray().map (node)-> $(node).view() + 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() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index cad027c9b..9e018ab3a 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -30,6 +30,11 @@ class Pane extends View @on 'focus', => @currentView.focus(); false @on 'focusin', => @makeActive() + afterAttach: -> + return if @attached + @attached = true + @trigger 'pane:attached' + makeActive: -> for pane in @getContainer().getPanes() when pane isnt this pane.makeInactive() diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 371f49fa8..f958aa5ba 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -166,6 +166,9 @@ class RootView extends View saveAll: -> editor.save() for editor in @getEditors() + eachPane: (callback) -> + @panes.eachPane(callback) + eachEditor: (callback) -> callback(editor) for editor in @getEditors() @on 'editor:attached', (e, editor) -> callback(editor) From 15d8a6cada8887dcdded426c93082ddae94fb582 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 16:26:50 -0700 Subject: [PATCH 079/160] Simplify pane:active-item-changed events Panes now trigger an event every time their active item changes, regardless of whether the pane itself is active. Panes also trigger events when the become active and when they are removed. The rootView now scopes its active-item-changed event listener only to active panes, and also listens to listens to pane activation and removal events to update the title when switching active panes and removing the last pane. --- spec/app/pane-spec.coffee | 29 ++++++++++++----------------- spec/app/root-view-spec.coffee | 27 ++++++++++++++++++++++----- src/app/pane.coffee | 6 +++--- src/app/root-view.coffee | 4 +++- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index d761c364c..308a5e7d8 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -27,7 +27,7 @@ describe "Pane", -> expect(view2.css('display')).toBe '' expect(pane.currentItem).toBe view2 - it "triggers 'pane:active-item-changed' if the pane is active and the item isn't already the currentItem", -> + it "triggers 'pane:active-item-changed' if the item isn't already the currentItem", -> pane.makeActive() itemChangedHandler = jasmine.createSpy("itemChangedHandler") container.on 'pane:active-item-changed', itemChangedHandler @@ -44,10 +44,6 @@ describe "Pane", -> expect(itemChangedHandler.argsForCall[0][1]).toBe editSession1 itemChangedHandler.reset() - pane.makeInactive() - pane.showItem(editSession2) - expect(itemChangedHandler).not.toHaveBeenCalled() - describe "when the given item isn't yet in the items list on the pane", -> it "adds it to the items list after the current item", -> view3 = $$ -> @div id: 'view-3', "View 3" @@ -144,6 +140,13 @@ describe "Pane", -> 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] = [] @@ -187,13 +190,6 @@ describe "Pane", -> expect(container.getPanes().length).toBe 1 window.rootView = focus: jasmine.createSpy("rootView.focus") - it "triggers a 'pane:active-item-changed' event with null", -> - itemChangedHandler = jasmine.createSpy("itemChangedHandler") - container.on 'pane:active-item-changed', itemChangedHandler - pane.remove() - expect(itemChangedHandler).toHaveBeenCalled() - expect(itemChangedHandler.argsForCall[0][1]).toBeNull() - describe "when the removed pane is focused", -> it "calls focus on rootView so we don't lose focus", -> container.attachToDom() @@ -214,17 +210,16 @@ describe "Pane", -> pane.focus() expect(focusHandler).toHaveBeenCalled() - it "triggers 'pane:active-item-changed' if it was not previously active", -> - itemChangedHandler = jasmine.createSpy("itemChangedHandler") - container.on 'pane:active-item-changed', itemChangedHandler + 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(itemChangedHandler.callCount).toBe 1 - expect(itemChangedHandler.argsForCall[0][1]).toBe pane.currentItem + expect(becameActiveHandler.callCount).toBe 1 describe "split methods", -> [pane1, view3, view4] = [] diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 85be2c4e0..cc56ee87e 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -165,17 +165,34 @@ describe "RootView", -> expect(rootView.title).toBe 'untitled' describe "when the project has a path", -> - describe "when there is no active pane item", -> - it "sets the title to the project's path", -> - rootView.getActivePane().remove() - expect(rootView.getActivePaneItem()).toBeUndefined() - expect(rootView.title).toBe project.getPath() + beforeEach -> + rootView.open('b') 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()}" + 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()}" + + 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 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", -> it "increases/decreases font size when increase/decrease-font-size events are triggered", -> fontSizeBefore = config.get('editor.fontSize') diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 9e018ab3a..5a95cf5a4 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -40,7 +40,7 @@ class Pane extends View pane.makeInactive() wasActive = @isActive() @addClass('active') - @trigger 'pane:active-item-changed', [@currentItem] unless wasActive + @trigger 'pane:became-active' unless wasActive makeInactive: -> @removeClass('active') @@ -80,7 +80,7 @@ class Pane extends View @currentItem = item @currentView = view @currentView.show() - @trigger 'pane:active-item-changed', [item] if @isActive() + @trigger 'pane:active-item-changed', [item] addItem: (item) -> return if _.include(@items, item) @@ -187,7 +187,7 @@ class Pane extends View sibling = parentAxis.children().detach() parentAxis.replaceWith(sibling) container.adjustPaneDimensions() - container.trigger 'pane:active-item-changed', [null] unless container.getActivePaneItem() + container.trigger 'pane:removed', [this] afterRemove: -> item.destroy?() for item in @getItems() diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index f958aa5ba..4515f50e2 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -40,7 +40,9 @@ class RootView extends View @handleFocus(e) if document.activeElement is document.body project.on 'path-changed', => @updateTitle() - @on 'pane:active-item-changed', => @updateTitle() + @on 'pane:became-active', => @updateTitle() + @on 'pane:active-item-changed', '.active.pane', => @updateTitle() + @on 'pane:removed', => @updateTitle() unless @getActivePane() @command 'window:increase-font-size', => config.set("editor.fontSize", config.get("editor.fontSize") + 1) From a40d05f6ee364d367f503e4efa09850dfc31e097 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 16:29:29 -0700 Subject: [PATCH 080/160] Rename Pane.currentItem/View to activeItem/View --- spec/app/pane-container-spec.coffee | 8 ++-- spec/app/pane-spec.coffee | 60 ++++++++++++++--------------- src/app/pane.coffee | 40 +++++++++---------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index 3a20ee3e4..063d3598b 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -27,13 +27,13 @@ describe "PaneContainer", -> it "focuses the pane following the focused pane or the first pane if no pane has focus", -> container.attachToDom() container.focusNextPane() - expect(pane1.currentItem).toMatchSelector ':focus' + expect(pane1.activeItem).toMatchSelector ':focus' container.focusNextPane() - expect(pane2.currentItem).toMatchSelector ':focus' + expect(pane2.activeItem).toMatchSelector ':focus' container.focusNextPane() - expect(pane3.currentItem).toMatchSelector ':focus' + expect(pane3.activeItem).toMatchSelector ':focus' container.focusNextPane() - expect(pane1.currentItem).toMatchSelector ':focus' + expect(pane1.activeItem).toMatchSelector ':focus' describe ".getActivePane()", -> it "returns the most-recently focused pane", -> diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 308a5e7d8..0d2abd87e 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -20,19 +20,19 @@ describe "Pane", -> expect(pane.itemViews.find('#view-1')).toExist() describe ".showItem(item)", -> - it "hides all item views except the one being shown and sets the currentItem", -> - expect(pane.currentItem).toBe view1 + 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.currentItem).toBe view2 + expect(pane.activeItem).toBe view2 - it "triggers 'pane:active-item-changed' if the item isn't already the currentItem", -> + 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.currentItem).toBe view1 + expect(pane.activeItem).toBe view1 pane.showItem(view2) pane.showItem(view2) expect(itemChangedHandler.callCount).toBe 1 @@ -45,20 +45,20 @@ describe "Pane", -> itemChangedHandler.reset() describe "when the given item isn't yet in the items list on the pane", -> - it "adds it to the items list after the current item", -> + it "adds it to the items list after the active item", -> view3 = $$ -> @div id: 'view-3', "View 3" pane.showItem(editSession1) - expect(pane.getCurrentItemIndex()).toBe 1 + expect(pane.getActiveItemIndex()).toBe 1 pane.showItem(view3) expect(pane.getItems()).toEqual [view1, editSession1, view3, view2, editSession2] - expect(pane.currentItem).toBe view3 - expect(pane.getCurrentItemIndex()).toBe 2 + expect(pane.activeItem).toBe view3 + expect(pane.getActiveItemIndex()).toBe 2 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.currentView + editor = pane.activeView expect(editor.css('display')).toBe '' expect(editor.activeEditSession).toBe editSession1 @@ -67,7 +67,7 @@ describe "Pane", -> pane.showItem(editSession1) pane.showItem(editSession2) expect(pane.itemViews.find('.editor').length).toBe 1 - editor = pane.currentView + editor = pane.activeView expect(editor.css('display')).toBe '' expect(editor.activeEditSession).toBe editSession2 @@ -76,18 +76,18 @@ describe "Pane", -> expect(pane.itemViews.find('#view-2')).not.toExist() pane.showItem(view2) expect(pane.itemViews.find('#view-2')).toExist() - expect(pane.currentView).toBe view2 + expect(pane.activeView).toBe view2 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.currentItem).toBe editSession1 + expect(pane.activeItem).toBe editSession1 pane.showItem(editSession2) pane.removeItem(editSession2) expect(pane.getItems()).toEqual [editSession1, view2] - expect(pane.currentItem).toBe editSession1 + expect(pane.activeItem).toBe editSession1 it "removes the pane when its last item is removed", -> pane.removeItem(item) for item in pane.getItems() @@ -112,7 +112,7 @@ describe "Pane", -> expect(editSession2.destroyed).toBeTruthy() describe "core:close", -> - it "removes the current item and does not bubble the event", -> + it "removes the active item and does not bubble the event", -> containerCloseHandler = jasmine.createSpy("containerCloseHandler") container.on 'core:close', containerCloseHandler @@ -124,15 +124,15 @@ describe "Pane", -> 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.currentItem).toBe view1 + expect(pane.activeItem).toBe view1 pane.trigger 'pane:show-previous-item' - expect(pane.currentItem).toBe editSession2 + expect(pane.activeItem).toBe editSession2 pane.trigger 'pane:show-previous-item' - expect(pane.currentItem).toBe view2 + expect(pane.activeItem).toBe view2 pane.trigger 'pane:show-next-item' - expect(pane.currentItem).toBe editSession2 + expect(pane.activeItem).toBe editSession2 pane.trigger 'pane:show-next-item' - expect(pane.currentItem).toBe view1 + expect(pane.activeItem).toBe view1 describe ".remove()", -> it "destroys all the pane's items", -> @@ -204,9 +204,9 @@ describe "Pane", -> expect(rootView.focus).not.toHaveBeenCalled() describe "when the pane is focused", -> - it "focuses the current item view", -> + it "focuses the active item view", -> focusHandler = jasmine.createSpy("focusHandler") - pane.currentItem.on 'focus', focusHandler + pane.activeItem.on 'focus', focusHandler pane.focus() expect(focusHandler).toHaveBeenCalled() @@ -231,11 +231,11 @@ describe "Pane", -> 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 current item if none are given + # 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.currentItem).not.toBe editSession1 # it's a copy + expect(pane2.activeItem).not.toBe editSession1 # it's a copy pane3 = pane2.splitRight(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] @@ -243,11 +243,11 @@ describe "Pane", -> 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 current item if none are given + # 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.currentItem).not.toBe editSession1 # it's a copy + expect(pane2.activeItem).not.toBe editSession1 # it's a copy pane3 = pane2.splitLeft(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] @@ -255,11 +255,11 @@ describe "Pane", -> 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 current item if none are given + # 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.currentItem).not.toBe editSession1 # it's a copy + expect(pane2.activeItem).not.toBe editSession1 # it's a copy pane3 = pane2.splitDown(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] @@ -267,11 +267,11 @@ describe "Pane", -> 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 current item if none are given + # 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.currentItem).not.toBe editSession1 # it's a copy + expect(pane2.activeItem).not.toBe editSession1 # it's a copy pane3 = pane2.splitUp(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 5a95cf5a4..1fad3f6fa 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -13,21 +13,21 @@ class Pane extends View @deserialize: ({items}) -> new Pane(items.map((item) -> deserialize(item))...) - currentItem: null + activeItem: null items: null initialize: (@items...) -> @viewsByClassName = {} @showItem(@items[0]) - @command 'core:close', @removeCurrentItem + @command 'core:close', @removeActiveItem @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem @command 'pane:split-left', => @splitLeft() @command 'pane:split-right', => @splitRight() @command 'pane:split-up', => @splitUp() @command 'pane:split-down', => @splitDown() - @on 'focus', => @currentView.focus(); false + @on 'focus', => @activeView.focus(); false @on 'focusin', => @makeActive() afterAttach: -> @@ -52,47 +52,47 @@ class Pane extends View new Array(@items...) showNextItem: => - index = @getCurrentItemIndex() + index = @getActiveItemIndex() if index < @items.length - 1 @showItemAtIndex(index + 1) else @showItemAtIndex(0) showPreviousItem: => - index = @getCurrentItemIndex() + index = @getActiveItemIndex() if index > 0 @showItemAtIndex(index - 1) else @showItemAtIndex(@items.length - 1) - getCurrentItemIndex: -> - @items.indexOf(@currentItem) + getActiveItemIndex: -> + @items.indexOf(@activeItem) showItemAtIndex: (index) -> @showItem(@items[index]) showItem: (item) -> - return if item is @currentItem + return if item is @activeItem @addItem(item) @itemViews.children().hide() view = @viewForItem(item) @itemViews.append(view) unless view.parent().is(@itemViews) - @currentItem = item - @currentView = view - @currentView.show() + @activeItem = item + @activeView = view + @activeView.show() @trigger 'pane:active-item-changed', [item] addItem: (item) -> return if _.include(@items, item) - @items.splice(@getCurrentItemIndex() + 1, 0, item) + @items.splice(@getActiveItemIndex() + 1, 0, item) item - removeCurrentItem: => - @removeItem(@currentItem) + removeActiveItem: => + @removeItem(@activeItem) false removeItem: (item) -> - @showNextItem() if item is @currentItem and @items.length > 1 + @showNextItem() if item is @activeItem and @items.length > 1 _.remove(@items, item) item.destroy?() @cleanupItemView(item) @@ -122,8 +122,8 @@ class Pane extends View view = @viewsByClassName[viewClass.name] = new viewClass(item) view - viewForCurrentItem: -> - @viewForItem(@currentItem) + viewForActiveItem: -> + @viewForItem(@activeItem) serialize: -> deserializer: "Pane" @@ -153,7 +153,7 @@ class Pane extends View .insertBefore(this) .append(@detach()) - items = [@copyCurrentItem()] unless items.length + items = [@copyActiveItem()] unless items.length pane = new Pane(items...) this[side](pane) @getContainer().adjustPaneDimensions() @@ -168,8 +168,8 @@ class Pane extends View getContainer: -> @closest('#panes').view() - copyCurrentItem: -> - deserialize(@currentItem.serialize()) + copyActiveItem: -> + deserialize(@activeItem.serialize()) remove: (selector, keepData) -> return super if keepData From dd120663b7e0ef9018ecad2cecb8f0d9b355128b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 17:11:29 -0700 Subject: [PATCH 081/160] Pane emits 'pane:item-added' events --- spec/app/pane-spec.coffee | 12 ++- src/app/pane.coffee | 4 +- src/packages/tabs/lib/tab-bar-view.coffee | 100 ++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 src/packages/tabs/lib/tab-bar-view.coffee diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 0d2abd87e..963ce359f 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -45,15 +45,25 @@ describe "Pane", -> itemChangedHandler.reset() describe "when the given item isn't yet in the items list on the pane", -> - it "adds it to the items list after the active item", -> + 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", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 1fad3f6fa..547160157 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -84,7 +84,9 @@ class Pane extends View addItem: (item) -> return if _.include(@items, item) - @items.splice(@getActiveItemIndex() + 1, 0, item) + index = @getActiveItemIndex() + 1 + @items.splice(index, 0, item) + @trigger 'pane:item-added', [item, index] item removeActiveItem: => 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..d98d12bc1 --- /dev/null +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -0,0 +1,100 @@ +$ = require 'jquery' +SortableList = require 'sortable-list' +TabView = require './tab-view' + +module.exports = +class TabBarView extends SortableList + @content: -> + @ul class: "tabs #{@viewClass()}" + + initialize: (@pane) -> + super + + @addTabForItem(item) for item in @pane.getItems() + + +# @addTabForEditSession(editSession) for editSession in @editor.editSessions +# +# @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) + + @on 'click', '.tab', (e) => + @editor.setActiveEditSessionIndex($(e.target).closest('.tab').index()) + @editor.focus() + + @on 'click', '.tab .close-icon', (e) => + index = $(e.target).closest('.tab').index() + @editor.destroyEditSessionIndex(index) + false + + @pane.prepend(this) + + addTabForItem: (item) -> + tabView = new TabView(item, @pane) + @append(tabView) + @setActiveTabView(tabView) if item is @pane.currentItem + + setActiveTabView: (tabView) -> + unless tabView.hasClass('active') + @find(".tab.active").removeClass('active') + tabView.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(".#{TabBarView.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() + 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() From 5240d9989fd0b87e0eb73833e9d4805c10e2e350 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 17:15:00 -0700 Subject: [PATCH 082/160] Pane emits 'pane:item-removed' events --- spec/app/pane-spec.coffee | 7 +++++++ src/app/pane.coffee | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 963ce359f..d263cff4b 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -99,6 +99,13 @@ describe "Pane", -> 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] + it "removes the pane when its last item is removed", -> pane.removeItem(item) for item in pane.getItems() expect(pane.hasParent()).toBeFalsy() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 547160157..eadccaa50 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -94,10 +94,14 @@ class Pane extends View false removeItem: (item) -> + index = @items.indexOf(item) + return if index == -1 + @showNextItem() if item is @activeItem and @items.length > 1 _.remove(@items, item) item.destroy?() @cleanupItemView(item) + @trigger 'pane:item-removed', [item, index] @remove() unless @items.length itemForPath: (path) -> From 0c24843e52a57c65fcff285afed04f01f89aa5d8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 17:41:00 -0700 Subject: [PATCH 083/160] Start converting tabs package to work with new panes / pane-items --- src/packages/tabs/lib/tab-bar-view.coffee | 46 +++++-- src/packages/tabs/lib/tab-view.coffee | 128 ++++++------------- src/packages/tabs/lib/tab.coffee | 40 ------ src/packages/tabs/lib/tabs.coffee | 5 + src/packages/tabs/package.cson | 2 +- src/packages/tabs/spec/tabs-spec.coffee | 147 ++++++++++------------ 6 files changed, 145 insertions(+), 223 deletions(-) delete mode 100644 src/packages/tabs/lib/tab.coffee create mode 100644 src/packages/tabs/lib/tabs.coffee diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index d98d12bc1..773c9ddde 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -1,4 +1,5 @@ $ = require 'jquery' +_ = require 'underscore' SortableList = require 'sortable-list' TabView = require './tab-view' @@ -9,14 +10,16 @@ class TabBarView extends SortableList initialize: (@pane) -> super - @addTabForItem(item) for item in @pane.getItems() + @pane.on 'pane:item-added', (e, item, index) => @addTabForItem(item, index) + @pane.on 'pane:item-removed', (e, item) => @removeTabForItem(item) + @pane.on 'pane:active-item-changed', => @updateActiveTab() + + @updateActiveTab() -# @addTabForEditSession(editSession) for editSession in @editor.editSessions -# # @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) => @@ -29,26 +32,45 @@ class TabBarView extends SortableList # fromTab.insertBefore(toTab) @on 'click', '.tab', (e) => - @editor.setActiveEditSessionIndex($(e.target).closest('.tab').index()) - @editor.focus() + tab = $(e.target).closest('.tab').view() + @pane.showItem(tab.item) + @pane.focus() @on 'click', '.tab .close-icon', (e) => - index = $(e.target).closest('.tab').index() - @editor.destroyEditSessionIndex(index) + tab = $(e.target).closest('.tab').view() + @pane.removeItem(tab.item) false @pane.prepend(this) - addTabForItem: (item) -> + addTabForItem: (item, index) -> tabView = new TabView(item, @pane) - @append(tabView) - @setActiveTabView(tabView) if item is @pane.currentItem + followingTab = @tabAtIndex(index) if index? + if followingTab + tabView.insertBefore(followingTab) + else + @append(tabView) - setActiveTabView: (tabView) -> + 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)) + removeTabAtIndex: (index) -> @find(".tab:eq(#{index})").remove() diff --git a/src/packages/tabs/lib/tab-view.coffee b/src/packages/tabs/lib/tab-view.coffee index 166233094..351fa15ed 100644 --- a/src/packages/tabs/lib/tab-view.coffee +++ b/src/packages/tabs/lib/tab-view.coffee @@ -1,100 +1,44 @@ -$ = 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) -> + @title.text(@item.getTitle()) - @addTabForEditSession(editSession) for editSession in @editor.editSessions - @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) +# @buffer = @editSession.buffer +# @subscribe @buffer, 'path-changed', => @updateFileName() +# @subscribe @buffer, 'contents-modified', => @updateModifiedStatus() +# @subscribe @buffer, 'saved', => @updateModifiedStatus() +# @subscribe @buffer, 'git-status-changed', => @updateModifiedStatus() +# @subscribe @editor, 'editor:edit-session-added', => @updateFileName() +# @subscribe @editor, 'editor:edit-session-removed', => @updateFileName() +# @updateFileName() +# @updateModifiedStatus() - @on 'click', '.tab', (e) => - @editor.setActiveEditSessionIndex($(e.target).closest('.tab').index()) - @editor.focus() - - @on 'click', '.tab .close-icon', (e) => - index = $(e.target).closest('.tab').index() - @editor.destroyEditSessionIndex(index) - false - - 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 @buffer.isModified() + @toggleClass('file-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('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/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..07ad016a9 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -1,93 +1,99 @@ $ = 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 +fdescribe "TabBarView", -> + [item1, item2, editSession1, editSession2, 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 + @content: (title) -> @div title + initialize: (@title) -> + getTitle: -> @title - it "highlights the tab for the current active edit session", -> - expect(editor.getActiveEditSessionIndex()).toBe 1 - expect(tabs.find('.tab:eq(1)')).toHaveClass 'active' + beforeEach -> + 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() + 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 - 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' + 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() - editor.setActiveEditSessionIndex(1) - expect(tabs.find('.active').length).toBe 1 - expect(tabs.find('.tab:eq(1)')).toHaveClass 'active' + it "highlights the tab for the active pane item", -> + expect(tabBar.find('.tab:eq(2)')).toHaveClass 'active' - 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' + 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' - 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' + pane.showItem(item2) + expect(tabBar.find('.active').length).toBe 1 + expect(tabBar.find('.tab:eq(2)')).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() + describe "when a new item is added to the pane", -> + ffit "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' - 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 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] + + tabBar.tabAtIndex(2).click() + expect(pane.activeItem).toBe pane.getItems()[2] + + expect(pane.focus.callCount).toBe 2 + + describe "when a tab's close icon is clicked", -> + it "removes the tab's item from the pane", -> + tabBar.tabForItem(item1).find('.close-icon').click() + expect(pane.getItems().length).toBe 2 + expect(pane.getItems().indexOf(item1)).toBe -1 + expect(tabBar.getTabs().length).toBe 2 + expect(tabBar.find('.tab:contains(Item 1)')).not.toExist() describe "when a file name associated with a tab changes", -> [buffer, oldPath, newPath] = [] @@ -110,21 +116,6 @@ describe "TabView", -> waitsFor "file to be renamed", -> tabFileName.text() == "renamed-file.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 - - 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 - describe "when two tabs have the same file name", -> [tempPath] = [] From de8198084cb8365419265620d1023f14a6556a25 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 17:54:41 -0700 Subject: [PATCH 084/160] EditSession emits 'title-changed' events when its buffer path changes --- spec/app/edit-session-spec.coffee | 14 ++++++++++++++ src/app/edit-session.coffee | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index cbd7a90e9..3889dc35d 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -16,6 +16,20 @@ describe "EditSession", -> afterEach -> fixturesProject.destroy() + describe "title", -> + 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' + + 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()", -> it "returns the most recently created cursor", -> diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 5ba7eda49..2d72d39c5 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -43,7 +43,9 @@ class EditSession @addCursorAtScreenPosition([0, 0]) @buffer.retain() - @subscribe @buffer, "path-changed", => @trigger "path-changed" + @subscribe @buffer, "path-changed", => + @trigger "title-changed" + @trigger "path-changed" @subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted" @subscribe @buffer, "markers-updated", => @mergeCursors() From 8898f81fc3c4105e709f7ee5d5a7d46d4c9011b1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 18:11:19 -0700 Subject: [PATCH 085/160] Add `$.fn.views` method to space pane, which returns an array of views --- vendor/space-pen.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) -> From 3456b2db3cfd05df182a31a9fe3d9e2b9051c8e4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 18:12:45 -0700 Subject: [PATCH 086/160] Tabs try to use an item's `longTitle` if two tab titles are the same This will replace edit-session-specific functionality that displayed the file's parent directory when two files with the same name were open. --- src/packages/tabs/lib/tab-view.coffee | 24 +++++++++-- src/packages/tabs/spec/tabs-spec.coffee | 53 +++++++++---------------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/packages/tabs/lib/tab-view.coffee b/src/packages/tabs/lib/tab-view.coffee index 351fa15ed..ab75a9c03 100644 --- a/src/packages/tabs/lib/tab-view.coffee +++ b/src/packages/tabs/lib/tab-view.coffee @@ -1,3 +1,4 @@ +$ = require 'jquery' {View} = require 'space-pen' fs = require 'fs' @@ -9,9 +10,8 @@ class TabView extends View @span class: 'close-icon' initialize: (@item, @pane) -> - @title.text(@item.getTitle()) - - + @item.on? 'title-changed', => @updateTitle() + @updateTitle() # @buffer = @editSession.buffer # @subscribe @buffer, 'path-changed', => @updateFileName() # @subscribe @buffer, 'contents-modified', => @updateModifiedStatus() @@ -22,6 +22,24 @@ class TabView extends View # @updateFileName() # @updateModifiedStatus() + updateTitle: -> + return if @updatingTitle + @updatingTitle = true + + 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 + + @title.text(title) + @updatingTitle = false + + getSiblingTabs: -> + @siblings('.tab').views() + updateModifiedStatus: -> if @buffer.isModified() @toggleClass('file-modified') unless @isModified diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 07ad016a9..22ff046fd 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -28,6 +28,7 @@ fdescribe "TabBarView", -> @content: (title) -> @div title initialize: (@title) -> getTitle: -> @title + getLongTitle: -> @longTitle beforeEach -> item1 = new TestView('Item 1') @@ -62,7 +63,7 @@ fdescribe "TabBarView", -> expect(tabBar.find('.tab:eq(2)')).toHaveClass 'active' describe "when a new item is added to the pane", -> - ffit "adds a tab for the new item at the same index as the item in 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) @@ -95,44 +96,28 @@ fdescribe "TabBarView", -> expect(tabBar.getTabs().length).toBe 2 expect(tabBar.find('.tab:contains(Item 1)')).not.toExist() - describe "when a file name associated with a tab changes", -> - [buffer, oldPath, newPath] = [] - - 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) - - afterEach -> - fs.remove(newPath) if fs.exists(newPath) - - 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 two tabs have the same file name", -> - [tempPath] = [] + 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' - beforeEach -> - tempPath = '/tmp/sample.js' - fs.write(tempPath, 'sample') + expect(tabBar.tabForItem(item1)).toHaveText "Grumpy Old Man" + expect(tabBar.tabForItem(item2)).toHaveText "Jolly Old Man" - afterEach -> - fs.remove(tempPath) if fs.exists(tempPath) + item2.longTitle = undefined + item2.trigger 'title-changed' - 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' + expect(tabBar.tabForItem(item1)).toHaveText "Grumpy Old Man" + expect(tabBar.tabForItem(item2)).toHaveText "Old Man" 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", -> From 21990cf98637f8b06153773903680f01283d97d5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 18:26:59 -0700 Subject: [PATCH 087/160] Add EditSession.getLongTitle --- spec/app/edit-session-spec.coffee | 15 +++++++++++---- src/app/edit-session.coffee | 8 ++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 3889dc35d..4c1bdc4a0 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -17,10 +17,17 @@ describe "EditSession", -> fixturesProject.destroy() describe "title", -> - 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 ".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") diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 2d72d39c5..f6e6b344a 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -63,6 +63,14 @@ class EditSession else 'untitled' + getLongTitle: -> + if path = @getPath() + fileName = fs.base(path) + directory = fs.base(fs.directory(path)) + "#{fileName} - #{directory}" + else + 'untitled' + destroy: -> return if @destroyed @destroyed = true From 887b5ea007b0cba7124638fe4d4adfea003979ba Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 19:03:06 -0700 Subject: [PATCH 088/160] Add Pane.moveItem(item, index) This is the precursor to supporting drag/drop of tabs within and between panes. --- spec/app/pane-spec.coffee | 23 +++++++++++++++++++++++ src/app/pane.coffee | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index d263cff4b..884399bf8 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -128,6 +128,29 @@ describe "Pane", -> pane.removeItem(editSession2) expect(editSession2.destroyed).toBeTruthy() + 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 "core:close", -> it "removes the active item and does not bubble the event", -> containerCloseHandler = jasmine.createSpy("containerCloseHandler") diff --git a/src/app/pane.coffee b/src/app/pane.coffee index eadccaa50..e01ec8fee 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -104,6 +104,12 @@ class Pane extends View @trigger 'pane:item-removed', [item, index] @remove() unless @items.length + moveItem: (item, newIndex) -> + oldIndex = @items.indexOf(item) + @items.splice(oldIndex, 1) + @items.splice(newIndex, 0, item) + @trigger 'pane:item-moved', [item, newIndex] + itemForPath: (path) -> _.detect @items, (item) -> item.getPath?() is path From 465bb146598b35fd95814297ed0b51074b5d90c4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 19:12:49 -0700 Subject: [PATCH 089/160] Reflect pane item order in tab bar --- src/packages/tabs/lib/tab-bar-view.coffee | 14 +++++++++++--- src/packages/tabs/spec/tabs-spec.coffee | 21 +++++++++------------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index 773c9ddde..930ecc981 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -13,6 +13,7 @@ class TabBarView extends SortableList @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() @@ -44,12 +45,19 @@ class TabBarView extends SortableList @pane.prepend(this) addTabForItem: (item, index) -> - tabView = new TabView(item, @pane) + @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 - tabView.insertBefore(followingTab) + tab.insertBefore(followingTab) else - @append(tabView) + @append(tab) removeTabForItem: (item) -> @tabForItem(item).remove() diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 22ff046fd..03029cb35 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -119,18 +119,15 @@ fdescribe "TabBarView", -> expect(tabBar.tabForItem(item1)).toHaveText "Grumpy Old Man" expect(tabBar.tabForItem(item2)).toHaveText "Old Man" - 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" - - 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", -> From 1d0cd16cd17972d8d8c49884761874bc2911c6ea Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Feb 2013 19:20:19 -0700 Subject: [PATCH 090/160] :lipstick: --- src/packages/tabs/spec/tabs-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 03029cb35..089b76c5b 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -22,7 +22,7 @@ describe "Tabs package main", -> expect(rootView.panes.find('.pane > .tabs').length).toBe 2 fdescribe "TabBarView", -> - [item1, item2, editSession1, editSession2, pane, tabBar] = [] + [item1, item2, editSession1, pane, tabBar] = [] class TestView extends View @content: (title) -> @div title From 4a7e5b74c61a6a443d6b638036b8c51b7ad303fd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 10:47:26 -0700 Subject: [PATCH 091/160] Make sure a pane view is showing before assigning its model object --- src/app/pane.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index e01ec8fee..fecf57fc5 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -74,12 +74,12 @@ class Pane extends View showItem: (item) -> return if item is @activeItem @addItem(item) - @itemViews.children().hide() view = @viewForItem(item) + @itemViews.children().not(view).hide() @itemViews.append(view) unless view.parent().is(@itemViews) + view.show() @activeItem = item @activeView = view - @activeView.show() @trigger 'pane:active-item-changed', [item] addItem: (item) -> From 61fa393e03973c5267c2b38f9e356285c83eace1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 11:41:41 -0700 Subject: [PATCH 092/160] Add indexOfPane and paneAtIndex to PaneContainer --- src/app/pane-container.coffee | 8 +++++++- src/app/root-view.coffee | 13 ++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index adac275b6..1e179c085 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -37,7 +37,13 @@ class PaneContainer extends View @children().first().view() getPanes: -> - @find('.pane').toArray().map (node)-> $(node).view() + @find('.pane').views() + + indexOfPane: (pane) -> + @getPanes().indexOf(pane.view()) + + paneAtIndex: (index) -> + @getPanes()[index] eachPane: (callback) -> callback(pane) for pane in @getPanes() diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 4515f50e2..83bedbf86 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -171,6 +171,12 @@ class RootView extends View eachPane: (callback) -> @panes.eachPane(callback) + getPanes: -> + @panes.getPanes() + + indexOfPane: (pane) -> + @panes.indexOfPane(pane) + eachEditor: (callback) -> callback(editor) for editor in @getEditors() @on 'editor:attached', (e, editor) -> callback(editor) @@ -181,10 +187,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 From 2e2ff3a1d094f96a2f243e37ac7f396cecbd157b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 14:19:33 -0700 Subject: [PATCH 093/160] Add Pane.destroyItem and rename removeActiveItem -> destroyActiveItem Pane.removeItem removes an item, but no longer tries to call destroy on it. This will facilitate moving items between panes. --- spec/app/pane-spec.coffee | 14 +++++++++----- src/app/pane.coffee | 12 ++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 884399bf8..bf2420eaa 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -88,6 +88,12 @@ describe "Pane", -> expect(pane.itemViews.find('#view-2')).toExist() expect(pane.activeView).toBe view2 + describe ".destroyItem(item)", -> + it "removes the item and destroys it if it's a model", -> + pane.destroyItem(editSession2) + expect(pane.getItems().indexOf(editSession2)).toBe -1 + expect(editSession2.destroyed).toBeTruthy() + describe ".removeItem(item)", -> it "removes the item from the items list and shows the next item if it was showing", -> pane.removeItem(view1) @@ -124,10 +130,6 @@ describe "Pane", -> pane.removeItem(editSession1) expect(pane.itemViews.find('.editor')).not.toExist() - it "calls destroy on the model", -> - pane.removeItem(editSession2) - expect(editSession2.destroyed).toBeTruthy() - 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") @@ -152,13 +154,15 @@ describe "Pane", -> itemMovedHandler.reset() describe "core:close", -> - it "removes the active item and does not bubble the event", -> + 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() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index fecf57fc5..83a970949 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -20,7 +20,7 @@ class Pane extends View @viewsByClassName = {} @showItem(@items[0]) - @command 'core:close', @removeActiveItem + @command 'core:close', @destroyActiveItem @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem @command 'pane:split-left', => @splitLeft() @@ -89,17 +89,21 @@ class Pane extends View @trigger 'pane:item-added', [item, index] item - removeActiveItem: => - @removeItem(@activeItem) + destroyActiveItem: => + @destroyItem(@activeItem) false + destroyItem: (item) -> + @removeItem(item) + item.destroy?() + removeItem: (item) -> index = @items.indexOf(item) return if index == -1 @showNextItem() if item is @activeItem and @items.length > 1 _.remove(@items, item) - item.destroy?() + @cleanupItemView(item) @trigger 'pane:item-removed', [item, index] @remove() unless @items.length From 7aba839dacf516934f55df60fb349119e213398e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 14:23:36 -0700 Subject: [PATCH 094/160] Fix exception when pane items with no view are removed from the pane --- src/app/pane.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 83a970949..ad2521f3d 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -124,7 +124,7 @@ class Pane extends View viewClass = item.getViewClass() otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass unless otherItemsForView.length - @viewsByClassName[viewClass.name].remove() + @viewsByClassName[viewClass.name]?.remove() delete @viewsByClassName[viewClass.name] viewForItem: (item) -> From 47621bd3b2cc9c3e51b3f9f178da7bcd0cd208ac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 14:25:00 -0700 Subject: [PATCH 095/160] Call Pane.destroyItem when close icon is clicked on a tab --- src/packages/tabs/lib/tab-bar-view.coffee | 2 +- src/packages/tabs/spec/tabs-spec.coffee | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index 930ecc981..0372052a7 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -39,7 +39,7 @@ class TabBarView extends SortableList @on 'click', '.tab .close-icon', (e) => tab = $(e.target).closest('.tab').view() - @pane.removeItem(tab.item) + @pane.destroyItem(tab.item) false @pane.prepend(this) diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 089b76c5b..5f528bad5 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -89,12 +89,13 @@ fdescribe "TabBarView", -> expect(pane.focus.callCount).toBe 2 describe "when a tab's close icon is clicked", -> - it "removes the tab's item from the pane", -> - tabBar.tabForItem(item1).find('.close-icon').click() + 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(item1)).toBe -1 + expect(pane.getItems().indexOf(editSession1)).toBe -1 + expect(editSession1.destroyed).toBeTruthy() expect(tabBar.getTabs().length).toBe 2 - expect(tabBar.find('.tab:contains(Item 1)')).not.toExist() + expect(tabBar.find('.tab:contains(sample.js)')).not.toExist() describe "when a tab item's title changes", -> it "updates the title of the item's tab", -> From 28141e315eb8440bce28d4bf5d511764caaa290a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 19:05:10 -0700 Subject: [PATCH 096/160] Make shouldAllowDrag method work properly --- src/app/sortable-list.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/sortable-list.coffee b/src/app/sortable-list.coffee index 08d727646..f105e834b 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' From 916c5caa3a8c37b2688268c234c50a9f27dd3d32 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 19:05:53 -0700 Subject: [PATCH 097/160] :lipstick: --- src/app/sortable-list.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/sortable-list.coffee b/src/app/sortable-list.coffee index f105e834b..ebdc675a1 100644 --- a/src/app/sortable-list.coffee +++ b/src/app/sortable-list.coffee @@ -47,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') From 9655fa8898526e17bd7a15e4dbe3e0ad22239530 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 19:06:28 -0700 Subject: [PATCH 098/160] Implement shouldAllowDrag in positive logic for tabs --- src/packages/tabs/lib/tab-bar-view.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index 0372052a7..4aed71f65 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -86,9 +86,8 @@ class TabBarView extends SortableList 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) + shouldAllowDrag: -> + (@paneContainer.getPanes().length > 1) or (@pane.getItems().length > 1) onDragStart: (event) => super From 0238061fa2adf3e25017c1cc2ecb24feb1928876 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 19:07:19 -0700 Subject: [PATCH 099/160] Make tab drag & drop work with new panes system --- src/app/pane.coffee | 4 + src/packages/tabs/lib/tab-bar-view.coffee | 43 +++--- src/packages/tabs/spec/tabs-spec.coffee | 153 +++++++++++++--------- 3 files changed, 111 insertions(+), 89 deletions(-) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index ad2521f3d..ba4488c6d 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -114,6 +114,10 @@ class Pane extends View @items.splice(newIndex, 0, item) @trigger 'pane:item-moved', [item, newIndex] + moveItemToPane: (item, pane, index) -> + @removeItem(item) + pane.addItem(item, index) + itemForPath: (path) -> _.detect @items, (item) -> item.getPath?() is path diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index 4aed71f65..aa4fb47f7 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -10,6 +10,8 @@ class TabBarView extends SortableList initialize: (@pane) -> super + + @paneContainer = @pane.getContainer() @addTabForItem(item) for item in @pane.getItems() @pane.on 'pane:item-added', (e, item, index) => @addTabForItem(item, index) @@ -91,39 +93,26 @@ class TabBarView extends SortableList onDragStart: (event) => super - pane = $(event.target).closest('.pane') - paneIndex = rootView.indexOfPane(pane) + paneIndex = @paneContainer.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' + 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 - 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(".#{TabBarView.viewClass()} .sortable:eq(#{previousDraggedTabIndex})") - - if draggedTab.is(droppedNearTab) - fromEditor.focus() - return - - if fromPaneIndex == toPaneIndex - droppedNearTab = @getSortableElement(event) - fromIndex = draggedTab.index() - toIndex = droppedNearTab.index() + if toPane is fromPane toIndex++ if fromIndex > toIndex - fromEditor.moveEditSessionToIndex(fromIndex, toIndex) - fromEditor.focus() + toPane.moveItem(item, toIndex) 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() + fromPane.moveItemToPane(item, toPane, toIndex) + toPane.showItem(item) + toPane.focus() diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 5f528bad5..7a70d19f9 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -21,16 +21,19 @@ describe "Tabs package main", -> expect(rootView.find('.pane').length).toBe 2 expect(rootView.panes.find('.pane > .tabs').length).toBe 2 -fdescribe "TabBarView", -> +describe "TabBarView", -> [item1, item2, editSession1, pane, tabBar] = [] class TestView extends View + @deserialize: ({title, longTitle}) -> new TestView(title, longTitle) @content: (title) -> @div title - initialize: (@title) -> + initialize: (@title, @longTitle) -> getTitle: -> @title getLongTitle: -> @longTitle + serialize: -> { deserializer: 'TestView', @title, @longTitle } beforeEach -> + registerDeserializer(TestView) item1 = new TestView('Item 1') item2 = new TestView('Item 2') editSession1 = project.buildEditSession('sample.js') @@ -40,6 +43,9 @@ fdescribe "TabBarView", -> paneContainer.append(pane) tabBar = new TabBarView(pane) + afterEach -> + unregisterDeserializer(TestView) + describe ".initialize(pane)", -> it "creates a tab for each item on the tab bar's parent pane", -> expect(pane.getItems().length).toBe 3 @@ -131,75 +137,98 @@ fdescribe "TabBarView", -> 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() From d69335f08dad2c1b00c2acb18f26faf5ab81fb70 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Feb 2013 19:07:42 -0700 Subject: [PATCH 100/160] Kill dead code --- src/packages/tabs/lib/tab-bar-view.coffee | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index aa4fb47f7..fbbab1330 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -21,19 +21,6 @@ class TabBarView extends SortableList @updateActiveTab() -# @setActiveTab(@editor.getActiveEditSessionIndex()) - -# @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) - @on 'click', '.tab', (e) => tab = $(e.target).closest('.tab').view() @pane.showItem(tab.item) @@ -81,13 +68,6 @@ class TabBarView extends SortableList updateActiveTab: -> @setActiveTab(@tabForItem(@pane.activeItem)) - removeTabAtIndex: (index) -> - @find(".tab:eq(#{index})").remove() - - containsEditSession: (editor, editSession) -> - for session in editor.editSessions - return true if editSession.getPath() is session.getPath() - shouldAllowDrag: -> (@paneContainer.getPanes().length > 1) or (@pane.getItems().length > 1) From fe0d3cad36b1bf1d6c85d2954f37da5417f154ed Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 12:25:02 -0700 Subject: [PATCH 101/160] Remove multiple edit session handling from Editor --- spec/app/editor-spec.coffee | 217 ++++++++++++------------------------ src/app/editor.coffee | 163 ++++----------------------- 2 files changed, 91 insertions(+), 289 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index a5cf751cb..0f9334ade 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -9,33 +9,41 @@ _ = require 'underscore' fs = require 'fs' describe "Editor", -> - [buffer, editor, cachedLineHeight] = [] + [buffer, editor, editSession, cachedLineHeight, cachedCharWidth] = [] beforeEach -> - editor = new Editor(project.buildEditSession('sample.js')) - buffer = editor.getBuffer() + 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(@charWidth * widthInChars) if widthInChars + this.width(getCharWidth() * widthInChars) if widthInChars $('#jasmine-content').append(this) - getLineHeight = -> return cachedLineHeight if cachedLineHeight? + calcDimensions() + cachedLineHeight + + getCharWidth = -> + return cachedCharWidth if cachedCharWidth? + calcDimensions() + cachedCharWidth + + calcDimensions = -> editorForMeasurement = new Editor(editSession: project.buildEditSession('sample.js')) editorForMeasurement.attachToDom() cachedLineHeight = editorForMeasurement.lineHeight + cachedCharWidth = editorForMeasurement.charWidth editorForMeasurement.remove() - cachedLineHeight 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 "when the editor is attached to the dom", -> it "calculates line height and char width and updates the pixel position of the cursor", -> @@ -92,147 +100,67 @@ 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.buildEditSession(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 + expect(editor.activeEditSession.destroyed).toBeTruthy() describe ".edit(editSession)", -> - otherEditSession = null + [newEditSession, newBuffer] = [] beforeEach -> - otherEditSession = project.buildEditSession() + 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 + console.log editor.getCursorView().css('left') + 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' - - describe "switching edit sessions", -> - [session0, session1, session2] = [] - - beforeEach -> - session0 = editor.activeEditSession - - editor.edit(project.buildEditSession('sample.txt')) - session1 = editor.activeEditSession - - editor.edit(project.buildEditSession('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.buildEditSession(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 + runs -> + expect(atom.confirm).toHaveBeenCalled() describe ".save()", -> describe "when the current buffer has a path", -> @@ -1817,11 +1745,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.buildEditSession(null)) + emptyEditSession = fixturesProject.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", -> @@ -2040,14 +1971,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.buildEditSession('sample.txt')) - editor.edit(project.buildEditSession('two-hundred.txt')) - editor.edit(project.buildEditSession()) - 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() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index d2ece3c63..bb7ad1ee1 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -49,8 +49,6 @@ class Editor extends View lineCache: null isFocused: false activeEditSession: null - closedEditSessions: null - editSessions: null attached: false lineOverdraw: 10 pendingChanges: null @@ -59,10 +57,7 @@ class Editor extends View 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 = new Editor(mini: state.mini, editSession: deserialize(state.editSession)) editor.isFocused = state.isFocused editor @@ -70,7 +65,7 @@ class Editor extends View if editSessionOrOptions instanceof EditSession editSession = editSessionOrOptions else - {editSession, @mini, deserializing} = (editSessionOrOptions ? {}) + {editSession, @mini} = (editSessionOrOptions ? {}) requireStylesheet 'editor.css' @@ -81,8 +76,6 @@ class Editor extends View @handleEvents() @cursorViews = [] @selectionViews = [] - @editSessions = [] - @closedEditSessions = [] @pendingChanges = [] @newCursors = [] @newSelections = [] @@ -96,14 +89,13 @@ 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") + else + throw new Error("Must supply an EditSession or mini: true") serialize: -> @saveScrollPositionForActiveEditSession() deserializer: "Editor" - editSessions: @editSessions.map (session) -> session.serialize() - activeEditSessionIndex: @getActiveEditSessionIndex() + editSession: @activeEditSession.serialize() isFocused: @isFocused copy: -> @@ -171,20 +163,9 @@ class Editor extends View 'editor:fold-current-row': @foldCurrentRow 'editor:unfold-current-row': @unfoldCurrentRow 'editor:fold-selection': @foldSelection - '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: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 @@ -434,10 +415,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 @@ -479,92 +457,17 @@ class Editor extends View @trigger 'editor:attached', [this] edit: (editSession) -> - index = @editSessions.indexOf(editSession) - index = @pushEditSession(editSession) if index == -1 - @setActiveEditSessionIndex(index) - - getModel: -> - @activeEditSession - - setModel: (editSession) -> - @edit(editSession) - - 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", => @@ -575,11 +478,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( @@ -589,30 +499,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() @@ -819,17 +705,10 @@ class Editor extends View 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] From fab3b4564ece9d634fb0154a34d7e87379199907 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 12:36:27 -0700 Subject: [PATCH 102/160] Add Pane.moveItemToPane specs. Fix bug moving the last edit session. --- spec/app/pane-spec.coffee | 25 +++++++++++++++++++++++++ src/app/editor.coffee | 2 +- src/app/pane.coffee | 4 +++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index bf2420eaa..d680624f2 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -153,6 +153,31 @@ describe "Pane", -> 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") diff --git a/src/app/editor.coffee b/src/app/editor.coffee index bb7ad1ee1..9819aecea 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -705,7 +705,7 @@ class Editor extends View afterRemove: -> @removed = true - @activeEditSession.destroy() + @activeEditSession?.destroy() $(window).off(".editor-#{@id}") $(document).off(".editor-#{@id}") diff --git a/src/app/pane.coffee b/src/app/pane.coffee index ba4488c6d..e88ccb7c6 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -128,7 +128,9 @@ class Pane extends View viewClass = item.getViewClass() otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass unless otherItemsForView.length - @viewsByClassName[viewClass.name]?.remove() + view = @viewsByClassName[viewClass.name] + view?.setModel(null) + view?.remove() delete @viewsByClassName[viewClass.name] viewForItem: (item) -> From 2bfc73afaaac5895016933c6bab0dc134bc14087 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 14:15:38 -0700 Subject: [PATCH 103/160] These should have been renamed w/ currentView/Item -> activeView/Item --- spec/app/root-view-spec.coffee | 16 ++++++++-------- src/app/editor.coffee | 8 ++++---- src/app/pane-container.coffee | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index cc56ee87e..aeede0fca 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -217,15 +217,15 @@ describe "RootView", -> 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 "opens and returns an edit session for an empty buffer in a new editor", -> editSession = rootView.open() - expect(rootView.getActivePane().currentItem).toBe editSession + expect(rootView.getActivePane().activeItem).toBe editSession expect(editSession.getPath()).toBeUndefined() describe "when called with a path", -> it "opens a buffer with the given path in a new editor", -> editSession = rootView.open('b') - expect(rootView.getActivePane().currentItem).toBe editSession + expect(rootView.getActivePane().activeItem).toBe editSession expect(editSession.getPath()).toBe require.resolve('fixtures/dir/b') describe "when there is an active pane", -> @@ -238,26 +238,26 @@ describe "RootView", -> it "opens an edit session with an empty buffer in the active pane", -> editSession = rootView.open() expect(activePane.getItems().length).toBe initialItemCount + 1 - expect(activePane.currentItem).toBe editSession + expect(activePane.activeItem).toBe editSession expect(editSession.getPath()).toBeUndefined() describe "when called with a path", -> 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.currentItem + previousEditSession = activePane.activeItem editSession = rootView.open('b') - expect(activePane.currentItem).toBe editSession + expect(activePane.activeItem).toBe editSession editSession = rootView.open('a') expect(editSession).not.toBe previousEditSession - expect(activePane.currentItem).toBe editSession + expect(activePane.activeItem).toBe editSession 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.currentItem).toBe editSession + expect(activePane.activeItem).toBe editSession describe ".saveAll()", -> it "saves all open editors", -> diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 9819aecea..1b4e8fe28 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -672,16 +672,16 @@ class Editor extends View @requestDisplayUpdate() splitLeft: (editSession) -> - @pane()?.splitLeft().currentView + @pane()?.splitLeft().activeView splitRight: (editSession) -> - @pane()?.splitRight().currentView + @pane()?.splitRight().activeView splitUp: (editSession) -> - @pane()?.splitUp().currentView + @pane()?.splitUp().activeView splitDown: (editSession) -> - @pane()?.splitDown().currentView + @pane()?.splitDown().activeView pane: -> @closest('.pane').view() diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 1e179c085..12c88cc09 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -58,10 +58,10 @@ class PaneContainer extends View @find('.pane.active').view() ? @find('.pane:first').view() getActivePaneItem: -> - @getActivePane()?.currentItem + @getActivePane()?.activeItem getActiveView: -> - @getActivePane()?.currentView + @getActivePane()?.activeView adjustPaneDimensions: -> if root = @getRoot() From 279ebc095886827c8d3f4aa56e4f89af6142bfab Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 14:23:33 -0700 Subject: [PATCH 104/160] Make RootView.getModifiedBuffers work w/ new system Eventually, this should probably become getModifiedPaneItems so that all kinds of items are given an opportunity to participate in the saving system. --- src/app/root-view.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 83bedbf86..1a0b59a44 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -11,6 +11,7 @@ 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 @@ -139,10 +140,9 @@ class RootView extends View 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: -> From a2ddd10d313c176312345d6d0e12a691afab1ef4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 15:25:16 -0700 Subject: [PATCH 105/160] Get fuzzy-finder specs passing w/ new panes Could still probably use some cleanup and I'm not sure everything is working correctly with regards to focus. --- src/app/root-view.coffee | 1 + .../fuzzy-finder/lib/fuzzy-finder-view.coffee | 33 ++-- .../spec/fuzzy-finder-spec.coffee | 171 +++++++++--------- 3 files changed, 97 insertions(+), 108 deletions(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 1a0b59a44..cdb14c3dd 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -89,6 +89,7 @@ class RootView extends View @remove() open: (path, options = {}) -> + path = project.resolve(path) if path? if activePane = @getActivePane() if editSession = activePane.itemForPath(path) activePane.showItem(editSession) diff --git a/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee index 480ecab33..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.buildEditSession(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 469b0fe96..423827379 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,9 +72,9 @@ 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') @@ -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", -> @@ -156,24 +147,21 @@ describe 'FuzzyFinder', -> 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.buildEditSession()) - 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,15 +170,15 @@ 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", -> + describe "when the active pane has an item for the selected path", -> + it "switches to the item for the selected path", -> expectedPath = fixturesProject.resolve('sample.txt') finderView.confirmed('sample.txt') @@ -199,21 +187,20 @@ 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') 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] = [] @@ -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') From 5b0f5727dc0435293df3664121bcd471012c8c43 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 15:37:59 -0700 Subject: [PATCH 106/160] Fix GFM grammar spec --- src/packages/gfm.tmbundle/spec/gfm-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/gfm.tmbundle/spec/gfm-spec.coffee b/src/packages/gfm.tmbundle/spec/gfm-spec.coffee index db307ab65..8f07e640e 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 = fixturesProject.buildEditSession('gfm.md') editSession.insertNewlineBelow() expect(editSession.buffer.lineForRow(1)).toBe ' ' From 2790e5d12b2f946b264ed32ff49cdf5add6358bb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 15:38:12 -0700 Subject: [PATCH 107/160] Fix package generator spec --- .../package-generator/spec/package-generator-spec.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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") From 8dc3afbccedab36fe7125152093e33ccebd06bf3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 15:38:20 -0700 Subject: [PATCH 108/160] Fix snippets spec --- src/packages/snippets/spec/snippets-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/snippets/spec/snippets-spec.coffee b/src/packages/snippets/spec/snippets-spec.coffee index c6e997d35..45701ccf3 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() From 2b53655934bddefdd9223a230c5fceffc99a9ac4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 15:38:43 -0700 Subject: [PATCH 109/160] Fix status bar spec --- src/packages/status-bar/spec/status-bar-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/status-bar/spec/status-bar-spec.coffee b/src/packages/status-bar/spec/status-bar-spec.coffee index f2d73b07b..8f22d6d31 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() From 7145136cd9b58e21a651e58331826828555d9752 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 15:39:27 -0700 Subject: [PATCH 110/160] Fix symbols view Makes a lot of assumptions about getActiveView being an editor. We'll need to revisit this. --- .../symbols-view/lib/symbols-view.coffee | 6 ++--- .../spec/symbols-view-spec.coffee | 22 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) 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() From 31f7d6669f060639dc5090b32843365a596e1aff Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 27 Feb 2013 16:20:33 -0700 Subject: [PATCH 111/160] Use project global in project spec --- spec/app/project-spec.coffee | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index db5eaaf92..ad5b01151 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -3,12 +3,8 @@ fs = require 'fs' _ = require 'underscore' describe "Project", -> - project = null beforeEach -> - project = new Project(require.resolve('fixtures/dir')) - - afterEach -> - project.destroy() + project.setPath(project.resolve('dir')) describe "when editSession is destroyed", -> it "removes edit session and calls destroy on buffer (if buffer is not referenced by other edit sessions)", -> From 9a93694a4c7e80fe179fb0a68a75825b053aa7d7 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 16:20:43 -0700 Subject: [PATCH 112/160] :lipstick: --- spec/app/project-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index ad5b01151..143d83d8c 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -6,7 +6,7 @@ describe "Project", -> beforeEach -> project.setPath(project.resolve('dir')) - 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.buildEditSession("a") anotherEditSession = project.buildEditSession("a") From 5291924bcc1d22c40d8ff3321a4285b467215d76 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 16:21:42 -0700 Subject: [PATCH 113/160] Set the project path when its first edit session is saved --- spec/app/project-spec.coffee | 10 ++++++++++ src/app/edit-session.coffee | 1 + 2 files changed, 11 insertions(+) diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index 143d83d8c..136e61a85 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -20,6 +20,16 @@ describe "Project", -> anotherEditSession.destroy() expect(project.editSessions.length).toBe 0 + 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 -> diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index f6e6b344a..1c065611d 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -44,6 +44,7 @@ class EditSession @buffer.retain() @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" From 6157a75868ad5d3d167632febe3d0c0c782e6fe0 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 16:54:06 -0700 Subject: [PATCH 114/160] Spec changeFocus option of RootView.open and default it to true --- spec/app/root-view-spec.coffee | 30 +++++++++++++++++++++++++----- src/app/root-view.coffee | 3 ++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index aeede0fca..450f80a36 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -4,6 +4,7 @@ Project = require 'project' RootView = require 'root-view' Buffer = require 'buffer' Editor = require 'editor' +Pane = require 'pane' {View, $$} = require 'space-pen' describe "RootView", -> @@ -213,33 +214,43 @@ describe "RootView", -> describe ".open(path, options)", -> describe "when there is no active pane", -> beforeEach -> + spyOn(Pane.prototype, 'focus') rootView.getActivePane().remove() expect(rootView.getActivePane()).toBeUndefined() describe "when called with no path", -> - it "opens and 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.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.getActivePane().activeItem).toBe editSession expect(editSession.getPath()).toBe require.resolve('fixtures/dir/b') + expect(rootView.getActivePane().focus).toHaveBeenCalled() + + 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 -> activePane = rootView.getActivePane() + spyOn(activePane, 'focus') initialItemCount = activePane.getItems().length describe "when called with no path", -> - it "opens an edit session with an empty buffer in the active pane", -> + it "opens an edit session with an empty buffer as an item on the active pane and focuses it", -> editSession = rootView.open() 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", -> describe "when the active pane already has an edit session item for the path being opened", -> @@ -248,16 +259,25 @@ describe "RootView", -> editSession = rootView.open('b') expect(activePane.activeItem).toBe editSession - - editSession = rootView.open('a') expect(editSession).not.toBe previousEditSession + + editSession = rootView.open(previousEditSession.getPath()) + expect(editSession).toBe previousEditSession expect(activePane.activeItem).toBe editSession + expect(activePane.focus).toHaveBeenCalled() + 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 changeFocus option is false", -> + it "does not focus the active pane", -> + editSession = rootView.open('b', changeFocus: false) + expect(activePane.focus).not.toHaveBeenCalled() describe ".saveAll()", -> it "saves all open editors", -> diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index cdb14c3dd..094e9bf0b 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -89,6 +89,7 @@ class RootView extends View @remove() open: (path, options = {}) -> + changeFocus = options.changeFocus ? true path = project.resolve(path) if path? if activePane = @getActivePane() if editSession = activePane.itemForPath(path) @@ -101,7 +102,7 @@ class RootView extends View activePane = new Pane(editSession) @panes.append(activePane) - activePane.focus() if options.changeFocus + activePane.focus() if changeFocus editSession editorFocused: (editor) -> From 3c9793d80384ab65e001dac0e184a02d3d4b63d8 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 16:57:05 -0700 Subject: [PATCH 115/160] Fix TreeView specs --- src/packages/tree-view/lib/tree-view.coffee | 9 +-- src/packages/tree-view/lib/tree.coffee | 2 +- .../tree-view/spec/tree-view-spec.coffee | 55 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) 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] = [] From 80859b0a9fffcb3b77114c11572378530acf6128 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 17:04:11 -0700 Subject: [PATCH 116/160] Fix CSS for .file-name -> .title class rename --- static/tabs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/tabs.css b/static/tabs.css index 29133287a..dc1aa6988 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; From 52b649dca50d7f8efd74e3a448717e3005aac799 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:17:52 -0700 Subject: [PATCH 117/160] Preserve focus when switching between pane items If the pane is currently focused, when showing a view associated with a new item, focus that view. --- spec/app/pane-spec.coffee | 9 +++++++++ src/app/pane.coffee | 2 ++ 2 files changed, 11 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index d680624f2..78ae84e22 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -44,6 +44,15 @@ describe "Pane", -> 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 -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index e88ccb7c6..0f5e9d96b 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -73,11 +73,13 @@ class Pane extends View showItem: (item) -> return if item is @activeItem + isFocused = @is(':has(:focus)') @addItem(item) 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] From ae95c04bbce05ce703dabfc71c6ab3e8704ded96 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:21:24 -0700 Subject: [PATCH 118/160] Focus next pane when removing the last pane item of a focused pane Previously, removing the last pane item also ruined our ability to determine if the pane had focus. Now, if we're removing the last item, we instead just go ahead and remove the entire pane. Remove contains logic to switch focus to the next pane if its active view is focused, which works as intended if we leave the active view in place. --- spec/app/pane-spec.coffee | 16 +++++++++++++--- src/app/pane.coffee | 23 +++++++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 78ae84e22..987258ae7 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -121,9 +121,19 @@ describe "Pane", -> expect(itemRemovedHandler).toHaveBeenCalled() expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 1] - it "removes the pane when its last item is removed", -> - pane.removeItem(item) for item in pane.getItems() - expect(pane.hasParent()).toBeFalsy() + 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", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 0f5e9d96b..0c4886f76 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -105,10 +105,8 @@ class Pane extends View @showNextItem() if item is @activeItem and @items.length > 1 _.remove(@items, item) - @cleanupItemView(item) @trigger 'pane:item-removed', [item, index] - @remove() unless @items.length moveItem: (item, newIndex) -> oldIndex = @items.indexOf(item) @@ -125,16 +123,20 @@ class Pane extends View cleanupItemView: (item) -> if item instanceof $ - item.remove() + viewToRemove = item else viewClass = item.getViewClass() otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass unless otherItemsForView.length - view = @viewsByClassName[viewClass.name] - view?.setModel(null) - view?.remove() + 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 @@ -197,19 +199,24 @@ class Pane extends View remove: (selector, keepData) -> return super if keepData + # find parent elements before removing from dom container = @getContainer() parentAxis = @parent('.row, .column') + if @is(':has(:focus)') - rootView?.focus() unless container.focusNextPane() + container.focusNextPane() or rootView?.focus() else if @isActive() container.makeNextPaneActive() super if parentAxis.children().length == 1 - sibling = parentAxis.children().detach() + sibling = parentAxis.children() + siblingFocused = sibling.is(':has(:focus)') + sibling.detach() parentAxis.replaceWith(sibling) + sibling.focus() if siblingFocused container.adjustPaneDimensions() container.trigger 'pane:removed', [this] From 3bf31e440db19086b54f5e46b57f06990140ee35 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:22:21 -0700 Subject: [PATCH 119/160] Remove code for setting the active editor from root view Supplanted by "active pane" --- src/app/editor.coffee | 1 - src/app/root-view.coffee | 20 -------------------- 2 files changed, 21 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 1b4e8fe28..2cbe1bf00 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -346,7 +346,6 @@ class Editor extends View false @hiddenInput.on 'focus', => - rootView?.editorFocused(this) @isFocused = true @addClass 'is-focused' diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 094e9bf0b..1e4d349db 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -105,26 +105,6 @@ class RootView extends View activePane.focus() if changeFocus 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() - - updateTitle: -> if projectPath = project.getPath() if item = @getActivePaneItem() From c1e226d6a388ca396236c752ad355d6c7e0aecd2 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:22:32 -0700 Subject: [PATCH 120/160] Kill unused event --- src/app/editor.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 2cbe1bf00..d82734fdc 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -171,7 +171,6 @@ class Editor extends View '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 From 15144514bb86e78404f23f6edadd18da01ec5420 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:26:21 -0700 Subject: [PATCH 121/160] Don't update editor's display if its edit session is null or destroyed --- src/app/editor.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index d82734fdc..736d745f9 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -811,7 +811,8 @@ class Editor extends View @pendingDisplayUpdate = false updateDisplay: (options={}) -> - return unless @attached + return unless @attached and @activeEditSession + return if @activeEditSession.destroyed @updateRenderedLines() @highlightCursorLine() @updateCursorViews() From 5d9e20afa4899d41cbb1b01741e58991fe844c30 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:26:46 -0700 Subject: [PATCH 122/160] Make Editor.getPath return null if edit session is null --- src/app/editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 736d745f9..7cf99e066 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -321,7 +321,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) From 5bba4cd9f7f30d193040364b5f2687d502f95935 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 27 Feb 2013 18:29:08 -0700 Subject: [PATCH 123/160] Kill dead tab view code --- src/packages/tabs/lib/tab-view.coffee | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/packages/tabs/lib/tab-view.coffee b/src/packages/tabs/lib/tab-view.coffee index ab75a9c03..27eceb727 100644 --- a/src/packages/tabs/lib/tab-view.coffee +++ b/src/packages/tabs/lib/tab-view.coffee @@ -12,15 +12,6 @@ class TabView extends View initialize: (@item, @pane) -> @item.on? 'title-changed', => @updateTitle() @updateTitle() -# @buffer = @editSession.buffer -# @subscribe @buffer, 'path-changed', => @updateFileName() -# @subscribe @buffer, 'contents-modified', => @updateModifiedStatus() -# @subscribe @buffer, 'saved', => @updateModifiedStatus() -# @subscribe @buffer, 'git-status-changed', => @updateModifiedStatus() -# @subscribe @editor, 'editor:edit-session-added', => @updateFileName() -# @subscribe @editor, 'editor:edit-session-removed', => @updateFileName() -# @updateFileName() -# @updateModifiedStatus() updateTitle: -> return if @updatingTitle From 29566d55c61fe8d46910333b722e127a37fae1af Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 07:02:37 -0700 Subject: [PATCH 124/160] Scope split-view bindings on body so fuzzy-finder can open in splits Previously, they were scoped on .pane, but fuzzy-finder isn't inside a pane and still needs to be able to respond to split events. --- src/app/keymaps/atom.cson | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index d3427fb45..4dbe961a1 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -30,16 +30,17 @@ 'ctrl-tab': 'window:focus-next-pane' 'ctrl-meta-f': 'window:toggle-full-screen' -'.pane': - 'meta-{': 'pane:show-previous-item' - 'meta-}': 'pane:show-next-item' - 'alt-meta-left': 'pane:show-previous-item' - 'alt-meta-right': 'pane:show-next-item' 'ctrl-|': 'pane:split-right' 'ctrl-w v': 'pane:split-right' 'ctrl--': 'pane:split-down' 'ctrl-w s': 'pane:split-down' +'.pane': + 'meta-{': 'pane:show-previous-item' + 'meta-}': 'pane:show-next-item' + 'alt-meta-left': 'pane:show-previous-item' + 'alt-meta-right': 'pane:show-next-item' + '.tool-panel': 'meta-escape': 'tool-panel:unfocus' 'escape': 'core:close' From 24c9f11cc96de1f064821e5112822a9a8f34ffa4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 08:18:57 -0700 Subject: [PATCH 125/160] Trigger modified-status-changed on buffers/edit sessions --- spec/app/buffer-spec.coffee | 57 ++++++++++++++++++++++++-- src/app/buffer-change-operation.coffee | 2 +- src/app/buffer.coffee | 9 +++- src/app/edit-session.coffee | 1 + 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index 3510d46f4..77c6845f4 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -180,23 +180,72 @@ describe 'Buffer', -> waitsFor 'change event', -> changeHandler.callCount > 0 - describe ".isModified()", -> - it "returns true when user changes buffer", -> + describe "modified status", -> + it "reports a modified status of true 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 a modified status of 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 a modified status of 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") expect(buffer.isModified()).toBe true + modifiedHandler.reset() buffer.save() + expect(modifiedHandler).toHaveBeenCalledWith(false) + expect(buffer.isModified()).toBe false + + it "reports a modified status of 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") + expect(buffer.isModified()).toBe true + modifiedHandler.reset() + + buffer.reload() + expect(modifiedHandler).toHaveBeenCalledWith(false) expect(buffer.isModified()).toBe false it "returns false for an empty buffer with no path", -> diff --git a/src/app/buffer-change-operation.coffee b/src/app/buffer-change-operation.coffee index b3ffda35e..a27539c77 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.scheduleModifiedStatusChangedEvent() @resumeMarkerObservation() @buffer.trigger 'markers-updated' diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 092f0d797..4958c03b9 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -69,6 +69,7 @@ class Buffer @file.on "removed", => @updateCachedDiskContents() + @trigger "modified-status-changed", @isModified() @trigger "contents-modified", {differsFromDisk: true} @file.on "moved", => @@ -78,6 +79,7 @@ class Buffer @trigger 'will-reload' @updateCachedDiskContents() @setText(@cachedDiskContents) + @trigger 'modified-status-changed', false @trigger 'reloaded' updateCachedDiskContents: -> @@ -252,6 +254,7 @@ class Buffer @setPath(path) @cachedDiskContents = @getText() @file.write(@getText()) + @trigger 'modified-status-changed', false @trigger 'saved' isModified: -> @@ -424,10 +427,14 @@ class Buffer return unless path git?.checkoutHead(path) - scheduleStoppedChangingEvent: -> + scheduleModifiedStatusChangedEvent: -> clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout stoppedChangingCallback = => @stoppedChangingTimeout = null + modifiedStatus = @isModified() + unless modifiedStatus is @previousModifiedStatus + @previousModifiedStatus = modifiedStatus + @trigger 'modified-status-changed', modifiedStatus @trigger 'contents-modified', {differsFromDisk: @isModified()} @stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 1c065611d..46c9094f3 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -49,6 +49,7 @@ class EditSession @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() From a1dc2cfc2d89218a8a0b4aafa1f897550db76575 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 08:20:11 -0700 Subject: [PATCH 126/160] Tabs indicate when their items are modified --- src/packages/tabs/lib/tab-view.coffee | 8 +++++--- src/packages/tabs/spec/tabs-spec.coffee | 24 +++++++++++++++++++++++- static/tabs.css | 8 ++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/packages/tabs/lib/tab-view.coffee b/src/packages/tabs/lib/tab-view.coffee index 27eceb727..9bfee397e 100644 --- a/src/packages/tabs/lib/tab-view.coffee +++ b/src/packages/tabs/lib/tab-view.coffee @@ -11,7 +11,9 @@ class TabView extends View initialize: (@item, @pane) -> @item.on? 'title-changed', => @updateTitle() + @item.on? 'modified-status-changed', => @updateModifiedStatus() @updateTitle() + @updateModifiedStatus() updateTitle: -> return if @updatingTitle @@ -32,11 +34,11 @@ class TabView extends View @siblings('.tab').views() updateModifiedStatus: -> - if @buffer.isModified() - @toggleClass('file-modified') unless @isModified + if @item.isModified?() + @addClass('modified') unless @isModified @isModified = true else - @removeClass('file-modified') if @isModified + @removeClass('modified') if @isModified @isModified = false updateFileName: -> diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index 7a70d19f9..d8d32e390 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -76,6 +76,12 @@ describe "TabBarView", -> 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) @@ -108,7 +114,7 @@ describe "TabBarView", -> editSession1.buffer.setPath('/this/is-a/test.txt') expect(tabBar.tabForItem(editSession1)).toHaveText 'test.txt' - describe "when two tabs have the same file name", -> + 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" @@ -126,6 +132,22 @@ describe "TabBarView", -> expect(tabBar.tabForItem(item1)).toHaveText "Grumpy Old Man" expect(tabBar.tabForItem(item2)).toHaveText "Old Man" + 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' + + editSession1.insertText('x') + advanceClock(editSession1.buffer.stoppedChangingDelay) + expect(editSession1.isModified()).toBeTruthy() + expect(tab).toHaveClass 'modified' + + editSession1.undo() + advanceClock(editSession1.buffer.stoppedChangingDelay) + expect(editSession1.isModified()).toBeFalsy() + expect(tab).not.toHaveClass 'modified' + 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"] diff --git a/static/tabs.css b/static/tabs.css index dc1aa6988..bb262bba1 100644 --- a/static/tabs.css +++ b/static/tabs.css @@ -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; } From 298a963148f2961b3e6a2d883515cb501ba7593d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 08:35:32 -0700 Subject: [PATCH 127/160] Rework Buffer's 'contents-modified' event This event now fires whenever the content of the buffer changes (after a rate-limiting delay) with a single boolean indicating the modified status of the buffer. There's now a separate event called 'modified-status-changed' to indicate events that change the boolean value of the isModified method, so we don't need to fire 'contents-modified' when the underlying file is deleted for instance. --- spec/app/buffer-spec.coffee | 67 +++++-------------- src/app/buffer-change-operation.coffee | 2 +- src/app/buffer.coffee | 5 +- .../status-bar/lib/status-bar-view.coffee | 6 +- 4 files changed, 24 insertions(+), 56 deletions(-) diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index 77c6845f4..0b69b2fa1 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -1105,61 +1105,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/src/app/buffer-change-operation.coffee b/src/app/buffer-change-operation.coffee index a27539c77..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.scheduleModifiedStatusChangedEvent() + @buffer.scheduleModifiedEvents() @resumeMarkerObservation() @buffer.trigger 'markers-updated' diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 4958c03b9..e8ccc91db 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -70,7 +70,6 @@ class Buffer @file.on "removed", => @updateCachedDiskContents() @trigger "modified-status-changed", @isModified() - @trigger "contents-modified", {differsFromDisk: true} @file.on "moved", => @trigger "path-changed", this @@ -427,15 +426,15 @@ class Buffer return unless path git?.checkoutHead(path) - scheduleModifiedStatusChangedEvent: -> + scheduleModifiedEvents: -> clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout stoppedChangingCallback = => @stoppedChangingTimeout = null modifiedStatus = @isModified() + @trigger 'contents-modified', modifiedStatus unless modifiedStatus is @previousModifiedStatus @previousModifiedStatus = modifiedStatus @trigger 'modified-status-changed', modifiedStatus - @trigger 'contents-modified', {differsFromDisk: @isModified()} @stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay) fileExists: -> 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 From 43b41e9ed96f96a5ad2fe9636e69f1977dcfedfe Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 08:53:58 -0700 Subject: [PATCH 128/160] Fix spell check spec --- src/packages/spell-check/spec/spell-check-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/spell-check/spec/spell-check-spec.coffee b/src/packages/spell-check/spec/spell-check-spec.coffee index bf3fba917..b860e650c 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.") From d5654cf0df5cfc73d8e0ec71f04b08558168e8ec Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 09:33:55 -0700 Subject: [PATCH 129/160] :lipstick: --- spec/app/editor-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 0f9334ade..bf7140c65 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -271,7 +271,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" From 9f7b804a6ca35a3e93c8e2aa4f9f1907cba6a92b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 09:46:34 -0700 Subject: [PATCH 130/160] Panes prompt to save modified items before destroying them --- spec/app/pane-spec.coffee | 68 ++++++++++++++++++++++++++++++++++++--- src/app/editor.coffee | 11 ------- src/app/pane.coffee | 29 +++++++++++++++-- 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 987258ae7..83e999bf9 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -98,10 +98,70 @@ describe "Pane", -> expect(pane.activeView).toBe view2 describe ".destroyItem(item)", -> - it "removes the item and destroys it if it's a model", -> - pane.destroyItem(editSession2) - expect(pane.getItems().indexOf(editSession2)).toBe -1 - expect(editSession2.destroyed).toBeTruthy() + 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 path", -> + 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 path", -> + it "presents a save-as dialog, then saves the item with the given path 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", -> diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 7cf99e066..85cc1298a 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -684,17 +684,6 @@ class Editor extends View pane: -> @closest('.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 - ) - remove: (selector, keepData) -> return super if keepData or @removed @trigger 'editor:will-be-removed' diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 0c4886f76..6829b33df 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -96,8 +96,33 @@ class Pane extends View false destroyItem: (item) -> - @removeItem(item) - item.destroy?() + reallyDestroyItem = => + @removeItem(item) + item.destroy?() + + if item.isModified?() + @promptToSaveItem(item, reallyDestroyItem) + else + reallyDestroyItem() + + promptToSaveItem: (item, nextAction) -> + path = item.getPath() + 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 + ) + + saveItem: (item, nextAction) -> + if item.getPath() + item.save() + nextAction() + else + atom.showSaveDialog (path) -> + item.saveAs(path) + nextAction() removeItem: (item) -> index = @items.indexOf(item) From 54fc9efdcbaa959eb27c236f93ffb7770bfd63b2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 10:11:06 -0700 Subject: [PATCH 131/160] Eliminate fixturesProject global. Use project global instead. --- spec/app/display-buffer-spec.coffee | 4 ++-- spec/app/edit-session-spec.coffee | 15 ++++++--------- spec/app/editor-spec.coffee | 2 +- spec/app/language-mode-spec.coffee | 10 +++++----- spec/app/text-mate-grammar-spec.coffee | 2 +- spec/app/theme-spec.coffee | 6 +++--- spec/app/tokenized-buffer-spec.coffee | 12 ++++++------ spec/spec-helper.coffee | 4 ++-- .../autocomplete/spec/autocomplete-spec.coffee | 2 +- .../spec/command-interpreter-spec.coffee | 9 ++++----- .../fuzzy-finder/spec/fuzzy-finder-spec.coffee | 6 +++--- src/packages/gfm.tmbundle/spec/gfm-spec.coffee | 2 +- src/packages/snippets/spec/snippets-spec.coffee | 2 +- 13 files changed, 36 insertions(+), 40 deletions(-) diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index de7404eee..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.buildEditSession('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.buildEditSession('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 4c1bdc4a0..1a4d3754d 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -9,13 +9,10 @@ describe "EditSession", -> buffer.setText(buffer.getText().replace(/[ ]{2}/g, "\t")) beforeEach -> - editSession = fixturesProject.buildEditSession('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", -> @@ -1736,7 +1733,7 @@ describe "EditSession", -> it "does not explode if the current language mode has no comment regex", -> editSession.destroy() - editSession = fixturesProject.buildEditSession(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) {" @@ -1814,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.buildEditSession(editSession.getPath()) + otherEditSession = project.buildEditSession(editSession.getPath()) otherEditSession.setSelectedBufferRange([[2, 2], [3, 3]]) otherEditSession.delete() @@ -2007,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.buildEditSession('sample.js', softTabs: false) + editSession = project.buildEditSession('sample.js', softTabs: false) expect(editSession.softTabs).toBeTruthy() - editSession = fixturesProject.buildEditSession('sample-with-tabs.coffee', softTabs: true) + editSession = project.buildEditSession('sample-with-tabs.coffee', softTabs: true) expect(editSession.softTabs).toBeFalsy() - editSession = fixturesProject.buildEditSession(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 bf7140c65..ef747e6e2 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1745,7 +1745,7 @@ 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", -> - emptyEditSession = fixturesProject.buildEditSession(null) + emptyEditSession = project.buildEditSession(null) editor.edit(emptyEditSession) expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1 diff --git a/spec/app/language-mode-spec.coffee b/spec/app/language-mode-spec.coffee index 1d53922f2..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.buildEditSession('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.buildEditSession('js', autoIndent: false) + jsEditSession = project.buildEditSession('js', autoIndent: false) expect(jsEditSession.languageMode.grammar.name).toBe "JavaScript" jsEditSession.destroy() describe "javascript", -> beforeEach -> - editSession = fixturesProject.buildEditSession('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.buildEditSession('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.buildEditSession('css.css', autoIndent: false) + editSession = project.buildEditSession('css.css', autoIndent: false) { buffer, languageMode } = editSession describe ".toggleLineCommentsForBufferRows(start, end)", -> 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 52e48cf58..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.buildEditSession('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.buildEditSession('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.buildEditSession('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.buildEditSession('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.buildEditSession('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.buildEditSession('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/spec-helper.coffee b/spec/spec-helper.coffee index 92c752871..fc93ecc61 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -30,8 +30,8 @@ jasmine.getEnv().defaultTimeoutInterval = 5000 beforeEach -> jQuery.fx.off = true - window.fixturesProject = new Project(require.resolve('fixtures')) - window.project = fixturesProject + + window.project = new Project(require.resolve('fixtures')) window.git = Git.open(fixturesProject.getPath()) window.project.on 'path-changed', -> window.git?.destroy() diff --git a/src/packages/autocomplete/spec/autocomplete-spec.coffee b/src/packages/autocomplete/spec/autocomplete-spec.coffee index d85b8b507..9bfa9af7f 100644 --- a/src/packages/autocomplete/spec/autocomplete-spec.coffee +++ b/src/packages/autocomplete/spec/autocomplete-spec.coffee @@ -40,7 +40,7 @@ describe "AutocompleteView", -> beforeEach -> window.rootView = new RootView - editor = new Editor(editSession: fixturesProject.buildEditSession('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/command-panel/spec/command-interpreter-spec.coffee b/src/packages/command-panel/spec/command-interpreter-spec.coffee index c9f590346..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.buildEditSession('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 diff --git a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee index 423827379..4530b8e9b 100644 --- a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee +++ b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee @@ -78,7 +78,7 @@ describe 'FuzzyFinder', -> 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 @@ -179,7 +179,7 @@ describe 'FuzzyFinder', -> describe "when the active pane has an item for the selected path", -> it "switches to the item for the selected path", -> - expectedPath = fixturesProject.resolve('sample.txt') + expectedPath = project.resolve('sample.txt') finderView.confirmed('sample.txt') expect(finderView.hasParent()).toBeFalsy() @@ -195,7 +195,7 @@ describe 'FuzzyFinder', -> expect(rootView.getActiveView()).toBe editor1 - expectedPath = fixturesProject.resolve('sample.txt') + expectedPath = project.resolve('sample.txt') finderView.confirmed('sample.txt') expect(finderView.hasParent()).toBeFalsy() diff --git a/src/packages/gfm.tmbundle/spec/gfm-spec.coffee b/src/packages/gfm.tmbundle/spec/gfm-spec.coffee index 8f07e640e..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.buildEditSession('gfm.md') + editSession = project.buildEditSession('gfm.md') editSession.insertNewlineBelow() expect(editSession.buffer.lineForRow(1)).toBe ' ' diff --git a/src/packages/snippets/spec/snippets-spec.coffee b/src/packages/snippets/spec/snippets-spec.coffee index 45701ccf3..b77b2d80b 100644 --- a/src/packages/snippets/spec/snippets-spec.coffee +++ b/src/packages/snippets/spec/snippets-spec.coffee @@ -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 From 6ae684d609dd4fa3d0bb5681d5f1d60a4b52c562 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 10:14:55 -0700 Subject: [PATCH 132/160] Kill commented specs that were used as a reminder --- spec/app/pane-spec.coffee | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 83e999bf9..7285ab778 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -503,23 +503,3 @@ describe "Pane", -> it "can serialize and deserialize the pane and all its serializable items", -> newPane = deserialize(pane.serialize()) expect(newPane.getItems()).toEqual [editSession1, editSession2] - -# This relates to confirming the closing of a tab -# -# 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() From 699e780e99429a2290ffd94c7ab17e85fef78dc4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 10:48:32 -0700 Subject: [PATCH 133/160] Handle save in panes with new 'core:save' event --- spec/app/pane-spec.coffee | 36 ++++++++++++++++++++++++++++++++++++ src/app/editor.coffee | 8 -------- src/app/keymaps/atom.cson | 1 + src/app/keymaps/editor.cson | 1 - src/app/pane.coffee | 12 ++++++++---- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 7285ab778..96973f95e 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -270,6 +270,42 @@ describe "Pane", -> expect(containerCloseHandler).not.toHaveBeenCalled() + describe "core:save", -> + describe "when the current item has a path", -> + 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 path", -> + 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 "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 diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 85cc1298a..f9b65b835 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -152,7 +152,6 @@ class Editor extends View 'core:select-down': @selectDown 'core:select-to-top': @selectToTop 'core:select-to-bottom': @selectToBottom - 'editor:save': @save 'editor:save-as': @saveAs 'editor:newline-below': @insertNewlineBelow 'editor:newline-above': @insertNewlineAbove @@ -611,13 +610,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 diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index 4dbe961a1..eb6b1628e 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -1,4 +1,5 @@ 'body': + 'meta-s': 'core:save' 'enter': 'core:confirm' 'escape': 'core:cancel' 'meta-w': 'core:close' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index e30e84e9b..fc8c859d4 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -2,7 +2,6 @@ 'meta-T': 'editor:undo-close-session' '.editor': - 'meta-s': 'editor:save' 'meta-S': 'editor:save-as' 'enter': 'editor:newline' 'meta-enter': 'editor:newline-below' diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 6829b33df..3295daa20 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -21,6 +21,7 @@ class Pane extends View @showItem(@items[0]) @command 'core:close', @destroyActiveItem + @command 'core:save', @saveActiveItem @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem @command 'pane:split-left', => @splitLeft() @@ -115,14 +116,17 @@ class Pane extends View "Don't Save", nextAction ) + saveActiveItem: => + @saveItem(@activeItem) + saveItem: (item, nextAction) -> - if item.getPath() + if item.getPath?() item.save() - nextAction() - else + nextAction?() + else if item.saveAs? atom.showSaveDialog (path) -> item.saveAs(path) - nextAction() + nextAction?() removeItem: (item) -> index = @items.indexOf(item) From 59a06acc0b72a21de47f88e0d715e2b13f389089 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 17:49:11 -0700 Subject: [PATCH 134/160] Fire 'modified-status-changed' events on changes after save/reload Buffer keeps state about the value with which it fired the last modified-status-changed event so that it doesn't fire it twice with the same boolean value. Every piece of code that triggers the event also needs to set this state, so now everything goes through the `triggerModifiedStatusChanged` method. --- spec/app/buffer-spec.coffee | 23 +++++++++++++++++++---- src/app/buffer.coffee | 15 +++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index 0b69b2fa1..84b58ddeb 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -181,7 +181,7 @@ describe 'Buffer', -> changeHandler.callCount > 0 describe "modified status", -> - it "reports a modified status of true after the user changes buffer", -> + 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 @@ -203,7 +203,7 @@ describe 'Buffer', -> advanceClock(buffer.stoppedChangingDelay) expect(modifiedHandler).toHaveBeenCalledWith(false) - it "reports a modified status of true after the underlying file is deleted", -> + 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') @@ -216,7 +216,7 @@ describe 'Buffer', -> waitsFor "modified status to change", -> modifiedHandler.callCount runs -> expect(buffer.isModified()).toBe true - it "reports a modified status of false after a modified buffer is saved", -> + it "reports the modified status changing to false after a modified buffer is saved", -> filePath = "/tmp/atom-tmp-file" fs.write(filePath, '') buffer.release() @@ -225,14 +225,22 @@ describe 'Buffer', -> 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() - it "reports a modified status of false after a modified buffer is reloaded", -> + 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() @@ -241,12 +249,19 @@ describe 'Buffer', -> 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() diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index e8ccc91db..00d0483e4 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -69,7 +69,7 @@ class Buffer @file.on "removed", => @updateCachedDiskContents() - @trigger "modified-status-changed", @isModified() + @triggerModifiedStatusChanged(@isModified()) @file.on "moved", => @trigger "path-changed", this @@ -78,7 +78,7 @@ class Buffer @trigger 'will-reload' @updateCachedDiskContents() @setText(@cachedDiskContents) - @trigger 'modified-status-changed', false + @triggerModifiedStatusChanged(false) @trigger 'reloaded' updateCachedDiskContents: -> @@ -253,7 +253,7 @@ class Buffer @setPath(path) @cachedDiskContents = @getText() @file.write(@getText()) - @trigger 'modified-status-changed', false + @triggerModifiedStatusChanged(false) @trigger 'saved' isModified: -> @@ -432,11 +432,14 @@ class Buffer @stoppedChangingTimeout = null modifiedStatus = @isModified() @trigger 'contents-modified', modifiedStatus - unless modifiedStatus is @previousModifiedStatus - @previousModifiedStatus = modifiedStatus - @trigger 'modified-status-changed', modifiedStatus + @triggerModifiedStatusChanged(modifiedStatus) @stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay) + triggerModifiedStatusChanged: (modifiedStatus) -> + return if modifiedStatus is @previousModifiedStatus + @previousModifiedStatus = modifiedStatus + @trigger 'modified-status-changed', modifiedStatus + fileExists: -> @file.exists() From 3f9ee08e767229cbcf87add6eebb225e6fc7d27e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 18:00:04 -0700 Subject: [PATCH 135/160] Handle save-as on pane. Replace 'editor:save-as' w/ 'core:save-as' Pane will only show the saveAs dialog if the item has a `saveAs` method. --- spec/app/pane-spec.coffee | 21 +++++++++++++++++++++ src/app/editor.coffee | 7 ------- src/app/keymaps/atom.cson | 1 + src/app/keymaps/editor.cson | 1 - src/app/pane.coffee | 13 +++++++++++-- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 96973f95e..d657e9b5c 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -306,6 +306,27 @@ describe "Pane", -> 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 diff --git a/src/app/editor.coffee b/src/app/editor.coffee index f9b65b835..0f900eb47 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -152,7 +152,6 @@ class Editor extends View 'core:select-down': @selectDown 'core:select-to-top': @selectToTop 'core:select-to-bottom': @selectToBottom - 'editor:save-as': @saveAs 'editor:newline-below': @insertNewlineBelow 'editor:newline-above': @insertNewlineAbove 'editor:toggle-soft-tabs': @toggleSoftTabs @@ -610,12 +609,6 @@ class Editor extends View @removeClass 'soft-wrap' $(window).off 'resize', @_setSoftWrapColumn - saveAs: (session=@activeEditSession, onSuccess) -> - atom.showSaveDialog (path) => - if path - session.saveAs(path) - onSuccess?() - autosave: -> @save() if @getPath()? diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index eb6b1628e..29e83880a 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -1,5 +1,6 @@ 'body': 'meta-s': 'core:save' + 'meta-S': 'core:save-as' 'enter': 'core:confirm' 'escape': 'core:cancel' 'meta-w': 'core:close' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index fc8c859d4..066385507 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -2,7 +2,6 @@ 'meta-T': 'editor:undo-close-session' '.editor': - 'meta-S': 'editor:save-as' 'enter': 'editor:newline' 'meta-enter': 'editor:newline-below' 'meta-shift-enter': 'editor:newline-above' diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 3295daa20..0696b24da 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -22,6 +22,7 @@ class Pane extends View @command 'core:close', @destroyActiveItem @command 'core:save', @saveActiveItem + @command 'core:save-as', @saveActiveItemAs @command 'pane:show-next-item', @showNextItem @command 'pane:show-previous-item', @showPreviousItem @command 'pane:split-left', => @splitLeft() @@ -119,12 +120,20 @@ class Pane extends View saveActiveItem: => @saveItem(@activeItem) + saveActiveItemAs: => + @saveItemAs(@activeItem) + saveItem: (item, nextAction) -> if item.getPath?() item.save() nextAction?() - else if item.saveAs? - atom.showSaveDialog (path) -> + else + @saveItemAs(item, nextAction) + + saveItemAs: (item, nextAction) -> + return unless item.saveAs? + atom.showSaveDialog (path) => + if path item.saveAs(path) nextAction?() From 685df18a3a919a4f775eb243eaba12fbcf36da2d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 28 Feb 2013 18:07:01 -0700 Subject: [PATCH 136/160] Fix breakages due to save method moving to Pane (except saveAll specs) --- spec/app/editor-spec.coffee | 51 +------------------ src/app/editor.coffee | 8 +-- .../status-bar/spec/status-bar-spec.coffee | 2 +- .../strip-trailing-whitespace-spec.coffee | 12 ++--- 4 files changed, 12 insertions(+), 61 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index ef747e6e2..04e6c9b57 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -139,7 +139,6 @@ describe "Editor", -> expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight expect(editor.scrollTop()).toBe previousScrollTop expect(editor.scrollView.scrollLeft()).toBe previousScrollLeft - console.log editor.getCursorView().css('left') expect(editor.getCursorView().position()).toEqual { top: 3 * editor.lineHeight, left: 5 * editor.charWidth } editor.insertText("goodbye") expect(editor.lineElementForScreenRow(3).text()).toMatch /^ vgoodbyear/ @@ -162,54 +161,6 @@ describe "Editor", -> runs -> expect(atom.confirm).toHaveBeenCalled() - describe ".save()", -> - describe "when the current buffer has a path", -> - tempFilePath = null - - beforeEach -> - project.setPath('/tmp') - tempFilePath = '/tmp/atom-temp.txt' - fs.write(tempFilePath, "") - editor.edit(project.buildEditSession(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.buildEditSession()) - 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() - describe ".scrollTop(n)", -> beforeEach -> editor.attachToDom(heightInLines: 5) @@ -2025,7 +1976,7 @@ describe "Editor", -> 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 diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 0f900eb47..a87790158 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -348,7 +348,7 @@ class Editor extends View @hiddenInput.on 'focusout', => @isFocused = false - @autosave() if config.get "editor.autosave" +# @autosave() if config.get "editor.autosave" @removeClass 'is-focused' @underlayer.on 'click', (e) => @@ -456,7 +456,7 @@ class Editor extends View return if editSession is @activeEditSession if @activeEditSession - @autosave() if config.get "editor.autosave" +# @autosave() if config.get "editor.autosave" @saveScrollPositionForActiveEditSession() @activeEditSession.off(".editor") @@ -609,8 +609,8 @@ class Editor extends View @removeClass 'soft-wrap' $(window).off 'resize', @_setSoftWrapColumn - autosave: -> - @save() if @getPath()? +# autosave: -> +# @save() if @getPath()? setFontSize: (fontSize) -> headTag = $("head") diff --git a/src/packages/status-bar/spec/status-bar-spec.coffee b/src/packages/status-bar/spec/status-bar-spec.coffee index 8f22d6d31..f7a88cf4d 100644 --- a/src/packages/status-bar/spec/status-bar-spec.coffee +++ b/src/packages/status-bar/spec/status-bar-spec.coffee @@ -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 e1fc838de..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 @@ -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" From bb15389b664303a86469705a590f8519d3c033be Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 10:24:21 -0700 Subject: [PATCH 137/160] Add 'Pane.saveItems' and corresponding event --- src/app/pane.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 0696b24da..12ff4d919 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -23,6 +23,7 @@ class Pane extends View @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:split-left', => @splitLeft() @@ -137,6 +138,9 @@ class Pane extends View item.saveAs(path) nextAction?() + saveItems: => + @saveItem(item) for item in @getItems() + removeItem: (item) -> index = @items.indexOf(item) return if index == -1 From fff5d5158f7069de9f5b891754c9d977ecb5cc35 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 10:35:48 -0700 Subject: [PATCH 138/160] Pass items through in editor's pane-splitting convenience methods --- src/app/editor.coffee | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index a87790158..a9e7f5751 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -654,17 +654,17 @@ class Editor extends View @updateLayerDimensions() @requestDisplayUpdate() - splitLeft: (editSession) -> - @pane()?.splitLeft().activeView + splitLeft: (items...) -> + @pane()?.splitLeft(items...).activeView - splitRight: (editSession) -> - @pane()?.splitRight().activeView + splitRight: (items...) -> + @pane()?.splitRight(items...).activeView - splitUp: (editSession) -> - @pane()?.splitUp().activeView + splitUp: (items...) -> + @pane()?.splitUp(items...).activeView - splitDown: (editSession) -> - @pane()?.splitDown().activeView + splitDown: (items...) -> + @pane()?.splitDown(items...).activeView pane: -> @closest('.pane').view() From da986b6a6c82888245d59f7c5aacede307c9e147 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 10:37:36 -0700 Subject: [PATCH 139/160] Fix RootView.saveAll() --- spec/app/root-view-spec.coffee | 3 +-- src/app/root-view.coffee | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 450f80a36..25cb21e6d 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -295,8 +295,7 @@ describe "RootView", -> buffer1.setText('edited1') expect(buffer1.isModified()).toBe(true) - editor2 = editor1.splitRight() - editor2.edit(project.buildEditSession('atom-temp2.txt')) + editor2 = editor1.splitRight(project.buildEditSession('atom-temp2.txt')) buffer2 = editor2.activeEditSession.buffer expect(buffer2.getText()).toBe("file2") expect(buffer2.isModified()).toBe(false) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 1e4d349db..0246861d9 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -148,7 +148,7 @@ class RootView extends View super saveAll: -> - editor.save() for editor in @getEditors() + pane.saveItems() for pane in @getPanes() eachPane: (callback) -> @panes.eachPane(callback) From 48c693d75681b70801942b9980df000729e4aefc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 14:37:46 -0700 Subject: [PATCH 140/160] Add 'pane:close' event, which destroys all pane items. Still have some issues with the presentation order of dialogs with multiple unsaved buffers and no paths. But for the 99% case this works as is. --- spec/app/pane-spec.coffee | 9 +++++++++ src/app/pane.coffee | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index d657e9b5c..89130e17c 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -270,6 +270,15 @@ describe "Pane", -> expect(containerCloseHandler).not.toHaveBeenCalled() + describe "pane:close", -> + it "destroys all items and removes the pane", -> + pane.showItem(editSession1) + initialItemCount = pane.getItems().length + pane.trigger 'pane:close' + expect(pane.hasParent()).toBeFalsy() + expect(editSession2.destroyed).toBeTruthy() + expect(editSession1.destroyed).toBeTruthy() + describe "core:save", -> describe "when the current item has a path", -> describe "when the current item has a save method", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 12ff4d919..1cbe2c312 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -30,6 +30,7 @@ class Pane extends View @command 'pane:split-right', => @splitRight() @command 'pane:split-up', => @splitUp() @command 'pane:split-down', => @splitDown() + @command 'pane:close', => @destroyItems() @on 'focus', => @activeView.focus(); false @on 'focusin', => @makeActive() @@ -108,6 +109,9 @@ class Pane extends View else reallyDestroyItem() + destroyItems: -> + @destroyItem(item) for item in @getItems() + promptToSaveItem: (item, nextAction) -> path = item.getPath() atom.confirm( From f0398f2331c72feda0613c031c8651a97a67a596 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 15:52:21 -0700 Subject: [PATCH 141/160] Ensure modal dialogs are presented in a coherent order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modal dialogs can be presented while other modal dialogs are already being displayed. Previously, dialogs were always displayed in the order they were requested. But say you have two untitled buffers in a pane and you close all items… You'll display prompt dialogs for both buffers asking the user if they want to save. If the user answers yes to the first dialog, they should see the path selection dialog before they see the save prompt for the second buffer. This commit uses a stack of queues to store deferred dialogs and allow dialogs presented by the dismissal of another dialog to take precedence over other pending dialogs. --- spec/app/atom-spec.coffee | 69 +++++++++++++++++++++++++++++++++++++++ spec/spec-helper.coffee | 2 ++ src/app/atom.coffee | 50 ++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 7 deletions(-) diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee index 261c86d4f..675443a19 100644 --- a/spec/app/atom-spec.coffee +++ b/spec/app/atom-spec.coffee @@ -141,3 +141,72 @@ 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)]() + + atom.sendMessageToBrowserProcess.simulatePathSelection = (path) -> + callback = @argsForCall[0][2] + @reset() + callback(path) + + 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/spec-helper.coffee b/spec/spec-helper.coffee index fc93ecc61..bbe236034 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -83,6 +83,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 6939a071f..597fb60cb 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -16,6 +16,8 @@ _.extend atom, loadedPackages: [] activatedAtomPackages: [] atomPackageStates: {} + presentingModal: false + pendingModals: [[]] getPathToOpen: -> @getWindowState('pathToOpen') ? window.location.params.pathToOpen @@ -102,15 +104,49 @@ _.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 + @presentModal(fn) if fn = @shiftPendingModal() + + 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') From e4bf73b41c83037bf19ce971b262b744ecaeb861 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 16:27:13 -0700 Subject: [PATCH 142/160] Give the view a chance to update before presenting next dialog --- spec/app/atom-spec.coffee | 2 ++ src/app/atom.coffee | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee index 675443a19..e614a2ff7 100644 --- a/spec/app/atom-spec.coffee +++ b/spec/app/atom-spec.coffee @@ -150,11 +150,13 @@ describe "the `atom` global", -> 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") diff --git a/src/app/atom.coffee b/src/app/atom.coffee index 597fb60cb..2e4635c18 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -131,7 +131,8 @@ _.extend atom, @pendingModals.push([]) # prioritize any modals presented during dismiss callback fn?(args...) @presentingModal = false - @presentModal(fn) if fn = @shiftPendingModal() + 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. From 7ebce683c651c0b437e1b3e0d4367361ab9dc0c8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 16:44:27 -0700 Subject: [PATCH 143/160] Move saveAll and specs to PaneContainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And simplify the specs… we don't *really* need to save. We can just ensure that save is called on everything. --- spec/app/pane-container-spec.coffee | 12 ++++++++++++ spec/app/root-view-spec.coffee | 30 ----------------------------- src/app/pane-container.coffee | 3 +++ src/app/root-view.coffee | 2 +- 4 files changed, 16 insertions(+), 31 deletions(-) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index 063d3598b..6c1d8179a 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -13,6 +13,8 @@ describe "PaneContainer", -> @content: -> @div tabindex: -1 initialize: (@myText) -> @text(@myText) serialize: -> deserializer: 'TestView', myText: @myText + getPath: -> "/tmp/hi" + save: -> @saved = true container = new PaneContainer pane1 = new Pane(new TestView('1')) @@ -72,6 +74,16 @@ describe "PaneContainer", -> pane4.splitDown() expect(panes).toEqual [] + 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()) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 25cb21e6d..6604f7521 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -279,36 +279,6 @@ describe "RootView", -> editSession = rootView.open('b', changeFocus: false) expect(activePane.focus).not.toHaveBeenCalled() - 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.getActiveView() - 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(project.buildEditSession('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 "window:toggle-invisibles event", -> it "shows/hides invisibles in all open and future editors", -> rootView.height(200) diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 12c88cc09..423ebab29 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -36,6 +36,9 @@ class PaneContainer extends View getRoot: -> @children().first().view() + saveAll: -> + pane.saveItems() for pane in @getPanes() + getPanes: -> @find('.pane').views() diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 0246861d9..0ce0b04ba 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -148,7 +148,7 @@ class RootView extends View super saveAll: -> - pane.saveItems() for pane in @getPanes() + @panes.saveAll() eachPane: (callback) -> @panes.eachPane(callback) From 4b8d786d2add7330dc0eb8f851b689885a57013d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 17:25:58 -0700 Subject: [PATCH 144/160] :lipstick: --- spec/app/pane-spec.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 89130e17c..d4066292e 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -273,7 +273,6 @@ describe "Pane", -> describe "pane:close", -> it "destroys all items and removes the pane", -> pane.showItem(editSession1) - initialItemCount = pane.getItems().length pane.trigger 'pane:close' expect(pane.hasParent()).toBeFalsy() expect(editSession2.destroyed).toBeTruthy() From f23d9091f2b2f70830e1336c3df719839c74219b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 17:26:16 -0700 Subject: [PATCH 145/160] Add pane:close-other-items --- spec/app/pane-spec.coffee | 7 +++++++ src/app/pane.coffee | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index d4066292e..103aa13ba 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -278,6 +278,13 @@ describe "Pane", -> 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 path", -> describe "when the current item has a save method", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 1cbe2c312..3978ca696 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -31,6 +31,7 @@ class Pane extends View @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() @@ -112,6 +113,9 @@ class Pane extends View destroyItems: -> @destroyItem(item) for item in @getItems() + destroyInactiveItems: -> + @destroyItem(item) for item in @getItems() when item isnt @activeItem + promptToSaveItem: (item, nextAction) -> path = item.getPath() atom.confirm( From d97e91bdcb5baabd88d77098f86aba97abffe2a7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 17:36:16 -0700 Subject: [PATCH 146/160] Make meta-# bindings work with new panes --- spec/app/pane-spec.coffee | 9 +++++++++ src/app/keymaps/atom.cson | 10 +++++++++- src/app/keymaps/editor.cson | 9 --------- src/app/pane.coffee | 18 ++++++++++++++++-- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 103aa13ba..f1344ba31 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -354,6 +354,15 @@ describe "Pane", -> 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 ".remove()", -> it "destroys all the pane's items", -> pane.remove() diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index 29e83880a..c621867c3 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -37,11 +37,19 @@ 'ctrl--': 'pane:split-down' 'ctrl-w s': 'pane:split-down' -'.pane': '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' '.tool-panel': 'meta-escape': 'tool-panel:unfocus' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index 066385507..8b2e3d8ad 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -16,15 +16,6 @@ 'shift-tab': 'editor:outdent-selected-rows' 'meta-[': 'editor:outdent-selected-rows' 'meta-]': 'editor:indent-selected-rows' - '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.coffee b/src/app/pane.coffee index 3978ca696..0a77a8a1d 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -26,6 +26,17 @@ class Pane extends View @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() @@ -74,10 +85,13 @@ class Pane extends View @items.indexOf(@activeItem) showItemAtIndex: (index) -> - @showItem(@items[index]) + @showItem(@itemAtIndex(index)) + + itemAtIndex: (index) -> + @items[index] showItem: (item) -> - return if item is @activeItem + return if !item? or item is @activeItem isFocused = @is(':has(:focus)') @addItem(item) view = @viewForItem(item) From f2e5fcc9020c3aae6aff0f9193e8bf31e254fb4b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 1 Mar 2013 18:14:28 -0700 Subject: [PATCH 147/160] Move autosave from editor into panes --- docs/getting-started.md | 2 +- docs/internals/configuration.md | 4 +-- spec/app/pane-spec.coffee | 62 +++++++++++++++++++++++++++++++++ src/app/editor.coffee | 6 ---- src/app/pane.coffee | 12 +++++++ 5 files changed, 77 insertions(+), 9 deletions(-) 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/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index f1344ba31..887050f7f 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -575,6 +575,68 @@ describe "Pane", -> expect(container.children('.pane').length).toBe 1 expect(pane1.outerWidth()).toBe container.width() + describe "autosave", -> + [initialActiveItem, initialActiveItemPath] = [] + + beforeEach -> + initialActiveItem = pane.activeItem + initialActiveItemPath = null + pane.activeItem.getPath = -> initialActiveItemPath + 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 path", -> + 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() + + initialActiveItemPath = '/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 path", -> + 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) + initialActiveItemPath = '/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 path", -> + # 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.getPath = -> undefined + view2.save = -> + pane.destroyItem(view2) + expect(pane.saveItem).not.toHaveBeenCalled() + + initialActiveItemPath = '/tmp/hi' + pane.destroyItem(initialActiveItem) + expect(initialActiveItem.save).toHaveBeenCalled() + describe ".itemForPath(path)", -> it "returns the item for which a call to .getPath() returns the given path", -> expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 diff --git a/src/app/editor.coffee b/src/app/editor.coffee index a9e7f5751..5e5d99fd8 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: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-" @@ -348,7 +347,6 @@ class Editor extends View @hiddenInput.on 'focusout', => @isFocused = false -# @autosave() if config.get "editor.autosave" @removeClass 'is-focused' @underlayer.on 'click', (e) => @@ -456,7 +454,6 @@ class Editor extends View return if editSession is @activeEditSession if @activeEditSession -# @autosave() if config.get "editor.autosave" @saveScrollPositionForActiveEditSession() @activeEditSession.off(".editor") @@ -609,9 +606,6 @@ class Editor extends View @removeClass 'soft-wrap' $(window).off 'resize', @_setSoftWrapColumn -# autosave: -> -# @save() if @getPath()? - setFontSize: (fontSize) -> headTag = $("head") styleTag = headTag.find("style.font-size") diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 0a77a8a1d..ba61be3eb 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -45,6 +45,7 @@ class Pane extends View @command 'pane:close-other-items', => @destroyInactiveItems() @on 'focus', => @activeView.focus(); false @on 'focusin', => @makeActive() + @on 'focusout', => @autosaveActiveItem() afterAttach: -> return if @attached @@ -92,6 +93,9 @@ class Pane extends View showItem: (item) -> return if !item? or item is @activeItem + + @autosaveActiveItem() if @activeItem + isFocused = @is(':has(:focus)') @addItem(item) view = @viewForItem(item) @@ -119,6 +123,8 @@ class Pane extends View @removeItem(item) item.destroy?() + @autosaveItem(item) + if item.isModified?() @promptToSaveItem(item, reallyDestroyItem) else @@ -163,6 +169,12 @@ class Pane extends View saveItems: => @saveItem(item) for item in @getItems() + autosaveActiveItem: -> + @autosaveItem(@activeItem) + + autosaveItem: (item) -> + @saveItem(item) if config.get('core.autosave') and item.getPath?() + removeItem: (item) -> index = @items.indexOf(item) return if index == -1 From 96fefe94f0a92ba5e16e2a0bdc2006245b21c941 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 5 Mar 2013 12:04:14 -0800 Subject: [PATCH 148/160] Fix broken specs after rebase --- spec/app/git-spec.coffee | 6 +++--- spec/spec-helper.coffee | 3 +-- src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee | 4 ++-- src/packages/spell-check/spec/spell-check-spec.coffee | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/spec/app/git-spec.coffee b/spec/app/git-spec.coffee index 04c5596fc..fcdb120d8 100644 --- a/spec/app/git-spec.coffee +++ b/spec/app/git-spec.coffee @@ -189,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 -> diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index bbe236034..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.project = new Project(require.resolve('fixtures')) - window.git = Git.open(fixturesProject.getPath()) + window.git = Git.open(project.getPath()) window.project.on 'path-changed', -> window.git?.destroy() window.git = Git.open(window.project.getPath()) diff --git a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee index 4530b8e9b..dcad26a9e 100644 --- a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee +++ b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee @@ -206,7 +206,7 @@ describe 'FuzzyFinder', -> [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') @@ -461,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/spell-check/spec/spell-check-spec.coffee b/src/packages/spell-check/spec/spell-check-spec.coffee index b860e650c..f2b4f4f3a 100644 --- a/src/packages/spell-check/spec/spell-check-spec.coffee +++ b/src/packages/spell-check/spec/spell-check-spec.coffee @@ -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() From 5ad53bb32ca17b9e1e337befc036d4a7dec337ba Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 11:57:17 -0800 Subject: [PATCH 149/160] Add restoreItem to Pane container --- spec/app/pane-container-spec.coffee | 60 +++++++++++++++++++++++++++-- spec/app/pane-spec.coffee | 38 +++++++++--------- src/app/edit-session.coffee | 7 ++++ src/app/pane-container.coffee | 22 +++++++++++ src/app/pane.coffee | 13 ++++--- src/app/root-view.coffee | 2 +- src/app/window.coffee | 5 ++- 7 files changed, 117 insertions(+), 30 deletions(-) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index 6c1d8179a..ae9cb619f 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -1,6 +1,7 @@ PaneContainer = require 'pane-container' Pane = require 'pane' {View, $$} = require 'space-pen' +_ = require 'underscore' $ = require 'jquery' describe "PaneContainer", -> @@ -9,12 +10,13 @@ describe "PaneContainer", -> beforeEach -> class TestView extends View registerDeserializer(this) - @deserialize: ({myText}) -> new TestView(myText) + @deserialize: ({name}) -> new TestView(name) @content: -> @div tabindex: -1 - initialize: (@myText) -> @text(@myText) - serialize: -> deserializer: 'TestView', myText: @myText - getPath: -> "/tmp/hi" + 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')) @@ -74,6 +76,56 @@ describe "PaneContainer", -> pane4.splitDown() expect(panes).toEqual [] + describe ".restoreItem()", -> + 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.restoreItem()).toBeTruthy() + expect(pane1.activeItem).toEqual item4 + + expect(container.restoreItem()).toBeTruthy() + expect(pane1.activeItem).toEqual item3 + + expect(container.restoreItem()).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.restoreItem() + + 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.restoreItem()).toBeTruthy() + expect(_.pluck(pane1.getItems(), 'name')).toEqual ['1', '4', '3'] + expect(pane1.activeItem).toEqual item3 + + expect(container.restoreItem()).toBeFalsy() + expect(pane1.activeItem).toEqual item3 + describe ".saveAll()", -> it "saves all open pane items", -> pane1.showItem(new TestView('4')) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 887050f7f..b25db976d 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -125,7 +125,7 @@ describe "Pane", -> expect(editSession2.destroyed).toBeFalsy() describe "if the [Save] option is selected", -> - describe "when the item has a path", -> + describe "when the item has a uri", -> it "saves the item before removing and destroying it", -> atom.confirm.selectOption('Save') @@ -133,8 +133,8 @@ describe "Pane", -> expect(pane.getItems().indexOf(editSession2)).toBe -1 expect(editSession2.destroyed).toBeTruthy() - describe "when the item has no path", -> - it "presents a save-as dialog, then saves the item with the given path before removing and destroying it", -> + 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') @@ -286,7 +286,7 @@ describe "Pane", -> expect(pane.getItems()).toEqual [editSession1] describe "core:save", -> - describe "when the current item has a path", -> + 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') @@ -299,7 +299,7 @@ describe "Pane", -> expect(pane.activeItem.save).toBeUndefined() pane.trigger 'core:save' - describe "when the current item has no path", -> + describe "when the current item has no uri", -> beforeEach -> spyOn(atom, 'showSaveDialog') @@ -576,17 +576,17 @@ describe "Pane", -> expect(pane1.outerWidth()).toBe container.width() describe "autosave", -> - [initialActiveItem, initialActiveItemPath] = [] + [initialActiveItem, initialActiveItemUri] = [] beforeEach -> initialActiveItem = pane.activeItem - initialActiveItemPath = null - pane.activeItem.getPath = -> initialActiveItemPath + 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 path", -> + 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() @@ -596,12 +596,12 @@ describe "Pane", -> expect(pane.saveItem).not.toHaveBeenCalled() expect(pane.activeItem.save).not.toHaveBeenCalled() - initialActiveItemPath = '/tmp/hi' + 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 path", -> + 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() @@ -614,12 +614,12 @@ describe "Pane", -> expect(initialActiveItem.save).not.toHaveBeenCalled() pane.showItem(initialActiveItem) - initialActiveItemPath = '/tmp/hi' + 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 path", -> + 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) @@ -628,19 +628,19 @@ describe "Pane", -> expect(pane.saveItem).not.toHaveBeenCalled() config.set("core.autosave", true) - view2.getPath = -> undefined + view2.getUri = -> undefined view2.save = -> pane.destroyItem(view2) expect(pane.saveItem).not.toHaveBeenCalled() - initialActiveItemPath = '/tmp/hi' + initialActiveItemUri = '/tmp/hi' pane.destroyItem(initialActiveItem) expect(initialActiveItem.save).toHaveBeenCalled() - describe ".itemForPath(path)", -> - it "returns the item for which a call to .getPath() returns the given path", -> - expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 - expect(pane.itemForPath(editSession2.getPath())).toBe editSession2 + 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", -> diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 46c9094f3..ba6ba0401 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -25,6 +25,12 @@ class EditSession session.setCursorScreenPosition(state.cursorScreenPosition) session + @identifiedBy: 'path' + + @deserializesToSameObject: (state, editSession) -> + state.path + + scrollTop: 0 scrollLeft: 0 languageMode: null @@ -151,6 +157,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/pane-container.coffee b/src/app/pane-container.coffee index 423ebab29..2db350200 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -1,4 +1,5 @@ {View} = require 'space-pen' +Pane = require 'pane' $ = require 'jquery' module.exports = @@ -13,6 +14,9 @@ class PaneContainer extends View @content: -> @div id: 'panes' + initialize: -> + @destroyedItemStates = [] + serialize: -> deserializer: 'PaneContainer' root: @getRoot()?.serialize() @@ -33,6 +37,24 @@ class PaneContainer extends View nextIndex = (currentIndex + 1) % panes.length panes[nextIndex].makeActive() + restoreItem: -> + 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() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index ba61be3eb..f061212a7 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -111,6 +111,7 @@ class Pane extends View return if _.include(@items, item) index = @getActiveItemIndex() + 1 @items.splice(index, 0, item) + @getContainer().itemAdded(item) @trigger 'pane:item-added', [item, index] item @@ -119,8 +120,10 @@ class Pane extends View false destroyItem: (item) -> + container = @getContainer() reallyDestroyItem = => @removeItem(item) + container.itemDestroyed(item) item.destroy?() @autosaveItem(item) @@ -137,7 +140,7 @@ class Pane extends View @destroyItem(item) for item in @getItems() when item isnt @activeItem promptToSaveItem: (item, nextAction) -> - path = item.getPath() + 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." @@ -153,7 +156,7 @@ class Pane extends View @saveItemAs(@activeItem) saveItem: (item, nextAction) -> - if item.getPath?() + if item.getUri?() item.save() nextAction?() else @@ -173,7 +176,7 @@ class Pane extends View @autosaveItem(@activeItem) autosaveItem: (item) -> - @saveItem(item) if config.get('core.autosave') and item.getPath?() + @saveItem(item) if config.get('core.autosave') and item.getUri?() removeItem: (item) -> index = @items.indexOf(item) @@ -194,8 +197,8 @@ class Pane extends View @removeItem(item) pane.addItem(item, index) - itemForPath: (path) -> - _.detect @items, (item) -> item.getPath?() is path + itemForUri: (uri) -> + _.detect @items, (item) -> item.getUri?() is uri cleanupItemView: (item) -> if item instanceof $ diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 0ce0b04ba..1e3eaf3d9 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -92,7 +92,7 @@ class RootView extends View changeFocus = options.changeFocus ? true path = project.resolve(path) if path? if activePane = @getActivePane() - if editSession = activePane.itemForPath(path) + if editSession = activePane.itemForUri(path) activePane.showItem(editSession) else editSession = project.buildEditSession(path) diff --git a/src/app/window.coffee b/src/app/window.coffee index 2ff52830c..109a5096d 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -155,7 +155,10 @@ window.unregisterDeserializer = (klass) -> delete deserializers[klass.name] window.deserialize = (state) -> - deserializers[state?.deserializer]?.deserialize(state) + getDeserializer(state)?.deserialize(state) + +window.getDeserializer = (state) -> + deserializers[state?.deserializer] window.measure = (description, fn) -> start = new Date().getTime() From ffb8bcd71deef960bc69901fec7a0801f63bc6c5 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 13:45:56 -0800 Subject: [PATCH 150/160] Add pane:reopen-closed-item command --- src/app/keymaps/atom.cson | 1 + src/app/keymaps/editor.cson | 3 --- src/app/root-view.coffee | 3 +++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index c621867c3..04cc02488 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -50,6 +50,7 @@ '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' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index 8b2e3d8ad..8b192fece 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -1,6 +1,3 @@ -'body': - 'meta-T': 'editor:undo-close-session' - '.editor': 'enter': 'editor:newline' 'meta-enter': 'editor:newline-below' diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 1e3eaf3d9..7fdff2cc3 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -63,6 +63,9 @@ class RootView extends View @command 'window:toggle-auto-indent-on-paste', => config.set("editor.autoIndentOnPaste", !config.get("editor.autoIndentOnPaste")) + @command 'pane:reopen-closed-item', => + @panes.restoreItem() + serialize: -> deserializer: 'RootView' panesViewState: @panes.serialize() From c1d19c4c5ce63edb3178a7f74bbf1b184e09c7bb Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 13:52:27 -0800 Subject: [PATCH 151/160] Rename restoreItem to reopenItem on pane container --- spec/app/pane-container-spec.coffee | 14 +++++++------- src/app/pane-container.coffee | 2 +- src/app/root-view.coffee | 4 +++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index ae9cb619f..56ea53336 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -76,7 +76,7 @@ describe "PaneContainer", -> pane4.splitDown() expect(panes).toEqual [] - describe ".restoreItem()", -> + describe ".reopenItem()", -> describe "when there is an active pane", -> it "reconstructs and shows the last-closed pane item", -> expect(container.getActivePane()).toBe pane3 @@ -88,13 +88,13 @@ describe "PaneContainer", -> pane3.destroyItem(item4) expect(container.getActivePane()).toBe pane1 - expect(container.restoreItem()).toBeTruthy() + expect(container.reopenItem()).toBeTruthy() expect(pane1.activeItem).toEqual item4 - expect(container.restoreItem()).toBeTruthy() + expect(container.reopenItem()).toBeTruthy() expect(pane1.activeItem).toEqual item3 - expect(container.restoreItem()).toBeFalsy() + expect(container.reopenItem()).toBeFalsy() expect(pane1.activeItem).toEqual item3 describe "when there is no active pane", -> @@ -105,7 +105,7 @@ describe "PaneContainer", -> pane3.destroyItem(item3) expect(container.getActivePane()).toBeUndefined() - container.restoreItem() + container.reopenItem() expect(container.getActivePane().activeItem).toEqual item3 @@ -119,11 +119,11 @@ describe "PaneContainer", -> expect(container.getActivePane()).toBe pane1 pane1.showItem(new TestView('4')) - expect(container.restoreItem()).toBeTruthy() + expect(container.reopenItem()).toBeTruthy() expect(_.pluck(pane1.getItems(), 'name')).toEqual ['1', '4', '3'] expect(pane1.activeItem).toEqual item3 - expect(container.restoreItem()).toBeFalsy() + expect(container.reopenItem()).toBeFalsy() expect(pane1.activeItem).toEqual item3 describe ".saveAll()", -> diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 2db350200..6fc367e56 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -37,7 +37,7 @@ class PaneContainer extends View nextIndex = (currentIndex + 1) % panes.length panes[nextIndex].makeActive() - restoreItem: -> + reopenItem: -> if lastItemState = @destroyedItemStates.pop() if activePane = @getActivePane() activePane.showItem(deserialize(lastItemState)) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 7fdff2cc3..ffdaaf2bd 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -58,13 +58,15 @@ class RootView extends View 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.restoreItem() + @panes.reopenItem() serialize: -> deserializer: 'RootView' From d4fc718e8e50d43563b1a138e9475cf0e6c09704 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 14:12:02 -0800 Subject: [PATCH 152/160] Update window title when a pane item's title changes --- spec/app/pane-spec.coffee | 18 ++++++++++++++++++ spec/app/root-view-spec.coffee | 8 +++++++- src/app/pane.coffee | 8 +++++++- src/app/root-view.coffee | 3 +-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index b25db976d..97c27dd8b 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -363,6 +363,24 @@ describe "Pane", -> 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() diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 6604f7521..d4c8a31c4 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -159,7 +159,7 @@ describe "RootView", -> rootView.trigger(event) expect(commandHandler).toHaveBeenCalled() - describe "title", -> + describe "window title", -> describe "when the project has no path", -> it "sets the title to 'untitled'", -> project.setPath(undefined) @@ -174,6 +174,12 @@ describe "RootView", -> item = rootView.getActivePaneItem() expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}" + 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()}" + 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() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index f061212a7..54b9e375d 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -94,10 +94,13 @@ class Pane extends View showItem: (item) -> return if !item? or item is @activeItem - @autosaveActiveItem() if @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) @@ -107,6 +110,9 @@ class Pane extends View @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 diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index ffdaaf2bd..6129f8fcd 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -32,8 +32,6 @@ class RootView extends View panes = deserialize(panesViewState) if panesViewState?.deserializer is 'PaneContainer' new RootView({panes}) - title: null - initialize: -> @command 'toggle-dev-tools', => atom.toggleDevTools() @on 'focus', (e) => @handleFocus(e) @@ -44,6 +42,7 @@ class RootView extends View @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) From 4f0bf9020bb1ebc8149b311f200f0698c2fe3732 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 16:05:26 -0800 Subject: [PATCH 153/160] Fix pane focus and active item serialization Also: Un-x root view serialization specs --- spec/app/pane-spec.coffee | 27 ++++++++++++++++++ spec/app/root-view-spec.coffee | 52 +++++++++++++--------------------- src/app/pane.coffee | 18 ++++++++++-- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 97c27dd8b..3c62fc797 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -664,3 +664,30 @@ describe "Pane", -> 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/root-view-spec.coffee b/spec/app/root-view-spec.coffee index d4c8a31c4..9cbd75837 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -18,39 +18,40 @@ describe "RootView", -> rootView.open(pathToOpen) rootView.focus() - xdescribe "@deserialize()", -> + describe "@deserialize()", -> viewState = null 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 = 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.buildEditSession('b')) - editor3.edit(project.buildEditSession('../sample.js')) - editor3.setCursorScreenPosition([2, 4]) - editor4.edit(project.buildEditSession('../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() @@ -82,11 +83,11 @@ 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() @@ -96,19 +97,6 @@ describe "RootView", -> 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 view", -> it "hands off focus to the active view", -> diff --git a/src/app/pane.coffee b/src/app/pane.coffee index 54b9e375d..ef29ce72f 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -10,8 +10,11 @@ class Pane extends View @div class: 'pane', => @div class: 'item-views', outlet: 'itemViews' - @deserialize: ({items}) -> - new Pane(items.map((item) -> deserialize(item))...) + @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 @@ -47,7 +50,11 @@ class Pane extends View @on 'focusin', => @makeActive() @on 'focusout', => @autosaveActiveItem() - afterAttach: -> + afterAttach: (onDom) -> + if @focusOnAttach and onDom + @focusOnAttach = null + @focus() + return if @attached @attached = true @trigger 'pane:attached' @@ -206,6 +213,9 @@ class Pane extends View itemForUri: (uri) -> _.detect @items, (item) -> item.getUri?() is uri + showItemForUri: (uri) -> + @showItem(@itemForUri(uri)) + cleanupItemView: (item) -> if item instanceof $ viewToRemove = item @@ -238,6 +248,8 @@ class Pane extends View serialize: -> deserializer: "Pane" + focused: @is(':has(:focus)') + activeItemUri: @activeItem.getUri?() if typeof @activeItem.serialize is 'function' items: _.compact(@getItems().map (item) -> item.serialize?()) adjustDimensions: -> # do nothing From 8333f14ef8d82ade7832c5bcde47bb325601ab52 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 16:06:17 -0800 Subject: [PATCH 154/160] Throw away serialized state if its version doesn't match deserializer --- spec/app/window-spec.coffee | 31 +++++++++++++++++++++++++++++++ src/app/window.coffee | 4 +++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index 787a80734..12f79248f 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -130,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/src/app/window.coffee b/src/app/window.coffee index 109a5096d..321a60f56 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -155,7 +155,9 @@ window.unregisterDeserializer = (klass) -> delete deserializers[klass.name] window.deserialize = (state) -> - getDeserializer(state)?.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] From 6257bcf0f5bb7b6e4514e28f60f567f7e06f602f Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 16:12:29 -0800 Subject: [PATCH 155/160] Rename root view serialization keys --- src/app/root-view.coffee | 10 +++++----- .../fuzzy-finder/spec/fuzzy-finder-spec.coffee | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 6129f8fcd..9ab1b3f45 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -27,9 +27,9 @@ class RootView extends View @div id: 'vertical', outlet: 'vertical', => @subview 'panes', panes ? new PaneContainer - @deserialize: ({ panesViewState, packageStates, projectPath }) -> - atom.atomPackageStates = packageStates ? {} - panes = deserialize(panesViewState) if panesViewState?.deserializer is 'PaneContainer' + @deserialize: ({ panes, packages, projectPath }) -> + atom.atomPackageStates = packages ? {} + panes = deserialize(panes) if panes?.deserializer is 'PaneContainer' new RootView({panes}) initialize: -> @@ -69,8 +69,8 @@ class RootView extends View serialize: -> deserializer: 'RootView' - panesViewState: @panes.serialize() - packageStates: atom.serializeAtomPackages() + panes: @panes.serialize() + packages: atom.serializeAtomPackages() handleFocus: (e) -> if @getActivePane() diff --git a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee index dcad26a9e..ffaae8cb0 100644 --- a/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee +++ b/src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee @@ -142,7 +142,7 @@ 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 From c3456dd5ac4ff19bbea8e37a68d57b5e901795ff Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 17:03:28 -0800 Subject: [PATCH 156/160] Remove serialization methods from editor --- src/app/editor.coffee | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 5e5d99fd8..1e2c0b464 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -55,11 +55,6 @@ class Editor extends View newSelections: null redrawOnReattach: false - @deserialize: (state) -> - editor = new Editor(mini: state.mini, editSession: deserialize(state.editSession)) - editor.isFocused = state.isFocused - editor - initialize: (editSessionOrOptions) -> if editSessionOrOptions instanceof EditSession editSession = editSessionOrOptions @@ -91,15 +86,6 @@ class Editor extends View else throw new Error("Must supply an EditSession or mini: true") - serialize: -> - @saveScrollPositionForActiveEditSession() - deserializer: "Editor" - editSession: @activeEditSession.serialize() - isFocused: @isFocused - - copy: -> - Editor.deserialize(@serialize(), rootView) - bindKeys: -> editorBindings = 'core:move-left': @moveCursorLeft From dba7c08f59b68957a05e381f2dd6a110b3f0c355 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 17:22:46 -0800 Subject: [PATCH 157/160] Add serialization version to root view --- src/app/root-view.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 9ab1b3f45..c907db4af 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -17,6 +17,8 @@ module.exports = class RootView extends View registerDeserializers(this, Pane, PaneRow, PaneColumn, Editor) + @version: 1 + @configDefaults: ignoredNames: [".git", ".svn", ".DS_Store"] disabledPackages: [] @@ -68,6 +70,7 @@ class RootView extends View @panes.reopenItem() serialize: -> + version: @constructor.version deserializer: 'RootView' panes: @panes.serialize() packages: atom.serializeAtomPackages() From cac6c854d2ffc1617e8c3cabe7bc623dacda5ce0 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 7 Mar 2013 10:25:51 -0800 Subject: [PATCH 158/160] :lipstick: --- src/app/edit-session.coffee | 1 - .../spec/markdown-preview-spec.coffee | 52 +++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index ba6ba0401..0d48a1f4e 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -30,7 +30,6 @@ class EditSession @deserializesToSameObject: (state, editSession) -> state.path - scrollTop: 0 scrollLeft: 0 languageMode: null diff --git a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee index 6e5ca6f0e..7704768ee 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee @@ -52,33 +52,33 @@ describe "MarkdownPreview", -> 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.getActiveView() - 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.getActiveView() - 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.getActiveView()).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() From 91b5c3e9c7c2f0ad2b0ae1c2d23cb8ac13128507 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 7 Mar 2013 11:07:03 -0800 Subject: [PATCH 159/160] Make refresh work again by fixing version in RootView.serialize --- src/app/root-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index c907db4af..2d3b8fa83 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -70,7 +70,7 @@ class RootView extends View @panes.reopenItem() serialize: -> - version: @constructor.version + version: RootView.version deserializer: 'RootView' panes: @panes.serialize() packages: atom.serializeAtomPackages() From 39fabaa34400bbf84873060792b190da17639e57 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Thu, 7 Mar 2013 11:09:39 -0800 Subject: [PATCH 160/160] Update RootView.deserialize specs so they break on a version mismatch --- spec/app/root-view-spec.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 9cbd75837..89c8ef8c1 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -31,7 +31,7 @@ describe "RootView", -> viewState = rootView.serialize() rootView.deactivate() - window.rootView = RootView.deserialize(viewState) + window.rootView = deserialize(viewState) rootView.attachToDom() expect(rootView.getEditors().length).toBe 2 @@ -55,7 +55,7 @@ describe "RootView", -> viewState = rootView.serialize() rootView.deactivate() - window.rootView = RootView.deserialize(viewState) + window.rootView = deserialize(viewState) rootView.attachToDom() expect(rootView.getEditors().length).toBe 4 @@ -92,7 +92,7 @@ describe "RootView", -> viewState = rootView.serialize() rootView.deactivate() - window.rootView = RootView.deserialize(viewState) + window.rootView = deserialize(viewState) rootView.attachToDom() expect(rootView.getEditors().length).toBe 0