1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-01 18:39:52 +03:00
vimr/NeoVimServer/server_ui.m

1112 lines
32 KiB
Mathematica
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
2016-10-22 18:25:43 +03:00
@import Foundation;
#import "Logging.h"
#import "server_globals.h"
#import "NeoVimServer.h"
#import "NeoVimBuffer.h"
2016-10-22 23:20:22 +03:00
#import "NeoVimWindow.h"
#import "NeoVimTab.h"
#import "CocoaCategories.h"
2017-01-06 12:31:45 +03:00
#import "DataWrapper.h"
2017-06-06 23:39:41 +03:00
// FileInfo and Boolean are #defined by Carbon and NeoVim:
// Since we don't need the Carbon versions of them, we rename
// them.
#define FileInfo CarbonFileInfo
#define Boolean CarbonBoolean
#import <nvim/vim.h>
#import <nvim/api/vim.h>
#import <nvim/ui.h>
#import <nvim/ui_bridge.h>
#import <nvim/ex_getln.h>
#import <nvim/fileio.h>
#import <nvim/undo.h>
2017-05-30 23:13:52 +03:00
#import <nvim/mouse.h>
#import <nvim/screen.h>
2017-05-30 23:13:52 +03:00
#import <nvim/edit.h>
2017-06-25 18:35:51 +03:00
#import <nvim/syntax.h>
2017-01-07 15:55:53 +03:00
#import <nvim/api/window.h>
#define pun_type(t, x) (*((t *) (&(x))))
// From NeoVimUiBridgeProtocol.h
typedef NS_ENUM(NSUInteger, FontTrait) {
FontTraitNone = 0,
FontTraitItalic = (1 << 0),
FontTraitBold = (1 << 1),
FontTraitUnderline = (1 << 2),
FontTraitUndercurl = (1 << 3)
};
typedef struct {
FontTrait fontTrait;
2017-05-27 18:48:07 +03:00
NSInteger foreground;
NSInteger background;
NSInteger special;
} CellAttributes;
static NSInteger _default_foreground = 0xFF000000;
static NSInteger _default_background = 0xFFFFFFFF;
static NSInteger _default_special = 0xFFFF0000;
// ---
typedef struct {
UIBridgeData *bridge;
Loop *loop;
bool stop;
} ServerUiData;
// We declare nvim_main because it's not declared in any header files of neovim
extern int nvim_main(int argc, char **argv);
// The thread in which neovim's main runs
static uv_thread_t _nvim_thread;
2017-06-06 23:39:41 +03:00
// Condition variable used by the XPC's init to wait till our custom UI initialization
// is finished inside neovim
static bool _is_ui_launched = false;
static uv_mutex_t _mutex;
static uv_cond_t _condition;
2016-07-10 12:46:00 +03:00
static ServerUiData *_server_ui_data;
static NSString *_marked_text = nil;
static NSInteger _marked_row = 0;
static NSInteger _marked_column = 0;
2017-06-06 23:39:41 +03:00
// for -> hanja popup, Cocoa first inserts , then sets marked text,
// cf docs/notes-on-cocoa-text-input.md
static NSInteger _marked_delta = 0;
static NSInteger _put_row = -1;
static NSInteger _put_column = -1;
static NSString *_backspace = nil;
2017-05-06 10:39:42 +03:00
static bool _dirty = false;
static NSInteger _initialWidth = 30;
static NSInteger _initialHeight = 15;
2017-01-06 12:16:55 +03:00
#pragma mark Helper functions
2016-11-10 19:33:14 +03:00
static inline String vim_string_from(NSString *str) {
return (String) { .data = (char *) str.cstr, .size = str.clength };
}
static void refresh_ui_screen(int type) {
update_screen(type);
setcursor();
ui_flush();
}
2017-01-06 12:16:55 +03:00
static bool has_dirty_docs() {
FOR_ALL_BUFFERS(buffer) {
if (buffer->b_p_bl == 0) {
continue;
}
if (bufIsChanged(buffer)) {
return true;
}
2017-01-06 12:16:55 +03:00
}
return false;
}
2017-05-06 10:39:42 +03:00
static void send_dirty_status() {
bool new_dirty_status = has_dirty_docs();
DLOG("dirty status: %d vs. %d", _dirty, new_dirty_status);
if (_dirty == new_dirty_status) {
return;
}
_dirty = new_dirty_status;
DLOG("sending dirty status: %d", _dirty);
NSData *data = [[NSData alloc] initWithBytes:&_dirty length:sizeof(bool)];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdDirtyStatusChanged data:data];
[data release];
}
static void send_cwd() {
char_u *temp = xmalloc(MAXPATHL);
if (os_dirname(temp, MAXPATHL) == FAIL) {
xfree(temp);
[_neovim_server sendMessageWithId:NeoVimServerMsgIdCwdChanged];
}
NSString *pwd = [NSString stringWithCString:(const char *) temp encoding:NSUTF8StringEncoding];
xfree(temp);
NSData *resultData = [pwd dataUsingEncoding:NSUTF8StringEncoding];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdCwdChanged data:resultData];
}
2017-06-25 18:35:51 +03:00
static HlAttrs HlAttrsFromAttrCode(int attr_code) {
HlAttrs rgb_attrs = { false, false, false, false, false, -1, -1, -1 };
attrentry_T *aep = syn_cterm_attr2entry(attr_code);
rgb_attrs.foreground = aep->rgb_fg_color;
rgb_attrs.background = aep->rgb_bg_color;
rgb_attrs.special = aep->rgb_sp_color;
rgb_attrs.reverse = (bool) (aep->rgb_ae_attr & HL_INVERSE);
2017-06-25 18:35:51 +03:00
return rgb_attrs;
}
static int foreground_for(HlAttrs attrs) {
return attrs.reverse ? attrs.background: attrs.foreground;
}
static int background_for(HlAttrs attrs) {
return attrs.reverse ? attrs.foreground: attrs.background;
}
2017-06-25 18:35:51 +03:00
static void send_colorscheme() {
// It seems that the highlight groupt only gets updated when the screen is redrawn.
// Since there's a guard var, probably it's safe to call it here...
if (need_highlight_changed) {
highlight_changed();
}
2017-06-25 18:35:51 +03:00
HlAttrs visualAttrs = HlAttrsFromAttrCode(highlight_attr[HLF_V]);
HlAttrs dirAttrs = HlAttrsFromAttrCode(highlight_attr[HLF_D]);
2017-06-25 18:35:51 +03:00
2017-06-26 00:17:00 +03:00
NSInteger values[] = {
normal_fg, normal_bg,
foreground_for(visualAttrs), background_for(visualAttrs),
foreground_for(dirAttrs),
2017-06-26 00:17:00 +03:00
};
NSData *resultData = [NSData dataWithBytes:values length:5 * sizeof(NSInteger)];
2017-06-25 18:35:51 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdColorSchemeChanged data:resultData];
}
2017-01-06 12:16:55 +03:00
static void insert_marked_text(NSString *markedText) {
_marked_text = [markedText retain]; // release when the final text is input in -vimInput
nvim_input(vim_string_from(markedText));
}
static void delete_marked_text() {
NSUInteger length = [_marked_text lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
[_marked_text release];
_marked_text = nil;
for (int i = 0; i < length; i++) {
nvim_input(vim_string_from(_backspace));
}
}
static void run_neovim(void *arg __unused) {
int argc = 1;
char **argv;
2017-01-06 12:16:55 +03:00
@autoreleasepool {
NSArray<NSString *> *nvimArgs = (NSArray *) arg;
argc = (int) nvimArgs.count + 1;
argv = (char **) malloc((argc + 1) * sizeof(char *));
argv[0] = "nvim";
for (int i = 0; i < nvimArgs.count; i++) {
argv[i + 1] = (char *) nvimArgs[(NSUInteger) i].cstr;
}
[nvimArgs release]; // retained in start_neovim()
}
nvim_main(argc, argv);
free(argv);
2016-11-10 19:33:14 +03:00
}
static void set_ui_size(UIBridgeData *bridge, int width, int height) {
bridge->ui->width = width;
bridge->ui->height = height;
bridge->bridge.width = width;
bridge->bridge.height = height;
}
2016-07-10 12:46:00 +03:00
static void server_ui_scheduler(Event event, void *d) {
UI *ui = d;
ServerUiData *data = ui->data;
loop_schedule(data->loop, event);
}
2016-07-10 12:46:00 +03:00
static void server_ui_main(UIBridgeData *bridge, UI *ui) {
Loop loop;
loop_init(&loop, NULL);
2016-07-10 12:46:00 +03:00
_server_ui_data = xcalloc(1, sizeof(ServerUiData));
ui->data = _server_ui_data;
_server_ui_data->bridge = bridge;
_server_ui_data->loop = &loop;
set_ui_size(bridge, (int) _initialWidth, (int) _initialHeight);
2016-07-10 12:46:00 +03:00
_server_ui_data->stop = false;
CONTINUE(bridge);
uv_mutex_lock(&_mutex);
_is_ui_launched = true;
uv_cond_signal(&_condition);
uv_mutex_unlock(&_mutex);
2016-07-10 12:46:00 +03:00
while (!_server_ui_data->stop) {
loop_poll_events(&loop, -1);
}
ui_bridge_stopped(bridge);
loop_close(&loop, false);
2016-07-10 12:46:00 +03:00
xfree(_server_ui_data);
xfree(ui);
}
2016-07-10 17:16:16 +03:00
#pragma mark NeoVim's UI callbacks
static void server_ui_resize(UI *ui __unused, Integer width, Integer height) {
NSInteger values[] = {width, height};
NSData *data = [[NSData alloc] initWithBytes:values length:(2 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdResize data:data];
[data release];
}
2016-07-10 12:46:00 +03:00
static void server_ui_clear(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdClear];
}
2016-07-10 12:46:00 +03:00
static void server_ui_eol_clear(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdEolClear];
}
static void server_ui_cursor_goto(UI *ui __unused, Integer row, Integer col) {
_put_row = row;
_put_column = col;
NSInteger values[] = {
row, col,
(NSInteger) curwin->w_cursor.lnum, curwin->w_cursor.col + 1
};
DLOG("%d:%d - %d:%d - %d:%d", values[0], values[1], values[2], values[3]);
NSData *data = [[NSData alloc] initWithBytes:values length:(4 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetPosition data:data];
[data release];
}
2016-07-10 12:46:00 +03:00
static void server_ui_update_menu(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetMenu];
}
2016-07-10 12:46:00 +03:00
static void server_ui_busy_start(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdBusyStart];
}
2016-07-10 12:46:00 +03:00
static void server_ui_busy_stop(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdBusyStop];
}
2016-07-10 12:46:00 +03:00
static void server_ui_mouse_on(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdMouseOn];
}
2016-07-10 12:46:00 +03:00
static void server_ui_mouse_off(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdMouseOff];
}
2017-06-06 23:39:41 +03:00
static void server_ui_mode_info_set(UI *ui __unused, Boolean enabled __unused,
Array cursor_styles __unused) {
2017-04-07 12:26:31 +03:00
// yet noop
}
static void server_ui_mode_change(UI *ui __unused, String mode_str __unused, Integer mode) {
NSInteger value = mode;
NSData *data = [[NSData alloc] initWithBytes:&value length:(1 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdModeChange data:data];
[data release];
}
2017-06-06 23:39:41 +03:00
static void server_ui_set_scroll_region(UI *ui __unused, Integer top, Integer bot,
Integer left, Integer right) {
NSInteger values[] = {top, bot, left, right};
NSData *data = [[NSData alloc] initWithBytes:values length:(4 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetScrollRegion data:data];
[data release];
}
static void server_ui_scroll(UI *ui __unused, Integer count) {
NSInteger value = count;
NSData *data = [[NSData alloc] initWithBytes:&value length:(1 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdScroll data:data];
[data release];
}
2016-07-10 12:46:00 +03:00
static void server_ui_highlight_set(UI *ui __unused, HlAttrs attrs) {
FontTrait trait = FontTraitNone;
if (attrs.italic) {
trait |= FontTraitItalic;
}
if (attrs.bold) {
trait |= FontTraitBold;
}
if (attrs.underline) {
trait |= FontTraitUnderline;
}
if (attrs.undercurl) {
trait |= FontTraitUndercurl;
}
CellAttributes cellAttrs;
cellAttrs.fontTrait = trait;
NSInteger fg = attrs.foreground == -1 ? _default_foreground : attrs.foreground;
NSInteger bg = attrs.background == -1 ? _default_background : attrs.background;
cellAttrs.foreground = attrs.reverse ? bg : fg;
cellAttrs.background = attrs.reverse ? fg : bg;
2017-06-06 23:39:41 +03:00
cellAttrs.special = attrs.special == -1 ? _default_special
: pun_type(unsigned int, attrs.special);
NSData *data = [[NSData alloc] initWithBytes:&cellAttrs length:sizeof(CellAttributes)];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetHighlightAttributes data:data];
[data release];
}
static void server_ui_put(UI *ui __unused, String str) {
NSString *string = [[NSString alloc] initWithBytes:str.data
length:str.size
encoding:NSUTF8StringEncoding];
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (_marked_text != nil && _marked_row == _put_row && _marked_column == _put_column) {
DLOG("putting marked text: '%s'", string.cstr);
[_neovim_server sendMessageWithId:NeoVimServerMsgIdPutMarked data:data];
} else if (_marked_text != nil
&& str.size == 0
&& _marked_row == _put_row
&& _marked_column == _put_column - 1) {
DLOG("putting marked text cuz zero");
[_neovim_server sendMessageWithId:NeoVimServerMsgIdPutMarked data:data];
} else {
DLOG("putting non-marked text: '%s'", string.cstr);
[_neovim_server sendMessageWithId:NeoVimServerMsgIdPut data:data];
}
_put_column += 1;
2016-08-15 21:30:44 +03:00
[string release];
}
2016-07-10 12:46:00 +03:00
static void server_ui_bell(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdBell];
}
2016-07-10 12:46:00 +03:00
static void server_ui_visual_bell(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdVisualBell];
}
2016-07-10 12:46:00 +03:00
static void server_ui_flush(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdFlush];
}
static void server_ui_update_fg(UI *ui __unused, Integer fg) {
NSInteger value[1];
if (fg == -1) {
value[0] = _default_foreground;
NSData *data = [[NSData alloc] initWithBytes:value length:(1 * sizeof(NSInteger))];
2016-07-09 23:52:59 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetForeground data:data];
[data release];
return;
}
_default_foreground = fg;
value[0] = fg;
NSData *data = [[NSData alloc] initWithBytes:value length:(1 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetForeground data:data];
[data release];
}
static void server_ui_update_bg(UI *ui __unused, Integer bg) {
NSInteger value[1];
if (bg == -1) {
value[0] = _default_background;
NSData *data = [[NSData alloc] initWithBytes:value length:(1 * sizeof(NSInteger))];
2016-07-09 23:52:59 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetBackground data:data];
[data release];
return;
}
_default_background = bg;
value[0] = bg;
NSData *data = [[NSData alloc] initWithBytes:value length:(1 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetBackground data:data];
[data release];
}
static void server_ui_update_sp(UI *ui __unused, Integer sp) {
NSInteger value[2];
if (sp == -1) {
value[0] = _default_special;
NSData *data = [[NSData alloc] initWithBytes:&value length:(1 * sizeof(NSInteger))];
2016-07-09 23:52:59 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetSpecial data:data];
[data release];
return;
}
_default_special = sp;
value[0] = sp;
NSData *data = [[NSData alloc] initWithBytes:&value length:(1 * sizeof(NSInteger))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetSpecial data:data];
[data release];
}
static void server_ui_set_title(UI *ui __unused, String title) {
if (title.size == 0) {
return;
}
NSString *string = [[NSString alloc] initWithCString:title.data encoding:NSUTF8StringEncoding];
2017-06-06 23:39:41 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetTitle
data:[string dataUsingEncoding:NSUTF8StringEncoding]];
[string release];
}
static void server_ui_set_icon(UI *ui __unused, String icon) {
if (icon.size == 0) {
return;
}
NSString *string = [[NSString alloc] initWithCString:icon.data encoding:NSUTF8StringEncoding];
2017-06-06 23:39:41 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetIcon
data:[string dataUsingEncoding:NSUTF8StringEncoding]];
[string release];
}
2016-07-10 12:46:00 +03:00
static void server_ui_stop(UI *ui __unused) {
[_neovim_server sendMessageWithId:NeoVimServerMsgIdStop];
ServerUiData *data = (ServerUiData *) ui->data;
data->stop = true;
2016-07-10 12:46:00 +03:00
}
#pragma mark Public
2017-01-06 12:16:55 +03:00
// called by neovim
2016-07-10 12:46:00 +03:00
void custom_ui_start(void) {
UI *ui = xcalloc(1, sizeof(UI));
ui->rgb = true;
ui->stop = server_ui_stop;
ui->resize = server_ui_resize;
ui->clear = server_ui_clear;
ui->eol_clear = server_ui_eol_clear;
ui->cursor_goto = server_ui_cursor_goto;
ui->update_menu = server_ui_update_menu;
ui->busy_start = server_ui_busy_start;
ui->busy_stop = server_ui_busy_stop;
ui->mouse_on = server_ui_mouse_on;
ui->mouse_off = server_ui_mouse_off;
2017-04-23 12:33:06 +03:00
ui->mode_info_set = server_ui_mode_info_set;
2016-07-10 12:46:00 +03:00
ui->mode_change = server_ui_mode_change;
ui->set_scroll_region = server_ui_set_scroll_region;
ui->scroll = server_ui_scroll;
ui->highlight_set = server_ui_highlight_set;
ui->put = server_ui_put;
ui->bell = server_ui_bell;
ui->visual_bell = server_ui_visual_bell;
ui->update_fg = server_ui_update_fg;
ui->update_bg = server_ui_update_bg;
ui->update_sp = server_ui_update_sp;
ui->flush = server_ui_flush;
2016-09-09 21:26:50 +03:00
ui->suspend = NULL;
2016-07-10 12:46:00 +03:00
ui->set_title = server_ui_set_title;
ui->set_icon = server_ui_set_icon;
ui_bridge_attach(ui, server_ui_main, server_ui_scheduler);
}
void custom_ui_autocmds_groups(
event_T event,
char_u *fname __unused,
char_u *fname_io __unused,
int group __unused,
bool force __unused,
buf_T *buf,
exarg_T *eap __unused
) {
2017-06-06 23:39:41 +03:00
// We don't need these events in the UI (yet) and they slow down scrolling: Enable them,
// if necessary, only after optimizing the scrolling.
if (event == EVENT_CURSORMOVED || event == EVENT_CURSORMOVEDI) {
return;
}
2016-09-06 19:53:07 +03:00
@autoreleasepool {
DLOG("got event %d for file %s in group %d.", event, fname, group);
2016-11-22 00:14:45 +03:00
if (event == EVENT_DIRCHANGED) {
send_cwd();
return;
}
2017-06-25 18:35:51 +03:00
if (event == EVENT_COLORSCHEME) {
send_colorscheme();
return;
}
2017-05-06 10:39:42 +03:00
if (event == EVENT_TEXTCHANGED
|| event == EVENT_TEXTCHANGEDI
|| event == EVENT_BUFWRITEPOST
|| event == EVENT_BUFLEAVE)
{
send_dirty_status();
}
2016-12-19 23:31:16 +03:00
NSUInteger eventCode = event;
NSMutableData *data;
if (buf == NULL) {
data = [[NSMutableData alloc] initWithBytes:&eventCode length:sizeof(NSInteger)];
} else {
NSInteger bufHandle = buf->handle;
data = [[NSMutableData alloc] initWithCapacity:(sizeof(NSInteger) + sizeof(NSInteger))];
[data appendBytes:&eventCode length:sizeof(NSInteger)];
[data appendBytes:&bufHandle length:sizeof(NSInteger)];
}
[_neovim_server sendMessageWithId:NeoVimServerMsgIdAutoCommandEvent data:data];
[data release];
}
}
2017-01-06 12:16:55 +03:00
#pragma mark Other help functions
void start_neovim(NSInteger width, NSInteger height, NSArray<NSString *> *args) {
_initialWidth = width;
_initialHeight = height;
// set $VIMRUNTIME to ${RESOURCE_PATH_OF_XPC_BUNDLE}/runtime
NSString *bundlePath = [NSBundle bundleForClass:[NeoVimServer class]].bundlePath;
2017-06-06 23:39:41 +03:00
NSString *resourcesPath = [bundlePath.stringByDeletingLastPathComponent
stringByAppendingPathComponent:@"Resources"];
NSString *runtimePath = [resourcesPath stringByAppendingPathComponent:@"runtime"];
setenv("VIMRUNTIME", runtimePath.fileSystemRepresentation, true);
// Set $LANG to en_US.UTF-8 such that the copied text to the system clipboard is not garbled.
setenv("LANG", "en_US.UTF-8", true);
uv_mutex_init(&_mutex);
uv_cond_init(&_condition);
uv_thread_create(&_nvim_thread, run_neovim, [args retain]); // released in run_neovim()
DLOG("NeoVim started");
// continue only after our UI main code for neovim has been fully initialized
uv_mutex_lock(&_mutex);
while (!_is_ui_launched) {
uv_cond_wait(&_condition, &_mutex);
}
uv_mutex_unlock(&_mutex);
uv_cond_destroy(&_condition);
uv_mutex_destroy(&_mutex);
_backspace = [[NSString alloc] initWithString:@"<BS>"];
bool value = msg_didany > 0;
NSData *data = [[NSData alloc] initWithBytes:&value length:sizeof(bool)];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdNeoVimReady data:data];
[data release];
}
2017-01-06 12:16:55 +03:00
void quit_neovim() {
DLOG("NeoVimServer exiting...");
2017-01-08 21:01:34 +03:00
CFRunLoopStop(_mainRunLoop);
}
2017-01-06 12:16:55 +03:00
#pragma mark Functions for neovim's main loop
2016-07-09 23:52:59 +03:00
2017-01-06 09:12:05 +03:00
typedef NSData *(^work_block)(NSData *);
2016-07-09 23:52:59 +03:00
2017-01-06 09:12:05 +03:00
static void work_and_write_data_sync(void **argv, work_block block) {
@autoreleasepool {
NSCondition *outputCondition = argv[1];
[outputCondition lock];
2016-07-09 23:52:59 +03:00
NSData *data = argv[0];
DataWrapper *wrapper = argv[2];
wrapper.data = block(data);
wrapper.dataReady = YES;
[data release]; // retained in local_server_callback
2016-07-09 23:52:59 +03:00
[outputCondition signal];
[outputCondition unlock];
}
2016-07-09 23:52:59 +03:00
}
2017-01-24 20:27:44 +03:00
//static void work_async(void **argv, work_block block) {
// @autoreleasepool {
// NSData *data = argv[0];
// block(data);
// [data release]; // retained in local_server_callback
// }
//}
2017-01-06 10:51:01 +03:00
static NSString *escaped_filename(NSString *filename) {
const char *file_system_rep = filename.fileSystemRepresentation;
2017-04-23 12:33:06 +03:00
char *escaped_filename = vim_strsave_fnameescape(file_system_rep, 0);
2017-06-06 23:39:41 +03:00
NSString *result = [NSString stringWithCString:(const char *) escaped_filename
encoding:NSUTF8StringEncoding];
xfree(escaped_filename);
return result;
}
2016-08-12 15:32:42 +03:00
2016-10-22 23:20:22 +03:00
static NeoVimBuffer *buffer_for(buf_T *buf) {
// To be sure...
if (buf == NULL) {
return nil;
}
2016-10-22 23:20:22 +03:00
if (buf->b_p_bl == 0) {
return nil;
}
NSString *fileName = nil;
if (buf->b_ffname != NULL) {
2017-06-06 23:39:41 +03:00
fileName = [NSString stringWithCString:(const char *) buf->b_ffname
encoding:NSUTF8StringEncoding];
2016-10-22 23:20:22 +03:00
}
bool current = curbuf == buf;
NeoVimBuffer *buffer = [[NeoVimBuffer alloc] initWithHandle:buf->handle
unescapedPath:fileName
2017-01-03 12:04:27 +03:00
dirty:(bool) buf->b_changed
readOnly:(bool) buf->b_p_ro
2016-10-22 23:20:22 +03:00
current:current];
return [buffer autorelease];
}
2017-05-30 23:13:52 +03:00
void neovim_scroll(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
NSInteger *values = (NSInteger *) data.bytes;
2017-06-24 15:12:11 +03:00
int horiz = (int) values[0];
int vert = (int) values[1];
int row = (int) values[2];
int column = (int) values[3];
2017-05-30 23:13:52 +03:00
if (horiz == 0 && vert == 0) {
return nil;
}
if (row < 0 || column < 0) {
row = 0;
column = 0;
}
2017-05-30 23:13:52 +03:00
// value > 0 => down or right
int horizDir;
int vertDir;
if (horiz != 0) {
horizDir = horiz > 0 ? MSCR_RIGHT: MSCR_LEFT;
2017-06-24 15:12:11 +03:00
custom_ui_scroll(horizDir, ABS(horiz), row, column);
2017-05-30 23:13:52 +03:00
}
if (vert != 0) {
vertDir = vert > 0 ? MSCR_DOWN: MSCR_UP;
2017-06-24 15:12:11 +03:00
custom_ui_scroll(vertDir, ABS(vert), row, column);
2017-05-30 23:13:52 +03:00
}
refresh_ui_screen(VALID);
2017-05-30 23:13:52 +03:00
return nil;
});
}
2017-01-06 09:12:05 +03:00
void neovim_select_window(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-01-06 09:12:05 +03:00
int handle = ((int *) data.bytes)[0];
2017-01-06 09:12:05 +03:00
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->handle == handle) {
2017-01-06 13:10:29 +03:00
Error err = ERROR_INIT;
2017-01-06 09:12:05 +03:00
nvim_set_current_win(win->handle, &err);
2017-04-28 20:08:27 +03:00
if (ERROR_SET(&err)) {
2017-01-06 09:12:05 +03:00
WLOG("Error selecting window with handle %d: %s", win->handle, err.msg);
return nil;
}
2017-01-06 09:12:05 +03:00
// nvim_set_current_win() does not seem to trigger a redraw.
refresh_ui_screen(0);
2017-01-06 09:12:05 +03:00
return nil;
}
}
2017-01-06 09:12:05 +03:00
return nil;
});
}
void neovim_tabs(void **argv) {
2017-01-06 09:12:05 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
NSMutableArray *tabs = [[NSMutableArray new] autorelease];
FOR_ALL_TABS(t) {
NSMutableArray *windows = [NSMutableArray new];
FOR_ALL_WINDOWS_IN_TAB(win, t) {
NeoVimBuffer *buffer = buffer_for(win->w_buffer);
if (buffer == nil) {
continue;
}
2017-01-06 09:12:05 +03:00
NeoVimWindow *window = [[NeoVimWindow alloc] initWithHandle:win->handle buffer:buffer];
[windows addObject:window];
[window release];
}
2017-01-06 09:12:05 +03:00
NeoVimTab *tab = [[NeoVimTab alloc] initWithHandle:t->handle windows:windows];
[windows release];
2017-01-06 09:12:05 +03:00
[tabs addObject:tab];
[tab release];
}
2017-01-06 12:16:55 +03:00
DLOG("tabs: %s", tabs.description.cstr);
2017-01-06 09:12:05 +03:00
return [NSKeyedArchiver archivedDataWithRootObject:tabs];
});
}
2016-10-22 23:20:22 +03:00
2017-01-06 09:12:05 +03:00
void neovim_buffers(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
NSMutableArray *buffers = [[NSMutableArray new] autorelease];
FOR_ALL_BUFFERS(buf) {
NeoVimBuffer *buffer = buffer_for(buf);
2016-10-22 23:20:22 +03:00
if (buffer == nil) {
continue;
}
2017-01-06 09:12:05 +03:00
[buffers addObject:buffer];
2016-10-22 23:20:22 +03:00
}
2017-01-06 12:16:55 +03:00
DLOG("buffers: %s", buffers.description.cstr);
2017-01-06 09:12:05 +03:00
return [NSKeyedArchiver archivedDataWithRootObject:buffers];
});
}
2016-10-22 23:20:22 +03:00
2017-01-06 09:12:05 +03:00
void neovim_vim_command_output(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-01-06 10:13:47 +03:00
NSString *input = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2016-10-22 23:20:22 +03:00
2017-01-06 13:10:29 +03:00
Error err = ERROR_INIT;
2017-01-08 12:04:04 +03:00
String commandOutput = nvim_command_output(vim_string_from(input), &err);
char_u *output = (char_u *) commandOutput.data;
2017-01-06 08:42:23 +03:00
2017-01-06 09:12:05 +03:00
// FIXME: handle err.set == true
NSString *result = nil;
2017-01-06 10:13:47 +03:00
if (output == NULL) {
WLOG("vim command output is null");
2017-04-28 20:08:27 +03:00
} else if (ERROR_SET(&err)) {
WLOG("vim command output for '%s' was not successful: %s", input.cstr, err.msg);
2017-01-06 10:13:47 +03:00
} else {
2017-06-06 23:39:41 +03:00
result = [[NSString alloc] initWithCString:(const char *) output
encoding:NSUTF8StringEncoding];
}
2017-01-06 08:42:23 +03:00
NSData *resultData = result == nil ? nil : [NSKeyedArchiver archivedDataWithRootObject:result];
2017-01-06 10:13:47 +03:00
[result release];
[input release];
2017-01-06 08:42:23 +03:00
2017-01-06 10:13:47 +03:00
return resultData;
2017-01-06 09:12:05 +03:00
});
}
2017-01-06 10:28:04 +03:00
void neovim_set_bool_option(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
bool *optionValues = (bool *) data.bytes;
bool optionValue = optionValues[0];
const char *string = (const char *)(optionValues + 1);
NSString *optionName = [[NSString alloc] initWithCString:string encoding:NSUTF8StringEncoding];
2017-01-06 13:10:29 +03:00
Error err = ERROR_INIT;
2017-01-06 10:28:04 +03:00
Object object = OBJECT_INIT;
object.type = kObjectTypeBoolean;
object.data.boolean = optionValue;
DLOG("%s to set: %d", optionName.cstr, optionValue);
nvim_set_option(vim_string_from(optionName), object, &err);
2017-04-28 20:08:27 +03:00
if (ERROR_SET(&err)) {
2017-01-06 10:28:04 +03:00
WLOG("Error setting the option '%s' to %d: %s", optionName.cstr, optionValue, err.msg);
}
[optionName release];
return nil;
});
}
2016-11-10 19:33:14 +03:00
2017-01-06 10:33:16 +03:00
void neovim_get_bool_option(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
NSString *option = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2016-11-11 15:37:38 +03:00
bool result = false;
2017-01-06 13:10:29 +03:00
Error err = ERROR_INIT;
2016-11-11 15:37:38 +03:00
Object resultObj = nvim_get_option(vim_string_from(option), &err);
2016-11-10 22:42:42 +03:00
2017-04-28 20:08:27 +03:00
if (ERROR_SET(&err)) {
2016-11-11 15:37:38 +03:00
WLOG("Error getting the boolean option '%s': %s", option.cstr, err.msg);
2016-11-10 22:42:42 +03:00
}
2016-11-11 15:37:38 +03:00
if (resultObj.type == kObjectTypeBoolean) {
result = resultObj.data.boolean;
} else {
2017-06-06 23:39:41 +03:00
WLOG("Error got no boolean value, but %d, for option '%s': %s",
resultObj.type, option.cstr, err.msg);
2016-11-10 22:42:42 +03:00
}
2016-11-11 15:37:38 +03:00
2017-01-06 10:33:16 +03:00
[option release];
return [NSKeyedArchiver archivedDataWithRootObject:@(result)];
});
2016-11-10 19:33:14 +03:00
}
2017-01-06 10:38:13 +03:00
void neovim_escaped_filenames(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
NSArray *fileNames = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableArray *result = [NSMutableArray new];
[fileNames enumerateObjectsUsingBlock:^(NSString* fileName, NSUInteger idx, BOOL *stop) {
[result addObject:escaped_filename(fileName)];
}];
2016-11-10 22:42:42 +03:00
2017-01-06 10:38:13 +03:00
return [NSKeyedArchiver archivedDataWithRootObject:result];
2016-11-10 22:42:42 +03:00
});
}
2017-01-06 10:51:01 +03:00
void neovim_has_dirty_docs(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
return [NSKeyedArchiver archivedDataWithRootObject:@(has_dirty_docs())];
});
}
2017-01-06 10:57:26 +03:00
void neovim_resize(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-01-06 12:27:08 +03:00
const int *values = data.bytes;
2017-01-06 10:57:26 +03:00
int width = values[0];
int height = values[1];
2016-11-10 19:33:14 +03:00
2017-01-06 10:57:26 +03:00
set_ui_size(_server_ui_data->bridge, width, height);
ui_refresh();
2016-11-10 19:33:14 +03:00
2017-01-06 10:57:26 +03:00
return nil;
});
}
2016-11-10 19:33:14 +03:00
2017-01-06 11:22:16 +03:00
void neovim_vim_command(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-01-06 11:22:16 +03:00
NSString *input = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2017-01-06 13:10:29 +03:00
Error err = ERROR_INIT;
2017-01-06 11:22:16 +03:00
nvim_command(vim_string_from(input), &err);
2017-04-28 20:08:27 +03:00
if (ERROR_SET(&err)) {
2017-01-07 15:55:53 +03:00
WLOG("ERROR while executing command %s: %s", input.cstr, err.msg);
}
2017-01-06 11:22:16 +03:00
[input release];
return nil;
});
}
2017-01-06 12:06:47 +03:00
void neovim_vim_input(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-06-06 23:39:41 +03:00
NSString *input = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
2017-01-06 12:06:47 +03:00
if (_marked_text == nil) {
nvim_input(vim_string_from(input));
return nil;
}
2017-06-06 23:39:41 +03:00
// Handle cases like -> arrow key: The previously marked text is the same as the finalized
// text which should inserted. Neovim's drawing code is optimized such that it does not call
// put in this case again, thus, we have to manually unmark the cells in the main app.
2017-01-06 12:06:47 +03:00
if ([_marked_text isEqualToString:input]) {
DLOG("unmarking text: '%s'\t now at %d:%d", input.cstr, _put_row, _put_column);
const char *str = _marked_text.cstr;
size_t cellCount = mb_string2cells((const char_u *) str);
2016-11-10 22:42:42 +03:00
2017-01-06 12:06:47 +03:00
for (int i = 1; i <= cellCount; i++) {
DLOG("unmarking at %d:%d", _put_row, _put_column - i);
NSInteger values[] = {_put_row, MAX(_put_column - i, 0)};
2017-01-06 12:06:47 +03:00
NSData *unmarkData = [[NSData alloc] initWithBytes:values length:(2 * sizeof(NSInteger))];
2017-01-06 12:06:47 +03:00
[_neovim_server sendMessageWithId:NeoVimServerMsgIdUnmark data:unmarkData];
[unmarkData release];
}
}
delete_marked_text();
nvim_input(vim_string_from(input));
return nil;
});
2016-11-10 19:33:14 +03:00
}
2017-01-06 12:06:47 +03:00
void neovim_vim_input_marked_text(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-06-06 23:39:41 +03:00
NSString *markedText = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
2017-01-06 12:06:47 +03:00
if (_marked_text == nil) {
_marked_row = _put_row;
_marked_column = _put_column + _marked_delta;
2017-06-06 23:39:41 +03:00
DLOG(
"marking position: %d:%d(%d + %d)", _put_row, _marked_column, _put_column, _marked_delta
);
2017-01-06 12:06:47 +03:00
_marked_delta = 0;
} else {
delete_marked_text();
}
2016-11-10 22:42:42 +03:00
2017-01-06 12:06:47 +03:00
DLOG("inserting marked text '%s' at %d:%d", markedText.cstr, _put_row, _put_column);
insert_marked_text(markedText);
2017-01-06 12:06:47 +03:00
return nil;
});
}
2017-01-06 12:06:47 +03:00
void neovim_delete(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-01-06 12:27:08 +03:00
const NSInteger *values = data.bytes;
2017-01-06 12:06:47 +03:00
NSInteger count = values[0];
_marked_delta = 0;
2017-06-06 23:39:41 +03:00
// Very ugly: When we want to have the Hanja for , Cocoa first finalizes , then sets
// the Hanja as marked text. The main app will call this method when this happens,
// thus compute how many cell we have to go backward to correctly
// mark the will-be-soon-inserted Hanja... See also docs/notes-on-cocoa-text-input.md
2017-01-06 12:06:47 +03:00
int emptyCounter = 0;
for (int i = 0; i < count; i++) {
_marked_delta -= 1;
2017-06-06 23:39:41 +03:00
// TODO: -1 because we assume that the cursor is one cell ahead,
// probably not always correct...
2017-01-06 12:06:47 +03:00
schar_T character = ScreenLines[_put_row * screen_Rows + _put_column - i - emptyCounter - 1];
if (character == 0x00 || character == ' ') {
// FIXME: dunno yet, why we have to also match ' '...
_marked_delta -= 1;
emptyCounter += 1;
}
}
DLOG("put cursor: %d:%d, count: %li, delta: %d", _put_row, _put_column, count, _marked_delta);
for (int i = 0; i < count; i++) {
nvim_input(vim_string_from(_backspace));
}
return nil;
});
}
2017-01-07 15:55:53 +03:00
void neovim_pwd(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
char_u *temp = xmalloc(MAXPATHL);
if (os_dirname(temp, MAXPATHL) == FAIL) {
xfree(temp);
return nil;
}
NSString *pwd = [NSString stringWithCString:(const char *) temp encoding:NSUTF8StringEncoding];
xfree(temp);
NSData *resultData = [NSKeyedArchiver archivedDataWithRootObject:pwd];
return resultData;
});
}
2017-01-07 15:55:53 +03:00
void neovim_cursor_goto(void **argv) {
2017-01-24 20:27:44 +03:00
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
2017-01-07 17:12:07 +03:00
const int *values = data.bytes;
2017-01-07 15:55:53 +03:00
Array position = ARRAY_DICT_INIT;
2017-01-07 17:12:07 +03:00
2017-01-07 15:55:53 +03:00
position.size = 2;
2017-01-07 17:12:07 +03:00
position.capacity = 2;
position.items = xmalloc(2 * sizeof(Object));
2017-01-07 15:55:53 +03:00
Object row = OBJECT_INIT;
row.type = kObjectTypeInteger;
2017-01-07 17:12:07 +03:00
row.data.integer = values[0];
2017-01-07 15:55:53 +03:00
Object col = OBJECT_INIT;
col.type = kObjectTypeInteger;
2017-01-07 17:12:07 +03:00
col.data.integer = values[1];
2017-01-07 15:55:53 +03:00
position.items[0] = row;
position.items[1] = col;
Error err = ERROR_INIT;
nvim_win_set_cursor(nvim_get_current_win(), position, &err);
2017-01-07 17:12:07 +03:00
// The above call seems to be not enough...
nvim_input((String) { .data="<ESC>", .size=5 });
2017-01-07 15:55:53 +03:00
xfree(position.items);
return nil;
});
}
2017-06-25 18:35:51 +03:00
void neovim_debug1(void **argv) {
work_and_write_data_sync(argv, ^NSData *(NSData *data) {
NSLog(@"normal fg: %#08X", normal_fg);
NSLog(@"normal bg: %#08X", normal_bg);
NSLog(@"normal sp: %#08X", normal_sp);
for (int i = 0; i < HLF_COUNT; i++) {
NSLog(@"%s: %#08X", hlf_names[i], HlAttrsFromAttrCode(highlight_attr[i]).foreground);
}
return nil;
});
}