diff options
-rw-r--r-- | core/.buffer.luadoc | 2 | ||||
-rw-r--r-- | core/ui.lua | 4 | ||||
-rw-r--r-- | doc/03_UserInterface.md | 8 | ||||
-rw-r--r-- | doc/04_WorkingWithFiles.md | 12 | ||||
-rw-r--r-- | doc/08_Preferences.md | 4 | ||||
-rw-r--r-- | doc/images/splitviews.png | bin | 127798 -> 124605 bytes | |||
-rw-r--r-- | src/textadept.c | 120 |
7 files changed, 126 insertions, 24 deletions
diff --git a/core/.buffer.luadoc b/core/.buffer.luadoc index b1265c2e..087fd8a3 100644 --- a/core/.buffer.luadoc +++ b/core/.buffer.luadoc @@ -773,6 +773,8 @@ -- @field tab_indents (bool) -- Tabbing indents within indentation. -- The default value is `false`. +-- @field tab_label (string) +-- The buffer's tab label in the tab bar. -- @field tab_width (number) -- The number of space characters represented by a tab character. -- The default value is `8`. diff --git a/core/ui.lua b/core/ui.lua index 9a1bd5a4..03a0adcf 100644 --- a/core/ui.lua +++ b/core/ui.lua @@ -19,6 +19,9 @@ local ui = ui -- The text displayed in the buffer statusbar. -- @field maximized (bool) -- Whether or not Textadept's window is maximized. +-- @field tabs (bool) +-- Whether or not to display the tab bar when multiple buffers are open. +-- The default value is `true`. module('ui')]] local theme = package.searchpath(not CURSES and 'light' or 'term', @@ -262,6 +265,7 @@ local function set_title() local basename = buffer.filename and filename:match('[^/\\]+$') or filename ui.title = string.format('%s %s Textadept (%s)', basename, buffer.modify and '*' or '-', filename) + buffer.tab_label = basename..(buffer.modify and '*' or '') end -- Changes Textadept title to show the buffer as being "clean" or "dirty". diff --git a/doc/03_UserInterface.md b/doc/03_UserInterface.md index d51101ef..6f232d37 100644 --- a/doc/03_UserInterface.md +++ b/doc/03_UserInterface.md @@ -20,6 +20,14 @@ key bindings. [key preferences]: 08_Preferences.html#Key.Bindings [complete list]: api/textadept.keys.html#Key.Bindings +## Tab Bar + +The tab bar displays all of Textadept's open buffers, although it's only visible +when two or more buffers are open. While only the GUI version supports tabs, +Textadept's [buffer browser][] is always available and far more powerful. + +[buffer browser]: 04_WorkingWithFiles.html#Buffers + ## Editor View Most of your time spent with Textadept is in the editor view. The GUI version diff --git a/doc/04_WorkingWithFiles.md b/doc/04_WorkingWithFiles.md index 4a941a40..6f61d1da 100644 --- a/doc/04_WorkingWithFiles.md +++ b/doc/04_WorkingWithFiles.md @@ -2,14 +2,10 @@ ## Buffers -One of the first things notably absent when opening multiple files in Textadept -is the lack of a tab bar showing the open files. This design decision allowed -Textadept to support unlimited split views from the very beginning. Having a -single tab bar for multiple views causes confusion and having one tab bar per -view clutters the interface. - -Instead of having tabs, Textadept has the buffer browser. Press `Ctrl+B` (`⌘B` -on Mac OSX | `M-B` or `M-S-B` in curses) to open it. +Despite the fact that Textadept can display multiple buffers with a tab bar, the +buffer browser is usually a faster way to switch between buffers or quickly +assess which files are open. Press `Ctrl+B` (`⌘B` on Mac OSX | `M-B` or `M-S-B` +in curses) to display this browser.  diff --git a/doc/08_Preferences.md b/doc/08_Preferences.md index 36bab575..e4fbe4cf 100644 --- a/doc/08_Preferences.md +++ b/doc/08_Preferences.md @@ -37,6 +37,10 @@ your *~/.textadept/init.lua*: textadept.editing.TYPEOVER_CHARS = false textadept.editing.STRIP_TRAILING_SPACES = false +To always hide the tab bar: + + ui.tabs = false + Now suppose you want to load all of Textadept's default modules except for the menu. You cannot do this after-the-fact from *~/.textadept/init.lua*. Instead you need Textadept to load your own module rather than the default one. Copy the diff --git a/doc/images/splitviews.png b/doc/images/splitviews.png Binary files differindex 9108a995..d9a4060d 100644 --- a/doc/images/splitviews.png +++ b/doc/images/splitviews.png diff --git a/src/textadept.c b/src/textadept.c index 9d20e502..ba48149c 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -85,7 +85,7 @@ typedef GtkWidget Scintilla; static char *textadept_home; static Scintilla *focused_view, *dummy_view; #if GTK -static GtkWidget *window, *menubar, *statusbar[2]; +static GtkWidget *window, *menubar, *tabbar, *statusbar[2]; static GtkAccelGroup *accel; #if __APPLE__ static GtkosxApplication *osxapp; @@ -135,6 +135,7 @@ static lua_State *lua; static int quit; #endif static int initing, closing; +static int show_tabs = TRUE, tab_sync; static int tVOID = 0, tINT = 1, tLENGTH = 2, /*tPOSITION = 3, tCOLOUR = 4,*/ tBOOL = 5, tKEYMOD = 6, tSTRING = 7, tSTRINGRESULT = 8; static int lL_init(lua_State *, int, char **, int); @@ -582,9 +583,7 @@ static int lce_focus(lua_State *L) { setCDKEntryValue(command_entry, command_text); char *clipboard = get_clipboard(); GPasteBuffer = copyChar(clipboard); // set the CDK paste buffer - curs_set(1); - activateCDKEntry(command_entry, NULL); - curs_set(0); + curs_set(1), activateCDKEntry(command_entry, NULL), curs_set(0); // Set Scintilla clipboard with new CDK paste buffer if necessary. if (strcmp(clipboard, GPasteBuffer)) set_clipboard(GPasteBuffer); free(clipboard), free(GPasteBuffer), GPasteBuffer = NULL; @@ -785,13 +784,30 @@ static void l_pushdoc(lua_State *L, sptr_t doc) { } /** + * Synchronizes the tabbar after switching between Scintilla views or documents. + */ +static void sync_tabbar() { +#if GTK + lua_getfield(lua, LUA_REGISTRYINDEX, "ta_buffers"); + l_pushdoc(lua, SS(focused_view, SCI_GETDOCPOINTER, 0, 0)); + lua_gettable(lua, -2); + int i = lua_tointeger(lua, -1) - 1; + lua_pop(lua, 2); // buffers and index + GtkNotebook *tabs = GTK_NOTEBOOK(tabbar); + tab_sync = TRUE, gtk_notebook_set_current_page(tabs, i), tab_sync = FALSE; +//#elif CURSES + // TODO: tabs +#endif +} + +/** * Change focus to the given Scintilla view. * Generates 'view_before_switch' and 'view_after_switch' events. * @param view The Scintilla view to focus. */ static void goto_view(Scintilla *view) { if (!initing && !closing) lL_event(lua, "view_before_switch", -1); - l_setglobalview(lua, focused_view = view); + l_setglobalview(lua, focused_view = view), sync_tabbar(); l_setglobaldoc(lua, SS(view, SCI_GETDOCPOINTER, 0, 0)); if (!initing && !closing) lL_event(lua, "view_after_switch", -1); } @@ -942,7 +958,10 @@ static int lui__index(lua_State *L) { lua_newtable(L); lua_pushinteger(L, width), lua_rawseti(L, -2, 1); lua_pushinteger(L, height), lua_rawseti(L, -2, 2); - } else lua_rawget(L, 1); + } else if (strcmp(key, "tabs") == 0) + lua_pushboolean(L, show_tabs); + else + lua_rawget(L, 1); return 1; } @@ -1007,6 +1026,14 @@ static int lui__newindex(lua_State *L) { int w = l_rawgetiint(L, 3, 1), h = l_rawgetiint(L, 3, 2); if (w > 0 && h > 0) gtk_window_resize(GTK_WINDOW(window), w, h); #endif + } else if (strcmp(key, "tabs") == 0) { + show_tabs = lua_toboolean(L, 3); +#if GTK + gtk_widget_set_visible(tabbar, show_tabs && + gtk_notebook_get_n_pages(GTK_NOTEBOOK(tabbar)) > 1); +//#elif CURSES + // TODO: tabs +#endif } else lua_rawset(L, 1); return 0; } @@ -1076,9 +1103,9 @@ static void lL_gotodoc(lua_State *L, Scintilla *view, int n, int relative) { lua_rawgeti(L, -1, (n > 0) ? n : lua_rawlen(L, -1)); } sptr_t doc = l_todoc(L, -1); - SS(view, SCI_SETDOCPOINTER, 0, doc); + SS(view, SCI_SETDOCPOINTER, 0, doc), sync_tabbar(); l_setglobaldoc(L, doc); - lua_pop(L, 2); // buffer table and buffers + lua_pop(L, 2); // buffer and buffers } /** @@ -1109,7 +1136,16 @@ static void lL_removedoc(lua_State *L, sptr_t doc) { lua_pushvalue(L, -2), lua_rawseti(L, -5, lua_rawlen(L, -5) + 1); lua_pushvalue(L, -2), lua_settable(L, -5); lua_pushinteger(L, lua_rawlen(L, -3)), lua_settable(L, -4); - } else lua_pop(L, 1); // value + } else { +#if GTK + // Remove the tab from the tabbar. + gtk_notebook_remove_page(GTK_NOTEBOOK(tabbar), i - 1); + gtk_widget_set_visible(tabbar, show_tabs && lua_rawlen(L, -2) > 2); +//#elif CURSES + // TODO: tabs +#endif + lua_pop(L, 1); // value + } } lua_pop(L, 1); // buffers lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); @@ -1304,8 +1340,21 @@ static int lbuf_property(lua_State *L) { (!is_buffer || !newindex) ? 2 : 3); } else lua_pop(L, 2); // non-table, ta_properties - // If the key is a Scintilla constant, return its value. - if (!newindex) { + if (strcmp(lua_tostring(L, 2), "tab_label") == 0) { + // Return or update the buffer's tab label. + lua_getfield(L, 1, "tab_pointer"); +#if GTK + GtkNotebook *tabs = GTK_NOTEBOOK(tabbar); + GtkWidget *tab = (GtkWidget *)lua_touserdata(L, -1); + lua_pushstring(L, gtk_notebook_get_tab_label_text(tabs, tab)); + if (newindex) + gtk_notebook_set_tab_label_text(tabs, tab, luaL_checkstring(L, 3)); +//#elif CURSES + // TODO: tabs +#endif + return !newindex ? 1 : 0; + } else if (!newindex) { + // If the key is a Scintilla constant, return its value. lua_getfield(L, LUA_REGISTRYINDEX, "ta_constants"); lua_pushvalue(L, 2), lua_gettable(L, -2); if (lua_isnumber(L, -1)) return 1; @@ -1325,6 +1374,12 @@ static void lL_adddoc(lua_State *L, sptr_t doc) { lua_newtable(L); lua_pushlightuserdata(L, (sptr_t *)doc); // TODO: can this fail? lua_pushvalue(L, -1), lua_setfield(L, -3, "doc_pointer"); +#if GTK + GtkWidget *tab = gtk_vbox_new(FALSE, 0); // placeholder in GtkNotebook + lua_pushlightuserdata(L, tab), lua_setfield(L, -3, "tab_pointer"); +//#elif CURSES + // TODO: tabs +#endif l_setcfunction(L, -2, "delete", lbuffer_delete); l_setcfunction(L, -2, "new", lbuffer_new); l_setcfunction(L, -2, "text_range", lbuffer_text_range); @@ -1354,6 +1409,20 @@ static void new_buffer(sptr_t doc) { lL_adddoc(lua, doc); SS(focused_view, SCI_ADDREFDOCUMENT, 0, doc); } +#if GTK + // Add a tab to the tabbar. + l_pushdoc(lua, SS(focused_view, SCI_GETDOCPOINTER, 0, 0)); + lua_getfield(lua, -1, "tab_pointer"); + GtkWidget *tab = (GtkWidget *)lua_touserdata(lua, -1); + 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_current_page(GTK_NOTEBOOK(tabbar), i); + tab_sync = FALSE; + lua_pop(lua, 2); // buffer and tab_pointer +//#elif CURSES + // TODO: tabs +#endif l_setglobaldoc(lua, doc); if (!initing) lL_event(lua, "buffer_new", -1); } @@ -1566,6 +1635,7 @@ static int lL_init(lua_State *L, int argc, char **argv, int reinit) { #endif #if CURSES lua_pushboolean(L, 1), lua_setglobal(L, "CURSES"); + show_tabs = 0; // TODO: tabs #endif const char *charset = NULL; #if GTK @@ -1758,6 +1828,19 @@ static void w_quit_osx(GtkosxApplication*_, void*__) { gtk_main_quit(); } #endif + +/** + * Signal for switching buffer tabs. + * When triggered by the user (i.e. not synchronizing the tabbar), switches to + * the specified buffer. + * Generates 'buffer_before_switch' and 'buffer_after_switch' events. + */ +static void t_tabchange(GtkNotebook*_, void*__, int page_num, void*___) { + if (tab_sync) return; + lL_event(lua, "buffer_before_switch", -1); + lL_gotodoc(lua, focused_view, page_num + 1, FALSE); + lL_event(lua, "buffer_after_switch", -1); +} #endif // if GTK /** @@ -1916,7 +1999,7 @@ static void split_view(Scintilla *view, int vertical) { SS(view2, SCI_SETSEL, anchor, current_pos); int new_first_line = SS(view2, SCI_GETFIRSTVISIBLELINE, 0, 0); SS(view2, SCI_LINESCROLL, first_line - new_first_line, 0); -#elif CURSES +//#elif CURSES // TODO: split. #endif } @@ -1963,7 +2046,7 @@ static int lview__newindex(lua_State *L) { int size = luaL_checkinteger(L, 3); if (size < 0) size = 0; if (GTK_IS_PANED(pane)) gtk_paned_set_position(GTK_PANED(pane), size); -#elif CURSES +//#elif CURSES // TODO: set size. #endif } else lua_rawset(L, 1); @@ -2192,6 +2275,12 @@ static void new_window() { menubar = gtk_menu_bar_new(); gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + tabbar = gtk_notebook_new(); + signal(tabbar, "switch-page", t_tabchange); + gtk_notebook_set_scrollable(GTK_NOTEBOOK(tabbar), TRUE); + gtk_box_pack_start(GTK_BOX(vbox), tabbar, FALSE, FALSE, 0); + gtk_widget_set_can_focus(tabbar, FALSE); + GtkWidget *hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); @@ -2231,9 +2320,8 @@ static void new_window() { gtk_box_pack_start(GTK_BOX(hboxs), statusbar[1], FALSE, FALSE, 0); gtk_widget_show_all(window); - gtk_widget_hide(menubar); // hide initially - gtk_widget_hide(findbox); // hide initially - gtk_widget_hide(command_entry); // hide initially + gtk_widget_hide(menubar), gtk_widget_hide(tabbar); // hide initially + gtk_widget_hide(findbox), gtk_widget_hide(command_entry); // hide initially dummy_view = scintilla_new(); #elif CURSES |