From 092d5984e16f2d8f844dc88a417ed6f9defdf42c Mon Sep 17 00:00:00 2001 From: mitchell <70453897+orbitalquark@users.noreply.github.com> Date: Fri, 18 Mar 2022 21:55:42 -0400 Subject: Added `move_buffer()` function and made tabs rearrangeable via drag and drop. --- core/init.lua | 8 ++++++++ docs/api.md | 10 ++++++++++ modules/lua/ta_api | 1 + modules/lua/ta_tags | 1 + src/textadept.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++--- test/test.lua | 31 ++++++++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 3 deletions(-) diff --git a/core/init.lua b/core/init.lua index 4290c901..56b7519f 100644 --- a/core/init.lua +++ b/core/init.lua @@ -133,6 +133,14 @@ local view -- The functions below are Lua C functions. +--- +-- Moves the buffer at index *from* to index *to*, shifting other buffers as necessary. +-- @param from Index of the buffer to move. +-- @param to Index to move the buffer to. +-- @class function +-- @name move_buffer +local move_buffer + --- -- Emits a `QUIT` event, and unless any handler returns `false`, quits Textadept. -- @see events.QUIT diff --git a/docs/api.md b/docs/api.md index f2c7239c..2c98084c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -98,6 +98,16 @@ The path to the user's *~/.textadept/* directory, where all preferences and user ### Functions defined by `_G` + +#### `move_buffer`(*from, to*) + +Moves the buffer at index *from* to index *to*, shifting other buffers as necessary. + +Parameters: + +* *`from`*: Index of the buffer to move. +* *`to`*: Index to move the buffer to. + #### `quit`() diff --git a/modules/lua/ta_api b/modules/lua/ta_api index f3d6de54..000ca551 100644 --- a/modules/lua/ta_api +++ b/modules/lua/ta_api @@ -728,6 +728,7 @@ modify buffer.modify (bool, Read-only)\nWhether or not the buffer has unsaved ch modify_rule lexer.modify_rule(lexer, id, rule)\nReplaces in lexer *lexer* the existing rule identified by string *id* with pattern *rule*.\n@param lexer The lexer to modify.\n@param id The id associated with this rule.\n@param rule The LPeg pattern of the rule. mouse_dwell_time view.mouse_dwell_time (number)\nThe number of milliseconds the mouse must idle before generating a `DWELL_START` event. A\ntime of `view.TIME_FOREVER` will never generate one. mouse_selection_rectangular_switch view.mouse_selection_rectangular_switch (bool)\nWhether or not pressing `view.rectangular_selection_modifier` when selecting text\nnormally with the mouse turns on rectangular selection.\nThe default value is `false`. +move_buffer _G.move_buffer(from, to)\nMoves the buffer at index *from* to index *to*, shifting other buffers as necessary.\n@param from Index of the buffer to move.\n@param to Index to move the buffer to. move_caret_inside_view buffer.move_caret_inside_view(buffer)\nMoves the caret into view if it is not already, removing any selections.\n@param buffer A buffer. move_extends_selection buffer.move_extends_selection (bool, Read-only)\nWhether or not regular caret movement alters the selected text.\n`buffer.selection_mode` dictates this property. move_selected_lines_down buffer.move_selected_lines_down(buffer)\nShifts the selected lines down one line.\n@param buffer A buffer. diff --git a/modules/lua/ta_tags b/modules/lua/ta_tags index 7443c621..6a448c2e 100644 --- a/modules/lua/ta_tags +++ b/modules/lua/ta_tags @@ -730,6 +730,7 @@ modify _HOME/core/.buffer.luadoc /^module('buffer')$/;" F class:buffer modify_rule _HOME/lexers/lexer.lua /^function M.modify_rule(lexer, id, rule)$/;" f class:lexer mouse_dwell_time _HOME/core/.view.luadoc /^module('view')$/;" F class:view mouse_selection_rectangular_switch _HOME/core/.view.luadoc /^module('view')$/;" F class:view +move_buffer _HOME/core/init.lua /^local move_buffer$/;" f move_caret_inside_view _HOME/core/.buffer.luadoc /^function move_caret_inside_view(buffer) end$/;" f class:buffer move_extends_selection _HOME/core/.buffer.luadoc /^module('buffer')$/;" F class:buffer move_selected_lines_down _HOME/core/.buffer.luadoc /^function move_selected_lines_down(buffer) end$/;" f class:buffer diff --git a/src/textadept.c b/src/textadept.c index 022f4604..40e6c641 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -1038,7 +1038,7 @@ static void register_command_entry_doc() { static void remove_doc(lua_State *L, sptr_t doc) { lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); for (size_t i = 1; i <= lua_rawlen(L, -1); lua_pop(L, 1), i++) { - Scintilla *view = (lua_rawgeti(L, -1, i), lua_toview(L, -1)); // ^popped + Scintilla *view = (lua_rawgeti(L, -1, i), lua_toview(L, -1)); // popped on loop if (doc == SS(view, SCI_GETDOCPOINTER, 0, 0)) goto_doc(L, view, -1, true); } lua_pop(L, 1); // views @@ -1412,6 +1412,7 @@ static void new_buffer(sptr_t doc) { tab_sync = true; int i = gtk_notebook_append_page(GTK_NOTEBOOK(tabbar), tab, NULL); gtk_widget_show(tab), gtk_widget_set_visible(tabbar, show_tabs(i > 0)); + gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(tabbar), tab, true); gtk_notebook_set_current_page(GTK_NOTEBOOK(tabbar), i); tab_sync = false; lua_pop(lua, 2); // tab_pointer and buffer @@ -1423,6 +1424,42 @@ static void new_buffer(sptr_t doc) { if (!initing) emit(lua, "buffer_new", -1); } +/** + * Moves the buffer from the given index to another index, shifting other buffers as necessary. + * @param from Index of the buffer to move. + * @param to Index to move the buffer to. + * @reorder_tabs Flag indicating whether or not to reorder tabs in the GUI. This is `false` + * when responding to a GUI reordering event and `true` when calling from Lua. + */ +static void move_buffer(int from, int to, bool reorder_tabs) { + lua_getglobal(lua, "table"), lua_getfield(lua, -1, "insert"), lua_replace(lua, -2); + lua_getfield(lua, LUA_REGISTRYINDEX, "ta_buffers"), lua_pushinteger(lua, to); + lua_getglobal(lua, "table"), lua_getfield(lua, -1, "remove"), lua_replace(lua, -2); + lua_getfield(lua, LUA_REGISTRYINDEX, "ta_buffers"), lua_pushinteger(lua, from), + lua_call(lua, 2, 1); // table.remove(_BUFFERS, from) --> buf + lua_call(lua, 3, 0); // table.insert(_BUFFERS, to, buf) + lua_getfield(lua, LUA_REGISTRYINDEX, "ta_buffers"); + for (int i = 1; i <= lua_rawlen(lua, -1); i++) + lua_rawgeti(lua, -1, i), lua_pushinteger(lua, i), lua_settable(lua, -3); // t[buffer] = i + lua_pop(lua, 1); + if (!reorder_tabs) return; +#if GTK + gtk_notebook_reorder_child( + GTK_NOTEBOOK(tabbar), gtk_notebook_get_nth_page(GTK_NOTEBOOK(tabbar), from - 1), to - 1); +//#elif CURSES +// TODO: tabs +#endif +} + +/** `_G.move_buffer` Lua function. */ +static int move_buffer_lua(lua_State *L) { + lua_getfield(lua, LUA_REGISTRYINDEX, "ta_buffers"); + int from = luaL_checkinteger(L, 1), to = luaL_checkinteger(L, 2); + luaL_argcheck(L, from >= 1 && from <= lua_rawlen(L, -1), 1, "position out of bounds"); + luaL_argcheck(L, to >= 1 && to <= lua_rawlen(L, -1), 2, "position out of bounds"); + return (lua_pop(L, 1), move_buffer(luaL_checkinteger(L, 1), luaL_checkinteger(L, 2), true), 0); +} + /** `_G.quit()` Lua function. */ static int quit(lua_State *L) { #if GTK @@ -1575,6 +1612,7 @@ static bool init_lua(lua_State *L, int argc, char **argv, bool reinit) { set_metatable(L, -1, "ta_ui", ui_index, ui_newindex); lua_setglobal(L, "ui"); + lua_pushcfunction(L, move_buffer_lua), lua_setglobal(L, "move_buffer"); lua_pushcfunction(L, quit), lua_setglobal(L, "quit"); lua_pushcfunction(L, reset), lua_setglobal(L, "reset"); lua_pushcfunction(L, add_timeout), lua_setglobal(L, "timeout"); @@ -1785,8 +1823,8 @@ static void close_lua(lua_State *L) { closing = true; while (unsplit_view(focused_view)) {} lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - for (size_t i = 1; i <= lua_rawlen(L, -1); i++) - lua_rawgeti(L, -1, i), delete_buffer(lua_todoc(L, -1)), lua_pop(L, 1); + for (size_t i = 1; i <= lua_rawlen(L, -1); lua_pop(L, 1), i++) + lua_rawgeti(L, -1, i), delete_buffer(lua_todoc(L, -1)); // popped on loop lua_pop(L, 1); // buffers scintilla_delete(focused_view), scintilla_delete(dummy_view); scintilla_delete(command_entry); @@ -1844,6 +1882,16 @@ static void terminate(GtkosxApplication *_, void *L) { static void tab_changed(GtkNotebook *_, GtkWidget *__, int tab_num, void *L) { if (!tab_sync) emit(L, "tab_clicked", LUA_TNUMBER, tab_num + 1, LUA_TNUMBER, 1, -1); } + +/** Signal for reordering tabs. */ +static void tab_reordered(GtkNotebook *_, GtkWidget *tab, int to, void *L) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + for (size_t i = 1; i <= lua_rawlen(L, -1); lua_pop(L, 2), i++) + if (tab == (lua_rawgeti(L, -1, i), lua_getfield(L, -1, "tab_pointer"), lua_touserdata(L, -1))) { + lua_pop(L, 3), move_buffer(i, to + 1, false); + break; + } +} #endif // if GTK /** @@ -2281,6 +2329,7 @@ static void new_window() { tabbar = gtk_notebook_new(); g_signal_connect(tabbar, "switch-page", G_CALLBACK(tab_changed), lua); + g_signal_connect(tabbar, "page-reordered", G_CALLBACK(tab_reordered), lua); gtk_notebook_set_scrollable(GTK_NOTEBOOK(tabbar), true); gtk_widget_set_can_focus(tabbar, false); gtk_box_pack_start(GTK_BOX(vbox), tabbar, false, false, 0); diff --git a/test/test.lua b/test/test.lua index c3c7a695..ed8fd3f5 100644 --- a/test/test.lua +++ b/test/test.lua @@ -4021,6 +4021,37 @@ function test_ui_restore_view_state() buffer:close() end +function test_move_buffer() + local buffer1 = buffer.new() + buffer1:set_text('1') + local buffer2 = buffer.new() + buffer2:set_text('2') + local buffer3 = buffer.new() + buffer3:set_text('3') + local buffer4 = buffer.new() + buffer4:set_text('4') + move_buffer(_BUFFERS[buffer4], _BUFFERS[buffer1]) + assert(_BUFFERS[buffer4] < _BUFFERS[buffer1], 'buffer4 not before buffer1') + assert(_BUFFERS[buffer1] < _BUFFERS[buffer2], 'buffer1 not before buffer2') + assert(_BUFFERS[buffer2] < _BUFFERS[buffer3], 'buffer2 not before buffer3') + move_buffer(_BUFFERS[buffer2], _BUFFERS[buffer3]) + assert(_BUFFERS[buffer4] < _BUFFERS[buffer1], 'buffer4 not before buffer1') + assert(_BUFFERS[buffer1] < _BUFFERS[buffer3], 'buffer1 not before buffer3') + assert(_BUFFERS[buffer3] < _BUFFERS[buffer2], 'buffer3 not before buffer2') + + assert_raises(function() move_buffer('') end, 'number expected') + assert_raises(function() move_buffer(1) end, 'number expected') + assert_raises(function() move_buffer(1, true) end, 'number expected') + assert_raises(function() move_buffer(1, 10) end, 'out of bounds') + assert_raises(function() move_buffer(1, -1) end, 'out of bounds') + assert_raises(function() move_buffer(10, 1) end, 'out of bounds') + assert_raises(function() move_buffer(-1, 1) end, 'out of bounds') + buffer1:close(true) + buffer2:close(true) + buffer3:close(true) + buffer4:close(true) +end + function test_reset() local _persist _G.foo = 'bar' -- cgit v1.2.3