summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua')
-rw-r--r--lua/telescope/actions/history.lua186
-rw-r--r--lua/telescope/actions/init.lua84
-rw-r--r--lua/telescope/actions/mt.lua22
-rw-r--r--lua/telescope/actions/set.lua14
-rw-r--r--lua/telescope/actions/state.lua16
-rw-r--r--lua/telescope/config.lua54
-rw-r--r--lua/telescope/pickers.lua7
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