diff --git a/README.md b/README.md index d5c29d1..470c7b8 100644 --- a/README.md +++ b/README.md @@ -70,14 +70,6 @@ use { 'ibhagwan/fzf-lua', 'vijaymarupudi/nvim-fzf' } } ``` ->**There's currently an issue with [nvim-fzf and some neovim 0.5 ->releases](https://github.com/vijaymarupudi/nvim-fzf/pull/15), I already fixed ->it my fork and waiting for the PR to be merged, for now you can replace ->`vijaymarupudi/nvim-fzf` with `ibhagwan/nvim-fzf`** ->```lua ->use { 'ibhagwan/fzf-lua', requires = { 'ibhagwan/nvim-fzf' } } ->``` - > **Note** if you already have fzf installed you do not need to install `fzf` > or `fzf.vim`, however if you do not have it installed, **you only need** fzf > which can be installed with (fzf.vim is not a requirement nor conflict): @@ -129,6 +121,21 @@ nnoremap lua require('fzf-lua').files() |`man_pages`|man pages| |`colorschemes`|color schemes| +## LSP Commands + +| Command | List | +| --- | --- | +|`lsp_references`|References| +|`lsp_definitions`|Definitions| +|`lsp_declarations`|Declarations| +|`lsp_typedefs`|Type Definitions| +|`lsp_implementations`|Implementations| +|`lsp_document_symbols`|Document Symbols| +|`lsp_workspace_symbols`|Workspace Symbols| +|`lsp_code_actions`|Code Actions| +|`lsp_document_diagnostics`|Document Diagnostics| +|`lsp_workspace_diagnostics`|Workspace Diagnostics| + ## Customization I tried to make it as customizable as possible, if you find you need to change something that isn’t below, open an issue and I’ll do my best to add it. @@ -147,8 +154,8 @@ require'fzf-lua'.setup { win_col = 0.50, -- window col position (0=left, 1=right) -- win_border = false, -- window border? or borderchars? win_border = { '╭', '─', '╮', '│', '╯', '─', '╰', '│' }, - fzf_args = '', -- adv: fzf extra args, empty unless adv fzf_layout = 'reverse', -- fzf '--layout=' + fzf_args = '', -- adv: fzf extra args, empty unless adv preview_cmd = '', -- 'head -n $FZF_PREVIEW_LINES', preview_border = 'border', -- border|noborder preview_wrap = 'nowrap', -- wrap|nowrap @@ -174,10 +181,27 @@ require'fzf-lua'.setup { ["ctrl-y"] = function(selected) print(selected[2]) end, } }, + git = { + prompt = 'GitFiles❯ ', + cmd = 'git ls-files --exclude-standard', + git_icons = true, -- show git icons? + file_icons = true, -- show file icons? + color_icons = true, -- colorize file|git icons + icons = { + ["M"] = { icon = "M", color = "yellow" }, + ["D"] = { icon = "D", color = "red" }, + ["A"] = { icon = "A", color = "green" }, + ["?"] = { icon = "?", color = "magenta" }, + -- ["M"] = { icon = "★", color = "red" }, + -- ["D"] = { icon = "✗", color = "red" }, + -- ["A"] = { icon = "+", color = "green" }, + }, + }, grep = { prompt = 'Rg❯ ', input_prompt = 'Grep For❯ ', -- cmd = "rg --vimgrep", + rg_opts = "--hidden --column --line-number --no-heading --color=always --smart-case -g '!{.git,node_modules}/*'", git_icons = true, -- show git icons? file_icons = true, -- show file icons? color_icons = true, -- colorize file|git icons @@ -194,13 +218,6 @@ require'fzf-lua'.setup { prompt = 'History❯ ', cwd_only = false, }, - git = { - prompt = 'GitFiles❯ ', - cmd = 'git ls-files --exclude-standard', - git_icons = true, -- show git icons? - file_icons = true, -- show file icons? - color_icons = true, -- colorize file|git icons - }, buffers = { prompt = 'Buffers❯ ', file_icons = true, -- show file icons? @@ -233,8 +250,23 @@ require'fzf-lua'.setup { end, }, quickfix = { - cwd = vim.loop.cwd(), + -- cwd = vim.loop.cwd(), + file_icons = true, + git_icons = true, + }, + lsp = { + prompt = '❯ ', + -- cwd = vim.loop.cwd(), file_icons = true, + git_icons = false, + lsp_icons = true, + severity = "hint", + icons = { + ["Error"] = { icon = "", color = "red" }, -- error + ["Warning"] = { icon = "", color = "yellow" }, -- warning + ["Information"] = { icon = "", color = "blue" }, -- info + ["Hint"] = { icon = "", color = "magenta" }, -- hint + }, }, -- placeholders for additional user customizations loclist = {}, @@ -243,18 +275,6 @@ require'fzf-lua'.setup { file_icon_colors = { -- override colors for extensions ["lua"] = "blue", }, - git_icons = { -- override colors for git icons - ["M"] = "M", --"★", - ["D"] = "D", --"✗", - ["A"] = "A", --"+", - ["?"] = "?" - }, - git_icon_colors = { -- override colors for git icon colors - ["M"] = "yellow", - ["D"] = "red", - ["A"] = "green", - ["?"] = "magenta" - }, fzf_binds = { -- fzf '--bind=' options 'f2:toggle-preview', 'f3:toggle-preview-wrap', @@ -265,11 +285,11 @@ require'fzf-lua'.setup { 'ctrl-f:page-down', 'ctrl-b:page-up', 'ctrl-a:toggle-all', - 'ctrl-u:clear-query', + 'ctrl-l:clear-query', }, window_on_create = function() -- nvim window options override vim.cmd("set winhl=Normal:Normal") -- popup bg match normal windows - end + end, } ``` @@ -292,22 +312,22 @@ EOF ## TODO -- [ ] Add built-in plugin documentation -- [ ] Add "hidden" options documentation -- [ ] Add FAQ - Add more providers + + [x] ~~LSP (refs, symbols, etc)~~ (2021-07-20) + + [ ] git commits + + [ ] git branches + [ ] vim commands + [ ] vim command history + [ ] vim keymaps + [ ] vim options - + [ ] search terms history + + [ ] search history + [ ] tags + [ ] marks + [ ] registers + [ ] spelling suggestions - + [ ] git commits - + [ ] git branches - + [ ] LSP (refs, symbols, etc) +- [ ] Add built-in plugin documentation +- [ ] Add "hidden" options documentation +- [ ] Add FAQ ### Credits diff --git a/lua/fzf-lua/config.lua b/lua/fzf-lua/config.lua index 6274130..bb60528 100644 --- a/lua/fzf-lua/config.lua +++ b/lua/fzf-lua/config.lua @@ -124,6 +124,12 @@ M.git = { color_icons = true, git_icons = true, actions = M.files.actions, + icons = { + ["M"] = { icon = "M", color = "yellow" }, + ["D"] = { icon = "D", color = "red" }, + ["A"] = { icon = "A", color = "green" }, + ["?"] = { icon = "?", color = "magenta" }, + }, } M.buffers = { @@ -177,12 +183,19 @@ M.manpages = { } M.lsp = { - prompt = 'LSP> ', - timeout = 1000, + prompt = '> ', file_icons = true and M._has_devicons, color_icons = true, git_icons = false, + lsp_icons = true, + severity = "hint", actions = M.files.actions, + icons = { + ["Error"] = { icon = "", color = "red" }, -- error + ["Warning"] = { icon = "", color = "yellow" }, -- warning + ["Information"] = { icon = "", color = "blue" }, -- info + ["Hint"] = { icon = "", color = "magenta" }, -- hint + }, } -- toggle preview @@ -238,19 +251,6 @@ M.file_icon_colors = { ["desktop"] = "magenta", } -M.git_icons = { - ["M"] = "M", - ["D"] = "D", - ["A"] = "A", - ["?"] = "?" -} - -M.git_icon_colors = { - ["M"] = "yellow", - ["D"] = "red", - ["A"] = "green", - ["?"] = "magenta" -} M.window_on_create = function() -- Set popup background same as normal windows diff --git a/lua/fzf-lua/core.lua b/lua/fzf-lua/core.lua index 16e500c..f4d8121 100644 --- a/lua/fzf-lua/core.lua +++ b/lua/fzf-lua/core.lua @@ -6,12 +6,6 @@ local actions = require "fzf-lua.actions" local M = {} --- invisible unicode char as icon|git separator --- this way we can split our string by space --- this causes "invalid escape sequence" error --- local nbsp = "\u{00a0}" -local nbsp = " " - M.get_devicon = function(file, ext) local icon = '' if not file or #file == 0 then return icon end @@ -99,22 +93,14 @@ local get_untracked_files = function() return untracked_files end -local color_icon = function(icon, ext) - if ext then - return utils.ansi_codes[config.file_icon_colors[ext] or "dark_grey"](icon) - else - return utils.ansi_codes[config.git_icon_colors[icon] or "green"](icon) - end -end - -local get_git_icon = function(file, diff_files, untracked_files) +local get_git_indicator = function(file, diff_files, untracked_files) if diff_files and diff_files[file] then - return config.git_icons[diff_files[file]] + return diff_files[file] end if untracked_files and untracked_files[file] then - return config.git_icons[untracked_files[file]] + return untracked_files[file] end - return nbsp + return utils.nbsp end @@ -136,16 +122,24 @@ M.make_entry_file = function(opts, x) x = path.relative(x, opts.cwd) end if opts.file_icons then - local extension = path.extension(x) - icon = M.get_devicon(x, extension) - if opts.color_icons then icon = color_icon(icon, extension) end + local ext = path.extension(x) + icon = M.get_devicon(x, ext) + if opts.color_icons then + icon = utils.ansi_codes[config.file_icon_colors[ext] or "dark_grey"](icon) + end prefix = prefix .. icon end if opts.git_icons then local filepath = x:match("^[^:]+") - icon = get_git_icon(filepath, opts.diff_files, opts.untracked_files) - if opts.color_icons then icon = color_icon(icon) end - prefix = prefix .. utils._if(#prefix>0, nbsp, '') .. icon + local indicator = get_git_indicator(filepath, opts.diff_files, opts.untracked_files) + icon = indicator + if config.git.icons[indicator] then + icon = config.git.icons[indicator].icon + if opts.color_icons then + icon = utils.ansi_codes[config.git.icons[indicator].color or "dark_grey"](icon) + end + end + prefix = prefix .. utils._if(#prefix>0, utils.nbsp, '') .. icon end if #prefix > 0 then x = prefix .. " " .. x @@ -180,7 +174,7 @@ M.fzf_files = function(opts) opts.untracked_files = get_untracked_files() end - local has_prefix = opts.file_icons or opts.git_icons + local has_prefix = opts.file_icons or opts.git_icons or opts.lsp_icons if not opts.filespec then opts.filespec = utils._if(has_prefix, "{2}", "{1}") end @@ -189,6 +183,10 @@ M.fzf_files = function(opts) M.build_fzf_cli(opts), config.winopts(opts.winopts)) + if opts.post_select_cb then + opts.post_select_cb() + end + if not selected then return end if #selected > 1 then diff --git a/lua/fzf-lua/init.lua b/lua/fzf-lua/init.lua index 556cf4a..87508de 100644 --- a/lua/fzf-lua/init.lua +++ b/lua/fzf-lua/init.lua @@ -124,7 +124,10 @@ function M.setup(opts) setopts(config.lsp, opts.lsp, { prompt = "string", cwd = "string", - timeout = "number", + severity = "string", + severity_exact = "string", + severity_bound = "string", + lsp_icons = "boolean", git_icons = "boolean", file_icons = "boolean", color_icons = "boolean", @@ -169,8 +172,8 @@ function M.setup(opts) setopt_tbl(config[k], opts[k], "actions") setopt_tbl(config[k], opts[k], "winopts") end - setopt_tbl(config, opts, "git_icons") - setopt_tbl(config, opts, "git_icon_colors") + setopt_tbl(config.git, opts.git, "icons") + setopt_tbl(config.lsp, opts.lsp, "icons") setopt_tbl(config, opts, "file_icon_colors") -- override the bat preview theme if set by caller if config.bat_theme and #config.bat_theme > 0 then @@ -202,6 +205,15 @@ M.help_tags = require'fzf-lua.providers.helptags'.helptags M.man_pages = require'fzf-lua.providers.manpages'.manpages M.colorschemes = require'fzf-lua.providers.colorschemes'.colorschemes -M.lsp_refs = require'fzf-lua.providers.lsp'.refs_async +M.lsp_typedefs = require'fzf-lua.providers.lsp'.typedefs +M.lsp_references = require'fzf-lua.providers.lsp'.references +M.lsp_definitions = require'fzf-lua.providers.lsp'.definitions +M.lsp_declarations = require'fzf-lua.providers.lsp'.declarations +M.lsp_implementations = require'fzf-lua.providers.lsp'.implementations +M.lsp_document_symbols = require'fzf-lua.providers.lsp'.document_symbols +M.lsp_workspace_symbols = require'fzf-lua.providers.lsp'.workspace_symbols +M.lsp_code_actions = require'fzf-lua.providers.lsp'.code_actions +M.lsp_document_diagnostics = require'fzf-lua.providers.lsp'.diagnostics +M.lsp_workspace_diagnostics = require'fzf-lua.providers.lsp'.workspace_diagnostics return M diff --git a/lua/fzf-lua/providers/grep.lua b/lua/fzf-lua/providers/grep.lua index d04cf25..0d6b0b4 100644 --- a/lua/fzf-lua/providers/grep.lua +++ b/lua/fzf-lua/providers/grep.lua @@ -56,7 +56,6 @@ M.grep = function(opts) -- save the next search as last_search so we -- let the caller have an option to run the -- same search again - -- print("1", opts.last_search, opts.search) if not opts.search or #opts.search == 0 then config.grep.last_search = vim.fn.input(opts.input_prompt) else diff --git a/lua/fzf-lua/providers/lsp.lua b/lua/fzf-lua/providers/lsp.lua index 1ad6c81..932eb60 100644 --- a/lua/fzf-lua/providers/lsp.lua +++ b/lua/fzf-lua/providers/lsp.lua @@ -2,103 +2,284 @@ if not pcall(require, "fzf") then return end --- local fzf = require "fzf" +local fzf = require "fzf" -- local fzf_helpers = require("fzf.helpers") -- local path = require "fzf-lua.path" local core = require "fzf-lua.core" local utils = require "fzf-lua.utils" local config = require "fzf-lua.config" +local actions = require "fzf-lua.actions" local M = {} -local getopts_lsp = function(opts, cfg) - opts = config.getopts(opts, cfg, { - "cwd", "prompt", "actions", "winopts", - "file_icons", "color_icons", "git_icons", - "separator" - }) +local function location_handler(opts, cb, _, result) + result = vim.tbl_islist(result) and result or {result} + -- Jump immediately if there is only one location + -- TODO: we can't preform the jump due to fzf already + -- being open resulting in: + -- Error executing vim.schedule lua callback: + -- /usr/share/nvim/runtime/lua/vim/lsp/util.lua:976: + -- Vim(normal):Can't re-enter normal mode from terminal mode + --[[ if #result == 1 then + utils.send_ctrl_c() + return vim.lsp.util.jump_to_location(result[1]) + end ]] + local items = vim.lsp.util.locations_to_items(result) + for _, entry in ipairs(items) do + entry = core.make_entry_lcol(opts, entry) + entry = core.make_entry_file(opts, entry) + cb(entry, function(err) + if err then return end + end) + end +end - if not opts.timeout then - opts.timeout = config.lsp.timeout or 10000 +local function symbol_handler(opts, cb, _, result) + result = vim.tbl_islist(result) and result or {result} + local items = vim.lsp.util.symbols_to_items(result) + for _, entry in ipairs(items) do + entry.filename = opts.filename + entry = core.make_entry_lcol(opts, entry) + entry = core.make_entry_file(opts, entry) + cb(entry, function(err) + if err then return end + end) end - return opts end -local set_fzf_args = function(opts) - local line_placeholder = 2 - if opts.file_icons == true or opts.git_icons == true then - line_placeholder = 3 +local function code_action_handler(opts, cb, _, code_actions) + opts.code_actions = {} + for i, action in ipairs(code_actions) do + local text = string.format("%s %s", + utils.ansi_codes.magenta(string.format("%d:", i)), + action.title) + opts.code_actions[tostring(i)] = action + cb(text, function(err) + if err then return end + end) end +end - opts.cli_args = "--nth=3 --delimiter='[: \\t]'" - opts.preview_args = string.format("--highlight-line={%d}", line_placeholder) - opts.preview_offset = string.format("+{%d}-/2", line_placeholder) - return opts +local function diagnostics_handler(opts, cb, _, entry) + local type = entry.type + entry = core.make_entry_lcol(opts, entry) + entry = core.make_entry_file(opts, entry) + if opts.lsp_icons and opts.cfg.icons[type] then + local severity = opts.cfg.icons[type] + local icon = severity.icon + if opts.color_icons then + icon = utils.ansi_codes[severity.color or "dark_grey"](icon) + end + if opts.file_icons or opts.git_icons then + -- entry = icon .. utils.nbsp .. utils.nbsp .. entry + entry = icon .. utils.nbsp .. utils.nbsp .. entry + else + -- entry = icon .. utils.nbsp .. " " .. entry + entry = icon .. utils.nbsp .. " " .. entry + end + end + cb(entry, function(err) + if err then return end + end) end -M.refs_async = function(opts) +local function wrap_handler(handler) + return function(err, method, result, client_id, bufnr, lspcfg) + local ret + if err then + ret = utils.err(err.message) + utils.send_ctrl_c() + elseif not result or vim.tbl_isempty(result) then + ret = utils.info(string.format('No %s found', string.lower(handler.label))) + utils.send_ctrl_c() + else + ret = handler.target(err, method, result, client_id, bufnr, lspcfg) + end + return ret + end +end - opts = getopts_lsp(opts, config.lsp) +local function set_lsp_fzf_fn(opts) - -- hangs/returns empty if inside the coroutine - local params = vim.lsp.util.make_position_params() - params.context = { includeDeclaration = true } - local results_lsp, lsp_err = vim.lsp.buf_request_sync(0, "textDocument/references", params, opts.timeout) - if lsp_err then - utils.err("Error finding LSP references: " .. lsp_err) - return + -- we must make the params here while we're on + -- our current buffer window, anything inside + -- fzf_fn is run while fzf term is open + opts.bufnr = vim.api.nvim_get_current_buf() + opts.filename = vim.api.nvim_buf_get_name(opts.bufnr) + if not opts.lsp_params then + opts.lsp_params = vim.lsp.util.make_position_params() + opts.lsp_params.context = { includeDeclaration = true } end opts.fzf_fn = function (cb) coroutine.wrap(function () local co = coroutine.running() - local locations = {} - for _, server_results in pairs(results_lsp) do - if server_results.result then - vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {}) - end - end - if vim.tbl_isempty(locations) then - utils.info("LSP references is empty.") - vim.api.nvim_feedkeys( - vim.api.nvim_replace_termcodes("", true, false, true), - 'n', true) - return + -- callback when a location is found + -- we use this passthrough so we can send the + -- coroutine variable (not used rn but who knows?) + opts.lsp_handler.target = function(_, _, result) + return opts.lsp_handler.handler(opts, cb, co, result) end - for _, entry in ipairs(locations) do - entry = core.make_entry_lcol(opts, entry) - entry = core.make_entry_file(opts, entry) - cb(entry, function(err) - if err then return end - coroutine.resume(co) - -- cb(nil) -- to close the pipe to fzf, this removes the loading - -- indicator in fzf - end) - coroutine.yield() + + local _, cancel_all = vim.lsp.buf_request(opts.bufnr, + opts.lsp_handler.method, opts.lsp_params, + wrap_handler(opts.lsp_handler)) + + -- cancel all remaining LSP requests + -- once the user made their selection + -- or closed the fzf popup + opts.post_select_cb = function() + if cancel_all then cancel_all() end end + + -- coroutine.yield() + end)() end - opts = set_fzf_args(opts) + return opts +end + +local set_fzf_files_args = function(opts) + local line_placeholder = 2 + if opts.file_icons or opts.git_icons or opts.lsp_icons then + line_placeholder = line_placeholder+1 + end + + opts.cli_args = opts.cli_args or "--delimiter='[: \\t]'" + opts.filespec = string.format("{%d}", line_placeholder-1) + opts.preview_args = string.format("--highlight-line={%d}", line_placeholder) + opts.preview_offset = string.format("+{%d}-/2", line_placeholder) + return opts +end + +local normalize_lsp_opts = function(opts, cfg) + opts = config.getopts(opts, cfg, { + "cwd", "actions", "winopts", + "file_icons", "color_icons", + "git_icons", "lsp_icons", "severity", + "severity_exact", "severity_bound", + }) + + if not opts.cwd then opts.cwd = vim.loop.cwd() end + if not opts.prompt then + opts.prompt = opts.lsp_handler.label .. cfg.prompt + end + + opts.cfg = nil + opts.bufnr = nil + opts.filename = nil + opts.lsp_params = nil + opts.code_actions = nil + + return opts +end + +local function fzf_lsp_locations(opts) + opts = normalize_lsp_opts(opts, config.lsp) + opts = set_fzf_files_args(opts) + opts = set_lsp_fzf_fn(opts) return core.fzf_files(opts) end +-- define the functions for wrap_module_fncs +M.references = function(opts) + return fzf_lsp_locations(opts) +end + +M.definitions = function(opts) + return fzf_lsp_locations(opts) +end + +M.declarations = function(opts) + return fzf_lsp_locations(opts) +end + +M.typedefs = function(opts) + return fzf_lsp_locations(opts) +end + +M.implementations = function(opts) + return fzf_lsp_locations(opts) +end + +M.document_symbols = function(opts) + if not opts then opts = {} end + -- TODO: filename hiding + -- since single document + opts.ignore_filename = true + return fzf_lsp_locations(opts) +end + +M.workspace_symbols = function(opts) + return fzf_lsp_locations(opts) +end + +M.code_actions = function(opts) + opts = normalize_lsp_opts(opts, config.lsp) + opts.lsp_params = vim.lsp.util.make_range_params() + opts.lsp_params.context = { + diagnostics = vim.lsp.diagnostic.get_line_diagnostics() + } + + -- "apply action" as default function + if not opts.actions then opts.actions = {} end + opts.actions.default = (function(selected) + local idx = selected[2]:match("(%d+)") + local action = opts.code_actions[idx] + if not action then return end + if action.edit or type(action.command) == 'table' then + if action.edit then + vim.lsp.util.apply_workspace_edit(action.edit) + end + if type(action.command) == 'table' then + vim.lsp.buf.execute_command(action.command) + end + else + vim.lsp.buf.execute_command(action) + end + end) + + opts.nomulti = true + opts.preview_window = 'right:0' + opts.cli_args = "--delimiter=':'" + opts = set_lsp_fzf_fn(opts) + + coroutine.wrap(function () + + local selected = fzf.fzf(opts.fzf_fn, + core.build_fzf_cli(opts), + config.winopts(opts.winopts)) + + if opts.post_select_cb then + opts.post_select_cb() + end + + if not selected then return end + + actions.act(opts.actions, selected[1], selected) + + end)() + +end + local convert_diagnostic_type = function(severity) -- convert from string to int - if type(severity) == 'string' then + if type(severity) == "string" then -- make sure that e.g. error is uppercased to Error return vim.lsp.protocol.DiagnosticSeverity[severity:gsub("^%l", string.upper)] end -- otherwise keep original value, incl. nil + if type(severity) ~= "number" then return nil end return severity end local filter_diag_severity = function(opts, severity) - if opts.severity ~= nil then - return opts.severity == severity - elseif opts.severity_limit ~= nil then - return severity <= opts.severity_limit + if opts.severity_exact ~= nil then + return opts.severity_exact == severity + elseif opts.severity ~= nil then + return severity <= opts.severity elseif opts.severity_bound ~= nil then return severity >= opts.severity_bound else @@ -106,24 +287,34 @@ local filter_diag_severity = function(opts, severity) end end -local diagnostics_to_tbl = function(opts) - opts = opts or {} - local items = {} +M.diagnostics = function(opts) + opts = normalize_lsp_opts(opts, config.lsp) + local lsp_type_diagnostic = vim.lsp.protocol.DiagnosticSeverity local current_buf = vim.api.nvim_get_current_buf() + -- save this so handler can get the lsp icon + opts.cfg = config.lsp + + -- hint = 4 + -- information = 3 + -- warning = 2 + -- error = 1 + -- severity: keep any equal or more severe (lower) + -- severity_exact: keep any matching exact severity + -- severity_bound: keep any equal or less severe (higher) opts.severity = convert_diagnostic_type(opts.severity) - opts.severity_limit = convert_diagnostic_type(opts.severity_limit) + opts.severity_exact = convert_diagnostic_type(opts.severity_exact) opts.severity_bound = convert_diagnostic_type(opts.severity_bound) local validate_severity = 0 - for _, v in ipairs({opts.severity, opts.severity_limit, opts.severity_bound}) do + for _, v in ipairs({opts.severity_exact, opts.severity, opts.severity_bound}) do if v ~= nil then validate_severity = validate_severity + 1 end if validate_severity > 1 then - utils.info('LSP: invalid severity parameters') - return {} + utils.warn("Invalid severity params, ignoring severity filters") + opts.severity, opts.severity_exact, opts.severity_bound = nil, nil, nil end end @@ -148,52 +339,135 @@ local diagnostics_to_tbl = function(opts) return buffer_diag end - local buffer_diags = opts.get_all and vim.lsp.diagnostic.get_all() or - {[current_buf] = vim.lsp.diagnostic.get(current_buf, opts.client_id)} - for bufnr, diags in pairs(buffer_diags) do - for _, diag in ipairs(diags) do - -- workspace diagnostics may include empty tables for unused bufnr - if not vim.tbl_isempty(diag) then - if filter_diag_severity(opts, diag.severity) then - table.insert(items, preprocess_diag(diag, bufnr)) + opts.fzf_fn = function (cb) + coroutine.wrap(function () + local co = coroutine.running() + + local buffer_diags = opts.diag_all and vim.lsp.diagnostic.get_all() or + {[current_buf] = vim.lsp.diagnostic.get(current_buf, opts.client_id)} + if #buffer_diags == 0 then + utils.info(string.format('No %s found', string.lower(opts.lsp_handler.label))) + utils.send_ctrl_c() + end + for bufnr, diags in pairs(buffer_diags) do + for _, diag in ipairs(diags) do + -- workspace diagnostics may include empty tables for unused bufnr + if not vim.tbl_isempty(diag) then + if filter_diag_severity(opts, diag.severity) then + diagnostics_handler(opts, cb, co, + preprocess_diag(diag, bufnr)) + end + end end end - end + -- coroutine.yield() + end)() end - -- sort results by bufnr (prioritize cur buf), severity, lnum - table.sort(items, function(a, b) - if a.bufnr == b.bufnr then - if a.type == b.type then - return a.lnum < b.lnum - else - return a.type < b.type - end + opts = set_fzf_files_args(opts) + return core.fzf_files(opts) +end + +M.workspace_diagnostics = function(opts) + if not opts then opts = {} end + opts.diag_all = true + return M.diagnostics(opts) +end + + +local function check_capabilities(feature) + local clients = vim.lsp.buf_get_clients(0) + + local supported_client = false + for _, client in pairs(clients) do + supported_client = client.resolved_capabilities[feature] + if supported_client then break end + end + + if supported_client then + return true + else + if #clients == 0 then + utils.info("LSP: no client attached") else - -- prioritize for current bufnr - if a.bufnr == current_buf then - return true - end - if b.bufnr == current_buf then - return false - end - return a.bufnr < b.bufnr + utils.info("LSP: server does not support " .. feature) end - end) - - return items + return false + end end +local handlers = { + ["code_actions"] = { + label = "Code Actions", + capability = "code_action", + method = "textDocument/codeAction", + handler = code_action_handler }, + ["references"] = { + label = "References", + capability = "find_references", + method = "textDocument/references", + handler = location_handler }, + ["definitions"] = { + label = "Definitions", + capability = "goto_definition", + method = "textDocument/definition", + handler = location_handler }, + ["declarations"] = { + label = "Declarations", + capability = "goto_declaration", + method = "textDocument/declaration", + handler = location_handler }, + ["typedefs"] = { + label = "Type Definitions", + capability = "type_definition", + method = "textDocument/typeDefinition", + handler = location_handler }, + ["implementations"] = { + label = "Implementations", + capability = "implementation", + method = "textDocument/implementation", + handler = location_handler }, + ["document_symbols"] = { + label = "Document Symbols", + capability = "document_symbol", + method = "textDocument/documentSymbol", + handler = symbol_handler }, + ["workspace_symbols"] = { + label = "Workspace Symbols", + capability = "workspace_symbol", + method = "workspace/symbol", + handler = symbol_handler }, + ["diagnostics"] = { + label = "Diagnostics", + capability = nil, + method = nil, + handler = diagnostics_handler }, + ["workspace_diagnostics"] = { + label = "Workspace Diagnostics", + capability = nil, + method = nil, + handler = diagnostics_handler }, +} -M.lsp_diag = function(opts) - local locations = diagnostics_to_tbl(opts) +local function wrap_module_fncs(mod) + for k, v in pairs(mod) do + mod[k] = function(opts) + opts = opts or {} - if vim.tbl_isempty(locations) then - utils.info("LSP diagnostics is empty.") - return + if not opts.lsp_handler then opts.lsp_handler = handlers[k] end + if not opts.lsp_handler then + utils.err(string.format("No LSP handler defined for %s", k)) + return + end + if opts.lsp_handler and opts.lsp_handler.capability + and not check_capabilities(opts.lsp_handler.capability) then + return + end + v(opts) + end end - return lsp_run(opts, config.lsp, locations) + return mod end -return M +return wrap_module_fncs(M) diff --git a/lua/fzf-lua/providers/manpages.lua b/lua/fzf-lua/providers/manpages.lua index 9d4df71..8538b0d 100644 --- a/lua/fzf-lua/providers/manpages.lua +++ b/lua/fzf-lua/providers/manpages.lua @@ -47,7 +47,6 @@ M.manpages = function(opts) if #selected > 1 then for i = 2, #selected do selected[i] = getmanpage(selected[i]) - print(selected[i]) end end diff --git a/lua/fzf-lua/providers/quickfix.lua b/lua/fzf-lua/providers/quickfix.lua index 6aeda2e..36a395a 100644 --- a/lua/fzf-lua/providers/quickfix.lua +++ b/lua/fzf-lua/providers/quickfix.lua @@ -24,6 +24,8 @@ local quickfix_run = function(opts, cfg, locations) "separator" }) + if not opts.cwd then opts.cwd = vim.loop.cwd() end + opts.fzf_fn = function (cb) for _, x in ipairs(results) do x = core.make_entry_file(opts, x) @@ -41,10 +43,11 @@ local quickfix_run = function(opts, cfg, locations) local line_placeholder = 2 if opts.file_icons == true or opts.git_icons == true then - line_placeholder = 3 + line_placeholder = line_placeholder+1 end - opts.cli_args = "--nth=3 --delimiter='[: \\t]'" + opts.cli_args = "--delimiter='[: \\t]'" + opts.filespec = string.format("{%d}", line_placeholder-1) opts.preview_args = string.format("--highlight-line={%d}", line_placeholder) --[[ # Preview with bat, matching line in the middle of the window below diff --git a/lua/fzf-lua/utils.lua b/lua/fzf-lua/utils.lua index 57160a4..4e469c2 100644 --- a/lua/fzf-lua/utils.lua +++ b/lua/fzf-lua/utils.lua @@ -7,6 +7,12 @@ end local M = {} +-- invisible unicode char as icon|git separator +-- this way we can split our string by space +-- this causes "invalid escape sequence" error +-- local nbsp = "\u{00a0}" +M.nbsp = " " + M._if = function(bool, a, b) if bool then return a @@ -182,4 +188,9 @@ function M.get_visual_selection() return table.concat(lines, "\n") end +function M.send_ctrl_c() + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("", true, false, true), 'n', true) +end + return M