snacks.nvim/lua/snacks/picker/util/init.lua
Folke Lemaitre 559d6c6bf2
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. -->
2025-01-14 22:53:59 +01:00

106 lines
2.8 KiB
Lua

---@class snacks.picker.util
local M = {}
---@param item snacks.picker.Item
function M.path(item)
if not (item and item.file) then
return
end
return vim.fs.normalize(item.cwd and item.cwd .. "/" .. item.file or item.file, { _fast = true, expand_env = false })
end
---@param item table<string, any>
---@param keys string[]
function M.text(item, keys)
local buffer = require("string.buffer").new()
for _, key in ipairs(keys) do
if item[key] then
if #buffer > 0 then
buffer:put(" ")
end
if key == "pos" or key == "end_pos" then
buffer:putf("%d:%d", item[key][1], item[key][2])
else
buffer:put(tostring(item[key]))
end
end
end
return buffer:get()
end
---@param text string
---@param width number
---@param opts? {align?: "left" | "right" | "center", truncate?: boolean}
function M.align(text, width, opts)
opts = opts or {}
opts.align = opts.align or "left"
local tw = vim.api.nvim_strwidth(text)
if tw > width then
return opts.truncate and (vim.fn.strcharpart(text, 0, width - 1) .. "") or text
end
local left = math.floor((width - tw) / 2)
local right = width - tw - left
if opts.align == "left" then
left, right = 0, width - tw
elseif opts.align == "right" then
left, right = width - tw, 0
end
return (" "):rep(left) .. text .. (" "):rep(right)
end
---@param text string
---@param width number
function M.truncate(text, width)
if vim.api.nvim_strwidth(text) > width then
return vim.fn.strcharpart(text, 0, width - 1) .. ""
end
return text
end
-- Stops visual mode and returns the selected text
function M.visual()
local modes = { "v", "V", Snacks.util.keycode("<C-v>") }
local mode = vim.fn.mode():sub(1, 1) ---@type string
if not vim.tbl_contains(modes, mode) then
return
end
-- stop visual mode
vim.cmd("normal! " .. mode)
local pos = vim.api.nvim_buf_get_mark(0, "<")
local end_pos = vim.api.nvim_buf_get_mark(0, ">")
-- for some reason, sometimes the column is off by one
-- see: https://github.com/folke/snacks.nvim/issues/190
local col_to = math.min(end_pos[2] + 1, #vim.api.nvim_buf_get_lines(0, end_pos[1] - 1, end_pos[1], false)[1])
local lines = vim.api.nvim_buf_get_text(0, pos[1] - 1, pos[2], end_pos[1] - 1, col_to, {})
local text = table.concat(lines, "\n")
---@class snacks.picker.Visual
local ret = {
pos = pos,
end_pos = end_pos,
text = text,
}
return ret
end
---@param str string
---@param data table<string, string>
function M.tpl(str, data)
return (str:gsub("(%b{})", function(w)
return data[w:sub(2, -2)] or w
end))
end
---@param str string
function M.title(str)
return table.concat(
vim.tbl_map(function(s)
return s:sub(1, 1):upper() .. s:sub(2)
end, vim.split(str, "_")),
" "
)
end
return M