Skip to content

Commit

Permalink
feat(menu): fuzzy finding (#77)
Browse files Browse the repository at this point in the history
* feat(menu): fuzzy find menu entries

* feat(api): add fuzzy finding api functions

* fix: only offset fzf entry win if it has a border

* feat: hover events in fuzzy finder

* feat(config): add `config.opts.fzf` for configuring fuzzy finder

Options can still be overridden by the `opts` table passed to
`fuzzy_find_open`.

* docs: document fzf options

* docs: document `dropbar_menu_t:fuzzy_find*`

* docs: document `fuzzy_find_*` api functions

* fix: restore menu cursor pos on fuzzy search exit

* refactor: remove unused args

* feat: set filetype for fuzzy finder

* feat!: use `<i>` to enter fzf mode

docs: document fzf mode default keybinds
feat: allow disabling default fzf keybinds by setting them to `false`

* feat: add fuzzy_find_on_click option

* fix: ensure clicked entry has a submenu before attempting to open fzf

* fix: remove unnecessary vim.schedule

fixes flicker when opening fzf menu

* fix: keep current context highlight in fzf mode

* fix: restore cursorcol when leaving fzf mode

* fix: nil check for cursor when restoring entries

* fix: explicitly return false if fzf not found

refactor: use vim.F.ok_or_nil

* feat(fzf): select first entry on <Enter>, last on <S-Enter>

* fix(fzf): only apply offset if menu has no border

* fix(mappings): don't perform fzf check in mapping

* refactor(fzf): use vim.notify_once for dependency warnings

* refactor(fzf): simplify fzf context hl update

* feat: fuzzy finder navigation functions

* fix: fzf auto-opening

* test: tests for fuzzy finder

* test: add telescope-fzf-native to test deps

* feat: fuzzy_find_navigate, update fzf mappings

* fix: update scrollbar when fuzzy finding

* fix(ci): disable swapfiles

---------

Co-authored-by: Theo Fabi <fabi.theo@gmail.com>
  • Loading branch information
willothy and theofabilous authored Sep 25, 2023
1 parent 54813b4 commit 8da1555
Show file tree
Hide file tree
Showing 8 changed files with 984 additions and 12 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ jobs:
mkdir -p "${NVIM_PACK_START}"
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim "${NVIM_PACK_START}/plenary.nvim"
FZF_DIR="${NVIM_PACK_START}/telescope-fzf-native.nvim"
git clone --depth 1 "https://github.com/nvim-telescope/telescope-fzf-native.nvim" "${FZF_DIR}"
cmake "-S${FZF_DIR}" "-B${FZF_DIR}/build" -DCMAKE_BUILD_TYPE=Release && cmake --build "${FZF_DIR}/build" --config Release && cmake --install "${FZF_DIR}/build" --prefix "${FZF_DIR}/build"
ln -s "${PWD}" "${NVIM_PACK_START}"
- name: run tests
Expand Down
301 changes: 299 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975-
As long as the language server or the treesitter parser is installed,
it should work just fine.

Optionally, you can install [telescope-fzf-native](https://github.com/nvim-telescope/telescope-fzf-native)
to add fuzzy search support to dropbar menus.

- [x] Drop-down menu components and winbar symbols that response to
mouse/cursor hovering:

Expand Down Expand Up @@ -131,6 +134,7 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975-
- Neovim **Nightly** (>= 0.10.0-dev)
- Optional
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons), if you want to see icons for different filetypes
- [telescope-fzf-native](https://github.com/nvim-telescope/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

Expand All @@ -140,15 +144,26 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975-

```lua
require('lazy').setup({
{ 'Bekaboo/dropbar.nvim' }
{
'Bekaboo/dropbar.nvim',
-- optional, but required for fuzzy finder support
dependencies = {
'nvim-telescope/telescope-fzf-native'
}
}
})
```

- Using [packer.nvim](https://github.com/wbthomason/packer.nvim)

```lua
require('packer').startup(function(use)
use('Bekaboo/dropbar.nvim')
use({
'Bekaboo/dropbar.nvim',
requires = {
'nvim-telescope/telescope-fzf-native'
}
})
end)
```

Expand Down Expand Up @@ -177,6 +192,13 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975-
`idx`.
- Inside interactive pick mode, press the corresponding pivot shown before
each component to select it
- Fuzzy finder
- Use dropbar_menu_t:fuzzy_find_open() to interactively
filter, select and preview entries using fzf
- `<i>`: enter fzf mode from the menu
- `<Esc>`: exit fzf mode
- `<Up>/<Down>`: move the cursor in fzf mode
- `<CR>`: call the on_click callback of the symbol under the cursor
- Default keymaps in drop-down menu
- `<LeftMouse>`: call the `on_click` callback of the symbol at the mouse
click
Expand Down Expand Up @@ -458,6 +480,13 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975-
end
menu:update_hover_hl({ mouse.line, mouse.column - 1 })
end,
i = function()
local menu = utils.menu.get_current()
if not menu then
return
end
menu:fuzzy_find_open()
end,
},
---@alias dropbar_menu_win_config_opts_t any|fun(menu: dropbar_menu_t):any
---@type table<string, dropbar_menu_win_config_opts_t>
Expand Down Expand Up @@ -516,6 +545,110 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975-
end,
},
},
fzf = {
---@type table<string, string | fun()>
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
-- Find the root menu
while menu and menu.prev_menu do
menu = menu.prev_menu
end
if menu then
menu:finish_preview(true)
menu:update_hover_hl()
end
return
end
if M.opts.menu.preview then
menu:preview_symbol_at({ mouse.line, mouse.column - 1 }, true)
end
menu:update_hover_hl({ mouse.line, mouse.column - 1 })
end,
['<Esc>'] = function()
require('dropbar.api').fuzzy_find_toggle()
end,
['<Enter>'] = function()
require('dropbar.api').fuzzy_find_click()
end,
['<S-Enter>'] = function()
require('dropbar.api').fuzzy_find_click(-1)
end,
['<Up>'] = function()
require('dropbar.api').fuzzy_find_navigate('up')
end,
['<Down>'] = function()
require('dropbar.api').fuzzy_find_navigate('down')
end,
['<C-k>'] = function()
require('dropbar.api').fuzzy_find_navigate('up')
end,
['<C-j>'] = function()
require('dropbar.api').fuzzy_find_navigate('down')
end,
},
-- 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.
win_configs = {},
---@type table
hl = {
---@type string
fg = vim.api.nvim_get_hl(0, { name = 'htmlTag', link = false }).fg,
---@type boolean
underline = true,
},
---@type string
prompt = '%#htmlTag# ',
---@type string
char_pattern = '[%w%p]',
---@type boolean
retain_inner_spaces = true,
---@type boolean
-- When opening an entry with a submenu via the fuzzy finder,
-- open the submenu in fuzzy finder mode.
fuzzy_find_on_click = true
},
sources = {
path = {
---@type string|fun(buf: integer): string
Expand Down Expand Up @@ -985,6 +1118,13 @@ menu:
end
menu:update_hover_hl({ mouse.line, mouse.column - 1 })
end,
i = function()
local menu = utils.menu.get_current()
if not menu then
return
end
menu:fuzzy_find_open()
end,
}
```
- `opts.menu.win_configs`: `table<string, dropbar_menu_win_config_opts_t>`
Expand Down Expand Up @@ -1050,6 +1190,147 @@ menu:
}
```

#### 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:
```lua
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
-- Find the root menu
while menu and menu.prev_menu do
menu = menu.prev_menu
end
if menu then
menu:finish_preview(true)
menu:update_hover_hl()
end
return
end
if M.opts.menu.preview then
menu:preview_symbol_at({ mouse.line, mouse.column - 1 }, true)
end
menu:update_hover_hl({ mouse.line, mouse.column - 1 })
end,
['<Esc>'] = function()
require('dropbar.api').fuzzy_find_toggle()
end,
['<Enter>'] = function()
require('dropbar.api').fuzzy_find_click()
end,
['<S-Enter>'] = function()
require('dropbar.api').fuzzy_find_click(-1)
end,
['<Up>'] = function()
require('dropbar.api').fuzzy_find_navigate('up')
end,
['<Down>'] = function()
require('dropbar.api').fuzzy_find_navigate('down')
end,
['<C-k>'] = function()
require('dropbar.api').fuzzy_find_navigate('up')
end,
['<C-j>'] = function()
require('dropbar.api').fuzzy_find_navigate('down')
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:
```lua
win_configs = {}
```

- opts.fzf.hl
- Highlight to use for the fuzzy finder.
- Options passed directly to `:h nvim_set_hl`
- Default:
```lua
hl = {
fg = vim.api.nvim_get_hl(0, { name = 'htmlTag', link = false }).fg,
underline = true,
}
```

- opts.fzf.prompt
- Prompt string that will be displayed in the statuscolumn of the fzf input window.
- Can include highlight groups
- Default:
```lua
prompt = '%#htmlTag# '
```

- opts.fzf.char_pattern
- Default:
```lua
char_pattern = '[%w%p]'
```

- opts.fzf.retain_inner_spaces
- Default:
```lua
retain_inner_spaces = true
```

- 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:
```lua
fuzzy_find_on_click = true
```

#### Sources

These options live under `opts.sources` and are used to control the behavior of
Expand Down Expand Up @@ -1259,6 +1540,17 @@ used to interact with the winbar or the drop-down menu:
- Pick a component from current winbar
- If `idx` is `nil`, enter interactive pick mode to select a component
- If `idx` is a number, directly pick the component at that index if it exists
- `fuzzy_find_toggle(opts: table?)`
- Toggle the fuzzy finder interface for the current dropbar menu
- Options override the default / config options for the fuzzy finder
- `fuzzy_find_click(component: number | (fun(entry: dropbar_menu_entry_t):dropbar_sumbol_t)?)`
- If `component` is a `number`, the `component`-nth symbol is selected,
unless `0` or `-1` is supplied, in which case the *first* or *last*
clickable component is selected, respectively.
- If it is a `function`, it receives the `dropbar_menu_entry_t` as an argument
and should return the `dropbar_symbol_t` that is to be clicked.
- `fuzzy_find_navigate(direction: "up" | "down")`
- Select the previous/next entry in the menu while fuzzy finding

### Utility Functions

Expand Down Expand Up @@ -1653,6 +1945,11 @@ Declared and defined in [`lua/dropbar/menu.lua`](https://github.com/Bekaboo/drop
| `dropbar_menu_t:open(opts: dropbar_menu_t?)` | open the menu with options `opts` |
| `dropbar_menu_t:close(restore_view: boolean?)` | close the menu |
| `dropbar_menu_t:toggle(opts: dropbar_menu_t?)` | toggle the menu |
| `dropbar_menu_t:fuzzy_find_restore_entries()` | restore menu buffer and entries in their original order before modification by fuzzy search
| `dropbar_menu_t:fuzzy_find_close()` | stop fuzzy finding and clean up allocated memory
| `dropbar_menu_t:fuzzy_find_click_on_entry(component: number or fun(dropbar_menu_entry_t):dropbar_symbol_t)` | click on the currently selected fuzzy menu entry, choosing the component to click according to component
| `dropbar_menu_t:fuzzy_find_open(opts: table?)` | open the fuzzy search menu, overriding fzf configuration with opts argument
| `dropbar_menu_t:fuzzy_find_navigate(direction: "up" or "down")` | select the next or previous entry in the menu when the fuzzy search bar is open |
#### `dropbar_menu_entry_t`
Expand Down
Loading

0 comments on commit 8da1555

Please sign in to comment.