1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-28 08:13:17 +03:00
vimr/NvimView/NvimServer/server_ui_bridge.c
2019-03-28 11:27:45 +01:00

481 lines
13 KiB
C

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
#include "server_log.h"
#define FileInfo CarbonFileInfo
#define Boolean CarbonBoolean
#include "server_shared_types.h"
#include "server.h"
#undef Boolean
#undef FileInfo
#include <nvim/api/private/defs.h>
#include <nvim/vim.h>
#include <nvim/fileio.h>
#include <nvim/undo.h>
#include <nvim/syntax.h>
#include <nvim/highlight.h>
#include <nvim/msgpack_rpc/helpers.h>
#include "server_ui_bridge.h"
server_ui_bridge_data_t bridge_data;
#pragma mark server_ui_bridge
static NSInteger default_foreground = 0xFF000000;
static NSInteger default_background = 0xFFFFFFFF;
static NSInteger default_special = 0xFFFF0000;
static bool are_buffers_dirty = false;
static msgpack_sbuffer flush_sbuffer;
static msgpack_packer flush_packer;
static void pack_flush_data(RenderDataType type, pack_block body);
static void send_cwd(void);
static void send_dirty_status(void);
static void send_colorscheme(void);
static void server_ui_scheduler(Event event, void *d);
static void server_ui_main(UIBridgeData *bridge, UI *ui);
#pragma mark ui_bridge callbacks
static void server_ui_flush(UI *ui __unused) {
if (flush_sbuffer.size == 0) { return; }
CFDataRef const data = CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault,
(const UInt8 *) flush_sbuffer.data,
flush_sbuffer.size,
kCFAllocatorNull
);
server_send_msg(NvimServerMsgIdFlush, data);
CFRelease(data);
free(msgpack_sbuffer_release(&flush_sbuffer));
msgpack_packer_init(&flush_packer, &flush_sbuffer, msgpack_sbuffer_write);
}
static void server_ui_grid_resize(
UI *ui __unused, Integer grid __unused, Integer width, Integer height
) {
server_ui_flush(NULL);
send_msg_packing(NvimServerMsgIdResize, ^(msgpack_packer *packer) {
msgpack_pack_array(packer, 2);
msgpack_pack_int64(packer, width);
msgpack_pack_int64(packer, height);
});
}
static void server_ui_grid_clear(UI *ui __unused, Integer grid __unused) {
server_send_msg(NvimServerMsgIdClear, NULL);
}
static void server_ui_cursor_goto(
UI *ui __unused,
Integer grid __unused,
Integer row,
Integer col
) {
pack_flush_data(RenderDataTypeGoto, ^(msgpack_packer *packer) {
msgpack_pack_array(packer, 2);
msgpack_pack_int64(packer, row);
msgpack_pack_int64(packer, col);
});
}
static void server_ui_update_menu(UI *ui __unused) {
server_send_msg(NvimServerMsgIdSetMenu, NULL);
}
static void server_ui_busy_start(UI *ui __unused) {
server_send_msg(NvimServerMsgIdBusyStart, NULL);
}
static void server_ui_busy_stop(UI *ui __unused) {
server_send_msg(NvimServerMsgIdBusyStop, NULL);
}
static void server_ui_mode_info_set(
UI *ui __unused,
Boolean enabled __unused,
Array cursor_styles __unused
) {}
static void server_ui_mode_change(
UI *ui __unused,
String mode_str __unused,
Integer mode
) {
send_msg_packing(NvimServerMsgIdModeChange, ^(msgpack_packer *packer) {
msgpack_pack_int64(packer, mode);
});
}
static void server_ui_grid_scroll(
UI *ui __unused,
Integer grid __unused,
Integer top,
Integer bot,
Integer left,
Integer right,
Integer rows,
Integer cols
) {
pack_flush_data(RenderDataTypeScroll, ^(msgpack_packer *packer) {
msgpack_pack_array(packer, 6);
msgpack_pack_int64(packer, top);
msgpack_pack_int64(packer, bot);
msgpack_pack_int64(packer, left);
msgpack_pack_int64(packer, right);
msgpack_pack_int64(packer, rows);
msgpack_pack_int64(packer, cols);
});
}
static void server_ui_hl_attr_define(
UI *ui __unused,
Integer id, HlAttrs attrs,
HlAttrs cterm_attrs __unused,
Array info __unused
) {
FontTrait trait = FontTraitNone;
if (attrs.rgb_ae_attr & HL_ITALIC) { trait |= FontTraitItalic; }
if (attrs.rgb_ae_attr & HL_BOLD) { trait |= FontTraitBold; }
if (attrs.rgb_ae_attr & HL_UNDERLINE) { trait |= FontTraitUnderline; }
if (attrs.rgb_ae_attr & HL_UNDERCURL) { trait |= FontTraitUndercurl; }
send_msg_packing(NvimServerMsgIdHighlightAttrs, ^(msgpack_packer *packer) {
msgpack_pack_array(packer, 6);
msgpack_pack_int64(packer, id);
msgpack_pack_uint64(packer, trait);
msgpack_pack_int32(packer, attrs.rgb_fg_color);
msgpack_pack_int32(packer, attrs.rgb_bg_color);
msgpack_pack_int32(packer, attrs.rgb_sp_color);
msgpack_pack_bool(packer, (bool) (attrs.rgb_ae_attr & HL_INVERSE));
});
}
static void server_ui_raw_line(
UI *ui __unused,
Integer grid __unused,
Integer row,
Integer startcol,
Integer endcol,
Integer clearcol,
Integer clearattr,
LineFlags flags,
const schar_T *chunk,
const sattr_T *attrs
) {
const Integer count = endcol - startcol;
pack_flush_data(RenderDataTypeRawLine, ^(msgpack_packer *packer) {
msgpack_pack_array(packer, 7);
msgpack_pack_int64(packer, row);
msgpack_pack_int64(packer, startcol);
msgpack_pack_int64(packer, endcol);
msgpack_pack_int64(packer, clearcol);
msgpack_pack_int64(packer, clearattr);
msgpack_pack_array(packer, (size_t) count);
for (Integer i = 0; i < count; i++) {
msgpack_pack_cstr(packer, (const char *) chunk[i]);
}
msgpack_pack_array(packer, (size_t) count);
for (Integer i = 0; i < count; i++) {
msgpack_pack_int16(packer, attrs[i]);
}
});
}
static void server_ui_bell(UI *ui __unused) {
server_send_msg(NvimServerMsgIdBell, NULL);
}
static void server_ui_visual_bell(UI *ui __unused) {
server_send_msg(NvimServerMsgIdVisualBell, NULL);
}
static void server_ui_default_colors_set(
UI *ui __unused,
Integer rgb_fg,
Integer rgb_bg,
Integer rgb_sp,
Integer cterm_fg __unused,
Integer cterm_bg __unused
) {
if (rgb_fg != -1) { default_foreground = rgb_fg; }
if (rgb_bg != -1) { default_background = rgb_bg; }
if (rgb_sp != -1) { default_special = rgb_sp; }
send_msg_packing(
NvimServerMsgIdDefaultColorsChanged,
^(msgpack_packer *packer) {
msgpack_pack_array(packer, 3);
msgpack_pack_int64(packer, default_foreground);
msgpack_pack_int64(packer, default_background);
msgpack_pack_int64(packer, default_special);
}
);
}
static void server_ui_set_title(UI *ui __unused, String title) {
if (title.size == 0) { return; }
send_msg_packing(NvimServerMsgIdSetTitle, ^(msgpack_packer *packer) {
msgpack_rpc_from_string(title, packer);
});
}
static void server_ui_option_set(UI *ui __unused, String name, Object value) {
send_msg_packing(NvimServerMsgIdOptionSet, ^(msgpack_packer *packer) {
msgpack_pack_map(packer, 1);
msgpack_rpc_from_string(name, packer);
msgpack_rpc_from_object(value, packer);
});
}
static void server_ui_stop(UI *ui __unused) {
server_send_msg(NvimServerMsgIdStop, NULL);
server_ui_bridge_data_t *const data = ui->data;
data->stop = true;
}
static void dummy(UI *ui __unused) {}
static void dummy2(UI *ui __unused, String icon __unused) {}
#pragma mark called by nvim
void custom_ui_start(void) {
UI *const ui = xcalloc(1, sizeof(UI));
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
ui->ui_ext[kUILinegrid] = true;
ui->rgb = true;
ui->stop = server_ui_stop;
ui->grid_resize = server_ui_grid_resize;
ui->grid_clear = server_ui_grid_clear;
ui->grid_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 = dummy;
ui->mouse_off = dummy;
ui->mode_info_set = server_ui_mode_info_set;
ui->mode_change = server_ui_mode_change;
ui->grid_scroll = server_ui_grid_scroll;
ui->hl_attr_define = server_ui_hl_attr_define;
ui->default_colors_set = server_ui_default_colors_set;
ui->raw_line = server_ui_raw_line;
ui->bell = server_ui_bell;
ui->visual_bell = server_ui_visual_bell;
ui->flush = server_ui_flush;
ui->suspend = dummy;
ui->set_title = server_ui_set_title;
ui->set_icon = dummy2;
ui->option_set = server_ui_option_set;
ui_bridge_attach(ui, server_ui_main, server_ui_scheduler);
}
void custom_ui_rpcevent_subscribed() {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
server_send_msg(NvimServerMsgIdRpcEventSubscribed, NULL);
});
}
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
) {
switch (event) {
case EVENT_BUFENTER:
case EVENT_BUFLEAVE:
case EVENT_BUFWINENTER:
case EVENT_BUFWINLEAVE:
case EVENT_BUFWRITEPOST:
case EVENT_COLORSCHEME:
case EVENT_DIRCHANGED:
case EVENT_TABENTER:
case EVENT_TEXTCHANGED:
case EVENT_TEXTCHANGEDI:
case EVENT_VIMENTER:
case EVENT_GUIENTER:
break;
default:
return;
}
if (event == EVENT_DIRCHANGED) {
send_cwd();
return;
}
if (event == EVENT_COLORSCHEME) {
send_colorscheme();
return;
}
if (event == EVENT_TEXTCHANGED
|| event == EVENT_TEXTCHANGEDI
|| event == EVENT_BUFWRITEPOST
|| event == EVENT_BUFLEAVE) {
send_dirty_status();
}
send_msg_packing(
NvimServerMsgIdAutoCommandEvent,
^(msgpack_packer *packer) {
msgpack_pack_array(packer, 2);
msgpack_pack_int64(packer, (NSInteger) event);
if (buf == NULL) {
msgpack_pack_int64(packer, -1);
} else {
msgpack_pack_int64(packer, (NSInteger) buf->handle);
}
});
}
#pragma mark helpers
static bool has_dirty_docs() {
FOR_ALL_BUFFERS(buffer) {
if (bufIsChanged(buffer)) { return true; }
}
return false;
}
static void pack_flush_data(RenderDataType type, pack_block body) {
msgpack_pack_array(&flush_packer, 2);
msgpack_pack_int64(&flush_packer, type);
body(&flush_packer);
}
static void send_dirty_status() {
const bool new_dirty_status = has_dirty_docs();
if (are_buffers_dirty == new_dirty_status) { return; }
are_buffers_dirty = new_dirty_status;
send_msg_packing(
NvimServerMsgIdDirtyStatusChanged,
^(msgpack_packer *packer) {
msgpack_pack_bool(packer, are_buffers_dirty);
}
);
}
static void send_cwd() {
char_u *const temp = xmalloc(MAXPATHL);
if (os_dirname(temp, MAXPATHL) == FAIL) {
xfree(temp);
server_send_msg(NvimServerMsgIdCwdChanged, NULL);
return;
}
send_msg_packing(NvimServerMsgIdCwdChanged, ^(msgpack_packer *packer) {
msgpack_pack_cstr(packer, (const char *) temp);
xfree(temp);
});
}
static int foreground_for(HlAttrs attrs) {
const int mask = attrs.rgb_ae_attr;
return mask & HL_INVERSE ? attrs.rgb_bg_color : attrs.rgb_fg_color;
}
static int background_for(HlAttrs attrs) {
const int mask = attrs.rgb_ae_attr;
return mask & HL_INVERSE ? attrs.rgb_fg_color : attrs.rgb_bg_color;
}
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(); }
const HlAttrs visualAttrs = syn_attr2entry(highlight_attr[HLF_V]);
const HlAttrs dirAttrs = syn_attr2entry(highlight_attr[HLF_D]);
send_msg_packing(
NvimServerMsgIdColorSchemeChanged,
^(msgpack_packer *packer) {
msgpack_pack_array(packer, 5);
msgpack_pack_int64(packer, normal_fg);
msgpack_pack_int64(packer, normal_bg);
msgpack_pack_int64(packer, foreground_for(visualAttrs));
msgpack_pack_int64(packer, background_for(visualAttrs));
msgpack_pack_int64(packer, foreground_for(dirAttrs));
}
);
}
void server_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;
}
static void server_ui_scheduler(Event event, void *d) {
UI *const ui = d;
server_ui_bridge_data_t *data = ui->data;
loop_schedule(data->loop, event);
}
static void server_ui_main(UIBridgeData *bridge, UI *ui) {
msgpack_sbuffer_init(&flush_sbuffer);
msgpack_packer_init(&flush_packer, &flush_sbuffer, msgpack_sbuffer_write);
Loop loop;
loop_init(&loop, NULL);
ui->data = &bridge_data;
bridge_data.bridge = bridge;
bridge_data.loop = &loop;
server_set_ui_size(bridge, bridge_data.init_width, bridge_data.init_height);
bridge_data.stop = false;
CONTINUE(bridge);
send_msg_packing(NvimServerMsgIdNvimReady, ^(msgpack_packer *packer) {
msgpack_pack_bool(packer, msg_didany > 0);
});
// We have to manually trigger this to initially get the colorscheme.
send_colorscheme();
while (!bridge_data.stop) { loop_poll_events(&loop, -1); }
ui_bridge_stopped(bridge);
loop_close(&loop, false);
server_destroy_local_port();
server_destroy_remote_port();
// ui is freed in ui_bridge_stop(), thus, no xfree(ui) here.
free(msgpack_sbuffer_release(&flush_sbuffer));
// mch_exit() of neovim will call exit(...)
}