diff --git a/plugins/by-name/dropbar/default.nix b/plugins/by-name/dropbar/default.nix new file mode 100644 index 00000000..5aaa849e --- /dev/null +++ b/plugins/by-name/dropbar/default.nix @@ -0,0 +1,51 @@ +{ lib, ... }: +lib.nixvim.plugins.mkNeovimPlugin { + name = "dropbar"; + packPathName = "dropbar.nvim"; + package = "dropbar-nvim"; + + maintainers = [ lib.maintainers.GaetanLepage ]; + + description = '' + A polished, IDE-like, highly-customizable winbar for Neovim with drop-down menu support and + multiple backends. + ''; + + settingsOptions = import ./settings-options.nix lib; + + settingsExample = { + bar = { + enable = true; + sources.__raw = '' + function(buf, _) + local sources = require('dropbar.sources') + return { + require('dropbar.utils').source.fallback({ + sources.lsp, + sources.treesitter, + sources.markdown, + }) + } + end + ''; + }; + menu.keymaps = { + h = "q"; + l.__raw = '' + function() + local dropbar = require('dropbar') + local utils = require('dropbar.utils') + local menu = utils.menu.get_current() + if not menu then + return + end + local cursor = vim.api.nvim_win_get_cursor(menu.win) + local component = menu.entries[cursor[1]]:first_clickable(cursor[2]) + if component then + menu:click_on(component, nil, 1, 'l') + end + end + ''; + }; + }; +} diff --git a/plugins/by-name/dropbar/settings-options.nix b/plugins/by-name/dropbar/settings-options.nix new file mode 100644 index 00000000..e6ed29dc --- /dev/null +++ b/plugins/by-name/dropbar/settings-options.nix @@ -0,0 +1,619 @@ +lib: +let + inherit (lib) types; + inherit (lib.nixvim) defaultNullOpts literalLua mkNullOrOption; + + mkPadding = + defaultNullOpts.mkNullable + (types.submodule { + options = lib.genAttrs [ "left" "right" ] ( + side: + lib.mkOption { + type = types.ints.unsigned; + description = "Padding for the ${side} side."; + } + ); + }) + { + left = 1; + right = 1; + }; +in +{ + bar = { + enable = + defaultNullOpts.mkBool + (literalLua '' + function(buf, win, _) + if + not vim.api.nvim_buf_is_valid(buf) + or not vim.api.nvim_win_is_valid(win) + or vim.fn.win_gettype(win) ~= "" + or vim.wo[win].winbar ~= "" + or vim.bo[buf].ft == 'help' + then + return false + end + + local stat = vim.uv.fs_stat(vim.api.nvim_buf_get_name(buf)) + if stat and stat.size > 1024 * 1024 then + return false + end + + return vim.bo[buf].ft == 'markdown' + or pcall(vim.treesitter.get_parser, buf) + or not vim.tbl_isempty(vim.lsp.get_clients({ + bufnr = buf, + method = 'textDocument/documentSymbol', + })) + end + '') + '' + Controls whether to enable the plugin for the current buffer and window. + + If a function is provided, it will be called with the current `bufnr` and `winid` and + should return a boolean. + ''; + + attach_events = + defaultNullOpts.mkListOf types.str + [ + "OptionSet" + "BufWinEnter" + "BufWritePost" + ] + '' + Controls when to evaluate the `enable()` function and attach the plugin to corresponding + buffer or window. + ''; + + update_debounce = defaultNullOpts.mkUnsignedInt 0 '' + Wait for a short time before updating the winbar, if another update request is received within + this time, the previous request will be cancelled. + This improves the performance when the user is holding down a key (e.g. `'j'`) to scroll the + window. + + If you encounter performance issues when scrolling the window, try setting this option to a + number slightly larger than `1000 / key_repeat_rate`. + ''; + + update_events = { + win = + defaultNullOpts.mkListOf types.str + [ + "CursorMoved" + "WinEnter" + "WinResized" + ] + '' + List of events that should trigger an update on the dropbar attached to a single window. + ''; + + buf = + defaultNullOpts.mkListOf types.str + [ + "BufModifiedSet" + "FileChangedShellPost" + "TextChanged" + "ModeChanged" + ] + '' + List of events that should trigger an update on all dropbars attached to a buffer. + ''; + + global = + defaultNullOpts.mkListOf types.str + [ + "DirChanged" + "VimResized" + ] + '' + List of events that should trigger an update on all dropbars in the current nvim + session. + ''; + }; + + hover = defaultNullOpts.mkBool true '' + Whether to highlight the symbol under the cursor. + + This feature requires 'mousemoveevent' to be enabled. + ''; + + sources = + defaultNullOpts.mkListOf + (types.submodule { + freeformType = with types; attrsOf anything; + + options = { + get_symbols = mkNullOrOption types.rawLua '' + ```lua + fun(buf: integer, win: integer, cursor: integer[]): dropbar_symbol_t[] + ``` + ''; + }; + }) + (literalLua '' + function(buf, _) + local sources = require('dropbar.sources') + local utils = require('dropbar.utils') + if vim.bo[buf].ft == 'markdown' then + return { + sources.path, + sources.markdown, + } + end + if vim.bo[buf].buftype == 'terminal' then + return { + sources.terminal, + } + end + return { + sources.path, + utils.source.fallback({ + sources.lsp, + sources.treesitter, + }), + } + end + '') + '' + List of sources to show in the winbar. + + If a function is provided, it will be called with the current `bufnr` and `winid` and + should return a list of sources. + + For more information about sources, see `|dropbar-developers-classes-dropbar_source_t|`. + ''; + + padding = mkPadding '' + Padding to use between the winbar and the window border. + ''; + + pick = { + pivots = defaultNullOpts.mkStr "abcdefghijklmnopqrstuvwxyz" '' + Pivots to use in pick mode. + ''; + }; + + truncate = defaultNullOpts.mkBool true '' + Whether to truncate the winbar if it doesn’t fit in the window. + ''; + }; + + menu = { + quick_navigation = defaultNullOpts.mkBool true '' + When on, automatically set the cursor to the closest previous/next clickable component in the + direction of cursor movement on `|CursorMoved|`. + ''; + + entry = { + padding = mkPadding '' + Padding to use between the menu entry and the menu border. + ''; + }; + + preview = defaultNullOpts.mkBool true '' + Whether to enable previewing for menu entries. + ''; + + keymaps = mkNullOrOption (with types; attrsOf (either str (attrsOf (maybeRaw str)))) '' + Buffer-local keymaps in the menu. + + Use ` = ` to map a key in normal mode in the menu buffer, or use + ` = table` to map a key in specific modes. + + See `:h dropbar-configuration-options-menu` for the default value. + ''; + + scrollbar = + defaultNullOpts.mkAttrsOf types.bool + { + enable = true; + background = true; + } + '' + Scrollbar configuration for the menu. + ''; + + win_configs = mkNullOrOption (with types; attrsOf anything) '' + Window configurations for the menu, see `:h nvim_open_win()`. + + Each config key in `menu.win_configs` accepts either a plain value which will be passed + directly to `nvim_open_win()`, or a function that takes the current `menu` (see + `|dropbar-developers-classes-dropbar_menu_t|`) as an argument and returns a value to be + passed to `nvim_open_win()`. + + See `:h dropbar-configuration-options-menu` for the default value. + ''; + }; + + fzf = { + keymaps = mkNullOrOption (with types; attrsOf (maybeRaw str)) '' + The keymaps that will apply in insert mode, in the fzf prompt buffer. + + See `:h dropbar-configuration-options-fzf` for the default value. + ''; + + win_configs = mkNullOrOption (with types; attrsOf anything) '' + Options passed to `:h nvim_open_win`. + + The fuzzy finder will use its parent window's config by default, but options set here will + override those. + Same config as `menu.win_configs` + + See `:h dropbar-configuration-options-fzf` for the default value. + ''; + + prompt = defaultNullOpts.mkStr "%#htmlTag# " '' + Prompt string that will be displayed in the `statuscolumn` of the fzf input window. + + Can include highlight groups + ''; + + char_pattern = defaultNullOpts.mkStr "[%w%p]" '' + Character pattern. + ''; + + retain_inner_spaces = defaultNullOpts.mkBool true '' + Whether to retain inner spaces. + ''; + + fuzzy_find_on_click = defaultNullOpts.mkBool true '' + When opening an entry with a submenu via the fuzzy finder, open the submenu in fuzzy finder + mode. + ''; + }; + + icons = { + enable = defaultNullOpts.mkBool true '' + Whether to enable icons. + ''; + + kinds = { + dir_icon = + defaultNullOpts.mkStr + (literalLua '' + function(_) + return M.opts.icons.kinds.symbols.Folder, 'DropBarIconKindFolder' + end + '') + '' + Directory icon and highlighting getter, set to empty string to disable. + ''; + + file_icon = + defaultNullOpts.mkStr + (literalLua '' + function(path) + return M.opts.icons.kinds.symbols.File, 'DropBarIconKindFile' + end + '') + '' + File icon and highlighting getter, set to empty string to disable. + ''; + + symbols = mkNullOrOption (with types; attrsOf str) '' + Table mapping the different kinds of symbols to their corresponding icons. + + See `:h dropbar-configuration-options-icons` for the default value. + ''; + }; + + ui = { + bar = + defaultNullOpts.mkAttrsOf types.str + { + separator = " "; + extends = "…"; + } + '' + Controls the icons used in the winbar UI. + ''; + + menu = + defaultNullOpts.mkAttrsOf types.str + { + separator = " "; + indicator = " "; + } + '' + Controls the icons used in the menu UI. + ''; + }; + }; + + symbol = { + on_click = mkNullOrOption (with types; maybeRaw (enum [ false ])) '' + function called when clicking or pressing `` on the symbol. + + See `:h dropbar-configuration-options-symbol` for the default value. + ''; + + preview = { + reorient = + defaultNullOpts.mkRaw + '' + function(_, range) + local invisible = range['end'].line - vim.fn.line('w$') + 1 + if invisible > 0 then + local view = vim.fn.winsaveview() --[[@as vim.fn.winrestview.dict]] + view.topline = math.min( + view.topline + invisible, + math.max(1, range.start.line - vim.wo.scrolloff + 1) + ) + vim.fn.winrestview(view) + end + end + '' + '' + Function to reorient the source window when previewing symbol given the source window + `win` and the range of the symbol `range`. + ''; + }; + + jump = { + reorient = + defaultNullOpts.mkRaw + '' + function(win, range) + local view = vim.fn.winsaveview() + local win_height = vim.api.nvim_win_get_height(win) + local topline = range.start.line - math.floor(win_height / 4) + if + topline > view.topline + and topline + win_height < vim.fn.line('$') + then + view.topline = topline + vim.fn.winrestview(view) + end + end + '' + '' + Function to reorient the source window when jumping to symbol given the source window + `win` and the range of the symbol `range`. + ''; + }; + }; + + sources = { + path = { + max_depth = defaultNullOpts.mkUnsignedInt 16 '' + Maximum number of symbols to return. + + A smaller number can help to improve performance in deeply nested paths. + ''; + + relative_to = + defaultNullOpts.mkStr + (literalLua '' + function(_, win) + -- Workaround for Vim:E5002: Cannot find window number + local ok, cwd = pcall(vim.fn.getcwd, win) + return ok and cwd or vim.fn.getcwd() + end + '') + '' + The path to use as the root of the relative path. + + If a function is provided, it will be called with the current buffer number and window + id as arguments and should return a string to be used as the root of the relative path. + + Notice: currently does not support `..` relative paths. + ''; + + path_filter = defaultNullOpts.mkRaw "function(_) return true end" '' + A function that takes a file name and returns whether to include it in the results shown in + the drop-down menu. + ''; + + modified = defaultNullOpts.mkRaw "function(sym) return sym end" '' + A function that takes the last symbol in the result got from the path source and returns an + alternative symbol to show if the current buffer is modified. + + For information about dropbar symbols, see `|dropbar-developers-classes-dropbar_symbol_t|`. + + To set a different icon, name, or highlights when the buffer is modified, you can change the + corresponding fields in the returned symbol: + + ```lua + function(sym) + return sym:merge({ + name = sym.name .. ' [+]', + icon = ' ', + name_hl = 'DiffAdded', + icon_hl = 'DiffAdded', + -- ... + }) + end + ``` + ''; + + preview = + defaultNullOpts.mkBool + (literalLua '' + function(path) + local stat = vim.uv.fs_stat(path) + if not stat or stat.type ~= 'file' then + return false + end + if stat.size > 524288 then + vim.notify( + string.format( + '[dropbar.nvim] file "%s" too large to preview', + path + ), + vim.log.levels.WARN + ) + return false + end + return true + end + '') + '' + A boolean or a function that takes a file path and returns whether to preview the file + under cursor. + ''; + }; + + treesitter = { + max_depth = defaultNullOpts.mkUnsignedInt 16 '' + Maximum number of symbols to return. + + A smaller number can help to improve performance in deeply nested trees (e.g. in big nested + json files). + ''; + + name_regex = defaultNullOpts.mkStr "[=[[#~!@\*&.]*[[:keyword:]]\+!\?\(\(\(->\)\+\|-\+\|\.\+\|:\+\|\s\+\)\?[#~!@\*&.]*[[:keyword:]]\+!\?\)*]=]" '' + Vim regex used to extract a short name from the node text. + ''; + + valid_types = + defaultNullOpts.mkListOf types.str + [ + "array" + "boolean" + "break_statement" + "call" + "case_statement" + "class" + "constant" + "constructor" + "continue_statement" + "delete" + "do_statement" + "element" + "enum" + "enum_member" + "event" + "for_statement" + "function" + "h1_marker" + "h2_marker" + "h3_marker" + "h4_marker" + "h5_marker" + "h6_marker" + "if_statement" + "interface" + "keyword" + "macro" + "method" + "module" + "namespace" + "null" + "number" + "operator" + "package" + "pair" + "property" + "reference" + "repeat" + "rule_set" + "scope" + "specifier" + "struct" + "switch_statement" + "type" + "type_parameter" + "unit" + "value" + "variable" + "while_statement" + "declaration" + "field" + "identifier" + "object" + "statement" + ] + '' + A list of treesitter node types to include in the results. + ''; + }; + + lsp = { + max_depth = defaultNullOpts.mkUnsignedInt 16 '' + Maximum number of symbols to return. + + A smaller number can help to improve performance when the language server returns huge list + of nested symbols. + ''; + + valid_symbols = + defaultNullOpts.mkListOf types.str + [ + "File" + "Module" + "Namespace" + "Package" + "Class" + "Method" + "Property" + "Field" + "Constructor" + "Enum" + "Interface" + "Function" + "Variable" + "Constant" + "String" + "Number" + "Boolean" + "Array" + "Object" + "Keyword" + "Null" + "EnumMember" + "Struct" + "Event" + "Operator" + "TypeParameter" + ] + '' + A list of LSP document symbols to include in the results. + ''; + + request = { + ttl_init = defaultNullOpts.mkUnsignedInt 60 '' + Number of times to retry a request before giving up. + ''; + + interval = defaultNullOpts.mkUnsignedInt 1000 '' + Number of milliseconds to wait between retries. + ''; + }; + }; + + markdown = { + max_depth = defaultNullOpts.mkUnsignedInt 6 '' + Maximum number of symbols to return. + ''; + + parse = { + look_ahead = defaultNullOpts.mkUnsignedInt 200 '' + Number of lines to update when cursor moves out of the parsed range. + ''; + }; + }; + + terminal = { + icon = + defaultNullOpts.mkStr + (literalLua '' + function(_) + return M.opts.icons.kinds.symbols.Terminal or ' ' + end + '') + '' + Icon to show before terminal names. + ''; + + name = defaultNullOpts.mkStr (literalLua "vim.api.nvim_buf_get_name") '' + Name for the current terminal buffer. + ''; + + show_current = defaultNullOpts.mkBool true '' + Show the current terminal buffer in the menu. + ''; + }; + }; +} diff --git a/tests/test-sources/plugins/by-name/dropbar/default.nix b/tests/test-sources/plugins/by-name/dropbar/default.nix new file mode 100644 index 00000000..2eed411c --- /dev/null +++ b/tests/test-sources/plugins/by-name/dropbar/default.nix @@ -0,0 +1,776 @@ +{ + empty = { + plugins.dropbar.enable = true; + }; + + example = { + plugins.dropbar = { + enable = true; + + settings = { + bar = { + enable = true; + sources.__raw = '' + function(buf, _) + local sources = require('dropbar.sources') + return { + require('dropbar.utils').source.fallback({ + sources.lsp, + sources.treesitter, + sources.markdown, + }) + } + end + ''; + }; + menu.keymaps = { + h = "q"; + l.__raw = '' + function() + local dropbar = require('dropbar') + local utils = require('dropbar.utils') + local menu = utils.menu.get_current() + if not menu then + return + end + local cursor = vim.api.nvim_win_get_cursor(menu.win) + local component = menu.entries[cursor[1]]:first_clickable(cursor[2]) + if component then + menu:click_on(component, nil, 1, 'l') + end + end + ''; + }; + }; + }; + }; + + defaults = { + plugins.dropbar = { + enable = true; + + settings = { + bar = { + enable.__raw = '' + function(buf, win, _) + if + not vim.api.nvim_buf_is_valid(buf) + or not vim.api.nvim_win_is_valid(win) + or vim.fn.win_gettype(win) ~= "" + or vim.wo[win].winbar ~= "" + or vim.bo[buf].ft == 'help' + then + return false + end + + local stat = vim.uv.fs_stat(vim.api.nvim_buf_get_name(buf)) + if stat and stat.size > 1024 * 1024 then + return false + end + + return vim.bo[buf].ft == 'markdown' + or pcall(vim.treesitter.get_parser, buf) + or not vim.tbl_isempty(vim.lsp.get_clients({ + bufnr = buf, + method = 'textDocument/documentSymbol', + })) + end + ''; + attach_events = [ + "OptionSet" + "BufWinEnter" + "BufWritePost" + ]; + update_debounce = 0; + update_events = { + win = [ + "CursorMoved" + "WinEnter" + "WinResized" + ]; + buf = [ + "BufModifiedSet" + "FileChangedShellPost" + "TextChanged" + "ModeChanged" + ]; + global = [ + "DirChanged" + "VimResized" + ]; + }; + hover = true; + sources.__raw = '' + function(buf, _) + local sources = require('dropbar.sources') + local utils = require('dropbar.utils') + if vim.bo[buf].ft == 'markdown' then + return { + sources.path, + sources.markdown, + } + end + if vim.bo[buf].buftype == 'terminal' then + return { + sources.terminal, + } + end + return { + sources.path, + utils.source.fallback({ + sources.lsp, + sources.treesitter, + }), + } + end + ''; + padding = { + left = 1; + right = 1; + }; + pick = { + pivots = "abcdefghijklmnopqrstuvwxyz"; + }; + truncate = true; + }; + menu = { + quick_navigation = true; + entry = { + padding = { + left = 1; + right = 1; + }; + }; + preview = true; + keymaps = { + q = "q"; + "" = "q"; + "".__raw = '' + function() + local menu = utils.menu.get_current() + if not menu then + return + end + local mouse = vim.fn.getmousepos() + local clicked_menu = utils.menu.get({ win = mouse.winid }) + -- If clicked on a menu, invoke the corresponding click action, + -- else close all menus and set the cursor to the clicked window + if clicked_menu then + clicked_menu:click_at({ + mouse.line, + mouse.column - 1 + }, nil, 1, 'l') + return + end + utils.menu.exec('close') + utils.bar.exec('update_current_context_hl') + if vim.api.nvim_win_is_valid(mouse.winid) then + vim.api.nvim_set_current_win(mouse.winid) + end + end + ''; + "".__raw = '' + function() + local menu = utils.menu.get_current() + if not menu then + return + end + local cursor = vim.api.nvim_win_get_cursor(menu.win) + local component = menu.entries[cursor[1]]:first_clickable(cursor[2]) + if component then + menu:click_on(component, nil, 1, 'l') + end + end + ''; + "".__raw = '' + function() + local menu = utils.menu.get_current() + if not menu then + return + end + local mouse = vim.fn.getmousepos() + utils.menu.update_hover_hl(mouse) + if M.opts.menu.preview then + utils.menu.update_preview(mouse) + end + end + ''; + i.__raw = '' + function() + local menu = utils.menu.get_current() + if not menu then + return + end + menu:fuzzy_find_open() + end + ''; + }; + scrollbar = { + enable = true; + background = true; + }; + win_configs = { + border = "none"; + style = "minimal"; + row.__raw = '' + function(menu) + return menu.prev_menu + and menu.prev_menu.clicked_at + and menu.prev_menu.clicked_at[1] - vim.fn.line('w') + or 0 + end + ''; + col.__raw = '' + ---@param menu dropbar_menu_t + function(menu) + if menu.prev_menu then + return menu.prev_menu._win_configs.width + + (menu.prev_menu.scrollbar and 1 or 0) + end + local mouse = vim.fn.getmousepos() + local bar = utils.bar.get({ win = menu.prev_win }) + if not bar then + return mouse.wincol + end + local _, range = bar:get_component_at(math.max(0, mouse.wincol - 1)) + return range and range.start or mouse.wincol + end + ''; + relative = "win"; + win.__raw = '' + function(menu) + return menu.prev_menu and menu.prev_menu.win + or vim.fn.getmousepos().winid + end + ''; + height.__raw = '' + function(menu) + return math.max( + 1, + math.min( + #menu.entries, + vim.go.pumheight ~= 0 and vim.go.pumheight + or math.ceil(vim.go.lines / 4) + ) + ) + end + ''; + width.__raw = '' + function(menu) + local min_width = vim.go.pumwidth ~= 0 and vim.go.pumwidth or 8 + if vim.tbl_isempty(menu.entries) then + return min_width + end + return math.max( + min_width, + math.max(unpack(vim.tbl_map(function(entry) + return entry:displaywidth() + end, menu.entries))) + ) + end + ''; + zindex.__raw = '' + function(menu) + if menu.prev_menu then + if menu.prev_menu.scrollbar and menu.prev_menu.scrollbar.thumb then + return vim.api.nvim_win_get_config(menu.prev_menu.scrollbar.thumb).zindex + end + return vim.api.nvim_win_get_config(menu.prev_win).zindex + end + end + ''; + }; + }; + fzf = { + keymaps = { + # TODO + }; + win_configs = { + relative = "win"; + anchor = "NW"; + height = 1; + win.__raw = '' + function(menu) + return menu.win + end + ''; + width.__raw = '' + function(menu) + local function border_width(border) + if type(border) == 'string' then + if border == 'none' or border == 'shadow' then + return 0 + end + return 2 -- left and right border + end + + local left, right = 1, 1 + if + (#border == 1 and border[1] == "") + or (#border == 4 and border[4] == "") + or (#border == 8 and border[8] == "") + then + left = 0 + end + if + (#border == 1 and border[1] == "") + or (#border == 4 and border[4] == "") + or (#border == 8 and border[4] == "") + then + right = 0 + end + return left + right + end + local menu_width = menu._win_configs.width + + border_width(menu._win_configs.border) + local self_width = menu._win_configs.width + local self_border = border_width( + ( + M.opts.fzf.win_configs + and M.eval(M.opts.fzf.win_configs.border, menu) + ) + or (menu.fzf_win_configs and M.eval( + menu.fzf_win_configs.border, + menu + )) + or menu._win_configs.border + ) + + if self_width + self_border > menu_width then + return self_width - self_border + else + return menu_width - self_border + end + end + ''; + row.__raw = '' + function(menu) + local menu_border = menu._win_configs.border + if + type(menu_border) == 'string' + and menu_border ~= 'shadow' + and menu_border ~= 'none' + then + return menu._win_configs.height + 1 + elseif menu_border == 'none' then + return menu._win_configs.height + end + local len_menu_border = #menu_border + if + len_menu_border == 1 and menu_border[1] ~= "" + or (len_menu_border == 2 or len_menu_border == 4) and menu_border[2] ~= "" + or len_menu_border == 8 and menu_border[8] ~= "" + then + return menu._win_configs.height + 1 + else + return menu._win_configs.height + end + end + ''; + col.__raw = '' + function(menu) + local menu_border = menu._win_configs.border + if + type(menu_border) == 'string' + and menu_border ~= 'shadow' + and menu_border ~= 'none' + then + return -1 + end + if + type(menu_border) == 'table' and menu_border[#menu_border] ~= "" + then + return -1 + end + return 0 + end + ''; + }; + prompt = "%#htmlTag# "; + char_pattern = "[%w%p]"; + retain_inner_spaces = true; + fuzzy_find_on_click = true; + }; + icons = { + enable = true; + kinds = { + dir_icon.__raw = '' + function(_) + return M.opts.icons.kinds.symbols.Folder, 'DropBarIconKindFolder' + end + ''; + file_icon.__raw = '' + function(path) + return M.opts.icons.kinds.symbols.File, 'DropBarIconKindFile' + end + ''; + symbols = { + Array = "󰅪 "; + Boolean = " "; + BreakStatement = "󰙧 "; + Call = "󰃷 "; + CaseStatement = "󱃙 "; + Class = " "; + Color = "󰏘 "; + Constant = "󰏿 "; + Constructor = " "; + ContinueStatement = "→ "; + Copilot = " "; + Declaration = "󰙠 "; + Delete = "󰩺 "; + DoStatement = "󰑖 "; + Element = "󰅩 "; + Enum = " "; + EnumMember = " "; + Event = " "; + Field = " "; + File = "󰈔 "; + Folder = "󰉋 "; + ForStatement = "󰑖 "; + Function = "󰊕 "; + H1Marker = "󰉫 "; + H2Marker = "󰉬 "; + H3Marker = "󰉭 "; + H4Marker = "󰉮 "; + H5Marker = "󰉯 "; + H6Marker = "󰉰 "; + Identifier = "󰀫 "; + IfStatement = "󰇉 "; + Interface = " "; + Keyword = "󰌋 "; + List = "󰅪 "; + Log = "󰦪 "; + Lsp = " "; + Macro = "󰁌 "; + MarkdownH1 = "󰉫 "; + MarkdownH2 = "󰉬 "; + MarkdownH3 = "󰉭 "; + MarkdownH4 = "󰉮 "; + MarkdownH5 = "󰉯 "; + MarkdownH6 = "󰉰 "; + Method = "󰆧 "; + Module = "󰏗 "; + Namespace = "󰅩 "; + Null = "󰢤 "; + Number = "󰎠 "; + Object = "󰅩 "; + Operator = "󰆕 "; + Package = "󰆦 "; + Pair = "󰅪 "; + Property = " "; + Reference = "󰦾 "; + Regex = " "; + Repeat = "󰑖 "; + RuleSet = "󰅩 "; + Scope = "󰅩 "; + Snippet = "󰩫 "; + Specifier = "󰦪 "; + Statement = "󰅩 "; + String = "󰉾 "; + Struct = " "; + SwitchStatement = "󰺟 "; + Terminal = " "; + Text = " "; + Type = " "; + TypeParameter = "󰆩 "; + Unit = " "; + Value = "󰎠 "; + Variable = "󰀫 "; + WhileStatement = "󰑖 "; + }; + }; + ui = { + bar = { + separator = " "; + extends = "…"; + }; + menu = { + separator = " "; + indicator = " "; + }; + }; + }; + symbol = { + on_click.__raw = '' + function(symbol) + -- Update current context highlights if the symbol + -- is shown inside a menu + if symbol.entry and symbol.entry.menu then + symbol.entry.menu:update_current_context_hl(symbol.entry.idx) + elseif symbol.bar then + symbol.bar:update_current_context_hl(symbol.bar_idx) + end + + -- Determine menu configs + local prev_win = nil ---@type integer? + local entries_source = nil ---@type dropbar_symbol_t[]? + local init_cursor = nil ---@type integer[]? + local win_configs = {} + if symbol.bar then -- If symbol inside a dropbar + prev_win = symbol.bar.win + entries_source = symbol.opts.siblings + init_cursor = symbol.opts.sibling_idx + and { symbol.opts.sibling_idx, 0 } + if symbol.bar.in_pick_mode then + ---@param tbl number[] + local function tbl_sum(tbl) + local sum = 0 + for _, v in ipairs(tbl) do + sum = sum + v + end + return sum + end + win_configs.relative = 'win' + win_configs.win = vim.api.nvim_get_current_win() + win_configs.row = 0 + win_configs.col = symbol.bar.padding.left + + tbl_sum(vim.tbl_map( + function(component) + return component:displaywidth() + + symbol.bar.separator:displaywidth() + end, + vim.tbl_filter(function(component) + return component.bar_idx < symbol.bar_idx + end, symbol.bar.components) + )) + end + elseif symbol.entry and symbol.entry.menu then -- If inside a menu + prev_win = symbol.entry.menu.win + entries_source = symbol.opts.children + end + + -- Toggle existing menu + if symbol.menu then + symbol.menu:toggle({ + prev_win = prev_win, + win_configs = win_configs, + }) + return + end + + -- Create a new menu for the symbol + if not entries_source or vim.tbl_isempty(entries_source) then + return + end + + local menu = require('dropbar.menu') + local configs = require('dropbar.configs') + symbol.menu = menu.dropbar_menu_t:new({ + prev_win = prev_win, + cursor = init_cursor, + win_configs = win_configs, + ---@param sym dropbar_symbol_t + entries = vim.tbl_map(function(sym) + local menu_indicator_icon = configs.opts.icons.ui.menu.indicator + local menu_indicator_on_click = nil + if not sym.children or vim.tbl_isempty(sym.children) then + menu_indicator_icon = + string.rep(' ', vim.fn.strdisplaywidth(menu_indicator_icon)) + menu_indicator_on_click = false + end + return menu.dropbar_menu_entry_t:new({ + components = { + sym:merge({ + name = "", + icon = menu_indicator_icon, + icon_hl = 'dropbarIconUIIndicator', + on_click = menu_indicator_on_click, + }), + sym:merge({ + on_click = function() + local current_menu = symbol.menu + while current_menu and current_menu.prev_menu do + current_menu = current_menu.prev_menu + end + if current_menu then + current_menu:close(false) + end + sym:jump() + end, + }), + }, + }) + end, entries_source), + }) + symbol.menu:toggle() + end + ''; + preview = { + reorient.__raw = '' + function(_, range) + local invisible = range['end'].line - vim.fn.line('w$') + 1 + if invisible > 0 then + local view = vim.fn.winsaveview() --[[@as vim.fn.winrestview.dict]] + view.topline = math.min( + view.topline + invisible, + math.max(1, range.start.line - vim.wo.scrolloff + 1) + ) + vim.fn.winrestview(view) + end + end + ''; + }; + jump = { + reorient.__raw = '' + function(win, range) + local view = vim.fn.winsaveview() + local win_height = vim.api.nvim_win_get_height(win) + local topline = range.start.line - math.floor(win_height / 4) + if + topline > view.topline + and topline + win_height < vim.fn.line('$') + then + view.topline = topline + vim.fn.winrestview(view) + end + end + ''; + }; + }; + sources = { + path = { + max_depth = 16; + relative_to.__raw = '' + function(_, win) + -- Workaround for Vim:E5002: Cannot find window number + local ok, cwd = pcall(vim.fn.getcwd, win) + return ok and cwd or vim.fn.getcwd() + end + ''; + path_filter.__raw = "function(_) return true end"; + modified.__raw = "function(sym) return sym end"; + preview.__raw = '' + function(path) + local stat = vim.uv.fs_stat(path) + if not stat or stat.type ~= 'file' then + return false + end + if stat.size > 524288 then + vim.notify( + string.format( + '[dropbar.nvim] file "%s" too large to preview', + path + ), + vim.log.levels.WARN + ) + return false + end + return true + end + ''; + }; + treesitter = { + max_depth = 16; + name_regex = "[=[[#~!@\*&.]*[[:keyword:]]\+!\?\(\(\(->\)\+\|-\+\|\.\+\|:\+\|\s\+\)\?[#~!@\*&.]*[[:keyword:]]\+!\?\)*]=]"; + valid_types = [ + "array" + "boolean" + "break_statement" + "call" + "case_statement" + "class" + "constant" + "constructor" + "continue_statement" + "delete" + "do_statement" + "element" + "enum" + "enum_member" + "event" + "for_statement" + "function" + "h1_marker" + "h2_marker" + "h3_marker" + "h4_marker" + "h5_marker" + "h6_marker" + "if_statement" + "interface" + "keyword" + "macro" + "method" + "module" + "namespace" + "null" + "number" + "operator" + "package" + "pair" + "property" + "reference" + "repeat" + "rule_set" + "scope" + "specifier" + "struct" + "switch_statement" + "type" + "type_parameter" + "unit" + "value" + "variable" + "while_statement" + "declaration" + "field" + "identifier" + "object" + "statement" + ]; + }; + lsp = { + max_depth = 16; + valid_symbols = [ + "File" + "Module" + "Namespace" + "Package" + "Class" + "Method" + "Property" + "Field" + "Constructor" + "Enum" + "Interface" + "Function" + "Variable" + "Constant" + "String" + "Number" + "Boolean" + "Array" + "Object" + "Keyword" + "Null" + "EnumMember" + "Struct" + "Event" + "Operator" + "TypeParameter" + ]; + request = { + ttl_init = 60; + interval = 1000; + }; + }; + markdown = { + max_depth = 6; + parse = { + look_ahead = 200; + }; + }; + terminal = { + icon.__raw = '' + function(_) + return M.opts.icons.kinds.symbols.Terminal or ' ' + end + ''; + name.__raw = "vim.api.nvim_buf_get_name"; + show_current = true; + }; + }; + }; + }; + }; +}