Sometimes ago I have stated I was using ViM.
After Bram Moolenaar's death,
ViM's future was changed
and I could not hope for Tree-sitter integration
anymore, so, after a while, I have decided to move to NeoViM.
I usually reflect on my workflow early in the morning, before the breakfast,
one of the thing I have noticed, using Telescope
to find/open files, is my tendency to re-open the same files over and over during
a coding session.
For example, I open A.hs
to mainly work on it, then temporarily open B.hs
to check or change something, close it, keep working on A.hs
, then open C.hs
change something, close it, keep working on A.hs
, re-open B.hs
, etc.
I have tried telescope-recent-files / telescope-all-recent,
but they were too limited (displaying only recent files), or heavy (requiring SQLite),
so I have decided to come up with my own solution.
To build a Telescope you need few things:
- A picker which is the input list (e.g. files, lines, history, git commits, etc.)
- A sorter to arrange results for the picker
- Eventually a previewer to print something in the panel
Let's start with the configuration:
require('telescope').setup {
defaults = {
file_sorter = opened_files_sorter,
},
}
A Sorter
is actually an object:
local function opened_files_sorter(opts)
opts = opts or {}
local sorters = require("telescope.sorters")
local fuzzy_sorter = sorters.get_fzy_sorter(opts)
return sorters.new {
scoring_function = function(_, prompt, line, entry, cb_add, cb_filter)
end,
highlighter = fuzzy_sorter.highlighter,
}
end
A Sorter
is composed by:
- A scoring function which mainly takes the prompt (what we are looking for), the line and the entry we are trying to sort (provided by the picker)
- A highlighting function
I have decided to take de builtin fuzzer sorter as basis.
Let's rely on it as first implementation:
local function opened_files_sorter(opts)
opts = opts or {}
local sorters = require("telescope.sorters")
local fuzzy_sorter = sorters.get_fzy_sorter(opts)
return sorters.new {
scoring_function = function(_, prompt, line, entry, cb_add, cb_filter)
local FILTERED = -1
local filepath = vim.fn.resolve(vim.fn.getcwd() .. '/' .. entry.value)
local base_score = fuzzy_sorter:scoring_function(prompt, line, entry, cb_add, cb_filter)
if base_score == FILTERED then
return FILTERED
end
return base_score
end
end,
highlighter = fuzzy_sorter.highlighter,
}
end
Then we should collect opened files:
local opened_files_since_start = {}
local function update_opened_files()
local current_file = vim.fn.expand('%:p')
opened_files_since_start[current_file] = true
end
local autocmd = vim.api.nvim_create_autocmd
autocmd('BufReadPost', {
pattern = '',
callback = update_opened_files
})
so we can change the score regarding opened files:
local function opened_files_sorter(opts)
opts = opts or {}
local sorters = require("telescope.sorters")
local fuzzy_sorter = sorters.get_fzy_sorter(opts)
return sorters.new {
scoring_function = function(_, prompt, line, entry, cb_add, cb_filter)
local FILTERED = -1
local filepath = vim.fn.resolve(vim.fn.getcwd() .. '/' .. entry.value)
local base_score = fuzzy_sorter:scoring_function(prompt, line, entry, cb_add, cb_filter)
if base_score == FILTERED then
return FILTERED
end
if opened_files_since_start[filepath] then
return 0.001 * base_score
else
return base_score
end
end,
highlighter = fuzzy_sorter.highlighter,
}
end
So far so good, to limit the results, I want to filter-out the currently opened files:
local function opened_files_sorter(opts)
opts = opts or {}
local sorters = require("telescope.sorters")
local fuzzy_sorter = sorters.get_fzy_sorter(opts)
return sorters.new {
scoring_function = function(_, prompt, line, entry, cb_add, cb_filter)
local FILTERED = -1
local filepath = vim.fn.resolve(vim.fn.getcwd() .. '/' .. entry.value)
for _, buf in ipairs(vim.fn.getbufinfo({ buflisted = 1 })) do
if buf.windows and #buf.windows > 0 and buf.name == filepath then
return FILTERED
end
end
local base_score = fuzzy_sorter:scoring_function(prompt, line, entry, cb_add, cb_filter)
if base_score == FILTERED then
return FILTERED
end
if opened_files_since_start[filepath] then
return 0.001 * base_score
else
return base_score
end
end,
highlighter = fuzzy_sorter.highlighter,
}
end
And finally, I want to get rid of binary files (i.e. file extensions already listed in wildignore
):
local function opened_files_sorter(opts)
opts = opts or {}
local sorters = require("telescope.sorters")
local fuzzy_sorter = sorters.get_fzy_sorter(opts)
return sorters.new {
scoring_function = function(_, prompt, line, entry, cb_add, cb_filter)
local FILTERED = -1
local filepath = vim.fn.resolve(vim.fn.getcwd() .. '/' .. entry.value)
for _, buf in ipairs(vim.fn.getbufinfo({ buflisted = 1 })) do
if buf.windows and #buf.windows > 0 and buf.name == filepath then
return FILTERED
end
end
for _, pat in ipairs(vim.split(vim.o.wildignore, ',')) do
if match_glob(pat, filepath) then
return FILTERED
end
end
local base_score = fuzzy_sorter:scoring_function(prompt, line, entry, cb_add, cb_filter)
if base_score == FILTERED then
return FILTERED
end
if opened_files_since_start[filepath] then
return 0.001 * base_score
else
return base_score
end
end,
highlighter = fuzzy_sorter.highlighter,
}
end