diff --git a/bin/benchmarks/scanner.lua b/bin/benchmarks/scanner.lua index 32c87f4b..fc35e7c1 100755 --- a/bin/benchmarks/scanner.lua +++ b/bin/benchmarks/scanner.lua @@ -20,7 +20,18 @@ benchmark({ setup = function(config) collectgarbage() - local scanner = require(config.source) + local scanner = nil + if config.stub then + -- For scanners that otherwise depend on Neovim for a list of candidates. + config.stub() + end + if type(config.source) == 'string' then + scanner = require(config.source) + elseif type(config.source) == 'function' then + scanner = config.source() + else + error('`source` should be a string or function') + end if scanner.name == 'watchman' then -- We don't have a real JSON parser here, so we fake it. local fallback = '/opt/homebrew/var/run/watchman/wincent-state/sock' @@ -30,10 +41,6 @@ benchmark({ local name = output:match('"sockname":%s*"([^"]+)"') or fallback scanner.set_sockname(name) end - if config.stub then - -- For scanners that otherwise depend on Neovim for a list of candidates. - config.stub(scanner) - end return scanner end, diff --git a/data/wincent/commandt/benchmark/configs/scanner.lua b/data/wincent/commandt/benchmark/configs/scanner.lua index 8505a3fb..55a9cabb 100644 --- a/data/wincent/commandt/benchmark/configs/scanner.lua +++ b/data/wincent/commandt/benchmark/configs/scanner.lua @@ -4,7 +4,15 @@ return { variants = { { name = 'buffer', - source = 'wincent.commandt.private.scanners.buffer', + source = function() + local command = require('wincent.commandt').default_options().finders.buffer.candidates() + local scanner = require('wincent.commandt.private.scanners.list').scanner + return { + scanner = function() + return scanner(command) + end, + } + end, times = times * 100, -- Scanner is too fast (misleadingly fast). skip_in_ci = false, stub = function() @@ -203,19 +211,65 @@ return { }, { name = 'find', - source = 'wincent.commandt.private.scanners.find', + source = function() + local command = require('wincent.commandt').default_options().finders.find.command('') + local scanner = require('wincent.commandt.private.scanners.command').scanner + return { + scanner = function() + return scanner(command) + end, + } + end, + stub = function() + assert(_G.vim == nil) + _G.vim = { + startswith = function() + return false + end, + } + end, + unstub = function() + _G.vim = nil + end, times = times, skip_in_ci = false, }, { name = 'git', - source = 'wincent.commandt.private.scanners.git', + source = function() + local command = require('wincent.commandt').default_options().finders.git.command('', {}) + local scanner = require('wincent.commandt.private.scanners.command').scanner + return { + scanner = function() + return scanner(command) + end, + } + end, times = times, skip_in_ci = false, }, { name = 'rg', - source = 'wincent.commandt.private.scanners.rg', + source = function() + local command = require('wincent.commandt').default_options().finders.rg.command('') + local scanner = require('wincent.commandt.private.scanners.command').scanner + return { + scanner = function() + return scanner(command) + end, + } + end, + stub = function() + assert(_G.vim == nil) + _G.vim = { + startswith = function() + return false + end, + } + end, + unstub = function() + _G.vim = nil + end, times = times, skip_in_ci = true, }, diff --git a/doc/command-t.txt b/doc/command-t.txt index 9a21f451..a4990564 100644 --- a/doc/command-t.txt +++ b/doc/command-t.txt @@ -573,7 +573,8 @@ HISTORY *command-t-history* main (not yet released) ~ -- ... +- feat: add back |:CommandTLine| finder + (https://github.com/wincent/command-t/pull/408). 6.0.0-a.4 (5 September 2022) ~ diff --git a/lua/wincent/commandt/init.lua b/lua/wincent/commandt/init.lua index e70f9084..5cdb0987 100644 --- a/lua/wincent/commandt/init.lua +++ b/lua/wincent/commandt/init.lua @@ -10,9 +10,179 @@ local merge = require('wincent.commandt.private.merge') local commandt = {} +local open = function(buffer, command) + buffer = vim.fn.fnameescape(buffer) + local is_visible = require('wincent.commandt.private.buffer_visible')(buffer) + if is_visible then + -- In order to be useful, `:sbuffer` needs `vim.o.switchbuf = 'usetab'`. + vim.cmd('sbuffer ' .. buffer) + else + vim.cmd(command .. ' ' .. buffer) + end +end + +local help_opened = false + local default_options = { always_show_dot_files = false, - finders = {}, + finders = { + -- Returns the list of paths currently loaded into buffers. + buffer = { + candidates = function() + -- TODO: don't include unlisted buffers unless user wants them (need some way + -- for them to signal that) + local handles = vim.api.nvim_list_bufs() + local paths = {} + for _, handle in ipairs(handles) do + if vim.api.nvim_buf_is_loaded(handle) then + local name = vim.api.nvim_buf_get_name(handle) + if name ~= '' then + local relative = vim.fn.fnamemodify(name, ':~:.') + table.insert(paths, relative) + end + end + end + return paths + end, + }, + find = { + command = function(directory) + if vim.startswith(directory, './') then + directory = directory:sub(3, -1) + end + if directory ~= '' and directory ~= '.' then + directory = vim.fn.shellescape(directory) + end + local drop = 0 + if directory == '' or directory == '.' then + -- Drop 2 characters because `find` will prefix every result with "./", + -- making it look like a dotfile. + directory = '.' + drop = 2 + -- TODO: decide what to do if somebody passes '..' or similar, because that + -- will also make the results get filtered out as though they were dotfiles. + -- I may end up needing to do some fancy, separate micromanagement of + -- prefixes and let the matcher operate on paths without prefixes. + end + -- TODO: support max depth, dot directory filter etc + local command = 'find -L ' .. directory .. ' -type f -print0 2> /dev/null' + return command, drop + end, + fallback = true, + }, + git = { + command = function(directory, options) + if directory ~= '' then + directory = vim.fn.shellescape(directory) + end + local command = 'git ls-files --exclude-standard --cached -z' + if options.submodules then + command = command .. ' --recurse-submodules' + elseif options.untracked then + command = command .. ' --others' + end + if directory ~= '' then + command = command .. ' -- ' .. directory + end + command = command .. ' 2> /dev/null' + return command + end, + fallback = true, + }, + help = { + candidates = function() + -- Neovim doesn't provide an easy way to get a list of all help tags. + -- `tagfiles()` only shows the tagfiles for the current buffer, so you need + -- to already be in a buffer of `'buftype'` `help` for that to work. + -- Likewise, `taglist()` only shows tags that apply to the current file + -- type, and `:tag` has the same restriction. + -- + -- So, we look for "doc/tags" files at every location in the `'runtimepath'` + -- and try to manually parse it. + local helptags = {} + local tagfiles = vim.api.nvim_get_runtime_file('doc/tags', true) + for _, tagfile in ipairs(tagfiles) do + if vim.fn.filereadable(tagfile) then + for _, tag in ipairs(vim.fn.readfile(tagfile)) do + local _, _, tag_text = tag:find('^%s*(%S+)%s+') + if tag_text ~= nil then + table.insert(helptags, tag_text) + end + end + end + end + -- TODO: memoize this? (ie. add `memoize = true`)? + return helptags + end, + open = function(item, kind) + local command = 'help' + if kind == 'split' then + -- Split is the default, so for this finder, we abuse "split" mode to do + -- the opposite of the default, using tricks noted in `:help help-curwin`. + -- + -- See also: https://github.com/vim/vim/issues/7534 + if not help_opened then + vim.cmd([[ + silent noautocmd keepalt help + silent noautocmd keepalt helpclose + ]]) + help_opened = true + end + if vim.fn.empty(vim.fn.getcompletion(item, 'help')) == 0 then + vim.cmd('silent noautocmd keepalt edit ' .. vim.o.helpfile) + end + elseif kind == 'tabedit' then + command = 'tab help' + elseif kind == 'vsplit' then + command = 'vertical help' + end + + -- E434 "Can't find tag pattern" is innocuous, so swallow it. For more + -- context, see: https://github.com/autozimu/LanguageClient-neovim/pull/731 + vim.cmd('try | ' .. command .. ' ' .. item .. ' | catch /E434/ | endtry') + end, + }, + line = { + candidates = function() + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local result = {} + for i, line in ipairs(lines) do + -- Skip blank/empty lines. + if not line:match('^%s*$') then + table.insert(result, vim.trim(line) .. ':' .. tostring(i)) + end + end + return result + end, + open = function(item) + -- Extract line number from (eg) "some line contents:100". + local suffix = string.find(item, '%d+$') + local index = tonumber(item:sub(suffix)) + vim.api.nvim_win_set_cursor(0, { index, 0 }) + end, + }, + rg = { + command = function(directory) + if vim.startswith(directory, './') then + directory = directory:sub(3, -1) + end + if directory ~= '' and directory ~= '.' then + directory = vim.fn.shellescape(directory) + end + local drop = 0 + if directory == '.' then + drop = 2 + end + local command = 'rg --files --null' + if #directory > 0 then + command = command .. ' ' .. directory + end + command = command .. ' 2> /dev/null' + return command, drop + end, + fallback = true, + }, + }, height = 15, ignore_case = nil, -- If nil, will infer from Neovim's `'ignorecase'`. @@ -69,9 +239,7 @@ local default_options = { never_show_dot_files = false, order = 'forward', -- 'forward', 'reverse'. position = 'center', -- 'bottom', 'center', 'top'. - open = function(item, kind) - commandt.open(item, kind) - end, + open = open, scanners = { git = { submodules = true, @@ -91,14 +259,6 @@ local allowed_options = concat(keys(default_options), { 'threads', }) -commandt.buffer_finder = function() - -- TODO: refactor to avoid duplication - local ui = require('wincent.commandt.private.ui') - local options = commandt.options() - local finder = require('wincent.commandt.private.finders.buffer')(options) - ui.show(finder, merge(options, { name = 'buffer' })) -end - commandt.default_options = function() return copy(default_options) end @@ -114,53 +274,39 @@ commandt.file_finder = function(directory) ui.show(finder, merge(options, { name = 'file' })) end -commandt.find_finder = function(directory) - directory = vim.trim(directory) - local ui = require('wincent.commandt.private.ui') - local options = commandt.options() - local finder = require('wincent.commandt.private.finders.find')(directory, options) - ui.show(finder, merge(options, { name = 'find' })) -end - commandt.finder = function(name, directory) local options = commandt.options() - if options.finders[name] == nil then + local config = options.finders[name] + if config == nil then error('commandt.finder(): no finder registered with name ' .. tostring(name)) end - directory = vim.trim(directory) - local finder = require('wincent.commandt.private.finders.command')(directory, options, name) + if directory ~= nil then + directory = vim.trim(directory) + end + local finder = nil + options.open = function(item, kind) + if config.open then + config.open(item, kind) + else + commandt.open(item, kind) + end + end + if config.candidates then + finder = require('wincent.commandt.private.finders.list')(directory, config.candidates, options) + else + finder = require('wincent.commandt.private.finders.command')(directory, config.command, options) + end + if config.fallback then + finder.fallback = require('wincent.commandt.private.finders.fallback')(finder, directory, options) + end local ui = require('wincent.commandt.private.ui') ui.show(finder, merge(options, { name = name })) end -commandt.git_finder = function(directory) - directory = vim.trim(directory) - local ui = require('wincent.commandt.private.ui') - local options = commandt.options() - local finder = require('wincent.commandt.private.finders.git')(directory, options) - ui.show(finder, merge(options, { name = 'git' })) -end - -commandt.help_finder = function() - -- TODO: refactor to avoid duplication - local ui = require('wincent.commandt.private.ui') - local options = commandt.options() - local finder = require('wincent.commandt.private.finders.help')(options) - ui.show(finder, merge(options, { name = 'help' })) -end - -- "Smart" open that will switch to an already open window containing the -- specified `buffer`, if one exists; otherwise, it will open a new window using -- `command` (which should be one of `edit`, `tabedit`, `split`, or `vsplit`). -commandt.open = function(buffer, command) - local is_visible = require('wincent.commandt.private.buffer_visible')(buffer) - if is_visible then - -- In order to be useful, `:sbuffer` needs `vim.o.switchbuf = 'usetab'`. - vim.cmd('sbuffer ' .. buffer) - else - vim.cmd(command .. ' ' .. buffer) - end -end +commandt.open = open local _options = copy(default_options) @@ -168,14 +314,6 @@ commandt.options = function() return copy(_options) end -commandt.rg_finder = function(directory) - directory = vim.trim(directory) - local ui = require('wincent.commandt.private.ui') - local options = commandt.options() - local finder = require('wincent.commandt.private.finders.rg')(directory, options) - ui.show(finder, merge(options, { name = 'rg' })) -end - commandt.setup = function(options) local errors = {} @@ -239,20 +377,29 @@ commandt.setup = function(options) reset(option, actual, defaults) end end - local require_boolean = function(option, actual, defaults) + local optional_function_or_string = function(option, actual, defaults) actual = actual ~= nil and actual or _options defaults = defaults ~= nil and defaults or default_options - if type(pick(option, actual)) ~= 'boolean' then - report(string.format('`%s` must be true or false', option)) + local value = pick(option, actual) + if value ~= nil and type(value) ~= 'function' and type(value) ~= 'string' then + report(string.format('`%s` must be a function or string or nil', option)) reset(option, actual, defaults) end end - local require_function_or_string = function(option, actual, defaults) + local optional_function_or_table = function(option, actual, defaults) actual = actual ~= nil and actual or _options defaults = defaults ~= nil and defaults or default_options local value = pick(option, actual) - if type(value) ~= 'function' and type(value) ~= 'string' then - report(string.format('`%s` must be a function or string', option)) + if value ~= nil and type(value) ~= 'function' and type(value) ~= 'table' then + report(string.format('`%s` must be a function or table or nil', option)) + reset(option, actual, defaults) + end + end + local require_boolean = function(option, actual, defaults) + actual = actual ~= nil and actual or _options + defaults = defaults ~= nil and defaults or default_options + if type(pick(option, actual)) ~= 'boolean' then + report(string.format('`%s` must be true or false', option)) reset(option, actual, defaults) end end @@ -336,7 +483,14 @@ commandt.setup = function(options) require_boolean('smart_case') for name, finder in pairs(options.finders or {}) do - require_function_or_string('finders.' .. name .. '.command', nil, { + optional_function_or_table('finders.' .. name .. '.candidates', nil, { + finders = { + [name] = { + command = 'true', + }, + }, + }) + optional_function_or_string('finders.' .. name .. '.command', nil, { finders = { [name] = { command = 'true', @@ -348,8 +502,9 @@ commandt.setup = function(options) [name] = {}, }, }) + -- TODO: complain if passed `candidates` _and_ `command`. for k, _ in pairs(finder) do - if not contains({ 'command', 'open' }, k) then + if not contains({ 'candidates', 'command', 'open' }, k) then report(string.format('unrecognized option in `finders.%s`: %s)', name, k)) end end diff --git a/lua/wincent/commandt/private/finders/command.lua b/lua/wincent/commandt/private/finders/command.lua index 74c2141b..4bc0b7e5 100644 --- a/lua/wincent/commandt/private/finders/command.lua +++ b/lua/wincent/commandt/private/finders/command.lua @@ -3,12 +3,14 @@ local ffi = require('ffi') -return function(directory, options, name) +return function(directory, command, options) local lib = require('wincent.commandt.private.lib') - local command = options.finders[name].command - command = type(command) == 'string' and command or command(directory) + local drop = 0 + if type(command) == 'function' then + command, drop = command(directory, options) + end local finder = {} - finder.scanner = require('wincent.commandt.private.scanners.command').scanner(command) + finder.scanner = require('wincent.commandt.private.scanners.command').scanner(command, drop) finder.matcher = lib.matcher_new(finder.scanner, options) finder.run = function(query) local results = lib.matcher_run(finder.matcher, query) @@ -19,9 +21,6 @@ return function(directory, options, name) end return strings end - finder.open = options.finders[name].open - or function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end + finder.open = options.open return finder end diff --git a/lua/wincent/commandt/private/finders/file.lua b/lua/wincent/commandt/private/finders/file.lua index 10c78791..f5512c20 100644 --- a/lua/wincent/commandt/private/finders/file.lua +++ b/lua/wincent/commandt/private/finders/file.lua @@ -19,8 +19,6 @@ return function(directory, options) end return strings end - finder.open = function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end + finder.open = options.open return finder end diff --git a/lua/wincent/commandt/private/finders/find.lua b/lua/wincent/commandt/private/finders/find.lua deleted file mode 100644 index 8be25c87..00000000 --- a/lua/wincent/commandt/private/finders/find.lua +++ /dev/null @@ -1,32 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local ffi = require('ffi') - --- TODO: remember cached directories -return function(directory, options) - local finder = {} - finder.fallback = require('wincent.commandt.private.finders.fallback')(finder, directory, options) - if vim.startswith(directory, './') then - directory = directory:sub(3, -1) - end - if directory ~= '' and directory ~= '.' then - directory = vim.fn.shellescape(directory) - end - local lib = require('wincent.commandt.private.lib') - finder.scanner = require('wincent.commandt.private.scanners.find').scanner(directory) - finder.matcher = lib.matcher_new(finder.scanner, options) - finder.run = function(query) - local results = lib.matcher_run(finder.matcher, query) - local strings = {} - for i = 0, results.count - 1 do - local str = results.matches[i] - table.insert(strings, ffi.string(str.contents, str.length)) - end - return strings - end - finder.open = function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end - return finder -end diff --git a/lua/wincent/commandt/private/finders/git.lua b/lua/wincent/commandt/private/finders/git.lua deleted file mode 100644 index 7abd102d..00000000 --- a/lua/wincent/commandt/private/finders/git.lua +++ /dev/null @@ -1,31 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local ffi = require('ffi') - --- TODO: eventually this will become a pure-C finder; for now we're just demoing --- the `command` scanner --- TODO: remember cached directories -return function(directory, options) - local finder = {} - finder.fallback = require('wincent.commandt.private.finders.fallback')(finder, directory, options) - if directory ~= '' then - directory = vim.fn.shellescape(directory) - end - local lib = require('wincent.commandt.private.lib') - finder.scanner = require('wincent.commandt.private.scanners.git').scanner(directory, options.scanners.git) - finder.matcher = lib.matcher_new(finder.scanner, options) - finder.run = function(query) - local results = lib.matcher_run(finder.matcher, query) - local strings = {} - for i = 0, results.count - 1 do - local str = results.matches[i] - table.insert(strings, ffi.string(str.contents, str.length)) - end - return strings - end - finder.open = function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end - return finder -end diff --git a/lua/wincent/commandt/private/finders/help.lua b/lua/wincent/commandt/private/finders/help.lua deleted file mode 100644 index 3e799f28..00000000 --- a/lua/wincent/commandt/private/finders/help.lua +++ /dev/null @@ -1,50 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local ffi = require('ffi') - -local help_opened = false - -return function(options) - local lib = require('wincent.commandt.private.lib') - local finder = {} - finder.scanner = require('wincent.commandt.private.scanners.help').scanner() - finder.matcher = lib.matcher_new(finder.scanner, options) - finder.run = function(query) - local results = lib.matcher_run(finder.matcher, query) - local strings = {} - for i = 0, results.count - 1 do - local str = results.matches[i] - table.insert(strings, ffi.string(str.contents, str.length)) - end - return strings - end - finder.open = function(item, kind) - local command = 'help' - if kind == 'split' then - -- Split is the default, so for this finder, we abuse "split" mode to do - -- the opposite of the default, using tricks noted in `:help help-curwin`. - -- - -- See also: https://github.com/vim/vim/issues/7534 - if not help_opened then - vim.cmd([[ - silent noautocmd keepalt help - silent noautocmd keepalt helpclose - ]]) - help_opened = true - end - if vim.fn.empty(vim.fn.getcompletion(item, 'help')) == 0 then - vim.cmd('silent noautocmd keepalt edit ' .. vim.o.helpfile) - end - elseif kind == 'tabedit' then - command = 'tab help' - elseif kind == 'vsplit' then - command = 'vertical help' - end - - -- E434 "Can't find tag pattern" is innocuous, so swallow it. For more - -- context, see: https://github.com/autozimu/LanguageClient-neovim/pull/731 - vim.cmd('try | ' .. command .. ' ' .. item .. ' | catch /E434/ | endtry') - end - return finder -end diff --git a/lua/wincent/commandt/private/finders/buffer.lua b/lua/wincent/commandt/private/finders/list.lua similarity index 57% rename from lua/wincent/commandt/private/finders/buffer.lua rename to lua/wincent/commandt/private/finders/list.lua index e4ac7318..2a33d2fd 100644 --- a/lua/wincent/commandt/private/finders/buffer.lua +++ b/lua/wincent/commandt/private/finders/list.lua @@ -3,10 +3,16 @@ local ffi = require('ffi') -return function(options) +return function(directory, candidates, options) local lib = require('wincent.commandt.private.lib') local finder = {} - finder.scanner = require('wincent.commandt.private.scanners.buffer').scanner() + if type(candidates) == 'function' then + finder.scanner = require('wincent.commandt.private.scanners.list').scanner(candidates(directory)) + elseif type(candidates) == 'table' then + finder.scanner = require('wincent.commandt.private.scanners.list').scanner(candidates) + else + error('wincent.commandt.private.finders.list() expected function or table') + end finder.matcher = lib.matcher_new(finder.scanner, options) finder.run = function(query) local results = lib.matcher_run(finder.matcher, query) @@ -17,8 +23,6 @@ return function(options) end return strings end - finder.open = function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end + finder.open = options.open return finder end diff --git a/lua/wincent/commandt/private/finders/rg.lua b/lua/wincent/commandt/private/finders/rg.lua deleted file mode 100644 index a40b4565..00000000 --- a/lua/wincent/commandt/private/finders/rg.lua +++ /dev/null @@ -1,32 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local ffi = require('ffi') - --- TODO: remember cached directories -return function(directory, options) - local finder = {} - finder.fallback = require('wincent.commandt.private.finders.fallback')(finder, directory, options) - if vim.startswith(directory, './') then - directory = directory:sub(3, -1) - end - if directory ~= '' and directory ~= '.' then - directory = vim.fn.shellescape(directory) - end - local lib = require('wincent.commandt.private.lib') - finder.scanner = require('wincent.commandt.private.scanners.rg').scanner(directory) - finder.matcher = lib.matcher_new(finder.scanner, options) - finder.run = function(query) - local results = lib.matcher_run(finder.matcher, query) - local strings = {} - for i = 0, results.count - 1 do - local str = results.matches[i] - table.insert(strings, ffi.string(str.contents, str.length)) - end - return strings - end - finder.open = function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end - return finder -end diff --git a/lua/wincent/commandt/private/finders/watchman.lua b/lua/wincent/commandt/private/finders/watchman.lua index 313ac6e4..96f42502 100644 --- a/lua/wincent/commandt/private/finders/watchman.lua +++ b/lua/wincent/commandt/private/finders/watchman.lua @@ -20,8 +20,6 @@ return function(directory, options) end return strings end - finder.open = function(item, kind) - options.open(vim.fn.fnameescape(item), kind) - end + finder.open = options.open return finder end diff --git a/lua/wincent/commandt/private/scanners/buffer.lua b/lua/wincent/commandt/private/scanners/buffer.lua deleted file mode 100644 index b5f74158..00000000 --- a/lua/wincent/commandt/private/scanners/buffer.lua +++ /dev/null @@ -1,30 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2021-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local buffer = {} - --- Returns the list of paths currently loaded into buffers. -local get = function() - -- TODO: don't include unlisted buffers unless user wants them (need some way - -- for them to signal that) - local handles = vim.api.nvim_list_bufs() - local paths = {} - for _, handle in ipairs(handles) do - if vim.api.nvim_buf_is_loaded(handle) then - local name = vim.api.nvim_buf_get_name(handle) - if name ~= '' then - local relative = vim.fn.fnamemodify(name, ':~:.') - table.insert(paths, relative) - end - end - end - return paths -end - -buffer.scanner = function() - local lib = require('wincent.commandt.private.lib') - local scanner = lib.scanner_new_copy(get()) - return scanner -end - -return buffer diff --git a/lua/wincent/commandt/private/scanners/command.lua b/lua/wincent/commandt/private/scanners/command.lua index 23136081..eaad098b 100644 --- a/lua/wincent/commandt/private/scanners/command.lua +++ b/lua/wincent/commandt/private/scanners/command.lua @@ -3,9 +3,9 @@ local command = {} -command.scanner = function(user_command) +command.scanner = function(user_command, drop) local lib = require('wincent.commandt.private.lib') - local scanner = lib.scanner_new_command(user_command) + local scanner = lib.scanner_new_command(user_command, drop) return scanner end diff --git a/lua/wincent/commandt/private/scanners/find.lua b/lua/wincent/commandt/private/scanners/find.lua deleted file mode 100644 index 48c8a373..00000000 --- a/lua/wincent/commandt/private/scanners/find.lua +++ /dev/null @@ -1,27 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local find = {} - --- Note: because `directory` is going to be interpolated into a command --- invocation, it should be shell escaped before calling this scanner. -find.scanner = function(directory) - local drop = 0 - if directory == '' or directory == '.' then - -- Drop 2 characters because `find` will prefix every result with "./", - -- making it look like a dotfile. - directory = '.' - drop = 2 - -- TODO: decide what to do if somebody passes '..' or similar, because that - -- will also make the results get filtered out as though they were dotfiles. - -- I may end up needing to do some fancy, separate micromanagement of - -- prefixes and let the matcher operate on paths without prefixes. - end - local lib = require('wincent.commandt.private.lib') - -- TODO: support max depth, dot directory filter etc - local command = 'find -L ' .. directory .. ' -type f -print0 2> /dev/null' - local scanner = lib.scanner_new_command(command, drop) - return scanner -end - -return find diff --git a/lua/wincent/commandt/private/scanners/git.lua b/lua/wincent/commandt/private/scanners/git.lua deleted file mode 100644 index 2ef30b5e..00000000 --- a/lua/wincent/commandt/private/scanners/git.lua +++ /dev/null @@ -1,23 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local git = {} - -git.scanner = function(directory, options) - options = options or {} - local lib = require('wincent.commandt.private.lib') - local command = 'git ls-files --exclude-standard --cached -z' - if options.submodules then - command = command .. ' --recurse-submodules' - elseif options.untracked then - command = command .. ' --others' - end - if directory ~= '' then - command = command .. ' -- ' .. directory - end - command = command .. ' 2> /dev/null' - local scanner = lib.scanner_new_command(command) - return scanner -end - -return git diff --git a/lua/wincent/commandt/private/scanners/help.lua b/lua/wincent/commandt/private/scanners/help.lua deleted file mode 100644 index c29cef8e..00000000 --- a/lua/wincent/commandt/private/scanners/help.lua +++ /dev/null @@ -1,48 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local help = {} - -local helptags = nil - --- Returns the list of helptags that can be opened with `:help {tag}` --- --- Will return a cached value unless `force` is truthy (or there is no cached --- value). -local get = function(force) - if helptags == nil or force then - -- Neovim doesn't provide an easy way to get a list of all help tags. - -- `tagfiles()` only shows the tagfiles for the current buffer, so you need - -- to already be in a buffer of `'buftype'` `help` for that to work. - -- Likewise, `taglist()` only shows tags that apply to the current file - -- type, and `:tag` has the same restriction. - -- - -- So, we look for "doc/tags" files at every location in the `'runtimepath'` - -- and try to manually parse it. - helptags = {} - - local tagfiles = vim.api.nvim_get_runtime_file('doc/tags', true) - - for _, tagfile in ipairs(tagfiles) do - if vim.fn.filereadable(tagfile) then - for _, tag in ipairs(vim.fn.readfile(tagfile)) do - local _, _, tag_text = tag:find('^%s*(%S+)%s+') - if tag_text ~= nil then - table.insert(helptags, tag_text) - end - end - end - end - end - - return helptags -end - -help.scanner = function() - local paths = get() - local lib = require('wincent.commandt.private.lib') - local scanner = lib.scanner_new_copy(paths) - return scanner -end - -return help diff --git a/lua/wincent/commandt/private/scanners/list.lua b/lua/wincent/commandt/private/scanners/list.lua new file mode 100644 index 00000000..5515def1 --- /dev/null +++ b/lua/wincent/commandt/private/scanners/list.lua @@ -0,0 +1,13 @@ +-- SPDX-FileCopyrightText: Copyright 2021-present Greg Hurrell and contributors. +-- SPDX-License-Identifier: BSD-2-Clause + +local list = {} + +-- Basic scanner that returns the supplied list of candidates. +list.scanner = function(candidates) + local lib = require('wincent.commandt.private.lib') + local scanner = lib.scanner_new_copy(candidates) + return scanner +end + +return list diff --git a/lua/wincent/commandt/private/scanners/rg.lua b/lua/wincent/commandt/private/scanners/rg.lua deleted file mode 100644 index 5e23c074..00000000 --- a/lua/wincent/commandt/private/scanners/rg.lua +++ /dev/null @@ -1,23 +0,0 @@ --- SPDX-FileCopyrightText: Copyright 2022-present Greg Hurrell and contributors. --- SPDX-License-Identifier: BSD-2-Clause - -local rg = {} - --- Note: because `directory` is going to be interpolated into a command --- invocation, it should be shell escaped before calling this scanner. -rg.scanner = function(directory) - local drop = 0 - if directory == '.' then - drop = 2 - end - local lib = require('wincent.commandt.private.lib') - local command = 'rg --files --null' - if #directory > 0 then - command = command .. ' ' .. directory - end - command = command .. ' 2> /dev/null' - local scanner = lib.scanner_new_command(command, drop) - return scanner -end - -return rg diff --git a/plugin/command-t.lua b/plugin/command-t.lua index 627ce3a0..6060b943 100644 --- a/plugin/command-t.lua +++ b/plugin/command-t.lua @@ -12,6 +12,7 @@ if vim.g.CommandTPreferredImplementation == 'lua' then CommandTFind = 'CommandTFind' CommandTGit = 'CommandTGit' CommandTHelp = 'CommandTHelp' + CommandTLine = 'CommandTLine' CommandTRipgrep = 'CommandTRipgrep' CommandTWatchman = 'CommandTWatchman' @@ -20,6 +21,7 @@ if vim.g.CommandTPreferredImplementation == 'lua' then vim.keymap.set('n', '(CommandTFind)', ':CommandTFind', { silent = true }) vim.keymap.set('n', '(CommandTGit)', ':CommandTGit', { silent = true }) vim.keymap.set('n', '(CommandTHelp)', ':CommandTHelp', { silent = true }) + vim.keymap.set('n', '(CommandTLine)', ':CommandTLine', { silent = true }) vim.keymap.set('n', '(CommandTRipgrep)', ':CommandTRipgrep', { silent = true }) vim.keymap.set('n', '(CommandTWatchman)', ':CommandTWatchman', { silent = true }) else @@ -28,6 +30,7 @@ else CommandTFind = 'KommandTFind' CommandTGit = 'KommandTGit' CommandTHelp = 'KommandTHelp' + CommandTLine = 'KommandTLine' CommandTRipgrep = 'KommandTRipgrep' CommandTWatchman = 'KommandTWatchman' end @@ -40,29 +43,39 @@ end, { }) vim.api.nvim_create_user_command(CommandTBuffer, function() - require('wincent.commandt').buffer_finder() -end, {}) + require('wincent.commandt').finder('buffer') +end, { + nargs = 0, +}) vim.api.nvim_create_user_command(CommandTFind, function(command) - require('wincent.commandt').find_finder(command.args) + require('wincent.commandt').finder('find', command.args) end, { complete = 'dir', nargs = '?', }) vim.api.nvim_create_user_command(CommandTGit, function(command) - require('wincent.commandt').git_finder(command.args) + require('wincent.commandt').finder('git', command.args) end, { complete = 'dir', nargs = '?', }) vim.api.nvim_create_user_command(CommandTHelp, function() - require('wincent.commandt').help_finder() -end, {}) + require('wincent.commandt').finder('help') +end, { + nargs = 0, +}) + +vim.api.nvim_create_user_command(CommandTLine, function() + require('wincent.commandt').finder('line') +end, { + nargs = 0, +}) vim.api.nvim_create_user_command(CommandTRipgrep, function(command) - require('wincent.commandt').rg_finder(command.args) + require('wincent.commandt').finder('rg', command.args) end, { complete = 'dir', nargs = '?',