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)
Contributing guidelines
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.chooselikerequire('mini.extra').pickers.buf_lines({scope = 'current'}, {source = {choose = my_impl}}), you can see the implementation in just over 100 lines below.implementation
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.