From 0b2c801978e9cfc9b6351fc9f037ff2ba93805bd Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Tue, 6 Apr 2021 19:59:42 -0400 Subject: [PATCH 01/11] feat: buf highlights for current buffer fuzzy find (#732) * feat: Add buffer highlights from treesitter * fix: Handle not having tree sitter in some buffers * fixup * fixup * fixup: move back to old node --- lua/telescope/builtin/files.lua | 48 +++++++++++++++++++++++++ lua/telescope/make_entry.lua | 20 +++++++++-- lua/telescope/pickers/entry_display.lua | 11 +++++- scratch/buffer_highlights.lua | 24 +++++++++++++ scratch/short_buf.lua | 4 +++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 scratch/buffer_highlights.lua create mode 100644 scratch/short_buf.lua diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index f091ad032f..819d9ae2e9 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -332,6 +332,7 @@ files.current_buffer_fuzzy_find = function(opts) -- All actions are on the current buffer local bufnr = vim.api.nvim_get_current_buf() local filename = vim.fn.expand(vim.api.nvim_buf_get_name(bufnr)) + local filetype = vim.api.nvim_buf_get_option(bufnr, "filetype") local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) local lines_with_numbers = {} @@ -345,6 +346,53 @@ files.current_buffer_fuzzy_find = function(opts) }) end + local ok, parser = pcall(vim.treesitter.get_parser, bufnr, filetype) + if ok then + local query = vim.treesitter.get_query(filetype, "highlights") + + local root = parser:parse()[1]:root() + + local highlighter = vim.treesitter.highlighter.new(parser) + local highlighter_query = highlighter:get_query(filetype) + + local line_highlights = setmetatable({}, { + __index = function(t, k) + local obj = {} + rawset(t, k, obj) + return obj + end, + }) + for id, node in query:iter_captures(root, bufnr, 0, -1) do + local hl = highlighter_query.hl_cache[id] + if hl then + local row1, col1, row2, col2 = node:range() + + if row1 == row2 then + local row = row1 + 1 + + for index = col1, col2 do + line_highlights[row][index] = hl + end + else + local row = row1 + 1 + for index = col1, #lines[row] do + line_highlights[row][index] = hl + end + + while row < row2 + 1 do + row = row + 1 + + for index = 0, #lines[row] do + line_highlights[row][index] = hl + end + end + end + end + end + + opts.line_highlights = line_highlights + end + pickers.new(opts, { prompt_title = 'Current Buffer Fuzzy', finder = finders.new_table { diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua index e4b3d4eec7..607b8ea5e8 100644 --- a/lua/telescope/make_entry.lua +++ b/lua/telescope/make_entry.lua @@ -685,14 +685,30 @@ function make_entry.gen_from_buffer_lines(opts) separator = ' │ ', items = { { width = 5 }, - { remaining = true }, + { remaining = true, }, }, } local make_display = function(entry) + return displayer { { entry.lnum, opts.lnum_highlight_group or 'TelescopeResultsSpecialComment' }, - entry.line + { + entry.line, function() + if not opts.line_highlights then return {} end + + local line_hl = opts.line_highlights[entry.lnum] or {} + -- TODO: We could probably squash these together if the are the same... + -- But I don't think that it's worth it at the moment. + local result = {} + + for col, hl in pairs(line_hl) do + table.insert(result, { {col, col+1}, hl }) + end + + return result + end + }, } end diff --git a/lua/telescope/pickers/entry_display.lua b/lua/telescope/pickers/entry_display.lua index 991c9bc9f4..f64f69339e 100644 --- a/lua/telescope/pickers/entry_display.lua +++ b/lua/telescope/pickers/entry_display.lua @@ -59,8 +59,16 @@ entry_display.create = function(configuration) hl_start = hl_start + #results[j] + (#configuration.separator or 1) end local hl_end = hl_start + #str:gsub('%s*$', '') - table.insert(highlights, { { hl_start, hl_end }, hl }) + + if type(hl) == "function" then + for _, hl_res in ipairs(hl()) do + table.insert(highlights, { { hl_res[1][1] + hl_start, hl_res[1][2] + hl_start }, hl_res[2] }) + end + else + table.insert(highlights, { { hl_start, hl_end }, hl }) + end end + table.insert(results, str) end end @@ -75,6 +83,7 @@ entry_display.create = function(configuration) table.insert(highlights, { { hl_start, hl_end }, configuration.separator_hl }) end end + local final_str = table.concat(results, configuration.separator or "│") if configuration.hl_chars then for i = 1, #final_str do diff --git a/scratch/buffer_highlights.lua b/scratch/buffer_highlights.lua new file mode 100644 index 0000000000..ee9edaeb9b --- /dev/null +++ b/scratch/buffer_highlights.lua @@ -0,0 +1,24 @@ +local a = vim.api + +local ns = a.nvim_create_namespace("treesitter/highlighter") +print(ns) +local bufnr = 0 + +-- P(a.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })) + +local parser = vim.treesitter.get_parser(bufnr, "lua") +local query = vim.treesitter.get_query("lua", "highlights") +P(query) + +local root = parser:parse()[1]:root() +print("root", root) + +local highlighter = vim.treesitter.highlighter.new(parser) +local highlighter_query = highlighter:get_query("lua") + +for id, node, metadata in query:iter_captures(root, bufnr, 0, -1) do + local row1, col1, row2, col2 = node:range() + print(highlighter_query.hl_cache[id]) + -- print(id, node, metadata, vim.treesitter.get_node_text(node, bufnr)) + -- print(">>>>", row1, col1, row2, col2) +end diff --git a/scratch/short_buf.lua b/scratch/short_buf.lua new file mode 100644 index 0000000000..387b2b5035 --- /dev/null +++ b/scratch/short_buf.lua @@ -0,0 +1,4 @@ +local x = {} +print(x); print("wow"); + +local function other() end From d367eb4fd81bfc6a25374288bdfa1543b1e6deb8 Mon Sep 17 00:00:00 2001 From: Kyoichiro Yamada Date: Thu, 8 Apr 2021 00:00:38 +0900 Subject: [PATCH 02/11] fix(git_branches): use the quoted fields instead of json-formatting and fix regressions with #695 (#704) --- lua/telescope/builtin/git.lua | 64 +++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/lua/telescope/builtin/git.lua b/lua/telescope/builtin/git.lua index 568a15f257..b2270ad477 100644 --- a/lua/telescope/builtin/git.lua +++ b/lua/telescope/builtin/git.lua @@ -78,18 +78,12 @@ git.bcommits = function(opts) end git.branches = function(opts) - local format = '{' - .. '"head":%(if:equals=*)%(HEAD)%(then)true%(else)false%(end)' - .. ',"refname":"%(refname)"' - .. ',"authorname":"%(authorname)"' - .. '%(if)%(upstream)%(then)' - .. ',"upstream":"%(upstream:lstrip=2)"' - .. '%(else)' - .. ',"upstream":""' - .. '%(end)' - .. ',"committerdate":"%(committerdate:format-local:%Y/%m/%d %H:%M:%S)"' - .. '}' - local output = utils.get_os_command_output({ 'git', 'for-each-ref', '--format', format }, opts.cwd) + local format = '%(HEAD)' + .. '%(refname)' + .. '%(authorname)' + .. '%(upstream:lstrip=2)' + .. '%(committerdate:format-local:%Y/%m/%d%H:%M:%S)' + local output = utils.get_os_command_output({ 'git', 'for-each-ref', '--perl', '--format', format }, opts.cwd) local results = {} local widths = { @@ -98,26 +92,42 @@ git.branches = function(opts) upstream = 0, committerdate = 0, } - local register_entry = function(entry, trim_refname_prefix) - entry.name = string.sub(entry.refname, string.len(trim_refname_prefix)+1) + local unescape_single_quote = function(v) + return string.gsub(v, "\\([\\'])", "%1") + end + local parse_line = function(line) + local fields = vim.split(string.sub(line, 2, -2), "''", true) + local entry = { + head = fields[1], + refname = unescape_single_quote(fields[2]), + authorname = unescape_single_quote(fields[3]), + upstream = unescape_single_quote(fields[4]), + committerdate = fields[5], + } + local prefix + if vim.startswith(entry.refname, 'refs/remotes/') then + prefix = 'refs/remotes/' + elseif vim.startswith(entry.refname, 'refs/heads/') then + prefix = 'refs/heads/' + else + return + end + local index = 1 + if entry.head ~= '*' then + index = #results + 1 + end + + entry.name = string.sub(entry.refname, string.len(prefix)+1) for key, value in pairs(widths) do - widths[key] = math.max(value, vim.fn.strdisplaywidth(entry[key])) + widths[key] = math.max(value, utils.strdisplaywidth(entry[key] or '')) end if string.len(entry.upstream) > 0 then widths.upstream_indicator = 2 end - table.insert(results, entry) + table.insert(results, index, entry) end - for _, v in ipairs(output) do - local entry = vim.fn.json_decode(v) - if entry.head then - goto continue - elseif vim.startswith(entry.refname, 'refs/remotes/') then - register_entry(entry, 'refs/remotes/') - elseif vim.startswith(entry.refname, 'refs/heads/') then - register_entry(entry, 'refs/heads/') - end - ::continue:: + for _, line in ipairs(output) do + parse_line(line) end if #results == 0 then return @@ -126,6 +136,7 @@ git.branches = function(opts) local displayer = entry_display.create { separator = " ", items = { + { width = 1 }, { width = widths.name }, { width = widths.authorname }, { width = widths.upstream_indicator }, @@ -136,6 +147,7 @@ git.branches = function(opts) local make_display = function(entry) return displayer { + {entry.head}, {entry.name, 'TelescopeResultsIdentifier'}, {entry.authorname}, {string.len(entry.upstream) > 0 and '=>' or ''}, From e5fbe6fe60149af8fdeef0d07cba06c029258ba0 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 7 Apr 2021 17:12:47 -0400 Subject: [PATCH 03/11] fix: Use standardized names for current buffer fuzzy find (#737) --- lua/telescope/builtin/files.lua | 2 +- lua/telescope/make_entry.lua | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index 819d9ae2e9..24d02a019d 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -340,9 +340,9 @@ files.current_buffer_fuzzy_find = function(opts) for lnum, line in ipairs(lines) do table.insert(lines_with_numbers, { lnum = lnum, - line = line, bufnr = bufnr, filename = filename, + text = line, }) end diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua index 607b8ea5e8..e97097384e 100644 --- a/lua/telescope/make_entry.lua +++ b/lua/telescope/make_entry.lua @@ -694,7 +694,7 @@ function make_entry.gen_from_buffer_lines(opts) return displayer { { entry.lnum, opts.lnum_highlight_group or 'TelescopeResultsSpecialComment' }, { - entry.line, function() + entry.text, function() if not opts.line_highlights then return {} end local line_hl = opts.line_highlights[entry.lnum] or {} @@ -713,17 +713,17 @@ function make_entry.gen_from_buffer_lines(opts) end return function(entry) - if opts.skip_empty_lines and string.match(entry.line, '^$') then + if opts.skip_empty_lines and string.match(entry.text, '^$') then return end return { valid = true, - ordinal = entry.line, + ordinal = entry.text, display = make_display, filename = entry.filename, lnum = entry.lnum, - line = entry.line, + text = entry.text, } end end From 64e59060b1750d0c86761693b6847c3db07afcd2 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Thu, 8 Apr 2021 10:35:44 -0400 Subject: [PATCH 04/11] feat: asyncify pickers - except for live_grep (#709) * something kind of works already * yayayayayayayayayayayayayayayayayayayayayayayayayayayayayayayayaya * use async for everything besides live jobs * fix: fixup autocmds previewer * fix: lints for prime * temp: Add example of how we can think about async sorters * feat: Allow picker to decide when to cancel * fix: simplify scoring logic and tests * fixup: name * fix: Move back towards more backwards compat methods * fixup: Remove results from opts * fixup: remove trailing quote * fixup: Attempt to clean up some more async items. Next is status * wip: Add todo for when bfredl implements extmarks over the EOL * wip * fixup: got em * fixup: cleaning * fixup: docs --- .gitignore | 2 + lua/telescope/builtin/files.lua | 14 +- lua/telescope/builtin/internal.lua | 2 - lua/telescope/entry_manager.lua | 10 +- lua/telescope/finders.lua | 187 ++----------- lua/telescope/finders/async_job_finder.lua | 73 +++++ .../finders/async_oneshot_finder.lua | 81 ++++++ lua/telescope/finders/async_static_finder.lua | 41 +++ lua/telescope/log.lua | 3 +- lua/telescope/pickers.lua | 261 ++++++++---------- lua/telescope/pickers/layout_strategies.lua | 40 ++- lua/telescope/pickers/window.lua | 17 ++ lua/telescope/sorters.lua | 29 +- lua/tests/automated/telescope_spec.lua | 7 +- 14 files changed, 414 insertions(+), 353 deletions(-) create mode 100644 lua/telescope/finders/async_job_finder.lua create mode 100644 lua/telescope/finders/async_oneshot_finder.lua create mode 100644 lua/telescope/finders/async_static_finder.lua create mode 100644 lua/telescope/pickers/window.lua diff --git a/.gitignore b/.gitignore index 6799a850a7..ddeae0dfcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ build/ doc/tags + +.luacheckcache diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index 24d02a019d..23ceaa7db7 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -211,16 +211,16 @@ end files.file_browser = function(opts) opts = opts or {} + opts.depth = opts.depth or 1 opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd() - - local gen_new_finder = function(path) + opts.new_finder = opts.new_finder or function(path) opts.cwd = path local data = {} scan.scan_dir(path, { hidden = opts.hidden or false, add_dirs = true, - depth = 1, + depth = opts.depth, on_insert = function(entry, typ) table.insert(data, typ == 'directory' and (entry .. os_sep) or entry) end @@ -242,8 +242,8 @@ files.file_browser = function(opts) end pickers.new(opts, { - prompt_title = 'Find Files', - finder = gen_new_finder(opts.cwd), + prompt_title = 'File Browser', + finder = opts.new_finder(opts.cwd), previewer = conf.file_previewer(opts), sorter = conf.file_sorter(opts), attach_mappings = function(prompt_bufnr, map) @@ -253,7 +253,7 @@ files.file_browser = function(opts) local new_cwd = vim.fn.expand(action_state.get_selected_entry().path:sub(1, -2)) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker.cwd = new_cwd - current_picker:refresh(gen_new_finder(new_cwd), { reset_prompt = true }) + current_picker:refresh(opts.new_finder(new_cwd), { reset_prompt = true }) end) local create_new_file = function() @@ -276,7 +276,7 @@ files.file_browser = function(opts) Path:new(fpath:sub(1, -2)):mkdir({ parents = true }) local new_cwd = vim.fn.expand(fpath) current_picker.cwd = new_cwd - current_picker:refresh(gen_new_finder(new_cwd), { reset_prompt = true }) + current_picker:refresh(opts.new_finder(new_cwd), { reset_prompt = true }) end end diff --git a/lua/telescope/builtin/internal.lua b/lua/telescope/builtin/internal.lua index 7d9d750633..7d2d716d3c 100644 --- a/lua/telescope/builtin/internal.lua +++ b/lua/telescope/builtin/internal.lua @@ -815,8 +815,6 @@ internal.autocommands = function(opts) inner_loop(line) end - -- print(vim.inspect(autocmd_table)) - pickers.new(opts,{ prompt_title = 'autocommands', finder = finders.new_table { diff --git a/lua/telescope/entry_manager.lua b/lua/telescope/entry_manager.lua index c7350cb8ae..f32d29f3c5 100644 --- a/lua/telescope/entry_manager.lua +++ b/lua/telescope/entry_manager.lua @@ -26,7 +26,7 @@ if past loop of must have scores, local EntryManager = {} EntryManager.__index = EntryManager -function EntryManager:new(max_results, set_entry, info, id) +function EntryManager:new(max_results, set_entry, info) log.trace("Creating entry_manager...") info = info or {} @@ -40,7 +40,6 @@ function EntryManager:new(max_results, set_entry, info, id) set_entry = set_entry or function() end return setmetatable({ - id = id, linked_states = LinkedList:new { track_at = max_results }, info = info, max_results = max_results, @@ -128,13 +127,6 @@ function EntryManager:_append_container(picker, new_container, should_update) end function EntryManager:add_entry(picker, score, entry) - if picker and picker.id then - if picker.request_number ~= self.id then - error("ADDING ENTRY TOO LATE!") - return - end - end - score = score or 0 local max_res = self.max_results diff --git a/lua/telescope/finders.lua b/lua/telescope/finders.lua index cf616b0546..d2acd0517c 100644 --- a/lua/telescope/finders.lua +++ b/lua/telescope/finders.lua @@ -3,6 +3,10 @@ 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() @@ -104,179 +108,24 @@ function JobFinder:_find(prompt, process_result, process_complete) self.job:start() end -local OneshotJobFinder = _callable_obj() - -function OneshotJobFinder: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({ - fn_command = opts.fn_command, - entry_maker = opts.entry_maker or make_entry.from_string, - - cwd = opts.cwd, - writer = opts.writer, - - maximum_results = opts.maximum_results, - - _started = false, - }, self) - - obj._find = coroutine.wrap(function(finder, _, process_result, process_complete) - local num_execution = 1 - local num_results = 0 - - local results = setmetatable({}, { - __newindex = function(t, k, v) - rawset(t, k, v) - process_result(v) - end - }) - - local job_opts = finder:fn_command(_) - if not job_opts then - error(debug.traceback("expected `job_opts` from fn_command")) - end - - local writer = nil - if job_opts.writer and Job.is_job(job_opts.writer) then - writer = job_opts.writer - elseif job_opts.writer then - writer = Job:new(job_opts.writer) - end - - local on_output = function(_, line) - -- This will call the metamethod, process_result - num_results = num_results + 1 - results[num_results] = finder.entry_maker(line) - end - - local completed = false - local job = Job:new { - command = job_opts.command, - args = job_opts.args, - cwd = job_opts.cwd or finder.cwd, - - maximum_results = finder.maximum_results, - - writer = writer, - - enable_recording = false, - - on_stdout = on_output, - on_stderr = on_output, - - on_exit = function() - process_complete() - completed = true - end, - } - - job:start() - - while true do - finder, _, process_result, process_complete = coroutine.yield() - num_execution = num_execution + 1 - - local current_count = num_results - for index = 1, current_count do - process_result(results[index]) - end - - if completed then - process_complete() - end - end - end) - - return obj -end - -function OneshotJobFinder:old_find(_, process_result, process_complete) - local first_run = false - - if not self._started then - first_run = true - - self._started = true - - end - - -- First time we get called, start on up that job. - -- Every time after that, just use the results LUL - if not first_run then - return - end -end - - - ---[[ ============================================================= -Static Finders - -A static finder has results that never change. -They are passed in directly as a result. --- ============================================================= ]] -local StaticFinder = _callable_obj() - -function StaticFinder:new(opts) - assert(opts, "Options are required. See documentation for usage") - - local input_results - if vim.tbl_islist(opts) then - input_results = opts - else - input_results = opts.results - end - - local entry_maker = opts.entry_maker or make_entry.gen_from_string() - - assert(input_results) - assert(input_results, "Results are required for static finder") - assert(type(input_results) == 'table', "self.results must be a table") - - local results = {} - for k, v in ipairs(input_results) do - local entry = entry_maker(v) - - if entry then - entry.index = k - table.insert(results, entry) - end - end - - return setmetatable({ results = results }, self) -end - -function StaticFinder:_find(_, process_result, process_complete) - for _, v in ipairs(self.results) do - process_result(v) - end - - process_complete() -end - - --- local - - --- 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) - if opts.results then - print("finder.new is deprecated with `results`. You should use `finder.new_table`") - return StaticFinder:new(opts) - end - + assert(not opts.results, "finder.new is deprecated with `results`. You should use `finder.new_table`") return JobFinder:new(opts) end finders.new_job = function(command_generator, entry_maker, maximum_results, cwd) + -- return async_job_finder { + -- command_generator = command_generator, + -- entry_maker = entry_maker, + -- maximum_results = maximum_results, + -- cwd = cwd, + -- } + return JobFinder:new { fn_command = function(_, prompt) local command_list = command_generator(prompt) @@ -298,18 +147,20 @@ finders.new_job = function(command_generator, entry_maker, maximum_results, cwd) } end ----@param command_list string[] Command list to execute. ----@param opts table +--- 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 {} - command_list = vim.deepcopy(command_list) + 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 OneshotJobFinder:new { + return async_oneshot_finder { entry_maker = opts.entry_maker or make_entry.gen_from_string(), cwd = opts.cwd, @@ -331,7 +182,7 @@ end -- results table, the results to run on -- entry_maker function, the function to convert results to entries. finders.new_table = function(t) - return StaticFinder:new(t) + return async_static_finder(t) end return finders diff --git a/lua/telescope/finders/async_job_finder.lua b/lua/telescope/finders/async_job_finder.lua new file mode 100644 index 0000000000..ee1b2c78a0 --- /dev/null +++ b/lua/telescope/finders/async_job_finder.lua @@ -0,0 +1,73 @@ +local log = require('telescope.log') +local Job = require('plenary.job') + +local async_lib = require('plenary.async_lib') +local async = async_lib.async +-- local await = async_lib.await +local void = async_lib.void + +local make_entry = require('telescope.make_entry') + +return function(opts) + local entry_maker = opts.entry_maker or make_entry.gen_from_string() + local fn_command = function(prompt) + local command_list = opts.command_generator(prompt) + if command_list == nil then + return nil + end + + local command = table.remove(command_list, 1) + + return { + command = command, + args = command_list, + } + end + + local job + return setmetatable({ + close = function() end, + }, { + __call = void(async(function(prompt, process_result, process_complete) + print("are we callin anything?", job) + if job and not job.is_shutdown then + log.debug("Shutting down old job") + job:shutdown() + end + + local job_opts = fn_command(prompt) + if not job_opts then return end + + local writer = nil + if job_opts.writer and Job.is_job(job_opts.writer) then + writer = job_opts.writer + elseif opts.writer then + writer = Job:new(job_opts.writer) + end + + job = Job:new { + command = job_opts.command, + args = job_opts.args, + cwd = job_opts.cwd or opts.cwd, + maximum_results = opts.maximum_results, + writer = writer, + enable_recording = false, + + on_stdout = vim.schedule_wrap(function(_, line) + if not line or line == "" then + return + end + + -- TODO: shutdown job here. + process_result(entry_maker(line)) + end), + + on_exit = function() + process_complete() + end, + } + + job:start() + end)), + }) +end diff --git a/lua/telescope/finders/async_oneshot_finder.lua b/lua/telescope/finders/async_oneshot_finder.lua new file mode 100644 index 0000000000..2345096798 --- /dev/null +++ b/lua/telescope/finders/async_oneshot_finder.lua @@ -0,0 +1,81 @@ +local async_lib = require('plenary.async_lib') +local async = async_lib.async +local await = async_lib.await +local void = async_lib.void + +local AWAITABLE = 1000 + +local make_entry = require('telescope.make_entry') + +local Job = require('plenary.job') + +return function(opts) + opts = opts or {} + + local entry_maker = opts.entry_maker or make_entry.from_string + local cwd = opts.cwd + local fn_command = assert(opts.fn_command, "Must pass `fn_command`") + + local results = {} + local num_results = 0 + + local job_started = false + local job_completed = false + return setmetatable({ + close = function() results = {}; job_started = false end, + results = results, + }, { + __call = void(async(function(_, prompt, process_result, process_complete) + if not job_started then + local job_opts = fn_command() + + local writer + if job_opts.writer and Job.is_job(job_opts.writer) then + writer = job_opts.writer + elseif job_opts.writer then + writer = Job:new(job_opts.writer) + end + + local job = Job:new { + command = job_opts.command, + args = job_opts.args, + cwd = job_opts.cwd or cwd, + maximum_results = opts.maximum_results, + writer = writer, + enable_recording = false, + + on_stdout = vim.schedule_wrap(function(_, line) + num_results = num_results + 1 + + local v = entry_maker(line) + results[num_results] = v + process_result(v) + end), + + on_exit = function() + process_complete() + job_completed = true + end, + } + + job:start() + job_started = true + end + + local current_count = num_results + for index = 1, current_count do + if process_result(results[index]) then + break + end + + if index % AWAITABLE == 0 then + await(async_lib.scheduler()) + end + end + + if job_completed then + process_complete() + end + end)), + }) +end diff --git a/lua/telescope/finders/async_static_finder.lua b/lua/telescope/finders/async_static_finder.lua new file mode 100644 index 0000000000..0756551ec5 --- /dev/null +++ b/lua/telescope/finders/async_static_finder.lua @@ -0,0 +1,41 @@ +local async_lib = require('plenary.async_lib') +local async = async_lib.async +local await = async_lib.await +local void = async_lib.void + +local make_entry = require('telescope.make_entry') + +return function(opts) + local input_results + if vim.tbl_islist(opts) then input_results = opts + else input_results = opts.results end + + local entry_maker = opts.entry_maker or make_entry.gen_from_string() + + local results = {} + for k, v in ipairs(input_results) do + local entry = entry_maker(v) + + if entry then + entry.index = k + table.insert(results, entry) + end + end + + return setmetatable({ + results = results, + close = function() end, + }, { + __call = void(async(function(_, _, process_result, process_complete) + for i, v in ipairs(results) do + if process_result(v) then break end + + if i % 1000 == 0 then + await(async_lib.scheduler()) + end + end + + process_complete() + end)), + }) +end diff --git a/lua/telescope/log.lua b/lua/telescope/log.lua index 9beabb1d65..c93472bbf7 100644 --- a/lua/telescope/log.lua +++ b/lua/telescope/log.lua @@ -1,5 +1,6 @@ +local user = vim.loop.os_getenv("USER") return require('plenary.log').new { plugin = 'telescope', - level = (vim.loop.os_getenv("USER") == 'tj' and 'debug') or 'warn', + level = ((user == 'tj' or user == 'tjdevries') and 'debug') or 'warn', } diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 5ba0ca00fc..7e658c3682 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -1,22 +1,28 @@ local a = vim.api local popup = require('popup') +local async_lib = require('plenary.async_lib') +local async_util = async_lib.util + +local async = async_lib.async +local await = async_lib.await +local channel = async_util.channel + require('telescope') local actions = require('telescope.actions') local action_set = require('telescope.actions.set') local config = require('telescope.config') local debounce = require('telescope.debounce') -local resolve = require('telescope.config.resolve') local log = require('telescope.log') local mappings = require('telescope.mappings') local state = require('telescope.state') local utils = require('telescope.utils') -local layout_strategies = require('telescope.pickers.layout_strategies') local entry_display = require('telescope.pickers.entry_display') -local p_highlights = require('telescope.pickers.highlights') +local p_highlighter = require('telescope.pickers.highlights') local p_scroller = require('telescope.pickers.scroller') +local p_window = require('telescope.pickers.window') local EntryManager = require('telescope.entry_manager') local MultiSelect = require('telescope.pickers.multi') @@ -73,6 +79,7 @@ function Picker:new(opts) cwd = opts.cwd, + _find_id = 0, _completion_callbacks = {}, _multi = MultiSelect:new(), @@ -85,7 +92,6 @@ function Picker:new(opts) sorting_strategy = get_default(opts.sorting_strategy, config.values.sorting_strategy), selection_strategy = get_default(opts.selection_strategy, config.values.selection_strategy), - get_window_options = opts.get_window_options, layout_strategy = layout_strategy, layout_config = get_default( opts.layout_config, @@ -116,12 +122,15 @@ function Picker:new(opts) preview_cutoff = get_default(opts.preview_cutoff, config.values.preview_cutoff), }, self) + obj.get_window_options = opts.get_window_options or p_window.get_window_options + + -- TODO: It's annoying that this is create and everything else is "new" obj.scroller = p_scroller.create( get_default(opts.scroll_strategy, config.values.scroll_strategy), obj.sorting_strategy ) - obj.highlighter = p_highlights.new(obj) + obj.highlighter = p_highlighter.new(obj) if opts.on_complete then for _, on_complete_item in ipairs(opts.on_complete) do @@ -132,52 +141,8 @@ function Picker:new(opts) return obj end -function Picker:_get_initial_window_options() - local popup_border = resolve.win_option(self.window.border) - local popup_borderchars = resolve.win_option(self.window.borderchars) - - local preview = { - title = self.preview_title, - border = popup_border.preview, - borderchars = popup_borderchars.preview, - enter = false, - highlight = false - } - - local results = { - title = self.results_title, - border = popup_border.results, - borderchars = popup_borderchars.results, - enter = false, - } - - local prompt = { - title = self.prompt_title, - border = popup_border.prompt, - borderchars = popup_borderchars.prompt, - enter = true - } - - return { - preview = preview, - results = results, - prompt = prompt, - } -end - -function Picker:get_window_options(max_columns, max_lines) - local layout_strategy = self.layout_strategy - local getter = layout_strategies[layout_strategy] - - if not getter then - error("Not a valid layout strategy: " .. layout_strategy) - end - - return getter(self, max_columns, max_lines) -end - --- Take a row and get an index. ---- @note: Rows are 0-indexed, and `index` is 1 indexed (table index) +---@note: Rows are 0-indexed, and `index` is 1 indexed (table index) ---@param index number: The row being displayed ---@return number The row for the picker to display in function Picker:get_row(index) @@ -308,6 +273,13 @@ function Picker:can_select_row(row) end end +function Picker:_next_find_id() + local find_id = self._find_id + 1 + self._find_id = find_id + + return find_id +end + function Picker:find() self:close_existing_pickers() self:reset_selection() @@ -317,7 +289,7 @@ function Picker:find() self.original_win_id = a.nvim_get_current_win() -- User autocmd run it before create Telescope window - vim.cmd'do User TelescopeFindPre' + vim.cmd [[doautocmd User TelescopeFindPre]] -- Create three windows: -- 1. Prompt window @@ -393,66 +365,70 @@ function Picker:find() local status_updater = self:get_status_updater(prompt_win, prompt_bufnr) local debounced_status = debounce.throttle_leading(status_updater, 50) + -- local debounced_status = status_updater - self.request_number = 0 - local on_lines = function(_, _, _, first_line, last_line) - self.request_number = self.request_number + 1 - self:_reset_track() + local tx, rx = channel.mpsc() + self.__on_lines = tx.send - if not vim.api.nvim_buf_is_valid(prompt_bufnr) then - log.debug("ON_LINES: Invalid prompt_bufnr", prompt_bufnr) - return - end + local main_loop = async(function() + while true do + await(async_lib.scheduler()) - if not first_line then first_line = 0 end - if not last_line then last_line = 1 end + local _, _, _, first_line, last_line = await(rx.last()) + self:_reset_track() - if first_line > 0 or last_line > 1 then - log.debug("ON_LINES: Bad range", first_line, last_line) - return - end + if not vim.api.nvim_buf_is_valid(prompt_bufnr) then + log.debug("ON_LINES: Invalid prompt_bufnr", prompt_bufnr) + return + end - local original_prompt = self:_get_prompt() - local on_input_result = self._on_input_filter_cb(original_prompt) or {} + if not first_line then first_line = 0 end + if not last_line then last_line = 1 end - local prompt = on_input_result.prompt or original_prompt - local finder = on_input_result.updated_finder + if first_line > 0 or last_line > 1 then + log.debug("ON_LINES: Bad range", first_line, last_line) + return + end - if finder then - self.finder:close() - self.finder = finder - end + local original_prompt = self:_get_prompt() + local on_input_result = self._on_input_filter_cb(original_prompt) or {} - if self.sorter then - self.sorter:_start(prompt) - end + local prompt = on_input_result.prompt or original_prompt + local finder = on_input_result.updated_finder - -- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display - self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats, self.request_number) + if finder then + self.finder:close() + self.finder = finder + end - local process_result = self:get_result_processor(prompt, debounced_status) - local process_complete = self:get_result_completor(self.results_bufnr, prompt, status_updater) + if self.sorter then + self.sorter:_start(prompt) + end - local ok, msg = pcall(function() - self.finder(prompt, process_result, vim.schedule_wrap(process_complete)) - end) + -- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display + self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats) - if not ok then - log.warn("Failed with msg: ", msg) - end - end + local find_id = self:_next_find_id() + local process_result = self:get_result_processor(find_id, prompt, debounced_status) + local process_complete = self:get_result_completor(self.results_bufnr, find_id, prompt, status_updater) - self.__on_lines = on_lines + local ok, msg = pcall(function() + self.finder(prompt, process_result, vim.schedule_wrap(process_complete)) + end) + + if not ok then + log.warn("Failed with msg: ", msg) + end + end + end) - on_lines(nil, nil, nil, 0, 1) + -- on_lines(nil, nil, nil, 0, 1) status_updater() -- Register attach vim.api.nvim_buf_attach(prompt_bufnr, false, { - on_lines = on_lines, + on_lines = tx.send, on_detach = function() - on_lines = nil - -- TODO: Can we add a "cleanup" / "teardown" function that completely removes these. self.finder = nil self.previewer = nil @@ -466,6 +442,8 @@ function Picker:find() end, }) + async_lib.run(main_loop()) + -- TODO: Use WinLeave as well? local on_buf_leave = string.format( [[ autocmd BufLeave ++nested ++once :silent lua require('telescope.pickers').on_close_prompt(%s)]], @@ -659,7 +637,8 @@ function Picker:refresh(finder, opts) if opts.reset_prompt then self:reset_prompt() end self.finder:close() - self.finder = finder + if finder then self.finder = finder end + self.__on_lines(nil, nil, nil, 0, 1) end @@ -695,6 +674,8 @@ function Picker:set_selection(row) local entry = self.manager:get_entry(self:get_index(row)) state.set_global_key("selected_entry", entry) + if not entry then return end + -- TODO: Probably should figure out what the rows are that made this happen... -- Probably something with setting a row that's too high for this? -- Not sure. @@ -775,6 +756,8 @@ function Picker:refresh_previewer() end function Picker:entry_adder(index, entry, _, insert) + if not entry then return end + local row = self:get_row(index) -- If it's less than 0, then we don't need to show it at all. @@ -799,18 +782,14 @@ function Picker:entry_adder(index, entry, _, insert) -- TODO: Don't need to schedule this if we schedule the adder. local offset = insert and 0 or 1 - local scheduled_request = self.request_number vim.schedule(function() if not vim.api.nvim_buf_is_valid(self.results_bufnr) then log.debug("ON_ENTRY: Invalid buffer") return end - if self.request_number ~= scheduled_request then - log.trace("Cancelling request number:", self.request_number, " // ", scheduled_request) - return - end - + -- TODO: Does this every get called? + -- local line_count = vim.api.nvim_win_get_height(self.results_win) local line_count = vim.api.nvim_buf_line_count(self.results_bufnr) if row > line_count then return @@ -850,11 +829,6 @@ function Picker:_reset_track() self.stats.filtered = 0 self.stats.highlights = 0 - - self.stats._sort_time = 0 - self.stats._add_time = 0 - self.stats._highlight_time = 0 - self.stats._start = vim.loop.hrtime() end function Picker:_track(key, func, ...) @@ -914,8 +888,7 @@ function Picker:get_status_updater(prompt_win, prompt_bufnr) return end - local expected_prompt_len = #self.prompt_prefix + 1 - local prompt_len = #current_prompt < expected_prompt_len and expected_prompt_len or #current_prompt + local prompt_len = #current_prompt local padding = string.rep(" ", vim.api.nvim_win_get_width(prompt_win) - prompt_len - #text - 3) vim.api.nvim_buf_clear_namespace(prompt_bufnr, ns_telescope_prompt, 0, 1) @@ -927,68 +900,61 @@ function Picker:get_status_updater(prompt_win, prompt_bufnr) {} ) + -- TODO: Wait for bfredl + -- vim.api.nvim_buf_set_extmark(prompt_bufnr, ns_telescope_prompt, 0, 0, { + -- end_line = 0, + -- -- end_col = start_column + #text, + -- virt_text = { { text, "NonText", } }, + -- virt_text_pos = "eol", + -- }) + self:_increment("status") end end -function Picker:get_result_processor(prompt, status_updater) - return function(entry) - if self.closed or self:is_done() then return end +function Picker:get_result_processor(find_id, prompt, status_updater) + local cb_add = function(score, entry) + self.manager:add_entry(self, score, entry) + status_updater() + end - self:_increment("processed") + local cb_filter = function(_) + self:_increment("filtered") + end - if not entry then - log.debug("No entry...") - return + return function(entry) + if find_id ~= self._find_id + or self.closed + or self:is_done() then + return true end - -- TODO: Should we even have valid? - if entry.valid == false then + self:_increment("processed") + + if not entry or entry.valid == false then return end + -- TODO: Probably should asyncify this / cache this / do something because this probably takes + -- a ton of time on large results. log.trace("Processing result... ", entry) - for _, v in ipairs(self.file_ignore_patterns or {}) do local file = type(entry.value) == 'string' and entry.value or entry.filename if file then if string.find(file, v) then - log.debug("SKIPPING", entry.value, "because", v) + log.trace("SKIPPING", entry.value, "because", v) self:_decrement("processed") return end end end - local sort_ok - local sort_score = 0 - if self.sorter then - sort_ok, sort_score = self:_track("_sort_time", pcall, self.sorter.score, self.sorter, prompt, entry) - - if not sort_ok then - log.warn("Sorting failed with:", prompt, entry, sort_score) - return - end - - if entry.ignore_count ~= nil and entry.ignore_count == true then - self:_decrement("processed") - end - - if sort_score == -1 then - self:_increment("filtered") - log.trace("Filtering out result: ", entry) - return - end - end - - self:_track("_add_time", self.manager.add_entry, self.manager, self, sort_score, entry) - - status_updater() + self.sorter:score(prompt, entry, cb_add, cb_filter) end end -function Picker:get_result_completor(results_bufnr, prompt, status_updater) +function Picker:get_result_completor(results_bufnr, find_id, prompt, status_updater) return function() if self.closed == true or self:is_done() then return end @@ -1030,17 +996,6 @@ function Picker:get_result_completor(results_bufnr, prompt, status_updater) self:clear_extra_rows(results_bufnr) self:highlight_displayed_rows(results_bufnr, prompt) - -- TODO: Cleanup. - self.stats._done = vim.loop.hrtime() - self.stats.time = (self.stats._done - self.stats._start) / 1e9 - - local function do_times(key) - self.stats[key] = self.stats["_" .. key] / 1e9 - end - - do_times("sort_time") - do_times("add_time") - do_times("highlight_time") self:_on_complete() diff --git a/lua/telescope/pickers/layout_strategies.lua b/lua/telescope/pickers/layout_strategies.lua index b8d9aea32a..b4c9fda560 100644 --- a/lua/telescope/pickers/layout_strategies.lua +++ b/lua/telescope/pickers/layout_strategies.lua @@ -61,6 +61,40 @@ local config = require('telescope.config') local resolve = require("telescope.config.resolve") +local function get_initial_window_options(picker) + local popup_border = resolve.win_option(picker.window.border) + local popup_borderchars = resolve.win_option(picker.window.borderchars) + + local preview = { + title = picker.preview_title, + border = popup_border.preview, + borderchars = popup_borderchars.preview, + enter = false, + highlight = false + } + + local results = { + title = picker.results_title, + border = popup_border.results, + borderchars = popup_borderchars.results, + enter = false, + } + + local prompt = { + title = picker.prompt_title, + border = popup_border.prompt, + borderchars = popup_borderchars.prompt, + enter = true + } + + return { + preview = preview, + results = results, + prompt = prompt, + } +end + + -- Check if there are any borders. Right now it's a little raw as -- there are a few things that contribute to the border local is_borderless = function(opts) @@ -105,7 +139,7 @@ layout_strategies.horizontal = function(self, max_columns, max_lines) scroll_speed = "The speed when scrolling through the previewer", }) - local initial_options = self:_get_initial_window_options() + local initial_options = get_initial_window_options(self) local preview = initial_options.preview local results = initial_options.results local prompt = initial_options.prompt @@ -203,7 +237,7 @@ end --- +--------------+ --- layout_strategies.center = function(self, columns, lines) - local initial_options = self:_get_initial_window_options() + local initial_options = get_initial_window_options(self) local preview = initial_options.preview local results = initial_options.results local prompt = initial_options.prompt @@ -273,7 +307,7 @@ layout_strategies.vertical = function(self, max_columns, max_lines) scroll_speed = "The speed when scrolling through the previewer", }) - local initial_options = self:_get_initial_window_options() + local initial_options = get_initial_window_options(self) local preview = initial_options.preview local results = initial_options.results local prompt = initial_options.prompt diff --git a/lua/telescope/pickers/window.lua b/lua/telescope/pickers/window.lua new file mode 100644 index 0000000000..76c1fe0d09 --- /dev/null +++ b/lua/telescope/pickers/window.lua @@ -0,0 +1,17 @@ +local p_layouts = require('telescope.pickers.layout_strategies') + +local p_window = {} + +function p_window.get_window_options(picker, max_columns, max_lines) + local layout_strategy = picker.layout_strategy + local getter = p_layouts[layout_strategy] + + if not getter then + error("Not a valid layout strategy: " .. layout_strategy) + end + + return getter(picker, max_columns, max_lines) +end + + +return p_window diff --git a/lua/telescope/sorters.lua b/lua/telescope/sorters.lua index 5ac7086e63..4147f59e50 100644 --- a/lua/telescope/sorters.lua +++ b/lua/telescope/sorters.lua @@ -32,12 +32,17 @@ Sorter.__index = Sorter --- --- Lower number is better (because it's like a closer match) --- But, any number below 0 means you want that line filtered out. ---- @field scoring_function function Function that has the interface: --- (sorter, prompt, line): number +---@field scoring_function function: Function that has the interface: (sorter, prompt, line): number +---@field tags table: Unique tags collected at filtering for tag completion +---@field filter_function function: Function that can filter results +---@field highlighter function: Highlights results to display them pretty +---@field discard boolean: Whether this is a discardable style sorter or not. +---@field score function: Override the score function if desired. function Sorter:new(opts) opts = opts or {} return setmetatable({ + score = opts.score, state = {}, tags = opts.tags, filter_function = opts.filter_function, @@ -77,13 +82,12 @@ end -- TODO: Consider doing something that makes it so we can skip the filter checks -- if we're not discarding. Also, that means we don't have to check otherwise as well :) -function Sorter:score(prompt, entry) - if not entry or not entry.ordinal then return -1 end +function Sorter:score(prompt, entry, cb_add, cb_filter) + if not entry or not entry.ordinal then return end local ordinal = entry.ordinal - if self:_was_discarded(prompt, ordinal) then - return FILTERED + return cb_filter(entry) end local filter_score @@ -92,14 +96,21 @@ function Sorter:score(prompt, entry) filter_score, prompt = self:filter_function(prompt, entry) end - local score = (filter_score == FILTERED and FILTERED or - self:scoring_function(prompt or "", ordinal, entry)) + if filter_score == FILTERED then + return cb_filter(entry) + end + local score = self:scoring_function(prompt or "", ordinal, entry) if score == FILTERED then self:_mark_discarded(prompt, ordinal) + return cb_filter(entry) end - return score + if cb_add then + return cb_add(score, entry) + else + return score + end end function Sorter:_was_discarded(prompt, ordinal) diff --git a/lua/tests/automated/telescope_spec.lua b/lua/tests/automated/telescope_spec.lua index 0f7bc85ad0..6ad09d78f2 100644 --- a/lua/tests/automated/telescope_spec.lua +++ b/lua/tests/automated/telescope_spec.lua @@ -100,7 +100,12 @@ describe('telescope', function() describe('fzy', function() local sorter = require'telescope.sorters'.get_fzy_sorter() local function score(prompt, line) - return sorter:score(prompt, {ordinal = line}) + return sorter:score( + prompt, + {ordinal = line}, + function(val) return val end, + function() return -1 end + ) end describe("matches", function() From 2ebbf7f9d4fbc29b6075dd8fce86e05675a745c2 Mon Sep 17 00:00:00 2001 From: Nitin Chaudhary Date: Fri, 9 Apr 2021 15:22:26 +0530 Subject: [PATCH 05/11] pickers(buffers): added only_cwd opt (#739) closes #733 Co-authored-by: Nitin Chaudhary --- lua/telescope/builtin/internal.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/telescope/builtin/internal.lua b/lua/telescope/builtin/internal.lua index 7d2d716d3c..a58c354f57 100644 --- a/lua/telescope/builtin/internal.lua +++ b/lua/telescope/builtin/internal.lua @@ -544,6 +544,9 @@ internal.buffers = function(opts) if opts.ignore_current_buffer and b == vim.api.nvim_get_current_buf() then return false end + if opts.only_cwd and not string.find(vim.api.nvim_buf_get_name(b), vim.loop.cwd()) then + return false + end return true end, vim.api.nvim_list_bufs()) if not next(bufnrs) then return end From ba1e674e68a251ce7dcaf44a7b815431ba563f8f Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 9 Apr 2021 13:04:01 -0400 Subject: [PATCH 06/11] fix: update to newer code (#744) --- lua/telescope/builtin/init.lua | 1 + lua/telescope/builtin/internal.lua | 41 ++++++++++++++++++++++++++++++ lua/telescope/make_entry.lua | 1 + 3 files changed, 43 insertions(+) diff --git a/lua/telescope/builtin/init.lua b/lua/telescope/builtin/init.lua index b7f45f51df..2b3c1cacdd 100644 --- a/lua/telescope/builtin/init.lua +++ b/lua/telescope/builtin/init.lua @@ -60,6 +60,7 @@ builtin.filetypes = require('telescope.builtin.internal').filetypes builtin.highlights = require('telescope.builtin.internal').highlights builtin.autocommands = require('telescope.builtin.internal').autocommands builtin.spell_suggest = require('telescope.builtin.internal').spell_suggest +builtin.tagstack = require('telescope.builtin.internal').tagstack builtin.lsp_references = require('telescope.builtin.lsp').references builtin.lsp_definitions = require('telescope.builtin.lsp').definitions diff --git a/lua/telescope/builtin/internal.lua b/lua/telescope/builtin/internal.lua index a58c354f57..7eefcb207c 100644 --- a/lua/telescope/builtin/internal.lua +++ b/lua/telescope/builtin/internal.lua @@ -862,6 +862,47 @@ internal.spell_suggest = function(opts) }):find() end +internal.tagstack = function(opts) + opts = opts or {} + local tagstack = vim.fn.gettagstack() + if vim.tbl_isempty(tagstack.items) then + print("No tagstack available") + return + end + + for _, value in pairs(tagstack.items) do + value.valid = true + value.bufnr = value.from[1] + value.lnum = value.from[2] + value.col = value.from[3] + value.filename = vim.fn.bufname(value.from[1]) + + value.text = vim.api.nvim_buf_get_lines( + value.bufnr, + value.lnum - 1, + value.lnum, + false + )[1] + end + + -- reverse the list + local tags = {} + for i = #tagstack.items, 1, -1 do + tags[#tags+1] = tagstack.items[i] + end + + pickers.new(opts, { + prompt_title = 'TagStack', + finder = finders.new_table { + results = tags, + entry_maker = make_entry.gen_from_quickfix(opts), + }, + previewer = previewers.vim_buffer_qflist.new(opts), + sorter = conf.generic_sorter(opts), + }):find() +end + + local function apply_checks(mod) for k, v in pairs(mod) do mod[k] = function(opts) diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua index e97097384e..c714e7b429 100644 --- a/lua/telescope/make_entry.lua +++ b/lua/telescope/make_entry.lua @@ -317,6 +317,7 @@ function make_entry.gen_from_quickfix(opts) ) .. ' ' .. entry.text, display = make_display, + bufnr = entry.bufnr, filename = filename, lnum = entry.lnum, col = entry.col, From 5bd6f5ca9828ea02f2c54d616ad65c72a5cdd7fb Mon Sep 17 00:00:00 2001 From: Senghan Bright Date: Fri, 9 Apr 2021 19:33:10 +0200 Subject: [PATCH 07/11] feat: add icons to git_status finder (#401) * add icons to git_status finder * fix lint warning * fix incorrect removed icon * refactor, more states/icons * refactor, widen columns to allow for 3char width icons * attempted col width fix * fixup: small comments Co-authored-by: TJ DeVries --- lua/telescope/make_entry.lua | 47 +++++++++++++++++++++++++++--------- plugin/telescope.vim | 1 + scratch/old_perf_debug | 36 --------------------------- 3 files changed, 36 insertions(+), 48 deletions(-) delete mode 100644 scratch/old_perf_debug diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua index c714e7b429..b9635bb10b 100644 --- a/lua/telescope/make_entry.lua +++ b/lua/telescope/make_entry.lua @@ -1019,28 +1019,51 @@ function make_entry.gen_from_autocommands(_) end end +local git_icon_defaults = { + added = "+", + changed = "~", + copied = ">", + deleted = "-", + renamed = "➡", + unmerged = "‡", + untracked = "?" +} + function make_entry.gen_from_git_status(opts) + opts = opts or {} + + local col_width = ((opts.git_icons and opts.git_icons.added) and opts.git_icons.added:len() + 2) or 2 local displayer = entry_display.create { - separator = " ", + separator = "", items = { - { width = 1 }, - { width = 1 }, + { width = col_width}, + { width = col_width}, { remaining = true }, } } - local make_display = function(entry) - local modified = "TelescopeResultsDiffChange" - local staged = "TelescopeResultsDiffAdd" + local icons = vim.tbl_extend("keep", opts.git_icons or {}, git_icon_defaults) - if entry.status == "??" then - modified = "TelescopeResultsDiffDelete" - staged = "TelescopeResultsDiffDelete" - end + local git_abbrev = { + ["A"] = {icon = icons.added, hl = "TelescopeResultsDiffAdd"}, + ["U"] = {icon = icons.unmerged, hl = "TelescopeResultsDiffAdd"}, + ["M"] = {icon = icons.changed, hl = "TelescopeResultsDiffChange"}, + ["C"] = {icon = icons.copied, hl = "TelescopeResultsDiffChange"}, + ["R"] = {icon = icons.renamed, hl = "TelescopeResultsDiffChange"}, + ["D"] = {icon = icons.deleted, hl = "TelescopeResultsDiffDelete"}, + ["?"] = {icon = icons.untracked, hl = "TelescopeResultsDiffUntracked"}, + } + + local make_display = function(entry) + local x = string.sub(entry.status, 1, 1) + local y = string.sub(entry.status, -1) + local status_x = git_abbrev[x] or {} + local status_y = git_abbrev[y] or {} + local empty_space = (" ") return displayer { - { string.sub(entry.status, 1, 1), staged }, - { string.sub(entry.status, -1), modified }, + { status_x.icon or empty_space, status_x.hl}, + { status_y.icon or empty_space, status_y.hl}, entry.value, } end diff --git a/plugin/telescope.vim b/plugin/telescope.vim index ee2f4759bd..2eea7b1073 100644 --- a/plugin/telescope.vim +++ b/plugin/telescope.vim @@ -69,6 +69,7 @@ highlight default link TelescopeResultsSpecialComment SpecialComment highlight default link TelescopeResultsDiffChange DiffChange highlight default link TelescopeResultsDiffAdd DiffAdd highlight default link TelescopeResultsDiffDelete DiffDelete +highlight default link TelescopeResultsDiffUntracked NonText " This is like "" in your terminal. " To use it, do `cmap (TelescopeFuzzyCommandSearch) diff --git a/scratch/old_perf_debug b/scratch/old_perf_debug deleted file mode 100644 index 3561a989b7..0000000000 --- a/scratch/old_perf_debug +++ /dev/null @@ -1,36 +0,0 @@ - - --- Until I have better profiling stuff, this will have to do. -PERF = function(...) end -PERF_DEBUG = PERF_DEBUG or nil -START = nil - -if PERF_DEBUG then - PERF = function(...) - local new_time = (vim.loop.hrtime() - START) / 1E9 - if select('#', ...) == 0 then - vim.schedule(function() - vim.api.nvim_buf_set_lines(PERF_DEBUG, -1, -1, false, { '' }) - end) - return - end - - local to_insert = '' - if START then - to_insert = tostring(new_time) .. ' | ' - end - - for _, v in ipairs({...}) do - if type(v) == 'table' then - to_insert = to_insert .. tostring(#v) .. ' | ' - else - to_insert = to_insert .. tostring(v) .. ' | ' - end - end - - vim.schedule(function() - vim.api.nvim_buf_set_lines(PERF_DEBUG, -1, -1, false, { to_insert }) - end) - end -end - From 253d3aaa6b43eac6b11341b325e34d37dc459af3 Mon Sep 17 00:00:00 2001 From: oberblastmeister <61095988+oberblastmeister@users.noreply.github.com> Date: Tue, 13 Apr 2021 14:39:14 -0400 Subject: [PATCH 08/11] added a new DynamicFinder (which can be used with rust_analyzer) (#705) * started tree finder * made tree more ergonmic * deleted unneeded comments * added stack root and node * added preprocessing * using staticfinder instead of separate finder, custom entry maker * added selections and remember * removed unused stuff * fixed warnings * fixed remember and selections pop * started branch * added go function * changed up test * removed root parameter from go function * changed back to not do_close * removed node and leaf classes * removed stack class instead for table.insert and table.remove * fixed warning * started branch * added better preprocessor and tree class * started some tests * finished making tests pass * cleaned up * fixed make entry and updated example * started * added some stuff * deleted uneeded stuff * added cancelable * changed workspace requester * use better cancellation mechanism * removed accidental stuff * removed useless print * delete more useless stuff * rename to dynamic * added request cancellation * CHECK IF NIL * removed unused * added trash global variable --- .luacheckrc | 1 + lua/telescope/builtin/init.lua | 2 ++ lua/telescope/builtin/lsp.lua | 33 +++++++++++++++++++++++++++++++ lua/telescope/finders.lua | 36 +++++++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index 49c9bcfaed..98c76bd4c9 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -13,6 +13,7 @@ ignore = { } globals = { + "_", "TelescopeGlobalState", "TelescopeCachedUppers", "TelescopeCachedTails", diff --git a/lua/telescope/builtin/init.lua b/lua/telescope/builtin/init.lua index 2b3c1cacdd..b9cc99f7c9 100644 --- a/lua/telescope/builtin/init.lua +++ b/lua/telescope/builtin/init.lua @@ -40,6 +40,7 @@ builtin.git_branches = require('telescope.builtin.git').branches builtin.git_status = require('telescope.builtin.git').status builtin.builtin = require('telescope.builtin.internal').builtin + builtin.planets = require('telescope.builtin.internal').planets builtin.symbols = require('telescope.builtin.internal').symbols builtin.commands = require('telescope.builtin.internal').commands @@ -70,5 +71,6 @@ builtin.lsp_document_diagnostics = require('telescope.builtin.lsp').diagnostics builtin.lsp_workspace_diagnostics = require('telescope.builtin.lsp').workspace_diagnostics builtin.lsp_range_code_actions = require('telescope.builtin.lsp').range_code_actions builtin.lsp_workspace_symbols = require('telescope.builtin.lsp').workspace_symbols +builtin.lsp_dynamic_workspace_symbols = require('telescope.builtin.lsp').dynamic_workspace_symbols return builtin diff --git a/lua/telescope/builtin/lsp.lua b/lua/telescope/builtin/lsp.lua index 741eb364b9..b2ab981443 100644 --- a/lua/telescope/builtin/lsp.lua +++ b/lua/telescope/builtin/lsp.lua @@ -4,6 +4,9 @@ local finders = require('telescope.finders') local make_entry = require('telescope.make_entry') local pickers = require('telescope.pickers') local utils = require('telescope.utils') +local a = require('plenary.async_lib') +local async, await = a.async, a.await +local channel = a.util.channel local conf = require('telescope.config').values @@ -218,6 +221,36 @@ lsp.workspace_symbols = function(opts) }):find() end +local function get_workspace_symbols_requester(bufnr) + local cancel = function() end + + return async(function(prompt) + local tx, rx = channel.oneshot() + cancel() + _, cancel = vim.lsp.buf_request(bufnr, "workspace/symbol", {query = prompt}, tx) + + local err, _, results_lsp = await(rx()) + assert(not err, err) + + local locations = vim.lsp.util.symbols_to_items(results_lsp or {}, bufnr) or {} + return locations + end) +end + +lsp.dynamic_workspace_symbols = function(opts) + local curr_bufnr = vim.api.nvim_get_current_buf() + + pickers.new(opts, { + prompt_title = 'LSP Dynamic Workspace Symbols', + finder = finders.new_dynamic { + entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), + fn = get_workspace_symbols_requester(curr_bufnr), + }, + previewer = conf.qflist_previewer(opts), + sorter = conf.generic_sorter() + }):find() +end + lsp.diagnostics = function(opts) local locations = utils.diagnostics_to_tbl(opts) diff --git a/lua/telescope/finders.lua b/lua/telescope/finders.lua index d2acd0517c..62c05a10f6 100644 --- a/lua/telescope/finders.lua +++ b/lua/telescope/finders.lua @@ -2,6 +2,8 @@ local Job = require('plenary.job') local make_entry = require('telescope.make_entry') local log = require('telescope.log') +local a = require('plenary.async_lib') +local await = a.await local async_static_finder = require('telescope.finders.async_static_finder') local async_oneshot_finder = require('telescope.finders.async_oneshot_finder') @@ -20,7 +22,6 @@ local _callable_obj = function() return obj end - --[[ ============================================================= JobFinder @@ -108,6 +109,35 @@ function JobFinder:_find(prompt, process_result, process_complete) 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.from_string, + }, self) + + return obj +end + +function DynamicFinder:_find(prompt, process_result, process_complete) + a.scope(function() + local results = await(self.fn(prompt)) + + for _, result in ipairs(results) do + if process_result(self.entry_maker(result)) then return end + end + + process_complete() + end) +end + --- Return a new Finder -- -- Use at your own risk. @@ -185,4 +215,8 @@ finders.new_table = function(t) return async_static_finder(t) end +finders.new_dynamic = function(t) + return DynamicFinder:new(t) +end + return finders From b7d0488db91240bed3afe4b82e5c974836ee060f Mon Sep 17 00:00:00 2001 From: Ben Smith <37027883+smithbm2316@users.noreply.github.com> Date: Tue, 13 Apr 2021 18:59:10 +0000 Subject: [PATCH 09/11] readme: fix broken links and spelling errors (#753) --- README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4e3653bada..9b1bdf6241 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Community driven built-in [pickers](#pickers), [sorters](#sorters) and [previewe - [Gallery](https://github.com/nvim-telescope/telescope.nvim/wiki/Gallery) - [FAQ](#faq) - [Configuration recipes](https://github.com/nvim-telescope/telescope.nvim/wiki/Configuration-Recipes) -- [Contributing](#contribution) +- [Contributing](#contributing) ## Getting Started @@ -113,7 +113,7 @@ This section should help you explore available options to configure and customize your `telescope.nvim`. Unlike most vim plugins, `telescope.nvim` can be customized either by applying -customizations globally or individual pre picker. +customizations globally or individual per picker. - **Global Customization** affecting all pickers can be done through the main `setup()` method (see defaults below) @@ -190,7 +190,7 @@ require('telescope').setup{ EOF ``` - + ### Options affecting Presentation @@ -419,7 +419,7 @@ Built-in functions. Ready to be bound to any key you like. :smile: | `builtin.git_commits` | Lists git commits with diff preview and on enter checkout the commit. | | `builtin.git_bcommits` | Lists buffer's git commits with diff preview and checkouts it out on enter. | | `builtin.git_branches` | Lists all branches with log preview, checkout action (), track action () and rebase action(). | -| `builtin.git_status` | Lists current changes per file with diff preview and add action. (Multiselection still WIP) | +| `builtin.git_status` | Lists current changes per file with diff preview and add action. (Multi-selection still WIP) | ### Treesitter Picker @@ -472,7 +472,7 @@ autocmd User TelescopePreviewerLoaded setlocal wrap | `sorters.fuzzy_with_index_bias` | Used to list stuff with consideration to when the item is added | A `Sorter` is called by the `Picker` on each item returned by the `Finder`. It -return a number, which is equivalent to the "distance" between the current +returns a number, which is equivalent to the "distance" between the current `prompt` and the `entry` returned by a `finder`. @@ -511,15 +511,15 @@ make a theme, check out `lua/telescope/themes.lua`. Telescope user autocmds: -| Event | Description | -|---------------------------------|----------------------------------------------------| -| `User TelescopeFindPre` | Do it before create Telescope all the float window | -| `User TelescopePreviewerLoaded` | Do it after Telescope previewer window create | +| Event | Description | +|---------------------------------|---------------------------------------------------------| +| `User TelescopeFindPre` | Do it before Telescope creates all the floating windows | +| `User TelescopePreviewerLoaded` | Do it after Telescope previewer window is created | ## Extensions -Telescope provides the capabilties to create & register extensions, which improve telescope in a variety of ways. +Telescope provides the capabilities to create & register extensions, which improve telescope in a variety of ways. Some extensions provide integration with external tools, outside of the scope of `builtins`. Others provide performance enhancements by using compiled C and interfacing directly with Lua. @@ -669,9 +669,8 @@ supports tab completions and settings options. ### How to change some defaults in built-in functions? -All options available from the setup function (see [Configuration options]()) and -some other functions can be easily changed in custom pickers or built-in -functions. +All options available from the setup function (see [Configuration options](#customization) +and some other functions can be easily changed in custom pickers or built-in functions. ```lua @@ -685,7 +684,7 @@ nnoremap fg :Telescope live_grep prompt_prefix=🔍 ### How to change Telescope Highlights group? -There are 10 highlights group you can play around with in order to meet your needs: +There are 10 highlight groups you can play around with in order to meet your needs: ```viml highlight TelescopeSelection guifg=#D79921 gui=bold " selected item From c5f0d05835f70f4bce15168d949563ef4c842e4d Mon Sep 17 00:00:00 2001 From: Ben Smith <37027883+smithbm2316@users.noreply.github.com> Date: Wed, 14 Apr 2021 09:31:05 +0000 Subject: [PATCH 10/11] git(action): create and checkout branch (#755) * added git action for creating and checking out a new branch, added basic docstrings for git actions * Added confirmation for creation of new branch, changed default mapping to * Switched back to `` default mapping for now --- lua/telescope/actions/init.lua | 40 ++++++++++++++++++++++++++++++++++ lua/telescope/builtin/git.lua | 5 +++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lua/telescope/actions/init.lua b/lua/telescope/actions/init.lua index de6c58b1b9..bee1f48b38 100644 --- a/lua/telescope/actions/init.lua +++ b/lua/telescope/actions/init.lua @@ -297,6 +297,38 @@ actions.insert_value = function(prompt_bufnr) return entry.value end +--- Create and checkout a new git branch if it doesn't already exist +---@param prompt_bufnr number: The prompt bufnr +actions.git_create_branch = function(prompt_bufnr) + local cwd = action_state.get_current_picker(prompt_bufnr).cwd + local new_branch = action_state.get_current_line() + + if new_branch == "" then + print('Please enter the name of the new branch to create') + else + local confirmation = vim.fn.input(string.format('Create new branch "%s"? [y/n]: ', new_branch)) + if string.len(confirmation) == 0 or string.sub(string.lower(confirmation), 0, 1) ~= 'y' then + print(string.format('Didn\'t create branch "%s"', new_branch)) + return + end + + actions.close(prompt_bufnr) + + local _, ret, stderr = utils.get_os_command_output({ 'git', 'checkout', '-b', new_branch }, cwd) + if ret == 0 then + print(string.format('Switched to a new branch: %s', new_branch)) + else + print(string.format( + 'Error when creating new branch: %s Git returned "%s"', + new_branch, + table.concat(stderr, ' ') + )) + end + end +end + +--- Checkout an existing git branch +---@param prompt_bufnr number: The prompt bufnr actions.git_checkout = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() @@ -313,6 +345,8 @@ actions.git_checkout = function(prompt_bufnr) end end +--- Tell git to track the currently selected remote branch in Telescope +---@param prompt_bufnr number: The prompt bufnr actions.git_track_branch = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() @@ -329,6 +363,8 @@ actions.git_track_branch = function(prompt_bufnr) end end +--- Delete the currently selected branch +---@param prompt_bufnr number: The prompt bufnr actions.git_delete_branch = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() @@ -349,6 +385,8 @@ actions.git_delete_branch = function(prompt_bufnr) end end +--- Rebase to selected git branch +---@param prompt_bufnr number: The prompt bufnr actions.git_rebase_branch = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() @@ -369,6 +407,8 @@ actions.git_rebase_branch = function(prompt_bufnr) end end +--- Stage/unstage selected file +---@param prompt_bufnr number: The prompt bufnr actions.git_staging_toggle = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() diff --git a/lua/telescope/builtin/git.lua b/lua/telescope/builtin/git.lua index b2270ad477..f7c0096b80 100644 --- a/lua/telescope/builtin/git.lua +++ b/lua/telescope/builtin/git.lua @@ -177,11 +177,12 @@ git.branches = function(opts) map('i', '', actions.git_rebase_branch) map('n', '', actions.git_rebase_branch) + map('i', '', actions.git_create_branch) + map('n', '', actions.git_create_branch) + map('i', '', actions.git_delete_branch) map('n', '', actions.git_delete_branch) - map('i', '', false) - map('n', '', false) return true end }):find() From 07d518105cdd6778919181ebf3502cd6f4e9f493 Mon Sep 17 00:00:00 2001 From: James Walmsley Date: Wed, 14 Apr 2021 16:31:22 +0100 Subject: [PATCH 11/11] picker(live_grep): add option to grep only over open files (#666) --- README.md | 8 ++++++++ lua/telescope/builtin/files.lua | 34 ++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9b1bdf6241..1a6a5e2edd 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,14 @@ Built-in functions. Ready to be bound to any key you like. :smile: | `builtin.live_grep` | Searches in current directory files. (respecting .gitignore) | | `builtin.file_browser` | Ivy-like file explorer. Creates files by typing in filename and pressing ``. Press `` without prompt for more info | +#### Options for builtin.live_grep + +| Keys | Description | Options | +|------------------------|-------------------------------------------------------|--------------| +| `grep_open_files` | Restrict live_grep to currently open files. | boolean | +| `search_dirs` | List of directories to search in. | list | + + ### Vim Pickers | Functions | Description | diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index 23ceaa7db7..a30f693719 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -13,6 +13,7 @@ local Path = require('plenary.path') local os_sep = Path.path.sep local flatten = vim.tbl_flatten +local filter = vim.tbl_filter local files = {} @@ -29,10 +30,28 @@ end -- Special keys: -- opts.search_dirs -- list of directory to search in +-- opts.grep_open_files -- boolean to restrict search to open files files.live_grep = function(opts) local vimgrep_arguments = opts.vimgrep_arguments or conf.vimgrep_arguments local search_dirs = opts.search_dirs - opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) + local grep_open_files = opts.grep_open_files + opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd() + + local filelist = {} + + if grep_open_files then + local bufnrs = filter(function(b) + if 1 ~= vim.fn.buflisted(b) then return false end + return true + end, vim.api.nvim_list_bufs()) + if not next(bufnrs) then return end + + local tele_path = require'telescope.path' + for _, bufnr in ipairs(bufnrs) do + local file = vim.api.nvim_buf_get_name(bufnr) + table.insert(filelist, tele_path.make_relative(file, opts.cwd)) + end + end if search_dirs then for i, path in ipairs(search_dirs) do @@ -49,15 +68,19 @@ files.live_grep = function(opts) prompt = escape_chars(prompt) - local args = flatten { vimgrep_arguments, prompt } + local search_list = {} if search_dirs then - table.insert(args, search_dirs) + table.insert(search_list, search_dirs) elseif os_sep == '\\' then - table.insert(args, '.') + table.insert(search_list, '.') end - return args + if grep_open_files then + search_list = filelist + end + + return flatten { vimgrep_arguments, prompt, search_list } end, opts.entry_maker or make_entry.gen_from_vimgrep(opts), opts.max_results, @@ -72,6 +95,7 @@ files.live_grep = function(opts) }):find() end + -- Special keys: -- opts.search -- the string to search. -- opts.search_dirs -- list of directory to search in