nvim-mini / nvim-mini/mini.nvim
[mini.extra] pickers.buf_lines should go to first matched character
Nobody has claimed this yet.
- Dominant language
- Lua
- Stars
- 9.5k
- Forks
- 310
- Avg merge
- 4h 16m
- Merged PRs (30d)
- 1
Description
Contributing guidelines
- I have read CONTRIBUTING.md
- I have read CODE_OF_CONDUCT.md
Module(s)
mini.extra
Description
Hi and thank you for all your work on mini.nvim and neovim!
I use the buf_lines picker extensively and it is likely my most used picker. The current behavior is selecting a line the cursor goes to the beginning of that line. This means that extra steps are needed to navigate to the thing you search for.
I'd like to advocate that when searching for something the intent is most likely to navigate the very thing the user searches for. Thus navigating to the first matched character is preferred. Today this can be achieved by overriding the source.choose like require('mini.extra').pickers.buf_lines({scope = 'current'}, {source = {choose = my_impl}}), you can see the implementation in just over 100 lines below.
implementation
local query_is_ignorecase = function(query)
if not vim.o.ignorecase then
return false
elseif not vim.o.smartcase then
return true
end
local prompt = table.concat(query)
return prompt == prompt:lower()
end
local match_query_group = function(query)
local parts = { {} }
for _, x in ipairs(query) do
local is_whitespace = x:find('^%s+$') ~= nil
if is_whitespace then
table.insert(parts, {})
else
table.insert(parts[#parts], x)
end
end
return #parts > 1, vim.tbl_map(table.concat, parts)
end
local match_find_query = function(s, query, init)
local first, to = string.find(s, query[1], init, true)
if first == nil then
return nil, nil
end
local last = first --[[@as number?]]
for i = 2, #query do
last, to = string.find(s, query[i], to + 1, true)
if not last then
return nil, nil
end
end
return first, last
end
local buf_lines_match_col = function(line, query)
if type(line) ~= 'string' or #query == 0 then
return nil
elseif query_is_ignorecase(query) then
line = line:lower()
query = vim.tbl_map(string.lower, query)
end
local is_fuzzy_forced = query[1] == '*'
local is_exact_plain = query[1] == "'"
local is_exact_start = query[1] == '^'
local is_exact_end = query[#query] == '$'
local is_grouped, grouped_parts = match_query_group(query)
if is_fuzzy_forced or is_exact_plain or is_exact_start or is_exact_end then
local start_offset = (is_fuzzy_forced or is_exact_plain or is_exact_start) and 2 or 1
local end_offset = #query
- ((not is_fuzzy_forced and not is_exact_plain and is_exact_end) and 1 or 0)
query = vim.list_slice(query, start_offset, end_offset)
elseif is_grouped then
query = grouped_parts
end
if #query == 0 then
return nil
end
local is_fuzzy_plain = not (is_exact_plain or is_exact_start or is_exact_end) and #query > 1
if is_fuzzy_forced or is_fuzzy_plain then
local first, last = match_find_query(line, query, 1)
if first == nil then
return nil
elseif first == last then
return first
end
local best_first, _best_last, best_width = first, last, last - first
while last do
local width = last - first
if width < best_width then
best_first, _best_last, best_width = first, last, width
end
first, last = match_find_query(line, query, first + 1)
end
return best_first
end
local prefix = is_exact_start and '^' or ''
local suffix = is_exact_end and '$' or ''
local pattern = prefix .. vim.pesc(table.concat(query)) .. suffix
return string.find(line, pattern)
end
local choose_buf_line_at_match = function(item)
local query = require('mini.pick').get_picker_query() or {}
local line = vim.api.nvim_buf_get_lines(item.bufnr, item.lnum - 1, item.lnum, false)[1]
local col = buf_lines_match_col(line, query)
if col then
item.col = col
end
return require('mini.pick').default_choose(item)
end
keymap.set('n', '<leader>/', function()
require('mini.extra').pickers.buf_lines(
{ scope = 'current' },
{ source = { choose = choose_buf_line_at_match } }
)
end)
I think it would be great if the buf_lines picker came with this feature by default or perhaps behind an option. Can this be considered?
Aside, both telescope and snacks.picker default behavior takes the user to the first matched character.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with mini.extra.pickers.buf_lines and its source.choose integration, then read mini.pick.get_picker_query and mini.pick.default_choose alongside the provided choose_buf_line_at_match implementation. Determine whether first-match cursor placement should be the default or an option, and verify that selecting a searched line places the cursor on its first matched character without breaking existing picker behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- lua
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100