mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2025-01-07 23:59:22 +03:00
Merge remote-tracking branch 'origin/master' into vim-core-changes
Conflicts: src/app/editor.coffee
This commit is contained in:
commit
a12c78100e
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,7 +8,7 @@ build
|
||||
.xcodebuild-info
|
||||
node_modules
|
||||
npm-debug.log
|
||||
/tags
|
||||
tags
|
||||
/cef/
|
||||
/sources.gypi
|
||||
/node/
|
||||
|
9
atom.gyp
9
atom.gyp
@ -251,8 +251,6 @@
|
||||
'native/message_translation.cpp',
|
||||
'native/message_translation.h',
|
||||
'native/message_translation.h',
|
||||
'native/path_watcher.h',
|
||||
'native/path_watcher.mm',
|
||||
'native/v8_extensions/atom.h',
|
||||
'native/v8_extensions/atom.mm',
|
||||
'native/v8_extensions/native.h',
|
||||
@ -269,6 +267,13 @@
|
||||
'native/mac/English.lproj/AtomWindow.xib',
|
||||
'native/mac/English.lproj/MainMenu.xib',
|
||||
],
|
||||
'conditions': [
|
||||
['CODE_SIGN', {
|
||||
'defines': [
|
||||
'CODE_SIGNING_ENABLED=1',
|
||||
],
|
||||
}],
|
||||
],
|
||||
'postbuilds': [
|
||||
{
|
||||
'postbuild_name': 'Copy Static Files',
|
||||
|
@ -143,12 +143,34 @@
|
||||
}
|
||||
|
||||
- (void)open:(NSString *)path pidToKillWhenWindowCloses:(NSNumber *)pid {
|
||||
for (NSWindow *window in [self windows]) {
|
||||
if (![window isExcludedFromWindowsMenu]) {
|
||||
AtomWindowController *controller = [window windowController];
|
||||
if ([path isEqualToString:controller.pathToOpen]) {
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
return;
|
||||
BOOL openingDirectory = false;
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&openingDirectory];
|
||||
|
||||
if (!pid) {
|
||||
for (NSWindow *window in [self windows]) {
|
||||
if (![window isExcludedFromWindowsMenu]) {
|
||||
AtomWindowController *controller = [window windowController];
|
||||
if (!openingDirectory) {
|
||||
BOOL openedPathIsDirectory = false;
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:controller.pathToOpen isDirectory:&openedPathIsDirectory];
|
||||
NSString *projectPath = NULL;
|
||||
if (openedPathIsDirectory) {
|
||||
projectPath = [NSString stringWithFormat:@"%@/", controller.pathToOpen];
|
||||
}
|
||||
else {
|
||||
projectPath = [controller.pathToOpen stringByDeletingLastPathComponent];
|
||||
}
|
||||
if ([path hasPrefix:projectPath]) {
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
[controller openPath:path];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ([path isEqualToString:controller.pathToOpen]) {
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,12 +254,14 @@
|
||||
}
|
||||
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];
|
||||
}
|
||||
|
||||
#if defined(CODE_SIGNING_ENABLED)
|
||||
SUUpdater.sharedUpdater.delegate = self;
|
||||
SUUpdater.sharedUpdater.automaticallyChecksForUpdates = YES;
|
||||
SUUpdater.sharedUpdater.automaticallyDownloadsUpdates = YES;
|
||||
[SUUpdater.sharedUpdater checkForUpdatesInBackground];
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,9 @@ bool AtomCefClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
|
||||
else if (name == "crash") {
|
||||
__builtin_trap();
|
||||
}
|
||||
else if (name == "restartRendererProcess") {
|
||||
RestartRendererProcess(browser);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
@ -252,3 +255,11 @@ bool AtomCefClient::Save(const std::string& path, const std::string& data) {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AtomCefClient::RestartRendererProcess(CefRefPtr<CefBrowser> browser) {
|
||||
// Navigating to the same URL has the effect of restarting the renderer
|
||||
// process, because cefode has overridden ContentBrowserClient's
|
||||
// ShouldSwapProcessesForNavigation method.
|
||||
CefRefPtr<CefFrame> frame = browser->GetFocusedFrame();
|
||||
frame->LoadURL(frame->GetURL());
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ class AtomCefClient : public CefClient,
|
||||
void EndTracing();
|
||||
|
||||
bool Save(const std::string& path, const std::string& data);
|
||||
void RestartRendererProcess(CefRefPtr<CefBrowser> browser);
|
||||
|
||||
protected:
|
||||
CefRefPtr<CefBrowser> m_Browser;
|
||||
|
@ -18,7 +18,6 @@ class AtomCefRenderProcessHandler : public CefRenderProcessHandler {
|
||||
CefRefPtr<CefProcessMessage> message) OVERRIDE;
|
||||
|
||||
void Reload(CefRefPtr<CefBrowser> browser);
|
||||
void Shutdown(CefRefPtr<CefBrowser> browser);
|
||||
bool CallMessageReceivedHandler(CefRefPtr<CefV8Context> context, CefRefPtr<CefProcessMessage> message);
|
||||
void InjectExtensionsIntoV8Context(CefRefPtr<CefV8Context> context);
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
#import "native/v8_extensions/atom.h"
|
||||
#import "native/v8_extensions/native.h"
|
||||
#import "native/message_translation.h"
|
||||
#import "path_watcher.h"
|
||||
#import "atom_cef_render_process_handler.h"
|
||||
|
||||
|
||||
@ -18,7 +17,6 @@ void AtomCefRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser
|
||||
void AtomCefRenderProcessHandler::OnContextReleased(CefRefPtr<CefBrowser> browser,
|
||||
CefRefPtr<CefFrame> frame,
|
||||
CefRefPtr<CefV8Context> context) {
|
||||
[PathWatcher removePathWatcherForContext:context];
|
||||
}
|
||||
|
||||
bool AtomCefRenderProcessHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
|
||||
@ -30,10 +28,6 @@ bool AtomCefRenderProcessHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser>
|
||||
Reload(browser);
|
||||
return true;
|
||||
}
|
||||
else if (name == "shutdown") {
|
||||
Shutdown(browser);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return CallMessageReceivedHandler(browser->GetMainFrame()->GetV8Context(), message);
|
||||
}
|
||||
@ -54,17 +48,6 @@ void AtomCefRenderProcessHandler::Reload(CefRefPtr<CefBrowser> browser) {
|
||||
context->Exit();
|
||||
}
|
||||
|
||||
void AtomCefRenderProcessHandler::Shutdown(CefRefPtr<CefBrowser> browser) {
|
||||
CefRefPtr<CefV8Context> context = browser->GetMainFrame()->GetV8Context();
|
||||
CefRefPtr<CefV8Value> global = context->GetGlobal();
|
||||
|
||||
context->Enter();
|
||||
CefV8ValueList arguments;
|
||||
CefRefPtr<CefV8Value> shutdownFunction = global->GetValue("shutdown");
|
||||
shutdownFunction->ExecuteFunction(global, arguments);
|
||||
context->Exit();
|
||||
}
|
||||
|
||||
bool AtomCefRenderProcessHandler::CallMessageReceivedHandler(CefRefPtr<CefV8Context> context, CefRefPtr<CefProcessMessage> message) {
|
||||
context->Enter();
|
||||
|
||||
|
@ -34,5 +34,6 @@ class AtomCefClient;
|
||||
|
||||
- (void)toggleDevTools;
|
||||
- (void)showDevTools;
|
||||
- (void)openPath:(NSString*)path;
|
||||
|
||||
@end
|
||||
|
@ -215,6 +215,16 @@
|
||||
_cefClient->GetBrowser()->GetHost()->SetFocus(true);
|
||||
}
|
||||
|
||||
- (void)openPath:(NSString*)path {
|
||||
if (_cefClient && _cefClient->GetBrowser()) {
|
||||
CefRefPtr<CefProcessMessage> openMessage = CefProcessMessage::Create("openPath");
|
||||
CefRefPtr<CefListValue> openArguments = openMessage->GetArgumentList();
|
||||
openArguments->SetSize(1);
|
||||
openArguments->SetString(0, [path UTF8String]);
|
||||
_cefClient->GetBrowser()->SendProcessMessage(PID_RENDERER, openMessage);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPidToKillOnClose:(NSNumber *)pid {
|
||||
_pidToKillOnClose = [pid retain];
|
||||
}
|
||||
@ -236,7 +246,7 @@
|
||||
|
||||
- (BOOL)windowShouldClose:(NSNotification *)notification {
|
||||
if (_cefClient && _cefClient->GetBrowser()) {
|
||||
_cefClient->GetBrowser()->SendProcessMessage(PID_RENDERER, CefProcessMessage::Create("shutdown"));
|
||||
_cefClient->GetBrowser()->GetHost()->CloseBrowser(false);
|
||||
}
|
||||
|
||||
if (_pidToKillOnClose) kill([_pidToKillOnClose intValue], SIGQUIT);
|
||||
|
@ -1,25 +0,0 @@
|
||||
#import "include/cef_base.h"
|
||||
#import "include/cef_v8.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^WatchCallback)(NSString *, NSString *);
|
||||
|
||||
@interface PathWatcher : NSObject {
|
||||
int _kq;
|
||||
CefRefPtr<CefV8Context> _context;
|
||||
NSMutableDictionary *_callbacksByPath;
|
||||
NSMutableDictionary *_fileDescriptorsByPath;
|
||||
|
||||
bool _keepWatching;
|
||||
}
|
||||
|
||||
+ (PathWatcher *)pathWatcherForContext:(CefRefPtr<CefV8Context>)context;
|
||||
+ (void)removePathWatcherForContext:(CefRefPtr<CefV8Context>)context;
|
||||
|
||||
- (id)initWithContext:(CefRefPtr<CefV8Context>)context;
|
||||
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback;
|
||||
- (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId error:(NSError **)error;
|
||||
- (void)unwatchAllPaths;
|
||||
- (NSArray *)watchedPaths;
|
||||
|
||||
@end
|
@ -1,273 +0,0 @@
|
||||
#import <sys/event.h>
|
||||
#import <sys/time.h>
|
||||
#import <sys/param.h>
|
||||
#import <fcntl.h>
|
||||
|
||||
#import "path_watcher.h"
|
||||
|
||||
static NSMutableArray *gPathWatchers;
|
||||
|
||||
@interface PathWatcher ()
|
||||
- (bool)usesContext:(CefRefPtr<CefV8Context>)context;
|
||||
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId;
|
||||
- (void)stopWatching;
|
||||
- (bool)isAtomicWrite:(struct kevent)event;
|
||||
@end
|
||||
|
||||
@implementation PathWatcher
|
||||
|
||||
+ (PathWatcher *)pathWatcherForContext:(CefRefPtr<CefV8Context>)context {
|
||||
if (!gPathWatchers) gPathWatchers = [[NSMutableArray alloc] init];
|
||||
|
||||
PathWatcher *pathWatcher = nil;
|
||||
for (PathWatcher *p in gPathWatchers) {
|
||||
if ([p usesContext:context]) {
|
||||
pathWatcher = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathWatcher) {
|
||||
pathWatcher = [[[PathWatcher alloc] initWithContext:context] autorelease];
|
||||
[gPathWatchers addObject:pathWatcher];
|
||||
}
|
||||
|
||||
return pathWatcher;
|
||||
}
|
||||
|
||||
+ (void)removePathWatcherForContext:(CefRefPtr<CefV8Context>)context {
|
||||
PathWatcher *pathWatcher = nil;
|
||||
for (PathWatcher *p in gPathWatchers) {
|
||||
if ([p usesContext:context]) {
|
||||
pathWatcher = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathWatcher) {
|
||||
[pathWatcher stopWatching];
|
||||
[gPathWatchers removeObject:pathWatcher];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
@synchronized(self) {
|
||||
close(_kq);
|
||||
for (NSString *path in [_callbacksByPath allKeys]) {
|
||||
[self removeKeventForPath:path];
|
||||
}
|
||||
[_callbacksByPath release];
|
||||
_context = nil;
|
||||
_keepWatching = false;
|
||||
}
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initWithContext:(CefRefPtr<CefV8Context>)context {
|
||||
self = [super init];
|
||||
|
||||
_keepWatching = YES;
|
||||
_callbacksByPath = [[NSMutableDictionary alloc] init];
|
||||
_fileDescriptorsByPath = [[NSMutableDictionary alloc] init];
|
||||
_kq = kqueue();
|
||||
_context = context;
|
||||
|
||||
if (_kq == -1) {
|
||||
[NSException raise:@"PathWatcher" format:@"Could not create kqueue"];
|
||||
}
|
||||
|
||||
[self performSelectorInBackground:@selector(watch) withObject:NULL];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (bool)usesContext:(CefRefPtr<CefV8Context>)context {
|
||||
return _context->IsSame(context);
|
||||
}
|
||||
|
||||
- (void)stopWatching {
|
||||
@synchronized(self) {
|
||||
[self unwatchAllPaths];
|
||||
_keepWatching = false;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback {
|
||||
NSString *callbackId = [[NSProcessInfo processInfo] globallyUniqueString];
|
||||
return [self watchPath:path callback:callback callbackId:callbackId];
|
||||
}
|
||||
|
||||
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId {
|
||||
@synchronized(self) {
|
||||
if (![self createKeventForPath:path]) {
|
||||
NSLog(@"WARNING: Failed to create kevent for path '%@'", path);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableDictionary *callbacks = [_callbacksByPath objectForKey:path];
|
||||
if (!callbacks) {
|
||||
callbacks = [NSMutableDictionary dictionary];
|
||||
[_callbacksByPath setObject:callbacks forKey:path];
|
||||
}
|
||||
|
||||
[callbacks setObject:callback forKey:callbackId];
|
||||
}
|
||||
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
- (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId error:(NSError **)error {
|
||||
@synchronized(self) {
|
||||
NSMutableDictionary *callbacks = [_callbacksByPath objectForKey:path];
|
||||
|
||||
if (callbacks) {
|
||||
if (callbackId) {
|
||||
[callbacks removeObjectForKey:callbackId];
|
||||
}
|
||||
else {
|
||||
[callbacks removeAllObjects];
|
||||
}
|
||||
|
||||
if (callbacks.count == 0) {
|
||||
[self removeKeventForPath:path];
|
||||
[_callbacksByPath removeObjectForKey:path];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)watchedPaths {
|
||||
return [_callbacksByPath allKeys];
|
||||
}
|
||||
|
||||
- (void)unwatchAllPaths {
|
||||
@synchronized(self) {
|
||||
NSArray *paths = [_callbacksByPath allKeys];
|
||||
for (NSString *path in paths) {
|
||||
[self unwatchPath:path callbackId:nil error:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)createKeventForPath:(NSString *)path {
|
||||
@synchronized(self) {
|
||||
if ([_fileDescriptorsByPath objectForKey:path]) {
|
||||
NSLog(@"we already have a kevent");
|
||||
return YES;
|
||||
}
|
||||
|
||||
int fd = open([path fileSystemRepresentation], O_EVTONLY, 0);
|
||||
if (fd < 0) {
|
||||
NSLog(@"WARNING: Could not create file descriptor for path '%@'. Error code %d.", path, errno);
|
||||
return NO;
|
||||
}
|
||||
|
||||
[_fileDescriptorsByPath setObject:[NSNumber numberWithInt:fd] forKey:path];
|
||||
|
||||
struct timespec timeout = { 0, 0 };
|
||||
struct kevent event;
|
||||
int filter = EVFILT_VNODE;
|
||||
int flags = EV_ADD | EV_ENABLE | EV_CLEAR;
|
||||
int filterFlags = NOTE_WRITE | NOTE_DELETE | NOTE_RENAME;
|
||||
EV_SET(&event, fd, filter, flags, filterFlags, 0, path);
|
||||
kevent(_kq, &event, 1, NULL, 0, &timeout);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeKeventForPath:(NSString *)path {
|
||||
@synchronized(self) {
|
||||
NSNumber *fdNumber = [_fileDescriptorsByPath objectForKey:path];
|
||||
if (!fdNumber) {
|
||||
NSLog(@"WARNING: Could not find file descriptor for path '%@'", path);
|
||||
return;
|
||||
}
|
||||
close([fdNumber integerValue]);
|
||||
[_fileDescriptorsByPath removeObjectForKey:path];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (bool)isAtomicWrite:(struct kevent)event {
|
||||
if (!event.fflags & NOTE_DELETE) return NO;
|
||||
const char *path = [(NSString *)event.udata fileSystemRepresentation];
|
||||
bool fileExists = access(path, F_OK) != -1;
|
||||
return fileExists;
|
||||
}
|
||||
|
||||
- (void)changePath:(NSString *)path toNewPath:(NSString *)newPath {
|
||||
@synchronized(self) {
|
||||
NSDictionary *callbacks = [NSDictionary dictionaryWithDictionary:[_callbacksByPath objectForKey:path]];
|
||||
[self unwatchPath:path callbackId:nil error:nil];
|
||||
for (NSString *callbackId in [callbacks allKeys]) {
|
||||
[self watchPath:newPath callback:[callbacks objectForKey:callbackId] callbackId:callbackId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)watch {
|
||||
struct kevent event;
|
||||
struct timespec timeout = { 5, 0 }; // 5 seconds timeout.
|
||||
|
||||
while (_keepWatching) {
|
||||
@autoreleasepool {
|
||||
int numberOfEvents = kevent(_kq, NULL, 0, &event, 1, &timeout);
|
||||
if (numberOfEvents == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *eventFlag = nil;
|
||||
NSString *newPath = nil;
|
||||
NSString *path = [(NSString *)event.udata retain];
|
||||
|
||||
if (event.fflags & NOTE_WRITE) {
|
||||
eventFlag = @"contents-change";
|
||||
}
|
||||
else if ([self isAtomicWrite:event]) {
|
||||
eventFlag = @"contents-change";
|
||||
// Atomic writes require the kqueue to be recreated
|
||||
[self removeKeventForPath:path];
|
||||
[self createKeventForPath:path];
|
||||
}
|
||||
else if (event.fflags & NOTE_DELETE) {
|
||||
eventFlag = @"remove";
|
||||
}
|
||||
else if (event.fflags & NOTE_RENAME) {
|
||||
eventFlag = @"move";
|
||||
char pathBuffer[MAXPATHLEN];
|
||||
fcntl((int)event.ident, F_GETPATH, &pathBuffer);
|
||||
close(event.ident);
|
||||
newPath = [NSString stringWithUTF8String:pathBuffer];
|
||||
if (!newPath) {
|
||||
NSLog(@"WARNING: Ignoring rename event for deleted file '%@'", path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *callbacks;
|
||||
@synchronized(self) {
|
||||
callbacks = [NSDictionary dictionaryWithDictionary:[_callbacksByPath objectForKey:path]];
|
||||
}
|
||||
|
||||
if ([eventFlag isEqual:@"move"]) {
|
||||
[self changePath:path toNewPath:newPath];
|
||||
}
|
||||
|
||||
if ([eventFlag isEqual:@"remove"]) {
|
||||
[self unwatchPath:path callbackId:nil error:nil];
|
||||
}
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
for (NSString *key in callbacks) {
|
||||
WatchCallback callback = [callbacks objectForKey:key];
|
||||
callback(eventFlag, newPath ? newPath : path);
|
||||
}
|
||||
});
|
||||
|
||||
[path release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -4,7 +4,6 @@
|
||||
#import "atom_application.h"
|
||||
#import "native.h"
|
||||
#import "include/cef_base.h"
|
||||
#import "path_watcher.h"
|
||||
|
||||
#import <iostream>
|
||||
|
||||
@ -22,8 +21,7 @@ namespace v8_extensions {
|
||||
|
||||
void Native::CreateContextBinding(CefRefPtr<CefV8Context> context) {
|
||||
const char* methodNames[] = {
|
||||
"writeToPasteboard", "readFromPasteboard", "quit", "watchPath",
|
||||
"unwatchPath", "getWatchedPaths", "unwatchAllPaths", "moveToTrash",
|
||||
"writeToPasteboard", "readFromPasteboard", "quit", "moveToTrash",
|
||||
"reload", "setWindowState", "getWindowState", "beep", "crash"
|
||||
};
|
||||
|
||||
@ -67,67 +65,6 @@ namespace v8_extensions {
|
||||
[NSApp terminate:nil];
|
||||
return true;
|
||||
}
|
||||
else if (name == "watchPath") {
|
||||
NSString *path = stringFromCefV8Value(arguments[0]);
|
||||
CefRefPtr<CefV8Value> function = arguments[1];
|
||||
|
||||
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
|
||||
|
||||
WatchCallback callback = ^(NSString *eventType, NSString *path) {
|
||||
context->Enter();
|
||||
|
||||
CefV8ValueList args;
|
||||
|
||||
args.push_back(CefV8Value::CreateString(string([eventType UTF8String], [eventType lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
|
||||
args.push_back(CefV8Value::CreateString(string([path UTF8String], [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
|
||||
function->ExecuteFunction(function, args);
|
||||
|
||||
context->Exit();
|
||||
};
|
||||
|
||||
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
|
||||
NSString *watchId = [pathWatcher watchPath:path callback:[[callback copy] autorelease]];
|
||||
if (watchId) {
|
||||
retval = CefV8Value::CreateString([watchId UTF8String]);
|
||||
}
|
||||
else {
|
||||
exception = string("Failed to watch path '") + string([path UTF8String]) + string("' (it may not exist)");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (name == "unwatchPath") {
|
||||
NSString *path = stringFromCefV8Value(arguments[0]);
|
||||
NSString *callbackId = stringFromCefV8Value(arguments[1]);
|
||||
NSError *error = nil;
|
||||
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
|
||||
[pathWatcher unwatchPath:path callbackId:callbackId error:&error];
|
||||
|
||||
if (error) {
|
||||
exception = [[error localizedDescription] UTF8String];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (name == "getWatchedPaths") {
|
||||
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
|
||||
NSArray *paths = [pathWatcher watchedPaths];
|
||||
|
||||
CefRefPtr<CefV8Value> pathsArray = CefV8Value::CreateArray([paths count]);
|
||||
|
||||
for (int i = 0; i < [paths count]; i++) {
|
||||
CefRefPtr<CefV8Value> path = CefV8Value::CreateString([[paths objectAtIndex:i] UTF8String]);
|
||||
pathsArray->SetValue(i, path);
|
||||
}
|
||||
retval = pathsArray;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (name == "unwatchAllPaths") {
|
||||
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
|
||||
[pathWatcher unwatchAllPaths];
|
||||
return true;
|
||||
}
|
||||
else if (name == "moveToTrash") {
|
||||
NSString *sourcePath = stringFromCefV8Value(arguments[0]);
|
||||
bool success = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
|
||||
|
@ -15,6 +15,7 @@
|
||||
"async": "0.2.6",
|
||||
"nak": "0.2.12",
|
||||
"spellchecker": "0.2.0",
|
||||
"pathwatcher": "0.1.5",
|
||||
"plist": "git://github.com/nathansobo/node-plist.git",
|
||||
"space-pen": "git://github.com/nathansobo/space-pen.git"
|
||||
},
|
||||
|
@ -2,4 +2,4 @@
|
||||
|
||||
set -ex
|
||||
rm -rf ~/.atom
|
||||
CI_BUILD=true rake clean test
|
||||
rake clean test
|
||||
|
@ -13,9 +13,7 @@ else
|
||||
TARGET=$1
|
||||
fi
|
||||
|
||||
DISTURL="https://gh-contractor-zcbenz.s3.amazonaws.com/cefode2/prebuilt-cef"
|
||||
CEF_BASENAME="cef_binary_3.1423.1133_macosx"
|
||||
CEF_SYMBOLS_BASENAME="${CEF_BASENAME}_symbols"
|
||||
DISTURL="https://gh-contractor-zcbenz.s3.amazonaws.com/cefode3/prebuilt-cef"
|
||||
|
||||
TEMP_DIR=$(mktemp -d -t prebuilt-cef-download.XXXXXX)
|
||||
trap "rm -rf \"${TEMP_DIR}\"" EXIT
|
||||
@ -29,8 +27,8 @@ fi
|
||||
CURRENT_VERSION=`cat cef/version 2>&1`
|
||||
|
||||
if [[ $LATEST_VERSION != $CURRENT_VERSION ]]; then
|
||||
echo "Downloading/extracting cefode2 u${LATEST_VERSION}..."
|
||||
if [ -z "$CI_BUILD" ]; then
|
||||
echo "Downloading/extracting cefode3 u${LATEST_VERSION}..."
|
||||
if [ -t 1 ] ; then # If run from the terminal
|
||||
CURL_ARGS="--progress-bar"
|
||||
else
|
||||
CURL_ARGS="-fsS"
|
||||
@ -38,7 +36,7 @@ if [[ $LATEST_VERSION != $CURRENT_VERSION ]]; then
|
||||
curl $CURL_ARGS "${DISTURL}/cef_binary_latest.zip" > "${TEMP_DIR}/cef.zip"
|
||||
unzip -q "${TEMP_DIR}/cef.zip" -d "${TEMP_DIR}"
|
||||
[ -e "${TARGET}" ] && rm -rf "${TARGET}"
|
||||
mv "${TEMP_DIR}/${CEF_BASENAME}" "${TARGET}"
|
||||
mv "${TEMP_DIR}"/*_macosx "${TARGET}"
|
||||
echo ${LATEST_VERSION} > 'cef/version'
|
||||
fi
|
||||
|
||||
@ -46,7 +44,7 @@ if [[ "${SYMBOLS}" != "1" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Downloading/extracting symbols for cefode2 u${LATEST_VERSION}..."
|
||||
echo "Downloading/extracting symbols for cefode3 u${LATEST_VERSION}..."
|
||||
curl --progress-bar "${DISTURL}/cef_binary_latest_symbols.zip" > "${TEMP_DIR}/symbols.zip"
|
||||
unzip -q "${TEMP_DIR}/symbols.zip" -d "${TEMP_DIR}"
|
||||
mv "${TEMP_DIR}/${CEF_SYMBOLS_BASENAME}"/* "${TARGET}/Release"
|
||||
mv "${TEMP_DIR}"/*_macosx_symbols/* "${TARGET}/Release"
|
||||
|
@ -708,6 +708,168 @@ describe "EditSession", ->
|
||||
expect(editSession.selectMarker('bogus')).toBeFalsy()
|
||||
expect(editSession.getSelectedBufferRange()).toEqual rangeBefore
|
||||
|
||||
describe ".addSelectionBelow()", ->
|
||||
describe "when the selection is non-empty", ->
|
||||
it "selects the same region of the line below current selections if possible", ->
|
||||
editSession.setSelectedBufferRange([[3, 16], [3, 21]])
|
||||
editSession.addSelectionForBufferRange([[3, 25], [3, 34]])
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 16], [3, 21]]
|
||||
[[3, 25], [3, 34]]
|
||||
[[4, 16], [4, 21]]
|
||||
[[4, 25], [4, 29]]
|
||||
]
|
||||
for cursor in editSession.getCursors()
|
||||
expect(cursor.isVisible()).toBeFalsy()
|
||||
|
||||
it "skips lines that are too short to create a non-empty selection", ->
|
||||
editSession.setSelectedBufferRange([[3, 31], [3, 38]])
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 31], [3, 38]]
|
||||
[[6, 31], [6, 38]]
|
||||
]
|
||||
|
||||
it "honors the original selection's range (goal range) when adding across shorter lines", ->
|
||||
editSession.setSelectedBufferRange([[3, 22], [3, 38]])
|
||||
editSession.addSelectionBelow()
|
||||
editSession.addSelectionBelow()
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 22], [3, 38]]
|
||||
[[4, 22], [4, 29]]
|
||||
[[5, 22], [5, 30]]
|
||||
[[6, 22], [6, 38]]
|
||||
]
|
||||
|
||||
it "clears selection goal ranges when the selection changes", ->
|
||||
editSession.setSelectedBufferRange([[3, 22], [3, 38]])
|
||||
editSession.addSelectionBelow()
|
||||
editSession.selectLeft()
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 22], [3, 37]]
|
||||
[[4, 22], [4, 29]]
|
||||
[[5, 22], [5, 28]]
|
||||
]
|
||||
|
||||
# goal range from previous add selection is honored next time
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 22], [3, 37]]
|
||||
[[4, 22], [4, 29]]
|
||||
[[5, 22], [5, 30]] # select to end of line 5 because line 4's goal range was reset by line 3 previously
|
||||
[[6, 22], [6, 28]]
|
||||
]
|
||||
|
||||
describe "when the selection is empty", ->
|
||||
it "does not skip lines that are shorter than the current column", ->
|
||||
editSession.setCursorBufferPosition([3, 36])
|
||||
editSession.addSelectionBelow()
|
||||
editSession.addSelectionBelow()
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 36], [3, 36]]
|
||||
[[4, 29], [4, 29]]
|
||||
[[5, 30], [5, 30]]
|
||||
[[6, 36], [6, 36]]
|
||||
]
|
||||
|
||||
it "skips empty lines when the column is non-zero", ->
|
||||
editSession.setCursorBufferPosition([9, 4])
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[9, 4], [9, 4]]
|
||||
[[11, 4], [11, 4]]
|
||||
]
|
||||
|
||||
it "does not skip empty lines when the column is zero", ->
|
||||
editSession.setCursorBufferPosition([9, 0])
|
||||
editSession.addSelectionBelow()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[9, 0], [9, 0]]
|
||||
[[10, 0], [10, 0]]
|
||||
]
|
||||
|
||||
describe ".addSelectionAbove()", ->
|
||||
describe "when the selection is non-empty", ->
|
||||
it "selects the same region of the line above current selections if possible", ->
|
||||
editSession.setSelectedBufferRange([[3, 16], [3, 21]])
|
||||
editSession.addSelectionForBufferRange([[3, 37], [3, 44]])
|
||||
editSession.addSelectionAbove()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[2, 16], [2, 21]]
|
||||
[[2, 37], [2, 40]]
|
||||
[[3, 16], [3, 21]]
|
||||
[[3, 37], [3, 44]]
|
||||
]
|
||||
for cursor in editSession.getCursors()
|
||||
expect(cursor.isVisible()).toBeFalsy()
|
||||
|
||||
it "skips lines that are too short to create a non-empty selection", ->
|
||||
editSession.setSelectedBufferRange([[6, 31], [6, 38]])
|
||||
editSession.addSelectionAbove()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 31], [3, 38]]
|
||||
[[6, 31], [6, 38]]
|
||||
]
|
||||
|
||||
it "honors the original selection's range (goal range) when adding across shorter lines", ->
|
||||
editSession.setSelectedBufferRange([[6, 22], [6, 38]])
|
||||
editSession.addSelectionAbove()
|
||||
editSession.addSelectionAbove()
|
||||
editSession.addSelectionAbove()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 22], [3, 38]]
|
||||
[[4, 22], [4, 29]]
|
||||
[[5, 22], [5, 30]]
|
||||
[[6, 22], [6, 38]]
|
||||
]
|
||||
|
||||
describe "when the selection is empty", ->
|
||||
it "does not skip lines that are shorter than the current column", ->
|
||||
editSession.setCursorBufferPosition([6, 36])
|
||||
editSession.addSelectionAbove()
|
||||
editSession.addSelectionAbove()
|
||||
editSession.addSelectionAbove()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[3, 36], [3, 36]]
|
||||
[[4, 29], [4, 29]]
|
||||
[[5, 30], [5, 30]]
|
||||
[[6, 36], [6, 36]]
|
||||
]
|
||||
|
||||
it "skips empty lines when the column is non-zero", ->
|
||||
editSession.setCursorBufferPosition([11, 4])
|
||||
editSession.addSelectionAbove()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[9, 4], [9, 4]]
|
||||
[[11, 4], [11, 4]]
|
||||
]
|
||||
|
||||
it "does not skip empty lines when the column is zero", ->
|
||||
editSession.setCursorBufferPosition([10, 0])
|
||||
editSession.addSelectionAbove()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [
|
||||
[[9, 0], [9, 0]]
|
||||
[[10, 0], [10, 0]]
|
||||
]
|
||||
|
||||
describe ".consolidateSelections()", ->
|
||||
it "destroys all selections but the most recent, returning true if any selections were destroyed", ->
|
||||
editSession.setSelectedBufferRange([[3, 16], [3, 21]])
|
||||
selection1 = editSession.getSelection()
|
||||
selection2 = editSession.addSelectionForBufferRange([[3, 25], [3, 34]])
|
||||
selection3 = editSession.addSelectionForBufferRange([[8, 4], [8, 10]])
|
||||
|
||||
expect(editSession.getSelections()).toEqual [selection1, selection2, selection3]
|
||||
expect(editSession.consolidateSelections()).toBeTruthy()
|
||||
expect(editSession.getSelections()).toEqual [selection3]
|
||||
expect(selection3.isEmpty()).toBeFalsy()
|
||||
expect(editSession.consolidateSelections()).toBeFalsy()
|
||||
expect(editSession.getSelections()).toEqual [selection3]
|
||||
|
||||
describe "when the cursor is moved while there is a selection", ->
|
||||
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
|
||||
|
||||
|
@ -765,6 +765,18 @@ describe "Editor", ->
|
||||
expect(editor.getSelectionViews().length).toBe 1
|
||||
expect(editor.find('.region').length).toBe 3
|
||||
|
||||
describe "when a selection is added and removed before the display is updated", ->
|
||||
it "does not attempt to render the selection", ->
|
||||
# don't update display until we request it
|
||||
jasmine.unspy(editor, 'requestDisplayUpdate')
|
||||
spyOn(editor, 'requestDisplayUpdate')
|
||||
|
||||
editSession = editor.activeEditSession
|
||||
selection = editSession.addSelectionForBufferRange([[3, 0], [3, 4]])
|
||||
selection.destroy()
|
||||
editor.updateDisplay()
|
||||
expect(editor.getSelectionViews().length).toBe 1
|
||||
|
||||
describe "when the selection is created with the selectAll event", ->
|
||||
it "does not scroll to the end of the buffer", ->
|
||||
editor.height(150)
|
||||
@ -2460,3 +2472,19 @@ describe "Editor", ->
|
||||
expect(fsUtils.write).toHaveBeenCalled()
|
||||
expect(fsUtils.write.argsForCall[0][0]).toBe '/tmp/state'
|
||||
expect(typeof fsUtils.write.argsForCall[0][1]).toBe 'string'
|
||||
|
||||
describe "when the escape key is pressed on the editor", ->
|
||||
it "clears multiple selections if there are any, and otherwise allows other bindings to be handled", ->
|
||||
keymap.bindKeys '.editor', 'escape': 'test-event'
|
||||
testEventHandler = jasmine.createSpy("testEventHandler")
|
||||
|
||||
editor.on 'test-event', testEventHandler
|
||||
editor.activeEditSession.addSelectionForBufferRange([[3, 0], [3, 0]])
|
||||
expect(editor.activeEditSession.getSelections().length).toBe 2
|
||||
|
||||
editor.trigger(keydownEvent('escape'))
|
||||
expect(editor.activeEditSession.getSelections().length).toBe 1
|
||||
expect(testEventHandler).not.toHaveBeenCalled()
|
||||
|
||||
editor.trigger(keydownEvent('escape'))
|
||||
expect(testEventHandler).toHaveBeenCalled()
|
||||
|
@ -54,6 +54,7 @@ describe 'File', ->
|
||||
waitsFor "remove event", (done) -> file.on 'removed', done
|
||||
|
||||
it "it updates its path", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'moved', moveHandler
|
||||
@ -67,6 +68,7 @@ describe 'File', ->
|
||||
expect(file.getPath()).toBe newPath
|
||||
|
||||
it "maintains 'contents-changed' events set on previous path", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'moved', moveHandler
|
||||
|
@ -113,7 +113,8 @@ describe "Keymap", ->
|
||||
describe "when the matching selectors differ in specificity", ->
|
||||
it "triggers the binding for the most specific selector", ->
|
||||
keymap.bindKeys 'div .child-node', 'x': 'foo'
|
||||
keymap.bindKeys '.command-mode .child-node', 'x': 'baz'
|
||||
keymap.bindKeys '.command-mode .child-node !important', 'x': 'baz'
|
||||
keymap.bindKeys '.command-mode .child-node', 'x': 'quux'
|
||||
keymap.bindKeys '.child-node', 'x': 'bar'
|
||||
|
||||
fooHandler = jasmine.createSpy 'fooHandler'
|
||||
|
@ -259,6 +259,32 @@ describe "Project", ->
|
||||
match: 'aa'
|
||||
range: [[1, 3], [1, 5]]
|
||||
|
||||
describe "when the core.excludeVcsIgnoredPaths config is truthy", ->
|
||||
[projectPath, ignoredPath] = []
|
||||
|
||||
beforeEach ->
|
||||
projectPath = fsUtils.resolveOnLoadPath('fixtures/git/working-dir')
|
||||
ignoredPath = fsUtils.join(projectPath, 'ignored.txt')
|
||||
fsUtils.write(ignoredPath, 'this match should not be included')
|
||||
|
||||
afterEach ->
|
||||
fsUtils.remove(ignoredPath) if fsUtils.exists(ignoredPath)
|
||||
|
||||
it "excludes ignored files", ->
|
||||
project.setPath(projectPath)
|
||||
config.set('core.excludeVcsIgnoredPaths', true)
|
||||
paths = []
|
||||
matches = []
|
||||
waitsForPromise ->
|
||||
project.scan /match/, ({path, match, range}) ->
|
||||
paths.push(path)
|
||||
matches.push(match)
|
||||
|
||||
runs ->
|
||||
expect(paths.length).toBe 0
|
||||
expect(matches.length).toBe 0
|
||||
|
||||
|
||||
describe "serialization", ->
|
||||
it "restores the project path", ->
|
||||
newProject = Project.deserialize(project.serialize())
|
||||
|
@ -32,10 +32,12 @@ describe 'Buffer', ->
|
||||
expect(buffer.undoManager.undoHistory.length).toBe 0
|
||||
|
||||
describe "when no file exists for the path", ->
|
||||
it "throws an exception", ->
|
||||
it "is modified and is initially empty", ->
|
||||
filePath = "does-not-exist.txt"
|
||||
expect(fsUtils.exists(filePath)).toBeFalsy()
|
||||
expect(-> project.bufferForPath(filePath)).toThrow()
|
||||
buffer = project.bufferForPath(filePath)
|
||||
expect(buffer.isModified()).toBeTruthy()
|
||||
expect(buffer.getText()).toBe ''
|
||||
|
||||
describe "when no path is given", ->
|
||||
it "creates an empty buffer", ->
|
||||
@ -63,6 +65,8 @@ describe 'Buffer', ->
|
||||
expect(eventHandler).toHaveBeenCalledWith(bufferToChange)
|
||||
|
||||
it "triggers a `path-changed` event when the file is moved", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
|
||||
fsUtils.remove(newPath) if fsUtils.exists(newPath)
|
||||
fsUtils.move(path, newPath)
|
||||
|
||||
@ -264,18 +268,44 @@ describe 'Buffer', ->
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(true)
|
||||
expect(buffer.isModified()).toBe true
|
||||
|
||||
it "reports the modified status changing to false after a buffer to a non-existent file is saved", ->
|
||||
filePath = "/tmp/atom-tmp-file"
|
||||
fsUtils.remove(filePath) if fsUtils.exists(filePath)
|
||||
expect(fsUtils.exists(filePath)).toBeFalsy()
|
||||
buffer.release()
|
||||
buffer = project.bufferForPath(filePath)
|
||||
modifiedHandler = jasmine.createSpy("modifiedHandler")
|
||||
buffer.on 'modified-status-changed', modifiedHandler
|
||||
|
||||
buffer.insert([0,0], "hi")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(buffer.isModified()).toBe true
|
||||
modifiedHandler.reset()
|
||||
|
||||
buffer.save()
|
||||
expect(fsUtils.exists(filePath)).toBeTruthy()
|
||||
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(false)
|
||||
expect(buffer.isModified()).toBe false
|
||||
modifiedHandler.reset()
|
||||
|
||||
buffer.insert([0, 0], 'x')
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(true)
|
||||
expect(buffer.isModified()).toBe true
|
||||
|
||||
it "returns false for an empty buffer with no path", ->
|
||||
buffer.release()
|
||||
buffer = project.bufferForPath(null)
|
||||
expect(buffer.isModified()).toBeFalsy()
|
||||
|
||||
it "returns true for a non-empty buffer with no path", ->
|
||||
buffer.release()
|
||||
buffer = project.bufferForPath(null)
|
||||
buffer.setText('a')
|
||||
expect(buffer.isModified()).toBeTruthy()
|
||||
buffer.setText('\n')
|
||||
expect(buffer.isModified()).toBeTruthy()
|
||||
buffer.release()
|
||||
buffer = project.bufferForPath(null)
|
||||
buffer.setText('a')
|
||||
expect(buffer.isModified()).toBeTruthy()
|
||||
buffer.setText('\n')
|
||||
expect(buffer.isModified()).toBeTruthy()
|
||||
|
||||
describe ".getLines()", ->
|
||||
it "returns an array of lines in the text contents", ->
|
||||
|
@ -13,6 +13,7 @@ File = require 'file'
|
||||
Editor = require 'editor'
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
fsUtils = require 'fs-utils'
|
||||
pathwatcher = require 'pathwatcher'
|
||||
RootView = require 'root-view'
|
||||
Git = require 'git'
|
||||
requireStylesheet "jasmine"
|
||||
@ -95,8 +96,8 @@ afterEach ->
|
||||
waits(0) # yield to ui thread to make screen update more frequently
|
||||
|
||||
ensureNoPathSubscriptions = ->
|
||||
watchedPaths = $native.getWatchedPaths()
|
||||
$native.unwatchAllPaths()
|
||||
watchedPaths = pathwatcher.getWatchedPaths()
|
||||
pathwatcher.closeAllWatchers()
|
||||
if watchedPaths.length > 0
|
||||
throw new Error("Leaking subscriptions for paths: " + watchedPaths.join(", "))
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
fsUtils = require 'fs-utils'
|
||||
_ = require 'underscore'
|
||||
Package = require 'package'
|
||||
TextMatePackage = require 'text-mate-package'
|
||||
Theme = require 'theme'
|
||||
|
||||
messageIdCounter = 1
|
||||
@ -127,6 +126,9 @@ _.extend atom,
|
||||
newWindow: (args...) ->
|
||||
@sendMessageToBrowserProcess('newWindow', args)
|
||||
|
||||
restartRendererProcess: ->
|
||||
@sendMessageToBrowserProcess('restartRendererProcess')
|
||||
|
||||
confirm: (message, detailedMessage, buttonLabelsAndCallbacks...) ->
|
||||
wrapCallback = (callback) => => @dismissModal(callback)
|
||||
@presentModal =>
|
||||
@ -208,9 +210,13 @@ _.extend atom,
|
||||
originalSendMessageToBrowserProcess(name, data)
|
||||
|
||||
receiveMessageFromBrowserProcess: (name, data) ->
|
||||
if name is 'reply'
|
||||
[messageId, callbackIndex] = data.shift()
|
||||
@pendingBrowserProcessCallbacks[messageId]?[callbackIndex]?(data...)
|
||||
switch name
|
||||
when 'reply'
|
||||
[messageId, callbackIndex] = data.shift()
|
||||
@pendingBrowserProcessCallbacks[messageId]?[callbackIndex]?(data...)
|
||||
when 'openPath'
|
||||
path = data[0]
|
||||
rootView?.open(path)
|
||||
|
||||
setWindowState: (keyPath, value) ->
|
||||
windowState = @getWindowState()
|
||||
|
@ -16,9 +16,10 @@ class BindingSet
|
||||
parser: null
|
||||
name: null
|
||||
|
||||
constructor: (@selector, commandsByKeystrokes, @index, @name) ->
|
||||
constructor: (selector, commandsByKeystrokes, @index, @name) ->
|
||||
BindingSet.parser ?= PEG.buildParser(fsUtils.read(require.resolve 'keystroke-pattern.pegjs'))
|
||||
@specificity = Specificity(@selector)
|
||||
@specificity = Specificity(selector)
|
||||
@selector = selector.replace(/!important/g, '')
|
||||
@commandsByKeystrokes = @normalizeCommandsByKeystrokes(commandsByKeystrokes)
|
||||
|
||||
commandForEvent: (event) ->
|
||||
|
@ -27,6 +27,9 @@ class BufferMarker
|
||||
isReversed: ->
|
||||
@tailPosition? and @headPosition.isLessThan(@tailPosition)
|
||||
|
||||
isRangeEmpty: ->
|
||||
@getHeadPosition().isEqual(@getTailPosition())
|
||||
|
||||
getRange: ->
|
||||
if @tailPosition
|
||||
new Range(@tailPosition, @headPosition)
|
||||
|
@ -12,9 +12,9 @@ class Cursor
|
||||
needsAutoscroll: null
|
||||
|
||||
constructor: ({@editSession, @marker}) ->
|
||||
@updateVisibility()
|
||||
@editSession.observeMarker @marker, (e) =>
|
||||
@setVisible(@selection.isEmpty())
|
||||
|
||||
@updateVisibility()
|
||||
{oldHeadScreenPosition, newHeadScreenPosition} = e
|
||||
{oldHeadBufferPosition, newHeadBufferPosition} = e
|
||||
{bufferChanged} = e
|
||||
@ -34,6 +34,7 @@ class Cursor
|
||||
@needsAutoscroll = true
|
||||
|
||||
destroy: ->
|
||||
@destroyed = true
|
||||
@editSession.destroyMarker(@marker)
|
||||
@editSession.removeCursor(this)
|
||||
@trigger 'destroyed'
|
||||
@ -59,6 +60,9 @@ class Cursor
|
||||
unless fn()
|
||||
@trigger 'autoscrolled' if @needsAutoscroll
|
||||
|
||||
updateVisibility: ->
|
||||
@setVisible(@editSession.isMarkerRangeEmpty(@marker))
|
||||
|
||||
setVisible: (visible) ->
|
||||
if @visible != visible
|
||||
@visible = visible
|
||||
@ -84,6 +88,7 @@ class Cursor
|
||||
|
||||
clearSelection: ->
|
||||
if @selection
|
||||
@selection.goalBufferRange = null
|
||||
@selection.clear() unless @selection.retainSelection
|
||||
|
||||
getScreenRow: ->
|
||||
|
@ -1,6 +1,7 @@
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
File = require 'file'
|
||||
EventEmitter = require 'event-emitter'
|
||||
|
||||
@ -39,12 +40,12 @@ class Directory
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchSubscription = fsUtils.watchPath @path, (eventType) =>
|
||||
@trigger "contents-changed" if eventType is "contents-change"
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType) =>
|
||||
@trigger "contents-changed" if eventType is "change"
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription?
|
||||
@watchSubscription.unwatch()
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
||||
|
||||
_.extend Directory.prototype, EventEmitter
|
||||
|
@ -400,6 +400,9 @@ class DisplayBuffer
|
||||
isMarkerReversed: (id) ->
|
||||
@buffer.isMarkerReversed(id)
|
||||
|
||||
isMarkerRangeEmpty: (id) ->
|
||||
@buffer.isMarkerRangeEmpty(id)
|
||||
|
||||
observeMarker: (id, callback) ->
|
||||
@getMarker(id).observe(callback)
|
||||
|
||||
|
@ -125,8 +125,8 @@ class EditSession
|
||||
getTabLength: -> @displayBuffer.getTabLength()
|
||||
setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)
|
||||
|
||||
clipBufferPosition: (bufferPosition) ->
|
||||
@buffer.clipPosition(bufferPosition)
|
||||
clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)
|
||||
clipBufferRange: (range) -> @buffer.clipRange(range)
|
||||
|
||||
indentationForBufferRow: (bufferRow) ->
|
||||
@indentLevelForLine(@lineForBufferRow(bufferRow))
|
||||
@ -556,6 +556,9 @@ class EditSession
|
||||
isMarkerReversed: (args...) ->
|
||||
@displayBuffer.isMarkerReversed(args...)
|
||||
|
||||
isMarkerRangeEmpty: (args...) ->
|
||||
@displayBuffer.isMarkerRangeEmpty(args...)
|
||||
|
||||
hasMultipleCursors: ->
|
||||
@getCursors().length > 1
|
||||
|
||||
@ -585,10 +588,10 @@ class EditSession
|
||||
unless options.preserveFolds
|
||||
@destroyFoldsIntersectingBufferRange(@getMarkerBufferRange(marker))
|
||||
cursor = @addCursor(marker)
|
||||
selection = new Selection({editSession: this, marker, cursor})
|
||||
selection = new Selection(_.extend({editSession: this, marker, cursor}, options))
|
||||
@selections.push(selection)
|
||||
selectionBufferRange = selection.getBufferRange()
|
||||
@mergeIntersectingSelections()
|
||||
@mergeIntersectingSelections() unless options.suppressMerge
|
||||
if selection.destroyed
|
||||
for selection in @getSelections()
|
||||
if selection.intersectsBufferRange(selectionBufferRange)
|
||||
@ -600,7 +603,7 @@ class EditSession
|
||||
addSelectionForBufferRange: (bufferRange, options={}) ->
|
||||
options = _.defaults({invalidationStrategy: 'never'}, options)
|
||||
marker = @markBufferRange(bufferRange, options)
|
||||
@addSelection(marker)
|
||||
@addSelection(marker, options)
|
||||
|
||||
setSelectedBufferRange: (bufferRange, options) ->
|
||||
@setSelectedBufferRanges([bufferRange], options)
|
||||
@ -623,13 +626,16 @@ class EditSession
|
||||
_.remove(@selections, selection)
|
||||
|
||||
clearSelections: ->
|
||||
lastSelection = @getLastSelection()
|
||||
for selection in @getSelections() when selection != lastSelection
|
||||
selection.destroy()
|
||||
lastSelection.clear()
|
||||
@consolidateSelections()
|
||||
@getSelection().clear()
|
||||
|
||||
clearAllSelections: ->
|
||||
selection.destroy() for selection in @getSelections()
|
||||
consolidateSelections: ->
|
||||
selections = @getSelections()
|
||||
if selections.length > 1
|
||||
selection.destroy() for selection in selections[0...-1]
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
getSelections: -> new Array(@selections...)
|
||||
|
||||
@ -761,6 +767,12 @@ class EditSession
|
||||
selectLine: ->
|
||||
@expandSelectionsForward (selection) => selection.selectLine()
|
||||
|
||||
addSelectionBelow: ->
|
||||
@expandSelectionsForward (selection) => selection.addSelectionBelow()
|
||||
|
||||
addSelectionAbove: ->
|
||||
@expandSelectionsBackward (selection) => selection.addSelectionAbove()
|
||||
|
||||
transpose: ->
|
||||
@mutateSelectedText (selection) =>
|
||||
if selection.isEmpty()
|
||||
|
@ -60,7 +60,7 @@ class Editor extends View
|
||||
if editSessionOrOptions instanceof EditSession
|
||||
editSession = editSessionOrOptions
|
||||
else
|
||||
{editSession, @mini} = (editSessionOrOptions ? {})
|
||||
{editSession, @mini} = editSessionOrOptions ? {}
|
||||
|
||||
requireStylesheet 'editor'
|
||||
|
||||
@ -104,6 +104,7 @@ class Editor extends View
|
||||
'editor:move-to-previous-word': @moveCursorToPreviousWord
|
||||
'editor:select-word': @selectWord
|
||||
'editor:newline': @insertNewline
|
||||
'editor:consolidate-selections': @consolidateSelections
|
||||
'editor:indent': @indent
|
||||
'editor:auto-indent': @autoIndent
|
||||
'editor:indent-selected-rows': @indentSelectedRows
|
||||
@ -124,6 +125,8 @@ class Editor extends View
|
||||
'editor:select-to-end-of-word': @selectToEndOfWord
|
||||
'editor:select-to-beginning-of-word': @selectToBeginningOfWord
|
||||
'editor:select-to-beginning-of-next-word': @selectToBeginningOfNextWord
|
||||
'editor:add-selection-below': @addSelectionBelow
|
||||
'editor:add-selection-above': @addSelectionAbove
|
||||
'editor:select-line': @selectLine
|
||||
'editor:transpose': @transpose
|
||||
'editor:upper-case': @upperCase
|
||||
@ -165,7 +168,7 @@ class Editor extends View
|
||||
documentation = {}
|
||||
for name, method of editorBindings
|
||||
do (name, method) =>
|
||||
@command name, => method.call(this); false
|
||||
@command name, (e) => method.call(this, e); false
|
||||
|
||||
getCursor: -> @activeEditSession.getCursor()
|
||||
getCursors: -> @activeEditSession.getCursors()
|
||||
@ -214,6 +217,8 @@ class Editor extends View
|
||||
selectAll: -> @activeEditSession.selectAll()
|
||||
selectToBeginningOfLine: -> @activeEditSession.selectToBeginningOfLine()
|
||||
selectToEndOfLine: -> @activeEditSession.selectToEndOfLine()
|
||||
addSelectionBelow: -> @activeEditSession.addSelectionBelow()
|
||||
addSelectionAbove: -> @activeEditSession.addSelectionAbove()
|
||||
selectToBeginningOfWord: -> @activeEditSession.selectToBeginningOfWord()
|
||||
selectToEndOfWord: -> @activeEditSession.selectToEndOfWord()
|
||||
selectToBeginningOfNextWord: -> @activeEditSession.selectToEndOfWord(); @activeEditSession.selectRight()
|
||||
@ -234,6 +239,7 @@ class Editor extends View
|
||||
cutToEndOfLine: -> @activeEditSession.cutToEndOfLine()
|
||||
insertText: (text, options) -> @activeEditSession.insertText(text, options)
|
||||
insertNewline: -> @activeEditSession.insertNewline()
|
||||
consolidateSelections: (e) -> e.abortKeyBinding() unless @activeEditSession.consolidateSelections()
|
||||
insertNewlineBelow: -> @activeEditSession.insertNewlineBelow()
|
||||
insertNewlineAbove: -> @activeEditSession.insertNewlineAbove()
|
||||
indent: (options) -> @activeEditSession.indent(options)
|
||||
@ -782,7 +788,7 @@ class Editor extends View
|
||||
|
||||
updateCursorViews: ->
|
||||
if @newCursors.length > 0
|
||||
@addCursorView(cursor) for cursor in @newCursors
|
||||
@addCursorView(cursor) for cursor in @newCursors when not cursor.destroyed
|
||||
@syncCursorAnimations()
|
||||
@newCursors = []
|
||||
|
||||
@ -794,11 +800,11 @@ class Editor extends View
|
||||
|
||||
updateSelectionViews: ->
|
||||
if @newSelections.length > 0
|
||||
@addSelectionView(selection) for selection in @newSelections
|
||||
@addSelectionView(selection) for selection in @newSelections when not selection.destroyed
|
||||
@newSelections = []
|
||||
|
||||
for selectionView in @getSelectionViews()
|
||||
if selectionView.destroyed
|
||||
if selectionView.needsRemoval
|
||||
selectionView.remove()
|
||||
else
|
||||
selectionView.updateDisplay()
|
||||
|
@ -2,6 +2,7 @@ EventEmitter = require 'event-emitter'
|
||||
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
@ -45,12 +46,13 @@ class File
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
handleNativeChangeEvent: (eventType, path) ->
|
||||
if eventType is "remove"
|
||||
if eventType is "delete"
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
@detectResurrectionAfterDelay()
|
||||
else if eventType is "move"
|
||||
else if eventType is "rename"
|
||||
@setPath(path)
|
||||
@trigger "moved"
|
||||
else if eventType is "contents-change"
|
||||
else if eventType is "change"
|
||||
oldContents = @read()
|
||||
newContents = @read(true)
|
||||
return if oldContents == newContents
|
||||
@ -62,19 +64,18 @@ class File
|
||||
detectResurrection: ->
|
||||
if @exists()
|
||||
@subscribeToNativeChangeEvents()
|
||||
@handleNativeChangeEvent("contents-change", @getPath())
|
||||
@handleNativeChangeEvent("change", @getPath())
|
||||
else
|
||||
@cachedContents = null
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
@trigger "removed"
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchSubscription = fsUtils.watchPath @path, (eventType, path) =>
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType, path) =>
|
||||
@handleNativeChangeEvent(eventType, path)
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription
|
||||
@watchSubscription.unwatch()
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
||||
|
||||
_.extend File.prototype, EventEmitter
|
||||
|
@ -9,6 +9,8 @@
|
||||
'ctrl-]': 'editor:unfold-current-row'
|
||||
'ctrl-{': 'editor:fold-all'
|
||||
'ctrl-}': 'editor:unfold-all'
|
||||
'alt-shift-down': 'editor:add-selection-below'
|
||||
'alt-shift-up': 'editor:add-selection-above'
|
||||
'alt-meta-ctrl-f': 'editor:fold-selection'
|
||||
'shift-tab': 'editor:outdent-selected-rows'
|
||||
'meta-[': 'editor:outdent-selected-rows'
|
||||
@ -30,3 +32,6 @@
|
||||
'enter': 'core:confirm',
|
||||
'escape': 'core:cancel'
|
||||
'meta-w': 'core:cancel'
|
||||
|
||||
'.editor !important, .editor.mini !important':
|
||||
'escape': 'editor:consolidate-selections'
|
||||
|
@ -7,6 +7,8 @@
|
||||
'ctrl-N': 'core:select-down'
|
||||
'ctrl-F': 'core:select-right'
|
||||
'ctrl-B': 'core:select-left'
|
||||
'alt-ctrl-n': 'editor:add-selection-below'
|
||||
'alt-ctrl-p': 'editor:add-selection-above'
|
||||
'ctrl-h': 'core:backspace'
|
||||
'ctrl-d': 'core:delete'
|
||||
|
||||
|
@ -97,11 +97,8 @@ class RootView extends View
|
||||
changeFocus = options.changeFocus ? true
|
||||
path = project.resolve(path) if path?
|
||||
if activePane = @getActivePane()
|
||||
if editSession = activePane.itemForUri(path)
|
||||
activePane.showItem(editSession)
|
||||
else
|
||||
editSession = project.buildEditSession(path)
|
||||
activePane.showItem(editSession)
|
||||
editSession = activePane.itemForUri(path) ? project.buildEditSession(path)
|
||||
activePane.showItem(editSession)
|
||||
else
|
||||
editSession = project.buildEditSession(path)
|
||||
activePane = new Pane(editSession)
|
||||
|
@ -8,13 +8,13 @@ class SelectionView extends View
|
||||
@div class: 'selection'
|
||||
|
||||
regions: null
|
||||
destroyed: false
|
||||
needsRemoval: false
|
||||
|
||||
initialize: ({@editor, @selection} = {}) ->
|
||||
@regions = []
|
||||
@selection.on 'screen-range-changed', => @editor.requestDisplayUpdate()
|
||||
@selection.on 'destroyed', =>
|
||||
@destroyed = true
|
||||
@needsRemoval = true
|
||||
@editor.requestDisplayUpdate()
|
||||
|
||||
updateDisplay: ->
|
||||
|
@ -4,11 +4,15 @@ _ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class Selection
|
||||
wordwise: false
|
||||
cursor: null
|
||||
marker: null
|
||||
editSession: null
|
||||
initialScreenRange: null
|
||||
goalBufferRange: null
|
||||
wordwise: false
|
||||
needsAutoscroll: null
|
||||
|
||||
constructor: ({@cursor, @marker, @editSession}) ->
|
||||
constructor: ({@cursor, @marker, @editSession, @goalBufferRange}) ->
|
||||
@cursor.selection = this
|
||||
@editSession.observeMarker @marker, => @screenRangeChanged()
|
||||
@cursor.on 'destroyed.selection', =>
|
||||
@ -148,6 +152,40 @@ class Selection
|
||||
selectToEndOfWord: ->
|
||||
@modifySelection => @cursor.moveToEndOfWord()
|
||||
|
||||
addSelectionBelow: ->
|
||||
range = (@goalBufferRange ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
|
||||
for row in [nextRow..@editSession.getLastBufferRow()]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editSession.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
|
||||
break
|
||||
|
||||
addSelectionAbove: ->
|
||||
range = (@goalBufferRange ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
|
||||
for row in [previousRow..0]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editSession.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
|
||||
break
|
||||
|
||||
insertText: (text, options={}) ->
|
||||
oldBufferRange = @getBufferRange()
|
||||
@editSession.destroyFoldsContainingBufferRow(oldBufferRange.end.row)
|
||||
@ -369,10 +407,14 @@ class Selection
|
||||
@getBufferRange().intersectsWith(bufferRange)
|
||||
|
||||
intersectsWith: (otherSelection) ->
|
||||
@getScreenRange().intersectsWith(otherSelection.getScreenRange())
|
||||
@getBufferRange().intersectsWith(otherSelection.getBufferRange())
|
||||
|
||||
merge: (otherSelection, options) ->
|
||||
@setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)
|
||||
if @goalBufferRange and otherSelection.goalBufferRange
|
||||
@goalBufferRange = @goalBufferRange.union(otherSelection.goalBufferRange)
|
||||
else if otherSelection.goalBufferRange
|
||||
@goalBufferRange = otherSelection.goalBufferRange
|
||||
otherSelection.destroy()
|
||||
|
||||
_.extend Selection.prototype, EventEmitter
|
||||
|
@ -37,17 +37,17 @@ class Buffer
|
||||
@lineEndings = []
|
||||
|
||||
if path
|
||||
throw "Path '#{path}' does not exist" unless fsUtils.exists(path)
|
||||
@setPath(path)
|
||||
if initialText?
|
||||
@setText(initialText)
|
||||
@updateCachedDiskContents()
|
||||
else
|
||||
else if fsUtils.exists(path)
|
||||
@reload()
|
||||
else
|
||||
@setText('')
|
||||
else
|
||||
@setText(initialText ? '')
|
||||
|
||||
|
||||
@undoManager = new UndoManager(this)
|
||||
|
||||
destroy: ->
|
||||
@ -340,6 +340,9 @@ class Buffer
|
||||
isMarkerReversed: (id) ->
|
||||
@validMarkers[id]?.isReversed()
|
||||
|
||||
isMarkerRangeEmpty: (id) ->
|
||||
@validMarkers[id]?.isRangeEmpty()
|
||||
|
||||
observeMarker: (id, callback) ->
|
||||
@validMarkers[id]?.observe(callback)
|
||||
|
||||
|
@ -25,6 +25,12 @@ window.setUpEnvironment = ->
|
||||
$(document).on 'keydown', keymap.handleKeyEvent
|
||||
keymap.bindDefaultKeys()
|
||||
|
||||
ignoreEvents = (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$(document).on 'dragover', ignoreEvents
|
||||
$(document).on 'drop', ignoreEvents
|
||||
|
||||
requireStylesheet 'reset'
|
||||
requireStylesheet 'atom'
|
||||
requireStylesheet 'overlay'
|
||||
@ -148,7 +154,16 @@ window.applyStylesheet = (id, text, ttype = 'bundled') ->
|
||||
$("head").append "<style class='#{ttype}' id='#{id}'>#{text}</style>"
|
||||
|
||||
window.reload = ->
|
||||
$native.reload()
|
||||
timesReloaded = process.global.timesReloaded ? 0
|
||||
++timesReloaded
|
||||
|
||||
restartValue = if window.location.search.indexOf('spec-bootstrap') == -1 then 10 else 3
|
||||
|
||||
if timesReloaded > restartValue
|
||||
atom.restartRendererProcess()
|
||||
else
|
||||
$native.reload()
|
||||
process.global.timesReloaded = timesReloaded
|
||||
|
||||
window.onerror = ->
|
||||
atom.showDevTools()
|
||||
|
@ -42,6 +42,7 @@ module.exports =
|
||||
goToMatchingPair: (editor) ->
|
||||
return unless @pairHighlighted
|
||||
return unless underlayer = editor.getPane()?.find('.underlayer')
|
||||
return unless underlayer.isVisible()
|
||||
|
||||
position = editor.getCursorBufferPosition()
|
||||
previousPosition = position.translate([0, -1])
|
||||
|
@ -34,8 +34,12 @@
|
||||
'name': 'markup.heading.gfm'
|
||||
}
|
||||
{
|
||||
'match': '\\:[^\\:\\s]+\\:'
|
||||
'match': '(\\:)([^\\:\\s]+)(\\:)'
|
||||
'name': 'string.emoji.gfm'
|
||||
'captures':
|
||||
'1': 'name': 'string.emoji.start.gfm'
|
||||
'2': 'name': 'string.emoji.word.gfm'
|
||||
'3': 'name': 'string.emoji.end.gfm'
|
||||
}
|
||||
{
|
||||
'match': '^\\s*[\\*]{3,}\\s*$'
|
||||
|
@ -78,7 +78,9 @@ describe "GitHub Flavored Markdown grammar", ->
|
||||
it "tokenizies an :emoji:", ->
|
||||
{tokens} = grammar.tokenizeLine("this is :no_good:")
|
||||
expect(tokens[0]).toEqual value: "this is ", scopes: ["source.gfm"]
|
||||
expect(tokens[1]).toEqual value: ":no_good:", scopes: ["source.gfm", "string.emoji.gfm"]
|
||||
expect(tokens[1]).toEqual value: ":", scopes: ["source.gfm", "string.emoji.gfm", "string.emoji.start.gfm"]
|
||||
expect(tokens[2]).toEqual value: "no_good", scopes: ["source.gfm", "string.emoji.gfm", "string.emoji.word.gfm"]
|
||||
expect(tokens[3]).toEqual value: ":", scopes: ["source.gfm", "string.emoji.gfm", "string.emoji.end.gfm"]
|
||||
|
||||
{tokens} = grammar.tokenizeLine("this is :no good:")
|
||||
expect(tokens[0]).toEqual value: "this is :no good:", scopes: ["source.gfm"]
|
||||
|
@ -18,7 +18,7 @@ class SpellCheckView extends View
|
||||
|
||||
@subscribeToBuffer()
|
||||
|
||||
subscribeToBuffer: ->
|
||||
unsubscribeFromBuffer: ->
|
||||
@destroyViews()
|
||||
@task?.abort()
|
||||
|
||||
@ -26,6 +26,9 @@ class SpellCheckView extends View
|
||||
@buffer.off '.spell-check'
|
||||
@buffer = null
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@unsubscribeFromBuffer()
|
||||
|
||||
if @spellCheckCurrentGrammar()
|
||||
@buffer = @editor.getBuffer()
|
||||
@buffer.on 'contents-modified.spell-check', => @updateMisspellings()
|
||||
@ -47,6 +50,10 @@ class SpellCheckView extends View
|
||||
@append(view)
|
||||
|
||||
updateMisspellings: ->
|
||||
unless @editor.activeEditSession?
|
||||
@unsubscribeFromBuffer()
|
||||
return
|
||||
|
||||
@task?.abort()
|
||||
|
||||
callback = (misspellings) =>
|
||||
|
@ -81,6 +81,8 @@ class TabBarView extends View
|
||||
event.preventDefault()
|
||||
return
|
||||
|
||||
event.originalEvent.dataTransfer.setData 'atom-event', 'true'
|
||||
|
||||
el = $(event.target).closest('.sortable')
|
||||
el.addClass 'is-dragging'
|
||||
event.originalEvent.dataTransfer.setData 'sortable-index', el.index()
|
||||
@ -93,6 +95,11 @@ class TabBarView extends View
|
||||
@find(".is-dragging").removeClass 'is-dragging'
|
||||
|
||||
onDragOver: (event) =>
|
||||
unless event.originalEvent.dataTransfer.getData('atom-event') is 'true'
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return
|
||||
|
||||
event.preventDefault()
|
||||
currentDropTargetIndex = @find(".is-drop-target").index()
|
||||
newDropTargetIndex = @getDropTargetIndex(event)
|
||||
@ -107,6 +114,11 @@ class TabBarView extends View
|
||||
|
||||
|
||||
onDrop: (event) =>
|
||||
unless event.originalEvent.dataTransfer.getData('atom-event') is 'true'
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return
|
||||
|
||||
event.stopPropagation()
|
||||
@children('.is-drop-target').removeClass 'is-drop-target'
|
||||
@children('.drop-target-is-after').removeClass 'drop-target-is-after'
|
||||
|
@ -278,3 +278,19 @@ describe "TabBarView", ->
|
||||
expect(pane2.getItems()).toEqual [item2b, item1]
|
||||
expect(pane2.activeItem).toBe item1
|
||||
expect(pane2.focus).toHaveBeenCalled()
|
||||
|
||||
describe 'when a non-tab is dragged to pane', ->
|
||||
it 'has no effect', ->
|
||||
expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
|
||||
expect(pane.getItems()).toEqual [item1, editSession1, item2]
|
||||
expect(pane.activeItem).toBe item2
|
||||
spyOn(pane, 'focus')
|
||||
|
||||
[dragStartEvent, dropEvent] = buildDragEvents(tabBar.tabAtIndex(0), tabBar.tabAtIndex(0))
|
||||
tabBar.onDrop(dropEvent)
|
||||
|
||||
expect(tabBar.getTabs().map (tab) -> tab.text()).toEqual ["Item 1", "sample.js", "Item 2"]
|
||||
expect(pane.getItems()).toEqual [item1, editSession1, item2]
|
||||
expect(pane.activeItem).toBe item2
|
||||
expect(pane.focus).not.toHaveBeenCalled()
|
||||
|
||||
|
@ -30,7 +30,7 @@ class DirectoryView extends View
|
||||
iconClass = 'submodule-icon'
|
||||
else
|
||||
@subscribe git, 'status-changed', (path, status) =>
|
||||
@updateStatus() if path.substring("#{@getPath()}/") is 0
|
||||
@updateStatus() if path.indexOf("#{@getPath()}/") is 0
|
||||
@subscribe git, 'statuses-changed', =>
|
||||
@updateStatus()
|
||||
@updateStatus()
|
||||
|
@ -312,11 +312,3 @@ module.exports =
|
||||
cson.readObjectAsync(path, done)
|
||||
else
|
||||
@readPlistAsync(path, done)
|
||||
|
||||
watchPath: (path, callback) ->
|
||||
path = @absolute(path)
|
||||
watchCallback = (eventType, eventPath) =>
|
||||
path = @absolute(eventPath) if eventType is 'move'
|
||||
callback(arguments...)
|
||||
id = $native.watchPath(path, watchCallback)
|
||||
unwatch: -> $native.unwatchPath(path, id)
|
||||
|
@ -34,6 +34,9 @@ $.fn.pageDown = ->
|
||||
$.fn.isOnDom = ->
|
||||
@closest(document.body).length is 1
|
||||
|
||||
$.fn.isVisible = ->
|
||||
@is(':visible')
|
||||
|
||||
$.fn.containsElement = (element) ->
|
||||
(element[0].compareDocumentPosition(this[0]) & 8) == 8
|
||||
|
||||
|
@ -11,7 +11,8 @@
|
||||
}
|
||||
|
||||
.tree-view .directory.selected > .header > .name,
|
||||
.tree-view .selected > .name {
|
||||
.tree-view .selected > .name,
|
||||
.tree-view .selected > .header > .disclosure-arrow {
|
||||
color: #d2d2d2;
|
||||
}
|
||||
|
||||
@ -39,7 +40,9 @@
|
||||
|
||||
.tree-view .entry:hover,
|
||||
.tree-view .directory .header:hover .name,
|
||||
.tree-view .directory .header:hover .disclosure-arrow {
|
||||
.tree-view .directory .header:hover .disclosure-arrow,
|
||||
.tree-view .selected > .directory > .header .disclosure-arrow,
|
||||
.tree-view .selected > .directory > .header:hover .disclosure-arrow {
|
||||
color: #ebebeb;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user