From 386eac2063bdda79279bbdea2fffcd7fc0ff682a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Andr=C3=A9=20Tanner?= Date: Wed, 12 Jul 2017 19:05:03 +0200 Subject: [PATCH] vis-lua: make selection first class primitives in Lua API --- lua/plugins/complete-filename.lua | 12 +- lua/plugins/complete-word.lua | 6 +- lua/plugins/number-inc-dec.lua | 6 +- lua/vis-std.lua | 12 +- main.c | 7 +- sam.c | 6 +- test | 2 +- view.c | 4 +- view.h | 2 +- vis-lua.c | 194 +++++++++++++++++------------- vis-modes.c | 4 +- vis.c | 2 +- 12 files changed, 142 insertions(+), 115 deletions(-) diff --git a/lua/plugins/complete-filename.lua b/lua/plugins/complete-filename.lua index 3648efe81..cb5b36102 100644 --- a/lua/plugins/complete-filename.lua +++ b/lua/plugins/complete-filename.lua @@ -1,9 +1,9 @@ --- complete file path at primary cursor location using vis-complete(1) +-- complete file path at primary selection location using vis-complete(1) vis:map(vis.modes.INSERT, "", function() local win = vis.win local file = win.file - local pos = win.cursor.pos + local pos = win.selection.pos if not pos then return end -- TODO do something clever here local range = file:text_object_word(pos > 0 and pos-1 or pos); @@ -19,15 +19,15 @@ vis:map(vis.modes.INSERT, "", function() return end file:insert(pos, out) - win.cursor.pos = pos + #out + win.selection.pos = pos + #out end, "Complete file path") --- complete file path at primary cursor location using vis-open(1) +-- complete file path at primary selection location using vis-open(1) vis:map(vis.modes.INSERT, "", function() local win = vis.win local file = win.file - local pos = win.cursor.pos + local pos = win.selection.pos if not pos then return end local range = file:text_object_word(pos > 0 and pos-1 or pos); if not range then return end @@ -48,5 +48,5 @@ vis:map(vis.modes.INSERT, "", function() out = out:gsub("\n$", "") file:delete(range) file:insert(range.start, out) - win.cursor.pos = range.start + #out + win.selection.pos = range.start + #out end, "Complete file name") diff --git a/lua/plugins/complete-word.lua b/lua/plugins/complete-word.lua index 68ce385d0..41d112c12 100644 --- a/lua/plugins/complete-word.lua +++ b/lua/plugins/complete-word.lua @@ -1,9 +1,9 @@ --- complete word at primary cursor location using vis-complete(1) +-- complete word at primary selection location using vis-complete(1) vis:map(vis.modes.INSERT, "", function() local win = vis.win local file = win.file - local pos = win.cursor.pos + local pos = win.selection.pos if not pos then return end local range = file:text_object_word(pos > 0 and pos-1 or pos); if not range then return end @@ -18,5 +18,5 @@ vis:map(vis.modes.INSERT, "", function() return end file:insert(pos, out) - win.cursor.pos = pos + #out + win.selection.pos = pos + #out end, "Complete word in file") diff --git a/lua/plugins/number-inc-dec.lua b/lua/plugins/number-inc-dec.lua index e1142efc7..26a88257a 100644 --- a/lua/plugins/number-inc-dec.lua +++ b/lua/plugins/number-inc-dec.lua @@ -15,8 +15,8 @@ local change = function(delta) if not count then count = 1 end vis.count = nil -- reset count, otherwise it affects next motion - for cursor in win:cursors_iterator() do - local pos = cursor.pos + for selection in win:selections_iterator() do + local pos = selection.pos if not pos then goto continue end local word = file:text_object_word(pos); if not word then goto continue end @@ -50,7 +50,7 @@ local change = function(delta) end file:delete(s, e - s) file:insert(s, number) - cursor.pos = s + selection.pos = s ::continue:: end end diff --git a/lua/vis-std.lua b/lua/vis-std.lua index e762d86f6..5f41b4df2 100644 --- a/lua/vis-std.lua +++ b/lua/vis-std.lua @@ -81,7 +81,7 @@ vis.events.subscribe(vis.events.WIN_STATUS, function(win) local left_parts = {} local right_parts = {} local file = win.file - local cursor = win.cursor + local selection = win.selection local mode = modes[vis.mode] if mode ~= '' and vis.win == win then @@ -91,18 +91,18 @@ vis.events.subscribe(vis.events.WIN_STATUS, function(win) table.insert(left_parts, (file.name or '[No Name]') .. (file.modified and ' [+]' or '') .. (vis.recording and ' @' or '')) - if #win.cursors > 1 then - table.insert(right_parts, cursor.number..'/'..#win.cursors) + if #win.selections > 1 then + table.insert(right_parts, selection.number..'/'..#win.selections) end local size = file.size - local pos = cursor.pos + local pos = selection.pos if not pos then pos = 0 end table.insert(right_parts, (size == 0 and "0" or math.ceil(pos/size*100)).."%") if not win.large then - local col = cursor.col - table.insert(right_parts, cursor.line..', '..col) + local col = selection.col + table.insert(right_parts, selection.line..', '..col) if size > 33554432 or col > 65536 then win.large = true end diff --git a/main.c b/main.c index c911c6921..6c927150f 100644 --- a/main.c +++ b/main.c @@ -1345,8 +1345,7 @@ static const char *selections_new(Vis *vis, const char *keys, const Arg *arg) { } if (sel_new) { view_selections_primary_set(sel_new); - if (anchored) - view_selections_anchor(sel_new); + view_selections_anchor(sel_new, anchored); } } vis_count_set(vis, VIS_COUNT_UNKNOWN); @@ -1452,7 +1451,7 @@ static const char *selections_match_next(Vis *vis, const char *keys, const Arg * size_t pos = text_char_prev(txt, word.end); if ((s = view_selections_new(view, pos))) { view_selections_set(s, &word); - view_selections_anchor(s); + view_selections_anchor(s, true); view_selections_primary_set(s); goto out; } @@ -1465,7 +1464,7 @@ static const char *selections_match_next(Vis *vis, const char *keys, const Arg * size_t pos = text_char_prev(txt, word.end); if ((s = view_selections_new(view, pos))) { view_selections_set(s, &word); - view_selections_anchor(s); + view_selections_anchor(s, true); view_selections_primary_set(s); } diff --git a/sam.c b/sam.c index 7bd4c5e90..a0a48ec13 100644 --- a/sam.c +++ b/sam.c @@ -1224,7 +1224,7 @@ enum SamError sam_cmd(Vis *vis, const char *s) { if (c->cursor) { if (visual) { view_selections_set(c->cursor, &sel); - view_selections_anchor(c->cursor); + view_selections_anchor(c->cursor, true); } else { if (memchr(c->data, '\n', c->len)) view_cursors_to(c->cursor, sel.start); @@ -1235,7 +1235,7 @@ enum SamError sam_cmd(Vis *vis, const char *s) { Selection *cursor = view_selections_new(c->win->view, sel.start); if (cursor) { view_selections_set(cursor, &sel); - view_selections_anchor(cursor); + view_selections_anchor(cursor, true); } } } @@ -1512,7 +1512,7 @@ static bool cmd_print(Vis *vis, Win *win, Command *cmd, const char *argv[], Sele return false; if (range->start != range->end) { view_selections_set(sel, range); - view_selections_anchor(sel); + view_selections_anchor(sel, true); } else { view_cursors_to(sel, range->start); view_selection_clear(sel); diff --git a/test b/test index e61362b8d..59be906c4 160000 --- a/test +++ b/test @@ -1 +1 @@ -Subproject commit e61362b8d8c461cc6cf3f969265152c77bdcad2c +Subproject commit 59be906c4fd4091016fa533c61f09d338e10da23 diff --git a/view.c b/view.c index 557a75827..fafabc5a6 100644 --- a/view.c +++ b/view.c @@ -1161,8 +1161,8 @@ void view_cursors_place(Selection *s, size_t line, size_t col) { view_cursors_to(s, pos); } -void view_selections_anchor(Selection *s) { - s->anchored = true; +void view_selections_anchor(Selection *s, bool anchored) { + s->anchored = anchored; } void view_selection_clear(Selection *s) { diff --git a/view.h b/view.h index f676f34d3..1da8a90ee 100644 --- a/view.h +++ b/view.h @@ -228,7 +228,7 @@ void view_selections_flip(Selection*); * Anchor selection. * Further updates will only update the cursor, the anchor will remain fixed. */ -void view_selections_anchor(Selection*); +void view_selections_anchor(Selection*, bool anchored); /** Check whether selection is anchored. */ bool view_selections_anchored(Selection*); /** Get position of selection cursor. */ diff --git a/vis-lua.c b/vis-lua.c index a87b177e9..3134b3c5f 100644 --- a/vis-lua.c +++ b/vis-lua.c @@ -36,8 +36,8 @@ #define VIS_LUA_TYPE_MARK "mark" #define VIS_LUA_TYPE_MARKS "marks" #define VIS_LUA_TYPE_WINDOW "window" -#define VIS_LUA_TYPE_CURSOR "cursor" -#define VIS_LUA_TYPE_CURSORS "cursors" +#define VIS_LUA_TYPE_SELECTION "selection" +#define VIS_LUA_TYPE_SELECTIONS "selections" #define VIS_LUA_TYPE_UI "ui" #define VIS_LUA_TYPE_REGISTERS "registers" #define VIS_LUA_TYPE_KEYACTION "keyaction" @@ -79,12 +79,12 @@ static void window_status_update(Vis *vis, Win *win) { vis_macro_recording(vis) ? " @": ""); left_count++; - int cursor_count = view_selections_count(view); - if (cursor_count > 1) { + int sel_count = view_selections_count(view); + if (sel_count > 1) { Selection *s = view_selections_primary_get(view); - int cursor_number = view_selections_number(s) + 1; + int sel_number = view_selections_number(s) + 1; snprintf(right_parts[right_count], sizeof(right_parts[right_count])-1, - "%d/%d", cursor_number, cursor_count); + "%d/%d", sel_number, sel_count); right_count++; } @@ -1207,7 +1207,7 @@ static bool command_lua(Vis *vis, Win *win, void *data, bool force, const char * return false; if (!sel) sel = view_selections_primary_get(win->view); - if (!obj_lightref_new(L, sel, VIS_LUA_TYPE_CURSOR)) + if (!obj_lightref_new(L, sel, VIS_LUA_TYPE_SELECTION)) return false; pushrange(L, range); if (pcall(vis, L, 5, 1) != 0) @@ -1224,13 +1224,13 @@ static bool command_lua(Vis *vis, Win *win, void *data, bool force, const char * * @tparam[opt] string help the single line help text as displayed in `:help` * @treturn bool whether the command has been successfully registered * @usage - * vis:command_register("foo", function(argv, force, win, cursor, range) + * vis:command_register("foo", function(argv, force, win, selection, range) * for i,arg in ipairs(argv) do * print(i..": "..arg) * end * print("was command forced with ! "..(force and "yes" or "no")) * print(win.file.name) - * print(cursor.pos) + * print(selection.pos) * print(range ~= nil and ('['..range.start..', '..range.finish..']') or "invalid range") * return true; * end) @@ -1554,12 +1554,12 @@ static const struct luaL_Reg registers_funcs[] = { * @tfield File file */ /*** - * The primary cursor of this window. - * @tfield Cursor cursor + * The primary selection of this window. + * @tfield Selection selection */ /*** - * The cursors of this window. - * @tfield Array(Cursor) cursors + * The selections of this window. + * @tfield Array(Selection) selections */ static int window_index(lua_State *L) { Win *win = obj_ref_check(L, 1, VIS_LUA_TYPE_WINDOW); @@ -1588,14 +1588,14 @@ static int window_index(lua_State *L) { return 1; } - if (strcmp(key, "cursor") == 0) { + if (strcmp(key, "selection") == 0) { Selection *sel = view_selections_primary_get(win->view); - obj_lightref_new(L, sel, VIS_LUA_TYPE_CURSOR); + obj_lightref_new(L, sel, VIS_LUA_TYPE_SELECTION); return 1; } - if (strcmp(key, "cursors") == 0) { - obj_ref_new(L, win->view, VIS_LUA_TYPE_CURSORS); + if (strcmp(key, "selections") == 0) { + obj_ref_new(L, win->view, VIS_LUA_TYPE_SELECTIONS); return 1; } } @@ -1603,11 +1603,11 @@ static int window_index(lua_State *L) { return index_common(L); } -static int window_cursors_iterator_next(lua_State *L) { +static int window_selections_iterator_next(lua_State *L) { Selection **handle = lua_touserdata(L, lua_upvalueindex(1)); if (!*handle) return 0; - Selection *sel = obj_lightref_new(L, *handle, VIS_LUA_TYPE_CURSOR); + Selection *sel = obj_lightref_new(L, *handle, VIS_LUA_TYPE_SELECTION); if (!sel) return 0; *handle = view_selections_next(sel); @@ -1615,15 +1615,15 @@ static int window_cursors_iterator_next(lua_State *L) { } /*** - * Create an iterator over all cursors of this window. - * @function cursors_iterator + * Create an iterator over all selections of this window. + * @function selections_iterator * @return the new iterator */ -static int window_cursors_iterator(lua_State *L) { +static int window_selections_iterator(lua_State *L) { Win *win = obj_ref_check(L, 1, VIS_LUA_TYPE_WINDOW); Selection **handle = lua_newuserdata(L, sizeof *handle); *handle = view_selections(win->view); - lua_pushcclosure(L, window_cursors_iterator_next, 1); + lua_pushcclosure(L, window_selections_iterator_next, 1); return 1; } @@ -1729,7 +1729,7 @@ static int window_draw(lua_State *L) { static const struct luaL_Reg window_funcs[] = { { "__index", window_index }, { "__newindex", newindex_common }, - { "cursors_iterator", window_cursors_iterator }, + { "selections_iterator", window_selections_iterator }, { "map", window_map }, { "unmap", window_unmap }, { "style_define", window_style_define }, @@ -1739,15 +1739,15 @@ static const struct luaL_Reg window_funcs[] = { { NULL, NULL }, }; -static int window_cursors_index(lua_State *L) { - View *view = obj_ref_check(L, 1, VIS_LUA_TYPE_CURSORS); +static int window_selections_index(lua_State *L) { + View *view = obj_ref_check(L, 1, VIS_LUA_TYPE_SELECTIONS); size_t index = luaL_checkunsigned(L, 2); size_t count = view_selections_count(view); if (index == 0 || index > count) goto err; for (Selection *s = view_selections(view); s; s = view_selections_next(s)) { if (!--index) { - obj_lightref_new(L, s, VIS_LUA_TYPE_CURSOR); + obj_lightref_new(L, s, VIS_LUA_TYPE_SELECTION); return 1; } } @@ -1756,101 +1756,118 @@ static int window_cursors_index(lua_State *L) { return 1; } -static int window_cursors_len(lua_State *L) { - View *view = obj_ref_check(L, 1, VIS_LUA_TYPE_CURSORS); +static int window_selections_len(lua_State *L) { + View *view = obj_ref_check(L, 1, VIS_LUA_TYPE_SELECTIONS); lua_pushunsigned(L, view_selections_count(view)); return 1; } -static const struct luaL_Reg window_cursors_funcs[] = { - { "__index", window_cursors_index }, - { "__len", window_cursors_len }, +static const struct luaL_Reg window_selections_funcs[] = { + { "__index", window_selections_index }, + { "__len", window_selections_len }, { NULL, NULL }, }; /*** - * A cursor object. + * A selection object. + * + * A selection is a non-empty, directed range with two endpoints called + * *cursor* and *anchor*. A selection can be anchored in which case + * the anchor remains fixed while only the position of the cursor is + * adjusted. For non-anchored selections both endpoints are updated. A + * singleton selection covers one character on which both cursor and + * anchor reside. There always exists a primary selection which remains + * visible (i.e. changes to its position will adjust the viewport). + * + * The range covered by a selection is represented as an interval whose + * endpoints are absolute byte offsets from the start of the file. + * Valid addresses are within the closed interval `[0, file.size]`. + * + * Selections are currently implemented using character marks into + * the underlying persistent + * [text management data structure](https://github.com/martanne/vis/wiki/Text-management-using-a-piece-chain). * - * Cursors are represented as absolute byte offsets from the start of the file. - * Valid cursor placements are within the closed interval `[0, file.size]`. - * Cursors are currently implemented using character marks into the underlying - * persistent [text management data structure](https://github.com/martanne/vis/wiki/Text-management-using-a-piece-chain). * This has a few consequences you should be aware of: * - * - A cursor becomes invalid when the underlying text range it is referencing - * is deleted: + * - A selection becomes invalid when the delimiting boundaries of the underlying + * text it is referencing is deleted: * - * -- leaves cursor in an invalid state - * win.file:delete(win.cursor.pos, 1) - * assert(win.cursor.pos == nil) + * -- leaves selection in an invalid state + * win.file:delete(win.selection.pos, 1) + * assert(win.selection.pos == nil) * * Like a regular mark it will become valid again when the text is reverted * to the state before the deletion. * - * - Inserts after the cursor position (`> cursor.pos`) will not affect the - * cursor postion. + * - Inserts after the selection position (`> selection.pos`) will not affect the + * selection postion. * - * local pos = win.cursor.pos + * local pos = win.selection.pos * win.file:insert(pos+1, "-") - * assert(win.cursor.pos == pos) + * assert(win.selection.pos == pos) * - * - Non-cached inserts before the cursor position (`<= cursor.pos`) will - * affect the mark and adjust the cursor postion by the number of bytes + * - Non-cached inserts before the selection position (`<= selection.pos`) will + * affect the mark and adjust the selection postion by the number of bytes * which were inserted. * - * local pos = win.cursor.pos + * local pos = win.selection.pos * win.file:insert(pos, "-") - * assert(win.cursor.pos == pos+1) + * assert(win.selection.pos == pos+1) * - * - Cached inserts before the cursor position (`<= cursor.pos`) will - * not affect the cursor position because the underlying text is replaced + * - Cached inserts before the selection position (`<= selection.pos`) will + * not affect the selection position because the underlying text is replaced * inplace. * - * For these reasons it is generally recommended to update the cursor position + * For these reasons it is generally recommended to update the selection position * after a modification. The general procedure amounts to: * - * 1. Read out the current cursor position + * 1. Read out the current selection position * 2. Perform text modifications - * 3. Update the cursor postion + * 3. Update the selection postion * * This is what @{Vis:insert} and @{Vis:replace} do internally. * - * @type Cursor + * @type Selection * @usage * local data = "new text" - * local pos = win.cursor.pos + * local pos = win.selection.pos * win.file:insert(pos, data) - * win.cursor.pos = pos + #data + * win.selection.pos = pos + #data */ /*** * The zero based byte position in the file. * - * Might be `nil` if the cursor is in an invalid state. - * Setting this field will move the cursor to the given position. + * Might be `nil` if the selection is in an invalid state. + * Setting this field will move the cursor endpoint of the + * selection to the given position. * @tfield int pos */ /*** - * The 1-based line the cursor resides on. + * The 1-based line the cursor of this selection resides on. * * @tfield int line * @see to */ /*** - * The 1-based column position the cursor resides on. + * The 1-based column position the cursor of this selection resides on. * @tfield int col * @see to */ /*** - * The 1-based cursor index. + * The 1-based selection index. * @tfield int number */ /*** - * The selection associated with this cursor. - * @tfield Range selection the selection or `nil` if not in visual mode + * The range covered by this selection. + * @tfield Range range */ -static int window_cursor_index(lua_State *L) { - Selection *sel = obj_lightref_check(L, 1, VIS_LUA_TYPE_CURSOR); +/*** + * Whether this selection is anchored. + * @tfield bool anchored + */ +static int window_selection_index(lua_State *L) { + Selection *sel = obj_lightref_check(L, 1, VIS_LUA_TYPE_SELECTION); if (!sel) { lua_pushnil(L); return 1; @@ -1878,18 +1895,24 @@ static int window_cursor_index(lua_State *L) { return 1; } - if (strcmp(key, "selection") == 0) { + if (strcmp(key, "range") == 0) { Filerange range = view_selections_get(sel); pushrange(L, &range); return 1; } + + if (strcmp(key, "anchored") == 0) { + lua_pushboolean(L, view_selections_anchored(sel)); + return 1; + } + } return index_common(L); } -static int window_cursor_newindex(lua_State *L) { - Selection *sel = obj_lightref_check(L, 1, VIS_LUA_TYPE_CURSOR); +static int window_selection_newindex(lua_State *L) { + Selection *sel = obj_lightref_check(L, 1, VIS_LUA_TYPE_SELECTION); if (!sel) return 0; if (lua_isstring(L, 2)) { @@ -1900,28 +1923,33 @@ static int window_cursor_newindex(lua_State *L) { return 0; } - if (strcmp(key, "selection") == 0) { + if (strcmp(key, "range") == 0) { Filerange range = getrange(L, 3); if (text_range_valid(&range)) { view_selections_set(sel, &range); - view_selections_anchor(sel); + view_selections_anchor(sel, true); } else { view_selection_clear(sel); } return 0; } + + if (strcmp(key, "anchored") == 0) { + view_selections_anchor(sel, lua_toboolean(L, 3)); + return 0; + } } return newindex_common(L); } /*** - * Move cursor. + * Move cursor of selection. * @function to * @tparam int line the 1-based line number * @tparam int col the 1-based column number */ -static int window_cursor_to(lua_State *L) { - Selection *sel = obj_lightref_check(L, 1, VIS_LUA_TYPE_CURSOR); +static int window_selection_to(lua_State *L) { + Selection *sel = obj_lightref_check(L, 1, VIS_LUA_TYPE_SELECTION); if (sel) { size_t line = checkpos(L, 2); size_t col = checkpos(L, 3); @@ -1930,10 +1958,10 @@ static int window_cursor_to(lua_State *L) { return 0; } -static const struct luaL_Reg window_cursor_funcs[] = { - { "__index", window_cursor_index }, - { "__newindex", window_cursor_newindex }, - { "to", window_cursor_to }, +static const struct luaL_Reg window_selection_funcs[] = { + { "__index", window_selection_index }, + { "__newindex", window_selection_newindex }, + { "to", window_selection_to }, { NULL, NULL }, }; @@ -2686,10 +2714,10 @@ void vis_lua_init(Vis *vis) { lua_pushlightuserdata(L, vis); luaL_setfuncs(L, file_marks_funcs, 1); - obj_type_new(L, VIS_LUA_TYPE_CURSOR); - luaL_setfuncs(L, window_cursor_funcs, 0); - obj_type_new(L, VIS_LUA_TYPE_CURSORS); - luaL_setfuncs(L, window_cursors_funcs, 0); + obj_type_new(L, VIS_LUA_TYPE_SELECTION); + luaL_setfuncs(L, window_selection_funcs, 0); + obj_type_new(L, VIS_LUA_TYPE_SELECTIONS); + luaL_setfuncs(L, window_selections_funcs, 0); obj_type_new(L, VIS_LUA_TYPE_UI); luaL_setfuncs(L, ui_funcs, 0); diff --git a/vis-modes.c b/vis-modes.c index 4aa6949de..b00ecfee3 100644 --- a/vis-modes.c +++ b/vis-modes.c @@ -186,14 +186,14 @@ static void vis_mode_operator_input(Vis *vis, const char *str, size_t len) { static void vis_mode_visual_enter(Vis *vis, Mode *old) { if (!old->visual) { for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) - view_selections_anchor(s); + view_selections_anchor(s, true); } } static void vis_mode_visual_line_enter(Vis *vis, Mode *old) { if (!old->visual) { for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) - view_selections_anchor(s); + view_selections_anchor(s, true); } if (!vis->action.op) vis_motion(vis, VIS_MOVE_NOP); diff --git a/vis.c b/vis.c index c8229ddcc..58599cb78 100644 --- a/vis.c +++ b/vis.c @@ -949,7 +949,7 @@ void vis_do(Vis *vis) { c.range = text_range_linewise(txt, &c.range); if (vis->mode->visual) { view_selections_set(sel, &c.range); - view_selections_anchor(sel); + view_selections_anchor(sel, true); } if (a->op) {