feat: Combine configuration into picker.new()

This commit is contained in:
TJ DeVries
2020-08-31 16:44:47 -04:00
parent 0af8a911b4
commit e38589f265
6 changed files with 298 additions and 309 deletions

View File

@@ -0,0 +1,109 @@
local NGram = {}
NGram.__index = NGram
function NGram:new(opts)
-- TODO: Add padding
opts = opts or {}
return setmetatable({
N = opts.N or 2,
split = opts.split or "/",
_depth = 5,
_grams = setmetatable({}, utils.default_table_mt)
}, self)
end
local min = math.min
function NGram:_split(word)
local word_len = #word
local result = {}
for i = 1, word_len - 1 do
-- for j = i + (self.N - 1), min(i + self._depth - 1, word_len) do
-- table.insert(result, string.sub(word, i, j))
-- end
table.insert(result, string.sub(word, i, i + self.N - 1))
end
return result
end
-- local function pairsByKeys (t, f)
-- local a = {}
-- for n in pairs(t) do table.insert(a, n) end
-- table.sort(a, f)
-- local i = 0 -- iterator variable
-- local iter = function () -- iterator function
-- i = i + 1
-- if a[i] == nil then return nil
-- else return a[i], t[a[i]]
-- end
-- end
-- return iter
-- end
function NGram:add(word)
local split_word = self:_split(word)
for _, k in ipairs(split_word) do
local counts = self._grams[k]
if counts[word] == nil then
counts[word] = 0
end
counts[word] = counts[word] + 1
end
end
function NGram:_items_sharing_ngrams(query)
local split_query = self:_split(query)
-- Matched string to number of N-grams shared with the query string.
local shared = {}
local remaining = {}
for _, ngram in ipairs(split_query) do
remaining = {}
for match, count in pairs(self._grams[ngram] or {}) do
remaining[match] = remaining[match] or count
if remaining[match] > 0 then
remaining[match] = remaining[match] - 1
shared[match] = (shared[match] or 0) + 1
end
end
end
return shared
end
function NGram:search(query, show_values)
local sharing_ngrams = self:_items_sharing_ngrams(query)
local results = {}
for name, count in pairs(sharing_ngrams) do
local allgrams = #query + #name - (2 * self.N) - count + 2
table.insert(results, {name, count / allgrams})
end
table.sort(results, function(left, right)
return left[2] > right[2]
end)
if not show_values then
for k, v in ipairs(results) do
results[k] = v[1]
end
end
return results
end
function NGram:find(query)
return self:search(query)[1]
end
function NGram:score(query)
return (self:search(query, true)[1] or {})[2] or 0
end

View File

@@ -8,50 +8,20 @@ local finders = require('telescope.finders')
local previewers = require('telescope.previewers') local previewers = require('telescope.previewers')
local pickers = require('telescope.pickers') local pickers = require('telescope.pickers')
local sorters = require('telescope.sorters') local sorters = require('telescope.sorters')
local utils = require('telescope.utils')
local builtin = {} local builtin = {}
local ifnil = function(x, was_nil, was_not_nil) if x == nil then return was_nil else return was_not_nil end end
builtin.git_files = function(opts) builtin.git_files = function(opts)
opts = opts or {} pickers.new(opts, {
prompt = 'Git File',
local show_preview = ifnil(opts.show_preview, true, opts.show_preview) finder = finders.new_oneshot_job({ "git", "ls-files" }),
previewer = previewers.cat,
local file_finder = finders.new { sorter = sorters.get_norcalli_sorter(),
static = true, }):find()
fn_command = function(self)
return {
command = 'git',
args = {'ls-files'}
}
end,
}
local file_previewer = previewers.cat
local file_picker = pickers.new {
previewer = show_preview and file_previewer,
selection_strategy = opts.selection_strategy,
}
-- local file_sorter = telescope.sorters.get_ngram_sorter()
-- local file_sorter = require('telescope.sorters').get_levenshtein_sorter()
local file_sorter = sorters.get_norcalli_sorter()
file_picker:find {
prompt = 'Simple File',
finder = file_finder,
sorter = file_sorter,
border = opts.border,
borderchars = opts.borderchars,
}
end end
builtin.live_grep = function() builtin.live_grep = function(opts)
local live_grepper = finders.new { local live_grepper = finders.new {
maximum_results = 1000, maximum_results = 1000,
@@ -68,15 +38,13 @@ builtin.live_grep = function()
end end
} }
local file_previewer = previewers.vimgrep pickers.new(opts, {
local file_picker = pickers.new { prompt = 'Live Grep',
previewer = file_previewer finder = live_grepper,
} previewer = previewers.vimgrep,
}):find()
-- local file_sorter = telescope.sorters.get_ngram_sorter()
-- local file_sorter = require('telescope.sorters').get_levenshtein_sorter()
-- local file_sorter = sorters.get_norcalli_sorter()
-- TODO: Incorporate this.
-- Weight the results somehow to be more likely to be the ones that you've opened. -- Weight the results somehow to be more likely to be the ones that you've opened.
-- local old_files = {} -- local old_files = {}
-- for _, f in ipairs(vim.v.oldfiles) do -- for _, f in ipairs(vim.v.oldfiles) do
@@ -102,15 +70,9 @@ builtin.live_grep = function()
-- end -- end
-- end -- end
-- } -- }
file_picker:find {
prompt = 'Live Grep',
finder = live_grepper,
sorter = oldfiles_sorter,
}
end end
builtin.lsp_references = function() builtin.lsp_references = function(opts)
local params = vim.lsp.util.make_position_params() local params = vim.lsp.util.make_position_params()
params.context = { includeDeclaration = true } params.context = { includeDeclaration = true }
@@ -120,87 +82,34 @@ builtin.lsp_references = function()
vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {}) vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {})
end end
local results = {} local results = utils.quickfix_items_to_entries(locations)
for _, entry in ipairs(locations) do
local vimgrep_str = string.format(
"%s:%s:%s: %s",
vim.fn.fnamemodify(entry.filename, ":."),
entry.lnum,
entry.col,
entry.text
)
table.insert(results, {
valid = true,
value = entry,
ordinal = vimgrep_str,
display = vimgrep_str,
})
end
if vim.tbl_isempty(results) then if vim.tbl_isempty(results) then
return return
end end
local lsp_reference_finder = finders.new { local reference_picker = pickers.new(opts, {
results = results prompt = 'LSP References',
} finder = finders.new_table(results),
previewer = previewers.qflist,
local reference_previewer = previewers.qflist sorter = sorters.get_norcalli_sorter(),
local reference_picker = pickers.new { }):find()
previewer = reference_previewer
}
reference_picker:find {
prompt = 'LSP References',
finder = lsp_reference_finder,
sorter = sorters.get_norcalli_sorter(),
}
end end
builtin.quickfix = function() builtin.quickfix = function(opts)
local locations = vim.fn.getqflist() local locations = vim.fn.getqflist()
local results = utils.quickfix_items_to_entries(locations)
local results = {}
for _, entry in ipairs(locations) do
if not entry.filename then
entry.filename = vim.api.nvim_buf_get_name(entry.bufnr)
end
local vimgrep_str = string.format(
"%s:%s:%s: %s",
vim.fn.fnamemodify(entry.filename, ":."),
entry.lnum,
entry.col,
entry.text
)
table.insert(results, {
valid = true,
value = entry,
ordinal = vimgrep_str,
display = vimgrep_str,
})
end
if vim.tbl_isempty(results) then if vim.tbl_isempty(results) then
return return
end end
local quickfix_finder = finders.new { pickers.new(opts, {
results = results prompt = 'Quickfix',
} finder = finders.new_table(results),
previewer = previewers.qflist,
local quickfix_previewer = previewers.qflist sorter = sorters.get_norcalli_sorter(),
local quickfix_picker = pickers.new { }):find()
previewer = quickfix_previewer
}
quickfix_picker:find {
prompt = 'Quickfix',
finder = quickfix_finder,
sorter = sorters.get_norcalli_sorter(),
}
end end
builtin.grep_string = function(opts) builtin.grep_string = function(opts)
@@ -208,42 +117,23 @@ builtin.grep_string = function(opts)
local search = opts.search or vim.fn.expand("<cword>") local search = opts.search or vim.fn.expand("<cword>")
local grepper = finders.new { local file_picker = pickers.new(opts, {
maximum_results = 10000, prompt = 'Find Word',
finder = finders.new_oneshot_job {'rg', '--vimgrep', search},
-- TODO: We can optimize these. previewer = previewers.vimgrep,
-- static = true,
fn_command = function()
return {
command = 'rg',
args = {"--vimgrep", search},
}
end
}
local file_picker = pickers.new {
previewer = previewers.vimgrep
}
file_picker:find {
prompt = 'Live Grep',
finder = grepper,
sorter = sorters.get_norcalli_sorter(), sorter = sorters.get_norcalli_sorter(),
} }):find()
end end
builtin.oldfiles = function() builtin.oldfiles = function(opts)
local oldfiles_finder = finders.new { pickers.new(opts, {
results = vim.v.oldfiles
}
local file_picker = pickers.new{}
file_picker:find {
prompt = 'Oldfiles', prompt = 'Oldfiles',
finder = oldfiles_finder, finder = finders.new_table(vim.tbl_filter(function(val)
sorter = sorters.get_norcalli_sorter() return 0 ~= vim.fn.filereadable(val)
} end, vim.v.oldfiles)),
sorter = sorters.get_norcalli_sorter(),
previewer = previewers.cat,
}):find()
end end
return builtin return builtin

View File

@@ -1,17 +1,19 @@
local Job = require('plenary.job') local Job = require('plenary.job')
local log = require('telescope.log') local log = require('telescope.log')
local utils = require('telescope.utils')
local finders = {} local finders = {}
-- TODO: We should make a few different "FinderGenerators": -- TODO: We should make a few different "FinderGenerators":
-- SimpleListFinder(my_list) -- SimpleListFinder(my_list)
-- FunctionFinder(my_func) -- FunctionFinder(my_func)
-- JobFinder(my_job_args) -- JobFinder(my_job_args)
---@class Finder ---@class Finder
local Finder = {} local Finder = {
hello = "world"
}
Finder.__index = Finder Finder.__index = Finder
Finder.__call = function(t, ... ) return t:_find(...) end Finder.__call = function(t, ... ) return t:_find(...) end
@@ -35,7 +37,7 @@ function Finder:new(opts)
-- string -- string
-- list -- list
-- ... -- ...
return setmetatable({ local obj = setmetatable({
results = opts.results, results = opts.results,
fn_command = opts.fn_command, fn_command = opts.fn_command,
@@ -45,7 +47,9 @@ function Finder:new(opts)
-- Maximum number of results to process. -- Maximum number of results to process.
-- Particularly useful for live updating large queries. -- Particularly useful for live updating large queries.
maximum_results = opts.maximum_results, maximum_results = opts.maximum_results,
}, Finder) }, self)
return obj
end end
-- Probably should use the word apply here, since we're apply the callback passed to us by -- Probably should use the word apply here, since we're apply the callback passed to us by
@@ -145,8 +149,38 @@ end
--- Return a new Finder --- Return a new Finder
-- --
--@return Finder --@return Finder
finders.new = function(...) finders.new = function(opts)
return Finder:new(...) return Finder:new(opts)
end
-- TODO: Is this worth making?
-- finders.new_responsive_job = function(opts)
-- return finders.new {
-- maximum_results = get_default(opts.maximum_results, 2000),
-- }
-- end
finders.new_oneshot_job = function(command_list)
command_list = vim.deepcopy(command_list)
local command = table.remove(command_list, 1)
return finders.new {
static = true,
fn_command = function()
return {
command = command,
args = command_list,
}
end,
}
end
finders.new_table = function(t)
return finders.new {
results = t
}
end end
-- We should add a few utility functions here... -- We should add a few utility functions here...
@@ -155,6 +189,6 @@ end
-- finders.new_one_shot_job -- finders.new_one_shot_job
-- finders.new_table -- finders.new_table
finders.Finder = Finder -- finders.Finder = Finder
return finders return finders

View File

@@ -1,5 +1,6 @@
local a = vim.api local a = vim.api
local popup = require('popup') local popup = require('popup')
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
local actions = require('telescope.actions') local actions = require('telescope.actions')
local log = require('telescope.log') local log = require('telescope.log')
@@ -8,15 +9,23 @@ local state = require('telescope.state')
local utils = require('telescope.utils') local utils = require('telescope.utils')
local Entry = require('telescope.entry') local Entry = require('telescope.entry')
local Sorter = require('telescope.sorters').Sorter
local Previewer = require('telescope.previewers').Previewer
local has_devicons, devicons = pcall(require, 'nvim-web-devicons') local get_default = utils.get_default
-- TODO: Make this work with deep extend I think.
local extend = function(opts, defaults)
local result = vim.deepcopy(opts or {})
for k, v in pairs(defaults or {}) do
if result[k] == nil then
result[k] = v
end
end
return result
end
local pickers = {} local pickers = {}
local ifnil = function(x, was_nil, was_not_nil) if x == nil then return was_nil else return was_not_nil end end
local default_mappings = { local default_mappings = {
i = { i = {
["<C-n>"] = actions.move_selection_next, ["<C-n>"] = actions.move_selection_next,
@@ -42,58 +51,60 @@ local default_mappings = {
local Picker = {} local Picker = {}
Picker.__index = Picker Picker.__index = Picker
assert(Sorter)
assert(Previewer)
---@class PickOpts
---@field filter Sorter
---@field maps table
---@field unseen string
--- Create new picker --- Create new picker
--- @param opts PickOpts
function Picker:new(opts) function Picker:new(opts)
opts = opts or {} opts = opts or {}
return setmetatable({ return setmetatable({
filter = opts.filter, prompt = opts.prompt,
finder = opts.finder,
sorter = opts.sorter,
previewer = opts.previewer, previewer = opts.previewer,
maps = opts.maps,
mappings = get_default(opts.mappings, default_mappings),
get_window_options = opts.get_window_options, get_window_options = opts.get_window_options,
selection_strategy = opts.selection_strategy, selection_strategy = opts.selection_strategy,
window = {
border = get_default(opts.border, {}),
borderchars = get_default(opts.borderchars, { '', '', '', '', '', '', '', ''}),
},
preview_cutoff = get_default(opts.preview_cutoff, 120),
}, Picker) }, Picker)
end end
function Picker:get_window_options(max_columns, max_lines, prompt_title, find_options) function Picker:get_window_options(max_columns, max_lines, prompt_title)
local popup_border = self.window.border
local popup_border = ifnil(find_options.border, {}, find_options.border) local popup_borderchars = self.window.borderchars
local preview = { local preview = {
border = popup_border, border = popup_border,
borderchars = find_options.borderchars or nil, borderchars = popup_borderchars,
enter = false, enter = false,
highlight = false highlight = false
} }
local results = { local results = {
border = popup_border, border = popup_border,
borderchars = find_options.borderchars or nil, borderchars = popup_borderchars,
enter = false, enter = false,
} }
local prompt = { local prompt = {
title = prompt_title, title = prompt_title,
border = popup_border, border = popup_border,
borderchars = find_options.borderchars or nil, borderchars = popup_borderchars,
enter = true enter = true
} }
-- TODO: Test with 120 width terminal -- TODO: Test with 120 width terminal
local width_padding = 10 local width_padding = 10
if not self.previewer or max_columns < find_options.preview_cutoff then if not self.previewer or max_columns < self.preview_cutoff then
width_padding = 2
preview.width = 0 preview.width = 0
elseif max_columns < 150 then elseif max_columns < 150 then
width_padding = 5 width_padding = 5
@@ -110,7 +121,7 @@ function Picker:get_window_options(max_columns, max_lines, prompt_title, find_op
local base_height local base_height
if max_lines < 40 then if max_lines < 40 then
base_height = math.floor(max_lines * 0.5) base_height = math.min(math.floor(max_lines * 0.8), max_lines - 8)
else else
base_height = math.floor(max_lines * 0.8) base_height = math.floor(max_lines * 0.8)
end end
@@ -143,21 +154,10 @@ function Picker:get_window_options(max_columns, max_lines, prompt_title, find_op
} }
end end
-- opts.preview_cutoff = 120 function Picker:find()
function Picker:find(opts) local prompt_string = assert(self.prompt, "Prompt is required.")
opts = opts or {} local finder = assert(self.finder, "Finder is required to do picking")
local sorter = self.sorter
if opts.preview_cutoff == nil then
opts.preview_cutoff = 120
end
opts.borderchars = opts.borderchars or { '', '', '', '', '', '', '', ''}
local finder = opts.finder
assert(finder, "Finder is required to do picking")
local sorter = opts.sorter
local prompt_string = opts.prompt
self.original_win_id = a.nvim_get_current_win() self.original_win_id = a.nvim_get_current_win()
@@ -165,7 +165,7 @@ function Picker:find(opts)
-- 1. Prompt window -- 1. Prompt window
-- 2. Options window -- 2. Options window
-- 3. Preview window -- 3. Preview window
local popup_opts = self:get_window_options(vim.o.columns, vim.o.lines, prompt_string, opts) local popup_opts = self:get_window_options(vim.o.columns, vim.o.lines, prompt_string)
-- TODO: Add back the borders after fixing some stuff in popup.nvim -- TODO: Add back the borders after fixing some stuff in popup.nvim
local results_win, results_opts = popup.create('', popup_opts.results) local results_win, results_opts = popup.create('', popup_opts.results)
@@ -338,8 +338,7 @@ function Picker:find(opts)
finder = finder, finder = finder,
}) })
-- mappings.set_keymap(prompt_bufnr, results_bufnr) mappings.apply_keymap(prompt_bufnr, self.mappings)
mappings.apply_keymap(prompt_bufnr, opts.mappings or default_mappings)
vim.cmd [[startinsert]] vim.cmd [[startinsert]]
end end
@@ -462,8 +461,9 @@ function Picker:set_selection(row)
end end
end end
pickers.new = function(...) pickers.new = function(opts, defaults)
return Picker:new(...) opts = extend(opts, defaults)
return Picker:new(opts)
end end
-- TODO: We should consider adding `process_bulk` or `bulk_entry_manager` for things -- TODO: We should consider adding `process_bulk` or `bulk_entry_manager` for things

View File

@@ -1,5 +1,25 @@
local utils = {} local utils = {}
utils.if_nil = function(x, was_nil, was_not_nil)
if x == nil then
return was_nil
else
return was_not_nil
end
end
utils.get_default = function(x, default)
return utils.if_nil(x, default, x)
end
utils.get_lazy_default = function(x, defaulter, ...)
if x == nil then
return defaulter(...)
else
return x
end
end
local function reversedipairsiter(t, i) local function reversedipairsiter(t, i)
i = i - 1 i = i - 1
if i ~= 0 then if i ~= 0 then
@@ -27,119 +47,31 @@ utils.repeated_table = function(n, val)
return empty_lines return empty_lines
end end
utils.quickfix_items_to_entries = function(locations)
local NGram = {}
NGram.__index = NGram
function NGram:new(opts)
-- TODO: Add padding
opts = opts or {}
return setmetatable({
N = opts.N or 2,
split = opts.split or "/",
_depth = 5,
_grams = setmetatable({}, utils.default_table_mt)
}, self)
end
local min = math.min
function NGram:_split(word)
local word_len = #word
local result = {}
for i = 1, word_len - 1 do
-- for j = i + (self.N - 1), min(i + self._depth - 1, word_len) do
-- table.insert(result, string.sub(word, i, j))
-- end
table.insert(result, string.sub(word, i, i + self.N - 1))
end
return result
end
-- local function pairsByKeys (t, f)
-- local a = {}
-- for n in pairs(t) do table.insert(a, n) end
-- table.sort(a, f)
-- local i = 0 -- iterator variable
-- local iter = function () -- iterator function
-- i = i + 1
-- if a[i] == nil then return nil
-- else return a[i], t[a[i]]
-- end
-- end
-- return iter
-- end
function NGram:add(word)
local split_word = self:_split(word)
for _, k in ipairs(split_word) do
local counts = self._grams[k]
if counts[word] == nil then
counts[word] = 0
end
counts[word] = counts[word] + 1
end
end
function NGram:_items_sharing_ngrams(query)
local split_query = self:_split(query)
-- Matched string to number of N-grams shared with the query string.
local shared = {}
local remaining = {}
for _, ngram in ipairs(split_query) do
remaining = {}
for match, count in pairs(self._grams[ngram] or {}) do
remaining[match] = remaining[match] or count
if remaining[match] > 0 then
remaining[match] = remaining[match] - 1
shared[match] = (shared[match] or 0) + 1
end
end
end
return shared
end
function NGram:search(query, show_values)
local sharing_ngrams = self:_items_sharing_ngrams(query)
local results = {} local results = {}
for name, count in pairs(sharing_ngrams) do
local allgrams = #query + #name - (2 * self.N) - count + 2
table.insert(results, {name, count / allgrams})
end
table.sort(results, function(left, right) for _, entry in ipairs(locations) do
return left[2] > right[2] local vimgrep_str = string.format(
end) "%s:%s:%s: %s",
vim.fn.fnamemodify(entry.filename, ":."),
entry.lnum,
entry.col,
entry.text
)
if not show_values then table.insert(results, {
for k, v in ipairs(results) do valid = true,
results[k] = v[1] value = entry,
end ordinal = vimgrep_str,
display = vimgrep_str,
})
end end
return results return results
end end
function NGram:find(query)
return self:search(query)[1]
end
function NGram:score(query)
return (self:search(query, true)[1] or {})[2] or 0
end
utils.new_ngram = function() utils.new_ngram = function()
return NGram:new() return require("telescope._private.NGram"):new()
end end
return utils return utils

View File

@@ -0,0 +1,24 @@
builtin.git_files = function(opts)
opts = opts or {}
opts.show_preview = get_default(opts.show_preview, true)
opts.finder = opts.finder or finders.new {
static = true,
fn_command = function()
return {
command = 'git',
args = {'ls-files'}
}
end,
}
opts.prompt = opts.prompt or 'Simple File'
opts.previewer = opts.previewer or previewers.cat
opts.sorter = opts.sorter or sorters.get_norcalli_sorter()
pickers.new(opts):find()
end