From e349843fc4083b9a34366fb21f68f7786ea5e5ea Mon Sep 17 00:00:00 2001 From: Nanozuki Crows Date: Tue, 30 Jul 2024 17:55:48 +0800 Subject: [PATCH 1/4] feat: try coding something --- lua/tabby/feature/tabwins.lua | 5 +++++ lua/tabby/feature/win_picker.lua | 11 +++++++++++ lua/tabby/tabline.lua | 10 +++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lua/tabby/feature/tabwins.lua b/lua/tabby/feature/tabwins.lua index 94884f1..d29754a 100644 --- a/lua/tabby/feature/tabwins.lua +++ b/lua/tabby/feature/tabwins.lua @@ -1,6 +1,7 @@ local tabwins = {} local api = require('tabby.module.api') +local win_picker = require('tabby.feature.win_picker') ---@class TabbyTab ---@field id number tabid @@ -48,6 +49,10 @@ function tabwins.new_tab(tabid, opt) return '' end end, + picker = function() + if win_picker.toggle_alt_draw then + end + end, } end diff --git a/lua/tabby/feature/win_picker.lua b/lua/tabby/feature/win_picker.lua index 4336b3a..aaf8882 100644 --- a/lua/tabby/feature/win_picker.lua +++ b/lua/tabby/feature/win_picker.lua @@ -24,4 +24,15 @@ function win_picker.select() end) end +win_picker.tabline_alt_draw = false + +function win_picker.toggle_alt_draw() + win_picker.tabline_alt_draw = not win_picker.tabline_alt_draw + vim.cmd.redrawtabline() + if win_picker.tabline_alt_draw then + local line_opt = require('tabby.tabline').cfg.opt -- TODO: ugly, refact this + local tabs = tabwins.new_tabs(line_opt or {}).tabs + end +end + return win_picker diff --git a/lua/tabby/tabline.lua b/lua/tabby/tabline.lua index 304ce53..34f1cdc 100644 --- a/lua/tabby/tabline.lua +++ b/lua/tabby/tabline.lua @@ -5,7 +5,7 @@ local win_picker = require('tabby.feature.win_picker') local tabline = {} -local module = { +tabline.cfg = { ---@type fun(line:TabbyLine):TabbyElement fn = nil, ---@type TabbyLineOption? @@ -13,10 +13,10 @@ local module = { } ---set tabline render function ----@param fn fun(line:TabbyLine):TabbyNode +---@param fn fun(line:TabbyLine):TabbyElement ---@param opt? TabbyLineOption function tabline.set(fn, opt) - module = { + tabline.cfg = { fn = fn, opt = opt, } @@ -47,8 +47,8 @@ end function tabline.render() tab_name.pre_render() - local line = lines.get_line(module.opt) - local element = module.fn(line) + local line = lines.get_line(tabline.cfg.opt) + local element = tabline.cfg.fn(line) if element.hl == nil or element.hl == '' then element.hl = 'TabLineFill' end From b7e0be464c6115a4c9c9ca505d1406e4a1779844 Mon Sep 17 00:00:00 2001 From: Nanozuki Crows Date: Wed, 31 Jul 2024 15:21:19 +0800 Subject: [PATCH 2/4] feat: complete most of all --- lua/tabby/feature/tab_jumper.lua | 74 ++++++++++++++++++++++++++++++++ lua/tabby/feature/tabwins.lua | 9 ++-- lua/tabby/feature/win_picker.lua | 11 ----- lua/tabby/tabline.lua | 8 +++- 4 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 lua/tabby/feature/tab_jumper.lua diff --git a/lua/tabby/feature/tab_jumper.lua b/lua/tabby/feature/tab_jumper.lua new file mode 100644 index 0000000..a3fa86b --- /dev/null +++ b/lua/tabby/feature/tab_jumper.lua @@ -0,0 +1,74 @@ +local tab_jumper = { + line = nil, + char_to_tabid = {}, ---@type table + tabid_to_char = {}, ---@type table + is_start = false, +} + +local alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + +---@param line TabbyLine +function tab_jumper.pre_render(line) + tab_jumper.line = line +end + +function tab_jumper.reset() + tab_jumper.char_to_tabid = {} + tab_jumper.tabid_to_char = {} +end + +function tab_jumper.build_indexes() + if tab_jumper.line == nil then + return + end + local tabs = tab_jumper.line.tabs().tabs + + -- use the first, not used char of tab's name + for _, tab in ipairs(tabs) do + local name = tab.name() + for i = 1, #name do + local char = name:sub(i, i):upper() + if tab_jumper.char_to_tabid[char] == nil then + tab_jumper.char_to_tabid[char] = tab.id + tab_jumper.tabid_to_char[tostring(tab.id)] = char + break + end + end + end + + -- then, for the remain tabs, use the first, not used char of alphabet + for _, tab in ipairs(tabs) do + if tab_jumper.tabid_to_char[tostring(tab.id)] == nil then + for i = 1, #alphabet do + local char = alphabet:sub(i, i) + if tab_jumper.char_to_tabid[char] == nil then + tab_jumper.char_to_tabid[char] = tab.id + tab_jumper.tabid_to_char[tostring(tab.id)] = char + break + end + end + end + end +end + +function tab_jumper.get_char(tabid) + local char = tab_jumper.tabid_to_char[tostring(tabid)] or '??' + return char +end + +function tab_jumper.start() + tab_jumper.build_indexes() + tab_jumper.is_start = true + + vim.cmd.redrawtabline() + local c = vim.fn.getcharstr():upper() + tab_jumper.is_start = false + if tab_jumper.char_to_tabid[c] ~= nil then + vim.api.nvim_set_current_tabpage(tab_jumper.char_to_tabid[c]) + else + tab_jumper.reset() + vim.cmd.redrawtabline() + end +end + +return tab_jumper diff --git a/lua/tabby/feature/tabwins.lua b/lua/tabby/feature/tabwins.lua index d29754a..47972c2 100644 --- a/lua/tabby/feature/tabwins.lua +++ b/lua/tabby/feature/tabwins.lua @@ -1,7 +1,7 @@ local tabwins = {} local api = require('tabby.module.api') -local win_picker = require('tabby.feature.win_picker') +local tab_jumper = require('tabby.feature.tab_jumper') ---@class TabbyTab ---@field id number tabid @@ -11,6 +11,7 @@ local win_picker = require('tabby.feature.win_picker') ---@field is_current fun():boolean return if this tab is current tab ---@field name fun():string return tab name ---@field close_btn fun(symbol:string):TabbyNode return close btn +---@field jumper fun():string return jumper ---new TabbyTab ---@param tabid number @@ -49,9 +50,11 @@ function tabwins.new_tab(tabid, opt) return '' end end, - picker = function() - if win_picker.toggle_alt_draw then + jumper = function() + if tab_jumper.is_start then + return tab_jumper.get_char(tabid) end + return '' end, } end diff --git a/lua/tabby/feature/win_picker.lua b/lua/tabby/feature/win_picker.lua index aaf8882..4336b3a 100644 --- a/lua/tabby/feature/win_picker.lua +++ b/lua/tabby/feature/win_picker.lua @@ -24,15 +24,4 @@ function win_picker.select() end) end -win_picker.tabline_alt_draw = false - -function win_picker.toggle_alt_draw() - win_picker.tabline_alt_draw = not win_picker.tabline_alt_draw - vim.cmd.redrawtabline() - if win_picker.tabline_alt_draw then - local line_opt = require('tabby.tabline').cfg.opt -- TODO: ugly, refact this - local tabs = tabwins.new_tabs(line_opt or {}).tabs - end -end - return win_picker diff --git a/lua/tabby/tabline.lua b/lua/tabby/tabline.lua index 34f1cdc..0f1a8ce 100644 --- a/lua/tabby/tabline.lua +++ b/lua/tabby/tabline.lua @@ -2,6 +2,7 @@ local builder = require('tabby.module.builder') local lines = require('tabby.feature.lines') local tab_name = require('tabby.feature.tab_name') local win_picker = require('tabby.feature.win_picker') +local tab_jumper = require('tabby.feature.tab_jumper') local tabline = {} @@ -31,16 +32,17 @@ function tabline.init() vim.o.tabline = '%!TabbyRenderTabline()' vim.cmd([[command! -nargs=1 TabRename lua require('tabby.feature.tab_name').set(0,)]]) vim.api.nvim_create_user_command('Tabby', function(opts) - vim.print('fargs:', opts.fargs) if opts.fargs[1] == 'rename_tab' then tab_name.set(0, opts.fargs[2] or '') elseif opts.fargs[1] == 'pick_window' then win_picker.select() + elseif opts.fargs[1] == 'jump_tab' then + tab_jumper.start() end end, { nargs = '+', complete = function(_, _, _) - return { 'rename_tab', 'pick_window' } + return { 'rename_tab', 'pick_window', 'jump_tab' } end, }) end @@ -48,6 +50,7 @@ end function tabline.render() tab_name.pre_render() local line = lines.get_line(tabline.cfg.opt) + tab_jumper.pre_render(line) local element = tabline.cfg.fn(line) if element.hl == nil or element.hl == '' then element.hl = 'TabLineFill' @@ -126,6 +129,7 @@ local function preset_tab(line, tab, opt) return { line.sep(left_sep(opt), hl, opt.theme.fill), tab.is_current() and status_icon[1] or status_icon[2], + tab.jumper(), tab.number(), tab.name(), tab.close_btn(opt.nerdfont and '' or '(x)'), From 396e6f2e76192503592ebc3227c1a970544af808 Mon Sep 17 00:00:00 2001 From: Nanozuki Crows Date: Wed, 31 Jul 2024 15:56:23 +0800 Subject: [PATCH 3/4] feat: getchar in silence, make api more explicit --- lua/tabby/feature/tab_jumper.lua | 12 ++++++++---- lua/tabby/feature/tabwins.lua | 8 ++++++-- lua/tabby/module/node.lua | 2 +- lua/tabby/tabline.lua | 12 +++++++----- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lua/tabby/feature/tab_jumper.lua b/lua/tabby/feature/tab_jumper.lua index a3fa86b..e241bc3 100644 --- a/lua/tabby/feature/tab_jumper.lua +++ b/lua/tabby/feature/tab_jumper.lua @@ -61,12 +61,16 @@ function tab_jumper.start() tab_jumper.is_start = true vim.cmd.redrawtabline() - local c = vim.fn.getcharstr():upper() + local ok, tabid = pcall(function() + local c = string.char(vim.fn.getchar()):upper() + return tab_jumper.char_to_tabid[c] + end) + tab_jumper.is_start = false - if tab_jumper.char_to_tabid[c] ~= nil then - vim.api.nvim_set_current_tabpage(tab_jumper.char_to_tabid[c]) + tab_jumper.reset() + if ok and tabid then + vim.api.nvim_set_current_tabpage(tabid) else - tab_jumper.reset() vim.cmd.redrawtabline() end end diff --git a/lua/tabby/feature/tabwins.lua b/lua/tabby/feature/tabwins.lua index 47972c2..4ed6547 100644 --- a/lua/tabby/feature/tabwins.lua +++ b/lua/tabby/feature/tabwins.lua @@ -11,7 +11,8 @@ local tab_jumper = require('tabby.feature.tab_jumper') ---@field is_current fun():boolean return if this tab is current tab ---@field name fun():string return tab name ---@field close_btn fun(symbol:string):TabbyNode return close btn ----@field jumper fun():string return jumper +---@field in_jump_mode fun():boolean return if tab is in jump mode +---@field jump_char fun():TabbyNode return jumper ---new TabbyTab ---@param tabid number @@ -50,7 +51,10 @@ function tabwins.new_tab(tabid, opt) return '' end end, - jumper = function() + in_jump_mode = function() + return tab_jumper.is_start + end, + jump_char = function() if tab_jumper.is_start then return tab_jumper.get_char(tabid) end diff --git a/lua/tabby/module/node.lua b/lua/tabby/module/node.lua index 25500aa..5b52b7b 100644 --- a/lua/tabby/module/node.lua +++ b/lua/tabby/module/node.lua @@ -7,7 +7,7 @@ ---@field lo? TabbyLayout ---@field click? TabbyClickHandler ---@field margin? string ----@field [...] TabbyNode[] children node +---@field [...] TabbyNode children node ---@class TabbyLayout ---@field justify? 'left'|'right' justify @default 'left' diff --git a/lua/tabby/tabline.lua b/lua/tabby/tabline.lua index 0f1a8ce..2f70691 100644 --- a/lua/tabby/tabline.lua +++ b/lua/tabby/tabline.lua @@ -36,13 +36,13 @@ function tabline.init() tab_name.set(0, opts.fargs[2] or '') elseif opts.fargs[1] == 'pick_window' then win_picker.select() - elseif opts.fargs[1] == 'jump_tab' then + elseif opts.fargs[1] == 'jump_to_tab' then tab_jumper.start() end end, { nargs = '+', complete = function(_, _, _) - return { 'rename_tab', 'pick_window', 'jump_tab' } + return { 'rename_tab', 'pick_window', 'jump_to_tab' } end, }) end @@ -128,9 +128,11 @@ local function preset_tab(line, tab, opt) local status_icon = opt.nerdfont and { '', '󰆣' } or { '+', '' } return { line.sep(left_sep(opt), hl, opt.theme.fill), - tab.is_current() and status_icon[1] or status_icon[2], - tab.jumper(), - tab.number(), + tab.in_jump_mode() and tab.jump_char() or { + tab.is_current() and status_icon[1] or status_icon[2], + tab.number(), + margin = ' ', + }, tab.name(), tab.close_btn(opt.nerdfont and '' or '(x)'), line.sep(right_sep(opt), hl, opt.theme.fill), From 3674d3a113f670a25ac6dc03f5511d60c01d9e5b Mon Sep 17 00:00:00 2001 From: Nanozuki Crows Date: Wed, 31 Jul 2024 16:46:03 +0800 Subject: [PATCH 4/4] doc: tab jump mode --- README.md | 71 +++++++++++++++++++++++++++------ doc/tabby.txt | 75 ++++++++++++++++++++++++++++++----- lua/tabby/feature/tabwins.lua | 4 +- lua/tabby/tabline.lua | 2 +- 4 files changed, 126 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d0d5b82..5543b8f 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ # tabby.nvim -A declarative, highly configurable, and neovim style tabline plugin. Use your -nvim tabs as a workspace multiplexer! +A highly configurable, and neovim style tabline plugin. Use your nvim tabs as +a workspace multiplexer! ![](https://raw.githubusercontent.com/wiki/nanozuki/tabby.nvim/assets/banner.png) @@ -26,13 +26,13 @@ changes will be introduced. At next major version, v3, `tabby.nvim` will cleaner all deprecated apis and remove all vimscript. -## Concept +## Features -**A line for the vim tab page, not for buffers** +### Tabline, not bufferline -A tab page in vim holds one or more windows(not buffers). You can easily switch -between tab pages to have several collections of windows to work on different -things. +A line for the vim tab page, not for buffers. A tabpage in vim holds one or +more windows(not buffers). You can easily switch between tab pages to have +several collections of windows to work on different things. Tabline can help you use multiple tabs. Meanwhile, the bufferline is simply an array of opened files. As a result, Bufferline limits the power of vim, @@ -47,12 +47,46 @@ For example, you are writing a backend service: - Tab4: Neogit.nvim ``` -**Declarative, highly configurable** +### Highly configurable -Tabby provides a declarative way to configure tabline. You can set the tabline -to whatever neovim natively supports and complete the config with any lua code. -At least that's the goal of tabby. And also, the tabby provides some presets to -quick start or as your example. +Tabby provides a highly configurable way to set up your personalized tabline. +There is no DSL for config; you can write any lua codes following the type hint. +But also, Tabby provides some presets for quick start and as your example. + +### Tab rename + +You can rename a tab by `Tabby rename_tab `. Display the tab name by +`tab.name()` (reference: [Tab](#Tab)) in your config. Config fallback name by +[Line-Option](#Line-Option) + +### Window picker + +Use command `Tabby pick_window` to open a selector to pick window in tabpages. +This picker use native neovim selector, you can use a general UI plugin to +enhance the appearance. + +### Jump mode for tabs + +Inspired by [barbar.nvim](https://github.com/romgrk/barbar.nvim?tab=readme-ov-file#jump-to-buffer-mode). +Type one key to jump to a tabpage. + + + +![](https://github.com/nanozuki/tabby.nvim/wiki/assets/tab-jump-mode.png) + + + +Use command `Tabby jump_to_tab` to get into jump mode. In jump mode, each tab +have a key which displayed in tabline by `tab.jump_key()`. You can check if in +jump mode by `tab.in_jump_mode()`. (reference: [Tab](#Tab)) + +For example in your config: + +```lua +tab.in_jump_mode() and tab.jump_key() or tab.number() +``` + +The jump char is also displayed in presets. ## Playground @@ -183,6 +217,8 @@ If you want to quick start? That's fine, you can [Use Preset Configs](#Use-Prese - `Tabby rename_tab `: Rename tab. Use name in line by `tab.name()` (ref: [Tab](#Tab)). Config fallback name by [Line-Option](#Line-Option) - `Tabby pick_window`: Open a selector to pick window in tabpages. +- `Tabby jump_to_tab`: Get one key to jump to tabpage, each keys are displayed + in tabline by `tab.jump_key()` ### Key mapping example @@ -427,6 +463,17 @@ tab.close_btn({symbol}) *tabby.tab.close_btn()* Return: ~ Node |tabby-node|, close button node. + +tab.jump_key() *tabby.tab.jump_key()* + In jump mode, return a key to jump to this tab, otherwise return empty + string. + + Return: ~ + String, a key to jump to this tab. + +tab.in_jump_mode() *tabby.tab.in_jump_mode()* + Return: ~ + Boolean, if this tab is in jump mode. ``` ### Win diff --git a/doc/tabby.txt b/doc/tabby.txt index 4ca0373..e7ce20c 100644 --- a/doc/tabby.txt +++ b/doc/tabby.txt @@ -4,7 +4,12 @@ Table of Contents *tabby-table-of-contents* 1. Compatibility and Versions |tabby-compatibility-and-versions| -2. Concept |tabby-concept| +2. Features |tabby-features| + - Tabline, not bufferline |tabby-features-tabline,-not-bufferline| + - Highly configurable |tabby-features-highly-configurable| + - Tab rename |tabby-features-tab-rename| + - Window picker |tabby-features-window-picker| + - Jump mode for tabs |tabby-features-jump-mode-for-tabs| 3. Playground |tabby-playground| 4. Install |tabby-install| 5. Setup |tabby-setup| @@ -46,13 +51,14 @@ remove all vimscript. ============================================================================== -2. Concept *tabby-concept* +2. Features *tabby-features* -**A line for the vim tab page, not for buffers** -A tab page in vim holds one or more windows(not buffers). You can easily switch -between tab pages to have several collections of windows to work on different -things. +TABLINE, NOT BUFFERLINE *tabby-features-tabline,-not-bufferline* + +A line for the vim tab page, not for buffers. A tabpage in vim holds one or +more windows(not buffers). You can easily switch between tab pages to have +several collections of windows to work on different things. Tabline can help you use multiple tabs. Meanwhile, the bufferline is simply an array of opened files. As a result, Bufferline limits the power of vim, @@ -67,12 +73,46 @@ For example, you are writing a backend service: - Tab4: Neogit.nvim < -**Declarative, highly configurable** -Tabby provides a declarative way to configure tabline. You can set the tabline -to whatever neovim natively supports and complete the config with any lua code. -At least that’s the goal of tabby. And also, the tabby provides some presets -to quick start or as your example. +HIGHLY CONFIGURABLE *tabby-features-highly-configurable* + +Tabby provides a highly configurable way to set up your personalized tabline. +There is no DSL for config; you can write any lua codes following the type +hint. But also, Tabby provides some presets for quick start and as your +example. + + +TAB RENAME *tabby-features-tab-rename* + +You can rename a tab by `Tabby rename_tab `. Display the tab name by +`tab.name()` (reference: |tabby-tab|) in your config. Config fallback name by +|tabby-line-option| + + +WINDOW PICKER *tabby-features-window-picker* + +Use command `Tabby pick_window` to open a selector to pick window in tabpages. +This picker use native neovim selector, you can use a general UI plugin to +enhance the appearance. + + +JUMP MODE FOR TABS *tabby-features-jump-mode-for-tabs* + +Inspired by barbar.nvim +. +Type one key to jump to a tabpage. + +Use command `Tabby jump_to_tab` to get into jump mode. In jump mode, each tab +have a key which displayed in tabline by `tab.jump_key()`. You can check if in +jump mode by `tab.in_jump_mode()`. (reference: |tabby-tab|) + +For example in your config: + +>lua + tab.in_jump_mode() and tab.jump_key() or tab.number() +< + +The jump char is also displayed in presets. ============================================================================== @@ -218,6 +258,8 @@ COMMANDS *tabby-setup-commands* - `Tabby rename_tab `: Rename tab. Use name in line by `tab.name()` (ref: |tabby-tab|). Config fallback name by |tabby-line-option| - `Tabby pick_window`: Open a selector to pick window in tabpages. +- `Tabby jump_to_tab`: Get one key to jump to tabpage, each keys are displayed + in tabline by `tab.jump_key()` KEY MAPPING EXAMPLE *tabby-setup-key-mapping-example* @@ -466,6 +508,17 @@ tab.close_btn({symbol}) *tabby.tab.close_btn()* Return: ~ Node |tabby-node|, close button node. +tab.jump_key() *tabby.tab.jump_key()* + In jump mode, return a key to jump to this tab, otherwise return empty + string. + + Return: ~ + String, a key to jump to this tab. + +tab.in_jump_mode() *tabby.tab.in_jump_mode()* + Return: ~ + Boolean, if this tab is in jump mode. + WIN *tabby-customize-win* diff --git a/lua/tabby/feature/tabwins.lua b/lua/tabby/feature/tabwins.lua index 4ed6547..c7e5f88 100644 --- a/lua/tabby/feature/tabwins.lua +++ b/lua/tabby/feature/tabwins.lua @@ -12,7 +12,7 @@ local tab_jumper = require('tabby.feature.tab_jumper') ---@field name fun():string return tab name ---@field close_btn fun(symbol:string):TabbyNode return close btn ---@field in_jump_mode fun():boolean return if tab is in jump mode ----@field jump_char fun():TabbyNode return jumper +---@field jump_key fun():TabbyNode return jumper ---new TabbyTab ---@param tabid number @@ -54,7 +54,7 @@ function tabwins.new_tab(tabid, opt) in_jump_mode = function() return tab_jumper.is_start end, - jump_char = function() + jump_key = function() if tab_jumper.is_start then return tab_jumper.get_char(tabid) end diff --git a/lua/tabby/tabline.lua b/lua/tabby/tabline.lua index 2f70691..05a0486 100644 --- a/lua/tabby/tabline.lua +++ b/lua/tabby/tabline.lua @@ -128,7 +128,7 @@ local function preset_tab(line, tab, opt) local status_icon = opt.nerdfont and { '', '󰆣' } or { '+', '' } return { line.sep(left_sep(opt), hl, opt.theme.fill), - tab.in_jump_mode() and tab.jump_char() or { + tab.in_jump_mode() and tab.jump_key() or { tab.is_current() and status_icon[1] or status_icon[2], tab.number(), margin = ' ',