feat(snacks): added snacks.picker (#445)

## Description

More info coming tomorrow.

In short:
- very fast. pretty much realtime filtering/sorting in huge repos (like
1.7 million files)
- extensible
- easy to customize the layout (and lots of presets) with
`snacks.layout`
- simple to create custom pickers
- `vim.ui.select`
- lots of builtin pickers
- uses treesitter highlighting wherever it makes sense
- fast lua fuzzy matcher which supports the [fzf
syntax](https://junegunn.github.io/fzf/search-syntax/) and additionally
supports field filters, like `file:lua$ 'function`

There's no snacks picker command, just use lua.

```lua
-- all pickers
Snacks.picker()

-- run files picker
Snacks.picker.files(opts)
Snacks.picker.pick("files", opts)
Snacks.picker.pick({source = "files", ...})
```

<!-- Describe the big picture of your changes to communicate to the
maintainers
  why we should accept this pull request. -->

## Todo
- [x] issue with preview loc not always correct when scrolling fast in
list (probably due to `snacks.scroll`)
- [x] `grep` (`live_grep`) is sometimes too fast in large repos and can
impact ui rendering. Not very noticeable, but something I want to look
at.
- [x] docs
- [x] treesitter highlights are broken. Messed something up somewhere

## Related Issue(s)

<!--
  If this PR fixes any issues, please link to the issue here.
  - Fixes #<issue_number>
-->

## Screenshots

<!-- Add screenshots of the changes if applicable. -->
This commit is contained in:
Folke Lemaitre 2025-01-14 22:53:59 +01:00 committed by GitHub
parent 1b7a57a0b1
commit 559d6c6bf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 12013 additions and 126 deletions

View file

@ -0,0 +1,74 @@
local M = {}
---Represents an item in a Neovim quickfix/loclist.
---@class qf.item
---@field bufnr? number The buffer number where the item originates.
---@field filename? string
---@field lnum number The start line number for the item.
---@field end_lnum? number The end line number for the item.
---@field pattern string A pattern related to the item. It can be a search pattern or any relevant string.
---@field col? number The column number where the item starts.
---@field end_col? number The column number where the item ends.
---@field module? string Module information (if any) associated with the item.
---@field nr? number A unique number or ID for the item.
---@field text? string A description or message related to the item.
---@field type? string The type of the item. E.g., "W" might stand for "Warning".
---@field valid number A flag indicating if the item is valid (1) or not (0).
---@field user_data? any Any user data associated with the item.
---@field vcol? number Visual column number. Indicates if the column number is a visual column number (when set to 1) or a byte index (when set to 0).
---@class snacks.picker
---@field loclist fun(opts?: snacks.picker.Config): snacks.Picker
---@field qflist fun(opts?: snacks.picker.Config): snacks.Picker
---@class snacks.picker.qf.Config
---@field qf_win? number
---@field filter? snacks.picker.filter.Config
local severities = {
E = vim.diagnostic.severity.ERROR,
W = vim.diagnostic.severity.WARN,
I = vim.diagnostic.severity.INFO,
H = vim.diagnostic.severity.HINT,
N = vim.diagnostic.severity.HINT,
}
---@param opts snacks.picker.qf.Config
---@type snacks.picker.finder
function M.qf(opts, filter)
local win = opts.qf_win
win = win == 0 and vim.api.nvim_get_current_win() or win
local list = win and vim.fn.getloclist(win, { all = true }) or vim.fn.getqflist({ all = true })
---@cast list { items?: qf.item[] }?
local ret = {} ---@type snacks.picker.finder.Item[]
for _, item in pairs(list and list.items or {}) do
local row = item.lnum == 0 and 1 or item.lnum
local col = (item.col == 0 and 1 or item.col) - 1
local end_row = item.end_lnum == 0 and row or item.end_lnum
local end_col = item.end_col == 0 and col or (item.end_col - 1)
if item.valid == 1 then
local file = item.filename or item.bufnr and vim.api.nvim_buf_get_name(item.bufnr) or nil
local text = item.text or ""
ret[#ret + 1] = {
pos = { row, col },
end_pos = item.end_lnum ~= 0 and { end_row, end_col } or nil,
text = file .. " " .. text,
line = item.text,
file = file,
severity = severities[item.type] or 0,
buf = item.bufnr,
item = item,
}
elseif #ret > 0 and ret[#ret].item.text and item.text then
ret[#ret].item.text = ret[#ret].item.text .. "\n" .. item.text
ret[#ret].item.line = ret[#ret].item.line .. "\n" .. item.text
end
end
return filter:filter(ret)
end
return M