From 495c133254206849d85d1610cf289a159f76834a Mon Sep 17 00:00:00 2001 From: bhagwan Date: Sun, 18 Jul 2021 08:04:22 -0700 Subject: [PATCH] nbsp revert, lsp_references added --- lua/fzf-lua/config.lua | 9 ++ lua/fzf-lua/core.lua | 24 +++- lua/fzf-lua/init.lua | 12 ++ lua/fzf-lua/path.lua | 4 + lua/fzf-lua/providers/lsp.lua | 199 +++++++++++++++++++++++++++++ lua/fzf-lua/providers/quickfix.lua | 20 +-- 6 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 lua/fzf-lua/providers/lsp.lua diff --git a/lua/fzf-lua/config.lua b/lua/fzf-lua/config.lua index 4a815fc..6274130 100644 --- a/lua/fzf-lua/config.lua +++ b/lua/fzf-lua/config.lua @@ -176,6 +176,15 @@ M.manpages = { }, } +M.lsp = { + prompt = 'LSP> ', + timeout = 1000, + file_icons = true and M._has_devicons, + color_icons = true, + git_icons = false, + actions = M.files.actions, +} + -- toggle preview -- toggle preview text wrap -- | page down|up diff --git a/lua/fzf-lua/core.lua b/lua/fzf-lua/core.lua index b013051..16e500c 100644 --- a/lua/fzf-lua/core.lua +++ b/lua/fzf-lua/core.lua @@ -8,7 +8,9 @@ local M = {} -- invisible unicode char as icon|git separator -- this way we can split our string by space -local nbsp = "\u{00a0}" +-- this causes "invalid escape sequence" error +-- local nbsp = "\u{00a0}" +local nbsp = " " M.get_devicon = function(file, ext) local icon = '' @@ -115,6 +117,18 @@ local get_git_icon = function(file, diff_files, untracked_files) return nbsp end + +M.make_entry_lcol = function(_, entry) + if not entry then return nil end + local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr) + return string.format("%s:%s:%s:%s%s", + filename, --utils.ansi_codes.magenta(filename), + utils.ansi_codes.green(tostring(entry.lnum)), + utils.ansi_codes.blue(tostring(entry.col)), + utils._if(entry.text and entry.text:find("^\t"), "", "\t"), + entry.text) +end + M.make_entry_file = function(opts, x) local icon local prefix = '' @@ -128,7 +142,8 @@ M.make_entry_file = function(opts, x) prefix = prefix .. icon end if opts.git_icons then - icon = get_git_icon(x, opts.diff_files, opts.untracked_files) + 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 end @@ -167,7 +182,7 @@ M.fzf_files = function(opts) local has_prefix = opts.file_icons or opts.git_icons if not opts.filespec then - opts.filespec = utils._if(has_prefix, "{2}", "{}") + opts.filespec = utils._if(has_prefix, "{2}", "{1}") end local selected = fzf.fzf(opts.fzf_fn, @@ -181,7 +196,8 @@ M.fzf_files = function(opts) if has_prefix then selected[i] = trim_entry(selected[i]) end - if opts.cwd and #opts.cwd > 0 then + if opts.cwd and #opts.cwd>0 and + not path.starts_with_separator(selected[i]) then selected[i] = path.join({opts.cwd, selected[i]}) end if opts.cb_selected then diff --git a/lua/fzf-lua/init.lua b/lua/fzf-lua/init.lua index cd2889b..556cf4a 100644 --- a/lua/fzf-lua/init.lua +++ b/lua/fzf-lua/init.lua @@ -121,6 +121,16 @@ function M.setup(opts) git_diff_cmd = "string", git_untracked_cmd = "string", }) + setopts(config.lsp, opts.lsp, { + prompt = "string", + cwd = "string", + timeout = "number", + git_icons = "boolean", + file_icons = "boolean", + color_icons = "boolean", + git_diff_cmd = "string", + git_untracked_cmd = "string", + }) setopts(config.git, opts.git, { prompt = "string", cmd = "string", @@ -192,4 +202,6 @@ 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 + return M diff --git a/lua/fzf-lua/path.lua b/lua/fzf-lua/path.lua index 62e33ee..05b025a 100644 --- a/lua/fzf-lua/path.lua +++ b/lua/fzf-lua/path.lua @@ -4,6 +4,10 @@ M.separator = function() return '/' end +M.starts_with_separator = function(path) + return path:match("^[^"..M.separator().."]+") +end + M.tail = (function() local os_sep = M.separator() local match_string = '[^' .. os_sep .. ']*$' diff --git a/lua/fzf-lua/providers/lsp.lua b/lua/fzf-lua/providers/lsp.lua new file mode 100644 index 0000000..1ad6c81 --- /dev/null +++ b/lua/fzf-lua/providers/lsp.lua @@ -0,0 +1,199 @@ +if not pcall(require, "fzf") then + return +end + +-- 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 M = {} + +local getopts_lsp = function(opts, cfg) + opts = config.getopts(opts, cfg, { + "cwd", "prompt", "actions", "winopts", + "file_icons", "color_icons", "git_icons", + "separator" + }) + + if not opts.timeout then + opts.timeout = config.lsp.timeout or 10000 + 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 + 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 +end + +M.refs_async = function(opts) + + opts = getopts_lsp(opts, config.lsp) + + -- 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 + 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 + 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() + end + end)() + end + + opts = set_fzf_args(opts) + return core.fzf_files(opts) +end + +local convert_diagnostic_type = function(severity) + -- convert from string to int + 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 + 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 + elseif opts.severity_bound ~= nil then + return severity >= opts.severity_bound + else + return true + end +end + +local diagnostics_to_tbl = function(opts) + opts = opts or {} + local items = {} + local lsp_type_diagnostic = vim.lsp.protocol.DiagnosticSeverity + local current_buf = vim.api.nvim_get_current_buf() + + opts.severity = convert_diagnostic_type(opts.severity) + opts.severity_limit = convert_diagnostic_type(opts.severity_limit) + 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 + if v ~= nil then + validate_severity = validate_severity + 1 + end + if validate_severity > 1 then + utils.info('LSP: invalid severity parameters') + return {} + end + end + + local preprocess_diag = function(diag, bufnr) + local filename = vim.api.nvim_buf_get_name(bufnr) + local start = diag.range['start'] + local finish = diag.range['end'] + local row = start.line + local col = start.character + + local buffer_diag = { + bufnr = bufnr, + filename = filename, + lnum = row + 1, + col = col + 1, + start = start, + finish = finish, + -- remove line break to avoid display issues + text = vim.trim(diag.message:gsub("[\n]", "")), + type = lsp_type_diagnostic[diag.severity] or lsp_type_diagnostic[1] + } + 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)) + end + end + 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 + 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 + end + end) + + return items +end + + +M.lsp_diag = function(opts) + local locations = diagnostics_to_tbl(opts) + + if vim.tbl_isempty(locations) then + utils.info("LSP diagnostics is empty.") + return + end + + return lsp_run(opts, config.lsp, locations) +end + +return M diff --git a/lua/fzf-lua/providers/quickfix.lua b/lua/fzf-lua/providers/quickfix.lua index 19b3fa7..6aeda2e 100644 --- a/lua/fzf-lua/providers/quickfix.lua +++ b/lua/fzf-lua/providers/quickfix.lua @@ -3,7 +3,7 @@ if not pcall(require, "fzf") then end -- local fzf = require "fzf" -local fzf_helpers = require("fzf.helpers") +-- local fzf_helpers = require("fzf.helpers") -- local path = require "fzf-lua.path" local core = require "fzf-lua.core" local utils = require "fzf-lua.utils" @@ -15,12 +15,7 @@ local quickfix_run = function(opts, cfg, locations) if not locations then return {} end local results = {} for _, entry in ipairs(locations) do - local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr) - table.insert(results, string.format("%s:%s:%s:\t%s", - filename, --utils.ansi_codes.magenta(filename), - utils.ansi_codes.green(tostring(entry.lnum)), - utils.ansi_codes.blue(tostring(entry.col)), - entry.text)) + table.insert(results, core.make_entry_lcol(opts, entry)) end opts = config.getopts(opts, cfg, { @@ -44,8 +39,13 @@ local quickfix_run = function(opts, cfg, locations) return x end ]] - opts.cli_args = "--delimiter='[: \\t]'" - opts.preview_args = "--highlight-line={3}" -- bat higlight + local line_placeholder = 2 + if opts.file_icons == true or opts.git_icons == true then + line_placeholder = 3 + end + + opts.cli_args = "--nth=3 --delimiter='[: \\t]'" + opts.preview_args = string.format("--highlight-line={%d}", line_placeholder) --[[ # Preview with bat, matching line in the middle of the window below # the fixed header of the top 3 lines @@ -57,7 +57,7 @@ local quickfix_run = function(opts, cfg, locations) # '--preview-window '~3:+{2}+3/2'' ]] - opts.preview_offset = "+{3}-/2" + opts.preview_offset = string.format("+{%d}-/2", line_placeholder) return core.fzf_files(opts) end