diff options
| author | TJ DeVries <devries.timothyj@gmail.com> | 2021-02-22 11:30:57 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-22 11:30:57 -0500 |
| commit | d7c02e3b52b5a13265e071d0de2d6a989110a515 (patch) | |
| tree | 165f83bac6ffbb8594f6afb53905bc7bc64a4359 /lua | |
| parent | 1c5e42a6a5a6d29be8fbf8dcefb0d8da535eac9a (diff) | |
feat: Action improvements (#472)
* feat: replace_map
* feat: Add action_set and action_state
* fix: Move all actions.get_ to action_state.get_
* fix: replace all internal references of _goto_file_selection_edit
* feat: add some docs
* fix: lint
* feat: actions.select
* remove mentions and usage of goto_file_selection APIs
* feat: special case attach_mappings to be overridable and defaultable
* Having goto_file_selection mappings will cause a error
as well as replacing deprecated goto_file_selection methodes
For config and replacing use this instead:
- actions.select_default
- actions.select_horizonal
- actions.select_vertical
- actions.select_tab
Only replacing:
- actions.set.edit -- for replacing all select functions
* adds actions.state.select_key_to_edit_key
Co-authored-by: Simon Hauser <Simon-Hauser@outlook.de>
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/telescope/WIP.lua | 3 | ||||
| -rw-r--r-- | lua/telescope/actions/init.lua | 210 | ||||
| -rw-r--r-- | lua/telescope/actions/mt.lua | 67 | ||||
| -rw-r--r-- | lua/telescope/actions/set.lua | 125 | ||||
| -rw-r--r-- | lua/telescope/actions/state.lua | 30 | ||||
| -rw-r--r-- | lua/telescope/builtin/files.lua | 11 | ||||
| -rw-r--r-- | lua/telescope/builtin/git.lua | 6 | ||||
| -rw-r--r-- | lua/telescope/builtin/internal.lua | 70 | ||||
| -rw-r--r-- | lua/telescope/builtin/lsp.lua | 5 | ||||
| -rw-r--r-- | lua/telescope/config.lua | 2 | ||||
| -rw-r--r-- | lua/telescope/mappings.lua | 31 | ||||
| -rw-r--r-- | lua/telescope/pickers.lua | 49 | ||||
| -rw-r--r-- | lua/tests/automated/action_spec.lua | 89 | ||||
| -rw-r--r-- | lua/tests/automated/telescope_spec.lua | 345 |
14 files changed, 667 insertions, 376 deletions
diff --git a/lua/telescope/WIP.lua b/lua/telescope/WIP.lua index f6dac64..cacb438 100644 --- a/lua/telescope/WIP.lua +++ b/lua/telescope/WIP.lua @@ -1,6 +1,7 @@ local make_entry = require('telescope.make_entry') local actions = require('telescope.actions') +local action_state = require('telescope.actions.state') local finders = require('telescope.finders') local previewers = require('telescope.previewers') local pickers = require('telescope.pickers') @@ -70,7 +71,7 @@ WIP.reloader = function(opts) attach_mappings = function(prompt_bufnr, map) local reload_package = function() - local selection = actions.get_selected_entry(prompt_bufnr) + local selection = action_state.get_selected_entry(prompt_bufnr) actions.close(prompt_bufnr) diff --git a/lua/telescope/actions/init.lua b/lua/telescope/actions/init.lua index bd2c4bd..c2354b4 100644 --- a/lua/telescope/actions/init.lua +++ b/lua/telescope/actions/init.lua @@ -3,162 +3,134 @@ local a = vim.api local log = require('telescope.log') -local path = require('telescope.path') local state = require('telescope.state') local utils = require('telescope.utils') +local action_state = require('telescope.actions.state') +local action_set = require('telescope.actions.set') + local transform_mod = require('telescope.actions.mt').transform_mod local actions = setmetatable({}, { __index = function(_, k) - error("Actions does not have a value: " .. tostring(k)) + -- TODO(conni2461): Remove deprecated messages + if k:find('goto_file_selection') then + error("`" .. k .. "` is removed and no longer usable. " .. + "Use `require('telescope.actions').select_` instead. Take a look at developers.md for more Information.") + elseif k == '_goto_file_selection' then + error("`_goto_file_selection` is deprecated and no longer replaceable. " .. + "Use `require('telescope.actions.set').edit` instead. Take a look at developers.md for more Information.") + end + + error("Key does not exist for 'telescope.actions': " .. tostring(k)) end }) ---- Get the current picker object for the prompt -function actions.get_current_picker(prompt_bufnr) - return state.get_status(prompt_bufnr).picker +-- TODO(conni2461): Remove deprecated messages +local action_is_deprecated = function(name, err) + local messager = err and error or log.info + + return messager( + string.format("`actions.%s()` is deprecated." + .. "Use require('telescope.actions.state').%s() instead", + name, + name + ) + ) end ---- Move the current selection of a picker {change} rows. ---- Handles not overflowing / underflowing the list. -function actions.shift_current_selection(prompt_bufnr, change) - actions.get_current_picker(prompt_bufnr):move_selection(change) +--- Get the current entry +function actions.get_selected_entry() + -- TODO(1.0): Remove + action_is_deprecated("get_selected_entry") + return action_state.get_selected_entry() end +function actions.get_current_line() + -- TODO(1.0): Remove + action_is_deprecated("get_current_line") + return action_state.get_current_line() +end + +--- Get the current picker object for the prompt +function actions.get_current_picker(prompt_bufnr) + -- TODO(1.0): Remove + action_is_deprecated("get_current_picker") + return action_state.get_current_picker(prompt_bufnr) +end + +--- Move the selection to the next entry function actions.move_selection_next(prompt_bufnr) - actions.shift_current_selection(prompt_bufnr, 1) + action_set.shift_selection(prompt_bufnr, 1) end +--- Move the selection to the previous entry function actions.move_selection_previous(prompt_bufnr) - actions.shift_current_selection(prompt_bufnr, -1) + action_set.shift_selection(prompt_bufnr, -1) end function actions.add_selection(prompt_bufnr) - local current_picker = actions.get_current_picker(prompt_bufnr) + local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:add_selection(current_picker:get_selection_row()) end function actions.remove_selection(prompt_bufnr) - local current_picker = actions.get_current_picker(prompt_bufnr) + local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:remove_selection(current_picker:get_selection_row()) end function actions.toggle_selection(prompt_bufnr) - local current_picker = actions.get_current_picker(prompt_bufnr) + local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:toggle_selection(current_picker:get_selection_row()) end ---- Get the current entry -function actions.get_selected_entry() - return state.get_global_key('selected_entry') -end - -function actions.get_current_line() - return state.get_global_key('current_line') -end - function actions.preview_scrolling_up(prompt_bufnr) - actions.get_current_picker(prompt_bufnr).previewer:scroll_fn(-30) + -- TODO: Make number configurable. + action_state.get_current_picker(prompt_bufnr).previewer:scroll_fn(-30) end function actions.preview_scrolling_down(prompt_bufnr) - actions.get_current_picker(prompt_bufnr).previewer:scroll_fn(30) + -- TODO: Make number configurable. + action_state.get_current_picker(prompt_bufnr).previewer:scroll_fn(30) end --- TODO: It seems sometimes we get bad styling. -function actions._goto_file_selection(prompt_bufnr, command) - local entry = actions.get_selected_entry(prompt_bufnr) - - if not entry then - print("[telescope] Nothing currently selected") - return - else - local filename, row, col - if entry.filename then - filename = entry.path or entry.filename - - -- TODO: Check for off-by-one - row = entry.row or entry.lnum - col = entry.col - elseif not entry.bufnr then - -- TODO: Might want to remove this and force people - -- to put stuff into `filename` - local value = entry.value - if not value then - print("Could not do anything with blank line...") - return - end - - if type(value) == "table" then - value = entry.display - end - - local sections = vim.split(value, ":") - - filename = sections[1] - row = tonumber(sections[2]) - col = tonumber(sections[3]) - end - - local preview_win = state.get_status(prompt_bufnr).preview_win - if preview_win then - a.nvim_win_set_config(preview_win, {style = ''}) - end +function actions.center(_) + vim.cmd(':normal! zz') +end - local entry_bufnr = entry.bufnr +function actions.select_default(prompt_bufnr) + return action_set.select(prompt_bufnr, "default") +end - actions.close(prompt_bufnr) +function actions.select_horizontal(prompt_bufnr) + return action_set.select(prompt_bufnr, "horizontal") +end - if entry_bufnr then - if command == 'edit' then - vim.cmd(string.format(":buffer %d", entry_bufnr)) - elseif command == 'new' then - vim.cmd(string.format(":sbuffer %d", entry_bufnr)) - elseif command == 'vnew' then - vim.cmd(string.format(":vert sbuffer %d", entry_bufnr)) - elseif command == 'tabedit' then - vim.cmd(string.format(":tab sb %d", entry_bufnr)) - end - else - filename = path.normalize(vim.fn.fnameescape(filename), vim.fn.getcwd()) - - local bufnr = vim.api.nvim_get_current_buf() - if filename ~= vim.api.nvim_buf_get_name(bufnr) then - vim.cmd(string.format(":%s %s", command, filename)) - bufnr = vim.api.nvim_get_current_buf() - a.nvim_buf_set_option(bufnr, "buflisted", true) - end - - if row and col then - local ok, err_msg = pcall(a.nvim_win_set_cursor, 0, {row, col}) - if not ok then - log.debug("Failed to move to cursor:", err_msg, row, col) - end - end - end - vim.api.nvim_command("doautocmd filetypedetect BufRead " .. vim.fn.fnameescape(filename)) - end +function actions.select_vertical(prompt_bufnr) + return action_set.select(prompt_bufnr, "vertical") end -function actions.center(_) - vim.cmd(':normal! zz') +function actions.select_tab(prompt_bufnr) + return action_set.select(prompt_bufnr, "tab") end -function actions.goto_file_selection_edit(prompt_bufnr) - actions._goto_file_selection(prompt_bufnr, "edit") +-- TODO: consider adding float! +-- https://github.com/nvim-telescope/telescope.nvim/issues/365 + +function actions.file_edit(prompt_bufnr) + return action_set.edit(prompt_bufnr, "edit") end -function actions.goto_file_selection_split(prompt_bufnr) - actions._goto_file_selection(prompt_bufnr, "new") +function actions.file_split(prompt_bufnr) + return action_set.edit(prompt_bufnr, "new") end -function actions.goto_file_selection_vsplit(prompt_bufnr) - actions._goto_file_selection(prompt_bufnr, "vnew") +function actions.file_vsplit(prompt_bufnr) + return action_set.edit(prompt_bufnr, "vnew") end -function actions.goto_file_selection_tabedit(prompt_bufnr) - actions._goto_file_selection(prompt_bufnr, "tabedit") +function actions.file_tab(prompt_bufnr) + return action_set.edit(prompt_bufnr, "tabedit") end function actions.close_pum(_) @@ -168,7 +140,7 @@ function actions.close_pum(_) end local do_close = function(prompt_bufnr, keepinsert) - local picker = actions.get_current_picker(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) local prompt_win = state.get_status(prompt_bufnr).prompt_win local original_win_id = picker.original_win_id @@ -192,7 +164,7 @@ function actions.close(prompt_bufnr) end actions.set_command_line = function(prompt_bufnr) - local entry = actions.get_selected_entry(prompt_bufnr) + local entry = action_state.get_selected_entry(prompt_bufnr) actions.close(prompt_bufnr) vim.fn.histadd("cmd", entry.value) @@ -200,8 +172,8 @@ actions.set_command_line = function(prompt_bufnr) end actions.edit_register = function(prompt_bufnr) - local entry = actions.get_selected_entry(prompt_bufnr) - local picker = actions.get_current_picker(prompt_bufnr) + local entry = action_state.get_selected_entry(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) vim.fn.inputsave() local updated_value = vim.fn.input("Edit [" .. entry.value .. "] ❯ ", entry.content) @@ -222,7 +194,7 @@ actions.edit_register = function(prompt_bufnr) end actions.paste_register = function(prompt_bufnr) - local entry = actions.get_selected_entry(prompt_bufnr) + local entry = action_state.get_selected_entry(prompt_bufnr) actions.close(prompt_bufnr) @@ -239,21 +211,21 @@ actions.paste_register = function(prompt_bufnr) end actions.run_builtin = function(prompt_bufnr) - local entry = actions.get_selected_entry(prompt_bufnr) + local entry = action_state.get_selected_entry(prompt_bufnr) do_close(prompt_bufnr, true) require('telescope.builtin')[entry.text]() end actions.insert_symbol = function(prompt_bufnr) - local selection = actions.get_selected_entry() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) vim.api.nvim_put({selection.value[1]}, '', true, true) end -- TODO: Think about how to do this. actions.insert_value = function(prompt_bufnr) - local entry = actions.get_selected_entry(prompt_bufnr) + local entry = action_state.get_selected_entry(prompt_bufnr) vim.schedule(function() actions.close(prompt_bufnr) @@ -263,15 +235,15 @@ actions.insert_value = function(prompt_bufnr) end actions.git_checkout = function(prompt_bufnr) - local cwd = actions.get_current_picker(prompt_bufnr).cwd - local selection = actions.get_selected_entry() + local cwd = action_state.get_current_picker(prompt_bufnr).cwd + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) utils.get_os_command_output({ 'git', 'checkout', selection.value }, cwd) end actions.git_staging_toggle = function(prompt_bufnr) - local cwd = actions.get_current_picker(prompt_bufnr).cwd - local selection = actions.get_selected_entry() + local cwd = action_state.get_current_picker(prompt_bufnr).cwd + local selection = action_state.get_selected_entry() -- If parts of the file are staged and unstaged at the same time, stage -- changes. Else toggle between staged and unstaged if the file is tracked, @@ -296,7 +268,7 @@ local entry_to_qf = function(entry) end actions.send_selected_to_qflist = function(prompt_bufnr) - local picker = actions.get_current_picker(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) local qf_entries = {} for entry in pairs(picker.multi_select) do @@ -309,7 +281,7 @@ actions.send_selected_to_qflist = function(prompt_bufnr) end actions.send_to_qflist = function(prompt_bufnr) - local picker = actions.get_current_picker(prompt_bufnr) + local picker = action_state.get_current_picker(prompt_bufnr) local manager = picker.manager local qf_entries = {} diff --git a/lua/telescope/actions/mt.lua b/lua/telescope/actions/mt.lua index 5ad14a1..5efeddf 100644 --- a/lua/telescope/actions/mt.lua +++ b/lua/telescope/actions/mt.lua @@ -1,23 +1,41 @@ local action_mt = {} +--- Checks all replacement combinations to determine which function to run. +--- If no replacement can be found, then it will run the original function +local run_replace_or_original = function(replacements, original_func, ...) + for _, replacement_map in ipairs(replacements or {}) do + for condition, replacement in pairs(replacement_map) do + if condition == true or condition(...) then + return replacement(...) + end + end + end + + return original_func(...) +end + action_mt.create = function(mod) local mt = { __call = function(t, ...) local values = {} - for _, v in ipairs(t) do - local func = t._replacements[v] or mod[v] - - if t._pre[v] then - t._pre[v](...) + for _, action_name in ipairs(t) do + if t._pre[action_name] then + t._pre[action_name](...) end - local result = {func(...)} + local result = { + run_replace_or_original( + t._replacements[action_name], + mod[action_name], + ... + ) + } for _, res in ipairs(result) do table.insert(values, res) end - if t._post[v] then - t._post[v](...) + if t._post[action_name] then + t._post[action_name](...) end end @@ -54,8 +72,33 @@ action_mt.create = function(mod) function mt:replace(v) assert(#self == 1, "Cannot replace an already combined action") + return self:replace_map { [true] = v } + end + + function mt:replace_if(condition, replacement) + assert(#self == 1, "Cannot replace an already combined action") + + return self:replace_map { [condition] = replacement } + end + + --- Replace table with + -- Example: + -- + -- actions.select:replace_map { + -- [function() return filetype == 'lua' end] = actions.file_split, + -- [function() return filetype == 'other' end] = actions.file_split_edit, + -- } + function mt:replace_map(tbl) + assert(#self == 1, "Cannot replace an already combined action") + local action_name = self[1] - mt._replacements[action_name] = v + + if not mt._replacements[action_name] then + mt._replacements[action_name] = {} + end + + table.insert(mt._replacements[action_name], 1, tbl) + return self end function mt:enhance(opts) @@ -69,6 +112,8 @@ action_mt.create = function(mod) if opts.post then mt._post[action_name] = opts.post end + + return self end return mt @@ -81,7 +126,9 @@ end action_mt.transform_mod = function(mod) local mt = action_mt.create(mod) - local redirect = {} + -- Pass the metatable of the module if applicable. + -- This allows for custom errors, lookups, etc. + local redirect = setmetatable({}, getmetatable(mod) or {}) for k, _ in pairs(mod) do redirect[k] = action_mt.transform(k, mt) diff --git a/lua/telescope/actions/set.lua b/lua/telescope/actions/set.lua new file mode 100644 index 0000000..13cc75f --- /dev/null +++ b/lua/telescope/actions/set.lua @@ -0,0 +1,125 @@ +local a = vim.api + +local log = require('telescope.log') +local path = require('telescope.path') +local state = require('telescope.state') + +local action_state = require('telescope.actions.state') + +local transform_mod = require('telescope.actions.mt').transform_mod + +--- Telescope action sets are used to provide an interface for managing +--- actions that all primarily do the same thing, but with slight tweaks. +--- +--- For example, when editing files you may want it in the current split, +--- a vertical split, etc. Instead of making users have to overwrite EACH +--- of those every time they want to change this behavior, they can instead +--- replace the `set` itself and then it will work great and they're done. +local set = setmetatable({}, { + __index = function(_, k) + error("'telescope.actions.set' does not have a value: " .. tostring(k)) + end +}) + +--- Move the current selection of a picker {change} rows. +--- Handles not overflowing / underflowing the list. +---@param prompt_bufnr number: The prompt bufnr +---@param change number: The amount to shift the selection by +set.shift_selection = function(prompt_bufnr, change) + action_state.get_current_picker(prompt_bufnr):move_selection(change) +end + +--- Select the current entry. This is the action set to overwrite common +--- actions by the user. +--- +--- By default maps to editing a file. +---@param prompt_bufnr number: The prompt bufnr +---@param type string: The type of selection to make +-- Valid types include: "default", "horizontal", "vertical", "tabedit" +set.select = function(prompt_bufnr, type) + return set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type)) +end + +--- Edit a file based on the current selection. +---@param prompt_bufnr number: The prompt bufnr +---@param command string: The command to use to open the file. +-- Valid commands include: "edit", "new", "vedit", "tabedit" +set.edit = function(prompt_bufnr, command) + local entry = action_state.get_selected_entry() + + if not entry then + print("[telescope] Nothing currently selected") + return + else + local filename, row, col + if entry.filename then + filename = entry.path or entry.filename + + -- TODO: Check for off-by-one + row = entry.row or entry.lnum + col = entry.col + elseif not entry.bufnr then + -- TODO: Might want to remove this and force people + -- to put stuff into `filename` + local value = entry.value + if not value then + print("Could not do anything with blank line...") + return + end + + if type(value) == "table" then + value = entry.display + end + + local sections = vim.split(value, ":") + + filename = sections[1] + row = tonumber(sections[2]) + col = tonumber(sections[3]) + end + + local preview_win = state.get_status(prompt_bufnr).preview_win + if preview_win then + a.nvim_win_set_config(preview_win, {style = ''}) + end + + local entry_bufnr = entry.bufnr + + require('telescope.actions').close(prompt_bufnr) + + if entry_bufnr then + if command == 'edit' then + vim.cmd(string.format(":buffer %d", entry_bufnr)) + elseif command == 'new' then + vim.cmd(string.format(":sbuffer %d", entry_bufnr)) + elseif command == 'vnew' then + vim.cmd(string.format(":vert sbuffer %d", entry_bufnr)) + elseif command == 'tabedit' then + vim.cmd(string.format(":tab sb %d", entry_bufnr)) + end + else + filename = path.normalize(vim.fn.fnameescape(filename), vim.fn.getcwd()) + + local bufnr = vim.api.nvim_get_current_buf() + if filename ~= vim.api.nvim_buf_get_name(bufnr) then + vim.cmd(string.format(":%s %s", command, filename)) + bufnr = vim.api.nvim_get_current_buf() + a.nvim_buf_set_option(bufnr, "buflisted", true) + end + + if row and col then + local ok, err_msg = pcall(a.nvim_win_set_cursor, 0, {row, col}) + if not ok then + log.debug("Failed to move to cursor:", err_msg, row, col) + end + end + end + vim.api.nvim_command("doautocmd filetypedetect BufRead " .. vim.fn.fnameescape(filename)) + end +end + +-- ================================================== +-- Transforms modules and sets the corect metatables. +-- ================================================== +set = transform_mod(set) +return set diff --git a/lua/telescope/actions/state.lua b/lua/telescope/actions/state.lua new file mode 100644 index 0000000..0752171 --- /dev/null +++ b/lua/telescope/actions/state.lua @@ -0,0 +1,30 @@ +local global_state = require('telescope.state') + +local action_state = {} + +--- Get the current entry +function action_state.get_selected_entry() + return global_state.get_global_key('selected_entry') +end + +--- Gets the current line +function action_state.get_current_line() + return global_state.get_global_key('current_line') +end + +--- Gets the current picker +function action_state.get_current_picker(prompt_bufnr) + return global_state.get_status(prompt_bufnr).picker +end + +local select_to_edit_map = { + default = "edit", + horizontal = "new", + vertical = "vnew", + tab = "tabedit", +} +function action_state.select_key_to_edit_key(type) + return select_to_edit_map[type] +end + +return action_state diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index 03828e6..8a46817 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -1,4 +1,5 @@ -local actions = require('telescope.actions') +local action_state = require('telescope.actions.state') +local action_set = require('telescope.actions.set') local finders = require('telescope.finders') local make_entry = require('telescope.make_entry') local pickers = require('telescope.pickers') @@ -256,9 +257,9 @@ files.current_buffer_fuzzy_find = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function() - actions._goto_file_selection:enhance { + action_set.select:enhance { post = function() - local selection = actions.get_selected_entry() + local selection = action_state.get_selected_entry() vim.api.nvim_win_set_cursor(0, {selection.lnum, 0}) end, } @@ -292,9 +293,9 @@ files.tags = function(opts) previewer = previewers.ctags.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function() - actions._goto_file_selection:enhance { + action_set.select:enhance { post = function() - local selection = actions.get_selected_entry() + local selection = action_set.get_selected_entry() if selection.scode then local scode = string.gsub(selection.scode, '[$]$', '') diff --git a/lua/telescope/builtin/git.lua b/lua/telescope/builtin/git.lua index a9dbfab..ba08aba 100644 --- a/lua/telescope/builtin/git.lua +++ b/lua/telescope/builtin/git.lua @@ -47,7 +47,7 @@ git.commits = function(opts) previewer = previewers.git_commit_diff.new(opts), sorter = conf.file_sorter(opts), attach_mappings = function() - actions.goto_file_selection_edit:replace(actions.git_checkout) + actions.select_default:replace(actions.git_checkout) return true end }):find() @@ -67,7 +67,7 @@ git.bcommits = function(opts) previewer = previewers.git_commit_diff.new(opts), sorter = conf.file_sorter(opts), attach_mappings = function() - actions.goto_file_selection_edit:replace(actions.git_checkout) + actions.select_default:replace(actions.git_checkout) return true end }):find() @@ -101,7 +101,7 @@ git.branches = function(opts) previewer = previewers.git_branch_log.new(opts), sorter = conf.file_sorter(opts), attach_mappings = function() - actions.goto_file_selection_edit:replace(actions.git_checkout) + actions.select_default:replace(actions.git_checkout) return true end }):find() diff --git a/lua/telescope/builtin/internal.lua b/lua/telescope/builtin/internal.lua index f932f48..f0eabc9 100644 --- a/lua/telescope/builtin/internal.lua +++ b/lua/telescope/builtin/internal.lua @@ -1,4 +1,6 @@ local actions = require('telescope.actions') +local action_set = require('telescope.actions.set') +local action_state = require('telescope.actions.state') local finders = require('telescope.finders') local make_entry = require('telescope.make_entry') local path = require('telescope.path') @@ -47,7 +49,7 @@ internal.builtin = function(opts) previewer = previewers.builtin.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(_) - actions.goto_file_selection_edit:replace(actions.run_builtin) + actions.select_default:replace(actions.run_builtin) return true end }):find() @@ -82,8 +84,8 @@ internal.planets = function(opts) previewer = previewers.cat.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) print("Enjoy astronomy! You viewed:", selection.display) @@ -137,7 +139,7 @@ internal.symbols = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function(_) - actions.goto_file_selection_edit:replace(actions.insert_symbol) + actions.select_default:replace(actions.insert_symbol) return true end }):find() @@ -168,8 +170,8 @@ internal.commands = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) local val = selection.value local cmd = string.format([[:%s ]], val.name) @@ -285,8 +287,8 @@ internal.vim_options = function(opts) -- previewer = previewers.help.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function() - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() local esc = "" if vim.fn.mode() == "i" then @@ -411,14 +413,14 @@ internal.help_tags = function(opts) previewer = previewers.help.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions._goto_file_selection:replace(function(_, cmd) - local selection = actions.get_selected_entry() + action_set.select:replace(function(_, cmd) + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) - if cmd == 'edit' or cmd == 'new' then + if cmd == 'default' or cmd == 'horizontal' then vim.cmd('help ' .. selection.value) - elseif cmd == 'vnew' then + elseif cmd == 'vertical' then vim.cmd('vert bo help ' .. selection.value) - elseif cmd == 'tabedit' then + elseif cmd == 'tab' then vim.cmd('tab help ' .. selection.value) end end) @@ -443,16 +445,16 @@ internal.man_pages = function(opts) previewer = previewers.man.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions._goto_file_selection:replace(function(_, cmd) - local selection = actions.get_selected_entry() + action_set.select:replace(function(_, cmd) + local selection = action_state.get_selected_entry() local args = selection.section .. ' ' .. selection.value actions.close(prompt_bufnr) - if cmd == 'edit' or cmd == 'new' then + if cmd == 'default' or cmd == 'horizontal' then vim.cmd('Man ' .. args) - elseif cmd == 'vnew' then + elseif cmd == 'vertical' then vim.cmd('vert bo Man ' .. args) - elseif cmd == 'tabedit' then + elseif cmd == 'tab' then vim.cmd('tab Man ' .. args) end end) @@ -487,8 +489,8 @@ internal.reloader = function(opts) sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) require('plenary.reload').reload_module(selection.value) @@ -566,8 +568,8 @@ internal.colorscheme = function(opts) -- TODO: better preview? sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) vim.cmd("colorscheme " .. selection.value) @@ -618,7 +620,7 @@ internal.registers = function(opts) -- use levenshtein as n-gram doesn't support <2 char matches sorter = sorters.get_levenshtein_sorter(), attach_mappings = function(_, map) - actions.goto_file_selection_edit:replace(actions.paste_register) + actions.select_default:replace(actions.paste_register) map('i', '<C-e>', actions.edit_register) return true @@ -657,8 +659,8 @@ internal.keymaps = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() vim.api.nvim_feedkeys( vim.api.nvim_replace_termcodes(selection.value.lhs, true, false, true), "t", true) @@ -679,8 +681,8 @@ internal.filetypes = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) vim.cmd('setfiletype ' .. selection[1]) end) @@ -700,8 +702,8 @@ internal.highlights = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) vim.cmd('hi ' .. selection.value) end) @@ -791,10 +793,10 @@ internal.autocommands = function(opts) previewer = previewers.autocommands.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions._goto_file_selection:replace(function(_, vim_cmd) - local selection = actions.get_selected_entry() + action_set.select:replace(function(_, type) + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) - vim.cmd(vim_cmd .. ' ' .. selection.value) + vim.cmd(action_state.select_key_to_edit_key(type) .. ' ' .. selection.value) end) return true @@ -815,8 +817,8 @@ internal.spell_suggest = function(opts) }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) vim.cmd('normal! ciw' .. selection[1]) vim.cmd('stopinsert') diff --git a/lua/telescope/builtin/lsp.lua b/lua/telescope/builtin/lsp.lua index 4a373b1..93fcfd2 100644 --- a/lua/telescope/builtin/lsp.lua +++ b/lua/telescope/builtin/lsp.lua @@ -1,4 +1,5 @@ local actions = require('telescope.actions') +local action_state = require('telescope.actions.state') local finders = require('telescope.finders') local make_entry = require('telescope.make_entry') local pickers = require('telescope.pickers') @@ -116,8 +117,8 @@ lsp.code_actions = function(opts) end }, attach_mappings = function(prompt_bufnr) - actions.goto_file_selection_edit:replace(function() - local selection = actions.get_selected_entry() + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) local val = selection.value diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index bbe4719..2f23fad 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -87,7 +87,7 @@ function config.set_defaults(defaults) -- Otherwise, just set the mapping to the function that you want it to be. -- -- ..., - -- ["<C-i>"] = actions.goto_file_selection_split + -- ["<C-i>"] = actions.select_default -- ..., -- set("mappings", {}) diff --git a/lua/telescope/mappings.lua b/lua/telescope/mappings.lua index 499be3e..efc833e 100644 --- a/lua/telescope/mappings.lua +++ b/lua/telescope/mappings.lua @@ -16,10 +16,10 @@ mappings.default_mappings = config.values.default_mappings or { ["<Down>"] = actions.move_selection_next, ["<Up>"] = actions.move_selection_previous, - ["<CR>"] = actions.goto_file_selection_edit + actions.center, - ["<C-x>"] = actions.goto_file_selection_split, - ["<C-v>"] = actions.goto_file_selection_vsplit, - ["<C-t>"] = actions.goto_file_selection_tabedit, + ["<CR>"] = actions.select_default + actions.center, + ["<C-x>"] = actions.select_horizontal, + ["<C-v>"] = actions.select_vertical, + ["<C-t>"] = actions.select_tab, ["<C-u>"] = actions.preview_scrolling_up, ["<C-d>"] = actions.preview_scrolling_down, @@ -30,10 +30,10 @@ mappings.default_mappings = config.values.default_mappings or { n = { ["<esc>"] = actions.close, - ["<CR>"] = actions.goto_file_selection_edit + actions.center, - ["<C-x>"] = actions.goto_file_selection_split, - ["<C-v>"] = actions.goto_file_selection_vsplit, - ["<C-t>"] = actions.goto_file_selection_tabedit, + ["<CR>"] = actions.select_default + actions.center, + ["<C-x>"] = actions.select_horizontal, + ["<C-v>"] = actions.select_vertical, + ["<C-t>"] = actions.select_tab, -- TODO: This would be weird if we switch the ordering. ["j"] = actions.move_selection_next, @@ -153,8 +153,19 @@ mappings.apply_keymap = function(prompt_bufnr, attach_mappings, buffer_keymap) telescope_map(prompt_bufnr, mode, key_bind, key_func, opts) end - if attach_mappings and not attach_mappings(prompt_bufnr, map) then - return + if attach_mappings then + local attach_results = attach_mappings(prompt_bufnr, map) + + if attach_results == nil then + error( + "Attach mappings must always return a value. `true` means use default mappings, " + .. "`false` means only use attached mappings" + ) + end + + if not attach_results then + return + end end for mode, mode_map in pairs(buffer_keymap or {}) do diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 007df7a..5462559 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -4,6 +4,7 @@ local popup = require('popup') require('telescope') local actions = require('telescope.actions') +local action_set = require('telescope.actions.set') local config = require('telescope.config') local debounce = require('telescope.debounce') local resolve = require('telescope.config.resolve') @@ -21,25 +22,6 @@ local EntryManager = require('telescope.entry_manager') local get_default = utils.get_default --- TODO: Make this work with deep extend I think. -local extend = function(opts, defaults) - local result = {} - - for k, v in pairs(opts or {}) do - assert(type(k) == 'string', "Should be string, opts") - result[k] = v - end - - for k, v in pairs(defaults or {}) do - if result[k] == nil then - assert(type(k) == 'string', "Should be string, defaults") - result[k] = v - end - end - - return result -end - local ns_telescope_matching = a.nvim_create_namespace('telescope_matching') local ns_telescope_prompt = a.nvim_create_namespace('telescope_prompt') local ns_telescope_prompt_prefix = a.nvim_create_namespace('telescope_prompt_prefix') @@ -62,7 +44,10 @@ function Picker:new(opts) end -- Reset actions for any replaced / enhanced actions. + -- TODO: Think about how we could remember to NOT have to do this... + -- I almost forgot once already, cause I'm not smart enough to always do it. actions._clear() + action_set._clear() local layout_strategy = get_default(opts.layout_strategy, config.values.layout_strategy) @@ -1016,7 +1001,31 @@ end pickers.new = function(opts, defaults) - return Picker:new(extend(opts, defaults)) + local result = {} + + for k, v in pairs(opts or {}) do + assert(type(k) == 'string', "Should be string, opts") + result[k] = v + end + + for k, v in pairs(defaults or {}) do + if result[k] == nil then + assert(type(k) == 'string', "Should be string, defaults") + result[k] = v + else + -- For attach mappings, we want people to be able to pass in another function + -- and apply their mappings after we've applied our defaults. + if k == 'attach_mappings' then + local opt_value = result[k] + result[k] = function(...) + v(...) + return opt_value(...) + end + end + end + end + + return Picker:new(result) end function pickers.on_close_prompt(prompt_bufnr) diff --git a/lua/tests/automated/action_spec.lua b/lua/tests/automated/action_spec.lua index 6beacf9..771ee50 100644 --- a/lua/tests/automated/action_spec.lua +++ b/lua/tests/automated/action_spec.lua @@ -1,3 +1,6 @@ +local actions = require('telescope.actions') +local action_set = require('telescope.actions.set') + local transform_mod = require('telescope.actions.mt').transform_mod local eq = function(a, b) @@ -51,6 +54,69 @@ describe('actions', function() eq("x", a.x()) end) + it('allows overriding an action only in specific cases with if', function() + local a = transform_mod { + x = function(e) return e * 10 end, + y = function() return "y" end, + } + + -- actions.file_goto_selection_edit:replace(...) + a.x:replace_if( + function(e) return e > 0 end, + function(e) return (e / 10) end + ) + eq(-100, a.x(-10)) + eq(10, a.x(100)) + eq(1, a.x(10)) + + a._clear() + eq(100, a.x(10)) + end) + + it('allows overriding an action only in specific cases with mod', function() + local a = transform_mod { + x = function(e) return e * 10 end, + y = function() return "y" end, + } + + -- actions.file_goto_selection_edit:replace(...) + a.x:replace_map { + [function(e) return e > 0 end] = function(e) return (e / 10) end, + [function(e) return e == 0 end] = function(e) return (e + 10) end, + } + + eq(-100, a.x(-10)) + eq(10, a.x(100)) + eq(1, a.x(10)) + eq(10, a.x(0)) + + a._clear() + eq(100, a.x(10)) + end) + + it('continuous replacement', function() + local a = transform_mod { + x = function() return "cleared" end, + y = function() return "y" end, + } + + -- Replace original, which becomes new fallback + a.x:replace(function() return "negative" end) + + -- actions.file_goto_selection_edit:replace(...) + a.x:replace_map { + [function(e) return e > 0 end] = function(e) return "positive" end, + [function(e) return e == 0 end] = function(e) return "zero" end, + } + + eq("positive", a.x(10)) + eq("zero" , a.x(0)) + eq("negative", a.x(-10)) + + a._clear() + eq("cleared", a.x(10)) + end) + it('enhance.pre', function() local a = transform_mod { x = function() return "x" end, @@ -159,4 +225,27 @@ describe('actions', function() a.x:replace(function(bufnr) return string.format("modified: %s", bufnr) end) eq("modified: 5", a.x(5)) end) + + describe('action_set', function() + it('can replace `action_set.edit`', function() + action_set.edit:replace(function(_, arg) return "replaced:" .. arg end) + eq("replaced:edit", actions.file_edit()) + eq("replaced:vnew", actions.file_vsplit()) + end) + + it('handles backwards compat with select and edit files', function() + -- Reproduce steps: + -- In config, we have { ["<CR>"] = actions.select, ... } + -- In caller, we have actions._goto:replace(...) + -- Person calls `select`, does not see update + action_set.edit:replace(function(_, arg) return "default_to_edit:" .. arg end) + eq("default_to_edit:edit", actions.select_default()) + + action_set.select:replace(function(_, arg) return "override_with_select:" .. arg end) + eq("override_with_select:default", actions.select_default()) + + -- Sometimes you might want to change the default selection... + -- but you don't want to prohibit the ability to edit the code... + end) + end) end) diff --git a/lua/tests/automated/telescope_spec.lua b/lua/tests/automated/telescope_spec.lua index 3e9f0cb..0f7bc85 100644 --- a/lua/tests/automated/telescope_spec.lua +++ b/lua/tests/automated/telescope_spec.lua @@ -1,202 +1,205 @@ -local assert = require('luassert') +local picker = require('telescope.pickers') -local log = require('telescope.log') -log.level = 'info' --- log.use_console = false +local eq = assert.are.same ---[[ -lua RELOAD('plenary'); require("plenary.test_harness"):test_directory("busted", "./tests/automated") ---]] - -describe('Picker', function() - describe('window_dimensions', function() - it('', function() - assert(true) +describe('telescope', function() + describe('Picker', function() + describe('window_dimensions', function() + it('', function() + assert(true) + end) end) - end) -end) - -describe('Sorters', function() - describe('generic_fuzzy_sorter', function() - it('sort matches well', function() - local sorter = require('telescope.sorters').get_generic_fuzzy_sorter() - - local exact_match = sorter:score('hello', {ordinal = 'hello'}) - local no_match = sorter:score('abcdef', {ordinal = 'ghijkl'}) - local ok_match = sorter:score('abcdef', {ordinal = 'ab'}) - assert(exact_match < no_match, "exact match better than no match") - assert(exact_match < ok_match, "exact match better than ok match") - assert(ok_match < no_match, "ok match better than no match") - end) + describe('attach_mappings', function() + it('should allow for passing in a function', function() + local p = picker.new({}, { attach_mappings = function() return 1 end }) + eq(1, p.attach_mappings()) + end) - it('sorts multiple finds better', function() - local sorter = require('telescope.sorters').get_generic_fuzzy_sorter() + it('should override an attach mappings passed in by opts', function() + local called_order = {} + local p = picker.new({ + attach_mappings = function() + table.insert(called_order, 'opts') + end, + }, { + attach_mappings = function() + table.insert(called_order, 'default') + end + }) - local multi_match = sorter:score('generics', 'exercises/generics/generics2.rs') - local one_match = sorter:score('abcdef', 'exercises/generics/README.md') + p.attach_mappings() - -- assert(multi_match < one_match) + eq({'default', 'opts'}, called_order) + end) end) end) - describe('fuzzy_file', function() - it('sort matches well', function() - local sorter = require('telescope.sorters').get_fuzzy_file() - - local exact_match = sorter:score('abcdef', {ordinal = 'abcdef'}) - local no_match = sorter:score('abcdef', {ordinal = 'ghijkl'}) - local ok_match = sorter:score('abcdef', {ordinal = 'ab'}) - - assert( - exact_match < no_match, - string.format("Exact match better than no match: %s %s", exact_match, no_match) - ) - assert( - exact_match < ok_match, - string.format("Exact match better than OK match: %s %s", exact_match, ok_match) - ) - assert(ok_match < no_match, "OK match better than no match") - end) - - it('sorts matches after last os sep better', function() - local sorter = require('telescope.sorters').get_fuzzy_file() + describe('Sorters', function() + describe('generic_fuzzy_sorter', function() + it('sort matches well', function() + local sorter = require('telescope.sorters').get_generic_fuzzy_sorter() - local better_match = sorter:score('aaa', {ordinal = 'bbb/aaa'}) - local worse_match = sorter:score('aaa', {ordinal = 'aaa/bbb'}) + local exact_match = sorter:score('hello', {ordinal = 'hello'}) + local no_match = sorter:score('abcdef', {ordinal = 'ghijkl'}) + local ok_match = sorter:score('abcdef', {ordinal = 'ab'}) - assert(better_match < worse_match, "Final match should be stronger") - end) + assert(exact_match < no_match, "exact match better than no match") + assert(exact_match < ok_match, "exact match better than ok match") + assert(ok_match < no_match, "ok match better than no match") + end) - pending('sorts multiple finds better', function() - local sorter = require('telescope.sorters').get_fuzzy_file() + it('sorts multiple finds better', function() + local sorter = require('telescope.sorters').get_generic_fuzzy_sorter() - local multi_match = sorter:score('generics', {ordinal = 'exercises/generics/generics2.rs'}) - local one_match = sorter:score('abcdef', {ordinal = 'exercises/generics/README.md'}) + local multi_match = sorter:score('generics', 'exercises/generics/generics2.rs') + local one_match = sorter:score('abcdef', 'exercises/generics/README.md') - assert(multi_match < one_match) + -- assert(multi_match < one_match) + end) end) - end) - describe('fzy', function() - local sorter = require'telescope.sorters'.get_fzy_sorter() - local function score(prompt, line) - return sorter:score(prompt, {ordinal = line}) - end + describe('fuzzy_file', function() + it('sort matches well', function() + local sorter = require('telescope.sorters').get_fuzzy_file() - describe("matches", function() - it("exact matches", function() - assert.True(score("a", "a") >= 0) - assert.True(score("a.bb", "a.bb") >= 0) - end) - it("ignore case", function() - assert.True(score("AbB", "abb") >= 0) - assert.True(score("abb", "ABB") >= 0) - end) - it("partial matches", function() - assert.True(score("a", "ab") >= 0) - assert.True(score("a", "ba") >= 0) - assert.True(score("aba", "baabbaab") >= 0) - end) - it("with delimiters between", function() - assert.True(score("abc", "a|b|c") >= 0) - end) - it("with empty query", function() - assert.True(score("", "") >= 0) - assert.True(score("", "a") >= 0) - end) - it("rejects non-matches", function() - assert.True(score("a", "") < 0) - assert.True(score("a", "b") < 0) - assert.True(score("aa", "a") < 0) - assert.True(score("ba", "a") < 0) - assert.True(score("ab", "a") < 0) - end) - end) + local exact_match = sorter:score('abcdef', {ordinal = 'abcdef'}) + local no_match = sorter:score('abcdef', {ordinal = 'ghijkl'}) + local ok_match = sorter:score('abcdef', {ordinal = 'ab'}) - describe("scoring", function() - it("prefers beginnings of words", function() - assert.True(score("amor", "app/models/order") < score("amor", "app/models/zrder")) + assert( + exact_match < no_match, + string.format("Exact match better than no match: %s %s", exact_match, no_match) + ) + assert( + exact_match < ok_match, + string.format("Exact match better than OK match: %s %s", exact_match, ok_match) + ) + assert(ok_match < no_match, "OK match better than no match") end) - it("prefers consecutive letters", function() - assert.True(score("amo", "app/models/foo") < score("amo", "app/m/foo")) - assert.True(score("erf", "perfect") < score("erf", "terrific")) - end) - it("prefers contiguous over letter following period", function() - assert.True(score("gemfil", "Gemfile") < score("gemfil", "Gemfile.lock")) - end) - it("prefers shorter matches", function() - assert.True(score("abce", "abcdef") < score("abce", "abc de")); - assert.True(score("abc", " a b c ") < score("abc", " a b c ")); - assert.True(score("abc", " a b c ") < score("abc", " a b c ")); - end) - it("prefers shorter candidates", function() - assert.True(score("test", "tests") < score("test", "testing")) - end) - it("prefers matches at the beginning", function() - assert.True(score("ab", "abbb") < score("ab", "babb")) - assert.True(score("test", "testing") < score("test", "/testing")) - end) - it("prefers matches at some locations", function() - assert.True(score("a", "/a") < score("a", "ba")) - assert.True(score("a", "bA") < score("a", "ba")) - assert.True(score("a", ".a") < score("a", "ba")) - end) - end) - local function positions(prompt, line) - return sorter:highlighter(prompt, line) - end + it('sorts matches after last os sep better', function() + local sorter = require('telescope.sorters').get_fuzzy_file() - describe("positioning", function() - it("favors consecutive positions", function() - assert.same({1, 5, 6}, positions("amo", "app/models/foo")) - end) - it("favors word beginnings", function() - assert.same({1, 5, 12, 13}, positions("amor", "app/models/order")) - end) - it("works when there are no bonuses", function() - assert.same({2, 4}, positions("as", "tags")) - assert.same({3, 8}, positions("as", "examples.txt")) - end) - it("favors smaller groupings of positions", function() - assert.same({3, 5, 7}, positions("abc", "a/a/b/c/c")) - assert.same({3, 5}, positions("ab", "caacbbc")) + local better_match = sorter:score('aaa', {ordinal = 'bbb/aaa'}) + local worse_match = sorter:score('aaa', {ordinal = 'aaa/bbb'}) + + assert(better_match < worse_match, "Final match should be stronger") end) - it("handles exact matches", function() - assert.same({1, 2, 3}, positions("foo", "foo")) + + pending('sorts multiple finds better', function() + local sorter = require('telescope.sorters').get_fuzzy_file() + + local multi_match = sorter:score('generics', {ordinal = 'exercises/generics/generics2.rs'}) + local one_match = sorter:score('abcdef', {ordinal = 'exercises/generics/README.md'}) + + assert(multi_match < one_match) end) - it("ignores empty requests", function() - assert.same({}, positions("", "")) - assert.same({}, positions("", "foo")) - assert.same({}, positions("foo", "")) + end) + + describe('fzy', function() + local sorter = require'telescope.sorters'.get_fzy_sorter() + local function score(prompt, line) + return sorter:score(prompt, {ordinal = line}) + end + + describe("matches", function() + it("exact matches", function() + assert.True(score("a", "a") >= 0) + assert.True(score("a.bb", "a.bb") >= 0) + end) + it("ignore case", function() + assert.True(score("AbB", "abb") >= 0) + assert.True(score("abb", "ABB") >= 0) + end) + it("partial matches", function() + assert.True(score("a", "ab") >= 0) + assert.True(score("a", "ba") >= 0) + assert.True(score("aba", "baabbaab") >= 0) + end) + it("with delimiters between", function() + assert.True(score("abc", "a|b|c") >= 0) + end) + it("with empty query", function() + assert.True(score("", "") >= 0) + assert.True(score("", "a") >= 0) + end) + it("rejects non-matches", function() + assert.True(score("a", "") < 0) + assert.True(score("a", "b") < 0) + assert.True(score("aa", "a") < 0) + assert.True(score("ba", "a") < 0) + assert.True(score("ab", "a") < 0) + end) + end) + + describe("scoring", function() + it("prefers beginnings of words", function() + assert.True(score("amor", "app/models/order") < score("amor", "app/models/zrder")) + end) + it("prefers consecutive letters", function() + assert.True(score("amo", "app/models/foo") < score("amo", "app/m/foo")) + assert.True(score("erf", "perfect") < score("erf", "terrific")) + end) + it("prefers contiguous over letter following period", function() + assert.True(score("gemfil", "Gemfile") < score("gemfil", "Gemfile.lock")) + end) + it("prefers shorter matches", function() + assert.True(score("abce", "abcdef") < score("abce", "abc de")); + assert.True(score("abc", " a b c ") < score("abc", " a b c ")); + assert.True(score("abc", " a b c ") < score("abc", " a b c ")); + end) + it("prefers shorter candidates", function() + assert.True(score("test", "tests") < score("test", "testing")) + end) + it("prefers matches at the beginning", function() + assert.True(score("ab", "abbb") < score("ab", "babb")) + assert.True(score("test", "testing") < score("test", "/testing")) + end) + it("prefers matches at some locations", function() + assert.True(score("a", "/a") < score("a", "ba")) + assert.True(score("a", "bA") < score("a", "ba")) + assert.True(score("a", ".a") < score("a", "ba")) + end) + end) + + local function positions(prompt, line) + return sorter:highlighter(prompt, line) + end + + describe("positioning", function() + it("favors consecutive positions", function() + assert.same({1, 5, 6}, positions("amo", "app/models/foo")) + end) + it("favors word beginnings", function() + assert.same({1, 5, 12, 13}, positions("amor", "app/models/order")) + end) + it("works when there are no bonuses", function() + assert.same({2, 4}, positions("as", "tags")) + assert.same({3, 8}, positions("as", "examples.txt")) + end) + it("favors smaller groupings of positions", function() + assert.same({3, 5, 7}, positions("abc", "a/a/b/c/c")) + assert.same({3, 5}, positions("ab", "caacbbc")) + end) + it("handles exact matches", function() + assert.same({1, 2, 3}, positions("foo", "foo")) + end) + it("ignores empty requests", function() + assert.same({}, positions("", "")) + assert.same({}, positions("", "foo")) + assert.same({}, positions("foo", "")) + end) end) end) - end) - describe('layout_strategies', function() - describe('center', function() - it('should handle large terminals', function() - -- TODO: This could call layout_strategies.center w/ some weird edge case. - -- and then assert stuff about the dimensions. + describe('layout_strategies', function() + describe('center', function() + it('should handle large terminals', function() + -- TODO: This could call layout_strategies.center w/ some weird edge case. + -- and then assert stuff about the dimensions. + end) end) end) end) - - -- describe('file_finder', function() - -- COMPLETED = false - -- PASSED = false - - -- require('tests.helpers').auto_find_files { - -- input = 'pickers.lua', - - -- condition = function() - -- print(vim.api.nvim_buf_get_name(0)) - -- return string.find(vim.api.nvim_buf_get_name(0), 'pickers.lua') - -- end, - -- } - - -- print("WAIT:", vim.wait(5000, function() return COMPLETED end)) - -- assert(PASSED) - -- end) end) |
