summaryrefslogtreecommitdiff
path: root/lua/telescope/actions/history.lua
blob: 8ec58e27ab3c0d0da9ee07fd5aa9cd2e2ebd3a35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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