summaryrefslogtreecommitdiff
path: root/lua/telescope/actions/history.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/telescope/actions/history.lua')
-rw-r--r--lua/telescope/actions/history.lua186
1 files changed, 186 insertions, 0 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