From f677f1d93be06c41eaabc87d845cdd5bd46a259f Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Mon, 2 Aug 2021 21:58:58 -0700 Subject: [PATCH] Feature: 'pinned' buffers similar to chrome tabs (#145) * Feature: 'pinned' buffers similar to chrome tabs * Add help docs for BufferPin command and options * Remove the pin_status option --- README.md | 4 ++ doc/barbar.txt | 8 +++- lua/bufferline/layout.lua | 6 +++ lua/bufferline/render.lua | 10 +++++ lua/bufferline/state.lua | 92 ++++++++++++++++++++++++++++++--------- plugin/bufferline.vim | 2 + 6 files changed, 101 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 166628f9..6c29b959 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ nnoremap :BufferGoto 6 nnoremap :BufferGoto 7 nnoremap :BufferGoto 8 nnoremap :BufferLast +" Pin/unpin buffer +nnoremap :BufferPin " Close buffer nnoremap :BufferClose " Wipeout buffer @@ -234,6 +236,7 @@ let bufferline.icon_separator_active = '▎' let bufferline.icon_separator_inactive = '▎' let bufferline.icon_close_tab = '' let bufferline.icon_close_tab_modified = '●' +let bufferline.icon_pinned = '車' " Sets the maximum padding width with which to surround each tab. let bufferline.maximum_padding = 4 @@ -301,6 +304,7 @@ vim.g.bufferline = { icon_separator_inactive = '▎', icon_close_tab = '', icon_close_tab_modified = '●', + icon_pinned = '車', -- Sets the maximum padding width with which to surround each tab maximum_padding = 1, diff --git a/doc/barbar.txt b/doc/barbar.txt index 6acd11fb..0416891a 100644 --- a/doc/barbar.txt +++ b/doc/barbar.txt @@ -47,7 +47,8 @@ The name of each command should be descriptive enough for you to use it. nnoremap :BufferGoto 7 nnoremap :BufferGoto 8 nnoremap :BufferLast - + " Pin/unpin buffer + nnoremap :BufferPin " Close buffer nnoremap :BufferClose @@ -199,6 +200,11 @@ Here are the groups that you should define if you'd like to style Barbar. The button used to close the tab when it has been modified since last save. + *g:bufferline.icon_pinned* +`g:bufferline.icon_pinned` string (default '車') + + The icon used to indicate that a buffer is pinned. + *g:bufferline.closable* `g:bufferline.closable` boolean (default v:true) diff --git a/lua/bufferline/layout.lua b/lua/bufferline/layout.lua index 0673cfa3..237cb012 100644 --- a/lua/bufferline/layout.lua +++ b/lua/bufferline/layout.lua @@ -49,6 +49,12 @@ local function calculate_buffers_width(state, base_width) + 1 -- space-after-buffer-index end + if state.is_pinned(buffer_number) then + width = width + + 1 -- spacing after filename + + strwidth(opts.icon_pinned) + end + if opts.closable then width = width + strwidth(not nvim.buf_get_option(buffer_number, 'modified') -- close-icon diff --git a/lua/bufferline/render.lua b/lua/bufferline/render.lua index 8fd023ec..a30cb0ae 100644 --- a/lua/bufferline/render.lua +++ b/lua/bufferline/render.lua @@ -148,6 +148,10 @@ local function render(update_names) local iconPrefix = '' local icon = '' + -- The pin icon + local pinPrefix = '' + local pin = '' + if has_numbers then local number_text = tostring(i) bufferIndexPrefix = hl('Buffer' .. status .. 'Index') @@ -173,6 +177,11 @@ local function render(update_names) iconPrefix = has_icon_custom_colors and hl('Buffer' .. status .. 'Icon') or hlName and hl(hlName) or namePrefix icon = iconChar .. ' ' end + + if state.is_pinned(buffer_number) then + pinPrefix = namePrefix + pin = ' ' .. icons.pinned + end end local closePrefix = '' @@ -211,6 +220,7 @@ local function render(update_names) {iconPrefix, icon}, {jumpLetterPrefix, jumpLetter}, {namePrefix, name}, + {pinPrefix, pin}, {'', padding}, {'', ' '}, {closePrefix, close}, diff --git a/lua/bufferline/state.lua b/lua/bufferline/state.lua index 0233b9dc..fb6913f0 100644 --- a/lua/bufferline/state.lua +++ b/lua/bufferline/state.lua @@ -20,6 +20,7 @@ local ANIMATION_OPEN_DURATION = 150 local ANIMATION_OPEN_DELAY = 50 local ANIMATION_CLOSE_DURATION = 100 local ANIMATION_SCROLL_DURATION = 200 +local PIN = "bufferline_pin" -------------------------------- -- Section: Application state -- @@ -56,6 +57,32 @@ function m.get_buffer_data(id) return m.buffers_by_id[id] end +-- Pinned buffers + +local function is_pinned(bufnr) + local ok, val = pcall(vim.api.nvim_buf_get_var, bufnr, PIN) + return ok and val +end + +local function sort_pins_to_left() + local pinned = {} + local unpinned = {} + for _, bufnr in ipairs(m.buffers) do + if is_pinned(bufnr) then + table.insert(pinned, bufnr) + else + table.insert(unpinned, bufnr) + end + end + m.buffers = vim.list_extend(pinned, unpinned) +end + +local function toggle_pin(bufnr) + bufnr = bufnr or 0 + vim.api.nvim_buf_set_var(bufnr, PIN, not is_pinned(bufnr)) + sort_pins_to_left() + vim.fn["bufferline#update"]() +end -- Scrolling @@ -140,6 +167,8 @@ local function open_buffers(new_buffers) end end + sort_pins_to_left() + -- We're done if there is no animations if vim.g.bufferline.animation == false then return @@ -338,6 +367,7 @@ local function move_buffer(from_idx, to_idx) local bufnr = m.buffers[from_idx] table.remove(m.buffers, from_idx) table.insert(m.buffers, to_idx, bufnr) + sort_pins_to_left() vim.fn['bufferline#update']() end @@ -457,34 +487,54 @@ end -- Ordering +local function with_pin_order(order_func) + return function(a, b) + local a_pinned = is_pinned(a) + local b_pinned = is_pinned(b) + if a_pinned and not b_pinned then + return true + elseif b_pinned and not a_pinned then + return false + else + return order_func(a, b) + end + end +end + local function is_relative_path(path) return fnamemodify(path, ':p') ~= path end local function order_by_directory() - table.sort(m.buffers, function(a, b) - local na = bufname(a) - local nb = bufname(b) - local ra = is_relative_path(na) - local rb = is_relative_path(nb) - if ra and not rb then - return true - end - if not ra and rb then - return false - end - return na < nb - end) - vim.fn['bufferline#update']() + table.sort( + m.buffers, + with_pin_order(function(a, b) + local na = bufname(a) + local nb = bufname(b) + local ra = is_relative_path(na) + local rb = is_relative_path(nb) + if ra and not rb then + return true + end + if not ra and rb then + return false + end + return na < nb + end) + ) + vim.fn["bufferline#update"]() end local function order_by_language() - table.sort(m.buffers, function(a, b) - local na = fnamemodify(bufname(a), ':e') - local nb = fnamemodify(bufname(b), ':e') - return na < nb - end) - vim.fn['bufferline#update']() + table.sort( + m.buffers, + with_pin_order(function(a, b) + local na = fnamemodify(bufname(a), ":e") + local nb = fnamemodify(bufname(b), ":e") + return na < nb + end) + ) + vim.fn["bufferline#update"]() end @@ -556,11 +606,13 @@ m.close_all_but_current = close_all_but_current m.close_buffers_right = close_buffers_right m.close_buffers_left = close_buffers_left +m.is_pinned = is_pinned m.move_current_buffer_to = move_current_buffer_to m.move_current_buffer = move_current_buffer m.goto_buffer = goto_buffer m.goto_buffer_relative = goto_buffer_relative +m.toggle_pin = toggle_pin m.order_by_directory = order_by_directory m.order_by_language = order_by_language diff --git a/plugin/bufferline.vim b/plugin/bufferline.vim index 14c85c2d..80d7c62b 100644 --- a/plugin/bufferline.vim +++ b/plugin/bufferline.vim @@ -68,6 +68,7 @@ command! -count -bang BufferMovePrevious call s:move_current_buffer(-v:cou command! -nargs=1 -bang BufferMove call s:move_current_buffer_to() command! -bang BufferPick call bufferline#pick_buffer() +command! BufferPin lua require'bufferline.state'.toggle_pin() command! -bang BufferOrderByDirectory call bufferline#order_by_directory() command! -bang BufferOrderByLanguage call bufferline#order_by_language() @@ -96,6 +97,7 @@ let s:DEFAULT_OPTIONS = { \ 'exclude_name': v:null, \ 'icon_close_tab': '', \ 'icon_close_tab_modified': '●', +\ 'icon_pinned': '車', \ 'icon_separator_active': '▎', \ 'icon_separator_inactive': '▎', \ 'icons': v:true,