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