summaryrefslogtreecommitdiff
path: root/lua/telescope/finders.lua
blob: fe1af9617544870ac956e8a4526340e0b11f1057 (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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
local Job = require "plenary.job"

local make_entry = require "telescope.make_entry"
local log = require "telescope.log"

local async_static_finder = require "telescope.finders.async_static_finder"
local async_oneshot_finder = require "telescope.finders.async_oneshot_finder"
local async_job_finder = require "telescope.finders.async_job_finder"

local finders = {}

local _callable_obj = function()
  local obj = {}

  obj.__index = obj
  obj.__call = function(t, ...)
    return t:_find(...)
  end

  obj.close = function() end

  return obj
end

--[[ =============================================================

    JobFinder

Uses an external Job to get results. Processes results as they arrive.

For more information about how Jobs are implemented, checkout 'plenary.job'

-- ============================================================= ]]
local JobFinder = _callable_obj()

--- Create a new finder command
---
---@param opts table Keys:
--     fn_command function The function to call
function JobFinder:new(opts)
  opts = opts or {}

  assert(not opts.results, "`results` should be used with finder.new_table")
  assert(not opts.static, "`static` should be used with finder.new_oneshot_job")

  local obj = setmetatable({
    entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
    fn_command = opts.fn_command,
    cwd = opts.cwd,
    writer = opts.writer,

    -- Maximum number of results to process.
    --  Particularly useful for live updating large queries.
    maximum_results = opts.maximum_results,
  }, self)

  return obj
end

function JobFinder:_find(prompt, process_result, process_complete)
  log.trace "Finding..."

  if self.job and not self.job.is_shutdown then
    log.debug "Shutting down old job"
    self.job:shutdown()
  end

  local on_output = function(_, line, _)
    if not line or line == "" then
      return
    end

    if self.entry_maker then
      line = self.entry_maker(line)
    end

    process_result(line)
  end

  local opts = self:fn_command(prompt)
  if not opts then
    return
  end

  local writer = nil
  if opts.writer and Job.is_job(opts.writer) then
    writer = opts.writer
  elseif opts.writer then
    writer = Job:new(opts.writer)
  end

  self.job = Job:new {
    command = opts.command,
    args = opts.args,
    cwd = opts.cwd or self.cwd,

    maximum_results = self.maximum_results,

    writer = writer,

    enable_recording = false,

    on_stdout = on_output,
    -- on_stderr = on_output,

    on_exit = function()
      process_complete()
    end,
  }

  self.job:start()
end

local DynamicFinder = _callable_obj()

function DynamicFinder:new(opts)
  opts = opts or {}

  assert(not opts.results, "`results` should be used with finder.new_table")
  assert(not opts.static, "`static` should be used with finder.new_oneshot_job")

  local obj = setmetatable({
    curr_buf = opts.curr_buf,
    fn = opts.fn,
    entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
  }, self)

  return obj
end

function DynamicFinder:_find(prompt, process_result, process_complete)
  local results = self.fn(prompt)

  for _, result in ipairs(results) do
    if process_result(self.entry_maker(result)) then
      return
    end
  end

  process_complete()
end

--- Return a new Finder
--
-- Use at your own risk.
-- This opts dictionary is likely to change, but you are welcome to use it right now.
-- I will try not to change it needlessly, but I will change it sometimes and I won't feel bad.
finders._new = function(opts)
  assert(not opts.results, "finder.new is deprecated with `results`. You should use `finder.new_table`")
  return JobFinder:new(opts)
end

finders.new_async_job = function(opts)
  if opts.writer then
    return finders._new(opts)
  end

  return async_job_finder(opts)
end

finders.new_job = function(command_generator, entry_maker, _, cwd)
  return async_job_finder {
    command_generator = command_generator,
    entry_maker = entry_maker,
    cwd = cwd,
  }
end

--- One shot job
---@param command_list string[]: Command list to execute.
---@param opts table: stuff
--         @key entry_maker function Optional: function(line: string) => table
--         @key cwd string
finders.new_oneshot_job = function(command_list, opts)
  opts = opts or {}

  assert(not opts.results, "`results` should be used with finder.new_table")

  command_list = vim.deepcopy(command_list)
  local command = table.remove(command_list, 1)

  return async_oneshot_finder {
    entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),

    cwd = opts.cwd,
    maximum_results = opts.maximum_results,

    fn_command = function()
      return {
        command = command,
        args = command_list,
      }
    end,
  }
end

--- Used to create a finder for a Lua table.
-- If you only pass a table of results, then it will use that as the entries.
--
-- If you pass a table, and then a function, it's used as:
--  results table, the results to run on
--  entry_maker function, the function to convert results to entries.
finders.new_table = function(t)
  return async_static_finder(t)
end

finders.new_dynamic = function(t)
  return DynamicFinder:new(t)
end

return finders