1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-01 01:32:04 +03:00
vimr/SwiftNeoVim/NeoVimAgent.m

440 lines
12 KiB
Mathematica
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
#import "NeoVimAgent.h"
2016-07-09 23:52:59 +03:00
#import "NeoVimMsgIds.h"
#import "NeoVimUiBridgeProtocol.h"
#import "Logger.h"
#import "NeoVimBuffer.h"
2016-07-10 15:24:45 +03:00
static const double qTimeout = 10;
2016-07-09 23:52:59 +03:00
#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(int)
data_to_array(bool)
2016-07-09 23:52:59 +03:00
data_to_array(CellAttributes)
@interface NeoVimAgent ()
2016-07-09 23:52:59 +03:00
- (void)handleMessageWithId:(SInt32)msgid data:(NSData *)data;
@end
static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
@autoreleasepool {
NeoVimAgent *agent = (__bridge NeoVimAgent *) info;
2016-07-09 23:52:59 +03:00
[agent handleMessageWithId:msgid data:(__bridge NSData *) (data)];
}
2016-07-10 15:24:45 +03:00
return NULL;
}
@implementation NeoVimAgent {
NSString *_uuid;
CFMessagePortRef _remoteServerPort;
CFMessagePortRef _localServerPort;
NSThread *_localServerThread;
CFRunLoopRef _localServerRunLoop;
NSTask *_neoVimServerTask;
bool _neoVimIsReady;
bool _isInitErrorPresent;
}
- (instancetype)initWithUuid:(NSString *)uuid {
self = [super init];
if (self == nil) {
return nil;
}
_uuid = uuid;
_neoVimIsReady = NO;
_isInitErrorPresent = NO;
2016-07-09 23:52:59 +03:00
return self;
}
// We cannot use -dealloc for this since -dealloc is not called until the run loop in the thread stops.
- (void)quit {
// Wait till we get the response from the server.
// If we don't wait here, then the NSTask.terminate msg below could get caught by neovim which causes a warning log.
[self sendMessageWithId:NeoVimAgentMsgIdQuit data:nil expectsReply:YES];
if (CFMessagePortIsValid(_remoteServerPort)) {
CFMessagePortInvalidate(_remoteServerPort);
}
2016-07-09 23:52:59 +03:00
CFRelease(_remoteServerPort);
if (CFMessagePortIsValid(_localServerPort)) {
CFMessagePortInvalidate(_localServerPort);
}
2016-07-09 23:52:59 +03:00
CFRelease(_localServerPort);
CFRunLoopStop(_localServerRunLoop);
2016-07-09 23:52:59 +03:00
[_localServerThread cancel];
// Just to be sure...
[_neoVimServerTask interrupt];
[_neoVimServerTask terminate];
}
- (void)launchNeoVimUsingLoginShell {
NSString *shellPath = [NSProcessInfo processInfo].environment[@"SHELL"];
if (shellPath == nil) {
shellPath = @"/bin/bash";
}
2016-08-18 18:40:30 +03:00
NSMutableArray *shellArgs = [NSMutableArray new];
if (![shellPath.lastPathComponent isEqualToString:@"tcsh"]) {
[shellArgs addObject:@"-l"];
}
[shellArgs addObjectsFromArray:@[
@"-c",
[NSString stringWithFormat:@"eval \"%@ %@ %@\"",
[self neoVimServerExecutablePath],
[self localServerName],
[self remoteServerName]]
]];
2016-08-18 18:40:30 +03:00
_neoVimServerTask = [[NSTask alloc] init];
_neoVimServerTask.currentDirectoryPath = NSHomeDirectory();
_neoVimServerTask.launchPath = shellPath;
_neoVimServerTask.arguments = shellArgs;
[_neoVimServerTask launch];
}
- (bool)runLocalServerAndNeoVimWithPath:(NSString *)path {
_localServerThread = [[NSThread alloc] initWithTarget:self selector:@selector(runLocalServer) object:nil];
[_localServerThread start];
[self launchNeoVimUsingLoginShell];
// Wait until neovim is ready (max. 10s).
NSDate *deadline = [[NSDate date] dateByAddingTimeInterval:qTimeout];
while (!_neoVimIsReady
&& [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:deadline]);
return !_isInitErrorPresent;
2016-07-09 23:52:59 +03:00
}
- (void)vimCommand:(NSString *)string {
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
[self sendMessageWithId:NeoVimAgentMsgIdCommand data:data expectsReply:NO];
}
2016-07-09 23:52:59 +03:00
- (void)vimInput:(NSString *)string {
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
[self sendMessageWithId:NeoVimAgentMsgIdInput data:data expectsReply:NO];
}
2016-07-09 23:52:59 +03:00
- (void)vimInputMarkedText:(NSString *_Nonnull)markedText {
NSData *data = [markedText dataUsingEncoding:NSUTF8StringEncoding];
[self sendMessageWithId:NeoVimAgentMsgIdInputMarked data:data expectsReply:NO];
}
2016-07-09 23:52:59 +03:00
- (void)deleteCharacters:(NSInteger)count {
NSData *data = [[NSData alloc] initWithBytes:&count length:sizeof(NSInteger)];
[self sendMessageWithId:NeoVimAgentMsgIdDelete data:data expectsReply:NO];
2016-07-09 23:52:59 +03:00
}
2016-07-09 23:52:59 +03:00
- (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];
}
- (bool)hasDirtyDocs {
2016-08-25 00:06:39 +03:00
NSData *response = [self sendMessageWithId:NeoVimAgentMsgIdGetDirtyDocs data:nil expectsReply:YES];
2016-08-13 12:20:03 +03:00
if (response == nil) {
2016-08-25 00:06:39 +03:00
log4Warn("The response for the msg %lu was nil.", NeoVimAgentMsgIdGetDirtyDocs);
2016-08-13 12:20:03 +03:00
return YES;
}
2016-08-13 12:20:03 +03:00
bool *values = data_to_bool_array(response, 1);
return values[0];
}
2016-08-25 00:06:39 +03:00
- (NSString *)escapedFileName:(NSString *)fileName {
return [self escapedFileNames:@[ fileName ]][0];
}
- (NSArray <NSString *>*)escapedFileNames:(NSArray <NSString *>*)fileNames {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:fileNames];
2016-08-25 00:06:39 +03:00
NSData *response = [self sendMessageWithId:NeoVimAgentMsgIdGetEscapeFileNames data:data expectsReply:YES];
2016-08-13 12:20:03 +03:00
if (response == nil) {
2016-08-25 00:06:39 +03:00
log4Warn("The response for the msg %lu was nil.", NeoVimAgentMsgIdGetEscapeFileNames);
2016-08-13 12:20:03 +03:00
return @[];
}
2016-08-13 12:20:03 +03:00
return [NSKeyedUnarchiver unarchiveObjectWithData:response];
}
2016-08-12 15:32:42 +03:00
- (NSArray <NeoVimBuffer *> *)buffers {
NSData *response = [self sendMessageWithId:NeoVimAgentMsgIdGetBuffers data:nil expectsReply:YES];
2016-08-13 12:20:03 +03:00
if (response == nil) {
log4Warn("The response for the msg %lu was nil.", NeoVimAgentMsgIdGetBuffers);
return @[];
}
return [NSKeyedUnarchiver unarchiveObjectWithData:response];
2016-08-12 15:32:42 +03:00
}
- (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
}
2016-07-10 15:10:09 +03:00
_localServerRunLoop = CFRunLoopGetCurrent();
2016-07-10 15:10:09 +03:00
CFRunLoopSourceRef runLoopSrc = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, _localServerPort, 0);
CFRunLoopAddSource(_localServerRunLoop, runLoopSrc, kCFRunLoopCommonModes);
2016-07-10 15:10:09 +03:00
CFRelease(runLoopSrc);
CFRunLoopRun();
}
2016-07-09 23:52:59 +03:00
- (void)establishNeoVimConnection {
_remoteServerPort = CFMessagePortCreateRemote(
kCFAllocatorDefault,
(__bridge CFStringRef) [self remoteServerName]
);
2016-08-13 12:20:03 +03:00
[self sendMessageWithId:NeoVimAgentMsgIdAgentReady data:nil expectsReply:NO];
2016-07-09 23:52:59 +03:00
}
- (NSData *)sendMessageWithId:(NeoVimAgentMsgId)msgid data:(NSData *)data expectsReply:(bool)expectsReply {
2016-07-09 23:52:59 +03:00
if (_remoteServerPort == NULL) {
2016-08-13 12:20:03 +03:00
log4Warn("Remote server is null: The msg %lu with data %@ could not be sent.", (unsigned long) msgid, data);
return nil;
}
CFDataRef responseData = NULL;
2016-08-12 16:16:43 +03:00
CFStringRef replyMode = expectsReply ? kCFRunLoopDefaultMode : NULL;
2016-07-09 23:52:59 +03:00
SInt32 responseCode = CFMessagePortSendRequest(
_remoteServerPort, msgid, (__bridge CFDataRef) data, qTimeout, qTimeout, replyMode, &responseData
2016-07-09 23:52:59 +03:00
);
if (responseCode != kCFMessagePortSuccess) {
2016-08-13 12:20:03 +03:00
log4Warn("Got response %d for the msg %lu with data %@.", responseCode, (unsigned long) msgid, data);
return nil;
}
if (responseData == NULL) {
return nil;
2016-07-09 23:52:59 +03:00
}
return [NSData dataWithData:(__bridge NSData *) responseData];
2016-07-09 23:52:59 +03:00
}
- (NSString *)neoVimServerExecutablePath {
return [[[NSBundle bundleForClass:[self class]] builtInPlugInsPath] stringByAppendingPathComponent:@"NeoVimServer"];
}
- (NSString *)localServerName {
return [NSString stringWithFormat:@"com.qvacua.vimr.%@", _uuid];
2016-07-09 23:52:59 +03:00
}
- (NSString *)remoteServerName {
return [NSString stringWithFormat:@"com.qvacua.vimr.neovim-server.%@", _uuid];
2016-07-09 23:52:59 +03:00
}
- (void)handleMessageWithId:(SInt32)msgid data:(NSData *)data {
switch (msgid) {
case NeoVimServerMsgIdServerReady:
2016-07-09 23:52:59 +03:00
[self establishNeoVimConnection];
return;
case NeoVimServerMsgIdNeoVimReady: {
bool *value = data_to_bool_array(data, 1);
_isInitErrorPresent = value[0];
_neoVimIsReady = YES;
2016-07-09 23:52:59 +03:00
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdResize: {
int *values = data_to_int_array(data, 2);
if (values == nil) {
return;
}
[_bridge resizeToWidth:values[0] height:values[1]];
return;
}
case NeoVimServerMsgIdClear:
2016-07-09 23:52:59 +03:00
[_bridge clear];
return;
case NeoVimServerMsgIdEolClear:
2016-07-09 23:52:59 +03:00
[_bridge eolClear];
return;
case NeoVimServerMsgIdSetPosition: {
int *values = data_to_int_array(data, 4);
[_bridge gotoPosition:(Position) { .row = values[0], .column = values[1] }
screenCursor:(Position) { .row = values[2], .column = values[3] }];
return;
}
case NeoVimServerMsgIdSetMenu:
2016-07-09 23:52:59 +03:00
[_bridge updateMenu];
return;
case NeoVimServerMsgIdBusyStart:
2016-07-09 23:52:59 +03:00
[_bridge busyStart];
return;
case NeoVimServerMsgIdBusyStop:
2016-07-09 23:52:59 +03:00
[_bridge busyStop];
return;
case NeoVimServerMsgIdMouseOn:
2016-07-09 23:52:59 +03:00
[_bridge mouseOn];
return;
case NeoVimServerMsgIdMouseOff:
2016-07-09 23:52:59 +03:00
[_bridge mouseOff];
return;
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdModeChange: {
int *values = data_to_int_array(data, 1);
2016-08-03 22:30:41 +03:00
[_bridge modeChange:(Mode) values[0]];
2016-07-09 23:52:59 +03:00
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetScrollRegion: {
int *values = data_to_int_array(data, 4);
[_bridge setScrollRegionToTop:values[0] bottom:values[1] left:values[2] right:values[3]];
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdScroll: {
int *values = data_to_int_array(data, 1);
[_bridge scroll:values[0]];
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetHighlightAttributes: {
CellAttributes *values = data_to_CellAttributes_array(data, 1);
[_bridge highlightSet:values[0]];
return;
}
2016-08-15 21:30:44 +03:00
case NeoVimServerMsgIdPut:
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdPutMarked: {
2016-08-15 21:30:44 +03:00
int *values = (int *) data.bytes;
int row = values[0];
int column = values[1];
NSString *string = [[NSString alloc] initWithBytes:(values + 2)
length:data.length - 2 * sizeof(int)
encoding:NSUTF8StringEncoding];
if (msgid == NeoVimServerMsgIdPut) {
[_bridge put:string screenCursor:(Position) { .row=row, .column=column }];
} else {
[_bridge putMarkedText:string screenCursor:(Position) { .row=row, .column=column }];
}
2016-07-09 23:52:59 +03:00
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdUnmark: {
int *values = data_to_int_array(data, 2);
[_bridge unmarkRow:values[0] column:values[1]];
return;
}
case NeoVimServerMsgIdBell:
2016-07-09 23:52:59 +03:00
[_bridge bell];
return;
case NeoVimServerMsgIdVisualBell:
[_bridge visualBell];
return;
case NeoVimServerMsgIdFlush:
2016-07-09 23:52:59 +03:00
[_bridge flush];
return;
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetForeground: {
int *values = data_to_int_array(data, 2);
[_bridge updateForeground:values[0] dark:(bool) values[1]];
2016-07-09 23:52:59 +03:00
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetBackground: {
int *values = data_to_int_array(data, 2);
[_bridge updateBackground:values[0] dark:(bool) values[1]];
2016-07-09 23:52:59 +03:00
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetSpecial: {
int *values = data_to_int_array(data, 2);
[_bridge updateSpecial:values[0] dark:(bool) values[1]];
2016-07-09 23:52:59 +03:00
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetTitle: {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[_bridge setTitle:string];
return;
}
2016-07-09 23:52:59 +03:00
case NeoVimServerMsgIdSetIcon: {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[_bridge setIcon:string];
return;
}
case NeoVimServerMsgIdDirtyStatusChanged: {
bool *values = data_to_bool_array(data, 1);
[_bridge setDirtyStatus:values[0]];
return;
}
case NeoVimServerMsgIdStop:
2016-07-09 23:52:59 +03:00
[_bridge stop];
return;
default:
2016-07-09 23:52:59 +03:00
return;
}
}
@end