1
1
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:
Tae Won Ha 2016-06-27 19:04:27 +02:00
parent 10ed436bd6
commit 7358243b07
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
11 changed files with 467 additions and 181 deletions

View File

@ -15,6 +15,7 @@
- (void)probe;
- (void)vimInput:(NSString * _Nonnull)input;
- (void)vimInputMarkedText:(NSString *_Nonnull)markedText;
- (void)debugScreenLines;

View File

@ -15,7 +15,8 @@
- (void)probe;
- (void)vimInput:(NSString * _Nonnull)input;
- (void)vimInput:(NSString *_Nonnull)input;
- (void)vimInputMarkedText:(NSString *_Nonnull)markedText;
- (void)debugScreenLines;

View File

@ -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 {

View File

@ -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) {

View File

@ -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.

View File

@ -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.

View File

@ -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")

View File

@ -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?)

View 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"))
}
}

View File

@ -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()

View File

@ -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 */,