diff options
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/telescope/actions/history.lua | 186 | ||||
| -rw-r--r-- | lua/telescope/actions/init.lua | 84 | ||||
| -rw-r--r-- | lua/telescope/actions/mt.lua | 22 | ||||
| -rw-r--r-- | lua/telescope/actions/set.lua | 14 | ||||
| -rw-r--r-- | lua/telescope/actions/state.lua | 16 | ||||
| -rw-r--r-- | lua/telescope/config.lua | 54 | ||||
| -rw-r--r-- | lua/telescope/pickers.lua | 7 |
7 files changed, 361 insertions, 22 deletions
diff --git a/lua/telescope/actions/history.lua b/lua/telescope/actions/history.lua new file mode 100644 index 0000000..8ec58e2 --- /dev/null +++ b/lua/telescope/actions/history.lua @@ -0,0 +1,186 @@ +local conf = require('telescope.config').values +local Path = require('plenary.path') + +local uv = vim.loop + +---@brief [[ +--- A base implementation of a prompt history that provides a simple history +--- and can be replaced with a custom implementation. +--- +--- For example: We provide a extension for a smart history that uses sql.nvim +--- to map histories to metadata, like the calling picker or cwd. +--- +--- So you have a history for: +--- - find_files project_1 +--- - grep_string project_1 +--- - live_grep project_1 +--- - find_files project_2 +--- - grep_string project_2 +--- - live_grep project_2 +--- - etc +--- +--- See github.com/nvim-telescope/telescope-smart-history.nvim +---@brief ]] +---@tag telescope.actions.history + +-- TODO(conni2461): currently not present in plenary path only sync. +-- But sync is just unnecessary here +local write_async = function(path, txt, flag) + uv.fs_open(path, flag, 438, function(open_err, fd) + assert(not open_err, open_err) + uv.fs_write(fd, txt, -1, function(write_err) + assert(not write_err, write_err) + uv.fs_close(fd, function(close_err) + assert(not close_err, close_err) + end) + end) + end) +end + +local append_async = function(path, txt) write_async(path, txt, "a") end + +local histories = {} + +--- Manages prompt history +---@class History @Manages prompt history +---@field enabled boolean: Will indicate if History is enabled or disabled +---@field path string: Will point to the location of the history file +---@field limit string: Will have the limit of the history. Can be nil, if limit is disabled. +---@field content table: History table. Needs to be filled by your own History implementation +---@field index number: Used to keep track of the next or previous index. Default is #content + 1 +histories.History = {} +histories.History.__index = histories.History + +--- Create a new History +---@param opts table: Defines the behavior of History +---@field init function: Will be called after handling configuration (required) +---@field append function: How to append a new prompt item (required) +---@field reset function: What happens on reset. Will be called when telescope closes (required) +---@field pre_get function: Will be called before a next or previous item will be returned (optional) +function histories.History:new(opts) + local obj = {} + if not conf.history or type(conf.history) ~= "table" then + obj.enabled = false + return setmetatable(obj, self) + end + obj.enabled = true + if conf.history.limit then + obj.limit = conf.history.limit + end + obj.path = vim.fn.expand(conf.history.path) + obj.content = {} + obj.index = 1 + + opts.init(obj) + obj._reset = opts.reset + obj._append = opts.append + obj._pre_get = opts.pre_get + + return setmetatable(obj, self) +end + +--- Shorthand to create a new history +function histories.new(...) + return histories.History:new(...) +end + +--- Will reset the history index to the default initial state. Will happen after the picker closed +function histories.History:reset() + if not self.enabled then return end + self._reset(self) +end + +--- Append a new line to the history +---@param line string: current line that will be appended +---@param picker table: the current picker object +---@param no_reset boolean: On default it will reset the state at the end. If you don't want to do this set to true +function histories.History:append(line, picker, no_reset) + if not self.enabled then return end + self._append(self, line, picker, no_reset) +end + +--- Will return the next history item. Can be nil if there are no next items +---@param line string: the current line +---@param picker table: the current picker object +---@return string: the next history item +function histories.History:get_next(line, picker) + if not self.enabled then + print("You are cycling to next the history item but history is disabled.", + "Read ':help telescope.defaults.history'") + return false + end + if self._pre_get then self._pre_get(self, line, picker) end + + local next_idx = self.index + 1 + if next_idx <= #self.content then + self.index = next_idx + return self.content[next_idx] + end + self.index = #self.content + 1 + return nil +end + +--- Will return the previous history item. Can be nil if there are no previous items +---@param line string: the current line +---@param picker table: the current picker object +---@return string: the previous history item +function histories.History:get_prev(line, picker) + if not self.enabled then + print("You are cycling to previous the history item but history is disabled.", + "Read ':help telescope.defaults.history'") + return false + end + if self._pre_get then self._pre_get(self, line, picker) end + + local next_idx = self.index - 1 + if self.index == #self.content + 1 then + if line ~= '' then self:append(line, picker, true) end + end + if next_idx >= 1 then + self.index = next_idx + return self.content[next_idx] + end + return nil +end + +--- A simple implementation of history. +--- +--- It will keep one unified history across all pickers. +histories.get_simple_history = function() + return histories.new({ + init = function(obj) + local p = Path:new(obj.path) + if not p:exists() then p:touch({ parents = true }) end + + obj.content = Path:new(obj.path):readlines() + obj.index = #obj.content + table.remove(obj.content, obj.index) + end, + reset = function(self) + self.index = #self.content + 1 + end, + append = function(self, line, _, no_reset) + if line ~= '' then + if self.content[#self.content] ~= line then + table.insert(self.content, line) + + local len = #self.content + if self.limit and len > self.limit then + local diff = len - self.limit + for i = diff, 1, -1 do + table.remove(self.content, i) + end + write_async(self.path, table.concat(self.content, '\n') .. '\n', 'w') + else + append_async(self.path, line .. '\n') + end + end + end + if not no_reset then + self:reset() + end + end, + }) +end + +return histories diff --git a/lua/telescope/actions/init.lua b/lua/telescope/actions/init.lua index bf45bb3..8ca96c7 100644 --- a/lua/telescope/actions/init.lua +++ b/lua/telescope/actions/init.lua @@ -196,21 +196,53 @@ function actions.center(_) vim.cmd(':normal! zz') end -function actions.select_default(prompt_bufnr) - return action_set.select(prompt_bufnr, "default") -end +actions.select_default = { + pre = function(prompt_bufnr) + action_state.get_current_history():append( + action_state.get_current_line(), + action_state.get_current_picker(prompt_bufnr) + ) + end, + action = function(prompt_bufnr) + return action_set.select(prompt_bufnr, "default") + end +} -function actions.select_horizontal(prompt_bufnr) - return action_set.select(prompt_bufnr, "horizontal") -end +actions.select_horizontal = { + pre = function(prompt_bufnr) + action_state.get_current_history():append( + action_state.get_current_line(), + action_state.get_current_picker(prompt_bufnr) + ) + end, + action = function(prompt_bufnr) + return action_set.select(prompt_bufnr, "horizontal") + end +} -function actions.select_vertical(prompt_bufnr) - return action_set.select(prompt_bufnr, "vertical") -end +actions.select_vertical = { + pre = function(prompt_bufnr) + action_state.get_current_history():append( + action_state.get_current_line(), + action_state.get_current_picker(prompt_bufnr) + ) + end, + action = function(prompt_bufnr) + return action_set.select(prompt_bufnr, "vertical") + end +} -function actions.select_tab(prompt_bufnr) - return action_set.select(prompt_bufnr, "tab") -end +actions.select_tab = { + pre = function(prompt_bufnr) + action_state.get_current_history():append( + action_state.get_current_line(), + action_state.get_current_picker(prompt_bufnr) + ) + end, + action = function(prompt_bufnr) + return action_set.select(prompt_bufnr, "tab") + end +} -- TODO: consider adding float! -- https://github.com/nvim-telescope/telescope.nvim/issues/365 @@ -238,6 +270,7 @@ function actions.close_pum(_) end actions._close = function(prompt_bufnr, keepinsert) + action_state.get_current_history():reset() 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 @@ -695,6 +728,33 @@ actions.complete_tag = function(prompt_bufnr) end +actions.cycle_history_next = function(prompt_bufnr) + local history = action_state.get_current_history() + local current_picker = actions.get_current_picker(prompt_bufnr) + local line = action_state.get_current_line() + + local entry = history:get_next(line, current_picker) + if entry == false then return end + + current_picker:reset_prompt() + if entry ~= nil then + current_picker:set_prompt(entry) + end +end + +actions.cycle_history_prev = function(prompt_bufnr) + local history = action_state.get_current_history() + local current_picker = actions.get_current_picker(prompt_bufnr) + local line = action_state.get_current_line() + + local entry = history:get_prev(line, current_picker) + if entry == false then return end + if entry ~= nil then + current_picker:reset_prompt() + current_picker:set_prompt(entry) + end +end + --- Open the quickfix list actions.open_qflist = function(_) vim.cmd [[copen]] diff --git a/lua/telescope/actions/mt.lua b/lua/telescope/actions/mt.lua index 5efeddf..7f233cf 100644 --- a/lua/telescope/actions/mt.lua +++ b/lua/telescope/actions/mt.lua @@ -19,6 +19,9 @@ action_mt.create = function(mod) __call = function(t, ...) local values = {} for _, action_name in ipairs(t) do + if t._static_pre[action_name] then + t._static_pre[action_name](...) + end if t._pre[action_name] then t._pre[action_name](...) end @@ -34,6 +37,9 @@ action_mt.create = function(mod) table.insert(values, res) end + if t._static_post[action_name] then + t._static_post[action_name](...) + end if t._post[action_name] then t._post[action_name](...) end @@ -55,8 +61,10 @@ action_mt.create = function(mod) return setmetatable(new_actions, getmetatable(lhs)) end, + _static_pre = {}, _pre = {}, _replacements = {}, + _static_post = {}, _post = {}, } @@ -119,8 +127,14 @@ action_mt.create = function(mod) return mt end -action_mt.transform = function(k, mt) - return setmetatable({k}, mt) +action_mt.transform = function(k, mt, mod, v) + local res = setmetatable({k}, mt) + if type(v) == "table" then + res._static_pre[k] = v.pre + res._static_post[k] = v.post + mod[k] = v.action + end + return res end action_mt.transform_mod = function(mod) @@ -130,8 +144,8 @@ action_mt.transform_mod = function(mod) -- 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) + for k, v in pairs(mod) do + redirect[k] = action_mt.transform(k, mt, mod, v) end redirect._clear = mt.clear diff --git a/lua/telescope/actions/set.lua b/lua/telescope/actions/set.lua index 5b39c54..e790e34 100644 --- a/lua/telescope/actions/set.lua +++ b/lua/telescope/actions/set.lua @@ -48,6 +48,20 @@ action_set.select = function(prompt_bufnr, type) return action_set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type)) end +-- goal: currently we have a workaround in actions/init.lua where we do this for all files +-- action_set.select = { +-- -- Will not be called if `select_default` is replaced rather than `action_set.select` because we never get here +-- pre = function(prompt_bufnr) +-- action_state.get_current_history():append( +-- action_state.get_current_line(), +-- action_state.get_current_picker(prompt_bufnr) +-- ) +-- end, +-- action = function(prompt_bufnr, type) +-- return action_set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type)) +-- end +-- } + local edit_buffer do local map = { diff --git a/lua/telescope/actions/state.lua b/lua/telescope/actions/state.lua index 7ef9f6c..8470f2d 100644 --- a/lua/telescope/actions/state.lua +++ b/lua/telescope/actions/state.lua @@ -7,6 +7,7 @@ ---@brief ]] local global_state = require('telescope.state') +local conf = require('telescope.config').values local action_state = {} @@ -36,4 +37,19 @@ function action_state.select_key_to_edit_key(type) return select_to_edit_map[type] end +function action_state.get_current_history() + local history = global_state.get_global_key("history") + if not history then + if not conf.history or type(conf.history) ~= "table" then + history = require('telescope.actions.history').get_simple_history() + global_state.set_global_key("history", history) + else + history = conf.history.handler() + global_state.set_global_key("history", history) + end + end + + return history +end + return action_state diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index dd3d453..00d4101 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -2,6 +2,7 @@ local strings = require "plenary.strings" local deprecated = require "telescope.deprecated" local sorters = require "telescope.sorters" local if_nil = vim.F.if_nil +local os_sep = require("plenary.path").path.sep -- Keep the values around between reloads _TelescopeConfigurationValues = _TelescopeConfigurationValues or {} @@ -206,15 +207,48 @@ local telescope_defaults = { end, }, - dynamic_preview_title = { - false, - [[ + dynamic_preview_title = { false, [[ Will change the title of the preview window dynamically, where it is supported. Means the preview window will for example show the full filename. - Default: false - ]], + Default: false]], + }, + + history = { { + path = vim.fn.stdpath("data") .. os_sep .. "telescope_history", + limit = 100, + handler = function(...) return require('telescope.actions.history').get_simple_history(...) end, + }, [[ + This field handles the configuration for prompt history. + By default it is a table, with default values (more below). + To disable history, set it to either false or nil. + + Currently mappings still need to be added, Example: + mappings = { + i = { + ["<C-Down>"] = require('telescope.actions').cycle_history_next, + ["<C-Up>"] = require('telescope.actions').cycle_history_prev, + }, + }, + + Fields: + - path: The path to the telescope history as string. + default: stdpath("data")/telescope_history + - limit: The amount of entries that will be written in the + history. + Warning: If limit is set to nil it will grown unbound. + default: 100 + - handler: A lua function that implements the history. + This is meant as a developer setting for extensions to + override the history handling, e.g., + https://github.com/nvim-telescope/telescope-smart-history.nvim, + which allows context sensitive (cwd + picker) history. + + Default: + require('telescope.actions.history').get_simple_history + ]], + }, -- Builtin configuration @@ -346,6 +380,16 @@ function config.set_defaults(user_defaults, tele_defaults) vim.tbl_deep_extend("keep", if_nil(config.values[name], {}), if_nil(default_val, {})) ) end + if name == "history" then + if not user_defaults[name] or not config.values[name] then + return false + end + + return smarter_depth_2_extend( + if_nil(user_defaults[name], {}), + vim.tbl_deep_extend("keep", if_nil(config.values[name], {}), if_nil(default_val, {})) + ) + end return first_non_null(user_defaults[name], config.values[name], default_val) end diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index a60d2df..449d869 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -473,7 +473,7 @@ function Picker:find() pcall(a.nvim_buf_set_option, prompt_bufnr, 'filetype', 'TelescopePrompt') if self.default_text then - vim.api.nvim_buf_set_lines(prompt_bufnr, 0, 1, false, {self.default_text}) + self:set_prompt(self.default_text) end if self.initial_mode == "insert" then @@ -544,6 +544,11 @@ function Picker:delete_selection(delete_cb) end) end +function Picker:set_prompt(str) + -- TODO(conni2461): As soon as prompt_buffers are fix use this: + -- vim.api.nvim_buf_set_lines(self.prompt_bufnr, 0, 1, false, { str }) + vim.api.nvim_feedkeys(str, 'n', false) +end function Picker.close_windows(status) local prompt_win = status.prompt_win |
