diff --git a/NvimView/NvimView/UiClient.h b/NvimView/NvimView/UiClient.h deleted file mode 100644 index a85f2c38..00000000 --- a/NvimView/NvimView/UiClient.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Tae Won Ha - http://taewon.de - @hataewon - * See LICENSE - */ - -@import Cocoa; - - -#import "NvimUiBridgeProtocol.h" - - -NS_ASSUME_NONNULL_BEGIN - -@interface UiClient : NSObject - -@property (nonatomic) bool useInteractiveZsh; -@property (nonatomic, copy) NSURL *cwd; -@property (nonatomic, nullable, retain) NSArray *nvimArgs; -@property (readonly) bool neoVimIsQuitting; -@property (nonatomic, weak) id bridge; - -@property (readonly) bool neoVimHasQuit; -@property (readonly) NSCondition *neoVimQuitCondition; - -- (instancetype)initWithUuid:(NSString *)uuid; - -- (void)debug; - -- (void)forceQuit; -- (void)quit; - -- (bool)runLocalServerAndNeoVimWithWidth:(NSInteger)width height:(NSInteger)height; - -- (void)vimInput:(NSString *)string; -- (void)vimInputMarkedText:(NSString *)markedText; -- (void)deleteCharacters:(NSInteger)count; - -- (void)resizeToWidth:(int)width height:(int)height; - -- (NSString * _Nullable)escapedFileName:(NSString *)fileName; -- (NSArray *)escapedFileNames:(NSArray *)fileNames; - -- (void)scrollHorizontal:(NSInteger)horiz vertical:(NSInteger)vert at:(Position)position; - -- (void)focusGained:(bool)gained; - -@end - -NS_ASSUME_NONNULL_END diff --git a/NvimView/NvimView/UiClient.m b/NvimView/NvimView/UiClient.m deleted file mode 100644 index 151e3f54..00000000 --- a/NvimView/NvimView/UiClient.m +++ /dev/null @@ -1,582 +0,0 @@ -/** - * Tae Won Ha - http://taewon.de - @hataewon - * See LICENSE - */ - -#import "UiClient.h" -#import "NeoVimMsgIds.h" -#import "Logger.h" - - -static const double qTimeout = 10; -static const double qForceExitDelay = 5; - -#define data_to_array(type) \ -static type *data_to_ ## type ## _array(NSData *data, NSUInteger count) { \ - NSUInteger length = count * sizeof( type ); \ - if (data.length != length) { \ - return NULL; \ - } \ - return ( type *) data.bytes; \ -} - - -data_to_array(NSInteger) -data_to_array(bool) - -static void log_cfmachport_error(SInt32 err, NeoVimAgentMsgId msgid, NSData *inputData) { - switch (err) { - case kCFMessagePortSendTimeout: - log4Warn("Got response kCFMessagePortSendTimeout = %d for the msg %ld with data %@.", - err, (long) msgid, inputData); - case kCFMessagePortReceiveTimeout: - log4Warn("Got response kCFMessagePortReceiveTimeout = %d for the msg %ld with data %@.", - err, (long) msgid, inputData); - case kCFMessagePortIsInvalid: - log4Warn("Got response kCFMessagePortIsInvalid = %d for the msg %ld with data %@.", - err, (long) msgid, inputData); - case kCFMessagePortTransportError: - log4Warn("Got response kCFMessagePortTransportError = %d for the msg %ld with data %@.", - err, (long) msgid, inputData); - case kCFMessagePortBecameInvalidError: - log4Warn("Got response kCFMessagePortBecameInvalidError = %d for the msg %ld with data %@.", - err, (long) msgid, inputData); - return; - - default: - return; - } -} - - -@interface UiClient () - -- (void)handleMessageWithId:(SInt32)msgid data:(NSData *)data; - -@end - - -static CFDataRef local_server_callback(CFMessagePortRef local __unused, SInt32 msgid, CFDataRef data, void *info) { - @autoreleasepool { - UiClient *agent = (__bridge UiClient *) info; - [agent handleMessageWithId:msgid data:(__bridge NSData *) (data)]; - } - - return NULL; -} - - -@implementation UiClient { - NSString *_uuid; - - CFMessagePortRef _remoteServerPort; - - CFMessagePortRef _localServerPort; - NSThread *_localServerThread; - CFRunLoopRef _localServerRunLoop; - - NSTask *_neoVimServerTask; - - bool _neoVimIsReady; - NSCondition *_neoVimReadyCondition; - bool _isInitErrorPresent; - - NSInteger _initialWidth; - NSInteger _initialHeight; - - volatile uint32_t _neoVimIsQuitting; -} - -- (instancetype)initWithUuid:(NSString *)uuid { - self = [super init]; - if (self == nil) { - return nil; - } - - _uuid = uuid; - _useInteractiveZsh = NO; - _neoVimIsReady = NO; - _neoVimReadyCondition = [NSCondition new]; - _isInitErrorPresent = NO; - - _initialWidth = 30; - _initialHeight = 15; - - _neoVimIsQuitting = 0; - - _neoVimHasQuit = false; - _neoVimQuitCondition = [NSCondition new]; - - return self; -} - -- (bool)neoVimIsQuitting { - return _neoVimIsQuitting == 1; -} - -- (void)debug { -#ifdef DEBUG - [self sendMessageWithId:NeoVimAgentDebug1 data:nil expectsReply:NO]; -#endif -} - -- (void)forceQuit { - log4Error("Force-quitting NvimServer %@", _uuid); - - OSAtomicOr32Barrier(1, &_neoVimIsQuitting); - - [self closeMachPorts]; - [self forceExitNeoVimServer]; - - [_neoVimQuitCondition lock]; - _neoVimHasQuit = true; - [_neoVimQuitCondition signal]; - [_neoVimQuitCondition unlock]; - - log4Error("Force-quit NvimServer %@", _uuid); - -} - -// We cannot use -dealloc for this since -dealloc is not called until the run loop in the thread stops. -- (void)quit { - OSAtomicOr32Barrier(1, &_neoVimIsQuitting); - - [self closeMachPorts]; - - [_neoVimServerTask waitUntilExit]; - - [_neoVimQuitCondition lock]; - _neoVimHasQuit = true; - [_neoVimQuitCondition signal]; - [_neoVimQuitCondition unlock]; - - log4Info("NvimServer %@ exited successfully", _uuid); -} - -- (void)closeMachPorts { - CFRunLoopStop(_localServerRunLoop); - [_localServerThread cancel]; - - if (CFMessagePortIsValid(_remoteServerPort)) { - CFMessagePortInvalidate(_remoteServerPort); - } - CFRelease(_remoteServerPort); - _remoteServerPort = NULL; - - if (CFMessagePortIsValid(_localServerPort)) { - CFMessagePortInvalidate(_localServerPort); - } - CFRelease(_localServerPort); - _localServerPort = NULL; -} - --(void)forceExitNeoVimServer { - log4Warn("Forcing backend neovim process to terminate after %lf seconds.", qForceExitDelay); - [_neoVimServerTask interrupt]; - [_neoVimServerTask terminate]; -} - -- (void)launchNeoVimUsingLoginShell { - __auto_type *selfEnv = [NSProcessInfo processInfo].environment; - - NSString *shellPath = selfEnv[@"SHELL"]; - if (shellPath == nil) { - shellPath = @"/bin/bash"; - } - - NSString *shellName = shellPath.lastPathComponent; - NSMutableArray *shellArgs = [NSMutableArray new]; - if (![shellName isEqualToString:@"tcsh"]) { - // tcsh does not like the -l option - [shellArgs addObject:@"-l"]; - } - if (_useInteractiveZsh && [shellName isEqualToString:@"zsh"]) { - [shellArgs addObject:@"-i"]; - } - - NSPipe *inputPipe = [NSPipe pipe]; - _neoVimServerTask = [[NSTask alloc] init]; - -#ifndef DEBUG - NSFileHandle *nullFileHandle = [NSFileHandle fileHandleWithNullDevice]; - _neoVimServerTask.standardOutput = nullFileHandle; - _neoVimServerTask.standardError = nullFileHandle; -#endif - - __auto_type listenAddress = [NSTemporaryDirectory() stringByAppendingPathComponent: - [NSString stringWithFormat:@"vimr_%@.sock", _uuid]]; - - __auto_type env = [NSMutableDictionary dictionaryWithDictionary:selfEnv]; - env[@"NVIM_LISTEN_ADDRESS"] = listenAddress; - - _neoVimServerTask.environment = env; - _neoVimServerTask.standardInput = inputPipe; - _neoVimServerTask.currentDirectoryPath = self.cwd == nil ? NSHomeDirectory() : self.cwd.path; - _neoVimServerTask.launchPath = shellPath; - _neoVimServerTask.arguments = shellArgs; - [_neoVimServerTask launch]; - - __auto_type *cmd = [NSString stringWithFormat:@"exec \"%@\" '%@' '%@'", - [self neoVimServerExecutablePath], - [self localServerName], - [self remoteServerName]]; - if (self.nvimArgs != nil) { - NSMutableArray *args = [NSMutableArray new]; - for (NSString *arg in self.nvimArgs) { - [args addObject:[NSString stringWithFormat:@"'%@'", arg]]; - } - cmd = [cmd stringByAppendingFormat:@" %@", [args componentsJoinedByString:@" "]]; - } - - cmd = [cmd stringByAppendingString:@" --headless"]; - - NSFileHandle *writeHandle = inputPipe.fileHandleForWriting; - [writeHandle writeData:[cmd dataUsingEncoding:NSUTF8StringEncoding]]; - [writeHandle closeFile]; -} - -- (bool)runLocalServerAndNeoVimWithWidth:(NSInteger)width height:(NSInteger)height { - _initialWidth = width; - _initialHeight = height; - - _localServerThread = [[NSThread alloc] initWithTarget:self selector:@selector(runLocalServer) object:nil]; - [_localServerThread start]; - - [self launchNeoVimUsingLoginShell]; - - // Wait until neovim is ready. - NSDate *deadline = [[NSDate date] dateByAddingTimeInterval:qTimeout]; - [_neoVimReadyCondition lock]; - while (!_neoVimIsReady && [_neoVimReadyCondition waitUntilDate:deadline]); - [_neoVimReadyCondition unlock]; - _neoVimReadyCondition = nil; - - return !_isInitErrorPresent; -} - -- (void)vimInput:(NSString *)string { - NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; - [self sendMessageWithId:NeoVimAgentMsgIdInput data:data expectsReply:NO]; -} - -- (void)vimInputMarkedText:(NSString *_Nonnull)markedText { - NSData *data = [markedText dataUsingEncoding:NSUTF8StringEncoding]; - [self sendMessageWithId:NeoVimAgentMsgIdInputMarked data:data expectsReply:NO]; -} - -- (void)deleteCharacters:(NSInteger)count { - NSData *data = [[NSData alloc] initWithBytes:&count length:sizeof(NSInteger)]; - [self sendMessageWithId:NeoVimAgentMsgIdDelete data:data expectsReply:NO]; -} - -- (void)resizeToWidth:(int)width height:(int)height { - int values[] = {width, height}; - NSData *data = [[NSData alloc] initWithBytes:values length:(2 * sizeof(int))]; - [self sendMessageWithId:NeoVimAgentMsgIdResize data:data expectsReply:NO]; -} - -- (NSString *)escapedFileName:(NSString *)fileName { - NSArray *fileNames = [self escapedFileNames:@[fileName]]; - if (fileNames.count == 0) { - return nil; - } - - return fileNames[0]; -} - -- (void)focusGained:(bool)gained { - bool values[] = {gained}; - NSData *data = [[NSData alloc] initWithBytes:values length:sizeof(bool)]; - [self sendMessageWithId:NeoVimAgentMsgIdFocusGained data:data expectsReply:NO]; -} - -- (void)scrollHorizontal:(NSInteger)horiz vertical:(NSInteger)vert at:(Position)position { - NSInteger values[] = {horiz, vert, position.row, position.column}; - NSData *data = [[NSData alloc] initWithBytes:values length:4 * sizeof(NSInteger)]; - [self sendMessageWithId:NeoVimAgentMsgIdScroll data:data expectsReply:NO]; -} - -- (NSArray *)escapedFileNames:(NSArray *)fileNames { - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:fileNames]; - NSData *response = [self sendMessageWithId:NeoVimAgentMsgIdGetEscapeFileNames data:data expectsReply:YES]; - if (response == nil) { - log4Warn("The response for the msg %ld was nil.", (long) NeoVimAgentMsgIdGetEscapeFileNames); - return @[]; - } - - return [NSKeyedUnarchiver unarchiveObjectWithData:response]; -} - -- (void)runLocalServer { - @autoreleasepool { - CFMessagePortContext localContext = { - .version = 0, - .info = (__bridge void *) self, - .retain = NULL, - .release = NULL, - .copyDescription = NULL - }; - - unsigned char shouldFreeLocalServer = false; - _localServerPort = CFMessagePortCreateLocal( - kCFAllocatorDefault, - (__bridge CFStringRef) [self localServerName], - local_server_callback, - &localContext, - &shouldFreeLocalServer - ); - - // FIXME: handle shouldFreeLocalServer = true - } - - _localServerRunLoop = CFRunLoopGetCurrent(); - CFRunLoopSourceRef runLoopSrc = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, _localServerPort, 0); - CFRunLoopAddSource(_localServerRunLoop, runLoopSrc, kCFRunLoopCommonModes); - CFRelease(runLoopSrc); - CFRunLoopRun(); -} - -- (void)establishNeoVimConnection { - _remoteServerPort = CFMessagePortCreateRemote( - kCFAllocatorDefault, - (__bridge CFStringRef) [self remoteServerName] - ); - - NSInteger values[] = { _initialWidth, _initialHeight }; - NSData *data = [NSData dataWithBytes:values length:2 * sizeof(NSInteger)]; - - [self sendMessageWithId:NeoVimAgentMsgIdAgentReady data:data expectsReply:NO]; -} - -- (NSData *)sendMessageWithId:(NeoVimAgentMsgId)msgid data:(NSData *)data expectsReply:(bool)expectsReply { - if (_neoVimIsQuitting == 1) { - // This happens often, e.g. when exiting full screen by closing all buffers. We try to resize the window after - // the message port has been closed. This is a quick-and-dirty fix. - // TODO: Fix for real... - log4Warn("Neovim is quitting, but trying to send message: %lu", (unsigned long) msgid); - return nil; - } - - if (_remoteServerPort == NULL) { - log4Warn("Remote server is null: The msg %lu with data %@ could not be sent.", (unsigned long) msgid, data); - return nil; - } - - CFDataRef responseData = NULL; - CFStringRef replyMode = expectsReply ? kCFRunLoopDefaultMode : NULL; - - SInt32 responseCode = CFMessagePortSendRequest( - _remoteServerPort, msgid, (__bridge CFDataRef) data, qTimeout, qTimeout, replyMode, &responseData - ); - - if (_neoVimIsQuitting == 1) { - return nil; - } - - if (responseCode != kCFMessagePortSuccess) { - log_cfmachport_error(responseCode, msgid, data); - - if (_neoVimIsQuitting == 0) { - [_bridge ipcBecameInvalid: - [NSString stringWithFormat: - @"Reason: sending msg to neovim failed for %lu with %d", (unsigned long) msgid, responseCode - ] - ]; - } - - return nil; - } - - if (responseData == NULL) { - return nil; - } - - NSData *result = (__bridge_transfer NSData *) responseData; - return result; -} - -- (NSString *)neoVimServerExecutablePath { - return [[[NSBundle bundleForClass:[self class]] builtInPlugInsPath] stringByAppendingPathComponent:@"NvimServer"]; -} - -- (NSString *)localServerName { - return [NSString stringWithFormat:@"com.qvacua.vimr.%@", _uuid]; -} - -- (NSString *)remoteServerName { - return [NSString stringWithFormat:@"com.qvacua.vimr.neovim-server.%@", _uuid]; -} - -- (void)handleMessageWithId:(SInt32)msgid data:(NSData *)data { - switch (msgid) { - - case NeoVimServerMsgIdServerReady: - [self establishNeoVimConnection]; - return; - - case NeoVimServerMsgIdNeoVimReady: { - bool *value = data_to_bool_array(data, 1); - _isInitErrorPresent = value[0]; - - [_neoVimReadyCondition lock]; - _neoVimIsReady = YES; - [_neoVimReadyCondition signal]; - [_neoVimReadyCondition unlock]; - - return; - } - - case NeoVimServerMsgIdResize: { - NSInteger *values = data_to_NSInteger_array(data, 2); - if (values == nil) { - return; - } - [_bridge resizeToWidth:values[0] height:values[1]]; - return; - } - - case NeoVimServerMsgIdClear: - [_bridge clear]; - return; - - case NeoVimServerMsgIdEolClear: - [_bridge eolClear]; - return; - - case NeoVimServerMsgIdSetMenu: - [_bridge updateMenu]; - return; - - case NeoVimServerMsgIdBusyStart: - [_bridge busyStart]; - return; - - case NeoVimServerMsgIdBusyStop: - [_bridge busyStop]; - return; - - case NeoVimServerMsgIdMouseOn: - [_bridge mouseOn]; - return; - - case NeoVimServerMsgIdMouseOff: - [_bridge mouseOff]; - return; - - case NeoVimServerMsgIdModeChange: { - NSInteger *values = data_to_NSInteger_array(data, 1); - [_bridge modeChange:(CursorModeShape) values[0]]; - return; - } - - case NeoVimServerMsgIdSetScrollRegion: { - NSInteger *values = data_to_NSInteger_array(data, 4); - [_bridge setScrollRegionToTop:values[0] bottom:values[1] left:values[2] right:values[3]]; - return; - } - - case NeoVimServerMsgIdScroll: { - NSInteger *values = data_to_NSInteger_array(data, 1); - [_bridge scroll:values[0]]; - return; - } - - case NeoVimServerMsgIdUnmark: { - NSInteger *values = data_to_NSInteger_array(data, 2); - [_bridge unmarkRow:values[0] column:values[1]]; - return; - } - - case NeoVimServerMsgIdBell: - [_bridge bell]; - return; - - case NeoVimServerMsgIdVisualBell: - [_bridge visualBell]; - return; - - case NeoVimServerMsgIdFlush: { - NSArray *renderData = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - - [_bridge flush:renderData]; - return; - } - - case NeoVimServerMsgIdSetForeground: { - NSInteger *values = data_to_NSInteger_array(data, 1); - [_bridge updateForeground:values[0]]; - return; - } - - case NeoVimServerMsgIdSetBackground: { - NSInteger *values = data_to_NSInteger_array(data, 1); - [_bridge updateBackground:values[0]]; - return; - } - - case NeoVimServerMsgIdSetSpecial: { - NSInteger *values = data_to_NSInteger_array(data, 1); - [_bridge updateSpecial:values[0]]; - return; - } - - case NeoVimServerMsgIdSetTitle: { - NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - [_bridge setTitle:string]; - return; - } - - case NeoVimServerMsgIdSetIcon: { - NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - [_bridge setIcon:string]; - return; - } - - case NeoVimServerMsgIdStop: - [_bridge stop]; - return; - - case NeoVimServerMsgIdDirtyStatusChanged: { - bool *values = data_to_bool_array(data, 1); - [_bridge setDirtyStatus:values[0]]; - return; - } - - case NeoVimServerMsgIdCwdChanged: { - if (data == nil) { - return; - } - - [_bridge cwdChanged:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; - return; - } - - case NeoVimServerMsgIdColorSchemeChanged: { - NSInteger *values = (NSInteger *) data.bytes; - NSMutableArray *array = [NSMutableArray new]; - for (int i = 0; i < 5; i++) { - [array addObject:@(values[i])]; - } - [_bridge colorSchemeChanged:array]; - return; - } - - case NeoVimServerMsgIdAutoCommandEvent: { - if (data.length == 2 * sizeof(NSInteger)) { - NSInteger *values = (NSInteger *) data.bytes; - NvimAutoCommandEvent event = (NvimAutoCommandEvent) values[0]; - NSInteger bufferHandle = (values + 1)[0]; - [_bridge autoCommandEvent:event bufferHandle:bufferHandle]; - } else { - NSInteger *values = data_to_NSInteger_array(data, 1); - [_bridge autoCommandEvent:(NvimAutoCommandEvent) values[0] bufferHandle:-1]; - } - return; - } - - default: - return; - } -} - -@end