diff --git a/.gitmodules b/.gitmodules index c3f82e270..d843b2761 100644 --- a/.gitmodules +++ b/.gitmodules @@ -46,6 +46,27 @@ [submodule "vendor/packages/python.tmbundle"] path = vendor/packages/python.tmbundle url = https://github.com/textmate/python.tmbundle +[submodule "vendor/packages/clojure.tmbundle"] + path = vendor/packages/clojure.tmbundle + url = https://github.com/mmcgrana/textmate-clojure [submodule "prebuilt-cef"] path = prebuilt-cef - url = https://github.com/github/prebuilt-cef + url = git@github.com:github/prebuilt-cef.git +[submodule "vendor/packages/yaml.tmbundle"] + path = vendor/packages/yaml.tmbundle + url = https://github.com/textmate/yaml.tmbundle.git +[submodule "vendor/packages/java.tmbundle"] + path = vendor/packages/java.tmbundle + url = https://github.com/textmate/java.tmbundle.git +[submodule "vendor/packages/php.tmbundle"] + path = vendor/packages/php.tmbundle + url = https://github.com/textmate/php.tmbundle.git +[submodule "vendor/packages/perl.tmbundle"] + path = vendor/packages/perl.tmbundle + url = https://github.com/textmate/perl.tmbundle.git +[submodule "vendor/packages/sass.tmbundle"] + path = vendor/packages/sass.tmbundle + url = https://github.com/alexsancho/SASS.tmbundle.git +[submodule "vendor/packages/less.tmbundle"] + path = vendor/packages/less.tmbundle + url = https://github.com/mathewbyrne/less.tmbundle.git diff --git a/README.md b/README.md index 876a6697f..11eee545e 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,3 @@ Requirements 1. gh-setup atom 2. cd ~/github/atom && `rake install` - diff --git a/Rakefile b/Rakefile index 071121f0b..afb06cc0a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,6 @@ ATOM_SRC_PATH = File.dirname(__FILE__) -DOT_ATOM_PATH = ENV['HOME'] + "/.atom" BUILD_DIR = 'atom-build' -require 'erb' - desc "Build Atom via `xcodebuild`" task :build => "create-xcode-project" do command = "xcodebuild -target Atom -configuration Release SYMROOT=#{BUILD_DIR}" @@ -17,7 +14,7 @@ end desc "Create xcode project from gyp file" task "create-xcode-project" => "update-cef" do `rm -rf atom.xcodeproj` - `gyp --depth=. atom.gyp` + `gyp --depth=. -D CODE_SIGN="#{ENV['CODE_SIGN']}" atom.gyp` end desc "Update CEF to the latest version specified by the prebuilt-cef submodule" @@ -32,50 +29,56 @@ task "bootstrap" do `script/bootstrap` end -desc "Creates symlink from `application_path() to /Applications/Atom and creates `atom` cli app" +desc "Copies Atom.app to /Applications and creates `atom` cli app" task :install => [:clean, :build] do path = application_path() exit 1 if not path # Install Atom.app - dest = "/Applications/#{File.basename(path)}" - `rm -rf #{dest}` - `cp -r #{path} #{File.expand_path(dest)}` + dest_path = "/Applications/#{File.basename(path)}" + `rm -rf #{dest_path}` + `cp -r #{path} #{File.expand_path(dest_path)}` - # Install cli atom - usr_bin_path = "/opt/github/bin" - cli_path = "#{usr_bin_path}/atom" - - template = ERB.new CLI_SCRIPT - namespace = OpenStruct.new(:application_path => dest, :resource_path => ATOM_SRC_PATH) - File.open(cli_path, "w") do |f| - f.write template.result(namespace.instance_eval { binding }) - f.chmod(0755) + # Install atom cli + if File.directory?("/opt/boxen") + cli_path = "/opt/boxen/bin/atom" + else + cli_path = "/opt/github/bin/atom" end - Rake::Task["create-dot-atom"].invoke() + FileUtils.cp("#{ATOM_SRC_PATH}/atom.sh", cli_path) + FileUtils.chmod(0755, cli_path) + Rake::Task["clone-default-bundles"].invoke() - puts "\033[32mType `atom` to start Atom! In Atom press `cmd-,` to edit your `~/.atom` directory\033[0m" + puts "\033[32mAtom is installed at `#{dest_path}`. Atom cli is installed at `#{cli_path}`\033[0m" end -desc "Creates .atom file if non exists" -task "create-dot-atom" do - dot_atom_template_path = ATOM_SRC_PATH + "/dot-atom" +desc "Package up the app for speakeasy" +task :package => ["setup-codesigning", "bump-patch-number", "build"] do + path = application_path() + exit 1 if not path - if File.exists?(DOT_ATOM_PATH) - user_config = "#{DOT_ATOM_PATH}/user.coffee" - old_user_config = "#{DOT_ATOM_PATH}/atom.coffee" + 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 - if File.exists?(old_user_config) - `mv #{old_user_config} #{user_config}` - puts "\033[32mRenamed #{old_user_config} to #{user_config}\033[0m" - end - else - `mkdir "#{DOT_ATOM_PATH}"` - `cp -r "#{dot_atom_template_path}/" "#{DOT_ATOM_PATH}"/` - `cp -r "#{ATOM_SRC_PATH}/themes/" "#{DOT_ATOM_PATH}"/themes/` - end +task "setup-codesigning" do + ENV['CODE_SIGN'] = "Developer ID Application: GitHub" +end + +desc "Bump patch number" +task "bump-patch-number" do + version_number = `/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString:" ./native/mac/info.plist` + major, minor, patch = version_number.match(/(\d+)\.(\d+)\.(\d+)/)[1..-1].map {|o| o.to_i} + `/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString #{major}.#{minor}.#{patch + 1}" ./native/mac/info.plist` + `/usr/libexec/PlistBuddy -c "Set :CFBundleVersion #{major}.#{minor}.#{patch + 1}" ./native/mac/info.plist` + + new_version_number = `/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString:" ./native/mac/info.plist` + puts "Bumped from #{version_number.strip} to #{new_version_number.strip}" end desc "Clone default bundles into vendor/bundles directory" @@ -96,7 +99,6 @@ desc "Run the specs" task :test => ["update-cef", "clone-default-bundles", "build"] do `pkill Atom` if path = application_path() - `rm -rf path` cmd = "#{path}/Contents/MacOS/Atom --test --resource-path=#{ATOM_SRC_PATH} 2> /dev/null" system(cmd) exit($?.exitstatus) @@ -130,30 +132,3 @@ def application_path return nil end - -CLI_SCRIPT = <<-EOF -#!/bin/sh -open <%= application_path %> -n --args --resource-path="<%= resource_path %>" --executed-from="$(pwd)" --pid=$$ $@ - -# Used to exit process when atom is used as $EDITOR -on_die() { - exit 0 -} -trap 'on_die' SIGQUIT SIGTERM - -# Don't exit process if we were told to wait. -while [ "$#" -gt "0" ]; do - case $1 in - -W|--wait) - WAIT=1 - ;; - esac - shift -done - -if [ $WAIT ]; then - while true; do - sleep 1 - done -fi -EOF diff --git a/atom.gyp b/atom.gyp index e06b0d664..a4d09f442 100644 --- a/atom.gyp +++ b/atom.gyp @@ -80,6 +80,9 @@ 'OTHER_LDFLAGS': ['-Wl,-headerpad_max_install_names'], # Necessary to avoid an "install_name_tool: changing install names or rpaths can't be redone" error. }, 'conditions': [ + ['CODE_SIGN' , { + 'xcode_settings': {'CODE_SIGN_IDENTITY': "<(CODE_SIGN)"}, + }], ['OS=="win" and win_use_allocator_shim==1', { 'dependencies': [ '<(DEPTH)/base/allocator/allocator.gyp:allocator', diff --git a/atom.sh b/atom.sh new file mode 100644 index 000000000..36d5e1b3d --- /dev/null +++ b/atom.sh @@ -0,0 +1,24 @@ +#!/bin/sh +open -a /Applications/Atom.app -n --args --executed-from="$(pwd)" --pid=$$ $@ + +# Used to exit process when atom is used as $EDITOR +on_die() { + exit 0 +} +trap 'on_die' SIGQUIT SIGTERM + +# Don't exit process if we were told to wait. +while [ "$#" -gt "0" ]; do + case $1 in + -W|--wait) + WAIT=1 + ;; + esac + shift +done + +if [ $WAIT ]; then + while true; do + sleep 1 + done +fi diff --git a/docs/getting-started.md b/docs/getting-started.md index e7b592b89..0c494952a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,6 +1,6 @@ # Getting Started -Welcome to Atom. This documentation is intented to offer a basic introduction +Welcome to Atom. This documentation is intended to offer a basic introduction of how to get productive with this editor. Then we'll delve into more details about configuring, theming, and extending Atom. @@ -36,6 +36,15 @@ bindings, but here's a list of a few useful commands. ## Usage Basics +### If You See A Rendering Bug + +Things are pretty stable, but we think we have a couple rendering bugs lurking +that are hard to reproduce. If you see one, please hit `meta-p` and type +"save debug snapshot". Run that command to save a snapshot of the misbehaving +editor and send it to us, along with a screenshot and your best description of +how you produced the bug. Refreshing with `meta-r` should usually resolve the +issue so you can keep working. + ### Working With Files #### Finding Files diff --git a/native/atom_application.h b/native/atom_application.h index 7db0bece8..cef8719b1 100644 --- a/native/atom_application.h +++ b/native/atom_application.h @@ -4,6 +4,7 @@ class AtomCefClient; @interface AtomApplication : NSApplication { + IBOutlet NSMenuItem *_versionMenuItem; NSWindowController *_backgroundWindowController; NSDictionary *_arguments; NSInvocation *_updateInvocation; @@ -23,8 +24,6 @@ class AtomCefClient; - (void)runSpecsThenExit:(BOOL)exitWhenDone; - (NSDictionary *)arguments; - (void)runBenchmarksThenExit:(BOOL)exitWhenDone; -- (NSString *)updateStatus; -- (void)installUpdate; @property (nonatomic, retain) NSDictionary *arguments; diff --git a/native/atom_application.mm b/native/atom_application.mm index 102774de5..26c3c7778 100644 --- a/native/atom_application.mm +++ b/native/atom_application.mm @@ -50,20 +50,20 @@ { "resource-path", required_argument, NULL, 'R' }, { "benchmark", no_argument, NULL, 'B' }, { "test", no_argument, NULL, 'T' }, - { "stable", no_argument, NULL, 'S' }, + { "dev", no_argument, NULL, 'D' }, { "pid", required_argument, NULL, 'P' }, { "wait", no_argument, NULL, 'W' }, { NULL, 0, NULL, 0 } }; - while ((opt = getopt_long(cleanArgc, cleanArgv, "K:R:BYSP:Wh?", longopts, &longindex)) != -1) { + while ((opt = getopt_long(cleanArgc, cleanArgv, "K:R:BYDP:Wh?", longopts, &longindex)) != -1) { NSString *key, *value; switch (opt) { case 'K': case 'R': case 'B': case 'T': - case 'S': + case 'D': case 'W': case 'P': key = [NSString stringWithUTF8String:longopts[longindex].name]; @@ -123,8 +123,10 @@ + (CefSettings)createCefSettings { CefSettings settings; + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + NSString *userAgent = [NSString stringWithFormat:@"GitHubAtom/%@", version]; CefString(&settings.cache_path) = [[self supportDirectory] UTF8String]; - CefString(&settings.user_agent) = ""; + CefString(&settings.user_agent) = [userAgent UTF8String]; CefString(&settings.log_file) = ""; CefString(&settings.javascript_flags) = ""; settings.remote_debugging_port = 9090; @@ -159,8 +161,8 @@ [self open:path pidToKillWhenWindowCloses:nil]; } -- (void)openUnstable:(NSString *)path { - [[AtomWindowController alloc] initUnstableWithPath:path]; +- (void)openDev:(NSString *)path { + [[AtomWindowController alloc] initDevWithPath:path]; } - (IBAction)runSpecs:(id)sender { @@ -213,18 +215,24 @@ } - (void)applicationWillFinishLaunching:(NSNotification *)notification { - SUUpdater.sharedUpdater.delegate = self; - SUUpdater.sharedUpdater.automaticallyChecksForUpdates = YES; - SUUpdater.sharedUpdater.automaticallyDownloadsUpdates = YES; - [SUUpdater.sharedUpdater checkForUpdatesInBackground]; + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + _versionMenuItem.title = [NSString stringWithFormat:@"Version %@", version]; - _backgroundWindowController = [[AtomWindowController alloc] initInBackground]; if ([self.arguments objectForKey:@"benchmark"]) { [self runBenchmarksThenExit:true]; } else if ([self.arguments objectForKey:@"test"]) { [self runSpecsThenExit:true]; } + else { + _backgroundWindowController = [[AtomWindowController alloc] initInBackground]; + if (![self.arguments objectForKey:@"dev"]) { + SUUpdater.sharedUpdater.delegate = self; + SUUpdater.sharedUpdater.automaticallyChecksForUpdates = YES; + SUUpdater.sharedUpdater.automaticallyDownloadsUpdates = YES; + [SUUpdater.sharedUpdater checkForUpdatesInBackground]; + } + } } - (void)applicationWillTerminate:(NSNotification *)notification { @@ -257,35 +265,25 @@ } } -- (NSString *)updateStatus { - return _updateStatus ? _updateStatus : @"idle"; -} - -- (void)installUpdate { - if (_updateInvocation) [_updateInvocation invoke]; -} - #pragma mark SUUpdaterDelegate - (void)updaterDidNotFindUpdate:(SUUpdater *)update { - _updateStatus = @"current"; } - (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update { - _updateStatus = @"downloading"; } - (void)updater:(SUUpdater *)updater willExtractUpdate:(SUAppcastItem *)update { - _updateStatus = @"installing"; } - (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)update immediateInstallationInvocation:(NSInvocation *)invocation { - _updateInvocation = invocation; - _updateStatus = @"ready"; + _updateInvocation = [invocation retain]; + _versionMenuItem.title = [NSString stringWithFormat:@"Update to %@", update.versionString]; + _versionMenuItem.target = _updateInvocation; + _versionMenuItem.action = @selector(invoke); } - (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)update { - _updateStatus = @"current"; } @end diff --git a/native/atom_cef_client.cpp b/native/atom_cef_client.cpp index 61be7900e..4427a8901 100644 --- a/native/atom_cef_client.cpp +++ b/native/atom_cef_client.cpp @@ -36,9 +36,9 @@ bool AtomCefClient::OnProcessMessageReceived(CefRefPtr browser, bool hasArguments = argumentList->GetSize() > 1; hasArguments ? Open(argumentList->GetString(1)) : Open(); } - if (name == "openUnstable") { + if (name == "openDev") { bool hasArguments = argumentList->GetSize() > 1; - hasArguments ? OpenUnstable(argumentList->GetString(1)) : OpenUnstable(); + hasArguments ? OpenDev(argumentList->GetString(1)) : OpenDev(); } else if (name == "newWindow") { NewWindow(); @@ -84,11 +84,8 @@ bool AtomCefClient::OnProcessMessageReceived(CefRefPtr browser, else if (name == "toggleFullScreen") { ToggleFullScreen(browser); } - else if (name == "update") { - Update(); - } - else if (name == "getUpdateStatus") { - GetUpdateStatus(messageId, browser); + else if (name == "getVersion") { + GetVersion(messageId, browser); } else { return false; diff --git a/native/atom_cef_client.h b/native/atom_cef_client.h index 812559c32..494009c5c 100644 --- a/native/atom_cef_client.h +++ b/native/atom_cef_client.h @@ -109,9 +109,8 @@ class AtomCefClient : public CefClient, void FocusPreviousWindow(); void Open(std::string path); void Open(); - void OpenUnstable(std::string path); - - void OpenUnstable(); + void OpenDev(std::string path); + void OpenDev(); void NewWindow(); void ToggleDevTools(CefRefPtr browser); void ShowDevTools(CefRefPtr browser); @@ -126,8 +125,7 @@ class AtomCefClient : public CefClient, void Log(const char *message); void Show(CefRefPtr browser); void ToggleFullScreen(CefRefPtr browser); - void Update(); - void GetUpdateStatus(int replyId, CefRefPtr browser); + void GetVersion(int replyId, CefRefPtr browser); IMPLEMENT_REFCOUNTING(AtomCefClient); IMPLEMENT_LOCKING(AtomCefClient); diff --git a/native/atom_cef_client_mac.mm b/native/atom_cef_client_mac.mm index 0ef9c0cbc..4be8f3592 100644 --- a/native/atom_cef_client_mac.mm +++ b/native/atom_cef_client_mac.mm @@ -56,17 +56,17 @@ void AtomCefClient::Open() { } } -void AtomCefClient::OpenUnstable(std::string path) { +void AtomCefClient::OpenDev(std::string path) { NSString *pathString = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding]; - [(AtomApplication *)[AtomApplication sharedApplication] openUnstable:pathString]; + [(AtomApplication *)[AtomApplication sharedApplication] openDev:pathString]; } -void AtomCefClient::OpenUnstable() { +void AtomCefClient::OpenDev() { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseDirectories:YES]; if ([panel runModal] == NSFileHandlingPanelOKButton) { NSURL *url = [[panel URLs] lastObject]; - OpenUnstable([[url path] UTF8String]); + OpenDev([[url path] UTF8String]); } } @@ -159,17 +159,13 @@ void AtomCefClient::Log(const char *message) { std::cout << message << "\n"; } -void AtomCefClient::Update() { - [(AtomApplication *)NSApp installUpdate]; -} - -void AtomCefClient::GetUpdateStatus(int replyId, CefRefPtr browser) { +void AtomCefClient::GetVersion(int replyId, CefRefPtr browser) { CefRefPtr replyMessage = CefProcessMessage::Create("reply"); CefRefPtr replyArguments = replyMessage->GetArgumentList(); - + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + replyArguments->SetSize(2); - replyArguments->SetString(1, [[(AtomApplication *)NSApp updateStatus] UTF8String]); + replyArguments->SetString(1, [version UTF8String]); replyArguments->SetList(0, CreateReplyDescriptor(replyId, 0)); - browser->SendProcessMessage(PID_RENDERER, replyMessage); -} \ No newline at end of file +} diff --git a/native/atom_window_controller.h b/native/atom_window_controller.h index b4737e268..09b7590c6 100644 --- a/native/atom_window_controller.h +++ b/native/atom_window_controller.h @@ -25,7 +25,7 @@ class AtomCefClient; @property (nonatomic, retain) NSString *pathToOpen; - (id)initWithPath:(NSString *)path; -- (id)initUnstableWithPath:(NSString *)path; +- (id)initDevWithPath:(NSString *)path; - (id)initInBackground; - (id)initSpecsThenExit:(BOOL)exitWhenDone; - (id)initBenchmarksThenExit:(BOOL)exitWhenDone; diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index 498070981..ebed667ee 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -33,6 +33,17 @@ AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication]; _resourcePath = [atomApplication.arguments objectForKey:@"resource-path"]; + if (!alwaysUseBundleResourcePath && !_resourcePath) { + NSString *defaultRepositoryPath = @"~/github/atom"; + defaultRepositoryPath = [defaultRepositoryPath stringByStandardizingPath]; + if ([defaultRepositoryPath characterAtIndex:0] == '/') { + BOOL isDir = false; + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:defaultRepositoryPath isDirectory:&isDir]; + if (isDir && exists) + _resourcePath = defaultRepositoryPath; + } + } + if (alwaysUseBundleResourcePath || !_resourcePath) { _resourcePath = [[NSBundle mainBundle] resourcePath]; } @@ -52,11 +63,11 @@ - (id)initWithPath:(NSString *)path { _pathToOpen = [path retain]; AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication]; - BOOL stable = [atomApplication.arguments objectForKey:@"stable"] != nil; - return [self initWithBootstrapScript:@"window-bootstrap" background:NO alwaysUseBundleResourcePath:stable]; + BOOL useBundleResourcePath = [atomApplication.arguments objectForKey:@"dev"] == nil; + return [self initWithBootstrapScript:@"window-bootstrap" background:NO alwaysUseBundleResourcePath:useBundleResourcePath]; } -- (id)initUnstableWithPath:(NSString *)path { +- (id)initDevWithPath:(NSString *)path { _pathToOpen = [path retain]; AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication]; return [self initWithBootstrapScript:@"window-bootstrap" background:NO alwaysUseBundleResourcePath:false]; @@ -64,9 +75,9 @@ - (id)initInBackground { AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication]; - BOOL stable = [atomApplication.arguments objectForKey:@"stable"] != nil; + BOOL useBundleResourcePath = [atomApplication.arguments objectForKey:@"dev"] == nil; - [self initWithBootstrapScript:@"window-bootstrap" background:YES alwaysUseBundleResourcePath:stable]; + [self initWithBootstrapScript:@"window-bootstrap" background:YES alwaysUseBundleResourcePath:useBundleResourcePath]; [self.window setFrame:NSMakeRect(0, 0, 0, 0) display:NO]; [self.window setExcludedFromWindowsMenu:YES]; [self.window setCollectionBehavior:NSWindowCollectionBehaviorStationary]; diff --git a/native/mac/English.lproj/MainMenu.xib b/native/mac/English.lproj/MainMenu.xib index eb83b300e..f8bcc62a8 100644 --- a/native/mac/English.lproj/MainMenu.xib +++ b/native/mac/English.lproj/MainMenu.xib @@ -2,13 +2,13 @@ 1050 - 12C54 - 2840 + 12C3103 + 3084 1187.34 625.00 com.apple.InterfaceBuilder.CocoaPlugin - 2840 + 3084 YES @@ -105,6 +105,15 @@ + + + YES + Version Info + + 2147483647 + + + YES @@ -213,6 +222,14 @@ 447 + + + _versionMenuItem + + + + 465 + terminate: @@ -323,6 +340,7 @@ + @@ -411,6 +429,11 @@ + + 464 + + + @@ -435,6 +458,7 @@ 458.IBPluginDependency 459.IBPluginDependency 460.IBPluginDependency + 464.IBPluginDependency 56.IBPluginDependency 57.IBPluginDependency @@ -460,6 +484,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -474,7 +499,7 @@ - 463 + 465 @@ -514,6 +539,17 @@ + + _versionMenuItem + NSMenuItem + + + _versionMenuItem + + _versionMenuItem + NSMenuItem + + IBProjectSource ./Classes/AtomApplication.h diff --git a/native/mac/atom.icns b/native/mac/atom.icns index 5ee78e5d4..d438c6be9 100644 Binary files a/native/mac/atom.icns and b/native/mac/atom.icns differ diff --git a/native/v8_extensions/native.mm b/native/v8_extensions/native.mm index 9ce163a30..77b924635 100644 --- a/native/v8_extensions/native.mm +++ b/native/v8_extensions/native.mm @@ -27,7 +27,8 @@ namespace v8_extensions { "exists", "read", "write", "absolute", "getAllFilePathsAsync", "traverseTree", "isDirectory", "isFile", "remove", "writeToPasteboard", "readFromPasteboard", "quit", "watchPath", "unwatchPath", "getWatchedPaths", "unwatchAllPaths", "makeDirectory", "move", "moveToTrash", "reload", "lastModified", - "md5ForPath", "exec", "getPlatform", "setWindowState", "getWindowState" + "md5ForPath", "exec", "getPlatform", "setWindowState", "getWindowState", "isMisspelled", + "getCorrectionsForMisspelling" }; CefRefPtr nativeObject = CefV8Value::CreateObject(NULL); @@ -447,41 +448,49 @@ namespace v8_extensions { context->Exit(); - stdout.fileHandleForReading.writeabilityHandler = nil; - stderr.fileHandleForReading.writeabilityHandler = nil; + stdout.fileHandleForReading.readabilityHandler = nil; + stderr.fileHandleForReading.readabilityHandler = nil; }; task.terminationHandler = ^(NSTask *) { - NSString *output = [[NSString alloc] initWithData:[[stdout fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding]; - NSString *errorOutput = [[NSString alloc] initWithData:[[stderr fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding]; - dispatch_sync(dispatch_get_main_queue(), ^() { - taskTerminatedHandle(output, errorOutput); - }); - [output release]; - [errorOutput release]; + @synchronized(task) { + NSData *outputData = [[stdout fileHandleForReading] readDataToEndOfFile]; + NSString *output = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; + NSData *errorData = [[stderr fileHandleForReading] readDataToEndOfFile]; + NSString *errorOutput = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + dispatch_sync(dispatch_get_main_queue(), ^() { + taskTerminatedHandle(output, errorOutput); + }); + [output release]; + [errorOutput release]; + } }; CefRefPtr stdoutFunction = options->GetValue("stdout"); if (stdoutFunction->IsFunction()) { - stdout.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) { - NSData *data = [fileHandle availableData]; - NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - dispatch_sync(dispatch_get_main_queue(), ^() { - outputHandle(contents, stdoutFunction); - }); - [contents release]; + stdout.fileHandleForReading.readabilityHandler = ^(NSFileHandle *fileHandle) { + @synchronized(task) { + NSData *data = [fileHandle availableData]; + NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + dispatch_sync(dispatch_get_main_queue(), ^() { + outputHandle(contents, stdoutFunction); + }); + [contents release]; + } }; } CefRefPtr stderrFunction = options->GetValue("stderr"); if (stderrFunction->IsFunction()) { - stderr.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) { - NSData *data = [fileHandle availableData]; - NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - dispatch_sync(dispatch_get_main_queue(), ^() { - outputHandle(contents, stderrFunction); - }); - [contents release]; + stderr.fileHandleForReading.readabilityHandler = ^(NSFileHandle *fileHandle) { + @synchronized(task) { + NSData *data = [fileHandle availableData]; + NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + dispatch_sync(dispatch_get_main_queue(), ^() { + outputHandle(contents, stderrFunction); + }); + [contents release]; + } }; } @@ -513,6 +522,29 @@ namespace v8_extensions { return true; } + else if (name == "isMisspelled") { + NSString *word = stringFromCefV8Value(arguments[0]); + NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:word startingAt:0]; + retval = CefV8Value::CreateBool(range.length > 0); + return true; + } + + else if (name == "getCorrectionsForMisspelling") { + NSString *misspelling = stringFromCefV8Value(arguments[0]); + NSSpellChecker *spellchecker = [NSSpellChecker sharedSpellChecker]; + NSString *language = [spellchecker language]; + NSRange range; + range.location = 0; + range.length = [misspelling length]; + NSArray *guesses = [spellchecker guessesForWordRange:range inString:misspelling language:language inSpellDocumentWithTag:0]; + CefRefPtr v8Guesses = CefV8Value::CreateArray([guesses count]); + for (int i = 0; i < [guesses count]; i++) { + v8Guesses->SetValue(i, CefV8Value::CreateString([[guesses objectAtIndex:i] UTF8String])); + } + retval = v8Guesses; + return true; + } + return false; }; diff --git a/script/atom.githubapp.com b/script/atom.githubapp.com deleted file mode 100644 index 86a5458c7..000000000 --- a/script/atom.githubapp.com +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAzLmZ/pTYsc9Wu1+nbf2ri7HjwkJG2LlwS18t+LkSUBNWH7/2 -plHjd4govWGGZSWdAR9bS7MpNIKdgypWXXIRTZxOgl91p+vm//brYZ+1fObC+cpA -/+AflzKICs0XMUdyJNUO9RNaqLYoH664cidVkgJdgVStvBxFZE8v7HmNW8R9/w6J -e0epeSCW7FnzUBZJmGJ0gw+mIQvHdqSt/57T7/TIGrpKQAZ1bWuYIwP2IteqiEiS -kmLbXbzbnQnGVD9zKfHry3XHkX550qCBtV0C4Gxnxh6QU6pa9wlgHt1iJoRWWPVS -mDOAywkMcADgP0yM8qDusxYA7rXJd+AQO51t6QIDAQABAoIBAQCifboln5yy3L+Z -bx2kJpBvC12cRSCYSe9AU6ZYYQSQ/BgCkUnCzwI9UJwNXIfgZk9MocfDuMs+u2Er -x4HPH4duU17ZCc2H36x0g1ZmIO5a2Ynt20NmFEzepCPodsouued2Jol9qcPuXs5P -/0y0hlXb0vbGfSB8SvAk4tlF12CEcXArb5ofs0ekjNiSFpfXklGEGKsi5uMr/gQB -ZKfszw13yYiHWRP1tDBH62sH6xOHFGaI5COh4WmeN0LijqYVT8om+Xgd2dPO7imJ -3Ylm5e1Fn/koISkGegp/i1BqmddWFVtUvvd/TdSi5gBqZDjYS/0It9cJCcmD0zmn -tCnwII2RAoGBAPEx3uO7uEKDcMhQkXmtjsfwnVLLYWipkjUqldPz+hdnEVchf7AF -B7/efH0x9OQtKCKIOhOsZ58NMSD0lIEnH/dsDenRBwJt9nIgzHWQ5ZaALkWFEedf -TzdjcXj64Du7M5sPtmK+VysPQRR8fTc7hP4tEFghpBN6o7r4gXokiuPdAoGBANlK -pTLqB8tbxnhGxhasVkbOrGAmAAjXj4p5kYjq739DYzDzZNyRZ8XVRepOWEKReuIe -5qxr/iRI8VeZgQjHu0Uorva03jamEs2lUo6v6igcyvPthU8a5UypueQH13tBfoYY -KZ7vkpUCmlNIKlMWmt6APx1NxBBq29vrgoFC06d9AoGBAIoYZqh3K2mRvjpCvvgu -4egiIMFtxujY5ehg6IYBU7igf3ImsOgECUrWxOoddzxJiogy/SWUeYO8VtuqjXhh -AVF1YYX08xF2CjuQAeByRLFl1JhjNzwE+uMYobiF02r/pO1sEp34own5Yuaq3DqK -bnvlK016fWnVtvbvH5riX09VAoGAWmiYe6qsO4yf84NfACgMbcGoLwkgko0+CBu0 -1gcKRyU+rCtZF/zX9JRcVrTqPChTvKmmyK7WOzquUAhqi8CuxoO66KMwogRPZokW -5I+FGdOBDiURhmxmY5Nq6NnqajpB7oE3MA32K1G97FDma1PIdDNgD00/+qS7fbFr -x2SKs/ECgYBDZnD16pXVCJ2JEzHkxZDA17N82Jltd4ju8d/kcNKBrsSZysFy00VT -eU5wC1uDVfWtw0TEXZXQqnzBGEbTZJ+nhWfrm5kimyUkmyhZOHJMMZNs9rpLfD2i -xsE/qqEKdhxZmV7bUWr+v2Grtajz4zHxyqB1YWU9qma+wKppA068BQ== ------END RSA PRIVATE KEY----- diff --git a/script/cibuild b/script/cibuild index 9860c8f30..e3e58ec23 100755 --- a/script/cibuild +++ b/script/cibuild @@ -1,15 +1,5 @@ -#!/usr/bin/env ruby +#!/bin/sh -sha = `git rev-parse HEAD`.chop -key = File.expand_path('../atom.githubapp.com', __FILE__) -system 'chmod', '600', key - -system 'ssh', - '-l', 'atom', - '-i', key, - '-o', 'StrictHostKeyChecking=no', - '-p', '6000', - 'atom.githubapp.com', - "rm -rf ~/.atom && cd /Users/atom/code/atom && git fetch -q origin && git reset -q --hard #{sha} && rake test" - -exit $?.exitstatus +set -ex +rm -rf ~/.atom +rake test diff --git a/script/copy-files-to-bundle b/script/copy-files-to-bundle index c509e7aef..67dfed4c4 100755 --- a/script/copy-files-to-bundle +++ b/script/copy-files-to-bundle @@ -38,4 +38,4 @@ for CSON_FILE in $CSON_FILES; do done; # Copy non-coffee files into bundle -rsync --archive --recursive --exclude="src/**/*.coffee" --exclude="src/**/*.cson" src static vendor spec benchmark themes "$RESOUCES_PATH" +rsync --archive --recursive --exclude="src/**/*.coffee" --exclude="src/**/*.cson" src static vendor spec benchmark themes dot-atom atom.sh "$RESOUCES_PATH" diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee index 2c34de5ee..dde4f0cc5 100644 --- a/spec/app/atom-spec.coffee +++ b/spec/app/atom-spec.coffee @@ -131,3 +131,13 @@ describe "the `atom` global", -> expect(packageStates['package-with-module']).toEqual someNumber: 1 expect(packageStates['package-with-serialize-error']).toBeUndefined() expect(console.error).toHaveBeenCalled() + + describe ".getVersion(callback)", -> + it "calls the callback with the current version number", -> + versionHandler = jasmine.createSpy("versionHandler") + atom.getVersion(versionHandler) + waitsFor -> + versionHandler.callCount > 0 + + runs -> + expect(versionHandler.argsForCall[0][0]).toMatch /^\d+\.\d+\.\d+$/ diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index 635f7d519..3510d46f4 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -741,6 +741,7 @@ describe 'Buffer', -> oldTailPosition: [4, 20] newTailPosition: [4, 20] bufferChanged: false + valid: true } observeHandler.reset() @@ -751,6 +752,7 @@ describe 'Buffer', -> oldHeadPosition: [6, 2] newHeadPosition: [6, 5] bufferChanged: true + valid: true } it "calls the given callback when the marker's tail position changes", -> @@ -762,6 +764,7 @@ describe 'Buffer', -> oldTailPosition: [4, 20] newTailPosition: [6, 2] bufferChanged: false + valid: true } observeHandler.reset() @@ -773,6 +776,7 @@ describe 'Buffer', -> oldTailPosition: [6, 2] newTailPosition: [6, 5] bufferChanged: true + valid: true } it "calls the callback when the selection's tail is cleared", -> @@ -784,6 +788,7 @@ describe 'Buffer', -> oldTailPosition: [4, 20] newTailPosition: [4, 23] bufferChanged: false + valid: true } it "only calls the callback once when both the marker's head and tail positions change due to the same operation", -> @@ -795,6 +800,7 @@ describe 'Buffer', -> oldHeadPosition: [4, 23] newHeadPosition: [4, 26] bufferChanged: true + valid: true } observeHandler.reset() @@ -806,6 +812,31 @@ describe 'Buffer', -> oldHeadPosition: [4, 26] newHeadPosition: [1, 1] bufferChanged: false + valid: true + } + + it "calls the callback with the valid flag set to false when the marker is invalidated", -> + buffer.deleteRow(4) + expect(observeHandler.callCount).toBe 1 + expect(observeHandler.argsForCall[0][0]).toEqual { + oldTailPosition: [4, 20] + newTailPosition: [4, 20] + oldHeadPosition: [4, 23] + newHeadPosition: [4, 23] + bufferChanged: true + valid: false + } + + observeHandler.reset() + buffer.undo() + expect(observeHandler.callCount).toBe 1 + expect(observeHandler.argsForCall[0][0]).toEqual { + oldTailPosition: [4, 20] + newTailPosition: [4, 20] + oldHeadPosition: [4, 23] + newHeadPosition: [4, 23] + bufferChanged: true + valid: true } it "allows the observation subscription to be cancelled", -> @@ -830,19 +861,20 @@ describe 'Buffer', -> buffer.undo() expect(buffer.getMarkerRange(marker)).toBeUndefined() - # even "stayValid" markers get destroyed properly - marker2 = buffer.markRange([[4, 20], [4, 23]], stayValid: true) + # even "invalidationStrategy: never" markers get destroyed properly + marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never') buffer.delete([[4, 15], [4, 25]]) buffer.destroyMarker(marker2) buffer.undo() expect(buffer.getMarkerRange(marker2)).toBeUndefined() describe "marker updates due to buffer changes", -> - [marker1, marker2] = [] + [marker1, marker2, marker3] = [] beforeEach -> marker1 = buffer.markRange([[4, 20], [4, 23]]) - marker2 = buffer.markRange([[4, 20], [4, 23]], stayValid: true) + marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never') + marker3 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'between') describe "when the buffer changes due to a new operation", -> describe "when the change precedes the marker range", -> @@ -874,20 +906,37 @@ describe 'Buffer', -> buffer.insert([4, 20], '...') expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]] + describe "when the invalidation strategy is 'between'", -> + it "invalidates the marker", -> + buffer.insert([4, 20], '...') + expect(buffer.getMarkerRange(marker3)).toBeUndefined() + describe "when the change is an insertion at the end of the marker range", -> it "moves the end point", -> buffer.insert([4, 23], '...') expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]] + describe "when the invalidation strategy is 'between'", -> + it "invalidates the marker", -> + buffer.insert([4, 23], '...') + expect(buffer.getMarkerRange(marker3)).toBeUndefined() + describe "when the change surrounds the marker range", -> - describe "when the marker was created with stayValid: false (the default)", -> + describe "when the marker's invalidation strategy is 'contains' (the default)", -> it "invalidates the marker", -> buffer.delete([[4, 15], [4, 25]]) expect(buffer.getMarkerRange(marker1)).toBeUndefined() buffer.undo() expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] - describe "when the marker was created with stayValid: true", -> + describe "when the marker's invalidation strategy is 'between'", -> + it "invalidates the marker", -> + buffer.delete([[4, 15], [4, 25]]) + expect(buffer.getMarkerRange(marker3)).toBeUndefined() + buffer.undo() + expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]] + + describe "when the marker's invalidation strategy is 'never'", -> it "does not invalidate the marker, but sets it to an empty range at the end of the change", -> buffer.change([[4, 15], [4, 25]], "...") expect(buffer.getMarkerRange(marker2)).toEqual [[4, 18], [4, 18]] @@ -895,14 +944,21 @@ describe 'Buffer', -> expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 23]] describe "when the change straddles the start of the marker range", -> - describe "when the marker was created with stayValid: false (the default)", -> + describe "when the marker's invalidation strategy is 'contains' (the default)", -> it "invalidates the marker", -> buffer.delete([[4, 15], [4, 22]]) expect(buffer.getMarkerRange(marker1)).toBeUndefined() buffer.undo() expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] - describe "when the marker was created with stayValid: true", -> + describe "when the marker's invalidation strategy is 'between'", -> + it "invalidates the marker", -> + buffer.delete([[4, 15], [4, 22]]) + expect(buffer.getMarkerRange(marker3)).toBeUndefined() + buffer.undo() + expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]] + + describe "when the marker's invalidation strategy is 'never'", -> it "moves the start of the marker range to the end of the change", -> buffer.delete([[4, 15], [4, 22]]) expect(buffer.getMarkerRange(marker2)).toEqual [[4, 15], [4, 16]] @@ -910,20 +966,49 @@ describe 'Buffer', -> expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] describe "when the change straddles the end of the marker range", -> - describe "when the marker was created with stayValid: false (the default)", -> + describe "when the marker's invalidation strategy is 'contains' (the default)", -> it "invalidates the marker", -> buffer.delete([[4, 22], [4, 25]]) expect(buffer.getMarkerRange(marker1)).toBeUndefined() buffer.undo() expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] - describe "when the marker was created with stayValid: true", -> + describe "when the marker's invalidation strategy is 'between'", -> + it "invalidates the marker", -> + buffer.delete([[4, 22], [4, 25]]) + expect(buffer.getMarkerRange(marker3)).toBeUndefined() + buffer.undo() + expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]] + + describe "when the marker's invalidation strategy is 'never'", -> it "moves the end of the marker range to the start of the change", -> buffer.delete([[4, 22], [4, 25]]) expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 22]] buffer.undo() expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] + describe "when the change is between the start and the end of the marker range", -> + describe "when the marker's invalidation strategy is 'contains' (the default)", -> + it "does not invalidate the marker", -> + buffer.insert([4, 21], 'x') + expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 24]] + buffer.undo() + expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] + + describe "when the marker's invalidation strategy is 'between'", -> + it "invalidates the marker", -> + buffer.insert([4, 21], 'x') + expect(buffer.getMarkerRange(marker3)).toBeUndefined() + buffer.undo() + expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]] + + describe "when the marker's invalidation strategy is 'never'", -> + it "moves the end of the marker range to the start of the change", -> + buffer.insert([4, 21], 'x') + expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 24]] + buffer.undo() + expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]] + describe "when the buffer changes due to the undo or redo of a previous operation", -> it "restores invalidated markers when undoing/redoing in the other direction", -> buffer.change([[4, 21], [4, 24]], "foo") diff --git a/spec/app/config-spec.coffee b/spec/app/config-spec.coffee index 661ef3c78..7062c4042 100644 --- a/spec/app/config-spec.coffee +++ b/spec/app/config-spec.coffee @@ -109,3 +109,27 @@ describe "Config", -> config.set("foo.bar.baz", "i'm back") expect(observeHandler).toHaveBeenCalledWith("i'm back") + + describe "initializeConfigDirectory()", -> + beforeEach -> + config.configDirPath = '/tmp/dot-atom-dir' + expect(fs.exists(config.configDirPath)).toBeFalsy() + + afterEach -> + fs.remove('/tmp/dot-atom-dir') if fs.exists('/tmp/dot-atom-dir') + + describe "when the configDirPath doesn't exist", -> + it "copies the contents of dot-atom to ~/.atom", -> + config.initializeConfigDirectory() + expect(fs.exists(config.configDirPath)).toBeTruthy() + expect(fs.exists(fs.join(config.configDirPath, 'packages'))).toBeTruthy() + expect(fs.exists(fs.join(config.configDirPath, 'snippets'))).toBeTruthy() + expect(fs.exists(fs.join(config.configDirPath, 'themes'))).toBeTruthy() + expect(fs.isFile(fs.join(config.configDirPath, 'config.cson'))).toBeTruthy() + + it "copies the bundles themes to ~/.atom", -> + config.initializeConfigDirectory() + expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-dark-ui/package.cson'))).toBeTruthy() + expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-light-ui/package.cson'))).toBeTruthy() + expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-dark-syntax.css'))).toBeTruthy() + expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-light-syntax.css'))).toBeTruthy() diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index 34f8fc74b..6f87a7ddb 100644 --- a/spec/app/display-buffer-spec.coffee +++ b/spec/app/display-buffer-spec.coffee @@ -661,6 +661,7 @@ describe "DisplayBuffer", -> newTailScreenPosition: [5, 4] newTailBufferPosition: [8, 4] bufferChanged: false + valid: true } observeHandler.reset() @@ -676,6 +677,7 @@ describe "DisplayBuffer", -> newTailScreenPosition: [5, 4] newTailBufferPosition: [8, 4] bufferChanged: true + valid: true } observeHandler.reset() @@ -691,6 +693,7 @@ describe "DisplayBuffer", -> newTailScreenPosition: [8, 4] newTailBufferPosition: [8, 4] bufferChanged: false + valid: true } observeHandler.reset() @@ -706,6 +709,7 @@ describe "DisplayBuffer", -> newTailScreenPosition: [5, 4] newTailBufferPosition: [8, 4] bufferChanged: false + valid: true } it "calls the callback whenever the marker tail's position changes in the buffer or on screen", -> @@ -721,6 +725,7 @@ describe "DisplayBuffer", -> newTailScreenPosition: [8, 20] newTailBufferPosition: [11, 20] bufferChanged: false + valid: true } observeHandler.reset() @@ -736,6 +741,40 @@ describe "DisplayBuffer", -> newTailScreenPosition: [8, 23] newTailBufferPosition: [11, 23] bufferChanged: true + valid: true + } + + it "calls the callback whenever the marker is invalidated or revalidated", -> + buffer.deleteRow(8) + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual { + oldHeadScreenPosition: [5, 10] + oldHeadBufferPosition: [8, 10] + newHeadScreenPosition: [5, 10] + newHeadBufferPosition: [8, 10] + oldTailScreenPosition: [5, 4] + oldTailBufferPosition: [8, 4] + newTailScreenPosition: [5, 4] + newTailBufferPosition: [8, 4] + bufferChanged: true + valid: false + } + + observeHandler.reset() + buffer.undo() + + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual { + oldHeadScreenPosition: [5, 10] + oldHeadBufferPosition: [8, 10] + newHeadScreenPosition: [5, 10] + newHeadBufferPosition: [8, 10] + oldTailScreenPosition: [5, 4] + oldTailBufferPosition: [8, 4] + newTailScreenPosition: [5, 4] + newTailBufferPosition: [8, 4] + bufferChanged: true + valid: true } it "does not call the callback for screen changes that don't change the position of the marker", -> diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 1cb279da1..f7a553cab 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -308,10 +308,11 @@ describe "Editor", -> spyOn(atom, "confirm") + contentsConflictedHandler = jasmine.createSpy("contentsConflictedHandler") + editSession.on 'contents-conflicted', contentsConflictedHandler fs.write(path, "a file change") - - waitsFor "file to trigger contents-changed event", (done) -> - editSession.one 'contents-conflicted', done + waitsFor -> + contentsConflictedHandler.callCount > 0 runs -> expect(atom.confirm).toHaveBeenCalled() @@ -2136,6 +2137,11 @@ describe "Editor", -> expect(editor.getSelection().isEmpty()).toBeTruthy() expect(editor.getCursorScreenPosition()).toEqual [5, 0] + it "keeps the gutter line and the editor line the same heights (regression)", -> + editor.getSelection().setBufferRange(new Range([4, 29], [7, 4])) + editor.trigger 'editor:fold-selection' + + expect(editor.gutter.find('.line-number:eq(4)').height()).toBe editor.renderedLines.find('.line:eq(4)').height() describe "when a fold placeholder line is clicked", -> it "removes the associated fold and places the cursor at its beginning", -> @@ -2859,3 +2865,17 @@ describe "Editor", -> 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 + spyOn(atom, 'showSaveDialog').andCallFake (callback) -> saveDialogCallback = callback + spyOn(fs, 'write') + + editor.trigger 'editor:save-debug-snapshot' + + expect(atom.showSaveDialog).toHaveBeenCalled() + saveDialogCallback('/tmp/state') + expect(fs.write).toHaveBeenCalled() + expect(fs.write.argsForCall[0][0]).toBe '/tmp/state' + expect(typeof fs.write.argsForCall[0][1]).toBe 'string' diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 368d095cb..15205bbc1 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -89,7 +89,6 @@ describe "RootView", -> expect(rootView.getEditors().length).toBe 0 viewState = rootView.serialize() - console.log viewState rootView.deactivate() window.rootView = RootView.deserialize(viewState) diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index f229e426e..32f9397f1 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -116,3 +116,16 @@ describe "Window", -> window.shutdown() window.shutdown() expect(atom.setRootViewStateForPath.callCount).toBe 1 + + describe ".installAtomCommand(commandPath)", -> + commandPath = '/tmp/installed-atom-command/atom' + + afterEach -> + fs.remove(commandPath) if fs.exists(commandPath) + + describe "when the command path doesn't exist", -> + it "copies atom.sh to the specified path", -> + expect(fs.exists(commandPath)).toBeFalsy() + window.installAtomCommand(commandPath) + expect(fs.exists(commandPath)).toBeTruthy() + expect(fs.read(commandPath).length).toBeGreaterThan 1 diff --git a/spec/fixtures/gfm.md b/spec/fixtures/gfm.md new file mode 100644 index 000000000..9d1859f8d --- /dev/null +++ b/spec/fixtures/gfm.md @@ -0,0 +1 @@ + * First list item diff --git a/spec/stdlib/child-process-spec.coffee b/spec/stdlib/child-process-spec.coffee index e0e002f00..e528aebec 100644 --- a/spec/stdlib/child-process-spec.coffee +++ b/spec/stdlib/child-process-spec.coffee @@ -114,7 +114,7 @@ describe 'Child Processes', -> afterEach -> jasmine.getEnv().defaultTimeoutInterval = originalTimeout - it "does not block indefinitally on stdout or stderr callbacks (regression)", -> + it "does not block indefinitely on stdout or stderr callbacks (regression)", -> output = [] waitsForPromise -> diff --git a/spec/stdlib/onig-reg-exp-spec.coffee b/spec/stdlib/onig-reg-exp-spec.coffee index 14955c317..7f43d47c7 100644 --- a/spec/stdlib/onig-reg-exp-spec.coffee +++ b/spec/stdlib/onig-reg-exp-spec.coffee @@ -3,13 +3,13 @@ OnigRegExp = require 'onig-reg-exp' describe "OnigRegExp", -> describe ".search(string, index)", -> it "returns an array of the match and all capture groups", -> - regex = new OnigRegExp("\\w(\\d+)") + regex = OnigRegExp.create("\\w(\\d+)") result = regex.search("----a123----") expect(result).toEqual ["a123", "123"] expect(result.index).toBe 4 expect(result.indices).toEqual [4, 5] it "returns null if it does not match", -> - regex = new OnigRegExp("\\w(\\d+)") + regex = OnigRegExp.create("\\w(\\d+)") result = regex.search("--------") expect(result).toBeNull() diff --git a/src/app/atom.coffee b/src/app/atom.coffee index 532dd558c..b5149622e 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -94,8 +94,8 @@ _.extend atom, open: (args...) -> @sendMessageToBrowserProcess('open', args) - openUnstable: (args...) -> - @sendMessageToBrowserProcess('openUnstable', args) + openDev: (args...) -> + @sendMessageToBrowserProcess('openDev', args) newWindow: (args...) -> @sendMessageToBrowserProcess('newWindow', args) @@ -179,3 +179,13 @@ _.extend atom, getUpdateStatus: (callback) -> @sendMessageToBrowserProcess('getUpdateStatus', [], callback) + + requireUserInitScript: -> + userInitScriptPath = fs.join(config.configDirPath, "user.coffee") + try + require userInitScriptPath if fs.isFile(userInitScriptPath) + catch error + console.error "Failed to load `#{userInitScriptPath}`", error.stack, error + + getVersion: (callback) -> + @sendMessageToBrowserProcess('getVersion', null, callback) diff --git a/src/app/buffer-change-operation.coffee b/src/app/buffer-change-operation.coffee index 928b4a07b..b3ffda35e 100644 --- a/src/app/buffer-change-operation.coffee +++ b/src/app/buffer-change-operation.coffee @@ -107,5 +107,4 @@ class BufferChangeOperation if validMarker = @buffer.validMarkers[id] validMarker.setRange(previousRange) else if invalidMarker = @buffer.invalidMarkers[id] - @buffer.validMarkers[id] = invalidMarker - + invalidMarker.revalidate() diff --git a/src/app/buffer-marker.coffee b/src/app/buffer-marker.coffee index 245e0142c..199171595 100644 --- a/src/app/buffer-marker.coffee +++ b/src/app/buffer-marker.coffee @@ -8,9 +8,10 @@ class BufferMarker headPosition: null tailPosition: null suppressObserverNotification: false - stayValid: false + invalidationStrategy: null - constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) -> + constructor: ({@id, @buffer, range, @invalidationStrategy, noTail, reverse}) -> + @invalidationStrategy ?= 'contains' @setRange(range, {noTail, reverse}) setRange: (range, options={}) -> @@ -71,23 +72,30 @@ class BufferMarker newTailPosition = @getTailPosition() @notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false}) - tryToInvalidate: (oldRange) -> - containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true) - containsEnd = oldRange.containsPoint(@getEndPosition(), exclusive: true) - return unless containsEnd or containsStart + tryToInvalidate: (changedRange) -> + betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false) + containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true) + containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true) - if @stayValid - previousRange = @getRange() - if containsStart and containsEnd - @setRange([oldRange.end, oldRange.end]) - else if containsStart - @setRange([oldRange.end, @getEndPosition()]) - else - @setRange([@getStartPosition(), oldRange.start]) - [@id, previousRange] - else - @invalidate() - [@id] + switch @invalidationStrategy + when 'between' + if betweenStartAndEnd or containsStart or containsEnd + @invalidate() + [@id] + when 'contains' + if containsStart or containsEnd + @invalidate() + [@id] + when 'never' + if containsStart or containsEnd + previousRange = @getRange() + if containsStart and containsEnd + @setRange([changedRange.end, changedRange.end]) + else if containsStart + @setRange([changedRange.end, @getEndPosition()]) + else + @setRange([@getStartPosition(), changedRange.start]) + [@id, previousRange] handleBufferChange: (bufferChange) -> @consolidateObserverNotifications true, => @@ -113,23 +121,31 @@ class BufferMarker [newRow, newColumn] observe: (callback) -> - @on 'position-changed', callback + @on 'changed', callback cancel: => @unobserve(callback) unobserve: (callback) -> - @off 'position-changed', callback + @off 'changed', callback containsPoint: (point) -> @getRange().containsPoint(point) - notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) -> + notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) -> return if @suppressObserverNotification - return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition) + + if newHeadPosition? and newTailPosition? + return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition) + else if newHeadPosition? + return if _.isEqual(newHeadPosition, oldHeadPosition) + else if newTailPosition? + return if _.isEqual(newTailPosition, oldTailPosition) + oldHeadPosition ?= @getHeadPosition() newHeadPosition ?= @getHeadPosition() oldTailPosition ?= @getTailPosition() newTailPosition ?= @getTailPosition() - @trigger 'position-changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} + valid = @buffer.validMarkers[@id]? + @trigger 'changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid} consolidateObserverNotifications: (bufferChanged, fn) -> @suppressObserverNotification = true @@ -141,8 +157,14 @@ class BufferMarker @suppressObserverNotification = false @notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) - invalidate: (preserve) -> + invalidate: -> delete @buffer.validMarkers[@id] @buffer.invalidMarkers[@id] = this + @notifyObservers(bufferChanged: true) + + revalidate: -> + delete @buffer.invalidMarkers[@id] + @buffer.validMarkers[@id] = this + @notifyObservers(bufferChanged: true) _.extend BufferMarker.prototype, EventEmitter diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 0200bddeb..0cf84f7a7 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -419,11 +419,6 @@ class Buffer return match[0][0] != '\t' undefined - logLines: (start=0, end=@getLastRow())-> - for row in [start..end] - line = @lineForRow(row) - console.log row, line, line.length - getRepo: -> @project?.repo checkoutHead: -> @@ -442,4 +437,15 @@ class Buffer fileExists: -> @file.exists() + logLines: (start=0, end=@getLastRow())-> + for row in [start..end] + line = @lineForRow(row) + console.log row, line, line.length + + getDebugSnapshot: -> + lines = ['Buffer:'] + for row in [0..@getLastRow()] + lines.push "#{row}: #{@lineForRow(row)}" + lines.join('\n') + _.extend(Buffer.prototype, EventEmitter) diff --git a/src/app/config.coffee b/src/app/config.coffee index cf6f97b27..2940b33f4 100644 --- a/src/app/config.coffee +++ b/src/app/config.coffee @@ -3,7 +3,6 @@ _ = require 'underscore' EventEmitter = require 'event-emitter' configDirPath = fs.absolute("~/.atom") -userInitScriptPath = fs.join(configDirPath, "user.coffee") bundledPackagesDirPath = fs.join(resourcePath, "src/packages") bundledThemesDirPath = fs.join(resourcePath, "themes") vendoredPackagesDirPath = fs.join(resourcePath, "vendor/packages") @@ -30,11 +29,29 @@ class Config @configFilePath = fs.resolve(configDirPath, 'config', ['json', 'cson']) @configFilePath ?= fs.join(configDirPath, 'config.cson') + initializeConfigDirectory: -> + return if fs.exists(@configDirPath) + + fs.makeDirectory(@configDirPath) + + templateConfigDirPath = fs.resolve(window.resourcePath, 'dot-atom') + + onConfigDirFile = (path) => + templatePath = fs.join(templateConfigDirPath, path) + configPath = fs.join(@configDirPath, path) + fs.write(configPath, fs.read(templatePath)) + fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true) + + configThemeDirPath = fs.join(@configDirPath, 'themes') + onThemeDirFile = (path) -> + templatePath = fs.join(bundledThemesDirPath, path) + configPath = fs.join(configThemeDirPath, path) + fs.write(configPath, fs.read(templatePath)) + fs.traverseTree(bundledThemesDirPath, onThemeDirFile, (path) -> true) + load: -> + @initializeConfigDirectory() @loadUserConfig() - @requireUserInitScript() - - loadUserConfig: -> if fs.exists(@configFilePath) @@ -81,10 +98,4 @@ class Config save: -> fs.writeObject(@configFilePath, @settings) - requireUserInitScript: -> - try - require userInitScriptPath if fs.exists(userInitScriptPath) - catch error - console.error "Failed to load `#{userInitScriptPath}`", error.stack, error - _.extend Config.prototype, EventEmitter diff --git a/src/app/display-buffer-marker.coffee b/src/app/display-buffer-marker.coffee index d4ce6936f..c8ed1648a 100644 --- a/src/app/display-buffer-marker.coffee +++ b/src/app/display-buffer-marker.coffee @@ -7,6 +7,7 @@ class DisplayBufferMarker bufferMarkerSubscription: null headScreenPosition: null tailScreenPosition: null + valid: true constructor: ({@id, @displayBuffer}) -> @buffer = @displayBuffer.buffer @@ -57,11 +58,11 @@ class DisplayBufferMarker observe: (callback) -> @observeBufferMarkerIfNeeded() - @on 'position-changed', callback + @on 'changed', callback cancel: => @unobserve(callback) unobserve: (callback) -> - @off 'position-changed', callback + @off 'changed', callback @unobserveBufferMarkerIfNeeded() observeBufferMarkerIfNeeded: -> @@ -69,13 +70,14 @@ class DisplayBufferMarker @getHeadScreenPosition() # memoize current value @getTailScreenPosition() # memoize current value @bufferMarkerSubscription = - @buffer.observeMarker @id, ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) => + @buffer.observeMarker @id, ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) => @notifyObservers oldHeadBufferPosition: oldHeadPosition newHeadBufferPosition: newHeadPosition oldTailBufferPosition: oldTailPosition newTailBufferPosition: newTailPosition bufferChanged: bufferChanged + valid: valid @displayBuffer.markers[@id] = this unobserveBufferMarkerIfNeeded: -> @@ -83,28 +85,37 @@ class DisplayBufferMarker @bufferMarkerSubscription.cancel() delete @displayBuffer.markers[@id] - notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged}) -> + notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) -> oldHeadScreenPosition = @getHeadScreenPosition() - @headScreenPosition = null - newHeadScreenPosition = @getHeadScreenPosition() - + newHeadScreenPosition = oldHeadScreenPosition oldTailScreenPosition = @getTailScreenPosition() - @tailScreenPosition = null - newTailScreenPosition = @getTailScreenPosition() + newTailScreenPosition = oldTailScreenPosition + valid ?= true - return if _.isEqual(newHeadScreenPosition, oldHeadScreenPosition) and _.isEqual(newTailScreenPosition, oldTailScreenPosition) + if valid + @headScreenPosition = null + newHeadScreenPosition = @getHeadScreenPosition() + @tailScreenPosition = null + newTailScreenPosition = @getTailScreenPosition() + + validChanged = valid isnt @valid + headScreenPositionChanged = not _.isEqual(newHeadScreenPosition, oldHeadScreenPosition) + tailScreenPositionChanged = not _.isEqual(newTailScreenPosition, oldTailScreenPosition) + return unless validChanged or headScreenPositionChanged or tailScreenPositionChanged oldHeadBufferPosition ?= @getHeadBufferPosition() - newHeadBufferPosition = @getHeadBufferPosition() + newHeadBufferPosition = @getHeadBufferPosition() ? oldHeadBufferPosition oldTailBufferPosition ?= @getTailBufferPosition() - newTailBufferPosition = @getTailBufferPosition() + newTailBufferPosition = @getTailBufferPosition() ? oldTailBufferPosition + @valid = valid - @trigger 'position-changed', { + @trigger 'changed', { oldHeadScreenPosition, newHeadScreenPosition, oldTailScreenPosition, newTailScreenPosition, oldHeadBufferPosition, newHeadBufferPosition, oldTailBufferPosition, newTailBufferPosition, bufferChanged + valid } _.extend DisplayBufferMarker.prototype, EventEmitter diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index 4abe7adcc..0f24a235d 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -332,8 +332,9 @@ class DisplayBuffer getMarkers: -> _.values(@markers) - markScreenRange: (screenRange) -> - @markBufferRange(@bufferRangeForScreenRange(screenRange)) + markScreenRange: (args...) -> + bufferRange = @bufferRangeForScreenRange(args.shift()) + @markBufferRange(bufferRange, args...) markBufferRange: (args...) -> @buffer.markRange(args...) @@ -419,4 +420,10 @@ class DisplayBuffer logLines: (start, end) -> @lineMap.logLines(start, end) + getDebugSnapshot: -> + lines = ["Display Buffer:"] + for screenLine, row in @lineMap.linesForScreenRows(0, @getLastRow()) + lines.push "#{row}: #{screenLine.text}" + lines.join('\n') + _.extend DisplayBuffer.prototype, EventEmitter diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 77e8360f2..b160d5468 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -538,11 +538,11 @@ class EditSession _.last(@cursors) addCursorAtScreenPosition: (screenPosition) -> - marker = @markScreenPosition(screenPosition, stayValid: true) + marker = @markScreenPosition(screenPosition, invalidationStrategy: 'never') @addSelection(marker).cursor addCursorAtBufferPosition: (bufferPosition) -> - marker = @markBufferPosition(bufferPosition, stayValid: true) + marker = @markBufferPosition(bufferPosition, invalidationStrategy: 'never') @addSelection(marker).cursor addCursor: (marker) -> @@ -565,7 +565,7 @@ class EditSession selection addSelectionForBufferRange: (bufferRange, options={}) -> - options = _.defaults({stayValid: true}, options) + options = _.defaults({invalidationStrategy: 'never'}, options) marker = @markBufferRange(bufferRange, options) @addSelection(marker) @@ -820,5 +820,11 @@ class EditSession @displayBuffer.tokenizedBuffer.resetScreenLines() grammarChanged + getDebugSnapshot: -> + [ + @displayBuffer.getDebugSnapshot() + @displayBuffer.tokenizedBuffer.getDebugSnapshot() + ].join('\n\n') + _.extend(EditSession.prototype, EventEmitter) _.extend(EditSession.prototype, Subscriber) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index c4fcf1bdb..5b5bc6502 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -19,7 +19,7 @@ class Editor extends View autosave: false autoIndent: true autoIndentOnPaste: false - nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?" + nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-" @nextEditorId: 1 @@ -194,6 +194,7 @@ class Editor extends View '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 documentation = {} for name, method of editorBindings @@ -1226,16 +1227,9 @@ class Editor extends View lineElementForScreenRow: (screenRow) -> @renderedLines.children(":eq(#{screenRow - @firstRenderedScreenRow})") - logScreenLines: (start, end) -> - @activeEditSession.logScreenLines(start, end) - toggleLineCommentsInSelection: -> @activeEditSession.toggleLineCommentsInSelection() - logRenderedLines: -> - @renderedLines.find('.line').each (n) -> - console.log n, $(this).text() - pixelPositionForBufferPosition: (position) -> @pixelPositionForScreenPosition(@screenPositionForBufferPosition(position)) @@ -1348,3 +1342,29 @@ class Editor extends View copyPathToPasteboard: -> path = @getPath() pasteboard.write(path) if path? + + saveDebugSnapshot: -> + atom.showSaveDialog (path) => + fs.write(path, @getDebugSnapshot()) if path + + getDebugSnapshot: -> + [ + "Debug Snapshot: #{@getPath()}" + @getRenderedLinesDebugSnapshot() + @activeEditSession.getDebugSnapshot() + @getBuffer().getDebugSnapshot() + ].join('\n\n') + + getRenderedLinesDebugSnapshot: -> + lines = ['Rendered Lines:'] + firstRenderedScreenRow = @firstRenderedScreenRow + @renderedLines.find('.line').each (n) -> + lines.push "#{firstRenderedScreenRow + n}: #{$(this).text()}" + lines.join('\n') + + logScreenLines: (start, end) -> + @activeEditSession.logScreenLines(start, end) + + logRenderedLines: -> + @renderedLines.find('.line').each (n) -> + console.log n, $(this).text() diff --git a/src/app/git.coffee b/src/app/git.coffee index 2b46fd4ee..866e2977f 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -24,7 +24,7 @@ class Git ignore: 1 << 14 constructor: (path, options={}) -> - @repo = new GitRepository(path) + @repo = GitRepository.open(path) refreshIndexOnFocus = options.refreshIndexOnFocus ? true if refreshIndexOnFocus $ = require 'jquery' diff --git a/src/app/grammar-view.coffee b/src/app/grammar-view.coffee index b20ef13e5..22963375b 100644 --- a/src/app/grammar-view.coffee +++ b/src/app/grammar-view.coffee @@ -22,15 +22,26 @@ class GrammarView extends SelectList itemForElement: (grammar) -> if grammar is @currentGrammar - grammarClass = 'current-grammar' + grammarClass = 'active-item' else - grammarClass = 'grammar' + grammarClass = 'inactive-item' $$ -> @li grammar.name, class: grammarClass populate: -> grammars = new Array(syntax.grammars...) + grammars.sort (grammarA, grammarB) -> + if grammarA.scopeName is 'text.plain' + -1 + else if grammarB.scopeName is 'text.plain' + 1 + else if grammarA.name < grammarB.name + -1 + else if grammarA.name > grammarB.name + 1 + else + 0 grammars.unshift(@autoDetect) @setArray(grammars) diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 9639e93df..695e733d1 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -20,14 +20,14 @@ class Keymap 'meta-n': 'new-window' 'meta-,': 'open-user-configuration' 'meta-o': 'open' - 'meta-O': 'open-unstable' + 'meta-O': 'open-dev' 'meta-w': 'core:close' 'alt-meta-i': 'toggle-dev-tools' $(document).command 'new-window', => atom.newWindow() $(document).command 'open-user-configuration', => atom.open(config.configDirPath) $(document).command 'open', => atom.open() - $(document).command 'open-unstable', => atom.openUnstable() + $(document).command 'open-dev', => atom.openDev() loadBundledKeymaps: -> @loadDirectory(require.resolve('keymaps')) diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index 3e1c1512c..848ae826f 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -30,13 +30,13 @@ class LanguageMode buffer = @editSession.buffer commentStartRegexString = _.escapeRegExp(commentStartString).replace(/(\s+)$/, '($1)?') - commentStartRegex = new OnigRegExp("^(\\s*)(#{commentStartRegexString})") + commentStartRegex = OnigRegExp.create("^(\\s*)(#{commentStartRegexString})") shouldUncomment = commentStartRegex.test(buffer.lineForRow(start)) if commentEndString = syntax.getProperty(scopes, "editor.commentEnd") if shouldUncomment commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '($1)?') - commentEndRegex = new OnigRegExp("(#{commentEndRegexString})(\\s*)$") + commentEndRegex = OnigRegExp.create("(#{commentEndRegexString})(\\s*)$") startMatch = commentStartRegex.search(buffer.lineForRow(start)) endMatch = commentEndRegex.search(buffer.lineForRow(end)) if startMatch and endMatch @@ -78,7 +78,7 @@ class LanguageMode continue if @editSession.isBufferRowBlank(row) indentation = @editSession.indentationForBufferRow(row) if indentation <= startIndentLevel - includeRowInFold = indentation == startIndentLevel and @foldEndRegexForScopes(scopes).search(@editSession.lineForBufferRow(row)) + includeRowInFold = indentation == startIndentLevel and @foldEndRegexForScopes(scopes)?.search(@editSession.lineForBufferRow(row)) foldEndRow = row if includeRowInFold break @@ -152,12 +152,12 @@ class LanguageMode increaseIndentRegexForScopes: (scopes) -> if increaseIndentPattern = syntax.getProperty(scopes, 'editor.increaseIndentPattern') - new OnigRegExp(increaseIndentPattern) + OnigRegExp.create(increaseIndentPattern) decreaseIndentRegexForScopes: (scopes) -> if decreaseIndentPattern = syntax.getProperty(scopes, 'editor.decreaseIndentPattern') - new OnigRegExp(decreaseIndentPattern) + OnigRegExp.create(decreaseIndentPattern) foldEndRegexForScopes: (scopes) -> if foldEndPattern = syntax.getProperty(scopes, 'editor.foldEndPattern') - new OnigRegExp(foldEndPattern) + OnigRegExp.create(foldEndPattern) diff --git a/src/app/project.coffee b/src/app/project.coffee index 61913a62f..9b8aa380d 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -194,7 +194,7 @@ class Project ChildProcess.exec command , bufferLines: true, stdout: (data) -> lines = data.split('\n') - lines.pop() # the last segment is a spurios '' because data always ends in \n due to bufferLines: true + lines.pop() # the last segment is a spurious '' because data always ends in \n due to bufferLines: true for line in lines readPath(line) if state is 'readingPath' readLine(line) if state is 'readingLines' diff --git a/src/app/range.coffee b/src/app/range.coffee index 136f52f15..a3870ea66 100644 --- a/src/app/range.coffee +++ b/src/app/range.coffee @@ -57,7 +57,11 @@ class Range else otherRange.intersectsWith(this) - containsPoint: (point, { exclusive } = {}) -> + containsRange: (otherRange, {exclusive} = {}) -> + { start, end } = Range.fromObject(otherRange) + @containsPoint(start, {exclusive}) and @containsPoint(end, {exclusive}) + + containsPoint: (point, {exclusive} = {}) -> point = Point.fromObject(point) if exclusive point.isGreaterThan(@start) and point.isLessThan(@end) diff --git a/src/app/text-mate-grammar.coffee b/src/app/text-mate-grammar.coffee index 9cc6d105d..01acb2f77 100644 --- a/src/app/text-mate-grammar.coffee +++ b/src/app/text-mate-grammar.coffee @@ -28,7 +28,7 @@ class TextMateGrammar constructor: ({ @name, @fileTypes, @scopeName, patterns, repository, @foldingStopMarker, firstLineMatch}) -> @initialRule = new Rule(this, {@scopeName, patterns}) @repository = {} - @firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch + @firstLineRegex = OnigRegExp.create(firstLineMatch) if firstLineMatch @fileTypes ?= [] for name, data of repository @@ -111,7 +111,7 @@ class Rule regex = pattern.regexSource regexes.push regex if regex - regexScanner = new OnigScanner(regexes) + regexScanner = OnigScanner.create(regexes) regexScanner.patterns = patterns @scannersByBaseGrammarName[baseGrammar.name] = regexScanner unless anchored regexScanner diff --git a/src/app/text-mate-package.coffee b/src/app/text-mate-package.coffee index 2a54d12fe..c4901cc02 100644 --- a/src/app/text-mate-package.coffee +++ b/src/app/text-mate-package.coffee @@ -73,15 +73,27 @@ class TextMatePackage extends Package scopedProperties + readObjectFromPath: (path, callback) -> + object = null + error = null + if fs.isObjectPath(path) + object = fs.readObject(path) + else + plist.parseString fs.read(path), (e, data) -> + error = e + object = data[0] + error = throw new Error("Failed to load object at path `#{path}`") unless object + callback(error, object) + getTextMatePreferenceObjects: -> preferenceObjects = [] if fs.exists(@preferencesPath) for preferencePath in fs.list(@preferencesPath) - plist.parseString fs.read(preferencePath), (e, data) => + @readObjectFromPath preferencePath, (e, object) => if e console.warn "Failed to parse preference at path '#{preferencePath}'", e.stack else - preferenceObjects.push(data[0]) + preferenceObjects.push(object) preferenceObjects propertiesFromTextMateSettings: (textMateSettings) -> diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index 9e0930bf0..70e982e28 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -226,4 +226,10 @@ class TokenizedBuffer line = @lineForScreenRow(row).text console.log row, line, line.length + getDebugSnapshot: -> + lines = ["Tokenized Buffer:"] + for screenLine, row in @linesForScreenRows(0, @getLastRow()) + lines.push "#{row}: #{screenLine.text}" + lines.join('\n') + _.extend(TokenizedBuffer.prototype, EventEmitter) diff --git a/src/app/window.coffee b/src/app/window.coffee index 2895d68f3..d814ba22c 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -1,5 +1,6 @@ fs = require 'fs' $ = require 'jquery' +ChildProcess = require 'child-process' require 'jquery-extensions' require 'underscore-extensions' require 'space-pen-extensions' @@ -39,6 +40,11 @@ window.setUpEnvironment = -> # This method is only called when opening a real application window window.startup = -> + if fs.isDirectory('/opt/boxen') + installAtomCommand('/opt/boxen/bin/atom') + else + installAtomCommand('/opt/github/bin/atom') + handleWindowEvents() config.load() atom.loadTextPackage() @@ -47,12 +53,10 @@ window.startup = -> atom.loadThemes() atom.loadPackages() keymap.loadUserKeymaps() + atom.requireUserInitScript() $(window).on 'beforeunload', -> shutdown(); false $(window).focus() - pathToOpen = atom.getPathToOpen() - rootView.open(pathToOpen) if !pathToOpen or fs.isFile(pathToOpen) - window.shutdown = -> return if not project and not rootView atom.setWindowState('pathToOpen', project.getPath()) @@ -65,6 +69,14 @@ window.shutdown = -> window.rootView = null window.project = null +window.installAtomCommand = (commandPath) -> + return if fs.exists(commandPath) + + bundledCommandPath = fs.resolve(window.resourcePath, 'atom.sh') + if bundledCommandPath? + fs.write(commandPath, fs.read(bundledCommandPath)) + ChildProcess.exec("chmod u+x '#{commandPath}'") + window.handleWindowEvents = -> $(window).on 'core:close', => window.close() $(window).command 'window:close', => window.close() @@ -76,12 +88,14 @@ window.buildProjectAndRootView = -> RootView = require 'root-view' Project = require 'project' - windowState = atom.getRootViewStateForPath(atom.getPathToOpen()) - if windowState?.project? - window.project = deserialize(windowState.project) - window.rootView = deserialize(windowState.rootView) - window.project ?= new Project(atom.getPathToOpen()) - window.rootView ?= new RootView + pathToOpen = atom.getPathToOpen() + windowState = atom.getRootViewStateForPath(pathToOpen) ? {} + window.project = deserialize(windowState.project) ? new Project(pathToOpen) + window.rootView = deserialize(windowState.rootView) ? new RootView + + if !windowState.rootView and (!pathToOpen or fs.isFile(pathToOpen)) + rootView.open(pathToOpen) + $(rootViewParentSelector).append(rootView) window.stylesheetElementForId = (id) -> @@ -126,7 +140,7 @@ window.registerDeserializer = (klass) -> deserializers[klass.name] = klass window.deserialize = (state) -> - deserializers[state.deserializer]?.deserialize(state) + deserializers[state?.deserializer]?.deserialize(state) window.measure = (description, fn) -> start = new Date().getTime() diff --git a/src/packages/autocomplete/lib/autocomplete.coffee b/src/packages/autocomplete/lib/autocomplete.coffee index d98ed5a75..fc0e3000b 100644 --- a/src/packages/autocomplete/lib/autocomplete.coffee +++ b/src/packages/autocomplete/lib/autocomplete.coffee @@ -7,4 +7,3 @@ module.exports = rootView.eachEditor (editor) => if editor.attached and not editor.mini @autoCompleteViews.push new AutocompleteView(editor) - diff --git a/src/packages/command-panel/lib/command-panel-view.coffee b/src/packages/command-panel/lib/command-panel-view.coffee index 0e14cfb79..fae00dfe0 100644 --- a/src/packages/command-panel/lib/command-panel-view.coffee +++ b/src/packages/command-panel/lib/command-panel-view.coffee @@ -11,7 +11,7 @@ module.exports = class CommandPanelView extends View @content: -> @div class: 'command-panel tool-panel', => - @div class: 'loading', outlet: 'loadingMessage' + @div class: 'loading is-loading', outlet: 'loadingMessage', 'Searching...' @div class: 'header', outlet: 'previewHeader', => @ul outlet: 'expandCollapse', class: 'expand-collapse', => @li class: 'expand', 'Expand All' @@ -53,6 +53,7 @@ class CommandPanelView extends View @previewList.hide() @previewHeader.hide() @errorMessages.hide() + @loadingMessage.hide() @prompt.iconSize(@miniEditor.getFontSize()) @history = state.history ? [] @@ -88,14 +89,7 @@ class CommandPanelView extends View @miniEditor.focus() toggleLoading: -> - if @loadingMessage.hasClass 'is-loading' - @loadingMessage.removeClass 'is-loading' - @loadingMessage.html '' - @loadingMessage.hide() - else - @loadingMessage.addClass 'is-loading' - @loadingMessage.html 'Searching...' - @loadingMessage.show() + @loadingMessage.toggle() onExpandAll: (event) => @previewList.expandAllPaths() diff --git a/src/packages/command-panel/spec/command-panel-spec.coffee b/src/packages/command-panel/spec/command-panel-spec.coffee index 2ae732738..e6005c4be 100644 --- a/src/packages/command-panel/spec/command-panel-spec.coffee +++ b/src/packages/command-panel/spec/command-panel-spec.coffee @@ -3,7 +3,7 @@ CommandPanelView = require 'command-panel/lib/command-panel-view' _ = require 'underscore' describe "CommandPanel", -> - [editor, buffer, commandPanel, CommandPanel] = [] + [editor, buffer, commandPanel] = [] beforeEach -> window.rootView = new RootView diff --git a/src/packages/editor-stats/lib/stats-tracker.coffee b/src/packages/editor-stats/lib/stats-tracker.coffee index d7cc8d1d8..2673a75b3 100644 --- a/src/packages/editor-stats/lib/stats-tracker.coffee +++ b/src/packages/editor-stats/lib/stats-tracker.coffee @@ -2,7 +2,7 @@ module.exports = class StatsTracker startDate: new Date hours: 6 - eventLog: [] + eventLog: {} constructor: -> date = new Date(@startDate) @@ -16,7 +16,7 @@ class StatsTracker rootView.on 'mouseup', => @track() clear: -> - @eventLog = [] + @eventLog = {} track: -> date = new Date diff --git a/src/packages/editor-stats/spec/editor-stats-spec.coffee b/src/packages/editor-stats/spec/editor-stats-spec.coffee index 3015b3ce8..5d8708e42 100644 --- a/src/packages/editor-stats/spec/editor-stats-spec.coffee +++ b/src/packages/editor-stats/spec/editor-stats-spec.coffee @@ -1,9 +1,10 @@ $ = require 'jquery' +_ = require 'underscore' RootView = require 'root-view' EditorStats = require 'editor-stats/lib/editor-stats-view' describe "EditorStats", -> - [editorStats, time] = [] + [editorStats] = [] simulateKeyUp = (key) -> e = $.Event "keydown", keyCode: key.charCodeAt(0) @@ -16,26 +17,22 @@ describe "EditorStats", -> beforeEach -> window.rootView = new RootView rootView.open('sample.js') - - date = new Date() - mins = date.getMinutes() - hours = date.getHours() - - mins = if mins == 60 then '01' else mins + 1 - time = "#{hours}:#{mins}" - editorStats = window.loadPackage('editor-stats').packageMain.stats describe "when a keyup event is triggered", -> + beforeEach -> + expect(_.values(editorStats.eventLog)).not.toContain 1 + expect(_.values(editorStats.eventLog)).not.toContain 2 + it "records the number of times a keyup is triggered", -> simulateKeyUp('a') - expect(editorStats.eventLog[time]).toBe 1 + expect(_.values(editorStats.eventLog)).toContain 1 simulateKeyUp('b') - expect(editorStats.eventLog[time]).toBe 2 + expect(_.values(editorStats.eventLog)).toContain 2 describe "when a mouseup event is triggered", -> it "records the number of times a mouseup is triggered", -> simulateClick() - expect(editorStats.eventLog[time]).toBe 1 + expect(_.values(editorStats.eventLog)).toContain 1 simulateClick() - expect(editorStats.eventLog[time]).toBe 2 + expect(_.values(editorStats.eventLog)).toContain 2 diff --git a/src/packages/gfm.tmbundle/Preferences/indent.cson b/src/packages/gfm.tmbundle/Preferences/indent.cson new file mode 100644 index 000000000..113cdcb96 --- /dev/null +++ b/src/packages/gfm.tmbundle/Preferences/indent.cson @@ -0,0 +1,4 @@ +'name': 'Indent' +'scope': 'source.gfm' +'settings': + 'increaseIndentPattern': '^\\s*([\\*\\+-])[ \\t]+' diff --git a/src/packages/gfm.tmbundle/spec/gfm-spec.coffee b/src/packages/gfm.tmbundle/spec/gfm-spec.coffee index 42b739832..db307ab65 100644 --- a/src/packages/gfm.tmbundle/spec/gfm-spec.coffee +++ b/src/packages/gfm.tmbundle/spec/gfm-spec.coffee @@ -132,3 +132,10 @@ describe "GitHub Flavored Markdown grammar", -> {tokens} = grammar.tokenizeLine("> Quotation") expect(tokens[0]).toEqual value: ">", scopes: ["source.gfm", "support.quote.gfm"] expect(tokens[1]).toEqual value: " Quotation", scopes: ["source.gfm", "comment.quote.gfm"] + + describe "auto indent", -> + it "indents newlines entered after list lines", -> + config.set('editor.autoIndent', true) + editSession = fixturesProject.buildEditSessionForPath('gfm.md') + editSession.insertNewlineBelow() + expect(editSession.buffer.lineForRow(1)).toBe ' ' diff --git a/src/packages/package-generator/keymaps/package-generator.cson b/src/packages/package-generator/keymaps/package-generator.cson deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/packages/spell-check/keymaps/spell-check.cson b/src/packages/spell-check/keymaps/spell-check.cson new file mode 100644 index 000000000..fd34ad96e --- /dev/null +++ b/src/packages/spell-check/keymaps/spell-check.cson @@ -0,0 +1,2 @@ +'.editor': + 'meta-0': 'editor:correct-misspelling' diff --git a/src/packages/spell-check/lib/corrections-view.coffee b/src/packages/spell-check/lib/corrections-view.coffee new file mode 100644 index 000000000..b0041a8c9 --- /dev/null +++ b/src/packages/spell-check/lib/corrections-view.coffee @@ -0,0 +1,71 @@ +{$$} = require 'space-pen' +Range = require 'range' +SelectList = require 'select-list' + +module.exports = +class CorrectionsView extends SelectList + @viewClass: -> "corrections #{super} popover-list" + + editor: null + corrections: null + misspellingRange: null + aboveCursor: false + + initialize: (@editor, @corrections, @misspellingRange) -> + super + + @attach() + + itemForElement: (word) -> + $$ -> + @li word + + selectNextItem: -> + super + + false + + selectPreviousItem: -> + super + + false + + confirmed: (correction) -> + @cancel() + return unless correction + @editor.transact => + @editor.setSelectedBufferRange(@editor.bufferRangeForScreenRange(@misspellingRange)) + @editor.insertText(correction) + + attach: -> + @aboveCursor = false + if @corrections.length > 0 + @setArray(@corrections) + else + @setError("No corrections found") + + @editor.appendToLinesView(this) + @setPosition() + @miniEditor.focus() + + detach: -> + super + + @editor.focus() + + setPosition: -> + { left, top } = @editor.pixelPositionForScreenPosition(@misspellingRange.start) + height = @outerHeight() + potentialTop = top + @editor.lineHeight + potentialBottom = potentialTop - @editor.scrollTop() + height + + if @aboveCursor or potentialBottom > @editor.outerHeight() + @aboveCursor = true + @css(left: left, top: top - height, bottom: 'inherit') + else + @css(left: left, top: potentialTop, bottom: 'inherit') + + populateList: -> + super + + @setPosition() diff --git a/src/packages/spell-check/lib/misspelling-view.coffee b/src/packages/spell-check/lib/misspelling-view.coffee new file mode 100644 index 000000000..eeb537671 --- /dev/null +++ b/src/packages/spell-check/lib/misspelling-view.coffee @@ -0,0 +1,61 @@ +{View} = require 'space-pen' +Range = require 'range' +CorrectionsView = require './corrections-view' + +module.exports = +class MisspellingView extends View + @content: -> + @div class: 'misspelling' + + initialize: (range, @editor) -> + @editSession = @editor.activeEditSession + range = @editSession.screenRangeForBufferRange(Range.fromObject(range)) + @startPosition = range.start + @endPosition = range.end + @misspellingValid = true + + @marker = @editSession.markScreenRange(range, invalidationStrategy: 'between') + @editSession.observeMarker @marker, ({newHeadScreenPosition, newTailScreenPosition, valid}) => + @startPosition = newTailScreenPosition + @endPosition = newHeadScreenPosition + @updateDisplayPosition = valid + @misspellingValid = valid + @hide() unless valid + + @subscribe @editor, 'editor:display-updated', => + @updatePosition() if @updateDisplayPosition + + @editor.command 'editor:correct-misspelling', => + return unless @misspellingValid and @containsCursor() + + screenRange = @getScreenRange() + misspelling = @editor.getTextInRange(@editor.bufferRangeForScreenRange(screenRange)) + corrections = $native.getCorrectionsForMisspelling(misspelling) + @correctionsView?.remove() + @correctionsView = new CorrectionsView(@editor, corrections, screenRange) + + @updatePosition() + + getScreenRange: -> + new Range(@startPosition, @endPosition) + + containsCursor: -> + cursor = @editor.getCursorScreenPosition() + @getScreenRange().containsPoint(cursor, exclusive: false) + + updatePosition: -> + @updateDisplayPosition = false + startPixelPosition = @editor.pixelPositionForScreenPosition(@startPosition) + endPixelPosition = @editor.pixelPositionForScreenPosition(@endPosition) + @css + top: startPixelPosition.top + left: startPixelPosition.left + width: endPixelPosition.left - startPixelPosition.left + height: @editor.lineHeight + @show() + + destroy: -> + @misspellingValid = false + @editSession.destroyMarker(@marker) + @correctionsView?.remove() + @remove() diff --git a/src/packages/spell-check/lib/spell-check-handler.coffee b/src/packages/spell-check/lib/spell-check-handler.coffee new file mode 100644 index 000000000..8040f4991 --- /dev/null +++ b/src/packages/spell-check/lib/spell-check-handler.coffee @@ -0,0 +1,14 @@ +module.exports = + findMisspellings: (text) -> + wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]]|$)/g + row = 0 + misspellings = [] + for line in text.split('\n') + while matches = wordRegex.exec(line) + word = matches[1] + continue unless $native.isMisspelled(word) + startColumn = matches.index + matches[0].length - word.length + endColumn = startColumn + word.length + misspellings.push([[row, startColumn], [row, endColumn]]) + row++ + callTaskMethod('misspellingsFound', misspellings) diff --git a/src/packages/spell-check/lib/spell-check-task.coffee b/src/packages/spell-check/lib/spell-check-task.coffee new file mode 100644 index 000000000..cdec62032 --- /dev/null +++ b/src/packages/spell-check/lib/spell-check-task.coffee @@ -0,0 +1,13 @@ +Task = require 'task' + +module.exports = +class SpellCheckTask extends Task + constructor: (@text, @callback) -> + super('spell-check/lib/spell-check-handler') + + started: -> + @callWorkerMethod('findMisspellings', @text) + + misspellingsFound: (misspellings) -> + @done() + @callback(misspellings) diff --git a/src/packages/spell-check/lib/spell-check-view.coffee b/src/packages/spell-check/lib/spell-check-view.coffee new file mode 100644 index 000000000..2117dc3ef --- /dev/null +++ b/src/packages/spell-check/lib/spell-check-view.coffee @@ -0,0 +1,53 @@ +{View} = require 'space-pen' +_ = require 'underscore' +SpellCheckTask = require './spell-check-task' +MisspellingView = require './misspelling-view' + +module.exports = +class SpellCheckView extends View + @content: -> + @div class: 'spell-check' + + views: [] + + initialize: (@editor) -> + @subscribe @editor, 'editor:path-changed', => @subscribeToBuffer() + @subscribe @editor, 'editor:grammar-changed', => @subscribeToBuffer() + @observeConfig 'editor.fontSize', => @subscribeToBuffer() + @observeConfig 'spell-check.grammars', => @subscribeToBuffer() + + @subscribeToBuffer() + + subscribeToBuffer: -> + @destroyViews() + @task?.abort() + return unless @spellCheckCurrentGrammar() + + @buffer?.off '.spell-check' + @buffer = @editor.getBuffer() + @buffer.on 'contents-modified.spell-check', => @updateMisspellings() + @updateMisspellings() + + spellCheckCurrentGrammar: -> + grammar = @editor.getGrammar().scopeName + _.contains config.get('spell-check.grammars'), grammar + + destroyViews: -> + if @views + view.destroy() for view in @views + @views = [] + + addViews: (misspellings) -> + for misspelling in misspellings + view = new MisspellingView(misspelling, @editor) + @views.push(view) + @append(view) + + updateMisspellings: -> + @task?.abort() + + callback = (misspellings) => + @destroyViews() + @addViews(misspellings) + @task = new SpellCheckTask(@buffer.getText(), callback) + @task.start() diff --git a/src/packages/spell-check/lib/spell-check.coffee b/src/packages/spell-check/lib/spell-check.coffee new file mode 100644 index 000000000..2465b22df --- /dev/null +++ b/src/packages/spell-check/lib/spell-check.coffee @@ -0,0 +1,20 @@ +SpellCheckView = require './spell-check-view' + +module.exports = + configDefaults: + grammars: [ + 'text.plain' + 'source.gfm' + 'text.git-commit' + ] + + activate: -> + if syntax.grammars.length > 1 + @subscribeToEditors() + else + syntax.on 'grammars-loaded', => @subscribeToEditors() + + subscribeToEditors: -> + rootView.eachEditor (editor) -> + if editor.attached and not editor.mini + editor.underlayer.append(new SpellCheckView(editor)) diff --git a/src/packages/spell-check/package.cson b/src/packages/spell-check/package.cson new file mode 100644 index 000000000..1405d4cd7 --- /dev/null +++ b/src/packages/spell-check/package.cson @@ -0,0 +1 @@ +'main': 'lib/spell-check.coffee' diff --git a/src/packages/spell-check/spec/spell-check-spec.coffee b/src/packages/spell-check/spec/spell-check-spec.coffee new file mode 100644 index 000000000..abdb98ac3 --- /dev/null +++ b/src/packages/spell-check/spec/spell-check-spec.coffee @@ -0,0 +1,98 @@ +RootView = require 'root-view' + +describe "Spell check", -> + [editor] = [] + + beforeEach -> + window.rootView = new RootView + rootView.open('sample.js') + config.set('spell-check.grammars', []) + window.loadPackage('spell-check') + rootView.attachToDom() + editor = rootView.getActiveEditor() + + it "decorates all misspelled words", -> + editor.setText("This middle of thiss sentencts has issues.") + config.set('spell-check.grammars', ['source.js']) + + waitsFor -> + editor.find('.misspelling').length > 0 + + runs -> + expect(editor.find('.misspelling').length).toBe 2 + + typo1StartPosition = editor.pixelPositionForBufferPosition([0, 15]) + typo1EndPosition = editor.pixelPositionForBufferPosition([0, 20]) + expect(editor.find('.misspelling:eq(0)').position()).toEqual typo1StartPosition + expect(editor.find('.misspelling:eq(0)').width()).toBe typo1EndPosition.left - typo1StartPosition.left + + typo2StartPosition = editor.pixelPositionForBufferPosition([0, 21]) + typo2EndPosition = editor.pixelPositionForBufferPosition([0, 30]) + expect(editor.find('.misspelling:eq(1)').position()).toEqual typo2StartPosition + expect(editor.find('.misspelling:eq(1)').width()).toBe typo2EndPosition.left - typo2StartPosition.left + + it "hides decorations when a misspelled word is edited", -> + editor.setText('notaword') + advanceClock(editor.getBuffer().stoppedChangingDelay) + config.set('spell-check.grammars', ['source.js']) + + waitsFor -> + editor.find('.misspelling').length > 0 + + runs -> + expect(editor.find('.misspelling').length).toBe 1 + editor.moveCursorToEndOfLine() + editor.insertText('a') + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.misspelling')).toBeHidden() + + describe "when spell checking for a grammar is removed", -> + it "removes all current decorations", -> + editor.setText('notaword') + advanceClock(editor.getBuffer().stoppedChangingDelay) + config.set('spell-check.grammars', ['source.js']) + + waitsFor -> + editor.find('.misspelling').length > 0 + + runs -> + expect(editor.find('.misspelling').length).toBe 1 + config.set('spell-check.grammars', []) + expect(editor.find('.misspelling').length).toBe 0 + + describe "when 'editor:correct-misspelling' is triggered on the editor", -> + describe "when the cursor touches a misspelling that has corrections", -> + it "displays the corrections for the misspelling and replaces the misspelling when a correction is selected", -> + editor.setText('fryday') + advanceClock(editor.getBuffer().stoppedChangingDelay) + config.set('spell-check.grammars', ['source.js']) + + waitsFor -> + editor.find('.misspelling').length > 0 + + runs -> + editor.trigger 'editor:correct-misspelling' + expect(editor.find('.corrections').length).toBe 1 + expect(editor.find('.corrections li').length).toBeGreaterThan 0 + expect(editor.find('.corrections li:first').text()).toBe "Friday" + editor.find('.corrections').view().confirmSelection() + expect(editor.getText()).toBe 'Friday' + expect(editor.getCursorBufferPosition()).toEqual [0, 6] + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.misspelling')).toBeHidden() + expect(editor.find('.corrections').length).toBe 0 + + describe "when the cursor touches a misspelling that has no corrections", -> + it "displays a message saying no corrections found", -> + editor.setText('asdfasdf') + advanceClock(editor.getBuffer().stoppedChangingDelay) + config.set('spell-check.grammars', ['source.js']) + + waitsFor -> + editor.find('.misspelling').length > 0 + + runs -> + editor.trigger 'editor:correct-misspelling' + expect(editor.find('.corrections').length).toBe 1 + expect(editor.find('.corrections li').length).toBe 0 + expect(editor.find('.corrections .error').text()).toBe "No corrections found" diff --git a/src/packages/spell-check/stylesheets/spell-check.css b/src/packages/spell-check/stylesheets/spell-check.css new file mode 100644 index 000000000..dc96f515e --- /dev/null +++ b/src/packages/spell-check/stylesheets/spell-check.css @@ -0,0 +1,4 @@ +.misspelling { + border-bottom: 1px dashed rgba(250, 128, 114, .5); + position: absolute; +} diff --git a/src/packages/symbols-view/lib/symbols-view.coffee b/src/packages/symbols-view/lib/symbols-view.coffee index 5fd700e61..58149f9f4 100644 --- a/src/packages/symbols-view/lib/symbols-view.coffee +++ b/src/packages/symbols-view/lib/symbols-view.coffee @@ -27,12 +27,11 @@ class SymbolsView extends SelectList $$ -> @li => @div name, class: 'label' - @div class: 'right', => - if position - text = "Line #{position.row + 1}" - else - text = fs.base(file) - @div text, class: 'function-details' + if position + text = "Line #{position.row + 1}" + else + text = fs.base(file) + @div text, class: 'right function-details' toggleFileSymbols: -> if @hasParent() diff --git a/src/packages/symbols-view/spec/symbols-view-spec.coffee b/src/packages/symbols-view/spec/symbols-view-spec.coffee index d8adf0c04..6566e8b59 100644 --- a/src/packages/symbols-view/spec/symbols-view-spec.coffee +++ b/src/packages/symbols-view/spec/symbols-view-spec.coffee @@ -104,28 +104,34 @@ describe "SymbolsView", -> describe "TagGenerator", -> it "generates tags for all JavaScript functions", -> + tags = [] + waitsForPromise -> - tags = [] path = require.resolve('fixtures/sample.js') callback = (tag) -> tags.push tag generator = new TagGenerator(path, callback) - generator.generate().done -> - expect(tags.length).toBe 2 - expect(tags[0].name).toBe "quicksort" - expect(tags[0].position.row).toBe 0 - expect(tags[1].name).toBe "quicksort.sort" - expect(tags[1].position.row).toBe 1 + generator.generate() + + runs -> + expect(tags.length).toBe 2 + expect(tags[0].name).toBe "quicksort" + expect(tags[0].position.row).toBe 0 + expect(tags[1].name).toBe "quicksort.sort" + expect(tags[1].position.row).toBe 1 it "generates no tags for text file", -> + tags = [] + waitsForPromise -> - tags = [] path = require.resolve('fixtures/sample.txt') callback = (tag) -> tags.push tag generator = new TagGenerator(path, callback) - generator.generate().done -> - expect(tags.length).toBe 0 + generator.generate() + + runs -> + expect(tags.length).toBe 0 describe "go to declaration", -> it "doesn't move the cursor when no declaration is found", -> @@ -155,11 +161,15 @@ describe "SymbolsView", -> expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [0,4] describe "when the tag is in a file that doesn't exist", -> + renamedPath = null + beforeEach -> - fs.move(project.resolve("tagged-duplicate.js"), project.resolve("tagged-duplicate-renamed.js")) + renamedPath = project.resolve("tagged-duplicate-renamed.js") + fs.remove(renamedPath) if fs.exists(renamedPath) + fs.move(project.resolve("tagged-duplicate.js"), renamedPath) afterEach -> - fs.move(project.resolve("tagged-duplicate-renamed.js"), project.resolve("tagged-duplicate.js")) + fs.move(renamedPath, project.resolve("tagged-duplicate.js")) it "doesn't display the tag", -> rootView.open("tagged.js") diff --git a/src/packages/toml.tmbundle/Syntaxes/toml.cson b/src/packages/toml.tmbundle/Syntaxes/toml.cson new file mode 100644 index 000000000..6aa5fb542 --- /dev/null +++ b/src/packages/toml.tmbundle/Syntaxes/toml.cson @@ -0,0 +1,50 @@ +'name': 'TOML' +'scopeName': 'source.toml' +'fileTypes': ['toml'] +'patterns': [ + { + 'match': '(?:^\\s*)(\\[([^\\]]+)\\])' + 'captures': + '2': 'name': 'variable.keygroup.toml' + 'name': 'keygroup.toml' + } + { + 'match': '(?:^\\s*)(\\S+)\\s*=' + 'captures': + '1': 'name': 'entity.key.toml' + 'name': 'key.toml' + } + { + 'begin': '"' + 'beginCaptures': + '0': 'name': 'string.begin.toml' + 'end': '"' + 'endCaptures': + '0': 'name': 'string.end.toml' + 'name': 'string.toml' + 'patterns': [ + 'match': '\\\\[nt0r"\\\\]' + 'name' : 'constant.character.escape.toml' + ] + } + { + 'match': '#.*$' + 'name': 'comment.toml' + } + { + 'match': 'true' + 'name': 'constant.language.boolean.true.toml' + } + { + 'match': 'false' + 'name': 'constant.language.boolean.false.toml' + } + { + 'match': '\\d{4}-\\d{2}-\\d{2}(T)\\d{2}:\\d{2}:\\d{2}(Z)' + 'name': 'support.date.toml' + } + { + 'match': '-?\\d+(\\.?\\d+)?' + 'name': 'constant.numeric.toml' + } +] diff --git a/src/packages/toml.tmbundle/spec/toml-spec.coffee b/src/packages/toml.tmbundle/spec/toml-spec.coffee new file mode 100644 index 000000000..576879912 --- /dev/null +++ b/src/packages/toml.tmbundle/spec/toml-spec.coffee @@ -0,0 +1,65 @@ +TextMatePackage = require 'text-mate-package' + +describe "TOML grammar", -> + grammar = null + + beforeEach -> + spyOn(syntax, "addGrammar") + pack = new TextMatePackage(require.resolve("toml.tmbundle")) + pack.load() + grammar = pack.grammars[0] + + it "parses the grammar", -> + expect(grammar).toBeTruthy() + expect(grammar.scopeName).toBe "source.toml" + + it "tokenizes comments", -> + {tokens} = grammar.tokenizeLine("# I am a comment") + expect(tokens[0]).toEqual value: "# I am a comment", scopes: ["source.toml", "comment.toml"] + + it "tokenizes strings", -> + {tokens} = grammar.tokenizeLine('"I am a string"') + expect(tokens[0]).toEqual value: '"', scopes: ["source.toml", "string.toml", "string.begin.toml"] + expect(tokens[1]).toEqual value: 'I am a string', scopes: ["source.toml", "string.toml"] + expect(tokens[2]).toEqual value: '"', scopes: ["source.toml", "string.toml","string.end.toml"] + + {tokens} = grammar.tokenizeLine('"I\'m \\n escaped"') + expect(tokens[0]).toEqual value: '"', scopes: ["source.toml", "string.toml", "string.begin.toml"] + expect(tokens[1]).toEqual value: "I'm ", scopes: ["source.toml", "string.toml"] + expect(tokens[2]).toEqual value: "\\n", scopes: ["source.toml", "string.toml", "constant.character.escape.toml"] + expect(tokens[3]).toEqual value: " escaped", scopes: ["source.toml", "string.toml"] + expect(tokens[4]).toEqual value: '"', scopes: ["source.toml", "string.toml", "string.end.toml"] + + it "tokenizes booleans", -> + {tokens} = grammar.tokenizeLine("true") + expect(tokens[0]).toEqual value: "true", scopes: ["source.toml", "constant.language.boolean.true.toml"] + {tokens} = grammar.tokenizeLine("false") + expect(tokens[0]).toEqual value: "false", scopes: ["source.toml", "constant.language.boolean.false.toml"] + + it "tokenizes numbers", -> + {tokens} = grammar.tokenizeLine("123") + expect(tokens[0]).toEqual value: "123", scopes: ["source.toml", "constant.numeric.toml"] + + {tokens} = grammar.tokenizeLine("-1") + expect(tokens[0]).toEqual value: "-1", scopes: ["source.toml", "constant.numeric.toml"] + + {tokens} = grammar.tokenizeLine("3.14") + expect(tokens[0]).toEqual value: "3.14", scopes: ["source.toml", "constant.numeric.toml"] + + {tokens} = grammar.tokenizeLine("-123.456") + expect(tokens[0]).toEqual value: "-123.456", scopes: ["source.toml", "constant.numeric.toml"] + + it "tokenizes dates", -> + {tokens} = grammar.tokenizeLine("1979-05-27T07:32:00Z") + expect(tokens[0]).toEqual value: "1979-05-27T07:32:00Z", scopes: ["source.toml", "support.date.toml"] + + it "tokenizes keygroups", -> + {tokens} = grammar.tokenizeLine("[keygroup]") + expect(tokens[0]).toEqual value: "[", scopes: ["source.toml", "keygroup.toml"] + expect(tokens[1]).toEqual value: "keygroup", scopes: ["source.toml", "keygroup.toml", "variable.keygroup.toml"] + expect(tokens[2]).toEqual value: "]", scopes: ["source.toml", "keygroup.toml"] + + it "tokenizes keys", -> + {tokens} = grammar.tokenizeLine("key =") + expect(tokens[0]).toEqual value: "key", scopes: ["source.toml", "key.toml", "entity.key.toml"] + expect(tokens[1]).toEqual value: " =", scopes: ["source.toml", "key.toml"] diff --git a/src/packages/tree-view/lib/tree-view.coffee b/src/packages/tree-view/lib/tree-view.coffee index 9e71a5a05..ff0408607 100644 --- a/src/packages/tree-view/lib/tree-view.coffee +++ b/src/packages/tree-view/lib/tree-view.coffee @@ -72,15 +72,12 @@ class TreeView extends ScrollView if @hasFocus() @detach() else - if @hasParent() - @focus() - else - @attach() + @attach() unless @hasParent() + @focus() attach: -> return unless project.getPath() rootView.horizontal.prepend(this) - @focus() detach: -> @scrollTopAfterAttach = @scrollTop() @@ -134,6 +131,7 @@ class TreeView extends ScrollView revealActiveFile: -> @attach() + @focus() return unless activeFilePath = rootView.getActiveEditor()?.getPath() diff --git a/src/stdlib/git-repository.coffee b/src/stdlib/git-repository.coffee index fc03e1368..7a52403b4 100644 --- a/src/stdlib/git-repository.coffee +++ b/src/stdlib/git-repository.coffee @@ -1,11 +1,11 @@ module.exports = class GitRepository - constructor: (path) -> + @open: (path) -> unless repo = $git.getRepository(path) - throw new Error("No Git repository found searching path: " + path) + throw new Error("No Git repository found searching path: #{path}") repo.constructor = GitRepository repo.__proto__ = GitRepository.prototype - return repo + repo getHead: $git.getHead getPath: $git.getPath diff --git a/src/stdlib/onig-reg-exp.coffee b/src/stdlib/onig-reg-exp.coffee index 93883da20..db7fa4632 100644 --- a/src/stdlib/onig-reg-exp.coffee +++ b/src/stdlib/onig-reg-exp.coffee @@ -1,11 +1,11 @@ module.exports = class OnigRegExp - constructor: (source) -> + @create: (source) -> regexp = $onigRegExp.buildOnigRegExp(source); regexp.constructor = OnigRegExp regexp.__proto__ = OnigRegExp.prototype regexp.source = source - return regexp + regexp search: $onigRegExp.search test: $onigRegExp.test diff --git a/src/stdlib/onig-scanner.coffee b/src/stdlib/onig-scanner.coffee index fb26939ec..80c457c2f 100644 --- a/src/stdlib/onig-scanner.coffee +++ b/src/stdlib/onig-scanner.coffee @@ -1,10 +1,10 @@ module.exports = class OnigScanner - constructor: (sources) -> + @create: (sources) -> scanner = $onigScanner.buildScanner(sources) scanner.constructor = OnigScanner scanner.__proto__ = OnigScanner.prototype scanner.sources = sources - return scanner + scanner findNextMatch: $onigScanner.findNextMatch diff --git a/src/stdlib/require.coffee b/src/stdlib/require.coffee index a51d89f11..d7e8b87dd 100644 --- a/src/stdlib/require.coffee +++ b/src/stdlib/require.coffee @@ -53,7 +53,7 @@ exts = code or= __read file eval("define(function(require, exports, module) { 'use strict';#{code}})\n//@ sourceURL=#{file}") __defines.pop()?.call() - coffee: (file, retry=true) -> + coffee: (file) -> cacheFilePath = getCacheFilePath(file) if __exists(cacheFilePath) compiled = __read(cacheFilePath) diff --git a/static/editor.css b/static/editor.css index b75743fb0..1b6c35a74 100644 --- a/static/editor.css +++ b/static/editor.css @@ -61,22 +61,20 @@ content: '\f078'; font-family: 'Octicons Regular'; -webkit-font-smoothing: antialiased; - color: #fba0e3; + opacity: .8; visibility: hidden; } .editor .fold-marker:after { + font-size: .8em; content: '\f09a'; opacity: .8; - color: #fba0e3; padding-left: .2em; font-family: 'Octicons Regular'; -webkit-font-smoothing: antialiased; - position: relative; - top: 2px; } -.editor .line.cursor-line .fold-marker { +.editor .line.cursor-line .fold-marker:after { opacity: 1; } diff --git a/static/select-list.css b/static/select-list.css index 847edb7ab..24676a9c2 100644 --- a/static/select-list.css +++ b/static/select-list.css @@ -4,9 +4,10 @@ overflow-y: auto; max-height: 312px; margin: 0; - background: red; padding: 0; line-height: 100%; + -webkit-user-select: none; + cursor: default; } .select-list ol:empty { @@ -42,4 +43,19 @@ font-weight: bold; color: white; text-shadow: 0 1px 0 #4E0000; -} \ No newline at end of file +} + +.select-list li.active-item:before { + font-family: 'Octicons Regular'; + font-size: 14px; + width: 14px; + height: 14px; + margin-right: 5px; + -webkit-font-smoothing: antialiased; + color: #9d9d9d; + content: '\f03a'; +} + +.select-list li.inactive-item { + padding-left: 29px; +} diff --git a/themes/atom-dark-syntax.css b/themes/atom-dark-syntax.css index f860c61b7..e14796b22 100644 --- a/themes/atom-dark-syntax.css +++ b/themes/atom-dark-syntax.css @@ -3,6 +3,12 @@ color: #c5c8c6; } +.editor .gutter .line-number.fold, +.editor .gutter .line-number:after, +.editor .fold-marker:after { + color: #fba0e3; +} + .editor .invisible { color: #c5c8c6; } diff --git a/themes/atom-dark-ui/editor.css b/themes/atom-dark-ui/editor.css index 27c26d81b..e4f254a93 100644 --- a/themes/atom-dark-ui/editor.css +++ b/themes/atom-dark-ui/editor.css @@ -18,8 +18,3 @@ -webkit-animation-duration: 1s; -webkit-animation-iteration-count: 1; } - -.editor .gutter .line-number.fold { - color: #fba0e3; - opacity: .8; -} diff --git a/themes/atom-dark-ui/select-list.css b/themes/atom-dark-ui/select-list.css index 2de49745b..789ac5313 100644 --- a/themes/atom-dark-ui/select-list.css +++ b/themes/atom-dark-ui/select-list.css @@ -7,12 +7,6 @@ color: #ddd; } -.select-list li .right { - color: #999; - display: inline-block; - margin-top: -3px; -} - .select-list .key-binding { background: -webkit-linear-gradient( rgba(100, 100, 100, 0.5), @@ -24,13 +18,14 @@ } .select-list li:hover { - background-color: #555; + background-color: #2c2c2c; } .select-list ol .selected { background-image: -webkit-linear-gradient(#7e7e7e, #737373); } +.select-list .right, .select-list .directory { color: #777; } @@ -39,6 +34,7 @@ color: #fff; } +.select-list ol .selected .right, .select-list ol .selected .directory { color: #ccc; -} \ No newline at end of file +} diff --git a/themes/atom-light-syntax.css b/themes/atom-light-syntax.css index 20f221a28..248630da8 100644 --- a/themes/atom-light-syntax.css +++ b/themes/atom-light-syntax.css @@ -3,6 +3,12 @@ color: #555; } +.editor .gutter .line-number.fold, +.editor .gutter .line-number:after, +.editor .fold-marker:after { + color: #e87b00; +} + .editor .invisible { color: #555; } diff --git a/themes/atom-light-ui/command-panel.css b/themes/atom-light-ui/command-panel.css index 0334a354a..abd189d79 100644 --- a/themes/atom-light-ui/command-panel.css +++ b/themes/atom-light-ui/command-panel.css @@ -103,3 +103,11 @@ .command-panel li.operation.selected .preview { color: #333; } + +.error-messages { + color: #333; +} + +.error .error-messages { + color: white; +} diff --git a/themes/atom-light-ui/editor.css b/themes/atom-light-ui/editor.css index 2acbdf43e..2c7c2d7a8 100644 --- a/themes/atom-light-ui/editor.css +++ b/themes/atom-light-ui/editor.css @@ -24,8 +24,3 @@ -webkit-animation-duration: 1s; -webkit-animation-iteration-count: 1; } - -.editor .gutter .line-number.fold { - color: #fba0e3; - opacity: .8; -} diff --git a/themes/atom-light-ui/overlay.css b/themes/atom-light-ui/overlay.css new file mode 100644 index 000000000..f789ada7d --- /dev/null +++ b/themes/atom-light-ui/overlay.css @@ -0,0 +1,6 @@ +.overlay { + background-color: #e5e5e5; + border-top: 1px solid #979797; + color: #333; + padding: 10px; +} diff --git a/themes/atom-light-ui/package.cson b/themes/atom-light-ui/package.cson index 53831688d..18cff697d 100644 --- a/themes/atom-light-ui/package.cson +++ b/themes/atom-light-ui/package.cson @@ -1,6 +1,7 @@ 'stylesheets': [ 'atom.css' 'editor.css' + 'overlay.css' 'select-list.css' 'tree-view.css' 'tabs.css' diff --git a/themes/atom-light-ui/select-list.css b/themes/atom-light-ui/select-list.css index f59cb7bce..2bd0a4e9d 100644 --- a/themes/atom-light-ui/select-list.css +++ b/themes/atom-light-ui/select-list.css @@ -20,4 +20,14 @@ .select-list ol .selected { background-color: #e0e0e0; -} \ No newline at end of file +} + +.select-list .right, +.select-list .directory { + color: #777; +} + +.select-list ol .selected .right, +.select-list ol .selected .directory { + color: #333; +} diff --git a/themes/atom-light-ui/status-bar.css b/themes/atom-light-ui/status-bar.css index 201be6e28..145d90845 100644 --- a/themes/atom-light-ui/status-bar.css +++ b/themes/atom-light-ui/status-bar.css @@ -12,4 +12,10 @@ .status-bar .git-status.octicons.new-status-icon { color: #5293d8; display: inline-block; -} \ No newline at end of file +} + +.status-bar .grammar-name:hover { + color: #000; + border: 1px solid rgba(50, 50, 50, 0.2); + border-radius: 2px; +} diff --git a/vendor/packages/clojure.tmbundle b/vendor/packages/clojure.tmbundle new file mode 160000 index 000000000..546b39c97 --- /dev/null +++ b/vendor/packages/clojure.tmbundle @@ -0,0 +1 @@ +Subproject commit 546b39c9725d88f1a0eaf74486ae50ca87ce368f diff --git a/vendor/packages/java.tmbundle b/vendor/packages/java.tmbundle new file mode 160000 index 000000000..1c315ed46 --- /dev/null +++ b/vendor/packages/java.tmbundle @@ -0,0 +1 @@ +Subproject commit 1c315ed46188e02d749939ec64160040d9c03f0b diff --git a/vendor/packages/less.tmbundle b/vendor/packages/less.tmbundle new file mode 160000 index 000000000..a202dd994 --- /dev/null +++ b/vendor/packages/less.tmbundle @@ -0,0 +1 @@ +Subproject commit a202dd9942f96bac0ee3c42552a35480682790c9 diff --git a/vendor/packages/perl.tmbundle b/vendor/packages/perl.tmbundle new file mode 160000 index 000000000..fccaee006 --- /dev/null +++ b/vendor/packages/perl.tmbundle @@ -0,0 +1 @@ +Subproject commit fccaee006dbaddbde7ff36ec0b67efc638bbf476 diff --git a/vendor/packages/php.tmbundle b/vendor/packages/php.tmbundle new file mode 160000 index 000000000..bb576b557 --- /dev/null +++ b/vendor/packages/php.tmbundle @@ -0,0 +1 @@ +Subproject commit bb576b557518802c458d85bc2c0a7430fe90e1a0 diff --git a/vendor/packages/sass.tmbundle b/vendor/packages/sass.tmbundle new file mode 160000 index 000000000..e2bef05af --- /dev/null +++ b/vendor/packages/sass.tmbundle @@ -0,0 +1 @@ +Subproject commit e2bef05afac130e4ebd1b2f8991e9563568faa47 diff --git a/vendor/packages/yaml.tmbundle b/vendor/packages/yaml.tmbundle new file mode 160000 index 000000000..173808ba4 --- /dev/null +++ b/vendor/packages/yaml.tmbundle @@ -0,0 +1 @@ +Subproject commit 173808ba47df99b22a43670bf4f8a71370339ae7