From fc759b4e8a08cfd77019ac1d2579e407a1d31915 Mon Sep 17 00:00:00 2001 From: ray-x Date: Sat, 8 May 2021 14:54:37 +1000 Subject: [PATCH] document symbol jump --- README.md | 9 ++- lua/navigator/dochighlight.lua | 118 ++++++++++++++++++++++++++++ lua/navigator/lspclient/attach.lua | 41 +++------- lua/navigator/lspclient/clients.lua | 73 +++++++++-------- lua/navigator/lspclient/mapping.lua | 3 + lua/navigator/reference.lua | 14 ++-- lua/navigator/treesitter.lua | 76 ++++++++++++++---- lua/navigator/util.lua | 40 +++++++++- 8 files changed, 287 insertions(+), 87 deletions(-) create mode 100644 lua/navigator/dochighlight.lua diff --git a/README.md b/README.md index 78e3ab4..6534d48 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Easy code navigation through LSP and 🌲🏡Treesitter symbols, diagnostic erro - Unorthodox UI with floating windows - Async request with lsp.buf_request for reference search - Treesitter symbol search. It is handy for large filas (Some of LSP e.g. sumneko_lua, there is a 100kb file size limition?) -- fzy search with Lua-JIT +- FZY search with Lua-JIT - Better navigation for diagnostic errors, Navigate through all files/buffers that contain errors/warnings - Grouping references/implementation/incomming/outgoing based on file names. - Nerdfont, emoji for LSP and Treesitter kind @@ -120,6 +120,7 @@ colorscheme: [aurora](https://github.com/ray-x/aurora) ![reference](https://github.com/ray-x/files/blob/master/img/navigator/ref.gif?raw=true) + ### Document Symbol ![document symbol](https://github.com/ray-x/files/blob/master/img/navigator/doc_symbol.gif?raw=true) @@ -128,6 +129,12 @@ colorscheme: [aurora](https://github.com/ray-x/aurora) ![workspace symbol](https://github.com/ray-x/files/blob/master/img/navigator/workspace_symbol.gif?raw=true) +# Current symbol highlight and jump backword/forward between symbols + +Document highlight provided by LSP. +Jump between symbols between symbols with treesitter +![doc jump](https://github.com/ray-x/files/blob/master/img/navigator/doc_hl_jump.gif?raw=true) + ### Diagnostic Diagnostic in single bufer diff --git a/lua/navigator/dochighlight.lua b/lua/navigator/dochighlight.lua new file mode 100644 index 0000000..b06d57e --- /dev/null +++ b/lua/navigator/dochighlight.lua @@ -0,0 +1,118 @@ +local util = require "navigator.util" +local log = util.log +local api = vim.api +local references = {} + +-- returns r1 < r2 based on start of range +local function before(r1, r2) + if r1.start.line < r2.start.line then + return true + end + if r2.start.line < r1.start.line then + return false + end + if r1.start.character < r2.start.character then + return true + end + return false +end + +local function handle_document_highlight(_, _, result, _, bufnr, _) + if not bufnr then + return + end + if type(result) ~= "table" then + vim.lsp.util.buf_clear_references(bufnr) + return + end + + table.sort( + result, + function(a, b) + return before(a.range, b.range) + end + ) + references[bufnr] = result +end +-- modify from vim-illuminate +local function goto_adjent_reference(opt) + log(opt) + opt = vim.tbl_extend("force", {forward = true, wrap = true}, opt or {}) + + local bufnr = vim.api.nvim_get_current_buf() + local refs = references[bufnr] + if not refs or #refs == 0 then + return nil + end + + local next = nil + local nexti = nil + local crow, ccol = unpack(vim.api.nvim_win_get_cursor(0)) + local crange = {start = {line = crow - 1, character = ccol}} + + for i, ref in ipairs(refs) do + local range = ref.range + if opt.forward then + if before(crange, range) and (not next or before(range, next)) then + next = range + nexti = i + end + else + if before(range, crange) and (not next or before(next, range)) then + next = range + nexti = i + end + log(nexti, next) + end + end + if not next and opt.wrap then + nexti = opt.reverse and #refs or 1 + next = refs[nexti].range + end + + log(next) + vim.api.nvim_win_set_cursor(0, {next.start.line + 1, next.start.character}) + return next +end + +local function documentHighlight() + api.nvim_exec( + [[ + hi LspReferenceRead cterm=bold gui=Bold ctermbg=yellow guibg=purple4 + hi LspReferenceText cterm=bold gui=Bold ctermbg=red guibg=gray27 + hi LspReferenceWrite cterm=bold gui=Bold,Italic ctermbg=red guibg=MistyRose + augroup lsp_document_highlight + autocmd! * + autocmd CursorHold lua vim.lsp.buf.document_highlight() + autocmd CursorMoved lua vim.lsp.buf.clear_references() + augroup END + ]], + false + ) + vim.lsp.handlers["textDocument/documentHighlight"] = function(_, _, result, _, bufnr) + if not result then + return + end + bufnr = api.nvim_get_current_buf() + vim.lsp.util.buf_clear_references(bufnr) + vim.lsp.util.buf_highlight_references(bufnr, result) + bufnr = bufnr or 0 + if type(result) ~= "table" then + vim.lsp.util.buf_clear_references(bufnr) + return + end + table.sort( + result, + function(a, b) + return before(a.range, b.range) + end + ) + references[bufnr] = result + end +end + +return { + documentHighlight = documentHighlight, + goto_adjent_reference = goto_adjent_reference, + handle_document_highlight = handle_document_highlight +} diff --git a/lua/navigator/lspclient/attach.lua b/lua/navigator/lspclient/attach.lua index 18e5ccf..90c1669 100644 --- a/lua/navigator/lspclient/attach.lua +++ b/lua/navigator/lspclient/attach.lua @@ -9,29 +9,6 @@ local diagnostic_map = function(bufnr) api.nvim_buf_set_keymap(bufnr, "n", "]O", ":lua vim.lsp.diagnostic.set_loclist()", opts) end local M = {} -local function documentHighlight() - api.nvim_exec( - [[ - hi LspReferenceRead cterm=bold gui=Bold ctermbg=yellow guibg=DarkOrchid3 - hi LspReferenceText cterm=bold gui=Bold ctermbg=red guibg=gray27 - hi LspReferenceWrite cterm=bold gui=Bold,Italic ctermbg=red guibg=MistyRose - augroup lsp_document_highlight - autocmd! * - autocmd CursorHold lua vim.lsp.buf.document_highlight() - autocmd CursorMoved lua vim.lsp.buf.clear_references() - augroup END - ]], - false - ) - vim.lsp.handlers["textDocument/documentHighlight"] = function(_, _, result, _) - if not result then - return - end - bufnr = api.nvim_get_current_buf() - vim.lsp.util.buf_clear_references(bufnr) - vim.lsp.util.buf_highlight_references(bufnr, result) - end -end M.on_attach = function(client, bufnr) log("attaching") @@ -46,19 +23,21 @@ M.on_attach = function(client, bufnr) api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc") - -- https://github.com/fsouza + require("navigator.lspclient.mapping").setup({client = client, bufnr = bufnr, cap = client.resolved_capabilities}) + if client.resolved_capabilities.document_highlight then - documentHighlight() + require("navigator.dochighlight").documentHighlight() end - require("navigator.lspclient.mapping").setup({client = client, bufnr = bufnr, cap = client.resolved_capabilities}) + require "navigator.lspclient.lspkind".init() - vim.cmd [[silent! packadd vim-illuminate]] - local hasilm, ilm = pcall(require, "illuminate") -- package.loaded["illuminate"] - if hasilm then - ilm.on_attach(client) + if not package.loaded["illuminate"] then + vim.cmd [[silent! packadd vim-illuminate]] + local hasilm, ilm = pcall(require, "illuminate") -- package.loaded["illuminate"] + if hasilm then + ilm.on_attach(client) + end end - require "navigator.lspclient.lspkind".init() local capabilities = vim.lsp.protocol.make_client_capabilities() capabilities.textDocument.completion.completionItem.snippetSupport = true diff --git a/lua/navigator/lspclient/clients.lua b/lua/navigator/lspclient/clients.lua index 1661bf8..9d63faf 100644 --- a/lua/navigator/lspclient/clients.lua +++ b/lua/navigator/lspclient/clients.lua @@ -2,6 +2,8 @@ local log = require "navigator.util".log local verbose = require "navigator.util".verbose +_Loading = false + if packer_plugins ~= nil then if not packer_plugins["neovim/nvim-lspconfig"] or not packer_plugins["neovim/nvim-lspconfig"].loaded then vim.cmd [[packadd nvim-lspconfig]] @@ -240,15 +242,52 @@ local function load_cfg(ft, client, cfg, loaded) end end lspconfig[client].setup(cfg) - log(client, "loading for", ft, cfg) + log(client, "loading for", ft) end end -- need to verify the lsp server is up end +local function wait_lsp_startup(ft) + local clients = vim.lsp.get_active_clients() or {} + local loaded = {} + + for _, client in ipairs(clients) do + if client ~= nil then + table.insert(loaded, client.name) + end + end + for _, lspclient in ipairs(servers) do + local cfg = setups[lspclient] or default_cfg + load_cfg(ft, lspclient, cfg, loaded) + end + -- + local timer = vim.loop.new_timer() + local i = 0 + timer:start( + 200, + 200, + function() + clients = vim.lsp.get_active_clients() or {} + i = i + 1 + if i > 5 or #clients > 0 then + timer:close() -- Always close handles to avoid leaks. + log("active", #clients, i) + _Loading = false + return true + end + _Loading = false + end + ) +end + vim.cmd([[autocmd filetype * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ? + local function setup(user_opts) verbose(debug.traceback()) + if _Loading == true then + return + end if lspconfig == nil then error("lsp-config need installed and enabled") @@ -267,35 +306,7 @@ local function setup(user_opts) return end - for i = 1, 2 do - local clients = vim.lsp.get_active_clients() or {} - local loaded = {} - for _, client in ipairs(clients) do - if client ~= nil then - table.insert(loaded, client.name) - end - end - for _, lspclient in ipairs(servers) do - local cfg = setups[lspclient] or default_cfg - load_cfg(ft, lspclient, cfg, loaded) - end - - local timer = vim.loop.new_timer() - local i = 0 - -- Waits 20ms, then repeats every 20ms until lsp is loaded. - timer:start( - 20, - 20, - function() - local clients = vim.lsp.get_active_clients() or {} - if i > 20 or #clients > 0 then - timer:close() -- Always close handles to avoid leaks. - log("active", #clients) - return true - end - i = i + 1 - end - ) - end + _Loading = true + wait_lsp_startup(ft) end return {setup = setup, cap = cap} diff --git a/lua/navigator/lspclient/mapping.lua b/lua/navigator/lspclient/mapping.lua index 2a16e14..f339782 100644 --- a/lua/navigator/lspclient/mapping.lua +++ b/lua/navigator/lspclient/mapping.lua @@ -36,6 +36,8 @@ local key_maps = { {key = "gG", func = "require('navigator.diagnostics').show_diagnostic()"}, {key = "]d", func = "diagnostic.goto_next()"}, {key = "[d", func = "diagnostic.goto_prev()"}, + {key = "]r", func = "require('navigator.treesitter').goto_next_usage()"}, + {key = "[r", func = "require('navigator.treesitter').goto_previous_usage()"}, {key = "", func = "definition()"}, {key = "g", func = "implementation()"} } @@ -160,6 +162,7 @@ function M.setup(user_opts) if not hassig then vim.lsp.handlers["textDocument/signatureHelp"] = require "navigator.signature".signature_handler end + -- vim.lsp.handlers["textDocument/hover"] = require 'navigator.hover'.hover_handler end diff --git a/lua/navigator/reference.lua b/lua/navigator/reference.lua index d440194..678502c 100644 --- a/lua/navigator/reference.lua +++ b/lua/navigator/reference.lua @@ -2,7 +2,7 @@ local util = require "navigator.util" local log = util.log local lsphelper = require "navigator.lspwrapper" local gui = require "navigator.gui" -local lsp = require 'navigator.lspwrapper' +local lsp = require "navigator.lspwrapper" local verbose = require "navigator.util".verbose -- local log = util.log -- local partial = util.partial @@ -12,8 +12,6 @@ local locations_to_items = lsphelper.locations_to_items --vim.api.nvim_set_option("navtator_options", {width = 90, height = 60, location = require "navigator.location".center}) -- local options = vim.g.navtator_options or {width = 60, height = 40, location = location.center} - - local function ref_hdlr(arg1, api, locations, num, bufnr) local opts = {} -- log("arg1", arg1) @@ -27,17 +25,17 @@ local function ref_hdlr(arg1, api, locations, num, bufnr) end verbose(locations) local items = locations_to_items(locations) - gui.new_list_view({items = items, api = 'Reference'}) + gui.new_list_view({items = items, api = "Reference"}) end local async_reference_request = function() local method = {"textDocument/references"} local ref_params = vim.lsp.util.make_position_params() - ref_params.context = {includeDeclaration = true;} + ref_params.context = {includeDeclaration = true} return lsp.call_async(method[1], ref_params, ref_hdlr) -- return asyncresult, canceller end - -return { reference_handler = ref_hdlr, -show_reference = async_reference_request +return { + reference_handler = ref_hdlr, + show_reference = async_reference_request } diff --git a/lua/navigator/treesitter.lua b/lua/navigator/treesitter.lua index f55fab8..68b596f 100644 --- a/lua/navigator/treesitter.lua +++ b/lua/navigator/treesitter.lua @@ -2,6 +2,7 @@ local gui = require "navigator.gui" local ts_locals = require "nvim-treesitter.locals" local parsers = require "nvim-treesitter.parsers" local ts_utils = require "nvim-treesitter.ts_utils" +local utils = require "nvim-treesitter.utils" local api = vim.api local util = require "navigator.util" local M = {} @@ -37,22 +38,28 @@ local function get_definitions(bufnr) local nodes_set = {} for _, loc in ipairs(local_nodes) do if loc.definition then - ts_locals.recurse_local_nodes(loc.definition, function(_, node, _, match) - -- lua doesn't compare tables by value, - -- use the value from byte count instead. - local _, _, start = node:start() - nodes_set[start] = {node = node, type = match or ""} - end) + ts_locals.recurse_local_nodes( + loc.definition, + function(_, node, _, match) + -- lua doesn't compare tables by value, + -- use the value from byte count instead. + local _, _, start = node:start() + nodes_set[start] = {node = node, type = match or ""} + end + ) end end -- Sort by order of appearance. local definition_nodes = vim.tbl_values(nodes_set) - table.sort(definition_nodes, function (a, b) - local _, _, start_a = a.node:start() - local _, _, start_b = b.node:start() - return start_a < start_b - end) + table.sort( + definition_nodes, + function(a, b) + local _, _, start_a = a.node:start() + local _, _, start_b = b.node:start() + return start_a < start_b + end + ) return definition_nodes end @@ -80,6 +87,41 @@ local function prepare_node(node, kind) return matches end +local lsp_reference = require "navigator.dochighlight".goto_adjent_reference + +function M.goto_adjacent_usage(bufnr, delta) + local opt = {forward = true} + log(delta) + if delta < 0 then + opt = {forward = false} + end + local bufnr = bufnr or api.nvim_get_current_buf() + local node_at_point = ts_utils.get_node_at_cursor() + if not node_at_point then + lsp_reference(opt) + return + end + + local def_node, scope = ts_locals.find_definition(node_at_point, bufnr) + local usages = ts_locals.find_usages(def_node, scope, bufnr) + + local index = utils.index_of(usages, node_at_point) + if not index then + lsp_reference(opt) + return + end + + local target_index = (index + delta + #usages - 1) % #usages + 1 + ts_utils.goto_node(usages[target_index]) +end + +function M.goto_next_usage(bufnr) + return M.goto_adjacent_usage(bufnr, 1) +end +function M.goto_previous_usage(bufnr) + return M.goto_adjacent_usage(bufnr, -1) +end + local function get_all_nodes(bufnr) bufnr = bufnr or 0 if not parsers.has_parser() then @@ -130,8 +172,10 @@ local function get_all_nodes(bufnr) item.kind = node.kind item.node_scope = get_smallest_context(item.tsdata) local start_line_node, _, _ = item.tsdata:start() - item.node_text = ts_utils.get_node_text(item.tsdata, bufnr)[1] - if item.node_text == '_' then goto continue end + item.node_text = ts_utils.get_node_tex(item.tsdata, bufnr)[1] + if item.node_text == "_" then + goto continue + end item.full_text = vim.trim(api.nvim_buf_get_lines(bufnr, start_line_node, start_line_node + 1, false)[1] or "") item.range = ts_utils.node_to_lsp_range(item.tsdata) item.uri = uri @@ -141,8 +185,10 @@ local function get_all_nodes(bufnr) item.lnum, item.col, _ = def.node:start() item.lnum = item.lnum + 1 item.col = item.col + 1 - local indent="" - if #parents > 1 then indent = string.rep(' ', #parents - 1) .. ' ' end + local indent = "" + if #parents > 1 then + indent = string.rep(" ", #parents - 1) .. " " + end item.text = string.format("%s%s%-10s\t🧩 %s", item.kind, indent, item.node_text, item.full_text) if #item.text > length then diff --git a/lua/navigator/util.lua b/lua/navigator/util.lua index 51753d2..e02090d 100644 --- a/lua/navigator/util.lua +++ b/lua/navigator/util.lua @@ -95,7 +95,7 @@ local default_config = { plugin = "navigator", use_console = false, use_file = true, - level = "info" + level = "error" } M._log = require("guihua.log").new({level = default_config.level}, true) @@ -240,4 +240,42 @@ function M.exclude(fname) return false end +--- virtual text + +-- name space search +local nss +local api = vim.api +local bufs + +function M.set_virt_eol(bufnr, lnum, chunks, priority, id) + if nss == nil then + nss = api.nvim_create_namespace("navigator_search") + end + bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + bufs[bufnr] = true + -- id may be nil + return api.nvim_buf_set_extmark(bufnr, nss, lnum, -1, {id = id, virt_text = chunks, priority = priority}) +end + +function M.clear_buf(bufnr) + if not bufnr then + return + end + bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + if bufs[bufnr] then + if api.nvim_buf_is_valid(bufnr) then + api.nvim_buf_clear_namespace(bufnr, nss, 0, -1) + -- nvim_buf_del_extmark + end + bufs[bufnr] = nil + end +end + +function M.clear_all_buf() + for bufnr in pairs(bufs) do + M.clear_buf(bufnr) + end + bufs = {} +end + return M