feat: cycle prompt history (#521)
history is enabled on default but cycle_history_next and cycle_history_prev is not mapped yet
Example:
require('telescope').setup {
defaults = {
mappings = {
i = {
["<C-Down>"] = require('telescope.actions').cycle_history_next,
["<C-Up>"] = require('telescope.actions').cycle_history_prev,
}
}
}
}
For more information :help telescope.defaults.history
big thanks to clason and all other testers :)
This commit is contained in:
1327
doc/telescope.txt
1327
doc/telescope.txt
File diff suppressed because it is too large
Load Diff
186
lua/telescope/actions/history.lua
Normal file
186
lua/telescope/actions/history.lua
Normal file
@@ -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
|
||||
@@ -196,21 +196,53 @@ function actions.center(_)
|
||||
vim.cmd(':normal! zz')
|
||||
end
|
||||
|
||||
function actions.select_default(prompt_bufnr)
|
||||
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
|
||||
end
|
||||
}
|
||||
|
||||
function actions.select_horizontal(prompt_bufnr)
|
||||
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
|
||||
end
|
||||
}
|
||||
|
||||
function actions.select_vertical(prompt_bufnr)
|
||||
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
|
||||
end
|
||||
}
|
||||
|
||||
function actions.select_tab(prompt_bufnr)
|
||||
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
|
||||
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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,20 +11,17 @@ docs.test = function()
|
||||
local input_files = {
|
||||
"./lua/telescope/init.lua",
|
||||
"./lua/telescope/builtin/init.lua",
|
||||
"./lua/telescope/themes.lua",
|
||||
"./lua/telescope/pickers/layout_strategies.lua",
|
||||
"./lua/telescope/config/resolve.lua",
|
||||
"./lua/telescope/actions/init.lua",
|
||||
"./lua/telescope/actions/state.lua",
|
||||
"./lua/telescope/actions/set.lua",
|
||||
"./lua/telescope/actions/utils.lua",
|
||||
"./lua/telescope/previewers/init.lua",
|
||||
"./lua/telescope/config/resolve.lua",
|
||||
"./lua/telescope/themes.lua",
|
||||
"./lua/telescope/actions/history.lua",
|
||||
}
|
||||
|
||||
table.sort(input_files, function(a, b)
|
||||
return #a < #b
|
||||
end)
|
||||
|
||||
local output_file = "./doc/telescope.txt"
|
||||
local output_file_handle = io.open(output_file, "w")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user