feat: Support Line Column in file pickers

Original issue: When selecting files in telescope iti is impossible to
enter paths from compiler that includes cursor locations, you need to clear it from the path.

This commit fixes the problem by stripping out the location from path
(important: only for file pickes) and using this location to set the
cursor when openning the file and highlight the line or poisition in the
previewer.

This feature requires a new option for a picker, whichi for now is basically
enabling location stripping but it is helpful for any file picker
builtin or external one.

By default equals `false` becuase most of pickers like live grep,
current buffer fuzy are messed up with locations stripping. It is only
useful for file searches.

Added test suite that covers alghoritm of stripping the location to the
`utils_spec.lua`
This commit is contained in:
Dmitriy Kovalenko
2023-11-20 10:39:49 +01:00
parent 18774ec792
commit 0dfe9e1edf
6 changed files with 142 additions and 2 deletions
+1
View File
@@ -384,6 +384,7 @@ files.find_files = function(opts)
pickers
.new(opts, {
prompt_title = "Find Files",
files_picker = true,
finder = finders.new_oneshot_job(find_command, opts),
previewer = conf.file_previewer(opts),
sorter = conf.file_sorter(opts),
+1
View File
@@ -46,6 +46,7 @@ git.files = function(opts)
pickers
.new(opts, {
prompt_title = "Git Files",
files_picker = true,
finder = finders.new_oneshot_job(
vim.tbl_flatten {
opts.git_command,
+27
View File
@@ -319,6 +319,8 @@ function Picker:new(opts)
cache_picker = config.resolve_table_opts(opts.cache_picker, vim.deepcopy(config.values.cache_picker)),
__scrolling_limit = tonumber(vim.F.if_nil(opts.temp__scrolling_limit, 250)),
allow_location_input = opts.files_picker or false,
}, self)
obj.create_layout = opts.create_layout or config.values.create_layout or default_create_layout
@@ -626,6 +628,24 @@ function Picker:find()
local start_time = vim.loop.hrtime()
local prompt = self:_get_next_filtered_prompt()
if self.allow_location_input == true then
local filename, line_number, column_number = utils.separate_file_path_location(prompt)
if line_number or column_number then
state.set_global_key("prompt_location", { row = line_number, col = column_number })
self:refresh_previewer()
elseif state.get_global_key "prompt_location" then
state.set_global_key("prompt_location", nil)
self:refresh_previewer()
end
-- it is important to continue behaving as if there is no location in prompt
prompt = filename
elseif state.get_global_key "prompt_location" then
-- in case new picker that does not support locations is opened clear the location
-- without refreshing previewer
state.set_global_key("prompt_location", nil)
end
-- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display
if self.cache_picker == false or self.cache_picker.is_cached ~= true then
@@ -1046,6 +1066,13 @@ function Picker:set_selection(row)
end
local entry = self.manager:get_entry(self:get_index(row))
local prompt_location = state.get_global_key "prompt_location"
if entry and prompt_location then
entry.lnum = prompt_location.row or 0
entry.col = prompt_location.col or 0
end
state.set_global_key("selected_entry", entry)
if not entry then
+27 -2
View File
@@ -4,6 +4,7 @@ local utils = require "telescope.utils"
local putils = require "telescope.previewers.utils"
local Previewer = require "telescope.previewers.previewer"
local conf = require("telescope.config").values
local global_state = require "telescope.state"
local pscan = require "plenary.scandir"
@@ -345,8 +346,6 @@ previewers.new_buffer_previewer = function(opts)
local old_bufs = {}
local bufname_table = {}
local global_state = require "telescope.state"
local preview_window_id
local function get_bufnr(self)
@@ -489,6 +488,29 @@ end
previewers.cat = defaulter(function(opts)
opts = opts or {}
local cwd = opts.cwd or vim.loop.cwd()
local function jump_to_line(bufnr, winid)
pcall(vim.api.nvim_buf_clear_namespace, bufnr, ns_previewer, 0, -1)
local location = global_state.get_global_key "prompt_location"
if location and location.row > 0 then
local highlight_range = location.col and location.col > 0 and { location.col - 1, location.col } or { 0, -1 }
pcall(
vim.api.nvim_buf_add_highlight,
bufnr,
ns_previewer,
"TelescopePreviewLine",
location.row - 1,
highlight_range[1],
highlight_range[2]
)
pcall(vim.api.nvim_win_set_cursor, winid, { location.row, location.col })
vim.api.nvim_buf_call(bufnr, function()
vim.cmd "norm! zz"
end)
end
end
return previewers.new_buffer_previewer {
title = "File Preview",
dyn_title = function(_, entry)
@@ -509,6 +531,9 @@ previewers.cat = defaulter(function(opts)
winid = self.state.winid,
preview = opts.preview,
file_encoding = opts.file_encoding,
callback = function(bufnr)
jump_to_line(bufnr, self.state.winid)
end,
})
end,
}
+41
View File
@@ -567,4 +567,45 @@ utils.list_find = function(func, list)
end
end
--- Takes the path and parses optional cursor location `$file:$line:$column`
--- If line or column not present `0` returne returned.
--- @param path string
utils.separate_file_path_location = function(path)
local location_numbers = {}
-- Split the 2 last `:` separated parts and if they are numbers treat
-- as line and columnd numbers
for i = #path, 1, -1 do
if path:sub(i, i) == ":" then
-- If this is the last `:` we ignore it for a case when user
-- just entered it with intention to enter a line or column number
-- to not display "No results" immediately.
if i == #path then
path = path:sub(1, i - 1)
else
local location_value = tonumber(path:sub(i + 1))
if location_value then
table.insert(location_numbers, location_value)
path = path:sub(1, i - 1)
if #location_numbers == 2 then
-- There couldn't be more than 2 : separated number
break
end
end
end
end
end
if #location_numbers == 2 then
-- because of the reverse the line number will be second
return path, location_numbers[2], location_numbers[1]
end
if #location_numbers == 1 then
return path, location_numbers[1], 0
end
return path, 0, 0
end
return utils
+45
View File
@@ -78,3 +78,48 @@ describe("is_uri", function()
end
end)
end)
describe("separates file path location", function()
local suites = {
{
input = "file.txt:12:4",
file = "file.txt",
row = 12,
col = 4,
},
{
input = "file.txt:12",
file = "file.txt",
row = 12,
col = 0,
},
{
input = "file:12:4",
file = "file",
row = 12,
col = 4,
},
{
input = "file:12:",
file = "file",
row = 12,
col = 0,
},
{
input = "file:",
file = "file",
row = 0,
col = 0,
},
}
for _, suite in ipairs(suites) do
it("separtates file path for " .. suite.input, function()
local file, row, col = utils.separate_file_path_location(suite.input)
assert.are.equal(file, suite.file)
assert.are.equal(row, suite.row)
assert.are.equal(col, suite.col)
end)
end
end)