mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
feat(picker): flexible filename format (#2294)
## Description This PR builds on top of #743 to add flexible filename formatting in the picker. ### Changes: - **Flexible path truncation**: Added support for different truncation strategies (`left`, `center`, `right`) instead of just a fixed number - `left`: truncates the beginning of the path (e.g., `…/path/to/file.lua`) - `center`: truncates the middle of the path (default behavior, e.g., `~/pro…/file.lua`) - `right`: truncates the end of the path (e.g., `~/projects/long…`) - **Dynamic width calculation**: The filename formatter now adapts to available window width using a resolve function - **Enhanced truncate utility**: Updated `M.truncate()` to support left-side truncation ### Implementation: The filename formatter now uses a `resolve` function that receives context including the picker, item, current offset, and maximum available width. This allows the formatter to make intelligent decisions about how to display the path based on actual available space. ## Related Issue(s) - Based on #743 - Addresses dynamic path formatting needs ## Technical Details The implementation introduces: 1. `snacks.picker.format.ctx` - context passed to resolve functions 2. `snacks.picker.format.resolve` - callback type for dynamic formatting 3. `Snacks.picker.highlight.resolve()` - resolves flex text elements in highlight arrays --------- Co-authored-by: qw457812 <37494864+qw457812@users.noreply.github.com>
This commit is contained in:
parent
475fb69947
commit
9ad5d5374a
5 changed files with 101 additions and 15 deletions
|
|
@ -1,7 +1,14 @@
|
|||
local M = {}
|
||||
|
||||
---@class snacks.picker.format.ctx
|
||||
---@field picker snacks.Picker
|
||||
---@field item snacks.picker.Item
|
||||
---@field offset number
|
||||
---@field max_width number
|
||||
|
||||
---@alias snacks.picker.format.resolve fun(ctx:snacks.picker.format.ctx):snacks.picker.Highlight[]
|
||||
---@alias snacks.picker.Extmark vim.api.keyset.set_extmark|{col:number, row?:number, field?:string}
|
||||
---@alias snacks.picker.Text {[1]:string, [2]:string?, virtual?:boolean, field?:string}
|
||||
---@alias snacks.picker.Text {[1]:string, [2]:string?, virtual?:boolean, field?:string, resolve?:snacks.picker.format.resolve}
|
||||
---@alias snacks.picker.Highlight snacks.picker.Text|snacks.picker.Extmark
|
||||
---@alias snacks.picker.format fun(item:snacks.picker.Item, picker:snacks.Picker):snacks.picker.Highlight[]
|
||||
---@alias snacks.picker.preview fun(ctx: snacks.picker.preview.ctx):boolean?
|
||||
|
|
@ -146,7 +153,11 @@ local defaults = {
|
|||
},
|
||||
file = {
|
||||
filename_first = false, -- display filename before the file path
|
||||
truncate = 40, -- truncate the file path to (roughly) this length
|
||||
--- * left: truncate the beginning of the path
|
||||
--- * center: truncate the middle of the path
|
||||
--- * right: truncate the end of the path
|
||||
---@type "left"|"center"|"right"
|
||||
truncate = "center",
|
||||
filename_only = false, -- only show the filename
|
||||
icon_width = 2, -- width of the icon (in characters)
|
||||
git_status_hl = true, -- use the git status highlight group for the filename
|
||||
|
|
|
|||
|
|
@ -475,6 +475,16 @@ function M:format(item)
|
|||
|
||||
-- Add the formatted item
|
||||
local line = self.picker.format(item, self.picker)
|
||||
|
||||
---@type snacks.picker.format.ctx
|
||||
local ctx = {
|
||||
picker = self.picker,
|
||||
item = item,
|
||||
offset = vim.api.nvim_strwidth(text),
|
||||
max_width = vim.api.nvim_win_get_width(self.win.win) - 5,
|
||||
}
|
||||
line = Snacks.picker.highlight.resolve(line, ctx)
|
||||
|
||||
while #line > 0 and type(line[#line][1]) == "string" and line[#line][1]:find("^%s*$") do
|
||||
table.remove(line)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,13 +41,26 @@ end
|
|||
|
||||
---@param item snacks.picker.Item
|
||||
function M.filename(item, picker)
|
||||
---@type snacks.picker.Text[]
|
||||
return {
|
||||
{
|
||||
"",
|
||||
resolve = function(ctx)
|
||||
return M._filename(ctx)
|
||||
end,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
---@type snacks.picker.format.resolve
|
||||
function M._filename(ctx)
|
||||
local picker, item = ctx.picker, ctx.item
|
||||
---@type snacks.picker.Highlight[]
|
||||
local ret = {}
|
||||
if not item.file then
|
||||
return ret
|
||||
end
|
||||
local path = Snacks.picker.util.path(item) or item.file
|
||||
path = Snacks.picker.util.truncpath(path, picker.opts.formatters.file.truncate or 40, { cwd = picker:cwd() })
|
||||
local name, cat = path, "file"
|
||||
if item.buf and vim.api.nvim_buf_is_loaded(item.buf) then
|
||||
name = vim.bo[item.buf].filetype
|
||||
|
|
@ -67,6 +80,9 @@ function M.filename(item, picker)
|
|||
ret[#ret + 1] = { icon, hl, virtual = true }
|
||||
end
|
||||
|
||||
local truncate = picker.opts.formatters.file.truncate
|
||||
path = Snacks.picker.util.truncpath(path, ctx.max_width, { cwd = picker:cwd(), kind = truncate })
|
||||
|
||||
local base_hl = item.dir and "SnacksPickerDirectory" or "SnacksPickerFile"
|
||||
local function is(prop)
|
||||
local it = item
|
||||
|
|
|
|||
|
|
@ -202,6 +202,42 @@ function M.winhl(prefix, links)
|
|||
return table.concat(ret, ",")
|
||||
end
|
||||
|
||||
--- Resolves the first flex text in the line.
|
||||
---@param line snacks.picker.Highlight[]
|
||||
---@param ctx snacks.picker.format.ctx
|
||||
function M.resolve(line, ctx)
|
||||
local ret = {} ---@type snacks.picker.Highlight[]
|
||||
local offset = 0
|
||||
local width = 0
|
||||
local resolve ---@type number?
|
||||
|
||||
for t, text in ipairs(line) do
|
||||
local w = M.offset({ text }, { char_idx = true })
|
||||
if not resolve and type(text) == "table" and text.resolve then
|
||||
---@cast text snacks.picker.Text
|
||||
resolve = t
|
||||
elseif resolve then
|
||||
width = width + w
|
||||
else
|
||||
width = width + w
|
||||
offset = offset + w
|
||||
end
|
||||
end
|
||||
|
||||
if resolve then
|
||||
ctx.offset = offset
|
||||
ctx.max_width = ctx.max_width - width
|
||||
vim.list_extend(ret, line, 1, resolve - 1)
|
||||
offset = M.offset(ret)
|
||||
vim.list_extend(ret, line[resolve].resolve(ctx))
|
||||
local diff = M.offset(ret) - offset
|
||||
vim.list_extend(ret, line, resolve + 1)
|
||||
M.fix_offset(ret, diff, resolve + 1)
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param line snacks.picker.Highlight[]
|
||||
---@param opts? {offset?:number}
|
||||
function M.to_text(line, opts)
|
||||
|
|
@ -248,13 +284,16 @@ function M.to_text(line, opts)
|
|||
end
|
||||
|
||||
---@param hl snacks.picker.Highlight[]
|
||||
function M.fix_offset(hl, offset)
|
||||
for _, t in ipairs(hl) do
|
||||
if t.col then
|
||||
t.col = t.col + offset
|
||||
end
|
||||
if t.end_col then
|
||||
t.end_col = t.end_col + offset
|
||||
---@param start_idx? number
|
||||
function M.fix_offset(hl, offset, start_idx)
|
||||
for i, t in ipairs(hl) do
|
||||
if start_idx == nil or i >= start_idx then
|
||||
if t.col then
|
||||
t.col = t.col + offset
|
||||
end
|
||||
if t.end_col then
|
||||
t.end_col = t.end_col + offset
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ function M.path(item)
|
|||
end
|
||||
|
||||
---@param path string
|
||||
---@param len? number
|
||||
---@param opts? {cwd?: string}
|
||||
---@param len number
|
||||
---@param opts? {cwd?: string, kind?: "left" | "center" | "right"}
|
||||
function M.truncpath(path, len, opts)
|
||||
opts = opts or {}
|
||||
local cwd = svim.fs.normalize(opts and opts.cwd or vim.fn.getcwd(), { _fast = true, expand_env = false })
|
||||
local home = svim.fs.normalize("~")
|
||||
path = svim.fs.normalize(path, { _fast = true, expand_env = false })
|
||||
|
|
@ -35,6 +36,12 @@ function M.truncpath(path, len, opts)
|
|||
end
|
||||
path = path:gsub("/$", "")
|
||||
|
||||
if opts.kind == "left" then
|
||||
return M.truncate(path, len, true)
|
||||
elseif opts.kind == "right" then
|
||||
return M.truncate(path, len, false)
|
||||
end
|
||||
|
||||
if vim.api.nvim_strwidth(path) <= len then
|
||||
return path
|
||||
end
|
||||
|
|
@ -135,9 +142,12 @@ 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) .. "…"
|
||||
---@param left? boolean
|
||||
function M.truncate(text, width, left)
|
||||
local tw = vim.api.nvim_strwidth(text)
|
||||
if tw > width then
|
||||
return left and "…" .. vim.fn.strcharpart(text, tw - width + 1, width - 1)
|
||||
or vim.fn.strcharpart(text, 0, width - 1) .. "…"
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue