diff --git a/lua/barbar.lua b/lua/barbar.lua index ec02e263..9db667fc 100644 --- a/lua/barbar.lua +++ b/lua/barbar.lua @@ -6,19 +6,19 @@ local command = vim.api.nvim_command --- @type function local create_user_command = vim.api.nvim_create_user_command --- @type function local set_option = vim.api.nvim_set_option --- @type function -local api = require'barbar.api' -local bbye = require'barbar.bbye' -local events = require'barbar.events' -local notify = require'barbar.utils'.notify -local render = require'barbar.ui.render' -local state = require'barbar.state' -local utils = require'barbar.utils' +local api = require('barbar.api') +local bbye = require('barbar.bbye') +local events = require('barbar.events') +local markdown_inline_code = require('barbar.utils').markdown_inline_code +local notify = require('barbar.utils').notify +local scroll = require('barbar.ui.render').scroll +local state = require('barbar.state') ------------------------------- -- Section: `barbar` module ------------------------------- ---- @class barbar +--- @class Barbar local barbar = {} --- Setup this plugin. @@ -47,7 +47,7 @@ function barbar.setup(options) local index = tonumber(tbl.args) if not index then return notify( - 'Invalid argument to ' .. utils.markdown_inline_code':BufferGoto', + 'Invalid argument to ' .. markdown_inline_code':BufferGoto', vim.log.levels.ERROR ) end @@ -59,7 +59,7 @@ function barbar.setup(options) local buffers = state.buffers local buffer_indices = {} - for i = 1, #buffers do + for i in ipairs(buffers) do table_insert(buffer_indices, tostring(i)) end @@ -82,7 +82,7 @@ function barbar.setup(options) vim.api.nvim_cmd and function(tbl) vim.cmd.BufferMovePrevious {count = tbl.count} end or function(tbl) command('BufferMovePrevious ' .. tbl.count) end, - {count = true, desc = 'Synonym for ' .. utils.markdown_inline_code':BufferMovePrevious'} + {count = true, desc = 'Synonym for ' .. markdown_inline_code':BufferMovePrevious'} ) create_user_command( @@ -188,13 +188,13 @@ function barbar.setup(options) create_user_command( 'BufferScrollLeft', - function(tbl) render.scroll(-max(1, tbl.count)) end, + function(tbl) scroll(-max(1, tbl.count)) end, {count = true, desc = 'Scroll the bufferline left'} ) create_user_command( 'BufferScrollRight', - function(tbl) render.scroll(max(1, tbl.count)) end, + function(tbl) scroll(max(1, tbl.count)) end, {count = true, desc = 'Scroll the bufferline right'} ) diff --git a/lua/barbar/animate.lua b/lua/barbar/animate.lua index 9ba4603c..43b1418d 100644 --- a/lua/barbar/animate.lua +++ b/lua/barbar/animate.lua @@ -17,7 +17,7 @@ local schedule_wrap = vim.schedule_wrap --- @field timer userdata --- @field type unknown ---- @class barbar.animate +--- @class barbar.Animate local animate = {} --- The amount of time between rendering the next part of the animation. diff --git a/lua/barbar/api.lua b/lua/barbar/api.lua index baf6d046..6191dda2 100644 --- a/lua/barbar/api.lua +++ b/lua/barbar/api.lua @@ -16,15 +16,17 @@ local set_current_buf = vim.api.nvim_set_current_buf --- @type function -- TODO: remove `vim.fs and` after 0.8 release local normalize = vim.fs and vim.fs.normalize -local animate = require'barbar.animate' -local bbye = require'barbar.bbye' -local Buffer = require'barbar.buffer' -local config = require'barbar.config' -local JumpMode = require'barbar.jump_mode' -local Layout = require'barbar.ui.layout' -local render = require'barbar.ui.render' -local state = require'barbar.state' -local utils = require'barbar.utils' +local animate = require('barbar.animate') +local bdelete = require('barbar.bbye').bdelete +local buffer = require('barbar.buffer') +local config = require('barbar.config') +local index_of = require('barbar.utils.list').index_of +local is_relative_path = require('barbar.fs').is_relative_path +local jump_mode = require('barbar.jump_mode') +local layout = require('barbar.ui.layout') +local notify = require('barbar.utils').notify +local render = require('barbar.ui.render') +local state = require('barbar.state') local ESC = vim.api.nvim_replace_termcodes('', true, false, true) @@ -32,8 +34,8 @@ local ESC = vim.api.nvim_replace_termcodes('', true, false, true) --- @param fn fun() --- @return nil local function pick_buffer_wrap(fn) - if JumpMode.reinitialize then - JumpMode.initialize_indexes() + if jump_mode.reinitialize then + jump_mode.initialize_indexes() end state.is_picking_buffer = true @@ -49,7 +51,7 @@ end --- @param buffer_number integer --- @return nil local function notify_buffer_not_found(buffer_number) - utils.notify( + notify( 'Current buffer (' .. buffer_number .. ") not found in barbar.nvim's list of buffers: " .. vim.inspect(state.buffers), vim.log.levels.ERROR ) @@ -73,7 +75,7 @@ local function with_pin_order(order_func) end end ---- @class barbar.api +--- @class barbar.Api local api = {} --- Close all open buffers, except the current one. @@ -83,7 +85,7 @@ function api.close_all_but_current() for _, buffer_number in ipairs(state.buffers) do if buffer_number ~= current_bufnr then - bbye.bdelete(false, buffer_number) + bdelete(false, buffer_number) end end @@ -93,10 +95,10 @@ end --- Close all open buffers, except those in visible windows. --- @return nil function api.close_all_but_visible() - local visible = Buffer.activities.Visible + local visible = buffer.activities.Visible for _, buffer_number in ipairs(state.buffers) do - if Buffer.get_activity(buffer_number) < visible then - bbye.bdelete(false, buffer_number) + if buffer.get_activity(buffer_number) < visible then + bdelete(false, buffer_number) end end @@ -108,7 +110,7 @@ end function api.close_all_but_pinned() for _, buffer_number in ipairs(state.buffers) do if not state.is_pinned(buffer_number) then - bbye.bdelete(false, buffer_number) + bdelete(false, buffer_number) end end @@ -122,7 +124,7 @@ function api.close_all_but_current_or_pinned() for _, buffer_number in ipairs(state.buffers) do if not state.is_pinned(buffer_number) and buffer_number ~= current_bufnr then - bbye.bdelete(false, buffer_number) + bdelete(false, buffer_number) end end @@ -132,13 +134,13 @@ end --- Close all buffers which are visually left of the current buffer. --- @return nil function api.close_buffers_left() - local idx = utils.index_of(state.buffers, get_current_buf()) + local idx = index_of(state.buffers, get_current_buf()) if idx == nil or idx == 1 then return end for i = idx - 1, 1, -1 do - bbye.bdelete(false, state.buffers[i]) + bdelete(false, state.buffers[i]) end render.update() @@ -147,13 +149,13 @@ end --- Close all buffers which are visually right of the current buffer. --- @return nil function api.close_buffers_right() - local idx = utils.index_of(state.buffers, get_current_buf()) + local idx = index_of(state.buffers, get_current_buf()) if idx == nil then return end for i = #state.buffers, idx + 1, -1 do - bbye.bdelete(false, state.buffers[i]) + bdelete(false, state.buffers[i]) end render.update() @@ -180,7 +182,7 @@ function api.goto_buffer(index) if buffer_number then set_current_buf(buffer_number) else - utils.notify( + notify( 'E86: buffer at index ' .. index .. ' in list ' .. vim.inspect(state.buffers) .. ' does not exist.', vim.log.levels.ERROR ) @@ -195,15 +197,15 @@ function api.goto_buffer_relative(steps) render.get_updated_buffers() if #state.buffers < 1 then - return utils.notify('E85: There is no listed buffer', vim.log.levels.ERROR) + return notify('E85: There is no listed buffer', vim.log.levels.ERROR) end local current_bufnr = render.set_current_win_listed_buffer() - local idx = utils.index_of(state.buffers, current_bufnr) + local idx = index_of(state.buffers, current_bufnr) if not idx then -- fall back to: 1. the alternate buffer, 2. the first buffer - idx = utils.index_of(state.buffers, bufnr'#') or 1 - utils.notify( + idx = index_of(state.buffers, bufnr'#') or 1 + notify( "Couldn't find buffer #" .. current_bufnr .. ' in the list: ' .. vim.inspect(state.buffers) .. '. Falling back to buffer #' .. state.buffers[idx], vim.log.levels.INFO @@ -222,7 +224,7 @@ local move_animation_data = { --- An incremental animation for `move_buffer_animated`. --- @return nil local function move_buffer_animated_tick(ratio, current_animation) - for _, current_number in ipairs(Layout.buffers) do + for _, current_number in ipairs(layout.buffers) do local current_data = state.get_buffer_data(current_number) if current_animation.running == true then @@ -263,7 +265,7 @@ local function move_buffer(from_idx, to_idx) local previous_positions if animation == true then - previous_positions = Layout.calculate_buffers_position_by_buffer_number() + previous_positions = layout.calculate_buffers_position_by_buffer_number() end table_remove(state.buffers, from_idx) @@ -271,7 +273,7 @@ local function move_buffer(from_idx, to_idx) state.sort_pins_to_left() if animation == true then - local current_index = utils.index_of(Layout.buffers, buffer_number) + local current_index = index_of(layout.buffers, buffer_number) local start_index = min(from_idx, current_index) local end_index = max(from_idx, current_index) @@ -281,9 +283,9 @@ local function move_buffer(from_idx, to_idx) animate.stop(move_animation) end - local next_positions = Layout.calculate_buffers_position_by_buffer_number() + local next_positions = layout.calculate_buffers_position_by_buffer_number() - for _, layout_bufnr in ipairs(Layout.buffers) do + for _, layout_bufnr in ipairs(layout.buffers) do local current_data = state.get_buffer_data(layout_bufnr) local previous_position = previous_positions[layout_bufnr] @@ -319,7 +321,7 @@ function api.move_current_buffer_to(idx) end local current_bufnr = get_current_buf() - local from_idx = utils.index_of(state.buffers, current_bufnr) + local from_idx = index_of(state.buffers, current_bufnr) if from_idx == nil then return notify_buffer_not_found(current_bufnr) @@ -335,7 +337,7 @@ function api.move_current_buffer(steps) render.update() local current_bufnr = get_current_buf() - local idx = utils.index_of(state.buffers, current_bufnr) + local idx = index_of(state.buffers, current_bufnr) if idx == nil then return notify_buffer_not_found(current_bufnr) @@ -361,8 +363,8 @@ function api.order_by_directory() -- TODO: remove this block after 0.8 releases if not normalize then - local a_is_relative = utils.is_relative_path(name_of_a) - if a_is_relative and utils.is_relative_path(name_of_b) then + local a_is_relative = is_relative_path(name_of_a) + if a_is_relative and is_relative_path(name_of_b) then return a_less_than_b end @@ -408,13 +410,13 @@ function api.pick_buffer() pick_buffer_wrap(function() local ok, letter = pcall(function() return char(getchar()) end) if ok and letter ~= '' then - if JumpMode.buffer_by_letter[letter] ~= nil then - set_current_buf(JumpMode.buffer_by_letter[letter]) + if jump_mode.buffer_by_letter[letter] ~= nil then + set_current_buf(jump_mode.buffer_by_letter[letter]) else - utils.notify("Couldn't find buffer", vim.log.levels.WARN) + notify("Couldn't find buffer", vim.log.levels.WARN) end else - utils.notify('Invalid input', vim.log.levels.WARN) + notify('Invalid input', vim.log.levels.WARN) end end) end @@ -426,15 +428,15 @@ function api.pick_buffer_delete() while true do local ok, letter = pcall(function() return char(getchar()) end) if ok and letter ~= '' then - if JumpMode.buffer_by_letter[letter] ~= nil then - bbye.bdelete(false, JumpMode.buffer_by_letter[letter]) + if jump_mode.buffer_by_letter[letter] ~= nil then + bdelete(false, jump_mode.buffer_by_letter[letter]) elseif letter == ESC then break else - utils.notify("Couldn't find buffer with letter '" .. letter .. "'", vim.log.levels.WARN) + notify("Couldn't find buffer with letter '" .. letter .. "'", vim.log.levels.WARN) end else - utils.notify('Invalid input', vim.log.levels.WARN) + notify('Invalid input', vim.log.levels.WARN) end render.update() @@ -446,7 +448,7 @@ end --- @param width integer the amount to offset --- @param text? string text to put in the offset --- @param hl? string ---- @param side? 'left'|'right' +--- @param side? side --- @return nil function api.set_offset(width, text, hl, side) if side == nil then diff --git a/lua/barbar/bbye.lua b/lua/barbar/bbye.lua index 52c428f5..9f539db1 100644 --- a/lua/barbar/bbye.lua +++ b/lua/barbar/bbye.lua @@ -41,9 +41,10 @@ local set_current_win = vim.api.nvim_set_current_win --- @type function local win_get_buf = vim.api.nvim_win_get_buf --- @type function local win_is_valid = vim.api.nvim_win_is_valid --- @type function -local config = require'barbar.config' -local state = require'barbar.state' -local utils = require'barbar.utils' +local config = require('barbar.config') +local list = require('barbar.utils.list') +local markdown_inline_code = require('barbar.utils').markdown_inline_code +local state = require('barbar.state') ------------------- -- Section: helpers @@ -100,10 +101,10 @@ local function get_focus_on_close(closing_number) end if focus_on_close == 'right' then - state_bufnrs = utils.list_reverse(state.buffers) + state_bufnrs = list.reverse(state.buffers) end - local index = utils.index_of(state_bufnrs, closing_number) + local index = list.index_of(state_bufnrs, closing_number) if index then index = index - 1 @@ -158,7 +159,7 @@ end -- Section: module ------------------ ---- @class bbye +--- @class barbar.Bbye local bbye = {} --- Delete a buffer @@ -249,7 +250,7 @@ function bbye.delete(action, force, buffer, mods) return err(msg) end else - return err('Could not delete buffer ' .. buffer_number .. ' with ' .. utils.markdown_inline_code(action)) + return err('Could not delete buffer ' .. buffer_number .. ' with ' .. markdown_inline_code(action)) end end end diff --git a/lua/barbar/buffer.lua b/lua/barbar/buffer.lua index ef889553..e84789b8 100644 --- a/lua/barbar/buffer.lua +++ b/lua/barbar/buffer.lua @@ -7,27 +7,29 @@ local min = math.min local table_concat = table.concat local table_insert = table.insert -local bufnr = vim.fn.bufnr --- @type function local buf_get_name = vim.api.nvim_buf_get_name --- @type function local buf_get_option = vim.api.nvim_buf_get_option --- @type function local buf_is_valid = vim.api.nvim_buf_is_valid --- @type function +local bufnr = vim.fn.bufnr --- @type function local bufwinnr = vim.fn.bufwinnr --- @type function -local ERROR = vim.diagnostic.severity.ERROR --- @type integer local get_current_buf = vim.api.nvim_get_current_buf --- @type function local get_diagnostics = vim.diagnostic.get --- @type fun(bufnr: integer): {severity: integer}[] -local HINT = vim.diagnostic.severity.HINT --- @type integer -local INFO = vim.diagnostic.severity.INFO --- @type integer local matchlist = vim.fn.matchlist --- @type function +local severity = vim.diagnostic.severity local split = vim.split local strcharpart = vim.fn.strcharpart --- @type function local strwidth = vim.api.nvim_strwidth --- @type function -local WARN = vim.diagnostic.severity.WARN --- @type integer -local config = require'barbar.config' -local utils = require'barbar.utils' +local basename = require('barbar.fs').basename +local config = require('barbar.config') +local slice_from_end = require('barbar.utils.list').slice_from_end local ELLIPSIS = '…' local ELLIPSIS_LEN = strwidth(ELLIPSIS) +local ERROR = severity.ERROR +local HINT = severity.HINT +local INFO = severity.INFO +local WARN = severity.WARN --- @alias barbar.buffer.activity 1|2|3|4 @@ -51,9 +53,45 @@ local function terminalname(name) end end +--- @class barbar.Buffer +local buffer = { activities = activities } + +--- @param buffer_number integer +--- @return integer[] # indexed on `vim.diagnostic.severity` +function buffer.count_diagnostics(buffer_number) + local count = {[ERROR] = 0, [HINT] = 0, [INFO] = 0, [WARN] = 0} + + for _, diagnostic in ipairs(get_diagnostics(buffer_number)) do + count[diagnostic.severity] = count[diagnostic.severity] + 1 + end + + return count +end + +--- For each severity in `diagnostics`: if it is enabled, and there are diagnostics associated with it in the `buffer_number` provided, call `f`. +--- @param buffer_number integer the buffer number to count diagnostics in +--- @param diagnostics barbar.config.options.icons.buffer.diagnostics the user configuration for diagnostics +--- @param f fun(count: integer, severity_idx: integer, option: barbar.config.options.icons.diagnostics.severity) the function to run when diagnostics of a specific severity are enabled and present in the `buffer_number` +--- @return nil +function buffer.for_each_counted_enabled_diagnostic(buffer_number, diagnostics, f) + local count + for i in ipairs(severity) do + local option = diagnostics[i] + if option.enabled then + if count == nil then + count = buffer.count_diagnostics(buffer_number) + end + + if count[i] > 0 then + f(count[i], i, option) + end + end + end +end + --- @param buffer_number integer --- @return barbar.buffer.activity # whether `bufnr` is inactive, the alternate file, visible, or currently selected (in that order). -local function get_activity(buffer_number) +function buffer.get_activity(buffer_number) if get_current_buf() == buffer_number then return activities.Current elseif config.options.highlight_alternate and bufnr('#') == buffer_number then @@ -65,138 +103,97 @@ local function get_activity(buffer_number) return activities.Inactive end ---- @param buffer_number integer ---- @return integer[] # indexed on `vim.diagnostic.severity` -local function count_diagnostics(buffer_number) - local count = {[ERROR] = 0, [HINT] = 0, [INFO] = 0, [WARN] = 0} - - for _, diagnostic in ipairs(get_diagnostics(buffer_number)) do - count[diagnostic.severity] = count[diagnostic.severity] + 1 +--- @param activity barbar.buffer.activity.name +--- @param modified boolean +--- @param pinned boolean +--- @return barbar.config.options.icons.buffer +function buffer.get_icons(activity, modified, pinned) + local icons_option = config.options.icons[activity:lower()] + if pinned then + icons_option = icons_option.pinned + elseif modified then + icons_option = icons_option.modified end - return count + return icons_option end ---- @class barbar.buffer -local buffer = { - activities = activities, - - count_diagnostics = count_diagnostics, - - --- For each severity in `diagnostics`: if it is enabled, and there are diagnostics associated with it in the `buffer_number` provided, call `f`. - --- @param buffer_number integer the buffer number to count diagnostics in - --- @param diagnostics barbar.config.options.icons.buffer.diagnostics the user configuration for diagnostics - --- @param f fun(count: integer, severity_idx: integer, option: barbar.config.options.icons.diagnostics.severity) the function to run when diagnostics of a specific severity are enabled and present in the `buffer_number` - --- @return nil - for_each_counted_enabled_diagnostic = function(buffer_number, diagnostics, f) - local count - for severity_idx, severity_option in ipairs(diagnostics) do - if severity_option.enabled then - if count == nil then - count = count_diagnostics(buffer_number) - end - - if count[severity_idx] > 0 then - f(count[severity_idx], severity_idx, severity_option) - end - end - end - end, - - get_activity = get_activity, - - --- @param activity barbar.buffer.activity.name - --- @param modified boolean - --- @param pinned boolean - --- @return barbar.config.options.icons.buffer - get_icons = function(activity, modified, pinned) - local icons_option = config.options.icons[activity:lower()] - if pinned then - icons_option = icons_option.pinned - elseif modified then - icons_option = icons_option.modified - end - - return icons_option - end, - - --- @param buffer_number integer - --- @param hide_extensions boolean? if `true`, exclude the extension of the file - --- @return string name - get_name = function(buffer_number, hide_extensions) - --- @type string - local name = buf_is_valid(buffer_number) and buf_get_name(buffer_number) or '' +--- @param buffer_number integer +--- @param hide_extensions boolean? if `true`, exclude the extension of the file +--- @return string name +function buffer.get_name(buffer_number, hide_extensions) + --- @type string + local name = buf_is_valid(buffer_number) and buf_get_name(buffer_number) or '' + + local no_name_title = config.options.no_name_title + local maximum_length = config.options.maximum_length + + if name ~= '' then + name = buf_get_option(buffer_number, 'buftype') == 'terminal' and terminalname(name) or basename(name, hide_extensions) + elseif no_name_title ~= nil and no_name_title ~= vim.NIL then + name = no_name_title + end - local no_name_title = config.options.no_name_title - local maximum_length = config.options.maximum_length + if name == '' then + name = '[buffer ' .. buffer_number .. ']' + end - if name ~= '' then - name = buf_get_option(buffer_number, 'buftype') == 'terminal' and terminalname(name) or utils.basename(name, hide_extensions) - elseif no_name_title ~= nil and no_name_title ~= vim.NIL then - name = no_name_title - end + if strwidth(name) > maximum_length then + local ext_index = name:reverse():find('%.') - if name == '' then - name = '[buffer ' .. buffer_number .. ']' + if ext_index ~= nil and (ext_index < maximum_length - ELLIPSIS_LEN) then + local extension = name:sub(-ext_index) + name = strcharpart(name, 0, maximum_length - ELLIPSIS_LEN - #extension) .. ELLIPSIS .. extension + else + name = strcharpart(name, 0, maximum_length - ELLIPSIS_LEN) .. ELLIPSIS end + end - if strwidth(name) > maximum_length then - local ext_index = name:reverse():find('%.') + return name +end - if ext_index ~= nil and (ext_index < maximum_length - ELLIPSIS_LEN) then - local extension = name:sub(-ext_index) - name = strcharpart(name, 0, maximum_length - ELLIPSIS_LEN - #extension) .. ELLIPSIS .. extension - else - name = strcharpart(name, 0, maximum_length - ELLIPSIS_LEN) .. ELLIPSIS - end - end +--- @param first string +--- @param second string +--- @return string, string +function buffer.get_unique_name(first, second) + local first_parts = split(first, separator) + local second_parts = split(second, separator) + + local length = 1 + local first_result = table_concat(slice_from_end(first_parts, length), separator) + local second_result = table_concat(slice_from_end(second_parts, length), separator) + + while first_result == second_result and + length < max(#first_parts, #second_parts) + do + length = length + 1 + first_result = table_concat(slice_from_end(first_parts, min(#first_parts, length)), separator) + second_result = table_concat(slice_from_end(second_parts, min(#second_parts, length)), separator) + end - return name - end, - - --- @param first string - --- @param second string - --- @return string, string - get_unique_name = function(first, second) - local first_parts = split(first, separator) - local second_parts = split(second, separator) - - local length = 1 - local first_result = table_concat(utils.list_slice_from_end(first_parts, length), separator) - local second_result = table_concat(utils.list_slice_from_end(second_parts, length), separator) - - while first_result == second_result and - length < max(#first_parts, #second_parts) - do - length = length + 1 - first_result = table_concat(utils.list_slice_from_end(first_parts, min(#first_parts, length)), separator) - second_result = table_concat(utils.list_slice_from_end(second_parts, min(#second_parts, length)), separator) - end + return first_result, second_result +end - return first_result, second_result - end, - - --- Filter buffer numbers which are not to be shown during the render process. - --- Does **not** mutate `bufnrs`. - --- @param bufnrs integer[] - --- @return integer[] bufnrs the shown buffers - hide = function(bufnrs) - local hide = config.options.hide - if hide.alternate or hide.current or hide.inactive or hide.visible then - local shown = {} - - for _, buffer_number in ipairs(bufnrs) do - local activity = activities[get_activity(buffer_number)] - if not hide[activity:lower()] then - table_insert(shown, buffer_number) - end +--- Filter buffer numbers which are not to be shown during the render process. +--- Does **not** mutate `bufnrs`. +--- @param bufnrs integer[] +--- @return integer[] bufnrs the shown buffers +function buffer.hide(bufnrs) + local hide = config.options.hide + if hide.alternate or hide.current or hide.inactive or hide.visible then + local shown = {} + + for _, buffer_number in ipairs(bufnrs) do + local activity = activities[buffer.get_activity(buffer_number)] + if not hide[activity:lower()] then + table_insert(shown, buffer_number) end - - bufnrs = shown end - return bufnrs - end, -} + bufnrs = shown + end + + return bufnrs +end return buffer diff --git a/lua/barbar/config.lua b/lua/barbar/config.lua index a5e37a1e..cef171d5 100644 --- a/lua/barbar/config.lua +++ b/lua/barbar/config.lua @@ -1,8 +1,11 @@ local deepcopy = vim.deepcopy +local get_sign = vim.fn.sign_getdefined --- @type function +local severity = vim.diagnostic.severity local table_concat = table.concat local tbl_deep_extend = vim.tbl_deep_extend -local utils = require'barbar.utils' +local table_set = require('barbar.utils.table').set +local utils = require('barbar.utils') --- The prefix used for `utils.deprecate` local DEPRECATE_PREFIX = '\nThe barbar.nvim option ' @@ -31,6 +34,10 @@ local DEFAULT_OPTIONS = { tabpages = true, } +--- A cache for `sign_getdefined` +--- @type string[] +local diagnostic_sign_cache = {} + --- @class barbar.config.options.hide --- @field alternate? boolean --- @field current? boolean @@ -47,43 +54,66 @@ local DEFAULT_OPTIONS = { --- @field [2] barbar.config.options.icons.diagnostics.severity --- @field [3] barbar.config.options.icons.diagnostics.severity --- @field [4] barbar.config.options.icons.diagnostics.severity -local DEFAULT_DIAGNOSTIC_ICONS = { - [vim.diagnostic.severity.ERROR] = { enabled = false, icon = 'Ⓧ ' }, - [vim.diagnostic.severity.HINT] = { enabled = false, icon = '💡' }, - [vim.diagnostic.severity.INFO] = { enabled = false, icon = 'ⓘ ' }, - [vim.diagnostic.severity.WARN] = { enabled = false, icon = '⚠️ ' }, -} + +--- Deeply extend `icons` to include the `DEFAULT_DIAGNOSTIC_ICONS` +--- HACK: required because `vim.tbl_deep_extend` does not deep extend lists. +--- @param icons table +--- @see vim.tbl_deep_extend +local function tbl_deep_extend_diagnostic_icons(icons) + for i, name in ipairs(severity) do + local diagnostic_severity_icons = icons.diagnostics[i] + if diagnostic_severity_icons == nil then + diagnostic_severity_icons = {} + icons.diagnostics[i] = diagnostic_severity_icons + end + + if diagnostic_severity_icons.enabled == nil then + diagnostic_severity_icons.enabled = false + end + + if diagnostic_severity_icons.icon == nil then + local cached = diagnostic_sign_cache[i] + if cached then + diagnostic_severity_icons.icon = cached + else + local sign = get_sign('DiagnosticSign' .. name:sub(1, 1) .. name:sub(2):lower()) + diagnostic_severity_icons.icon = sign + diagnostic_sign_cache[i] = sign + end + end + end +end --- @class barbar.config.options.icons.buffer.filetype --- @field custom_colors? boolean if present, this color will be used for ALL filetype icons ---- @field enabled? boolean iff `true`, show the `devicons` for the associated buffer's `filetype`. +--- @field enabled boolean iff `true`, show the `devicons` for the associated buffer's `filetype`. --- @class barbar.config.options.icons.buffer.separator ---- @field left? string a buffer's left separator ---- @field right? string a buffer's right separator +--- @field left string a buffer's left separator +--- @field right string a buffer's right separator --- @class barbar.config.options.icons.scroll --- @field left string --- @field right string --- @class barbar.config.options.icons.buffer ---- @field buffer_index? boolean iff `true`, show the index of the associated buffer with respect to the ordering of the buffers in the tabline. ---- @field buffer_number? boolean iff `true`, show the `bufnr` for the associated buffer. ---- @field filename? boolean iff `true`, show the filename ---- @field button? false|string the button which is clicked to close / save a buffer, or indicate that it is pinned. ---- @field diagnostics? barbar.config.options.icons.buffer.diagnostics the diagnostic icons ---- @field filetype? barbar.config.options.icons.buffer.filetype filetype icon options ---- @field separator? barbar.config.options.icons.buffer.separator the left-hand separator between buffers in the tabline +--- @field buffer_index boolean iff `true`, show the index of the associated buffer with respect to the ordering of the buffers in the tabline. +--- @field buffer_number boolean iff `true`, show the `bufnr` for the associated buffer. +--- @field filename boolean iff `true`, show the filename +--- @field button false|string the button which is clicked to close / save a buffer, or indicate that it is pinned. +--- @field diagnostics barbar.config.options.icons.buffer.diagnostics the diagnostic icons +--- @field filetype barbar.config.options.icons.buffer.filetype filetype icon options +--- @field separator barbar.config.options.icons.buffer.separator the separators between buffers in the tabline --- @class barbar.config.options.icons.state: barbar.config.options.icons.buffer ---- @field modified? barbar.config.options.icons.buffer the icons used for an modified buffer ---- @field pinned? barbar.config.options.icons.buffer the icons used for a pinned buffer +--- @field modified barbar.config.options.icons.buffer the icons used for an modified buffer +--- @field pinned barbar.config.options.icons.buffer the icons used for a pinned buffer --- @class barbar.config.options.icons: barbar.config.options.icons.state ---- @field alternate? barbar.config.options.icons.state the icons used for an alternate buffer ---- @field current? barbar.config.options.icons.state the icons for the current buffer +--- @field alternate barbar.config.options.icons.state the icons used for an alternate buffer +--- @field current barbar.config.options.icons.state the icons for the current buffer --- @field inactive barbar.config.options.icons.state the icons for inactive buffers ---- @field visible? barbar.config.options.icons.state the icons for visible buffers +--- @field visible barbar.config.options.icons.state the icons for visible buffers --- @field scroll barbar.config.options.icons.scroll the scroll arrows local DEFAULT_ICONS = { buffer_index = false, @@ -157,7 +187,7 @@ local DEPRECATED_OPTIONS = { --- @field clickable boolean --- @field exclude_ft string[] --- @field exclude_name string[] ---- @field focus_on_close 'left'|'right' +--- @field focus_on_close side --- @field hide barbar.config.options.hide --- @field highlight_alternate boolean --- @field highlight_inactive_file_icons boolean @@ -174,7 +204,7 @@ local DEPRECATED_OPTIONS = { --- @field sidebar_filetypes {[string]: nil|barbar.config.options.sidebar_filetype} --- @field tabpages boolean ---- @class barbar.config +--- @class barbar.Config --- @field options barbar.config.options local config = { options = {} } @@ -206,7 +236,7 @@ function config.setup(options) for deprecated_option, new_option in pairs(DEPRECATED_OPTIONS) do local user_setting = options[deprecated_option] if user_setting then - utils.tbl_set(options, new_option, user_setting) + table_set(options, new_option, user_setting) utils.deprecate( DEPRECATE_PREFIX .. utils.markdown_inline_code(deprecated_option), utils.markdown_inline_code(table_concat(new_option, '.')) @@ -219,8 +249,8 @@ function config.setup(options) -- TODO: remove after v2 -- Edge case deprecated option if options.closable == false then - utils.tbl_set(options, { 'icons', 'button' }, false) - utils.tbl_set(options, { 'icons', 'modified', 'button' }, false) + table_set(options, { 'icons', 'button' }, false) + table_set(options, { 'icons', 'modified', 'button' }, false) utils.deprecate( DEPRECATE_PREFIX .. utils.markdown_inline_code'closable', utils.markdown_inline_code'icons.button' .. @@ -252,50 +282,39 @@ function config.setup(options) config.options = tbl_deep_extend('keep', options, DEFAULT_OPTIONS, { icons = default_icons }) - -- NOTE: we do this because `vim.tbl_deep_extend` doesn't deep copy lists - for i, default_diagnostic_severity_icons in ipairs(DEFAULT_DIAGNOSTIC_ICONS) do - local diagnostic_severity_icons = config.options.icons.diagnostics[i] or {} + do + local icons = config.options.icons - if diagnostic_severity_icons.enabled == nil then - diagnostic_severity_icons.enabled = default_diagnostic_severity_icons.enabled - end + --- `config.options.icons` without the recursive structure + --- @type barbar.config.options.icons.buffer + local base_options = { + buffer_index = icons.buffer_index, + buffer_number = icons.buffer_number, + filename = icons.filename, + button = icons.button, + diagnostics = icons.diagnostics, + filetype = icons.filetype, + separator = icons.separator, + } - if diagnostic_severity_icons.icon == nil then - diagnostic_severity_icons.icon = default_diagnostic_severity_icons.icon - end - end + local modified_icons = icons.modified or {} + local pinned_icons = icons.pinned or {} - local icons = config.options.icons - - --- `config.options.icons` without the recursive structure - --- @type barbar.config.options.icons.buffer - local base_options = { - buffer_index = icons.buffer_index, - buffer_number = icons.buffer_number, - filename = icons.filename, - button = icons.button, - diagnostics = icons.diagnostics, - filetype = icons.filetype, - separator = icons.separator, - } - - -- resolve all of the icons for the activities - for _, activity in ipairs { 'alternate', 'current', 'inactive', 'visible' } do - local activity_options = tbl_deep_extend('keep', config.options.icons[activity] or {}, base_options) - config.options.icons[activity] = activity_options - config.options.icons[activity].modified = tbl_deep_extend( - 'keep', - config.options.icons[activity].modified or {}, - config.options.icons.modified or {}, - activity_options - ) + diagnostic_sign_cache = {} - config.options.icons[activity].pinned = tbl_deep_extend( - 'keep', - config.options.icons[activity].pinned or {}, - config.options.icons.pinned or {}, - activity_options - ) + -- resolve all of the icons for the activities + for _, activity in ipairs { 'alternate', 'current', 'inactive', 'visible' } do + local activity_icons = tbl_deep_extend('keep', config.options.icons[activity] or {}, base_options) + tbl_deep_extend_diagnostic_icons(activity_icons) + + activity_icons.pinned = tbl_deep_extend('keep', activity_icons.pinned or {}, pinned_icons, activity_icons) + tbl_deep_extend_diagnostic_icons(activity_icons.pinned) + + activity_icons.modified = tbl_deep_extend('keep', activity_icons.modified or {}, modified_icons, activity_icons) + tbl_deep_extend_diagnostic_icons(activity_icons.modified) + + icons[activity] = activity_icons + end end end diff --git a/lua/barbar/events.lua b/lua/barbar/events.lua index 47858d1a..b92fe263 100644 --- a/lua/barbar/events.lua +++ b/lua/barbar/events.lua @@ -21,14 +21,15 @@ local tbl_isempty = vim.tbl_isempty local win_get_position = vim.api.nvim_win_get_position --- @type function local win_get_width = vim.api.nvim_win_get_width --- @type function -local api = require'barbar.api' -local bbye = require'barbar.bbye' -local config = require'barbar.config' -local highlight = require'barbar.highlight' -local JumpMode = require'barbar.jump_mode' -local render = require'barbar.ui.render' -local state = require'barbar.state' -local utils = require'barbar.utils' +local bdelete = require('barbar.bbye').bdelete +local config = require('barbar.config') +local highlight_reset_cache = require('barbar.utils.highlight').reset_cache +local highlight_setup = require('barbar.highlight').setup +local jump_mode = require('barbar.jump_mode') +local relative = require('barbar.fs').relative +local render = require('barbar.ui.render') +local set_offset = require('barbar.api').set_offset +local state = require('barbar.state') --- The `` used for the close click handler local CLOSE_CLICK_MODS = vim.api.nvim_cmd and { confirm = true } or 'confirm' @@ -36,7 +37,7 @@ local CLOSE_CLICK_MODS = vim.api.nvim_cmd and { confirm = true } or 'confirm' --- Whether barbar is currently set up to render. local enabled = false ---- @class barbar.events +--- @class barbar.Events local events = {} --- Create and reset autocommand groups associated with this plugin. @@ -60,7 +61,7 @@ function events.close_click_handler(buffer) buf_call(buffer, function() command('w') end) exec_autocmds('BufModifiedSet', {buffer = buffer}) else - bbye.bdelete(false, buffer, CLOSE_CLICK_MODS) + bdelete(false, buffer, CLOSE_CLICK_MODS) end end @@ -81,13 +82,13 @@ function events.enable() create_autocmd({'VimLeave'}, { callback = state.save_recently_closed, group = augroup_misc }) create_autocmd({'BufNewFile', 'BufReadPost'}, { - callback = function(tbl) JumpMode.assign_next_letter(tbl.buf) end, + callback = function(tbl) jump_mode.assign_next_letter(tbl.buf) end, group = augroup_misc, }) create_autocmd({'BufDelete', 'BufWipeout'}, { - callback = vim.schedule_wrap(function(tbl) - JumpMode.unassign_letter_for(tbl.buf) + callback = schedule_wrap(function(tbl) + jump_mode.unassign_letter_for(tbl.buf) state.push_recently_closed(tbl.file) render.update() end), @@ -96,8 +97,8 @@ function events.enable() create_autocmd('ColorScheme', { callback = function() - utils.hl.reset_cache() - highlight.setup() + highlight_reset_cache() + highlight_setup() end, group = augroup_misc, }) @@ -150,7 +151,7 @@ function events.enable() right = {}, --- @type {[string]: nil|integer} } - --- @param side 'left'|'right' + --- @param side side --- @return integer total_width local function total_widths(side) local offset = 0 @@ -168,7 +169,7 @@ function events.enable() create_autocmd('FileType', { callback = function(tbl) local bufwinid --- @type nil|integer - local side --- @type 'left'|'right' + local side --- @type side local autocmd = create_autocmd({'BufWinEnter', 'WinScrolled'}, { callback = function() if bufwinid == nil then @@ -187,7 +188,7 @@ function events.enable() if width ~= widths[ft] then widths[side][ft] = width widths[other_side][ft] = nil - api.set_offset(total_widths(side), option.text, nil, side) + set_offset(total_widths(side), option.text, nil, side) end end, group = augroup_render, @@ -197,7 +198,7 @@ function events.enable() buffer = tbl.buf, callback = function() widths[side][ft] = nil - api.set_offset(total_widths(side), nil, nil, side) + set_offset(total_widths(side), nil, nil, side) del_autocmd(autocmd) end, group = augroup_render, @@ -265,14 +266,14 @@ function events.enable() for _, bufnr in ipairs(state.buffers) do local name = buf_get_name(bufnr) if use_relative_file_paths then - name = utils.relative(name) + name = relative(name) end -- escape quotes table_insert(buffers, {name = name, pinned = state.is_pinned(bufnr)}) end - vim.g.Bufferline__session_restore = "lua require'barbar.state'.restore_buffers " .. + vim.g.Bufferline__session_restore = "lua require('barbar.state').restore_buffers " .. vim.inspect(buffers, {newline = ' ', indent = ''}) end, group = augroup_misc, @@ -318,7 +319,7 @@ function events.main_click_handler(bufnr, _, btn, _) -- NOTE: in Vimscript this was not `==`, it was a regex compare `=~` if btn == 'm' then - bbye.bdelete(false, bufnr) + bdelete(false, bufnr) else render.set_current_win_listed_buffer() set_current_buf(bufnr) @@ -331,8 +332,8 @@ end --- @return nil function events.on_option_changed(user_config) config.setup(user_config) -- NOTE: must be first `setup` called here - highlight.setup() - JumpMode.set_letters(config.options.letters) + highlight_setup() + jump_mode.set_letters(config.options.letters) -- Don't jump-start barbar if it is disabled if enabled then diff --git a/lua/barbar/fs.lua b/lua/barbar/fs.lua index 021066a6..b3b95695 100644 --- a/lua/barbar/fs.lua +++ b/lua/barbar/fs.lua @@ -2,33 +2,87 @@ -- fs.lua -- +local fnamemodify = vim.fn.fnamemodify --- @type function +--- @class barbar.Fs local fs = {} +--- Get +--- @param path string +--- @param hide_extension? boolean if `true`, exclude the extension of the file in the basename +--- @return string basename +function fs.basename(path, hide_extension) + return fnamemodify(path, hide_extension and ':t:r' or ':t') +end + +--- @param path string +--- @return boolean is_relative `true` if `path` is relative to the CWD +function fs.is_relative_path(path) + return fs.relative(path) == path +end + +--- @param filepath string +--- @param mode? openmode +--- @return string|nil error_message, any content +function fs.read(filepath, mode) + local file, open_err = io.open(filepath, mode or 'r') + + -- Ignore if the file doesn't exist or isn't readable + if open_err ~= nil then + return + elseif file == nil then + return + end + + local content, read_err = file:read('*a') + if read_err ~= nil then + return read_err + end + + do + local success, close_err = file:close() + if close_err ~= nil then + return close_err + elseif success == false then + return 'Error while closing ' .. filepath + end + end + + return nil, content +end + +--- @param path string +--- @return string relative_path +function fs.relative(path) + return fnamemodify(path, ':~:.') +end + --- @param filepath string --- @param content string ---- @param mode string ---- @return string | nil err +--- @param mode? openmode +--- @return string|nil error_message function fs.write(filepath, content, mode) - mode = mode or 'w' - local file, open_err = io.open(filepath, mode) + local file, open_err = io.open(filepath, mode or 'w') + if open_err ~= nil then return open_err elseif file == nil then - return 'Error while opening file' + return 'Error while opening ' .. filepath end - local _, write_err = file:write(content) - if write_err ~= nil then - return write_err + + do + local _, write_err = file:write(content) + if write_err ~= nil then + return write_err + end end local success, close_err = file:close() if close_err ~= nil then return close_err elseif success == false then - return 'Error while closing file' + return 'Error while closing ' .. filepath end - return nil end return fs diff --git a/lua/barbar/highlight.lua b/lua/barbar/highlight.lua index 541435f4..a8e8e180 100644 --- a/lua/barbar/highlight.lua +++ b/lua/barbar/highlight.lua @@ -1,8 +1,8 @@ -- !::exe [So] -local config = require 'barbar.config' -local hl = require'barbar.utils'.hl -local icons = require 'barbar.icons' +local config = require('barbar.config') +local hl = require('barbar.utils.highlight') +local icons = require('barbar.icons') -- Setup the highlight groups used by the plugin. hl.set_default_link('BufferAlternate', 'BufferDefaultAlternate') @@ -70,93 +70,93 @@ hl.set_default_link('BufferDefaultOffset', 'BufferTabpageFill') hl.set_default_link('BufferDefaultVisibleIcon', 'BufferVisible') hl.set_default_link('BufferDefaultVisibleNumber', 'BufferVisibleIndex') ---- @class barbar.highlight -local highlight = { - --- Setup the highlight groups for this plugin. - --- @return nil - setup = function() - local fg_current = hl.fg_or_default({'Normal'}, '#efefef', 255) - local fg_inactive = hl.fg_or_default({'TabLineFill'}, '#888888', 102) - local fg_target = {gui = 'red'} --- @type barbar.utils.hl.color - fg_target.cterm = fg_target.gui - - local fg_error = hl.fg_or_default({'ErrorMsg'}, '#A80000', 124) - local fg_hint = hl.fg_or_default({'HintMsg'}, '#D5508F', 168) - local fg_info = hl.fg_or_default({'InfoMsg'}, '#FFB7B7', 217) - local fg_warn = hl.fg_or_default({'WarningMsg'}, '#FF8900', 208) - - local fg_modified = hl.fg_or_default({'WarningMsg'}, '#E5AB0E', 178) - local fg_special = hl.fg_or_default({'Special'}, '#599eff', 75) - local fg_subtle = hl.fg_or_default({'NonText', 'Comment'}, '#555555', 240) - - local bg_current = hl.bg_or_default({'Normal'}, 'none') - local bg_inactive = hl.bg_or_default({'TabLineFill', 'StatusLine'}, 'none') - - -- Alternate: alternate buffer - -- Current: current buffer - -- Visible: visible but not current buffer - -- Inactive: invisible but not current buffer - -- -Icon: filetype icon - -- -Index: buffer index - -- -Number: buffer number - -- -Mod: when modified - -- -Sign: the separator between buffers - -- -Target: letter in buffer-picking mode - if config.options.highlight_alternate then - local fg_alternate = hl.fg_or_default({'TabLineFill'}, '#ead0a0', 223) - local bg_alternate = hl.bg_or_default({'TabLineSel', 'Normal'}, 'none') - - hl.set('BufferDefaultAlternate', bg_alternate, fg_alternate) - hl.set('BufferDefaultAlternateERROR', bg_alternate, fg_error) - hl.set('BufferDefaultAlternateHINT', bg_alternate, fg_hint) - hl.set('BufferDefaultAlternateIndex', bg_alternate, fg_special) - hl.set('BufferDefaultAlternateINFO', bg_alternate, fg_info) - hl.set('BufferDefaultAlternateMod', bg_alternate, fg_modified) - hl.set('BufferDefaultAlternateSign', bg_alternate, fg_special) - hl.set('BufferDefaultAlternateTarget', bg_alternate, fg_target, nil, {bold = true}) - hl.set('BufferDefaultAlternateWARN', bg_alternate, fg_warn) - end - - hl.set('BufferDefaultCurrent', bg_current, fg_current) - hl.set('BufferDefaultCurrentERROR', bg_current, fg_error) - hl.set('BufferDefaultCurrentHINT', bg_current, fg_hint) - hl.set('BufferDefaultCurrentIndex', bg_current, fg_special) - hl.set('BufferDefaultCurrentINFO', bg_current, fg_info) - hl.set('BufferDefaultCurrentMod', bg_current, fg_modified) - hl.set('BufferDefaultCurrentSign', bg_current, fg_special) - hl.set('BufferDefaultCurrentTarget', bg_current, fg_target, nil, {bold = true}) - hl.set('BufferDefaultCurrentWARN', bg_current, fg_warn) - - hl.set('BufferDefaultInactive', bg_inactive, fg_inactive) - hl.set('BufferDefaultInactiveERROR', bg_inactive, fg_error) - hl.set('BufferDefaultInactiveHINT', bg_inactive, fg_hint) - hl.set('BufferDefaultInactiveIndex', bg_inactive, fg_subtle) - hl.set('BufferDefaultInactiveINFO', bg_inactive, fg_info) - hl.set('BufferDefaultInactiveMod', bg_inactive, fg_modified) - hl.set('BufferDefaultInactiveSign', bg_inactive, fg_subtle) - hl.set('BufferDefaultInactiveTarget', bg_inactive, fg_target, nil, {bold = true}) - hl.set('BufferDefaultInactiveWARN', bg_inactive, fg_warn) - - hl.set('BufferDefaultTabpageFill', bg_inactive, fg_inactive) - hl.set('BufferDefaultTabpages', bg_inactive, fg_special, nil, {bold = true}) - - if config.options.highlight_visible then - local fg_visible = hl.fg_or_default({'TabLineSel'}, '#efefef', 255) - local bg_visible = hl.bg_or_default({'TabLineSel', 'Normal'}, 'none') - - hl.set('BufferDefaultVisible', bg_visible, fg_visible) - hl.set('BufferDefaultVisibleERROR', bg_visible, fg_error) - hl.set('BufferDefaultVisibleHINT', bg_visible, fg_hint) - hl.set('BufferDefaultVisibleIndex', bg_visible, fg_visible) - hl.set('BufferDefaultVisibleINFO', bg_visible, fg_info) - hl.set('BufferDefaultVisibleMod', bg_visible, fg_modified) - hl.set('BufferDefaultVisibleSign', bg_visible, fg_visible) - hl.set('BufferDefaultVisibleTarget', bg_visible, fg_target, nil, {bold = true}) - hl.set('BufferDefaultVisibleWARN', bg_visible, fg_warn) - end - - icons.set_highlights() +--- @class barbar.Highlight +local highlight = {} + +--- Setup the highlight groups for this plugin. +--- @return nil +function highlight.setup() + local fg_current = hl.fg_or_default({'Normal'}, '#efefef', 255) + local fg_inactive = hl.fg_or_default({'TabLineFill'}, '#888888', 102) + local fg_target = {gui = 'red'} --- @type barbar.utils.hl.color + fg_target.cterm = fg_target.gui + + local fg_error = hl.fg_or_default({'ErrorMsg'}, '#A80000', 124) + local fg_hint = hl.fg_or_default({'HintMsg'}, '#D5508F', 168) + local fg_info = hl.fg_or_default({'InfoMsg'}, '#FFB7B7', 217) + local fg_warn = hl.fg_or_default({'WarningMsg'}, '#FF8900', 208) + + local fg_modified = hl.fg_or_default({'WarningMsg'}, '#E5AB0E', 178) + local fg_special = hl.fg_or_default({'Special'}, '#599eff', 75) + local fg_subtle = hl.fg_or_default({'NonText', 'Comment'}, '#555555', 240) + + local bg_current = hl.bg_or_default({'Normal'}, 'none') + local bg_inactive = hl.bg_or_default({'TabLineFill', 'StatusLine'}, 'none') + + -- Alternate: alternate buffer + -- Current: current buffer + -- Visible: visible but not current buffer + -- Inactive: invisible but not current buffer + -- -Icon: filetype icon + -- -Index: buffer index + -- -Number: buffer number + -- -Mod: when modified + -- -Sign: the separator between buffers + -- -Target: letter in buffer-picking mode + if config.options.highlight_alternate then + local fg_alternate = hl.fg_or_default({'TabLineFill'}, '#ead0a0', 223) + local bg_alternate = hl.bg_or_default({'TabLineSel', 'Normal'}, 'none') + + hl.set('BufferDefaultAlternate', bg_alternate, fg_alternate) + hl.set('BufferDefaultAlternateERROR', bg_alternate, fg_error) + hl.set('BufferDefaultAlternateHINT', bg_alternate, fg_hint) + hl.set('BufferDefaultAlternateIndex', bg_alternate, fg_special) + hl.set('BufferDefaultAlternateINFO', bg_alternate, fg_info) + hl.set('BufferDefaultAlternateMod', bg_alternate, fg_modified) + hl.set('BufferDefaultAlternateSign', bg_alternate, fg_special) + hl.set('BufferDefaultAlternateTarget', bg_alternate, fg_target, nil, {bold = true}) + hl.set('BufferDefaultAlternateWARN', bg_alternate, fg_warn) end -} + + hl.set('BufferDefaultCurrent', bg_current, fg_current) + hl.set('BufferDefaultCurrentERROR', bg_current, fg_error) + hl.set('BufferDefaultCurrentHINT', bg_current, fg_hint) + hl.set('BufferDefaultCurrentIndex', bg_current, fg_special) + hl.set('BufferDefaultCurrentINFO', bg_current, fg_info) + hl.set('BufferDefaultCurrentMod', bg_current, fg_modified) + hl.set('BufferDefaultCurrentSign', bg_current, fg_special) + hl.set('BufferDefaultCurrentTarget', bg_current, fg_target, nil, {bold = true}) + hl.set('BufferDefaultCurrentWARN', bg_current, fg_warn) + + hl.set('BufferDefaultInactive', bg_inactive, fg_inactive) + hl.set('BufferDefaultInactiveERROR', bg_inactive, fg_error) + hl.set('BufferDefaultInactiveHINT', bg_inactive, fg_hint) + hl.set('BufferDefaultInactiveIndex', bg_inactive, fg_subtle) + hl.set('BufferDefaultInactiveINFO', bg_inactive, fg_info) + hl.set('BufferDefaultInactiveMod', bg_inactive, fg_modified) + hl.set('BufferDefaultInactiveSign', bg_inactive, fg_subtle) + hl.set('BufferDefaultInactiveTarget', bg_inactive, fg_target, nil, {bold = true}) + hl.set('BufferDefaultInactiveWARN', bg_inactive, fg_warn) + + hl.set('BufferDefaultTabpageFill', bg_inactive, fg_inactive) + hl.set('BufferDefaultTabpages', bg_inactive, fg_special, nil, {bold = true}) + + if config.options.highlight_visible then + local fg_visible = hl.fg_or_default({'TabLineSel'}, '#efefef', 255) + local bg_visible = hl.bg_or_default({'TabLineSel', 'Normal'}, 'none') + + hl.set('BufferDefaultVisible', bg_visible, fg_visible) + hl.set('BufferDefaultVisibleERROR', bg_visible, fg_error) + hl.set('BufferDefaultVisibleHINT', bg_visible, fg_hint) + hl.set('BufferDefaultVisibleIndex', bg_visible, fg_visible) + hl.set('BufferDefaultVisibleINFO', bg_visible, fg_info) + hl.set('BufferDefaultVisibleMod', bg_visible, fg_modified) + hl.set('BufferDefaultVisibleSign', bg_visible, fg_visible) + hl.set('BufferDefaultVisibleTarget', bg_visible, fg_target, nil, {bold = true}) + hl.set('BufferDefaultVisibleWARN', bg_visible, fg_warn) + end + + icons.set_highlights() +end return highlight diff --git a/lua/barbar/icons.lua b/lua/barbar/icons.lua index 2eaece27..95cfa7c8 100644 --- a/lua/barbar/icons.lua +++ b/lua/barbar/icons.lua @@ -9,8 +9,8 @@ local buf_get_option = vim.api.nvim_buf_get_option --- @type function local fnamemodify = vim.fn.fnamemodify --- @type function local hlexists = vim.fn.hlexists --- @type function -local utils = require'barbar.utils' -local hl = utils.hl +local hl = require('barbar.utils.highlight') +local utils = require('barbar.utils') --- @type boolean, {get_icon: fun(name: string, ext?: string, opts?: {default: nil|boolean}): string, string} local ok, web = pcall(require, 'nvim-web-devicons') @@ -37,62 +37,60 @@ end --- @type barbar.icons.group[] local hl_groups = {} ---- @class barbar.icons -local icons = { - --- Re-highlight all of the groups which have been set before. Checks for updated highlight groups. - --- @return nil - set_highlights = vim.schedule_wrap(function() - for _, group in ipairs(hl_groups) do - hl_buffer_icon(group.buffer_status, group.icon_hl) - end - end), - - get_icon = ok and - --- @param bufnr integer - --- @param buffer_activity barbar.buffer.activity.name - --- @return string icon, string highlight_group - function(bufnr, buffer_activity) - local basename, extension = '', '' - local filetype = buf_get_option(bufnr, 'filetype') - local icon_char, icon_hl = '', '' - - -- nvim-web-devicon only handles filetype icons, not other types (eg directory) - -- thus we need to do some work here - if filetype == 'netrw' or filetype == 'LuaTree' then - icon_char, icon_hl = '', 'Directory' +--- @class barbar.Icons +local icons = {} + +icons.get_icon = ok and + --- @param bufnr integer + --- @param buffer_activity barbar.buffer.activity.name + --- @return string icon, string highlight_group + function(bufnr, buffer_activity) + local basename, extension = '', '' + local filetype = buf_get_option(bufnr, 'filetype') + local icon_char, icon_hl = '', '' + + -- nvim-web-devicon only handles filetype icons, not other types (eg directory) + -- thus we need to do some work here + if filetype == 'netrw' or filetype == 'LuaTree' then + icon_char, icon_hl = '', 'Directory' + else + if filetype == 'fugitive' or filetype == 'gitcommit' then + basename, extension = 'git', 'git' else - if filetype == 'fugitive' or filetype == 'gitcommit' then - basename, extension = 'git', 'git' - else - basename = fnamemodify(buf_get_name(bufnr), ':t') - extension = fnamemodify(basename, ':e') - end - - icon_char, icon_hl = web.get_icon(basename, extension, { default = true }) + basename = fnamemodify(buf_get_name(bufnr), ':t') + extension = fnamemodify(basename, ':e') end - if icon_hl and hlexists(icon_hl .. buffer_activity) < 1 then - hl_buffer_icon(buffer_activity, icon_hl) - table_insert(hl_groups, {buffer_status = buffer_activity, icon_hl = icon_hl}) - end + icon_char, icon_hl = web.get_icon(basename, extension, { default = true }) + end - return icon_char, icon_hl .. buffer_activity - end or - --- @param buffer_activity barbar.buffer.activity.name - --- @return string icon, string highlight_group - function(_, buffer_activity) - local invalid_option = utils.markdown_inline_code'icons.filetype.enabled' - utils.notify_once( - 'barbar.nvim: ' .. invalid_option .. ' is set to ' .. utils.markdown_inline_code'true' .. - ' but ' .. utils.markdown_inline_code'nvim-dev-icons' .. ' was not found.' .. - '\nbarbar.nvim: icons have been disabled. Set ' .. invalid_option .. ' to ' .. - utils.markdown_inline_code'false' .. ' or ' .. 'install ' .. - utils.markdown_inline_code'nvim-dev-icons' .. 'to disable this message.', - vim.log.levels.WARN - ) - - return '', 'Buffer' .. buffer_activity .. 'Icon' - end, -} + if icon_hl and hlexists(icon_hl .. buffer_activity) < 1 then + hl_buffer_icon(buffer_activity, icon_hl) + table_insert(hl_groups, {buffer_status = buffer_activity, icon_hl = icon_hl}) + end + + return icon_char, icon_hl .. buffer_activity + end or + function(_, buffer_activity) + local invalid_option = utils.markdown_inline_code'icons.filetype.enabled' + utils.notify_once( + 'barbar.nvim: ' .. invalid_option .. ' is set to ' .. utils.markdown_inline_code'true' .. + ' but ' .. utils.markdown_inline_code'nvim-dev-icons' .. ' was not found.' .. + '\nbarbar.nvim: icons have been disabled. Set ' .. invalid_option .. ' to ' .. + utils.markdown_inline_code'false' .. ' or ' .. 'install ' .. + utils.markdown_inline_code'nvim-dev-icons' .. 'to disable this message.', + vim.log.levels.WARN + ) + + return '', 'Buffer' .. buffer_activity .. 'Icon' + end + +--- Re-highlight all of the groups which have been set before. Checks for updated highlight groups. +--- @return nil +icons.set_highlights = vim.schedule_wrap(function() + for _, group in ipairs(hl_groups) do + hl_buffer_icon(group.buffer_status, group.icon_hl) + end +end) return icons diff --git a/lua/barbar/jump_mode.lua b/lua/barbar/jump_mode.lua index 34c4987f..df6e090d 100644 --- a/lua/barbar/jump_mode.lua +++ b/lua/barbar/jump_mode.lua @@ -8,7 +8,7 @@ local split = vim.fn.split --- @type function local strcharpart = vim.fn.strcharpart --- @type function local strwidth = vim.api.nvim_strwidth --- @type function -local config = require'barbar.config' +local config = require('barbar.config') ---------------------------------------- -- Section: Buffer-picking mode state -- @@ -24,36 +24,36 @@ local letters = {} --- @field private letter_by_buffer {[integer]: string} a bi-directional map of buffer integers and their letters. --- @field private letter_status {[integer]: boolean} --- @field reinitialize boolean whether an `initialize_indexes` operation has been queued. -local JumpMode = {} +local jump_mode = {} --- Reset the module to a valid default state --- @return nil -function JumpMode.initialize_indexes() - JumpMode.buffer_by_letter = {} - JumpMode.index_by_letter = {} - JumpMode.letter_by_buffer = {} - JumpMode.letter_status = {} +function jump_mode.initialize_indexes() + jump_mode.buffer_by_letter = {} + jump_mode.index_by_letter = {} + jump_mode.letter_by_buffer = {} + jump_mode.letter_status = {} for index, letter in ipairs(letters) do - JumpMode.index_by_letter[letter] = index - JumpMode.letter_status[index] = false + jump_mode.index_by_letter[letter] = index + jump_mode.letter_status[index] = false end - JumpMode.reinitialize = false + jump_mode.reinitialize = false end --- Set the letters which can be used by jump mode. --- @param chars string --- @return nil -function JumpMode.set_letters(chars) +function jump_mode.set_letters(chars) letters = split(chars, [[\zs]]) - JumpMode.initialize_indexes() + jump_mode.initialize_indexes() end --- @param bufnr integer --- @return nil|string assigned -function JumpMode.assign_next_letter(bufnr) - if JumpMode.letter_by_buffer[bufnr] ~= nil then +function jump_mode.assign_next_letter(bufnr) + if jump_mode.letter_by_buffer[bufnr] ~= nil then return end @@ -64,14 +64,14 @@ function JumpMode.assign_next_letter(bufnr) for i = 1, strwidth(name) do local letter = strcharpart(name, i - 1, 1):lower() - if JumpMode.index_by_letter[letter] ~= nil then - local index = JumpMode.index_by_letter[letter] - local status = JumpMode.letter_status[index] + if jump_mode.index_by_letter[letter] ~= nil then + local index = jump_mode.index_by_letter[letter] + local status = jump_mode.letter_status[index] if status == false then - JumpMode.letter_status[index] = true + jump_mode.letter_status[index] = true -- letter = m.letters[index] - JumpMode.buffer_by_letter[letter] = bufnr - JumpMode.letter_by_buffer[bufnr] = letter + jump_mode.buffer_by_letter[letter] = bufnr + jump_mode.letter_by_buffer[bufnr] = letter return letter end end @@ -79,12 +79,12 @@ function JumpMode.assign_next_letter(bufnr) end -- Otherwise, assign a letter by usable order - for i, status in ipairs(JumpMode.letter_status) do + for i, status in ipairs(jump_mode.letter_status) do if status == false then local letter = letters[i] - JumpMode.letter_status[i] = true - JumpMode.buffer_by_letter[letter] = bufnr - JumpMode.letter_by_buffer[bufnr] = letter + jump_mode.letter_status[i] = true + jump_mode.buffer_by_letter[letter] = bufnr + jump_mode.letter_by_buffer[bufnr] = letter return letter end end @@ -92,35 +92,35 @@ end --- @param bufnr integer --- @return string letter assiegned to `bufnr` -function JumpMode.get_letter(bufnr) - return JumpMode.letter_by_buffer[bufnr] or JumpMode.assign_next_letter(bufnr) +function jump_mode.get_letter(bufnr) + return jump_mode.letter_by_buffer[bufnr] or jump_mode.assign_next_letter(bufnr) end --- @param letter string --- @return nil -function JumpMode.unassign_letter(letter) +function jump_mode.unassign_letter(letter) if letter == '' or letter == nil then return end - local index = JumpMode.index_by_letter[letter] + local index = jump_mode.index_by_letter[letter] - JumpMode.letter_status[index] = false + jump_mode.letter_status[index] = false - if JumpMode.buffer_by_letter[letter] ~= nil then - local bufnr = JumpMode.buffer_by_letter[letter] - JumpMode.buffer_by_letter[letter] = nil - JumpMode.letter_by_buffer[bufnr] = nil + if jump_mode.buffer_by_letter[letter] ~= nil then + local bufnr = jump_mode.buffer_by_letter[letter] + jump_mode.buffer_by_letter[letter] = nil + jump_mode.letter_by_buffer[bufnr] = nil end - JumpMode.reinitialize = true + jump_mode.reinitialize = true end --- Unassign the letter which is assigned to `bufnr.` --- @param bufnr integer --- @return nil -function JumpMode.unassign_letter_for(bufnr) - JumpMode.unassign_letter(JumpMode.letter_by_buffer[bufnr]) +function jump_mode.unassign_letter_for(bufnr) + jump_mode.unassign_letter(jump_mode.letter_by_buffer[bufnr]) end -return JumpMode +return jump_mode diff --git a/lua/barbar/state.lua b/lua/barbar/state.lua index 404e21d2..bc4ae752 100644 --- a/lua/barbar/state.lua +++ b/lua/barbar/state.lua @@ -20,9 +20,10 @@ local tbl_contains = vim.tbl_contains local tbl_filter = vim.tbl_filter local tbl_map = vim.tbl_map -local Buffer = require'barbar.buffer' -local config = require'barbar.config' -local utils = require'barbar.utils' +local buffer = require('barbar.buffer') +local config = require('barbar.config') +local fs = require('barbar.fs') +local utils = require('barbar.utils') local CACHE_PATH = vim.fn.stdpath('cache') .. '/barbar.json' @@ -48,7 +49,7 @@ local CACHE_PATH = vim.fn.stdpath('cache') .. '/barbar.json' --- @field left barbar.state.offset.side --- @field right barbar.state.offset.side ---- @class barbar.state +--- @class barbar.State --- @field buffers integer[] the open buffers, in visual order. --- @field data_by_bufnr {[integer]: barbar.state.data} the buffer data indexed on buffer number --- @field is_picking_buffer boolean whether the user is currently in jump-mode @@ -98,7 +99,7 @@ function state.get_buffer_list() not tbl_contains(exclude_ft, buf_get_option(bufnr, 'filetype')) then local name = buf_get_name(bufnr) - if not tbl_contains(exclude_name, utils.basename(name, hide_extensions)) then + if not tbl_contains(exclude_name, fs.basename(name, hide_extensions)) then table_insert(result, bufnr) end end @@ -196,7 +197,7 @@ function state.update_names() -- Compute names for i, buffer_n in ipairs(state.buffers) do - local name = Buffer.get_name(buffer_n, hide_extensions) + local name = buffer.get_name(buffer_n, hide_extensions) if buffer_index_by_name[name] == nil then buffer_index_by_name[name] = i @@ -205,7 +206,7 @@ function state.update_names() local other_i = buffer_index_by_name[name] local other_n = state.buffers[other_i] local new_name, new_other_name = - Buffer.get_unique_name( + buffer.get_unique_name( buf_get_name(buffer_n), buf_get_name(state.buffers[other_i])) @@ -230,7 +231,7 @@ function state.set_offset(width, text, hl) utils.markdown_inline_code'barbar.api.set_offset' ) - require'barbar.api'.set_offset(width, text, hl) + require('barbar.api').set_offset(width, text, hl) end --- Restore the buffers @@ -269,50 +270,18 @@ end --- Save recently_closed list --- @return nil function state.save_recently_closed() - local file, open_err = io.open(CACHE_PATH, 'w') - if open_err ~= nil then - return utils.notify(open_err, vim.log.levels.ERROR) - elseif file == nil then - return utils.notify('Could not open ' .. CACHE_PATH, vim.log.levels.ERROR) - end - do - local _, write_err = file:write(json_encode({ - recently_closed = state.recently_closed, - })) - if write_err ~= nil then - return utils.notify(write_err, vim.log.levels.ERROR) - end - end - local success, close_err = file:close() - if close_err ~= nil then - return utils.notify(close_err, vim.log.levels.ERROR) - elseif success == false then - return utils.notify('Could not close ' .. CACHE_PATH, vim.log.levels.ERROR) + local err_msg = fs.write(CACHE_PATH, json_encode({ recently_closed = state.recently_closed })) + if err_msg then + utils.notify(err_msg, vim.log.levels.WARN) end end --- Save recently_closed list --- @return nil function state.load_recently_closed() - local file, open_err = io.open(CACHE_PATH, 'r') - - -- Ignore if the file doesn't exist or isn't readable - if open_err ~= nil then - return - elseif file == nil then - return - end - - local content, read_err = file:read('*a') - if read_err ~= nil then - return utils.notify(read_err, vim.log.levels.ERROR) - end - - local success, close_err = file:close() - if close_err ~= nil then - return - elseif success == false then - return + local err_msg, content = fs.read(CACHE_PATH) + if err_msg then + utils.notify(err_msg, vim.log.levels.WARN) end local ok, result = pcall(json_decode, content, {luanil = {array = true, object = true}}) diff --git a/lua/barbar/types.lua b/lua/barbar/types.lua new file mode 100644 index 00000000..c3534fcd --- /dev/null +++ b/lua/barbar/types.lua @@ -0,0 +1,3 @@ +--- @meta + +--- @alias side 'left'|'right' diff --git a/lua/barbar/ui/layout.lua b/lua/barbar/ui/layout.lua index c05af191..e97637c7 100644 --- a/lua/barbar/ui/layout.lua +++ b/lua/barbar/ui/layout.lua @@ -12,10 +12,10 @@ local get_option = vim.api.nvim_get_option --- @type function local strwidth = vim.api.nvim_strwidth --- @type function local tabpagenr = vim.fn.tabpagenr --- @type function -local Buffer = require'barbar.buffer' -local config = require'barbar.config' -local icons = require'barbar.icons' -local state = require'barbar.state' +local buffer = require('barbar.buffer') +local config = require('barbar.config') +local get_icon = require('barbar.icons').get_icon +local state = require('barbar.state') --- The number of sides of each buffer in the tabline. local SIDES_OF_BUFFER = 2 @@ -49,23 +49,21 @@ local SPACE_LEN = #' ' --- @class barbar.ui.layout.data.tabpages --- @field width integer the amount of space allocated to the tabpage indicator - ---- @class barbar.ui.layout +--- @class barbar.ui.Layout --- @field buffers integer[] different from `state.buffers` in that the `hide` option is respected. Only updated when calling `calculate_buffers_width`. -local Layout = { buffers = {} } +local layout = { buffers = {} } --- Calculate the current layout of the bufferline. --- @return barbar.ui.layout.data -function Layout.calculate() - +function layout.calculate() local total_width = get_option('columns') local left_width = state.offset.left.width local right_width = state.offset.right.width - local tabpages_width = Layout.calculate_tabpages_width() + local tabpages_width = layout.calculate_tabpages_width() local buffers_width = total_width - state.offset.left.width - state.offset.right.width - tabpages_width - local pinned_count, pinned_sum, unpinned_sum, widths = Layout.calculate_buffers_width() + local pinned_count, pinned_sum, unpinned_sum, widths = layout.calculate_buffers_width() local pinned_width = pinned_sum + (pinned_count * config.options.minimum_padding * SIDES_OF_BUFFER) local unpinned_allocated_width = buffers_width - pinned_width @@ -113,22 +111,20 @@ end --- @param bufnr integer the buffer to calculate the width of --- @param index integer the buffer's numerical index --- @return integer width -function Layout.calculate_buffer_width(bufnr, index) +function layout.calculate_buffer_width(bufnr, index) local buffer_data = state.get_buffer_data(bufnr) if buffer_data.closing then return buffer_data.width or buffer_data.computed_width or 0 end - local buffer_activity = Buffer.activities[Buffer.get_activity(bufnr)] - local buffer_name = buffer_data.name or '[no name]' - - local icons_option = Buffer.get_icons(buffer_activity, buf_get_option(bufnr, 'modified'), buffer_data.pinned) + local buffer_activity = buffer.activities[buffer.get_activity(bufnr)] + local icons_option = buffer.get_icons(buffer_activity, buf_get_option(bufnr, 'modified'), buffer_data.pinned) local width = strwidth(icons_option.separator.left) local filename_enabled = icons_option.filename if filename_enabled then - width = width + strwidth(buffer_name) + width = width + strwidth(buffer_data.name or '[no name]') end if icons_option.buffer_index then @@ -140,7 +136,7 @@ function Layout.calculate_buffer_width(bufnr, index) end if icons_option.filetype.enabled then - local file_icon = icons.get_icon(bufnr, buffer_activity) + local file_icon = get_icon(bufnr, buffer_activity) width = width + strwidth(file_icon) if filename_enabled then @@ -148,7 +144,7 @@ function Layout.calculate_buffer_width(bufnr, index) end end - Buffer.for_each_counted_enabled_diagnostic(bufnr, icons_option.diagnostics, function(count, _, option) + buffer.for_each_counted_enabled_diagnostic(bufnr, icons_option.diagnostics, function(count, _, option) width = width + SPACE_LEN + strwidth(option.icon) + #tostring(count) end) @@ -161,25 +157,25 @@ function Layout.calculate_buffer_width(bufnr, index) end --- @return {[integer]: integer} position_by_bufnr -function Layout.calculate_buffers_position_by_buffer_number() - local layout = Layout.calculate() +function layout.calculate_buffers_position_by_buffer_number() + local data = layout.calculate() local positions = {} local pinned_position = 0 - local unpinned_position = layout.buffers.pinned_width + local unpinned_position = data.buffers.pinned_width - for i, buffer_number in ipairs(Layout.buffers) do + for i, buffer_number in ipairs(layout.buffers) do if state.is_pinned(buffer_number) then positions[buffer_number] = pinned_position - pinned_position = pinned_position + Layout.calculate_width( - layout.buffers.base_widths[i], + pinned_position = pinned_position + layout.calculate_width( + data.buffers.base_widths[i], config.options.minimum_padding ) else positions[buffer_number] = unpinned_position - unpinned_position = unpinned_position + Layout.calculate_width( - layout.buffers.base_widths[i], - layout.buffers.padding + unpinned_position = unpinned_position + layout.calculate_width( + data.buffers.base_widths[i], + data.buffers.padding ) end end @@ -189,16 +185,16 @@ end --- Calculate the width of the buffers --- @return integer pinned_count, integer pinned_sum, integer unpinned_sum, integer[] widths -function Layout.calculate_buffers_width() - Layout.buffers = Buffer.hide(state.buffers) +function layout.calculate_buffers_width() + layout.buffers = buffer.hide(state.buffers) local pinned_count = 0 local pinned_sum = 0 local unpinned_sum = 0 local widths = {} - for i, bufnr in ipairs(Layout.buffers) do - local width = Layout.calculate_buffer_width(bufnr, i) + for i, bufnr in ipairs(layout.buffers) do + local width = layout.calculate_buffer_width(bufnr, i) if state.is_pinned(bufnr) then pinned_count = pinned_count + 1 pinned_sum = pinned_sum + width @@ -214,7 +210,7 @@ end --- The number of characters needed to represent the tabpages. --- @return integer width -function Layout.calculate_tabpages_width() +function layout.calculate_tabpages_width() if not config.options.tabpages then return 0 end @@ -231,8 +227,8 @@ end --- @param base_width integer --- @param padding_width integer --- @return integer width -function Layout.calculate_width(base_width, padding_width) +function layout.calculate_width(base_width, padding_width) return base_width + (padding_width * SIDES_OF_BUFFER) end -return Layout +return layout diff --git a/lua/barbar/ui/nodes.lua b/lua/barbar/ui/nodes.lua index ca1ab3af..fb2f0ad5 100644 --- a/lua/barbar/ui/nodes.lua +++ b/lua/barbar/ui/nodes.lua @@ -1,34 +1,34 @@ -- --- nodes.lua +-- node_list.lua -- local table_insert = table.insert local strcharpart = vim.fn.strcharpart --- @type function local strwidth = vim.api.nvim_strwidth --- @type function ---- @class barbar.ui.Nodes ---- @see barbar.ui.node --- Operations on `node`s. -local Nodes = {} +--- @see barbar.ui.node +--- @class barbar.ui.Nodes +local nodes = {} ---- Sums the width of the nodes ---- @param nodes barbar.ui.node[] +--- Sums the width of the node_list +--- @param node_list barbar.ui.node[] --- @return integer -function Nodes.width(nodes) +function nodes.width(node_list) local result = 0 - for _, node in ipairs(nodes) do + for _, node in ipairs(node_list) do result = result + strwidth(node.text) end return result end ---- Concatenates some `nodes` into a valid tabline string. ---- @param nodes barbar.ui.node[] +--- Concatenates some `node_list` into a valid tabline string. +--- @param node_list barbar.ui.node[] --- @return string -function Nodes.to_string(nodes) +function nodes.to_string(node_list) local result = '' - for _, node in ipairs(nodes) do + for _, node in ipairs(node_list) do -- NOTE: We have to escape the text in case it contains '%', which is a special character to the -- tabline. -- To escape '%', we make it '%%'. It just so happens that '%' is also a special character @@ -39,46 +39,46 @@ function Nodes.to_string(nodes) return result end ---- Concatenates some `nodes` into a raw string. ---- @param nodes barbar.ui.node[] +--- Concatenates some `node_list` into a raw string. +--- @param node_list barbar.ui.node[] --- For debugging purposes. --- @return string -function Nodes.to_raw_string(nodes) +function nodes.to_raw_string(node_list) local result = '' - for _, node in ipairs(nodes) do + for _, node in ipairs(node_list) do result = result .. node.text end return result end ---- Insert `other` into `nodes` at the `position`. ---- @param nodes barbar.ui.node[] +--- Insert `other` into `node_list` at the `position`. +--- @param node_list barbar.ui.node[] --- @param position integer --- @return barbar.ui.node[] with_insertions -function Nodes.insert(nodes, position, node) - return Nodes.insert_many(nodes, position, { node }) +function nodes.insert(node_list, position, node) + return nodes.insert_many(node_list, position, { node }) end ---- Insert `others` into `nodes` at the `position`. ---- @param nodes barbar.ui.node[] +--- Insert `others` into `node_list` at the `position`. +--- @param node_list barbar.ui.node[] --- @param position integer --- @param others barbar.ui.node[] --- @return barbar.ui.node[] with_insertions -function Nodes.insert_many(nodes, position, others) +function nodes.insert_many(node_list, position, others) if position < 0 then - local others_width = Nodes.width(others) + local others_width = nodes.width(others) local others_end = position + others_width if others_end < 0 then - return nodes + return node_list end local available_width = others_end position = 0 - others = Nodes.slice_left(others, available_width) + others = nodes.slice_left(others, available_width) end @@ -87,8 +87,8 @@ function Nodes.insert_many(nodes, position, others) local new_nodes = {} local i = 1 - while i <= #nodes do - local node = nodes[i] + while i <= #node_list do + local node = node_list[i] local node_width = strwidth(node.text) -- While we haven't found the position... @@ -109,7 +109,7 @@ function Nodes.insert_many(nodes, position, others) }) end - -- Add new other nodes + -- Add new other node_list local others_width = 0 for _, other in ipairs(others) do local other_width = strwidth(other.text) @@ -119,10 +119,10 @@ function Nodes.insert_many(nodes, position, others) local end_position = position + others_width - -- Then, resume adding previous nodes + -- Then, resume adding previous node_list -- table.insert(new_nodes, 'then') - while i <= #nodes do - local previous_node = nodes[i] + while i <= #node_list do + local previous_node = node_list[i] local previous_node_width = strwidth(previous_node.text) local previous_node_start_position = current_position local previous_node_end_position = current_position + previous_node_width @@ -150,15 +150,15 @@ function Nodes.insert_many(nodes, position, others) return new_nodes end ---- Select from `nodes` while fitting within the provided `width`, discarding all indices larger than the last index that fits. +--- Select from `node_list` while fitting within the provided `width`, discarding all indices larger than the last index that fits. --- @param width integer --- @return barbar.ui.node[] sliced -function Nodes.slice_right(nodes, width) +function nodes.slice_right(node_list, width) local accumulated_width = 0 local new_nodes = {} - for _, node in ipairs(nodes) do + for _, node in ipairs(node_list) do local text_width = strwidth(node.text) accumulated_width = accumulated_width + text_width @@ -174,17 +174,17 @@ function Nodes.slice_right(nodes, width) return new_nodes end ---- Select from `nodes` in reverse while fitting within the provided `width`, discarding all indices less than the last index that fits. ---- @param nodes barbar.ui.node[] +--- Select from `node_list` in reverse while fitting within the provided `width`, discarding all indices less than the last index that fits. +--- @param node_list barbar.ui.node[] --- @param width integer --- @return barbar.ui.node[] sliced -function Nodes.slice_left(nodes, width) +function nodes.slice_left(node_list, width) local accumulated_width = 0 local new_nodes = {} - for i = #nodes, 1, -1 do - local node = nodes[i] --- @type barbar.ui.node (it cannot be `nil`) + for i = #node_list, 1, -1 do + local node = node_list[i] --- @type barbar.ui.node (it cannot be `nil`) local text_width = strwidth(node.text) accumulated_width = accumulated_width + text_width @@ -201,4 +201,4 @@ function Nodes.slice_left(nodes, width) return new_nodes end -return Nodes +return nodes diff --git a/lua/barbar/ui/render.lua b/lua/barbar/ui/render.lua index fd32fe74..e0bd41ae 100644 --- a/lua/barbar/ui/render.lua +++ b/lua/barbar/ui/render.lua @@ -5,6 +5,7 @@ local max = math.max local min = math.min local table_insert = table.insert +local table_remove = table.remove local buf_get_option = vim.api.nvim_buf_get_option --- @type function local buf_is_valid = vim.api.nvim_buf_is_valid --- @type function @@ -26,16 +27,17 @@ local tbl_contains = vim.tbl_contains local tbl_filter = vim.tbl_filter local win_get_buf = vim.api.nvim_win_get_buf --- @type function -local animate = require'barbar.animate' -local Buffer = require'barbar.buffer' -local config = require'barbar.config' --- local fs = require'barbar.fs' -- For debugging purposes -local Nodes = require'barbar.ui.nodes' -local icons = require'barbar.icons' -local JumpMode = require'barbar.jump_mode' -local Layout = require'barbar.ui.layout' -local state = require'barbar.state' -local utils = require'barbar.utils' +local animate = require('barbar.animate') +local buffer = require('barbar.buffer') +local config = require('barbar.config') +-- local fs = require('barbar.fs') -- For debugging purposes +local get_icon = require('barbar.icons').get_icon +local get_letter = require('barbar.jump_mode').get_letter +local index_of = require('barbar.utils.list').index_of +local layout = require('barbar.ui.layout') +local nodes = require('barbar.ui.nodes') +local notify = require('barbar.utils').notify +local state = require('barbar.state') --- Last value for tabline --- @type string @@ -65,7 +67,7 @@ local ANIMATION = { --- @field target integer the place where the bufferline is scrolled/wants to scroll to. local scroll = { current = 0, target = 0 } ---- @class barbar.ui.render +--- @class barbar.ui.Render local render = {} --- An incremental animation for `close_buffer_animated`. @@ -127,15 +129,15 @@ end --- Opens a buffer with animation. --- @param bufnr integer ---- @param layout barbar.ui.layout.data +--- @param data barbar.ui.layout.data --- @return nil -local function open_buffer_start_animation(layout, bufnr) +local function open_buffer_start_animation(data, bufnr) local buffer_data = state.get_buffer_data(bufnr) - local index = utils.index_of(Layout.buffers, bufnr) + local index = index_of(layout.buffers, bufnr) - buffer_data.computed_width = Layout.calculate_width( - layout.buffers.base_widths[index] or Layout.calculate_buffer_width(bufnr, #Layout.buffers + 1), - layout.buffers.padding + buffer_data.computed_width = layout.calculate_width( + data.buffers.base_widths[index] or layout.calculate_buffer_width(bufnr, #layout.buffers + 1), + data.buffers.padding ) local target_width = buffer_data.computed_width or 0 @@ -158,7 +160,7 @@ local function open_buffers(new_buffers) -- Open next to the currently opened tab -- Find the new index where the tab will be inserted - local new_index = utils.index_of(state.buffers, state.last_current_buffer) + local new_index = index_of(state.buffers, state.last_current_buffer) if new_index ~= nil then new_index = new_index + 1 else @@ -169,7 +171,7 @@ local function open_buffers(new_buffers) -- Insert the buffers where they go for _, new_buffer in ipairs(new_buffers) do - if utils.index_of(state.buffers, new_buffer) == nil then + if index_of(state.buffers, new_buffer) == nil then local actual_index = new_index local should_insert_at_end = config.options.insert_at_end or @@ -208,10 +210,10 @@ local function open_buffers(new_buffers) -- Update names because they affect the layout state.update_names() - local layout = Layout.calculate() + local data = layout.calculate() for _, buffer_number in ipairs(new_buffers) do - open_buffer_start_animation(layout, buffer_number) + open_buffer_start_animation(data, buffer_number) end end @@ -303,6 +305,7 @@ local function set_scroll_tick(new_scroll, animation) if animation.running == false then scroll_animation = nil end + render.update(nil, false) end @@ -330,7 +333,10 @@ end --- @param s? string --- @return nil function render.set_tabline(s) - s = s or '' + if s == nil then + s = '' + end + if last_tabline ~= s then last_tabline = s set_option('tabline', s) @@ -339,54 +345,54 @@ function render.set_tabline(s) end --- Compute the buffer hl-groups ---- @param layout barbar.ui.layout.data +--- @param data barbar.ui.layout.data --- @param bufnrs integer[] --- @param refocus? boolean ---- @return barbar.ui.container[] pinned_groups, barbar.ui.container[] clumps -local function get_bufferline_containers(layout, bufnrs, refocus) +--- @return barbar.ui.container[] pinend, barbar.ui.container[] unpinned, nil|{[1]: integer, pinned: boolean} current_buffer_index +local function get_bufferline_containers(data, bufnrs, refocus) local click_enabled = has('tablineat') and config.options.clickable local accumulated_pinned_width = 0 --- the width of pinned buffers accumulated while iterating local accumulated_unpinned_width = 0 --- the width of buffers accumulated while iterating - local current_buffer_index = nil --- @type nil|integer + local current_buffer_index = nil --- @type nil|{[1]: integer, pinned: boolean} local done = false --- if all of the visible buffers have been clumped local containers = {} --- @type barbar.ui.container[] local pinned_containers = {} --- @type barbar.ui.container[] local pinned_pad_text = (' '):rep(config.options.minimum_padding) - local unpinned_pad_text = (' '):rep(layout.buffers.padding) + local unpinned_pad_text = (' '):rep(data.buffers.padding) for i, bufnr in ipairs(bufnrs) do - local activity = Buffer.get_activity(bufnr) - local activity_name = Buffer.activities[activity] + local activity = buffer.get_activity(bufnr) + local activity_name = buffer.activities[activity] local buffer_data = state.get_buffer_data(bufnr) local modified = buf_get_option(bufnr, 'modified') local pinned = buffer_data.pinned if pinned then buffer_data.computed_position = accumulated_pinned_width - buffer_data.computed_width = Layout.calculate_width(layout.buffers.base_widths[i], config.options.minimum_padding) + buffer_data.computed_width = layout.calculate_width(data.buffers.base_widths[i], config.options.minimum_padding) else - buffer_data.computed_position = accumulated_unpinned_width + layout.buffers.pinned_width - buffer_data.computed_width = Layout.calculate_width(layout.buffers.base_widths[i], layout.buffers.padding) + buffer_data.computed_position = accumulated_unpinned_width + data.buffers.pinned_width + buffer_data.computed_width = layout.calculate_width(data.buffers.base_widths[i], data.buffers.padding) end local container_width = buffer_data.width or buffer_data.computed_width - if activity == Buffer.activities.Current and refocus ~= false then - current_buffer_index = i + if activity == buffer.activities.Current and refocus ~= false then + current_buffer_index = {i, pinned = pinned} local start = accumulated_unpinned_width local end_ = accumulated_unpinned_width + container_width if scroll.target > start then render.set_scroll(start) - elseif scroll.target + layout.buffers.unpinned_allocated_width < end_ then - render.set_scroll(scroll.target + (end_ - (scroll.target + layout.buffers.unpinned_allocated_width))) + elseif scroll.target + data.buffers.unpinned_allocated_width < end_ then + render.set_scroll(scroll.target + (end_ - (scroll.target + data.buffers.unpinned_allocated_width))) end end - local scroll_current = min(scroll.current, layout.buffers.scroll_max) + local scroll_current = min(scroll.current, data.buffers.scroll_max) if pinned then accumulated_pinned_width = accumulated_pinned_width + container_width @@ -396,7 +402,7 @@ local function get_bufferline_containers(layout, bufnrs, refocus) if accumulated_unpinned_width < scroll_current then goto continue -- HACK: there is no `continue` keyword elseif (refocus == false or (refocus ~= false and current_buffer_index ~= nil)) and - accumulated_unpinned_width - scroll_current > layout.buffers.unpinned_allocated_width + accumulated_unpinned_width - scroll_current > data.buffers.unpinned_allocated_width then done = true end @@ -405,7 +411,7 @@ local function get_bufferline_containers(layout, bufnrs, refocus) local buffer_name = buffer_data.name or '[no name]' local buffer_hl = wrap_hl('Buffer' .. activity_name .. (modified and 'Mod' or '')) - local icons_option = Buffer.get_icons(activity_name, modified, pinned) + local icons_option = buffer.get_icons(activity_name, modified, pinned) --- Prefix this value to allow an element to be clicked local clickable = click_enabled and ('%' .. bufnr .. '@barbar#events#main_click_handler@') or '' @@ -452,7 +458,7 @@ local function get_bufferline_containers(layout, bufnrs, refocus) local icon = { hl = clickable, text = '' } if state.is_picking_buffer then - local letter = JumpMode.get_letter(bufnr) + local letter = get_letter(bufnr) -- Replace first character of buf name with jump letter if letter and not icons_option.filetype.enabled then @@ -469,7 +475,7 @@ local function get_bufferline_containers(layout, bufnrs, refocus) jump_letter.text = ' ' end elseif icons_option.filetype.enabled then - local iconChar, iconHl = icons.get_icon(bufnr, activity_name) + local iconChar, iconHl = get_icon(bufnr, activity_name) local hlName = (activity_name == 'Inactive' and not config.options.highlight_inactive_file_icons) and 'BufferInactive' or iconHl @@ -489,7 +495,6 @@ local function get_bufferline_containers(layout, bufnrs, refocus) local padding = { hl = buffer_hl, text = pinned and pinned_pad_text or unpinned_pad_text } local container = { --- @type barbar.ui.container - activity = activity, nodes = { left_separator, padding, buffer_index, buffer_number, icon, jump_letter, name }, --- @diagnostic disable-next-line:assign-type-mismatch it is assigned just earlier position = buffer_data.position or buffer_data.computed_position, @@ -497,7 +502,7 @@ local function get_bufferline_containers(layout, bufnrs, refocus) width = container_width, } - Buffer.for_each_counted_enabled_diagnostic(bufnr, icons_option.diagnostics, function(count, idx, option) + buffer.for_each_counted_enabled_diagnostic(bufnr, icons_option.diagnostics, function(count, idx, option) table_insert(container.nodes, { hl = wrap_hl('Buffer' .. activity_name .. severity[idx]), text = ' ' .. option.icon .. count, @@ -517,7 +522,7 @@ local function get_bufferline_containers(layout, bufnrs, refocus) ::continue:: end - return pinned_containers, containers + return pinned_containers, containers, current_buffer_index end local HL = { @@ -532,8 +537,8 @@ local HL = { --- @param refocus? boolean if `true`, the bufferline will be refocused on the current buffer (default: `true`) --- @return nil|string syntax local function generate_tabline(bufnrs, refocus) - local layout = Layout.calculate() - local pinned_containers, containers = get_bufferline_containers(layout, bufnrs, refocus) + local data = layout.calculate() + local pinned, unpinned, current_buffer_index = get_bufferline_containers(data, bufnrs, refocus) -- Create actual tabline string local result = '' @@ -546,102 +551,89 @@ local function generate_tabline(bufnrs, refocus) local content = { { hl = hl, text = state.offset.left.text } } local content_max_width = state.offset.left.width - 2 - offset_nodes = - Nodes.insert_many( - offset_nodes, - 1, - Nodes.slice_right(content, content_max_width)) - - result = result .. Nodes.to_string(offset_nodes) + offset_nodes = nodes.insert_many(offset_nodes, 1, nodes.slice_right(content, content_max_width)) + result = result .. nodes.to_string(offset_nodes) end -- Buffer tabs do --- @type barbar.ui.container - local content = { { hl = HL.FILL, text = (' '):rep(layout.buffers.width) } } + local content = { { hl = HL.FILL, text = (' '):rep(data.buffers.width) } } do - local current_container = nil - for _, container in ipairs(containers) do - -- We insert the current buffer after the others so it's always on top - if container.activity ~= Buffer.activities.Current then - content = Nodes.insert_many( - content, - container.position - scroll.current, - container.nodes) - else - current_container = container - end + local current_container + if current_buffer_index ~= nil and current_buffer_index.pinned == false then + current_container = table_remove(unpinned, current_buffer_index[1]) + end + + for _, container in ipairs(unpinned) do + content = nodes.insert_many(content, container.position - scroll.current, container.nodes) end if current_container ~= nil then - local container = current_container - content = Nodes.insert_many( - content, - container.position - scroll.current, - container.nodes) + content = nodes.insert_many(content, current_container.position - scroll.current, current_container.nodes) end end do local inactive_separator = config.options.icons.inactive.separator.left - if inactive_separator ~= nil and #containers > 0 and - layout.buffers.unpinned_width + strwidth(inactive_separator) <= layout.buffers.unpinned_allocated_width + if inactive_separator ~= nil and #unpinned > 0 and + data.buffers.unpinned_width + strwidth(inactive_separator) <= data.buffers.unpinned_allocated_width then - content = Nodes.insert( - content, - layout.buffers.used_width, - { text = inactive_separator, hl = HL.SIGN_INACTIVE }) + content = nodes.insert(content, data.buffers.used_width, { text = inactive_separator, hl = HL.SIGN_INACTIVE }) end end - if #pinned_containers > 0 then - local current_container = nil - for _, container in ipairs(pinned_containers) do - if container.activity ~= Buffer.activities.Current then - content = Nodes.insert_many(content, container.position, container.nodes) - else - current_container = container - end + if #pinned > 0 then + local current_container + if current_buffer_index ~= nil and current_buffer_index.pinned == true then + current_container = table_remove(pinned, current_buffer_index[1]) end + + for _, container in ipairs(pinned) do + content = nodes.insert_many(content, container.position - scroll.current, container.nodes) + end + if current_container ~= nil then - local container = current_container - content = Nodes.insert_many(content, container.position, container.nodes) + content = nodes.insert_many(content, current_container.position - scroll.current, current_container.nodes) end end - local filler = { { hl = HL.FILL, text = (' '):rep(layout.buffers.width) } } - content = Nodes.insert_many(filler, 0, content) - content = Nodes.slice_right(content, layout.buffers.width) + local filler = { { hl = HL.FILL, text = (' '):rep(data.buffers.width) } } + content = nodes.insert_many(filler, 0, content) + content = nodes.slice_right(content, data.buffers.width) local has_left_scroll = scroll.current > 0 if has_left_scroll then - content = Nodes.insert(content, layout.buffers.pinned_width, - { hl = HL.SCROLL_ARROW, text = config.options.icons.scroll.left }) + content = nodes.insert(content, data.buffers.pinned_width, { + hl = HL.SCROLL_ARROW, + text = config.options.icons.scroll.left, + }) end - local has_right_scroll = layout.buffers.used_width - scroll.current > layout.buffers.width + local has_right_scroll = data.buffers.used_width - scroll.current > data.buffers.width if has_right_scroll then - content = Nodes.insert(content, layout.buffers.width - 1, - { hl = HL.SCROLL_ARROW, text = config.options.icons.scroll.right }) + content = nodes.insert(content, data.buffers.width - 1, { + hl = HL.SCROLL_ARROW, + text = config.options.icons.scroll.right, + }) end -- Render bufferline string - result = result .. Nodes.to_string(content) + result = result .. nodes.to_string(content) - -- Prevent the expansion of the last click node + -- Prevent the expansion of the last click group if config.options.clickable then result = result .. '%0@barbar#events#main_click_handler@' end end -- Tabpages - do - if layout.tabpages.width > 0 then - result = result .. Nodes.to_string({ - { hl = HL.TABPAGES, text = ' ' .. tabpagenr() .. '/' .. tabpagenr('$') .. ' ', }, - }) - end + if data.tabpages.width > 0 then + result = result .. nodes.to_string({{ + hl = HL.TABPAGES, + text = ' ' .. tabpagenr() .. '/' .. tabpagenr('$') .. ' ', + }}) end -- Right offset @@ -652,13 +644,9 @@ local function generate_tabline(bufnrs, refocus) local content = { { hl = hl, text = state.offset.right.text } } local content_max_width = state.offset.right.width - 2 - offset_nodes = - Nodes.insert_many( - offset_nodes, - 1, - Nodes.slice_right(content, content_max_width)) + offset_nodes = nodes.insert_many(offset_nodes, 1, nodes.slice_right(content, content_max_width)) - result = result .. Nodes.to_string(offset_nodes) + result = result .. nodes.to_string(offset_nodes) end -- NOTE: For development or debugging purposes, the following code can be used: @@ -683,7 +671,7 @@ function render.update(update_names, refocus) return end - local buffers = Buffer.hide(render.get_updated_buffers(update_names)) + local buffers = buffer.hide(render.get_updated_buffers(update_names)) -- Auto hide/show if applicable if config.options.auto_hide then @@ -717,7 +705,7 @@ function render.update(update_names, refocus) if not ok then command('BarbarDisable') - utils.notify( + notify( "Barbar detected an error while running. Barbar disabled itself :/ " .. "Include this in your report: " .. tostring(result), diff --git a/lua/barbar/ui/types.lua b/lua/barbar/ui/types.lua index bf88e3a3..bdaa51d9 100644 --- a/lua/barbar/ui/types.lua +++ b/lua/barbar/ui/types.lua @@ -5,7 +5,6 @@ --- @field text string the content being rendered --- @class barbar.ui.container ---- @field activity barbar.buffer.activity --- @field nodes barbar.ui.node[] --- @field position integer --- @field width integer diff --git a/lua/barbar/utils.lua b/lua/barbar/utils.lua index fe8cde61..0df00ed5 100644 --- a/lua/barbar/utils.lua +++ b/lua/barbar/utils.lua @@ -2,345 +2,45 @@ -- utils.lua -- -local table_insert = table.insert - -local fnamemodify = vim.fn.fnamemodify --- @type function -local get_hl = vim.api.nvim_get_hl --- @type function -local get_hl_by_name = vim.api.nvim_get_hl_by_name --- @type function -local hlexists = vim.fn.hlexists --- @type function -local list_slice = vim.list_slice local notify = vim.notify local notify_once = vim.notify_once -local set_hl = vim.api.nvim_set_hl --- @type function -local tbl_isempty = vim.tbl_isempty - ---- @type {[string]: table} -local hl_groups_cache = {} ---- Remove `tbl[key]` from `tbl` and return it ---- @param tbl table ---- @param key string ---- @return any -local function tbl_remove_key (tbl, key) - local value = rawget(tbl, key) - rawset(tbl, key, nil) - return value -end +--- @class barbar.Utils +local utils = {} -local get_hl_util = get_hl and - --- @param name string - --- @return table - function(name) return get_hl(0, {link = false, name = name}) end or +utils.deprecate = vim.deprecate and + --- Notify a user that something has been deprecated, and that there is an alternative. --- @param name string - --- @return table - function(name) - if hlexists(name) < 1 then - return {} - end - - local definition = get_hl_by_name(name, true) - for original, new in pairs {background = 'bg', foreground = 'fg', special = 'sp'} do - local attribute_definition = tbl_remove_key(definition, original) - if attribute_definition then - definition[new] = attribute_definition - end - end - - local cterm = get_hl_by_name(name, false) - definition.ctermfg = cterm.foreground - definition.ctermbg = cterm.background - - return definition - end - ---- @param group string the groups to source the color from. ---- @return table -local function get_hl_cached(group) - local hl_cached = hl_groups_cache[group] - if hl_cached then - return hl_cached - end - - local hl = get_hl_util(group) - hl_groups_cache[group] = hl - return hl -end - ---- Generate a color. ---- @param groups string[] the groups to source the color from. ---- @param attribute 'bg'|'ctermbg'|'ctermfg'|'fg'|'sp' where to look for the color. ---- @param default barbar.utils.hl.color.value a color name (`string`), GUI hex (`string`), or cterm color code (`integer`). ---- @return barbar.utils.hl.color.value color -local function get_hl_color_or_default(groups, attribute, default) - for _, group in ipairs(groups) do - local hl_attribute = get_hl_cached(group)[attribute] - if hl_attribute then - return hl_attribute - end + --- @param alternative string + --- @return nil + function(name, alternative) + vim.deprecate(name, alternative, '2.0.0', 'barbar.nvim') + end or + function(name, alternative) + utils.notify_once(name .. ' is deprecated. Use ' .. alternative .. 'instead.', vim.log.levels.WARN) end - return default +--- Return "\``s`\`" +--- @param s string +--- @return string inline_code +function utils.markdown_inline_code(s) + return '`' .. s .. '`' end ---- Return the index of element `n` in `list. ---- @generic T ---- @param list T[] ---- @param t T ---- @return nil|integer index -local function index_of(list, t) - for i, value in ipairs(list) do - if value == t then - return i - end - end - return nil +--- Use `vim.notify` with a `msg` and log `level`. Integrates with `nvim-notify`. +--- @param msg string +--- @param level 0|1|2|3|4|5 +--- @return nil +function utils.notify(msg, level) + notify(msg, level, {title = 'barbar.nvim'}) end --- Use `vim.notify` with a `msg` and log `level`. Integrates with `nvim-notify`. --- @param msg string --- @param level 0|1|2|3|4|5 --- @return nil -local function notify_once_util(msg, level) +function utils.notify_once(msg, level) notify_once(msg, level, {title = 'barbar.nvim'}) end ---- @param path string ---- @return string relative_path -local function relative(path) - return fnamemodify(path, ':~:.') -end - ---- @class barbar.utils -local utils = { - --- @param path string - --- @param hide_extension? boolean if `true`, exclude the extension of the file in the basename - --- @return string basename - basename = function(path, hide_extension) - local modifier = ':t' - - if hide_extension then - modifier = modifier .. ':r' - end - - return fnamemodify(path, modifier) - end, - - deprecate = vim.deprecate and - --- Notify a user that something has been deprecated, and that there is an alternative. - --- @param name string - --- @param alternative string - --- @return nil - function(name, alternative) - vim.deprecate(name, alternative, '2.0.0', 'barbar.nvim') - end or - function(name, alternative) - notify_once_util(name .. ' is deprecated. Use ' .. alternative .. 'instead.', vim.log.levels.WARN) - end, - - --- utilities for working with highlight groups. - --- @class barbar.utils.hl - hl = { - --- @class barbar.utils.hl.attributes see |:h attr-list| - --- @field blend? integer 0–100 - --- @field bold? boolean - --- @field default? boolean - --- @field italic? boolean - --- @field nocombine? boolean - --- @field reverse? boolean - --- @field standout? boolean - --- @field strikethrough? boolean - --- @field undercurl? boolean - --- @field underdashed? boolean - --- @field underdotted? boolean - --- @field underdouble? boolean - --- @field underline? boolean - - --- @alias barbar.utils.hl.color.value integer|string - - --- @class barbar.utils.hl.color - --- @field cterm barbar.utils.hl.color.value - --- @field gui barbar.utils.hl.color.value - - --- Generate a color. - --- @param groups string[] the groups to source the color from. - --- @return nil|barbar.utils.hl.attributes - attributes = function(groups) - for _, group in ipairs(groups) do - local hl = get_hl_cached(group) - if not tbl_isempty(hl) then - return hl - end - end - end, - - --- Generate a background color. - --- @param groups string[] the groups to source the background color from. - --- @param default barbar.utils.hl.color.value the background color to use if no `groups` have a valid background color. - --- @param default_cterm? barbar.utils.hl.color.value the color to use if no `groups` have a valid color and `termguicolors == false`. - --- @return barbar.utils.hl.color color - bg_or_default = function(groups, default, default_cterm) - return { - cterm = get_hl_color_or_default(groups, 'ctermbg', default_cterm or default), - gui = get_hl_color_or_default(groups, 'bg', default), - } - end, - - --- Generate a foreground color. - --- @param groups string[] the groups to source the foreground color from. - --- @param default barbar.utils.hl.color.value the foreground color to use if no `groups` have a valid foreground color. - --- @param default_cterm? barbar.utils.hl.color.value the color to use if no `groups` have a valid color and `termguicolors == false`. - --- @return barbar.utils.hl.color color - fg_or_default = function(groups, default, default_cterm) - return { - cterm = get_hl_color_or_default(groups, 'ctermfg', default_cterm or default), - gui = get_hl_color_or_default(groups, 'fg', default), - } - end, - - --- Reset the `nvim_get_hl` cache - reset_cache = function() hl_groups_cache = {} end, - - --- Set some highlight `group`'s default definition with respect to `&termguicolors` - --- @param group string the name of the highlight group to set - --- @param bg barbar.utils.hl.color - --- @param fg barbar.utils.hl.color - --- @param sp? barbar.utils.hl.color.value - --- @param attributes? barbar.utils.hl.attributes whether the highlight group should be bolded - --- @return nil - set = function(group, bg, fg, sp, attributes) - if not attributes then - attributes = {} - end - - attributes.bg = bg.gui - attributes.ctermbg = bg.cterm - attributes.ctermfg = fg.cterm - attributes.fg = fg.gui - attributes.sp = sp - attributes[vim.type_idx] = nil - - set_hl(0, group, attributes) - end, - - --- Set the default highlight `group_name` as a link to `link_name` - --- @param group_name string the name of the group to by-default be linked to `link_name` - --- @param link_name string the name of the group to by-default link `group_name` to - --- @return nil - set_default_link = function(group_name, link_name) - set_hl(0, group_name, {default = true, link = link_name}) - end, - - --- Generate a foreground color. - --- @param groups string[] the groups to source the foreground color from. - --- @param default string the foreground color to use if no `groups` have a valid foreground color. - --- @return barbar.utils.hl.color.value color - sp_or_default = function(groups, default) - return get_hl_color_or_default(groups, 'sp', default) - end, - }, - - index_of = index_of, - - --- @param path string - --- @return boolean is_relative `true` if `path` is relative to the CWD - is_relative_path = function(path) - return relative(path) == path - end, - - --- Reverse the order of elements in some `list`. - --- @generic T - --- @param list T[] - --- @return T[] reversed - list_reverse = function(list) - local reversed = {} - for i = #list, 1, -1 do - table_insert(reversed, list[i]) - end - return reversed - end, - - --- Run `vim.list_slice` on some `list`, `index`ed from the end of the list. - --- @generic T - --- @param list T[] - --- @param index_from_end number - --- @return T[] sliced - list_slice_from_end = function(list, index_from_end) - return list_slice(list, #list - index_from_end + 1) - end, - - --- Return "\``s`\`" - --- @param s string - --- @return string inline_code - markdown_inline_code = function(s) - return '`' .. s .. '`' - end, - - --- Use `vim.notify` with a `msg` and log `level`. Integrates with `nvim-notify`. - --- @param msg string - --- @param level 0|1|2|3|4|5 - --- @return nil - notify = function(msg, level) - notify(msg, level, {title = 'barbar.nvim'}) - end, - - notify_once = notify_once_util, - - relative = relative, - - --- Set `fallback` as a secondary source of keys when indexing `tbl`. Example: - --- - --- ```lua - --- local fallback = {bar = true} - --- local tbl = {} - --- - --- print(tbl.bar) -- `nil` - --- setfallbacktable(tbl, fallback) - --- print(tbl.bar) -- `true` - --- - --- tbl.bar = false - --- print(tbl.bar, fallback.bar) -- `false`, `true` - --- ``` - --- - --- WARN: this mutates `tbl`! - --- @param tbl? table - --- @param fallback table - --- @return table tbl corresponding to the `tbl` parameter - setfallbacktable = function(tbl, fallback) - return setmetatable(tbl or {}, {__index = fallback}) - end, - - tbl_remove_key = tbl_remove_key, - - --- Set a `value` in a `tbl` multiple `keys` deep. - -- - --- ```lua - --- assert(vim.deep_equal( - --- {a = {b = {c = 'd'}}}, - --- tbl_set({}, {'a', 'b', 'c'}, 'd') - --- )) - --- ``` - --- - --- WARN: this mutates `tbl`! - --- - --- @generic T - --- @param tbl table - --- @param keys table - --- @param value T - --- @return nil - tbl_set = function(tbl, keys, value) - local current = tbl - for i = 1, #keys - 1 do - local key = keys[i] - - if current[key] == nil then - current[key] = {} - end - - current = current[key] - end - - current[keys[#keys]] = value - end, -} - return utils diff --git a/lua/barbar/utils/highlight.lua b/lua/barbar/utils/highlight.lua new file mode 100644 index 00000000..b60795ae --- /dev/null +++ b/lua/barbar/utils/highlight.lua @@ -0,0 +1,169 @@ +local get_hl = vim.api.nvim_get_hl --- @type function +local get_hl_by_name = vim.api.nvim_get_hl_by_name --- @type function +local hlexists = vim.fn.hlexists --- @type function +local set_hl = vim.api.nvim_set_hl --- @type function +local tbl_isempty = vim.tbl_isempty + +local tbl = require('barbar.utils.table') + +--- @type {[string]: nil|table} +local hl_groups_cache = {} + +local get_hl_util = get_hl and + --- @param name string + --- @return table + function(name) return get_hl(0, {link = false, name = name}) end or + function(name) + if hlexists(name) < 1 then + return {} + end + + local definition = get_hl_by_name(name, true) + for original, new in pairs {background = 'bg', foreground = 'fg', special = 'sp'} do + local attribute_definition = tbl.remove_key(definition, original) + if attribute_definition then + definition[new] = attribute_definition + end + end + + local cterm = get_hl_by_name(name, false) + definition.ctermfg = cterm.foreground + definition.ctermbg = cterm.background + + return definition + end + +--- @param group string the groups to source the color from. +--- @return table +local function get_hl_cached(group) + do + local hl_cached = hl_groups_cache[group] + if hl_cached then + return hl_cached + end + end + + local hl = get_hl_util(group) + hl_groups_cache[group] = hl + return hl +end + +--- Generate a color. +--- @param groups string[] the groups to source the color from. +--- @param attribute 'bg'|'ctermbg'|'ctermfg'|'fg'|'sp' where to look for the color. +--- @param default barbar.utils.hl.color.value a color name (`string`), GUI hex (`string`), or cterm color code (`integer`). +--- @return barbar.utils.hl.color.value color +local function get_hl_color_or_default(groups, attribute, default) + for _, group in ipairs(groups) do + local hl_attribute = get_hl_cached(group)[attribute] + if hl_attribute then + return hl_attribute + end + end + + return default +end + +--- @class barbar.utils.hl.attributes see |:h attr-list| +--- @field blend? integer 0–100 +--- @field bold? boolean +--- @field default? boolean +--- @field italic? boolean +--- @field nocombine? boolean +--- @field reverse? boolean +--- @field standout? boolean +--- @field strikethrough? boolean +--- @field undercurl? boolean +--- @field underdashed? boolean +--- @field underdotted? boolean +--- @field underdouble? boolean +--- @field underline? boolean + +--- @alias barbar.utils.hl.color.value integer|string + +--- @class barbar.utils.hl.color +--- @field cterm barbar.utils.hl.color.value +--- @field gui barbar.utils.hl.color.value + +--- utilities for working with highlight groups. +--- @class barbar.utils.Hl +local hl = {} + +--- Generate a color. +--- @param groups string[] the groups to source the color from. +--- @return nil|barbar.utils.hl.attributes +function hl.attributes(groups) + for _, group in ipairs(groups) do + local cached = get_hl_cached(group) + if not tbl_isempty(cached) then + return cached + end + end +end + +--- Generate a background color. +--- @param groups string[] the groups to source the background color from. +--- @param default barbar.utils.hl.color.value the background color to use if no `groups` have a valid background color. +--- @param default_cterm? barbar.utils.hl.color.value the color to use if no `groups` have a valid color and `termguicolors == false`. +--- @return barbar.utils.hl.color color +function hl.bg_or_default(groups, default, default_cterm) + return { + cterm = get_hl_color_or_default(groups, 'ctermbg', default_cterm or default), + gui = get_hl_color_or_default(groups, 'bg', default), + } +end + +--- Generate a foreground color. +--- @param groups string[] the groups to source the foreground color from. +--- @param default barbar.utils.hl.color.value the foreground color to use if no `groups` have a valid foreground color. +--- @param default_cterm? barbar.utils.hl.color.value the color to use if no `groups` have a valid color and `termguicolors == false`. +--- @return barbar.utils.hl.color color +function hl.fg_or_default(groups, default, default_cterm) + return { + cterm = get_hl_color_or_default(groups, 'ctermfg', default_cterm or default), + gui = get_hl_color_or_default(groups, 'fg', default), + } +end + +--- Reset the `nvim_get_hl` cache +function hl.reset_cache() hl_groups_cache = {} end + +--- Set some highlight `group`'s default definition with respect to `&termguicolors` +--- @param group string the name of the highlight group to set +--- @param bg barbar.utils.hl.color +--- @param fg barbar.utils.hl.color +--- @param sp? barbar.utils.hl.color.value +--- @param attributes? barbar.utils.hl.attributes whether the highlight group should be bolded +--- @return nil +function hl.set(group, bg, fg, sp, attributes) + if not attributes then + attributes = {} + end + + attributes.bg = bg.gui + attributes.ctermbg = bg.cterm + attributes.ctermfg = fg.cterm + attributes.fg = fg.gui + attributes.sp = sp + attributes[vim.type_idx] = nil + + set_hl(0, group, attributes) +end + +--- Set the default highlight `group_name` as a link to `link_name` +--- @param group_name string the name of the group to by-default be linked to `link_name` +--- @param link_name string the name of the group to by-default link `group_name` to +--- @return nil +function hl.set_default_link(group_name, link_name) + set_hl(0, group_name, {default = true, link = link_name}) +end + +--- Generate a foreground color. +--- @param groups string[] the groups to source the foreground color from. +--- @param default string the foreground color to use if no `groups` have a valid foreground color. +--- @return barbar.utils.hl.color.value color +function hl.sp_or_default(groups, default) + return get_hl_color_or_default(groups, 'sp', default) +end + +return hl diff --git a/lua/barbar/utils/list.lua b/lua/barbar/utils/list.lua new file mode 100644 index 00000000..19128267 --- /dev/null +++ b/lua/barbar/utils/list.lua @@ -0,0 +1,43 @@ +local table_insert = table.insert + +local list_slice = vim.list_slice + +--- @class barbar.utils.List +local list = {} + +--- Return the index of element `n` in `list. +--- @generic T +--- @param tbl T[] +--- @param t T +--- @return nil|integer index +function list.index_of(tbl, t) + for i, value in ipairs(tbl) do + if value == t then + return i + end + end + return nil +end + +--- reverse the order of elements in some `list`. +--- perf: don't do `ipairs(list_reverse(list))`, just do `for i = #list, 1, -1` instead. +--- @generic t +--- @param tbl t[] +function list.reverse(tbl) + local reversed = {} + for i = #tbl, 1, -1 do + table_insert(reversed, tbl[i]) + end + return reversed +end + +--- Run `vim.list_slice` on some `list`, `index`ed from the end of the list. +--- @generic T +--- @param tbl T[] +--- @param index_from_end number +--- @return T[] sliced +function list.slice_from_end(tbl, index_from_end) + return list_slice(tbl, #tbl - index_from_end + 1) +end + +return list diff --git a/lua/barbar/utils/table.lua b/lua/barbar/utils/table.lua new file mode 100644 index 00000000..a5daab62 --- /dev/null +++ b/lua/barbar/utils/table.lua @@ -0,0 +1,45 @@ +--- @class barbar.utils.Table +local table = {} + +--- Remove `tbl[key]` from `tbl` and return it +--- @param tbl table +--- @param key string +--- @return any +function table.remove_key(tbl, key) + local value = rawget(tbl, key) + rawset(tbl, key, nil) + return value +end + +--- Set a `value` in a `tbl` multiple `keys` deep. +-- +--- ```lua +--- assert(vim.deep_equal( +--- {a = {b = {c = 'd'}}}, +--- tbl_set({}, {'a', 'b', 'c'}, 'd') +--- )) +--- ``` +--- +--- WARN: this mutates `tbl`! +--- +--- @generic T +--- @param tbl table +--- @param keys table +--- @param value T +--- @return nil +function table.set(tbl, keys, value) + local current = tbl + for i = 1, #keys - 1 do + local key = keys[i] + + if current[key] == nil then + current[key] = {} + end + + current = current[key] + end + + current[keys[#keys]] = value +end + +return table diff --git a/lua/bufferline.lua b/lua/bufferline.lua index e3b5e09a..f48d72ae 100644 --- a/lua/bufferline.lua +++ b/lua/bufferline.lua @@ -1 +1 @@ -return require'barbar' +return require('barbar') diff --git a/lua/bufferline/animate.lua b/lua/bufferline/animate.lua index 2efb7fca..65236720 100644 --- a/lua/bufferline/animate.lua +++ b/lua/bufferline/animate.lua @@ -1 +1 @@ -return require'barbar.animate' +return require('barbar.animate') diff --git a/lua/bufferline/api.lua b/lua/bufferline/api.lua index 18a336d0..46b7eed0 100644 --- a/lua/bufferline/api.lua +++ b/lua/bufferline/api.lua @@ -1 +1 @@ -return require'barbar.api' +return require('barbar.api') diff --git a/lua/bufferline/bbye.lua b/lua/bufferline/bbye.lua index 3951c0c3..85b77475 100644 --- a/lua/bufferline/bbye.lua +++ b/lua/bufferline/bbye.lua @@ -1 +1 @@ -return require'barbar.bbye' +return require('barbar.bbye') diff --git a/lua/bufferline/buffer.lua b/lua/bufferline/buffer.lua index 1f647b12..ce4521cd 100644 --- a/lua/bufferline/buffer.lua +++ b/lua/bufferline/buffer.lua @@ -1 +1 @@ -return require'barbar.buffer' +return require('barbar.buffer') diff --git a/lua/bufferline/config.lua b/lua/bufferline/config.lua index 396d0bac..8e4b3deb 100644 --- a/lua/bufferline/config.lua +++ b/lua/bufferline/config.lua @@ -1 +1 @@ -return require'barbar.config' +return require('barbar.config') diff --git a/lua/bufferline/events.lua b/lua/bufferline/events.lua index 9672a499..015486ba 100644 --- a/lua/bufferline/events.lua +++ b/lua/bufferline/events.lua @@ -1 +1 @@ -return require'barbar.events' +return require('barbar.events') diff --git a/lua/bufferline/highlight.lua b/lua/bufferline/highlight.lua index 74077ec3..ac763e9a 100644 --- a/lua/bufferline/highlight.lua +++ b/lua/bufferline/highlight.lua @@ -1 +1 @@ -return require'barbar.highlight' +return require('barbar.highlight') diff --git a/lua/bufferline/icons.lua b/lua/bufferline/icons.lua index 7deca167..570b8dae 100644 --- a/lua/bufferline/icons.lua +++ b/lua/bufferline/icons.lua @@ -1 +1 @@ -return require'barbar.icons' +return require('barbar.icons') diff --git a/lua/bufferline/jump_mode.lua b/lua/bufferline/jump_mode.lua index 5910663e..f0916772 100644 --- a/lua/bufferline/jump_mode.lua +++ b/lua/bufferline/jump_mode.lua @@ -1 +1 @@ -return require'barbar.jump_mode' +return require('barbar.jump_mode') diff --git a/lua/bufferline/layout.lua b/lua/bufferline/layout.lua index 5926ef18..27aa5844 100644 --- a/lua/bufferline/layout.lua +++ b/lua/bufferline/layout.lua @@ -1 +1 @@ -return require'barbar.ui.layout' +return require('barbar.ui.layout') diff --git a/lua/bufferline/render.lua b/lua/bufferline/render.lua index dcdb3a69..58e7629b 100644 --- a/lua/bufferline/render.lua +++ b/lua/bufferline/render.lua @@ -1,4 +1,4 @@ -local render = require('barbar.ui.render') +local render = vim.deepcopy(require('barbar.ui.render')) --- @deprecated use `state.restore_buffers` instead render.restore_buffers = require('barbar.state').restore_buffers diff --git a/lua/bufferline/state.lua b/lua/bufferline/state.lua index 2ccace91..1e5461e3 100644 --- a/lua/bufferline/state.lua +++ b/lua/bufferline/state.lua @@ -1 +1 @@ -return require'barbar.state' +return require('barbar.state') diff --git a/lua/bufferline/utils.lua b/lua/bufferline/utils.lua index 958187b3..c1e6ccd0 100644 --- a/lua/bufferline/utils.lua +++ b/lua/bufferline/utils.lua @@ -1 +1,25 @@ -return require'barbar.utils' +local utils = vim.deepcopy(require('barbar.utils')) + +do + local fs = require('barbar.fs') + utils.basename = fs.basename + utils.is_relative_path = fs.is_relative_path + utils.relative = fs.relative +end + +utils.hl = require('barbar.utils.hl') + +do + local list = require('barbar.utils.list') + utils.index_of = list.index_of + utils.list_reverse = list.reverse + utils.list_slice_from_end = list.slice_from_end +end + +do + local tbl = require('barbar.utils.table') + utils.tbl_remove_key = tbl.remove_key + utils.tbl_set = tbl.set +end + +return utils diff --git a/plugin/barbar.lua b/plugin/barbar.lua index bb74c874..d99b339a 100644 --- a/plugin/barbar.lua +++ b/plugin/barbar.lua @@ -8,12 +8,12 @@ if vim.g.barbar_auto_setup ~= false then local options = vim.g.bufferline if options then - require'barbar.utils'.notify_once( - "`g:bufferline` is deprecated, use `require'barbar'.setup` instead. " .. + require('barbar.utils').notify_once( + "`g:bufferline` is deprecated, use `require('barbar').setup` instead. " .. 'See `:h barbar-setup` for more information.', vim.log.levels.WARN ) end - require'barbar'.setup(options) + require('barbar').setup(options) end