mirror of
https://github.com/qvacua/vimr.git
synced 2024-11-24 11:37:32 +03:00
Add some support for marked text
- implement some of usual text-editing related selectors of NSResponder - do not send <S-a> for A - introduce a dispatch queue in XPC which is needed to sync all marked stuff
This commit is contained in:
parent
10ed436bd6
commit
7358243b07
@ -15,6 +15,7 @@
|
||||
- (void)probe;
|
||||
|
||||
- (void)vimInput:(NSString * _Nonnull)input;
|
||||
- (void)vimInputMarkedText:(NSString *_Nonnull)markedText;
|
||||
|
||||
- (void)debugScreenLines;
|
||||
|
||||
|
@ -15,7 +15,8 @@
|
||||
|
||||
- (void)probe;
|
||||
|
||||
- (void)vimInput:(NSString * _Nonnull)input;
|
||||
- (void)vimInput:(NSString *_Nonnull)input;
|
||||
- (void)vimInputMarkedText:(NSString *_Nonnull)markedText;
|
||||
|
||||
- (void)debugScreenLines;
|
||||
|
||||
|
@ -20,23 +20,6 @@
|
||||
|
||||
#define pun_type(t, x) (*((t *)(&x)))
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
static id <NeoVimUiBridgeProtocol> neo_vim_osx_ui;
|
||||
|
||||
static unsigned int default_foreground = qDefaultForeground;
|
||||
static unsigned int default_background = qDefaultBackground;
|
||||
static unsigned int default_special = qDefaultSpecial;
|
||||
|
||||
typedef struct {
|
||||
UIBridgeData *bridge;
|
||||
Loop *loop;
|
||||
@ -48,6 +31,35 @@ typedef struct {
|
||||
SignalWatcher cont_handle;
|
||||
} XpcUiData;
|
||||
|
||||
// We declare nvim_main because it's not declared in any header files of neovim
|
||||
extern int nvim_main(int argc, char **argv);
|
||||
|
||||
static unsigned int default_foreground = qDefaultForeground;
|
||||
static unsigned int default_background = qDefaultBackground;
|
||||
static unsigned int default_special = qDefaultSpecial;
|
||||
|
||||
// The thread in which neovim's main runs
|
||||
static uv_thread_t _nvim_thread;
|
||||
|
||||
// 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;
|
||||
|
||||
static id <NeoVimUiBridgeProtocol> _neo_vim_osx_ui;
|
||||
|
||||
static int _row = 0;
|
||||
static int _column = 0;
|
||||
static int _markedRow = 0;
|
||||
static int _markedColumn = 0;
|
||||
static NSString *_markedText = nil;
|
||||
|
||||
static dispatch_queue_t _queue;
|
||||
|
||||
static void xpc_async(void (^block)()) {
|
||||
dispatch_async(_queue, block);
|
||||
}
|
||||
|
||||
static void sigcont_cb(SignalWatcher *watcher __unused, int signum __unused, void *data) {
|
||||
((XpcUiData *) data)->cont_received = true;
|
||||
}
|
||||
@ -77,10 +89,10 @@ static void osx_xpc_ui_main(UIBridgeData *bridge, UI *ui) {
|
||||
data->stop = false;
|
||||
CONTINUE(bridge);
|
||||
|
||||
uv_mutex_lock(&mutex);
|
||||
is_ui_launched = true;
|
||||
uv_cond_signal(&condition);
|
||||
uv_mutex_unlock(&mutex);
|
||||
uv_mutex_lock(&_mutex);
|
||||
_is_ui_launched = true;
|
||||
uv_cond_signal(&_condition);
|
||||
uv_mutex_unlock(&_mutex);
|
||||
|
||||
while (!data->stop) {
|
||||
loop_poll_events(&loop, -1);
|
||||
@ -110,171 +122,231 @@ static void suspend_event(void **argv) {
|
||||
}
|
||||
|
||||
static void xpc_ui_resize(UI *ui __unused, int width, int height) {
|
||||
//printf("resize: %d:%d\n", width, height);
|
||||
[neo_vim_osx_ui resizeToWidth:width height:height];
|
||||
xpc_async(^{
|
||||
//printf("resize: %d:%d\n", width, height);
|
||||
[_neo_vim_osx_ui resizeToWidth:width height:height];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_clear(UI *ui __unused) {
|
||||
//printf("clear\n");
|
||||
[neo_vim_osx_ui clear];
|
||||
xpc_async(^{
|
||||
//printf("clear\n");
|
||||
[_neo_vim_osx_ui clear];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_eol_clear(UI *ui __unused) {
|
||||
//printf("eol clear\n");
|
||||
[neo_vim_osx_ui eolClear];
|
||||
xpc_async(^{
|
||||
//printf("eol clear\n");
|
||||
[_neo_vim_osx_ui eolClear];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_cursor_goto(UI *ui __unused, int row, int col) {
|
||||
//printf("cursor goto %d:%d\n", row, col);
|
||||
[neo_vim_osx_ui cursorGotoRow:row column:col];
|
||||
xpc_async(^{
|
||||
//printf("cursor goto %d:%d\n", row, col);
|
||||
_row = row;
|
||||
_column = col;
|
||||
|
||||
[_neo_vim_osx_ui cursorGotoRow:row column:col];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_update_menu(UI *ui __unused) {
|
||||
//printf("update menu\n");
|
||||
[neo_vim_osx_ui updateMenu];
|
||||
xpc_async(^{
|
||||
//printf("update menu\n");
|
||||
[_neo_vim_osx_ui updateMenu];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_busy_start(UI *ui __unused) {
|
||||
//printf("busy start\n");
|
||||
[neo_vim_osx_ui busyStart];
|
||||
xpc_async(^{
|
||||
//printf("busy start\n");
|
||||
[_neo_vim_osx_ui busyStart];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_busy_stop(UI *ui __unused) {
|
||||
//printf("busy stop\n");
|
||||
[neo_vim_osx_ui busyStop];
|
||||
xpc_async(^{
|
||||
//printf("busy stop\n");
|
||||
[_neo_vim_osx_ui busyStop];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_mouse_on(UI *ui __unused) {
|
||||
//printf("mouse on\n");
|
||||
[neo_vim_osx_ui mouseOn];
|
||||
xpc_async(^{
|
||||
//printf("mouse on\n");
|
||||
[_neo_vim_osx_ui mouseOn];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_mouse_off(UI *ui __unused) {
|
||||
//printf("mouse off\n");
|
||||
[neo_vim_osx_ui mouseOff];
|
||||
xpc_async(^{
|
||||
//printf("mouse off\n");
|
||||
[_neo_vim_osx_ui mouseOff];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_mode_change(UI *ui __unused, int mode) {
|
||||
//printf("mode change %04x\n", mode);
|
||||
[neo_vim_osx_ui modeChange:mode];
|
||||
xpc_async(^{
|
||||
//printf("mode change %04x\n", mode);
|
||||
[_neo_vim_osx_ui modeChange:mode];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_set_scroll_region(UI *ui __unused, int top, int bot, int left, int right) {
|
||||
//printf("set scroll region: %d, %d, %d, %d\n", top, bot, left, right);
|
||||
[neo_vim_osx_ui setScrollRegionToTop:top bottom:bot left:left right:right];
|
||||
xpc_async(^{
|
||||
//printf("set scroll region: %d, %d, %d, %d\n", top, bot, left, right);
|
||||
[_neo_vim_osx_ui setScrollRegionToTop:top bottom:bot left:left right:right];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_scroll(UI *ui __unused, int count) {
|
||||
//printf("scroll %d\n", count);
|
||||
[neo_vim_osx_ui scroll:count];
|
||||
xpc_async(^{
|
||||
//printf("scroll %d\n", count);
|
||||
[_neo_vim_osx_ui scroll:count];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_highlight_set(UI *ui __unused, HlAttrs attrs) {
|
||||
//printf("highlight set\n");
|
||||
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;
|
||||
xpc_async(^{
|
||||
//printf("highlight set\n");
|
||||
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;
|
||||
|
||||
unsigned int fg = attrs.foreground == -1 ? default_foreground : pun_type(unsigned int, attrs.foreground);
|
||||
unsigned int bg = attrs.background == -1 ? default_background : pun_type(unsigned int, attrs.background);
|
||||
unsigned int fg = attrs.foreground == -1 ? default_foreground : pun_type(unsigned int, attrs.foreground);
|
||||
unsigned int bg = attrs.background == -1 ? default_background : pun_type(unsigned int, attrs.background);
|
||||
|
||||
cellAttrs.foreground = attrs.reverse ? bg : fg;
|
||||
cellAttrs.background = attrs.reverse ? fg : bg;
|
||||
cellAttrs.special = attrs.special == -1 ? default_special : pun_type(unsigned int, attrs.special);
|
||||
cellAttrs.foreground = attrs.reverse ? bg : fg;
|
||||
cellAttrs.background = attrs.reverse ? fg : bg;
|
||||
cellAttrs.special = attrs.special == -1 ? default_special : pun_type(unsigned int, attrs.special);
|
||||
|
||||
[neo_vim_osx_ui highlightSet:cellAttrs];
|
||||
[_neo_vim_osx_ui highlightSet:cellAttrs];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_put(UI *ui __unused, uint8_t *str, size_t len) {
|
||||
NSString *string = [[NSString alloc] initWithBytes:str length:len encoding:NSUTF8StringEncoding];
|
||||
//printf("put: %zu:'%s'\n", len, [string cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
[neo_vim_osx_ui put:string];
|
||||
[string release];
|
||||
xpc_async(^{
|
||||
NSString *string = [[NSString alloc] initWithBytes:str length:len encoding:NSUTF8StringEncoding];
|
||||
// printf("put: %lu:'%s'\n", len, [string cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
|
||||
if (_markedText != nil && _markedColumn == _column && _markedRow == _row) {
|
||||
[_neo_vim_osx_ui putMarkedText:string];
|
||||
} else if (_markedText != nil && len == 0 && _markedColumn == _column - 1) {
|
||||
[_neo_vim_osx_ui putMarkedText:string];
|
||||
} else {
|
||||
[_neo_vim_osx_ui put:string];
|
||||
}
|
||||
|
||||
_column += 1;
|
||||
[string release];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_bell(UI *ui __unused) {
|
||||
//printf("bell\n");
|
||||
[neo_vim_osx_ui bell];
|
||||
xpc_async(^{
|
||||
//printf("bell\n");
|
||||
[_neo_vim_osx_ui bell];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_visual_bell(UI *ui __unused) {
|
||||
//printf("visual bell\n");
|
||||
[neo_vim_osx_ui visualBell];
|
||||
xpc_async(^{
|
||||
//printf("visual bell\n");
|
||||
[_neo_vim_osx_ui visualBell];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_flush(UI *ui __unused) {
|
||||
//printf("flush\n");
|
||||
[neo_vim_osx_ui flush];
|
||||
xpc_async(^{
|
||||
//printf("flush\n");
|
||||
[_neo_vim_osx_ui flush];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_update_fg(UI *ui __unused, int fg) {
|
||||
xpc_async(^{
|
||||
// printf("update fg: %x\n", fg);
|
||||
if (fg != -1) {
|
||||
default_foreground = pun_type(unsigned int, fg);
|
||||
}
|
||||
[neo_vim_osx_ui updateForeground:fg];
|
||||
if (fg != -1) {
|
||||
default_foreground = pun_type(unsigned int, fg);
|
||||
}
|
||||
[_neo_vim_osx_ui updateForeground:fg];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_update_bg(UI *ui __unused, int bg) {
|
||||
xpc_async(^{
|
||||
// printf("update bg: %x\n", bg);
|
||||
if (bg != -1) {
|
||||
default_background = pun_type(unsigned int, bg);
|
||||
}
|
||||
[neo_vim_osx_ui updateBackground:bg];
|
||||
if (bg != -1) {
|
||||
default_background = pun_type(unsigned int, bg);
|
||||
}
|
||||
[_neo_vim_osx_ui updateBackground:bg];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_update_sp(UI *ui __unused, int sp) {
|
||||
xpc_async(^{
|
||||
// printf("update sp: %x\n", sp);
|
||||
if (sp != -1) {
|
||||
default_special = pun_type(unsigned int, sp);
|
||||
}
|
||||
[neo_vim_osx_ui updateSpecial:sp];
|
||||
if (sp != -1) {
|
||||
default_special = pun_type(unsigned int, sp);
|
||||
}
|
||||
[_neo_vim_osx_ui updateSpecial:sp];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_suspend(UI *ui __unused) {
|
||||
//printf("suspend\n");
|
||||
[neo_vim_osx_ui suspend];
|
||||
xpc_async(^{
|
||||
//printf("suspend\n");
|
||||
[_neo_vim_osx_ui suspend];
|
||||
|
||||
XpcUiData *data = ui->data;
|
||||
// FIXME: dunno whether we need this: copied from tui.c
|
||||
// kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
|
||||
// before continuing. This is done in another callback to avoid
|
||||
// loop_poll_events recursion
|
||||
queue_put_event(data->loop->fast_events, event_create(1, suspend_event, 1, ui));
|
||||
XpcUiData *data = ui->data;
|
||||
// FIXME: dunno whether we need this: copied from tui.c
|
||||
// kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
|
||||
// before continuing. This is done in another callback to avoid
|
||||
// loop_poll_events recursion
|
||||
queue_put_event(data->loop->fast_events, event_create(1, suspend_event, 1, ui));
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_set_title(UI *ui __unused, char *title) {
|
||||
//printf("set title: %s\n", title);
|
||||
NSString *string = [[NSString alloc] initWithCString:title encoding:NSUTF8StringEncoding];
|
||||
[neo_vim_osx_ui setTitle:string];
|
||||
[string release];
|
||||
xpc_async(^{
|
||||
//printf("set title: %s\n", title);
|
||||
NSString *string = [[NSString alloc] initWithCString:title encoding:NSUTF8StringEncoding];
|
||||
[_neo_vim_osx_ui setTitle:string];
|
||||
[string release];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_set_icon(UI *ui __unused, char *icon) {
|
||||
//printf("set title: %s\n", icon);
|
||||
NSString *string = [[NSString alloc] initWithCString:icon encoding:NSUTF8StringEncoding];
|
||||
[neo_vim_osx_ui setIcon:string];
|
||||
[string release];
|
||||
xpc_async(^{
|
||||
//printf("set title: %s\n", icon);
|
||||
NSString *string = [[NSString alloc] initWithCString:icon encoding:NSUTF8StringEncoding];
|
||||
[_neo_vim_osx_ui setIcon:string];
|
||||
[string release];
|
||||
});
|
||||
}
|
||||
|
||||
static void xpc_ui_stop(UI *ui __unused) {
|
||||
//printf("stop\n");
|
||||
[neo_vim_osx_ui stop];
|
||||
xpc_async(^{
|
||||
//printf("stop\n");
|
||||
[_neo_vim_osx_ui stop];
|
||||
|
||||
XpcUiData *data = (XpcUiData *) ui->data;
|
||||
data->stop = true;
|
||||
XpcUiData *data = (XpcUiData *) ui->data;
|
||||
data->stop = true;
|
||||
});
|
||||
}
|
||||
|
||||
static void run_neovim(void *arg __unused) {
|
||||
@ -318,20 +390,16 @@ void custom_ui_start(void) {
|
||||
ui_bridge_attach(ui, osx_xpc_ui_main, osx_xpc_ui_scheduler);
|
||||
}
|
||||
|
||||
// We don't wait in this callback because the input events are coming from the XPC's main thread, but we call it the
|
||||
// same as in tui.c. This function is called in the main_loop of neovim.
|
||||
static void wait_input_enqueue(void **argv) {
|
||||
static void neovim_input(void **argv) {
|
||||
NSString *input = (NSString *) argv[0];
|
||||
|
||||
// FIXME: check the length of the consumed bytes by neovim and if not fully consumed, call vim_input again.
|
||||
vim_input((String) {
|
||||
.data=(char *) input.UTF8String,
|
||||
.size=[input lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
|
||||
.data = (char *) input.UTF8String,
|
||||
.size = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
|
||||
});
|
||||
|
||||
// Release NSString that is retained in -NeoVimXpcImpl.vimInput:. The retain call was in the main thread of the XPC
|
||||
// service.
|
||||
[input release];
|
||||
[input release]; // retain in loop_schedule(&main_loop, ...) (in _queue) somewhere
|
||||
}
|
||||
|
||||
@implementation NeoVimXpcImpl
|
||||
@ -342,33 +410,35 @@ static void wait_input_enqueue(void **argv) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_queue = dispatch_queue_create("xpc_callback_queue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// set $VIMRUNTIME to ${RESOURCE_PATH_OF_XPC_BUNDLE}/runtime
|
||||
NSString *runtimePath = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:@"runtime"];
|
||||
setenv("VIMRUNTIME", runtimePath.fileSystemRepresentation, true);
|
||||
|
||||
uv_mutex_init(&mutex);
|
||||
uv_cond_init(&condition);
|
||||
uv_mutex_init(&_mutex);
|
||||
uv_cond_init(&_condition);
|
||||
|
||||
uv_thread_create(&nvim_thread, run_neovim, NULL);
|
||||
uv_thread_create(&_nvim_thread, run_neovim, NULL);
|
||||
|
||||
// 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_lock(&_mutex);
|
||||
while (!_is_ui_launched) {
|
||||
uv_cond_wait(&_condition, &_mutex);
|
||||
}
|
||||
uv_mutex_unlock(&mutex);
|
||||
uv_mutex_unlock(&_mutex);
|
||||
|
||||
uv_cond_destroy(&condition);
|
||||
uv_mutex_destroy(&mutex);
|
||||
uv_cond_destroy(&_condition);
|
||||
uv_mutex_destroy(&_mutex);
|
||||
|
||||
// casting because [ui retain] returns an NSObject
|
||||
neo_vim_osx_ui = (id <NeoVimUiBridgeProtocol>) [ui retain];
|
||||
_neo_vim_osx_ui = (id <NeoVimUiBridgeProtocol>) [ui retain];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[neo_vim_osx_ui release];
|
||||
[_neo_vim_osx_ui release];
|
||||
// FIXME: uv_thread_join(&thread) here after terminating neovim
|
||||
|
||||
[super dealloc];
|
||||
@ -378,9 +448,57 @@ static void wait_input_enqueue(void **argv) {
|
||||
// noop
|
||||
}
|
||||
|
||||
- (void)vimInput:(NSString *)input {
|
||||
// We retain, but not release here since it will be released in wait_input_enqueue.
|
||||
loop_schedule(&main_loop, event_create(1, wait_input_enqueue, 1, [input retain]));
|
||||
- (void)vimInput:(NSString *_Nonnull)input {
|
||||
xpc_async(^{
|
||||
if (_markedText == nil) {
|
||||
loop_schedule(&main_loop, event_create(1, neovim_input, 1, [input retain])); // release in neovim_input
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if ([_markedText isEqualToString:input]) {
|
||||
[_neo_vim_osx_ui unmarkRow:_row column:MAX(_column - 1, 0)];
|
||||
if (ScreenLines[_row * screen_Columns + MAX(_column - 1, 0)] == 0x00) {
|
||||
[_neo_vim_osx_ui unmarkRow:_row column:MAX(_column - 2, 0)];
|
||||
}
|
||||
}
|
||||
|
||||
[self deleteMarkedText];
|
||||
loop_schedule(&main_loop, event_create(1, neovim_input, 1, [input retain])); // release in neovim_input
|
||||
});
|
||||
}
|
||||
|
||||
- (void)vimInputMarkedText:(NSString *_Nonnull)markedText {
|
||||
xpc_async(^{
|
||||
if (_markedText != nil) {
|
||||
[self deleteMarkedText];
|
||||
}
|
||||
|
||||
[self insertMarkedText:markedText];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)insertMarkedText:(NSString *_Nonnull)markedText {
|
||||
_markedRow = _row;
|
||||
_markedColumn = _column;
|
||||
_markedText = [markedText retain]; // release when the final text is input in -vimInput
|
||||
|
||||
loop_schedule(&main_loop, event_create(1, neovim_input, 1, [_markedText retain])); // release in neovim_input
|
||||
}
|
||||
|
||||
- (void)deleteMarkedText {
|
||||
NSUInteger length = [_markedText lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
|
||||
|
||||
[_markedText release];
|
||||
_markedText = nil;
|
||||
|
||||
NSString *backspace = [[NSString alloc] initWithString:@"<BS>"];
|
||||
for (int i = 0; i < length; i++) {
|
||||
loop_schedule(&main_loop, event_create(1, neovim_input, 1, [backspace retain])); // release in neovim_input
|
||||
}
|
||||
[backspace release];
|
||||
}
|
||||
|
||||
- (void)debugScreenLines {
|
||||
|
@ -6,8 +6,20 @@
|
||||
import Foundation
|
||||
|
||||
struct Cell: CustomStringConvertible {
|
||||
private let attributes: CellAttributes
|
||||
|
||||
let string: String
|
||||
let attrs: CellAttributes
|
||||
var marked: Bool
|
||||
|
||||
var attrs: CellAttributes {
|
||||
return self.marked ? self.attributes.reversedColor : self.attributes
|
||||
}
|
||||
|
||||
init(string: String, attrs: CellAttributes, marked: Bool = false) {
|
||||
self.string = string
|
||||
self.attributes = attrs
|
||||
self.marked = marked
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return self.string.characters.count > 0 ? self.string : "*"
|
||||
@ -153,7 +165,17 @@ class Grid: CustomStringConvertible {
|
||||
|
||||
func putMarkedText(string: String) {
|
||||
// NOTE: Maybe there's a better way to indicate marked text than inverting...
|
||||
self.cells[self.position.row][self.position.column] = Cell(string: string, attrs: self.attrs.reversedColor)
|
||||
self.cells[self.position.row][self.position.column] = Cell(string: string, attrs: self.attrs, marked: true)
|
||||
self.position.column += 1
|
||||
}
|
||||
|
||||
func unmarkCell(position: Position) {
|
||||
print("!!!!! unmarking: \(position)")
|
||||
self.cells[position.row][position.column].marked = false
|
||||
}
|
||||
|
||||
func currentCell() -> Cell {
|
||||
return self.cells[self.position.row][self.position.column]
|
||||
}
|
||||
|
||||
private func clearRegion(region: Region) {
|
||||
|
@ -154,7 +154,7 @@ public class InputTestView: NSView, NSTextInputClient {
|
||||
/// "Cmd-Ctrl-Return" you'll get the Emoji-popup at the rect by firstRectForCharacterRange(actualRange:) where the
|
||||
/// first range is the result of this method.
|
||||
public func selectedRange() -> NSRange {
|
||||
let result = NSRange(location: 13, length: 0)
|
||||
let result = NSRange(location: 1, length: 0)
|
||||
Swift.print("\(#function): returning \(result)")
|
||||
return result
|
||||
}
|
||||
@ -168,7 +168,7 @@ public class InputTestView: NSView, NSTextInputClient {
|
||||
return NSRange(location: self.text.characters.count, length: markedText.characters.count)
|
||||
}
|
||||
|
||||
return NSRange(location: NSNotFound, length: 0)
|
||||
return NSRange(location: NSNotFound, length: 1)
|
||||
}
|
||||
|
||||
/* Returns whether or not the receiver has marked text.
|
||||
@ -183,7 +183,7 @@ public class InputTestView: NSView, NSTextInputClient {
|
||||
*/
|
||||
public func attributedSubstringForProposedRange(aRange: NSRange, actualRange: NSRangePointer) -> NSAttributedString? {
|
||||
Swift.print("\(#function): \(aRange), \(actualRange)")
|
||||
return NSAttributedString(string: "t")
|
||||
return NSAttributedString(string: "하")
|
||||
}
|
||||
|
||||
/* Returns an array of attribute names recognized by the receiver.
|
||||
|
@ -69,7 +69,10 @@ typedef struct {
|
||||
/**
|
||||
* Draw string at the current cursor which was set by a previous cursorGotoRow:column callback.
|
||||
*/
|
||||
- (void)put:(NSString *)string;
|
||||
- (void)put:(NSString *_Nonnull)string;
|
||||
|
||||
- (void)putMarkedText:(NSString *_Nonnull)markedText;
|
||||
- (void)unmarkRow:(int)row column:(int)column;
|
||||
|
||||
- (void)bell;
|
||||
- (void)visualBell;
|
||||
@ -90,8 +93,8 @@ typedef struct {
|
||||
*/
|
||||
- (void)updateSpecial:(int)sp;
|
||||
- (void)suspend;
|
||||
- (void)setTitle:(NSString *)title;
|
||||
- (void)setIcon:(NSString *)icon;
|
||||
- (void)setTitle:(NSString *_Nonnull)title;
|
||||
- (void)setIcon:(NSString *_Nonnull)icon;
|
||||
|
||||
/**
|
||||
* NeoVim has been stopped.
|
||||
|
@ -5,6 +5,41 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
enum Mode {
|
||||
/*
|
||||
#define NORMAL 0x01 /* Normal mode, command expected */
|
||||
#define VISUAL 0x02 /* Visual mode - use get_real_state() */
|
||||
#define OP_PENDING 0x04 /* Normal mode, operator is pending - use
|
||||
get_real_state() */
|
||||
#define CMDLINE 0x08 /* Editing command line */
|
||||
#define INSERT 0x10 /* Insert mode */
|
||||
#define LANGMAP 0x20 /* Language mapping, can be combined with
|
||||
INSERT and CMDLINE */
|
||||
|
||||
#define REPLACE_FLAG 0x40 /* Replace mode flag */
|
||||
#define REPLACE (REPLACE_FLAG + INSERT)
|
||||
# define VREPLACE_FLAG 0x80 /* Virtual-replace mode flag */
|
||||
# define VREPLACE (REPLACE_FLAG + VREPLACE_FLAG + INSERT)
|
||||
#define LREPLACE (REPLACE_FLAG + LANGMAP)
|
||||
|
||||
#define NORMAL_BUSY (0x100 + NORMAL) /* Normal mode, busy with a command */
|
||||
#define HITRETURN (0x200 + NORMAL) /* waiting for return or command */
|
||||
#define ASKMORE 0x300 /* Asking if you want --more-- */
|
||||
#define SETWSIZE 0x400 /* window size has changed */
|
||||
#define ABBREV 0x500 /* abbreviation instead of mapping */
|
||||
#define EXTERNCMD 0x600 /* executing an external command */
|
||||
#define SHOWMATCH (0x700 + INSERT) /* show matching paren */
|
||||
#define CONFIRM 0x800 /* ":confirm" prompt */
|
||||
#define SELECTMODE 0x1000 /* Select mode, only for mappings */
|
||||
#define TERM_FOCUS 0x2000 // Terminal focus mode
|
||||
|
||||
// all mode bits used for mapping
|
||||
#define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS)
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/// Contiguous piece of cells of a row that has the same attributes.
|
||||
private struct RowRun: CustomStringConvertible {
|
||||
|
||||
@ -76,13 +111,21 @@ public class NeoVimView: NSView {
|
||||
|
||||
let dirtyRects = self.rectsBeingDrawn()
|
||||
|
||||
// Swift.print(self.grid)
|
||||
|
||||
self.rowRunIntersecting(rects: dirtyRects).forEach { rowFrag in
|
||||
self.drawBackground(positions: rowFrag.range.map { self.positionOnView(rowFrag.row, column: $0) },
|
||||
background: rowFrag.attrs.background)
|
||||
|
||||
let positions = rowFrag.range
|
||||
// filter out the put(0, 0)s (after a wide character)
|
||||
.filter { self.grid.cells[rowFrag.row][$0].string.characters.count > 0 }
|
||||
.map { self.positionOnView(rowFrag.row, column: $0) }
|
||||
|
||||
self.drawBackground(positions: positions, background: rowFrag.attrs.background)
|
||||
// self.drawBackground(positions: positions, background: rowFrag.attrs.background)
|
||||
if positions.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let string = self.grid.cells[rowFrag.row][rowFrag.range].reduce("") { $0 + $1.string }
|
||||
let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + self.descent + self.leading) }
|
||||
@ -163,6 +206,14 @@ public class NeoVimView: NSView {
|
||||
return CGRect(x: left * self.cellSize.width, y: (CGFloat(self.grid.size.height) - bottom) * self.cellSize.height,
|
||||
width: width * self.cellSize.width, height: height * self.cellSize.height)
|
||||
}
|
||||
|
||||
func vimNamedKeys(string: String) -> String {
|
||||
return "<\(string)>"
|
||||
}
|
||||
|
||||
func vimPlainString(string: String) -> String {
|
||||
return string.stringByReplacingOccurrencesOfString("<", withString: self.vimNamedKeys("lt"))
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
|
@ -22,37 +22,24 @@ extension NeoVimView: NSTextInputClient {
|
||||
let chars = event.characters!
|
||||
let charsIgnoringModifiers = shift || capslock ? event.charactersIgnoringModifiers!.lowercaseString
|
||||
: event.charactersIgnoringModifiers!
|
||||
|
||||
|
||||
let vimModifiers = self.vimModifierFlags(modifierFlags)
|
||||
if vimModifiers.characters.count > 0 {
|
||||
self.xpc.vimInput(self.vimWrapNamedKeys(vimModifiers + charsIgnoringModifiers))
|
||||
self.xpc.vimInput(self.vimNamedKeys(vimModifiers + charsIgnoringModifiers))
|
||||
} else {
|
||||
self.xpc.vimInput(self.vimStringFromPlainString(chars))
|
||||
self.xpc.vimInput(self.vimPlainString(chars))
|
||||
}
|
||||
|
||||
self.keyDownDone = true
|
||||
}
|
||||
|
||||
private func vimWrapNamedKeys(string: String) -> String {
|
||||
return "<\(string)>"
|
||||
}
|
||||
|
||||
private func vimStringFromPlainString(string: String) -> String {
|
||||
return string.stringByReplacingOccurrencesOfString("<", withString: self.vimWrapNamedKeys("lt"))
|
||||
}
|
||||
|
||||
private func vimModifierFlags(modifierFlags: NSEventModifierFlags) -> String {
|
||||
var result = ""
|
||||
|
||||
let shift = modifierFlags.contains(.ShiftKeyMask)
|
||||
let control = modifierFlags.contains(.ControlKeyMask)
|
||||
let option = modifierFlags.contains(.AlternateKeyMask)
|
||||
let command = modifierFlags.contains(.CommandKeyMask)
|
||||
|
||||
if shift {
|
||||
result += "S-"
|
||||
}
|
||||
|
||||
|
||||
if control {
|
||||
result += "C-"
|
||||
}
|
||||
@ -71,21 +58,48 @@ extension NeoVimView: NSTextInputClient {
|
||||
public func insertText(aString: AnyObject, replacementRange: NSRange) {
|
||||
Swift.print("\(#function): \(aString), \(replacementRange)")
|
||||
|
||||
self.xpc.vimInput(self.vimStringFromPlainString(String(aString)))
|
||||
switch aString {
|
||||
case let string as String:
|
||||
self.xpc.vimInput(self.vimPlainString(string))
|
||||
case let attributedString as NSAttributedString:
|
||||
self.xpc.vimInput(self.vimPlainString(attributedString.string))
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
self.markedText = nil
|
||||
self.keyDownDone = true
|
||||
}
|
||||
|
||||
public override func doCommandBySelector(aSelector: Selector) {
|
||||
Swift.print("\(#function): \(aSelector)")
|
||||
|
||||
// TODO: handle when ㅎ -> delete
|
||||
|
||||
if self.respondsToSelector(aSelector) {
|
||||
Swift.print("\(#function): calling \(aSelector)")
|
||||
self.performSelector(aSelector, withObject: self)
|
||||
self.keyDownDone = true
|
||||
return
|
||||
}
|
||||
|
||||
Swift.print("\(#function): \(aSelector) not implemented, forwarding input to vim")
|
||||
self.keyDownDone = false
|
||||
}
|
||||
|
||||
public func setMarkedText(aString: AnyObject, selectedRange: NSRange, replacementRange: NSRange) {
|
||||
Swift.print("\(#function): \(aString), \(selectedRange), \(replacementRange)")
|
||||
self.markedText = String(aString)
|
||||
self.grid.putMarkedText(self.markedText!)
|
||||
self.setNeedsDisplayInRect(self.cellRect(self.grid.position.row, column: self.grid.position.column))
|
||||
|
||||
switch aString {
|
||||
case let string as String:
|
||||
self.markedText = string
|
||||
case let attributedString as NSAttributedString:
|
||||
self.markedText = attributedString.string
|
||||
default:
|
||||
self.markedText = String(aString) // should not occur
|
||||
}
|
||||
|
||||
self.xpc.vimInputMarkedText(self.markedText!)
|
||||
self.keyDownDone = true
|
||||
}
|
||||
|
||||
@ -117,7 +131,7 @@ extension NeoVimView: NSTextInputClient {
|
||||
|
||||
public func hasMarkedText() -> Bool {
|
||||
let result = self.markedText != nil
|
||||
Swift.print("\(#function): returning \(result)")
|
||||
// Swift.print("\(#function): returning \(result)")
|
||||
return result
|
||||
}
|
||||
|
||||
@ -150,12 +164,6 @@ extension NeoVimView: NSTextInputClient {
|
||||
}
|
||||
|
||||
/*
|
||||
public func moveForward(sender: AnyObject?)
|
||||
public func moveRight(sender: AnyObject?)
|
||||
public func moveBackward(sender: AnyObject?)
|
||||
public func moveLeft(sender: AnyObject?)
|
||||
public func moveUp(sender: AnyObject?)
|
||||
public func moveDown(sender: AnyObject?)
|
||||
public func moveWordForward(sender: AnyObject?)
|
||||
public func moveWordBackward(sender: AnyObject?)
|
||||
public func moveToBeginningOfLine(sender: AnyObject?)
|
||||
@ -198,14 +206,9 @@ extension NeoVimView: NSTextInputClient {
|
||||
public func moveToLeftEndOfLineAndModifySelection(sender: AnyObject?)
|
||||
public func moveToRightEndOfLineAndModifySelection(sender: AnyObject?)
|
||||
|
||||
public func scrollPageUp(sender: AnyObject?)
|
||||
public func scrollPageDown(sender: AnyObject?)
|
||||
public func scrollLineUp(sender: AnyObject?)
|
||||
public func scrollLineDown(sender: AnyObject?)
|
||||
|
||||
public func scrollToBeginningOfDocument(sender: AnyObject?)
|
||||
public func scrollToEndOfDocument(sender: AnyObject?)
|
||||
|
||||
public func transpose(sender: AnyObject?)
|
||||
public func transposeWords(sender: AnyObject?)
|
||||
|
||||
@ -231,8 +234,6 @@ extension NeoVimView: NSTextInputClient {
|
||||
public func lowercaseWord(sender: AnyObject?)
|
||||
public func capitalizeWord(sender: AnyObject?)
|
||||
|
||||
public func deleteForward(sender: AnyObject?)
|
||||
public func deleteBackward(sender: AnyObject?)
|
||||
public func deleteBackwardByDecomposingPreviousCharacter(sender: AnyObject?)
|
||||
public func deleteWordForward(sender: AnyObject?)
|
||||
public func deleteWordBackward(sender: AnyObject?)
|
||||
|
58
SwiftNeoVim/NeoVimViewResponder.swift
Normal file
58
SwiftNeoVim/NeoVimViewResponder.swift
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
/// NeoVim's named keys can be found in keymap.c
|
||||
extension NeoVimView {
|
||||
|
||||
public override func moveForward(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("Right"))
|
||||
}
|
||||
|
||||
public override func moveRight(sender: AnyObject?) {
|
||||
self.moveForward(sender)
|
||||
}
|
||||
|
||||
public override func moveBackward(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("Left"))
|
||||
}
|
||||
|
||||
public override func moveLeft(sender: AnyObject?) {
|
||||
self.moveBackward(sender)
|
||||
}
|
||||
|
||||
public override func moveUp(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("Up"))
|
||||
}
|
||||
|
||||
public override func moveDown(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("Down"))
|
||||
}
|
||||
|
||||
public override func deleteForward(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("DEL"))
|
||||
}
|
||||
|
||||
public override func deleteBackward(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("BS"))
|
||||
}
|
||||
|
||||
public override func scrollPageUp(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("PageUp"))
|
||||
}
|
||||
|
||||
public override func scrollPageDown(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("PageDown"))
|
||||
}
|
||||
|
||||
public override func scrollToBeginningOfDocument(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("Home"))
|
||||
}
|
||||
|
||||
public override func scrollToEndOfDocument(sender: AnyObject?) {
|
||||
self.xpc.vimInput(self.vimNamedKeys("End"))
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ extension NeoVimView: NeoVimUiBridgeProtocol {
|
||||
}
|
||||
|
||||
public func modeChange(mode: Int32) {
|
||||
// Swift.print("### mode change to: \(String(format: "%04X", mode))")
|
||||
Swift.print("### mode change to: \(String(format: "%04X", mode))")
|
||||
}
|
||||
|
||||
public func setScrollRegionToTop(top: Int32, bottom: Int32, left: Int32, right: Int32) {
|
||||
@ -101,15 +101,42 @@ extension NeoVimView: NeoVimUiBridgeProtocol {
|
||||
|
||||
public func put(string: String) {
|
||||
DispatchUtils.gui {
|
||||
// Swift.print("\(#function): \(string)")
|
||||
let curPos = Position(row: self.grid.position.row, column: self.grid.position.column)
|
||||
self.grid.put(string)
|
||||
|
||||
// Swift.print("### put: \(curPos) -> '\(string)'")
|
||||
|
||||
self.setNeedsDisplayInRect(self.cellRect(curPos.row, column: curPos.column))
|
||||
if string.characters.count == 0 {
|
||||
self.setNeedsDisplayPosition(row: curPos.row, column: max(curPos.column - 1, 0))
|
||||
}
|
||||
self.setNeedsDisplayPosition(row: curPos.row, column: curPos.column)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func putMarkedText(markedText: String) {
|
||||
DispatchUtils.gui {
|
||||
// Swift.print("\(#function): \(markedText)")
|
||||
let curPos = Position(row: self.grid.position.row, column: self.grid.position.column)
|
||||
self.grid.putMarkedText(markedText)
|
||||
|
||||
if markedText.characters.count == 0 {
|
||||
self.setNeedsDisplayPosition(row: curPos.row, column: max(curPos.column - 1, 0))
|
||||
}
|
||||
self.setNeedsDisplayPosition(row: curPos.row, column: curPos.column)
|
||||
}
|
||||
}
|
||||
|
||||
private func setNeedsDisplayPosition(row row: Int, column: Int) {
|
||||
self.setNeedsDisplayInRect(self.cellRect(row, column: column))
|
||||
}
|
||||
|
||||
public func unmarkRow(row: Int32, column: Int32) {
|
||||
DispatchUtils.gui {
|
||||
// Swift.print("\(#function): \(row):\(column)")
|
||||
self.grid.unmarkCell(Position(row: Int(row), column: Int(column)))
|
||||
self.setNeedsDisplayPosition(row: Int(row), column: Int(column))
|
||||
}
|
||||
}
|
||||
|
||||
public func bell() {
|
||||
DispatchUtils.gui {
|
||||
NSBeep()
|
||||
|
@ -49,6 +49,7 @@
|
||||
4BEE79151D16D2100012EDAA /* DispatchUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE79141D16D2100012EDAA /* DispatchUtils.swift */; };
|
||||
4BEE79171D16D3800012EDAA /* CellAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE79161D16D3800012EDAA /* CellAttributes.swift */; };
|
||||
4BEF363D1D1EC045002A9898 /* NeoVimViewEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF363C1D1EC045002A9898 /* NeoVimViewEvents.swift */; };
|
||||
4BFF04841D21984100063EF3 /* NeoVimViewResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF04831D21984100063EF3 /* NeoVimViewResponder.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -150,6 +151,7 @@
|
||||
4BEE79141D16D2100012EDAA /* DispatchUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchUtils.swift; sourceTree = "<group>"; };
|
||||
4BEE79161D16D3800012EDAA /* CellAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellAttributes.swift; sourceTree = "<group>"; };
|
||||
4BEF363C1D1EC045002A9898 /* NeoVimViewEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimViewEvents.swift; sourceTree = "<group>"; };
|
||||
4BFF04831D21984100063EF3 /* NeoVimViewResponder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimViewResponder.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -310,6 +312,7 @@
|
||||
4BEF363C1D1EC045002A9898 /* NeoVimViewEvents.swift */,
|
||||
4BEE79111D16D0AC0012EDAA /* NeoVimViewUiBridge.swift */,
|
||||
4B401B191D046E0600D99EDC /* NeoVimViewDelegate.swift */,
|
||||
4BFF04831D21984100063EF3 /* NeoVimViewResponder.swift */,
|
||||
);
|
||||
name = NeoVimView;
|
||||
sourceTree = "<group>";
|
||||
@ -513,6 +516,7 @@
|
||||
4BCADE081D11ED12004DAD0F /* CocoaExtensions.swift in Sources */,
|
||||
4B2A2C0F1D0353E30074CE9A /* NeoVim.swift in Sources */,
|
||||
4B401B1A1D046E0600D99EDC /* NeoVimViewDelegate.swift in Sources */,
|
||||
4BFF04841D21984100063EF3 /* NeoVimViewResponder.swift in Sources */,
|
||||
1929B728262BAA14FC93F6AC /* NeoVimView.swift in Sources */,
|
||||
4BEF363D1D1EC045002A9898 /* NeoVimViewEvents.swift in Sources */,
|
||||
4BEE79121D16D0AC0012EDAA /* NeoVimViewUiBridge.swift in Sources */,
|
||||
|
Loading…
Reference in New Issue
Block a user