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:
Simon Hauser
2021-07-09 20:45:29 +02:00
committed by GitHub
parent 385020eb23
commit 3699605627
9 changed files with 1098 additions and 625 deletions

View 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