2016-07-09 15:04:09 +03:00
|
|
|
/**
|
|
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
|
|
* See LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
#import "NeoVimServer.h"
|
|
|
|
#import "server_globals.h"
|
2016-07-10 15:43:26 +03:00
|
|
|
#import "Logging.h"
|
2016-08-26 16:31:30 +03:00
|
|
|
#import "CocoaCategories.h"
|
2017-01-05 21:35:44 +03:00
|
|
|
#import "Wrapper.h"
|
|
|
|
|
|
|
|
#define FileInfo CarbonFileInfo
|
|
|
|
#define Boolean CarbonBoolean
|
|
|
|
|
|
|
|
#import <nvim/vim.h>
|
|
|
|
#import <nvim/api/vim.h>
|
|
|
|
#import <nvim/main.h>
|
|
|
|
#import <nvim/ui.h>
|
2016-07-09 15:04:09 +03:00
|
|
|
|
|
|
|
|
2016-08-15 11:56:19 +03:00
|
|
|
// When #define'd you can execute the NeoVimServer binary and neovim will be started:
|
|
|
|
// $ ./NeoVimServer local remote
|
|
|
|
#undef DEBUG_NEOVIM_SERVER_STANDALONE
|
|
|
|
//#define DEBUG_NEOVIM_SERVER_STANDALONE
|
|
|
|
|
|
|
|
|
2016-12-07 22:53:12 +03:00
|
|
|
static const double qTimeout = 10;
|
2016-07-09 15:04:09 +03:00
|
|
|
|
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(NSInteger)
|
|
|
|
|
2016-11-10 22:42:42 +03:00
|
|
|
static inline NSUInteger response_id_from_data(NSData *data) {
|
2016-11-11 15:37:38 +03:00
|
|
|
NSUInteger *values = (NSUInteger *) data.bytes;
|
2016-11-10 22:42:42 +03:00
|
|
|
return values[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline NSData *data_without_response_id(NSData *data) {
|
2016-11-11 15:37:38 +03:00
|
|
|
NSUInteger *values = (NSUInteger *) data.bytes;
|
2016-11-10 22:42:42 +03:00
|
|
|
NSUInteger length = data.length - sizeof(NSUInteger);
|
|
|
|
return length == 0 ? NSData.new : [NSData dataWithBytes:(values + 1) length:length];
|
|
|
|
}
|
|
|
|
|
2016-07-09 23:52:59 +03:00
|
|
|
@interface NeoVimServer ()
|
|
|
|
|
2017-01-05 21:35:44 +03:00
|
|
|
- (NSCondition *)outputCondition;
|
2016-07-18 21:16:56 +03:00
|
|
|
- (NSData *)handleMessageWithId:(SInt32)msgid data:(NSData *)data;
|
2016-07-09 23:52:59 +03:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2017-01-05 21:35:44 +03:00
|
|
|
static CFDataRef dataByWaiting(NSCondition *condition, CFDataRef data, argv_callback cb) {
|
|
|
|
Wrapper *wrapper = [[Wrapper alloc] init];
|
|
|
|
NSDate *deadline = [[NSDate date] dateByAddingTimeInterval:qTimeout];
|
|
|
|
|
|
|
|
[condition lock];
|
|
|
|
|
|
|
|
loop_schedule(&main_loop, event_create(1, cb, 3, data, condition, wrapper));
|
|
|
|
|
|
|
|
while (wrapper.data == nil && [condition waitUntilDate:deadline]);
|
|
|
|
[condition unlock];
|
|
|
|
|
|
|
|
return (__bridge CFDataRef) wrapper.data;
|
|
|
|
}
|
|
|
|
|
2016-07-09 15:04:09 +03:00
|
|
|
static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
|
|
|
|
@autoreleasepool {
|
2016-07-10 15:24:45 +03:00
|
|
|
NeoVimServer *neoVimServer = (__bridge NeoVimServer *) info;
|
2017-01-05 21:35:44 +03:00
|
|
|
CFRetain(data); // release in the loop callbacks!
|
|
|
|
|
|
|
|
switch (msgid) {
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdSelectWindow: {
|
|
|
|
loop_schedule(&main_loop, event_create(1, neovim_select_window, 1, data));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdGetTabs: {
|
|
|
|
return dataByWaiting(neoVimServer.outputCondition, data, neovim_tabs);
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
NSData *responseData = [neoVimServer handleMessageWithId:msgid data:(__bridge NSData *) data];
|
|
|
|
|
|
|
|
if (responseData == nil) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2016-07-10 15:24:45 +03:00
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
return CFDataCreateCopy(kCFAllocatorDefault, (__bridge CFDataRef) responseData);
|
|
|
|
}
|
2016-07-09 15:04:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@implementation NeoVimServer {
|
|
|
|
NSString *_localServerName;
|
|
|
|
NSString *_remoteServerName;
|
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
CFMessagePortRef _remoteServerPort;
|
|
|
|
|
2016-07-09 15:04:09 +03:00
|
|
|
NSThread *_localServerThread;
|
|
|
|
CFMessagePortRef _localServerPort;
|
2016-07-18 21:16:56 +03:00
|
|
|
CFRunLoopRef _localServerRunLoop;
|
2017-01-05 21:35:44 +03:00
|
|
|
|
|
|
|
NSCondition *_outputCondition;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSCondition *)outputCondition {
|
|
|
|
return _outputCondition;
|
2016-07-09 15:04:09 +03:00
|
|
|
}
|
|
|
|
|
2016-07-10 15:43:26 +03:00
|
|
|
- (instancetype)initWithLocalServerName:(NSString *)localServerName remoteServerName:(NSString *)remoteServerName {
|
2016-07-09 15:04:09 +03:00
|
|
|
self = [super init];
|
|
|
|
if (self == nil) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2017-01-05 21:35:44 +03:00
|
|
|
_outputCondition = [[NSCondition alloc] init];
|
|
|
|
|
2016-07-10 15:24:45 +03:00
|
|
|
_localServerName = localServerName;
|
|
|
|
_remoteServerName = remoteServerName;
|
2016-07-09 15:04:09 +03:00
|
|
|
|
|
|
|
_localServerThread = [[NSThread alloc] initWithTarget:self selector:@selector(runLocalServer) object:nil];
|
2016-08-26 18:27:42 +03:00
|
|
|
_localServerThread.name = localServerName;
|
2016-07-09 15:04:09 +03:00
|
|
|
[_localServerThread start];
|
|
|
|
|
2016-08-15 11:56:19 +03:00
|
|
|
#ifndef DEBUG_NEOVIM_SERVER_STANDALONE
|
2016-07-10 15:24:45 +03:00
|
|
|
_remoteServerPort = CFMessagePortCreateRemote(kCFAllocatorDefault, (__bridge CFStringRef) _remoteServerName);
|
2016-08-15 11:56:19 +03:00
|
|
|
#endif
|
2016-07-09 15:04:09 +03:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
2016-07-18 21:16:56 +03:00
|
|
|
if (CFMessagePortIsValid(_remoteServerPort)) {
|
|
|
|
CFMessagePortInvalidate(_remoteServerPort);
|
|
|
|
}
|
|
|
|
CFRelease(_remoteServerPort);
|
|
|
|
|
|
|
|
if (CFMessagePortIsValid(_localServerPort)) {
|
|
|
|
CFMessagePortInvalidate(_localServerPort);
|
|
|
|
}
|
2016-07-09 15:04:09 +03:00
|
|
|
CFRelease(_localServerPort);
|
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
CFRunLoopStop(_localServerRunLoop);
|
2016-07-09 15:04:09 +03:00
|
|
|
[_localServerThread cancel];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)runLocalServer {
|
|
|
|
@autoreleasepool {
|
|
|
|
unsigned char shouldFree = false;
|
|
|
|
CFMessagePortContext localContext = {
|
|
|
|
.version = 0,
|
2016-07-10 15:24:45 +03:00
|
|
|
.info = (__bridge void *) self,
|
2016-07-09 15:04:09 +03:00
|
|
|
.retain = NULL,
|
|
|
|
.release = NULL,
|
|
|
|
.copyDescription = NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
_localServerPort = CFMessagePortCreateLocal(
|
|
|
|
kCFAllocatorDefault,
|
2016-07-10 15:24:45 +03:00
|
|
|
(__bridge CFStringRef) _localServerName,
|
2016-07-09 15:04:09 +03:00
|
|
|
local_server_callback,
|
|
|
|
&localContext,
|
|
|
|
&shouldFree
|
|
|
|
);
|
|
|
|
|
|
|
|
// FIXME: handle shouldFree == true
|
|
|
|
}
|
2016-07-10 15:10:09 +03:00
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
_localServerRunLoop = CFRunLoopGetCurrent();
|
2016-07-10 15:10:09 +03:00
|
|
|
CFRunLoopSourceRef runLoopSrc = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, _localServerPort, 0);
|
2016-07-18 21:16:56 +03:00
|
|
|
CFRunLoopAddSource(_localServerRunLoop, runLoopSrc, kCFRunLoopCommonModes);
|
2016-07-10 15:10:09 +03:00
|
|
|
CFRelease(runLoopSrc);
|
2016-08-15 11:56:19 +03:00
|
|
|
|
|
|
|
#ifdef DEBUG_NEOVIM_SERVER_STANDALONE
|
|
|
|
server_start_neovim();
|
|
|
|
#endif
|
|
|
|
|
2016-07-10 15:10:09 +03:00
|
|
|
CFRunLoopRun();
|
2016-07-09 15:04:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendMessageWithId:(NeoVimServerMsgId)msgid {
|
|
|
|
[self sendMessageWithId:msgid data:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendMessageWithId:(NeoVimServerMsgId)msgid data:(NSData *)data {
|
2016-08-15 11:56:19 +03:00
|
|
|
#ifdef DEBUG_NEOVIM_SERVER_STANDALONE
|
|
|
|
return;
|
|
|
|
#endif
|
2016-11-20 14:05:14 +03:00
|
|
|
|
2016-07-09 23:52:59 +03:00
|
|
|
if (_remoteServerPort == NULL) {
|
2016-08-26 16:31:30 +03:00
|
|
|
WLOG("Remote server is null: The msg (%lu:%s) could not be sent.", (unsigned long) msgid, data.cdesc);
|
2016-07-09 23:52:59 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-09 15:04:09 +03:00
|
|
|
SInt32 responseCode = CFMessagePortSendRequest(
|
2016-07-10 15:24:45 +03:00
|
|
|
_remoteServerPort, msgid, (__bridge CFDataRef) data, qTimeout, qTimeout, NULL, NULL
|
2016-07-09 15:04:09 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
if (responseCode == kCFMessagePortSuccess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-26 16:31:30 +03:00
|
|
|
WLOG("The msg (%lu:%s) could not be sent: %d", (unsigned long) msgid, data.cdesc, responseCode);
|
2016-07-09 15:04:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)notifyReadiness {
|
2016-08-15 11:56:19 +03:00
|
|
|
#ifndef DEBUG_NEOVIM_SERVER_STANDALONE
|
2016-07-09 15:04:09 +03:00
|
|
|
[self sendMessageWithId:NeoVimServerMsgIdServerReady data:nil];
|
2016-08-15 11:56:19 +03:00
|
|
|
#endif
|
2016-07-09 15:04:09 +03:00
|
|
|
}
|
|
|
|
|
2016-08-26 18:27:42 +03:00
|
|
|
- (void)quit {
|
|
|
|
server_quit();
|
|
|
|
}
|
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
- (NSData *)handleMessageWithId:(SInt32)msgid data:(NSData *)data {
|
2016-07-09 23:52:59 +03:00
|
|
|
switch (msgid) {
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdAgentReady:
|
2016-07-10 12:46:00 +03:00
|
|
|
server_start_neovim();
|
2016-07-18 21:16:56 +03:00
|
|
|
return nil;
|
2016-07-09 23:52:59 +03:00
|
|
|
|
2016-08-04 21:01:03 +03:00
|
|
|
case NeoVimAgentMsgIdCommand: {
|
|
|
|
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
|
|
server_vim_command(string);
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2016-09-02 19:22:40 +03:00
|
|
|
case NeoVimAgentMsgIdCommandOutput: {
|
2016-11-10 22:42:42 +03:00
|
|
|
NSUInteger responseId = response_id_from_data(data);
|
|
|
|
NSString *command = [[NSString alloc] initWithData:data_without_response_id(data) encoding:NSUTF8StringEncoding];
|
2016-09-02 19:22:40 +03:00
|
|
|
|
|
|
|
server_vim_command_output(responseId, command);
|
2016-11-10 22:42:42 +03:00
|
|
|
return nil;
|
|
|
|
}
|
2016-09-02 19:22:40 +03:00
|
|
|
|
2016-07-09 23:52:59 +03:00
|
|
|
case NeoVimAgentMsgIdInput: {
|
|
|
|
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
2016-07-10 12:46:00 +03:00
|
|
|
server_vim_input(string);
|
2016-07-09 23:52:59 +03:00
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
return nil;
|
2016-07-09 23:52:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdInputMarked: {
|
|
|
|
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
2016-07-10 12:46:00 +03:00
|
|
|
server_vim_input_marked_text(string);
|
2016-07-09 23:52:59 +03:00
|
|
|
|
2016-07-18 21:16:56 +03:00
|
|
|
return nil;
|
2016-07-09 23:52:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdDelete: {
|
|
|
|
NSInteger *values = data_to_NSInteger_array(data, 1);
|
2016-07-10 12:46:00 +03:00
|
|
|
server_delete(values[0]);
|
2016-07-18 21:16:56 +03:00
|
|
|
return nil;
|
2016-07-09 23:52:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdResize: {
|
|
|
|
int *values = data_to_int_array(data, 2);
|
2016-07-10 12:46:00 +03:00
|
|
|
server_resize(values[0], values[1]);
|
2016-07-18 21:16:56 +03:00
|
|
|
return nil;
|
2016-07-09 23:52:59 +03:00
|
|
|
}
|
|
|
|
|
2016-08-12 19:00:05 +03:00
|
|
|
case NeoVimAgentMsgIdQuit:
|
2016-08-26 18:27:42 +03:00
|
|
|
// exit() after returning the response such that the agent can get the response and so does not log a warning.
|
|
|
|
[self performSelector:@selector(quit) onThread:_localServerThread withObject:nil waitUntilDone:NO];
|
2016-08-12 19:00:05 +03:00
|
|
|
return nil;
|
|
|
|
|
2016-08-25 00:06:39 +03:00
|
|
|
case NeoVimAgentMsgIdGetDirtyDocs: {
|
2016-07-18 21:16:56 +03:00
|
|
|
bool dirty = server_has_dirty_docs();
|
|
|
|
return [NSData dataWithBytes:&dirty length:sizeof(bool)];
|
|
|
|
}
|
2016-07-09 23:52:59 +03:00
|
|
|
|
2016-08-25 00:06:39 +03:00
|
|
|
case NeoVimAgentMsgIdGetEscapeFileNames: {
|
2016-08-12 11:51:16 +03:00
|
|
|
NSArray <NSString *> *fileNames = [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
|
|
|
NSMutableArray <NSString *> *result = [NSMutableArray new];
|
|
|
|
|
|
|
|
[fileNames enumerateObjectsUsingBlock:^(NSString* fileName, NSUInteger idx, BOOL *stop) {
|
|
|
|
[result addObject:server_escaped_filename(fileName)];
|
|
|
|
}];
|
|
|
|
|
|
|
|
return [NSKeyedArchiver archivedDataWithRootObject:result];
|
|
|
|
}
|
|
|
|
|
2016-08-12 15:32:42 +03:00
|
|
|
case NeoVimAgentMsgIdGetBuffers: {
|
2017-01-04 19:38:18 +03:00
|
|
|
return [NSKeyedArchiver archivedDataWithRootObject:server_buffers()];
|
2016-08-12 15:32:42 +03:00
|
|
|
}
|
|
|
|
|
2016-11-10 19:33:14 +03:00
|
|
|
case NeoVimAgentMsgIdGetBoolOption: {
|
2016-11-10 22:42:42 +03:00
|
|
|
NSUInteger responseId = response_id_from_data(data);
|
|
|
|
NSString *optionName = [[NSString alloc] initWithData:data_without_response_id(data)
|
|
|
|
encoding:NSUTF8StringEncoding];
|
|
|
|
|
|
|
|
server_get_bool_option(responseId, optionName);
|
|
|
|
return nil;
|
2016-11-10 19:33:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case NeoVimAgentMsgIdSetBoolOption: {
|
2016-11-10 22:42:42 +03:00
|
|
|
NSUInteger responseId = response_id_from_data(data);
|
|
|
|
|
|
|
|
NSData *paramData = data_without_response_id(data);
|
|
|
|
bool *optionValues = (bool *) paramData.bytes;
|
|
|
|
bool optionValue = optionValues[0];
|
|
|
|
|
|
|
|
const char *string = (const char *)(optionValues + 1);
|
2016-11-10 19:33:14 +03:00
|
|
|
NSString *optionName = [[NSString alloc] initWithCString:string encoding:NSUTF8StringEncoding];
|
|
|
|
|
2016-11-10 22:42:42 +03:00
|
|
|
server_set_bool_option(responseId, optionName, optionValue);
|
2016-11-10 19:33:14 +03:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2016-07-09 23:52:59 +03:00
|
|
|
default:
|
2016-07-18 21:16:56 +03:00
|
|
|
return nil;
|
2016-07-09 23:52:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-09 15:04:09 +03:00
|
|
|
@end
|