dropbar.nvim
IDE-like breadcrumbs, out of the box
git clone https://github.com/Bekaboo/dropbar.nvim.gitBekaboo/dropbar.nvimdropbar.nvim
IDE-like breadcrumbs, out of the box
Introduction
A polished, IDE-like, highly-customizable winbar for Neovim with drop-down menus and multiple backends.
For more information see :h dropbar.
Features
dropbar-demo-light.mp4
- Opening drop-down menus or go to definition with a single mouse click

- Pick mode for quickly selecting a component in the winbar with shortcuts

- Automatically truncating long components
- Better truncation when winbar is still too long after shortening all components
- Multiple backends that support fall-backs
dropbar.nvimcomes with five builtin sources:- lsp: gets symbols from language servers using nvim's builtin LSP framework
- markdown: a custom incremental parser that gets symbol information about markdown headings
- path: gets current file path
- treesitter: gets symbols from treesitter parsers using nvim's builtin treesitter integration
- terminal: easily switch terminal buffers using the dropdown menu To make a new source yourself, see making a new source. For source fall-backs support, see bar options.
- Zero config & Zero dependency
dropbar.nvimdoes not require nvim-lspconfig, nvim-treesitter or any third-party UI libraries to work. As long as the language server or the treesitter parser is installed, it should work just fine. Optionally, you can install telescope-fzf-native to add fuzzy search support to dropbar menus. - Drop-down menu components and winbar symbols that response to
mouse/cursor hovering:
- This features requires
:h mousemoveeventto be enabled.
- This features requires
- Preview symbols in their source windows when hovering over them in the
drop-down menu

- Reorient the source window on previewing or after jumping to a symbol
- Add scrollbar to the menu when the symbol list is too long

Requirements
- Neovim >= 0.11.0
- Optional
- nvim-web-devicons, if you want to see icons for different filetypes
- telescope-fzf-native, if you want fuzzy search support
- Working language server installation for the lsp source to work
- Working treesitter parser installation for the treesitter source to work
Installation
-
Using lazy.nvim
require('lazy').setup({ { 'Bekaboo/dropbar.nvim', -- optional, but required for fuzzy finder support dependencies = { 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' }, config = function() local dropbar_api = require('dropbar.api') vim.keymap.set('n', '<Leader>;', dropbar_api.pick, { desc = 'Pick symbols in winbar' }) vim.keymap.set('n', '[;', dropbar_api.goto_context_start, { desc = 'Go to start of current context' }) vim.keymap.set('n', '];', dropbar_api.select_next_context, { desc = 'Select next context' }) end } }) -
Using packer.nvim
require('packer').startup(function(use) use({ 'Bekaboo/dropbar.nvim', requires = { 'nvim-telescope/telescope-fzf-native.nvim', run = 'make' }, config = function () local dropbar_api = require('dropbar.api') vim.keymap.set('n', '<Leader>;', dropbar_api.pick, { desc = 'Pick symbols in winbar' }) vim.keymap.set('n', '[;', dropbar_api.goto_context_start, { desc = 'Go to start of current context' }) vim.keymap.set('n', '];', dropbar_api.select_next_context, { desc = 'Select next context' }) end }) end) -
Using native package manager
mkdir -p ~/.local/share/nvim/site/pack/packages/ git clone https://github.com/Bekaboo/dropbar.nvim ~/.local/share/nvim/site/pack/packages/start/dropbar.nvim
Lazy-loading is unneeded as it is already done in plugin/dropbar.lua.
Usage
- Basics
- Moves the cursor around and see the winbar reflects your current context
- Mouse support
- Click on a component in the winbar to open a drop-down menu of its siblings
- Click on an entry in the drop-down menu to go to its location
- Click on the indicator in the drop-down menu to open a sub-menu of its children
- Pick mode
- Use
require('dropbar.api').pick()to enter interactive pick mode orrequire('dropbar.api').pick(<idx>)to directly select a component atidx. - Inside interactive pick mode, press the corresponding pivot shown before each component to select it
- Use
- Fuzzy finder
- Use
dropbar_menu_t:fuzzy_find_open()to interactively filter, select and preview entries using fzf <Esc>: exit fzf mode<Up>/<Down>: move the cursor in fzf mode<CR>: call the on_click callback of the symbol under the cursor
- Use
- Default keymaps in drop-down menu
<LeftMouse>: call theon_clickcallback of the symbol at the mouse click<CR>: find the first clickable symbol in the current drop-down menu entry and call itson_clickcallbacki: enter fzf mode from the menuq/<Esc>: close current menu- To disable, remap or add new keymaps in the drop-down menu, see menu options
Usage with vim.ui.select
Dropbar can be used as a drop-in replacement for Neovim's builtin vim.ui.select menu.
To enable this functionality, simply replace vim.ui.select with dropbar.utils.menu.select:
vim.ui.select = require('dropbar.utils.menu').select
Configuration
Options
For all available options and their default values, see lua/dropbar/configs.lua.
Below are the detailed explanation of the options.
Bar
These options live under opts.bar and are used to control the behavior of the
winbar:
opts.bar.enable:boolean|fun(buf: integer?, win: integer?, info: table?): boolean- Controls whether to attach dropbar to 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
- Default:
function(buf, win, _) buf = vim._resolve_bufnr(buf) if not vim.api.nvim_buf_is_valid(buf) or not vim.api.nvim_win_is_valid(win) then return false end 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,
opts.bar.attach_events:(string|{event: string, pattern: string})[]- Controls when to evaluate the
enable()function and attach the plugin to corresponding buffer or window - Default:
{ 'TermOpen', 'BufEnter', 'BufWinEnter', 'BufWritePost', 'FileType', 'LspAttach', }
- Controls when to evaluate the
opts.bar.update_debounce:number- 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 - Default:
32
- 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.
opts.bar.update_events.win:(string|{event: string, pattern: string})[]- List of events that should trigger an update on the dropbar attached to a single window
- Default:
{ 'CursorMoved', 'WinEnter', 'WinResized', }
opts.bar.update_events.buf:(string|{event: string, pattern: string})[]- List of events that should trigger an update on all dropbars attached to a buffer
- Default:
{ -- Neovim v0.13 removes `BufModifiedSet`, use `OptionSet` with -- `pattern=modified` instead { event = 'OptionSet', pattern = 'modified', }, 'FileChangedShellPost', 'TextChanged', 'ModeChanged', }
opts.bar.update_events.global:(string|{event: string, pattern: string})[]- List of events that should trigger an update of all dropbars in current nvim session
- Default:
{ 'DirChanged', 'VimResized', }
opts.bar.hover:boolean- Whether to highlight the symbol under the cursor
- This feature requires
'mousemoveevent'to be enabled - Default:
true
opts.bar.sources:dropbar_source_t[]|fun(buf: integer, win: integer): dropbar_source_t[]- 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
- Default:
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 - For more information about sources, see
dropbar_source_t.
opts.bar.padding:{ left: number, right: number }- Padding to use between the winbar and the window border
- Default:
{ left = 1, right = 1 }
opts.bar.pick.pivots:string- Pivots to use in pick mode
- Default:
'abcdefghijklmnopqrstuvwxyz'
opts.bar.truncate:boolean- Whether to truncate the winbar if it doesn't fit in the window
- Default:
true
opts.bar.gc.interval:number- Interval of periodic garbage collection, i.e. remove winbars attached to invalid buffers/windows, in ms
Menu
These options live under opts.menu and are used to control the behavior of the
menu:
-
opts.menu.quick_navigation:boolean- When on, automatically set the cursor to the closest previous/next
clickable component in the direction of cursor movement on
CursorMoved - Default:
true
- When on, automatically set the cursor to the closest previous/next
clickable component in the direction of cursor movement on
-
opts.menu.entry.padding:{ left: number, right: number }- Padding to use between the menu entry and the menu border
- Default:
{ left = 1, right = 1 }
-
opts.menu.preview:boolean- Whether to enable previewing for menu entries
- Default:
true
-
opts.menu.hover:boolean- Whether to highlight the symbol under the cursor
- This feature requires
'mousemoveevent'to be enabled - Default:
true
-
opts.menu.keymaps:table<string, function|string|table<string, function>|table<string, string>>- Buffer-local keymaps in the menu
- Use
<key> = <function|string>to map a key in normal mode in the menu buffer, or use<key> = table<mode, function|string>to map a key in specific modes. - Default:
{ ['q'] = '<C-w>q', ['<Esc>'] = '<C-w>q', ['<LeftMouse>'] = 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, ['<CR>'] = 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, ['<MouseMove>'] = function() local menu = utils.menu.get_current() if not menu then return end local mouse = vim.fn.getmousepos() if M.opts.menu.hover then utils.menu.update_hover_hl(mouse) end if M.opts.menu.preview then utils.menu.update_preview(mouse) end end, ['i'] = function() local menu = utils.menu.get_current() if not menu then return end menu:fuzzy_find_open() end, },
-
opts.menu.scrollbar:table<string, boolean>- Scrollbar configuration for the menu.
- Default:
{ enable = true, -- if false, only the scrollbar thumb will be shown background = true }
-
opts.menu.win_configs:table<string, dropbar_menu_win_config_opts_t>- Window configurations for the menu, see
:h nvim_open_win() - Each config key in
opts.menu.win_configsaccepts either a plain value which will be passes directly tonvim_open_win(), or a function that takes the current menu (seedropbar_menu_t) as an argument and returns a value to be passed tonvim_open_win(). - Default:
{ style = 'minimal', row = function(menu) return menu.prev_menu and menu.prev_menu.clicked_at and menu.prev_menu.clicked_at[1] - vim.fn.line('w0') or 0 end, ---@param menu dropbar_menu_t col = 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 = require('dropbar.api').get_dropbar( vim.api.nvim_win_get_buf(menu.prev_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 = function(menu) return menu.prev_menu and menu.prev_menu.win or vim.fn.getmousepos().winid end, height = 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 = 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 = function(menu) if not menu.prev_menu then return end return menu.prev_menu.scrollbar and menu.prev_menu.scrollbar.thumb and vim.api.nvim_win_get_config(menu.prev_menu.scrollbar.thumb).zindex or vim.api.nvim_win_get_config(menu.prev_win).zindex end, }
- Window configurations for the menu, see
Fzf
These options live under opts.fzf and are used to control the behavior and
appearance of the fuzzy finder interface.
-
opts.fzf.keymaps- The keymaps that will apply in insert mode, in the fzf prompt buffer
- Same config as opts.menu.keymaps
- Default:
keymaps = { ['<LeftMouse>'] = function() ---@type dropbar_menu_t local menu = utils.menu.get_current() if not menu then return end local mouse = vim.fn.getmousepos() if not mouse then return end if mouse.winid ~= menu.win then local default_func = M.opts.menu.keymaps['<LeftMouse>'] if type(default_func) == 'function' then default_func() end menu:fuzzy_find_close(false) return elseif mouse.winrow > vim.api.nvim_buf_line_count(menu.buf) then return end vim.api.nvim_win_set_cursor(menu.win, { mouse.line, mouse.column - 1 }) menu:fuzzy_find_click_on_entry(function(entry) return entry:get_component_at(mouse.column - 1, true) end) end, ['<MouseMove>'] = function() ---@type dropbar_menu_t local menu = utils.menu.get_current() if not menu then return end local mouse = vim.fn.getmousepos() if not mouse then return end -- If mouse is not in the menu window or on the border, end preview -- and clear hover highlights if mouse.winid ~= menu.win or mouse.line <= 0 or mouse.column <= 0 or mouse.winrow > #menu.entries then menu = menu:root() --[[@as dropbar_menu_t]] if menu then menu:finish_preview(true) if M.opts.menu.hover then menu:update_hover_hl() end end return end if M.opts.menu.preview then menu:preview_symbol_at({ mouse.line, mouse.column - 1 }, true) end if M.opts.menu.hover then menu:update_hover_hl({ mouse.line, mouse.column - 1 }) end end, ['<Up>'] = api.fuzzy_find_prev, ['<Down>'] = api.fuzzy_find_next, ['<C-k>'] = api.fuzzy_find_prev, ['<C-j>'] = api.fuzzy_find_next, ['<C-p>'] = api.fuzzy_find_prev, ['<C-n>'] = api.fuzzy_find_next, ['<CR>'] = api.fuzzy_find_click, ['<S-Enter>'] = function() api.fuzzy_find_click(-1) end, }
-
opts.fzf.win_configs- 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 opts.menu.win_configs
- Default:
win_configs = { relative = 'win', anchor = 'NW', height = 1, win = function(menu) return menu.win end, width = function(menu) local function border_width(border) if not border then border = vim.go.winborder end if type(border) == 'string' then if border == '' or 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 = function(menu) local menu_border = menu._win_configs.border or vim.go.border if type(menu_border) == 'string' and menu_border ~= 'shadow' and menu_border ~= 'none' and menu_border ~= '' then return menu._win_configs.height + 1 elseif menu_border == 'none' or menu_border == '' 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 = function(menu) local menu_border = menu._win_configs.border or vim.go.border if type(menu_border) == 'string' and menu_border ~= 'shadow' and menu_border ~= 'none' and menu_border ~= '' then return -1 end if type(menu_border) == 'table' and menu_border[#menu_border] ~= '' then return -1 end return 0 end, },
- Options passed to
-
opts.fzf.prompt- Prompt string that will be displayed in the statuscolumn of the fzf input window.
- Can include highlight groups
- Default:
prompt = '%#htmlTag# '
-
opts.fzf.char_pattern- Default:
char_pattern = '[%w%p]'
- Default:
-
opts.fzf.retain_inner_spaces- Default:
retain_inner_spaces = true
- Default:
-
opts.fzf.fuzzy_find_on_click- When opening an entry with a submenu via the fuzzy finder, open the submenu in fuzzy finder mode.
- Default:
fuzzy_find_on_click = true
Icons
These options live under opts.icons and are used to configure the icons
used by the plugin:
opts.icons.enable:boolean- Whether to enable icons
- Default:
true
opts.icons.kinds.dir_icon:fun(path: string): string, string?|string?- Directory icon and highlighting getter, set to empty string to disable
- Default:
function(_) return M.opts.icons.kinds.symbols.Folder, 'DropBarIconKindFolder' end
opts.icons.kinds.file_icon:fun(path: string): string, string?|string?- File icon and highlighting getter, set to empty string to disable
- Default:
function(path) local icon_kind_opts = M.opts.icons.kinds local file_icon = icon_kind_opts.symbols.File local file_icon_hl = 'DropBarIconKindFile' local devicons_ok, devicons = pcall(require, 'nvim-web-devicons') if not devicons_ok then return file_icon, file_icon_hl end -- Try to find icon using the filename, explicitly disable the -- default icon so that we can try to find the icon using the -- filetype if the filename does not have a corresponding icon local devicon, devicon_hl = devicons.get_icon( vim.fs.basename(path), vim.fn.fnamemodify(path, ':e'), { default = false } ) -- No corresponding devicon found using the filename, try finding icon -- with filetype if the file is loaded as a buf in nvim if not devicon then ---@type integer? local buf = vim.iter(vim.api.nvim_list_bufs()):find(function(buf) return vim.api.nvim_buf_get_name(buf) == path end) if buf then local filetype = vim.api.nvim_get_option_value('filetype', { buf = buf }) devicon, devicon_hl = devicons.get_icon_by_filetype(filetype) end end file_icon = devicon and devicon .. ' ' or file_icon file_icon_hl = devicon_hl return file_icon, file_icon_hl end
opts.icons.kinds.symbols:table<string, string>- Table mapping the different kinds of symbols to their corresponding icons
- Default:
{ Array = ' ', BlockMappingPair = ' ', Boolean = ' ', BreakStatement = ' ', Call = ' ', CaseStatement = ' ', Class = ' ', Color = ' ', Constant = ' ', Constructor = ' ', ContinueStatement = '→ ', Copilot = ' ', Declaration = ' ', Delete = ' ', DoStatement = ' ', Element = ' ', Enum = ' ', EnumMember = ' ', Event = ' ', Field = ' ', File = ' ', Folder = ' ', ForStatement = ' ', Function = ' ', GotoStatement = ' ', 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 = ' ', Return = ' ', Rule = ' ', RuleSet = ' ', Scope = ' ', Section = ' ', Snippet = ' ', Specifier = ' ', Statement = ' ', String = ' ', Struct = ' ', SwitchStatement = ' ', Table = ' ', Terminal = ' ', Text = ' ', Type = ' ', TypeParameter = ' ', Unit = ' ', Value = ' ', Variable = ' ', WhileStatement = ' ', }
opts.icons.ui.bar:table<string, string>- Controls the icons used in the winbar UI
- Default:
{ separator = ' ', extends = '…', }
opts.icons.ui.menu:table<string, string>- Controls the icons used in the menu UI
- Default:
{ separator = ' ', indicator = ' ', }
Symbol
These options live under opts.symbol and are used to control the behavior of
the symbols:
opts.symbol.on_click():fun(symbol: dropbar_symbol_t, min_width: integer?, n_clicks: integer?, button: string?, modifiers: string?)|false?- Default function called when clicking or pressing
<CR>on the symbol - Default:
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 root_menu = symbol.menu and symbol.menu:root() if root_menu then root_menu:close(false) end if current_menu then current_menu:close(false) end sym:jump() end, }), }, }) end, entries_source), }) symbol.menu:toggle() end,
- Default function called when clicking or pressing
opts.symbol.preview.reorient:fun(win: integer, range: {start: {line: integer, character: integer}, end: {line: integer, character: integer}})- Function to reorient the source window when previewing symbol given
the source window
winand the range of the symbolrange - Default:
function() end
- Function to reorient the source window when previewing symbol given
the source window
opts.symbol.jump.reorient:fun(win: integer, range: {start: {line: integer, character: integer}, end: {line: integer, character: integer}})- Function to reorient the source window after jumping to symbol given
the source window
winand the range of the symbolrange - Default:
function() end
- Function to reorient the source window after jumping to symbol given
the source window
Sources
These options live under opts.sources and are used to control the behavior of
each sources.
Path
opts.sources.path.max_depth:integer- Maximum number of symbols to return
- A smaller number can help to improve performance in deeply nested paths
- Default:
16
opts.sources.path.relative_to:string|fun(buf: integer, win: integer): string- 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 - Default:
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
opts.sources.path.filter:function(name: string): boolean- A function that takes a file name and returns whether to include it in the results shown in the drop-down menu
- Default:
function(_) return true end
opts.sources.path.modified:function(sym: dropbar_symbol_t): dropbar_symbol_t- A function that takes the last
symbol
dropbar_symbol_tin the result got from the path source and returns an alternative symboldropbar_symbol_tto show if the current buffer is modified - Default:
function(sym) return sym end
- To set a different icon, name, or highlights when the buffer is modified,
you can change the corresponding fields in the returned
symbol
dropbar_symbol_tfunction(sym) return sym:merge({ name = sym.name .. '[+]', icon = ' ', name_hl = 'DiffAdded', icon_hl = 'DiffAdded', -- ... }) end
- A function that takes the last
symbol
opts.sources.path.preview:boolean|fun(path: string): boolean?|nil- A boolean or a function that takes a file path and returns whether to preview the file under cursor
- Default:
true
opts.sources.path.min_widths:integer[]- Minimum width of each symbols when truncated, in reverse order
(e.g.
{10}forces the last symbol has width >= 10) - Default:
{}
- Minimum width of each symbols when truncated, in reverse order
(e.g.
Treesitter
opts.sources.treesitter.max_depth:integer- 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)
- Default:
16
opts.sources.treesitter.name_regex:string- Vim regex used to extract a short name from the node text
- Default:
[=[[#~!@\*&.]*[[:keyword:]]\+!\?\(\(\(->\)\+\|-\+\|\.\+\|:\+\|\s\+\)\?[#~!@\*&.]*[[:keyword:]]\+!\?\)*]=]
opts.sources.treesitter.valid_types:string[]- A list of treesitter node types to include in the results
- Default:
{ 'block_mapping_pair', 'array', 'boolean', 'break_statement', 'call', 'case_statement', 'class', 'constant', 'constructor', 'continue_statement', 'delete', 'do_statement', 'element', 'enum', 'enum_member', 'event', 'for_statement', 'function', 'goto_statement', 'if_statement', 'interface', 'keyword', 'macro', 'method', 'namespace', 'null', 'number', 'operator', 'package', 'pair', 'property', 'reference', 'repeat', 'return_statement', 'rule', 'rule_set', 'scope', 'section', 'specifier', 'struct', 'switch_statement', 'table', 'type', 'type_parameter', 'unit', 'value', 'variable', 'while_statement', 'declaration', 'field', 'identifier', 'object', 'statement', }
opts.sources.treesitter.min_widths:integer[]- Minimum width of each symbols when truncated, in reverse order
(e.g.
{10}forces the last symbol has width >= 10) - Default:
{}
- Minimum width of each symbols when truncated, in reverse order
(e.g.
LSP
opts.sources.lsp.max_depth:integer- Maximum number of symbols to return
- A smaller number can help to improve performance when the language server returns huge list of nested symbols
- Default:
16
opts.sources.lsp.valid_symbols:string[]- A list of LSP document symbols to include in the results
- Default:
{ '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', }
opts.sources.lsp.request.ttl_init:number- Number of times to retry a request before giving up
- Default:
60
opts.sources.lsp.request.interval:number- Number of milliseconds to wait between retries
- Default:
1000
opts.sources.lsp.min_widths:integer[]- Minimum width of each symbols when truncated, in reverse order
(e.g.
{10}forces the last symbol has width >= 10) - Default:
{}
- Minimum width of each symbols when truncated, in reverse order
(e.g.
Markdown
opts.sources.markdown.max_depth:integer- Maximum number of symbols to return
- Default:
6
opts.sources.markdown.parse.look_ahead:number- Number of lines to update when cursor moves out of the parsed range
- Default:
200
opts.sources.markdown.min_widths:integer[]- Minimum width of each symbols when truncated, in reverse order
(e.g.
{10}forces the last symbol has width >= 10) - Default:
{}
- Minimum width of each symbols when truncated, in reverse order
(e.g.
Terminal
Thanks @willothy for implementing this.
-
opts.sources.terminal.icon:string|fun(buf: integer): string- Icon to show before terminal names
- Default:
function(_) return M.opts.icons.kinds.symbols.Terminal or ' ' end
-
opts.sources.terminal.name:string|fun(buf: integer): string- Default:
vim.api.nvim_buf_get_name - Easy to integrate with other plugins (for example, toggleterm.nvim):
name = function(buf) local name = vim.api.nvim_buf_get_name(buf) -- the second result val is the terminal object local term = select(2, require("toggleterm.terminal").indentify(name)) if term then return term.display_name or term.name else return name end end
- Default:
-
opts.sources.terminal.show_current: boolean- Show the current terminal buffer in the menu
- Default:
true
Highlighting
dropbar.nvim defines sets of highlight groups. Override them in your
colorscheme to change the appearance of the drop-down menu:
| Highlight group | Description | Attributes |
|---|---|---|
| DropBarCurrentContext | Background of selected/clicked symbol in dropbar | { link = 'Visual' } |
| DropBarCurrentContextIcon | Highlight for selected/clicked symbol's icon in dropbar | { link = 'DropBarCurrentContext' } |
| DropBarCurrentContextName | Highlight for selected/clicked symbol's name in dropbar | { link = 'DropBarCurrentContext' } |
| DropBarFzfMatch | Fzf fuzzy search matches | { link = 'Special' } |
| DropBarHover | Background of the dropbar symbol when the mouse is hovering over it | { link = 'Visual' } |
| DropBarIconKindDefault | Default highlight for dropbar icons | { link = 'Special' } |
| DropBarIconKindDefaultNC | Default highlight for dropbar icons in non-current windows | { link = 'WinBarNC' } |
| DropBarIconKind... | Highlights of corresponding symbol kind icons | { link = 'Repeat' } |
| DropBarIconKind...NC | Highlights of corresponding symbol kind icons in non-current windows | { link = 'DropBarIconKindDefaultNC' } |
| DropBarIconUIIndicator | Shortcuts before entries in utils.menu.select() |
{ link = 'SpecialChar' } |
| DropBarIconUIPickPivot | Shortcuts shown before each symbol after entering pick mode | { link = 'Error' } |
| DropBarIconUISeparator | Separator between each symbol in dropbar | { link = 'Comment' } |
| DropBarIconUISeparatorMenu | Separator between each symbol in dropbar menus | { link = 'DropBarIconUISeparator' } |
| DropBarMenuCurrentContext | Background of current line in dropbar menus | { link = 'PmenuSel' } |
| DropBarMenuFloatBorder | Border of dropbar menus | { link = 'FloatBorder' } |
| DropBarMenuHoverEntry | Background of hovered line in dropbar menus | { link = 'IncSearch' } |
| DropBarMenuHoverIcon | Background of hovered symbol icon in dropbar menus | { reverse = true } |
| DropBarMenuHoverSymbol | Background of hovered symbol name in dropbar menus | { bold = true } |
| DropBarMenuNormalFloat | Normal text in dropbar menus | { link = 'NormalFloat' } |
| DropBarMenuSbar | Scrollbar background of dropbar menus | { link = 'PmenuSbar' } |
| DropBarMenuThumb | Scrollbar thumb of dropbar menus | { link = 'PmenuThumb' } |
| DropBarPreview | Range of the symbol under the cursor in source code | { link = 'Visual' } |
| DropBarKind... | Highlights of corresponding symbol kind names | undefined |
| DropBarKind...NC | Highlights of corresponding symbol kind names in non-current windows | undefined |
Configuration Examples
Custom Filename Highlight Group
This configuration highlights filenames from path source with custom highlight
group DropBarFileName.
local dropbar = require('dropbar')
local sources = require('dropbar.sources')
local utils = require('dropbar.utils')
vim.api.nvim_set_hl(0, 'DropBarFileName', { fg = '#FFFFFF', italic = true })
local custom_path = {
get_symbols = function(buff, win, cursor)
local symbols = sources.path.get_symbols(buff, win, cursor)
symbols[#symbols].name_hl = 'DropBarFileName'
if vim.bo[buff].modified then
symbols[#symbols].name = symbols[#symbols].name .. ' [+]'
symbols[#symbols].name_hl = 'DiffAdded'
end
return symbols
end,
}
dropbar.setup({
bar = {
sources = function(buf, _)
if vim.bo[buf].ft == 'markdown' then
return {
custom_path,
sources.markdown,
}
end
if vim.bo[buf].buftype == 'terminal' then
return {
sources.terminal,
}
end
return {
custom_path,
utils.source.fallback {
sources.lsp,
sources.treesitter,
},
}
end,
},
})
Normalize Path in Special Buffers
Some plugins, e.g. oil and
fugitive, have buffers with file path
confusing for dropbar.nvim. This is because their buffers names start with
things like oil:// or fugitive://.
This configuration should addresses the issue:
require('dropbar').setup({
bar = {
enable = function(buf, win, _)
buf = vim._resolve_bufnr(buf)
if
not vim.api.nvim_buf_is_valid(buf)
or not vim.api.nvim_win_is_valid(win)
then
return false
end
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].bt == 'terminal'
or 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,
},
sources = {
path = {
relative_to = function(buf, win)
-- Show full path in oil or fugitive buffers
local bufname = vim.api.nvim_buf_get_name(buf)
if
vim.startswith(bufname, 'oil://')
or vim.startswith(bufname, 'fugitive://')
then
local root = bufname:gsub('^%S+://', '', 1)
while root and root ~= vim.fs.dirname(root) do
root = vim.fs.dirname(root)
end
return root
end
local ok, cwd = pcall(vim.fn.getcwd, win)
return ok and cwd or vim.fn.getcwd()
end,
},
},
})
Developers
Architecture
The flow chart below should well illustrate what does dropbar do user moves
around in their window or clicks at a symbol in the winbar:
more like this
vim-dap
Vim/Neovim debugger plugin providing a terminal interface to the Debug Adapter Protocol
tobira.nvim
Open the next door in your Vim journey — personalized command discovery based on your actual usage
