From 777acc4b92e9cfd74bb23129c4482a9020ebe271 Mon Sep 17 00:00:00 2001 From: ray-x Date: Fri, 21 May 2021 21:39:46 +1000 Subject: [PATCH] merge treesitter branch. Improve reference search with treesitter scope. Better highlight in reference search result --- README.md | 131 +++++++++++++++--- lua/navigator.lua | 46 +++++-- lua/navigator/codeAction.lua | 4 +- lua/navigator/gui.lua | 141 +++++++++++++------- lua/navigator/hierarchy.lua | 48 +++---- lua/navigator/lspclient/attach.lua | 4 +- lua/navigator/lspclient/clients.lua | 92 +++++++++---- lua/navigator/lspclient/init.lua | 9 -- lua/navigator/lspclient/mapping.lua | 81 +++++++---- lua/navigator/lspwrapper.lua | 72 +++++++--- lua/navigator/reference.lua | 8 +- lua/navigator/render.lua | 149 +++++++++++++++++++++ lua/navigator/symbols.lua | 26 +--- lua/navigator/treesitter.lua | 200 ++++++++++++++++++++-------- lua/navigator/util.lua | 110 +++++++++++---- 15 files changed, 826 insertions(+), 295 deletions(-) delete mode 100644 lua/navigator/lspclient/init.lua create mode 100644 lua/navigator/render.lua diff --git a/README.md b/README.md index 06c526f..9571ab0 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,67 @@ # Navigator -Easy code navigation through LSP and 🌲🏡Treesitter symbols, diagnostic errors. +- Easy code navigation through LSP and 🌲🏡Treesitter symbols; view diagnostic errors. -![document symbol](https://github.com/ray-x/files/blob/master/img/navigator/doc_symbol.gif?raw=true) +- Put language server and tree sitter's parser together. Not only for better highlight but also display symbol context +and scope. + +Here is an example + +Show javascript call tree 🌲 of a variable inside a closure. Similar to incoming&outgoing calls from LSP. This feature +is designed for the symbol analysis. +![js_closure_call_tree](https://user-images.githubusercontent.com/1681295/119120589-cee23700-ba6f-11eb-95c5-b9ac8d445c31.jpg) + +Explains: +- There are 3 references for the symbol *browser* in closure.js +- The first reference of browser is an assigement, an emoji of 📝 indicates the value changed in this line. In many +cases, we search for reference to find out where the value changed. +- The second reference of `browser` is inside function `displayName` and `displayName` sit inside `makeFunc`, So you +will see ` displayName{} <- makeFunc{}` +- The third similar to the second, as var browser is on the right side of '=', the value not changed in this line +and emoji is not shown. + +Struct type references in multiple Go ﳑ files + +![go_reference](https://user-images.githubusercontent.com/1681295/119123823-54b3b180-ba73-11eb-8790-097601e10f6a.gif) + +This feature can provide you info in which function/class/method the variable was referenced. It is handy for large +project where class/function defination is too long to fit into preview window. Also provides a birdview of where the +variable is referenced. # Features: -- LSP easy setup. Support the most commonly used lsp clients setup. Dynamic lsp activation based on buffer type. +- LSP easy setup. Support the most commonly used lsp clients setup. Dynamic lsp activation based on buffer type. This +also enable you handle workspace combine mix types of codes (e.g. Go + javascript + yml) + - Out of box experience. 10 lines of minimum vimrc can turn your neovim into a full-featured LSP & Treesitter powered IDE -- Unorthodox UI with floating windows + +- Unorthodox UI with floating windows, navigator provides a visual way to manage and navigate through symbols, diagnostic errors, reference etc. Is covers +all features(handler) provided by LSP from commenly used search reference, to less commenly used search for interface +implementation. + - 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 + - Better navigation for diagnostic errors, Navigate through all files/buffers that contain errors/warnings -- Grouping references/implementation/incomming/outgoing based on file names. + +- Grouping references/implementation/incoming/outgoing based on file names. + +- Treesitter based variable/function context analysis. It is 10x times faster compared to purely rely on LSP. In most +of the case, it takes treesitter less than 4 ms to read and render all nodes for a file of 1,000 LOC. + +- The first plugin, IMO, that allows you to search in all treesitter symbols in the workspace. + - Nerdfont, emoji for LSP and Treesitter kind +- Optimize display (remove trailing bracket/space), display the caller of reference, de-duplicate lsp results (e.g reference +in the same line). Using treesitter for file preview highlighter etc + # Why a new plugin -After installed a handful of lsp plugins, I still got ~800 loc for lsp and treesitter and still increasing because I need -to tune the lsp plugins to fit my requirements. Navigator.lua help user setup lspconfig with only a few lines of codes. -This plugin provides a visual way to manage and navigate through symbols, diagnostic errors, reference etc. -It also the first plugin, IMO, that allows you to search in all treesitter symbols in the workspace. +I'd like to go beyond what the system is providing. # Similar projects / special mentions: @@ -30,11 +70,14 @@ It also the first plugin, IMO, that allows you to search in all treesitter symbo - [fuzzy](https://github.com/amirrezaask/fuzzy.nvim) - [lspsaga](https://github.com/glepnir/lspsaga.nvim) - [fzf-lsp lsp with fzf as gui backend](https://github.com/gfanto/fzf-lsp.nvim) +- [nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects) # Install +Require nvim-0.5.0 (a.k.a nightly) + You can remove your lspconfig setup and use this plugin. -The plugin depends on lspconfig and [guihua.lua](https://github.com/ray-x/guihua.lua), which provides GUI and fzy support(thanks [romgrk](romgrk/fzy-lua-native)). +The plugin depends on lspconfig and [guihua.lua](https://github.com/ray-x/guihua.lua), which provides GUI and fzy support(migrate from [romgrk's project](romgrk/fzy-lua-native)). ```vim Plug 'neovim/nvim-lspconfig' @@ -42,6 +85,8 @@ Plug 'ray-x/guihua.lua', {'do': 'cd lua/fzy && make' } Plug 'ray-x/navigator.lua' ``` +Note: Highly recommened: 'nvim-treesitter/nvim-treesitter' + Packer ```lua @@ -79,17 +124,56 @@ lua < or `:q!` to kill the floating window - (or \, \) to move -- \ to open location or apply code actions +- \ or \ to open location or apply code actions. Note: \ might be binded in insert mode by other plugins ## Configuration @@ -121,8 +205,7 @@ colorscheme: [aurora](https://github.com/ray-x/aurora) ### Reference -![reference](https://github.com/ray-x/files/blob/master/img/navigator/ref.gif?raw=true) - +Pls check first part of README ### Document Symbol @@ -132,7 +215,7 @@ 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 +# Current symbol highlight and jump backward/forward between symbols Document highlight provided by LSP. Jump between symbols between symbols with treesitter (with `]r` and `[r`) @@ -150,7 +233,7 @@ Show diagnostic in all buffers ### Implementation -![implementation](https://github.com/ray-x/files/blob/master/img/navigator/implemention.jpg?raw=true) +![implementation](https://user-images.githubusercontent.com/1681295/118735346-967e0580-b883-11eb-8c1e-88c5810f7e05.jpg?raw=true) ### Fzy search in reference @@ -160,17 +243,18 @@ Show diagnostic in all buffers ![code actions](https://github.com/ray-x/files/blob/master/img/navigator/codeaction.jpg?raw=true) -Fill struct with gopls +#### Fill struct with gopls + ![code actions fill struct](https://github.com/ray-x/files/blob/master/img/navigator/fill_struct.gif?raw=true) ### Code preview with highlight -![code preview](https://github.com/ray-x/files/blob/master/img/navigator/preview_with_hl.jpg?raw=true) +![treesitter_preview](https://user-images.githubusercontent.com/1681295/118900852-4bccbe00-b955-11eb-82f6-0747b1b64e7c.jpg) ### Treesitter symbol Treetsitter symbols in all buffers -![treesitter](https://github.com/ray-x/files/blob/master/img/navigator/treesitter.jpg?raw=true) +![treesitter](https://user-images.githubusercontent.com/1681295/118734953-cc6eba00-b882-11eb-9db8-0a052630d57e.jpg?raw=true) ### Signature help @@ -184,7 +268,7 @@ Improved signature help with current parameter highlighted ![incomming](https://github.com/ray-x/files/blob/master/img/navigator/incomming.jpg?raw=true) -### Light bulb when codeAction avalible +### Light bulb if codeAction available ![lightbulb](https://github.com/ray-x/files/blob/master/img/navigator/lightbulb.jpg?raw=true) @@ -192,6 +276,9 @@ Improved signature help with current parameter highlighted ![nerdfont](https://github.com/ray-x/files/blob/master/img/navigator/icon_nerd.jpg?raw=true) +# Break changes and known issues +[known issues I am working on](https://github.com/ray-x/navigator.lua/issues/1) + # Todo - Early phase, bugs expected, PR and suggestions are welcome diff --git a/lua/navigator.lua b/lua/navigator.lua index e70fe10..c0adf2e 100644 --- a/lua/navigator.lua +++ b/lua/navigator.lua @@ -2,7 +2,8 @@ local M = {} _NgConfigValues = { debug = false, -- log output not implemented code_action_icon = " ", - width = nil, -- valeu of cols TODO allow float e.g. 0.6 + width = 0.6, -- valeu of cols TODO allow float e.g. 0.6 + preview_height = 0.35, height = nil, on_attach = nil, -- function(client, bufnr) @@ -11,8 +12,15 @@ _NgConfigValues = { sumneko_root_path = vim.fn.expand("$HOME") .. "/github/sumneko/lua-language-server", sumneko_binary = vim.fn.expand("$HOME") .. "/github/sumneko/lua-language-server/bin/macOS/lua-language-server", - treesitter_call_tree = true, - code_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true} + code_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true}, + treesitter_call_tree = true, -- treesitter variable context + lsp = { + format_on_save = true, -- set to false to disasble lsp code format on save (if you are using prettier/efm/formater etc) + tsserver = { + filetypes = {'typescript'} -- disable javascript etc, + -- set to {} to disable the lspclient for all filetype + } + } } vim.cmd("command! -nargs=0 LspLog call v:lua.open_lsp_log()") @@ -20,26 +28,36 @@ vim.cmd("command! -nargs=0 LspRestart call v:lua.reload_lsp()") local extend_config = function(opts) opts = opts or {} - if next(opts) == nil then return end + if next(opts) == nil then + return + end for key, value in pairs(opts) do - if _NgConfigValues[key] == nil then - error(string.format("[] Key %s not valid", key)) - return - end - if type(M.config_values[key]) == "table" then - for k, v in pairs(value) do _NgConfigValues[key][k] = v end + -- if _NgConfigValues[key] == nil then + -- error(string.format("[] Key %s not valid", key)) + -- return + -- end + if type(_NgConfigValues[key]) == "table" then + for k, v in pairs(value) do + _NgConfigValues[key][k] = v + end else _NgConfigValues[key] = value end end end -M.config_values = function() return _NgConfigValues end +M.config_values = function() + return _NgConfigValues +end M.setup = function(cfg) extend_config(cfg) + -- local log = require"navigator.util".log + -- log(debug.traceback()) + -- log(cfg, _NgConfigValues) -- print("loading navigator") - require("navigator.lspclient").setup(_NgConfigValues) + require('navigator.lspclient.clients').setup(_NgConfigValues) + require("navigator.lspclient.mapping").setup(_NgConfigValues) require("navigator.reference") require("navigator.definition") require("navigator.hierarchy") @@ -49,6 +67,10 @@ M.setup = function(cfg) vim.cmd [[autocmd CursorHold,CursorHoldI * lua require'navigator.codeAction'.code_action_prompt()]] end -- vim.cmd("autocmd BufNewFile,BufRead *.go setlocal noexpandtab tabstop=4 shiftwidth=4") + if not _NgConfigValues.loaded then + vim.cmd([[autocmd FileType * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ? + _NgConfigValues.loaded = true + end end return M diff --git a/lua/navigator/codeAction.lua b/lua/navigator/codeAction.lua index 33a7ad0..b6be639 100644 --- a/lua/navigator/codeAction.lua +++ b/lua/navigator/codeAction.lua @@ -1,6 +1,6 @@ local util = require "navigator.util" local log = util.log -local verbose = util.verbose +local trace = util.trace local code_action = {} local gui = require "navigator.gui" local config = require("navigator").config_values() @@ -42,7 +42,7 @@ function code_action.code_action_handler(err, _, actions, cid, bufnr, _, customS vim.lsp.buf.execute_command(action_chosen) end - verbose(action_chosen) + trace(action_chosen) end gui.new_list_view { diff --git a/lua/navigator/gui.lua b/lua/navigator/gui.lua index b4e2fcf..9bccbe7 100644 --- a/lua/navigator/gui.lua +++ b/lua/navigator/gui.lua @@ -3,29 +3,10 @@ local ListView = require "guihua.listview" local TextView = require "guihua.textview" local util = require "navigator.util" local log = require"navigator.util".log -local verbose = require"navigator.util".verbose +local trace = require"navigator.util".trace local api = vim.api -function M.new_preview(opts) - return TextView:new({ - loc = "top_center", - rect = { - height = opts.height, -- opts.preview_heigh or 12, -- TODO 12 - width = opts.width or 100, - pos_x = opts.pos_x or 0, - pos_y = opts.pos_y or 4 - }, - -- data = display_data, - relative = opts.relative, - -- data = opts.items, -- either items or uri - uri = opts.uri, - syntax = opts.syntax, - enter = opts.enter or false, - range = opts.range, - display_range = opts.display_range, - hl_line = opts.hl_line - }) -end +local top_center = require"guihua.location".top_center function M._preview_location(opts) -- location, width, pos_x, pos_y local uri = opts.location.uri @@ -34,7 +15,9 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y return end local bufnr = vim.uri_to_bufnr(uri) - if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) end + if not api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end -- local display_range = opts.location.range @@ -56,15 +39,18 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y -- local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line, false) -- local syntax = api.nvim_buf_get_option(bufnr, "ft") - if syntax == nil or #syntax < 1 then syntax = "c" end + if syntax == nil or #syntax < 1 then + syntax = "c" + end - -- verbose(syntax, contents) + -- trace(syntax, contents) local win_opts = { syntax = syntax, width = opts.width, height = display_range['end'].line - display_range.start.line + 1, - pos_x = opts.offset_x or 0, - pos_y = opts.offset_y or 10, + preview_height = opts.preview_height, + pos_x = opts.offset_x, + pos_y = opts.offset_y, range = opts.range, display_range = display_range, uri = uri, @@ -72,23 +58,44 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y } -- win_opts.items = contents win_opts.hl_line = opts.lnum - display_range.start.line - if win_opts.hl_line < 0 then win_opts.hl_line = 1 end - verbose(opts.lnum, opts.range.start.line, win_opts.hl_line) - local w = M.new_preview(win_opts) - + if win_opts.hl_line < 0 then + win_opts.hl_line = 1 + end + trace(opts.lnum, opts.range.start.line, win_opts.hl_line) + local w = TextView:new({ + loc = "offset_center", + rect = { + height = win_opts.height, -- opts.preview_heigh or 12, -- TODO 12 + width = win_opts.width, + pos_x = win_opts.pos_x, + pos_y = win_opts.pos_y + }, + list_view_height = win_opts.height, + -- data = display_data, + relative = win_opts.relative, + -- data = opts.items, -- either items or uri + uri = win_opts.uri, + syntax = win_opts.syntax, + enter = win_opts.enter or false, + range = win_opts.range, + display_range = win_opts.display_range, + hl_line = win_opts.hl_line + }) return w end function M.preview_uri(opts) -- uri, width, line, col, offset_x, offset_y local line_beg = opts.lnum - 1 - if line_beg >= 2 then line_beg = line_beg - 2 end + if line_beg >= 2 then + line_beg = line_beg - 2 + end local loc = {uri = opts.uri, range = {start = {line = line_beg}}} -- TODO: preview height - loc.range["end"] = {line = opts.lnum + 12} + loc.range["end"] = {line = opts.lnum + opts.preview_height} opts.location = loc - log("uri", opts.uri, opts.lnum, opts.location) + trace("uri", opts.uri, opts.lnum, opts.location.range.start.line, opts.location.range['end'].line) return M._preview_location(opts) end @@ -99,58 +106,94 @@ function M.new_list_view(opts) local data = {} local wwidth = api.nvim_get_option("columns") - local width = math.min(opts.width or config.width or 120, math.floor(wwidth * 0.8)) + + local loc = "top_center" + local width = math.floor(wwidth * 0.75) + if config.width ~= nil and config.width > 0.3 and config.width < 0.99 then + width = math.floor(wwidth * config.width) + end + width = math.min(opts.width or 120, width) + opts.width = width local wheight = config.height or math.floor(api.nvim_get_option("lines") * 0.8) local prompt = opts.prompt or false - opts.width = width if opts.rawdata then data = items else - data = require"guihua.util".prepare_for_render(items, opts) + data = require"navigator.render".prepare_for_render(items, opts) end + local border = _NgConfigValues.border or 'shadow' + if data and not vim.tbl_isempty(data) then -- replace -- TODO: 10 vimrc opt - if #data > 10 and opts.prompt == nil then prompt = true end + if #data > 10 and opts.prompt == nil then + loc = "top_center" + prompt = true + end + + local lheight = math.min(#data, math.floor(wheight / 2)) + local pheight = math.min(wheight - lheight, math.floor(wheight / 2)) + + local r, _ = top_center(lheight, width) + + local offset_y = r + lheight + -- style shadow took 1 lines + if border ~= 'none' then + if border == 'shadow' then + offset_y = offset_y + 1 + else + offset_y = offset_y + 1 -- single? + end + end + -- if border is not set, this should be r+lheigh + if prompt then + offset_y = offset_y + 1 -- need to check this out + end - local height = math.min(#data, math.floor(wheight / 2)) - local offset_y = height + 2 -- style shadow took 2 lines - if prompt then offset_y = offset_y + 1 end return ListView:new({ - loc = "top_center", + loc = loc, prompt = prompt, relative = opts.relative, style = opts.style, api = opts.api, - rect = {height = height, width = width, pos_x = 0, pos_y = 0}, + rect = {height = lheight, width = width, pos_x = 0, pos_y = 0}, + -- preview_height = pheight, ft = opts.ft or 'guihua', -- data = display_data, data = data, on_confirm = opts.on_confirm or function(pos) - if pos == 0 then pos = 1 end + if pos == 0 then + pos = 1 + end local l = data[pos] if l.filename ~= nil then - verbose("openfile ", l.filename, l.lnum, l.col) + trace("openfile ", l.filename, l.lnum, l.col) util.open_file_at(l.filename, l.lnum, l.col) end end, on_move = opts.on_move or function(pos) - if pos == 0 then pos = 1 end + if pos == 0 then + pos = 1 + end local l = data[pos] - verbose("on move", pos, l) - verbose("on move", pos, l.text or l, l.uri, l.filename) + trace("on move", pos, l) + trace("on move", pos, l.text or l, l.uri, l.filename) -- todo fix - if l.uri == nil then l.uri = "file:///" .. l.filename end + if l.uri == nil then + l.uri = "file:///" .. l.filename + end return M.preview_uri({ uri = l.uri, width = width, + height = lheight, -- this is to cal offset + preview_height = pheight, lnum = l.lnum, col = l.col, range = l.range, offset_x = 0, offset_y = offset_y, - border = "double" + border = border }) end }) diff --git a/lua/navigator/hierarchy.lua b/lua/navigator/hierarchy.lua index 2186b62..dcc4a49 100644 --- a/lua/navigator/hierarchy.lua +++ b/lua/navigator/hierarchy.lua @@ -6,25 +6,22 @@ local lsphelper = require "navigator.lspwrapper" local cwd = vim.fn.getcwd(0) local M = {} -local function call_hierarchy_handler(direction, err, _, result, _, _, - error_message) +local function call_hierarchy_handler(direction, err, _, result, _, _, error_message) -- log('call_hierarchy') - assert(#vim.lsp.buf_get_clients() > 0, - "Must have a client running to use lsp_tags") + assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags") if err ~= nil then + log("dir", direction, "result", result, "err", err) print("ERROR: " .. error_message) return end - -- log("dir", direction, "result", result) local items = {} for _, call_hierarchy_call in pairs(result) do local call_hierarchy_item = call_hierarchy_call[direction] local kind = ' ' if call_hierarchy_item.kind then - kind = require'navigator.lspclient.lspkind'.symbol_kind( - call_hierarchy_item.kind) .. ' ' + kind = require'navigator.lspclient.lspkind'.symbol_kind(call_hierarchy_item.kind) .. ' ' end for _, range in pairs(call_hierarchy_call.fromRanges) do local filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)) @@ -47,21 +44,18 @@ end local call_hierarchy_handler_from = partial(call_hierarchy_handler, "from") local call_hierarchy_handler_to = partial(call_hierarchy_handler, "to") -local function incoming_calls_handler(bang, err, method, result, client_id, - bufnr) - assert(#vim.lsp.buf_get_clients() > 0, - "Must have a client running to use lsp_tags") - local results = call_hierarchy_handler_from(err, method, result, client_id, - bufnr, "Incoming calls not found") +local function incoming_calls_handler(bang, err, method, result, client_id, bufnr) + assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags") + local results = call_hierarchy_handler_from(err, method, result, client_id, bufnr, + "Incoming calls not found") local ft = vim.api.nvim_buf_get_option(bufnr, "ft") gui.new_list_view({items = results, ft = ft, api = ' '}) end -local function outgoing_calls_handler(bang, err, method, result, client_id, - bufnr) - local results = call_hierarchy_handler_to(err, method, result, client_id, - bufnr, "Outgoing calls not found") +local function outgoing_calls_handler(bang, err, method, result, client_id, bufnr) + local results = call_hierarchy_handler_to(err, method, result, client_id, bufnr, + "Outgoing calls not found") local ft = vim.api.nvim_buf_get_option(bufnr, "ft") gui.new_list_view({items = results, ft = ft, api = ' '}) @@ -69,23 +63,23 @@ local function outgoing_calls_handler(bang, err, method, result, client_id, end function M.incoming_calls(bang, opts) - assert(#vim.lsp.buf_get_clients() > 0, - "Must have a client running to use lsp_tags") - if not lsphelper.check_capabilities("call_hierarchy") then return end + assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags") + if not lsphelper.check_capabilities("call_hierarchy") then + return + end local params = vim.lsp.util.make_position_params() - util.call_sync("callHierarchy/incomingCalls", params, opts, - partial(incoming_calls_handler, bang)) + util.call_sync("callHierarchy/incomingCalls", params, opts, partial(incoming_calls_handler, bang)) end function M.outgoing_calls(bang, opts) - assert(#vim.lsp.buf_get_clients() > 0, - "Must have a client running to use lsp_tags") - if not lsphelper.check_capabilities("call_hierarchy") then return end + assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags") + if not lsphelper.check_capabilities("call_hierarchy") then + return + end local params = vim.lsp.util.make_position_params() - util.call_sync("callHierarchy/outgoingCalls", params, opts, - partial(outgoing_calls_handler, bang)) + util.call_sync("callHierarchy/outgoingCalls", params, opts, partial(outgoing_calls_handler, bang)) end M.incoming_calls_call = partial(M.incoming_calls, 0) diff --git a/lua/navigator/lspclient/attach.lua b/lua/navigator/lspclient/attach.lua index ec39633..163d3de 100644 --- a/lua/navigator/lspclient/attach.lua +++ b/lua/navigator/lspclient/attach.lua @@ -3,7 +3,7 @@ local lsp = require("vim.lsp") local util = require "navigator.util" local log = util.log -local verbose = util.verbose +local trace = util.trace local diagnostic_map = function(bufnr) local opts = {noremap = true, silent = true} @@ -20,7 +20,7 @@ M.on_attach = function(client, bufnr) return end log("attaching", bufnr) - verbose(client) + trace(client) local hassig, sig = pcall(require, "lsp_signature") if hassig then sig.on_attach() end diagnostic_map(bufnr) diff --git a/lua/navigator/lspclient/clients.lua b/lua/navigator/lspclient/clients.lua index e6174fb..e3cf3ba 100644 --- a/lua/navigator/lspclient/clients.lua +++ b/lua/navigator/lspclient/clients.lua @@ -1,6 +1,6 @@ -- todo allow config passed in local log = require"navigator.util".log -local verbose = require"navigator.util".verbose +local trace = require"navigator.util".trace _Loading = false @@ -8,7 +8,9 @@ if packer_plugins ~= nil then -- packer installed local loader = require"packer".loader if not packer_plugins["neovim/nvim-lspconfig"] or - not packer_plugins["neovim/nvim-lspconfig"].loaded then loader("nvim-lspconfig") end + not packer_plugins["neovim/nvim-lspconfig"].loaded then + loader("nvim-lspconfig") + end if not packer_plugins["ray-x/guihua.lua"] or not packer_plugins["guihua.lua"].loaded then loader("guihua.lua") -- if lazyloading @@ -16,13 +18,15 @@ if packer_plugins ~= nil then end local has_lsp, lspconfig = pcall(require, "lspconfig") -if not has_lsp then error("loading lsp config") end +if not has_lsp then + error("loading lsp config") +end local highlight = require "navigator.lspclient.highlight" local util = lspconfig.util local config = require"navigator".config_values() -local cap = vim.lsp.protocol.make_client_capabilities() +-- local cap = vim.lsp.protocol.make_client_capabilities() local on_attach = require("navigator.lspclient.attach").on_attach -- gopls["ui.completion.usePlaceholders"] = true @@ -48,7 +52,9 @@ add("$VIMRUNTIME") -- add your config local home = vim.fn.expand("$HOME") -if vim.fn.isdirectory(home .. "/.config/nvim") then add(home .. "/.config/nvim") end +if vim.fn.isdirectory(home .. "/.config/nvim") then + add(home .. "/.config/nvim") +end -- add plugins it may be very slow to add all in path -- if vim.fn.isdirectory(home .. "/.config/share/nvim/site/pack/packer") then @@ -64,7 +70,7 @@ library[vim.fn.expand("$VIMRUNTIME/lua/vim/lsp")] = true local setups = { gopls = { on_attach = on_attach, - capabilities = cap, + -- capabilities = cap, filetypes = {"go", "gomod"}, message_level = vim.lsp.protocol.MessageType.Error, cmd = { @@ -86,8 +92,9 @@ local setups = { completeUnimported = true, staticcheck = true, matcher = "fuzzy", + experimentalDiagnosticsDelay = "500ms", symbolMatcher = "fuzzy", - gofumpt = true, + gofumpt = false, -- true, -- turn on for new repos, gofmpt is good but also create code turmoils buildFlags = {"-tags", "integration"} -- buildFlags = {"-tags", "functional"} } @@ -204,22 +211,25 @@ local default_cfg = {on_attach = on_attach} -- check and load based on file type local function load_cfg(ft, client, cfg, loaded) - -- log("trying", client) if lspconfig[client] == nil then - log("not supported", client) + log("not supported by nvim", client) return end local lspft = lspconfig[client].document_config.default_config.filetypes local should_load = false if lspft ~= nil and #lspft > 0 then - for _, value in ipairs(lspft) do if ft == value then should_load = true end end + for _, value in ipairs(lspft) do + if ft == value then + should_load = true + end + end if should_load then for _, c in pairs(loaded) do if client == c then -- loaded - verbose(client, "already been loaded for", ft, loaded) + trace(client, "already been loaded for", ft, loaded) return end end @@ -230,19 +240,36 @@ local function load_cfg(ft, client, cfg, loaded) -- need to verify the lsp server is up end -local function wait_lsp_startup(ft, retry) +local function wait_lsp_startup(ft, retry, lsp_opts) retry = retry or false local clients = vim.lsp.get_active_clients() or {} local loaded = {} - for i = 1, 2 do + for _ = 1, 2 do for _, client in ipairs(clients) do - if client ~= nil then table.insert(loaded, client.name) end + if client ~= nil then + table.insert(loaded, client.name) + end end for _, lspclient in ipairs(servers) do + if lsp_opts[lspclient] ~= nil and lsp_opts[lspclient].filetypes ~= nil then + if not vim.tbl_contains(lsp_opts[lspclient].filetypes, ft) then + trace("ft", ft, "disabled for", lspclient) + goto continue + end + end local cfg = setups[lspclient] or default_cfg + -- if user provides override values + -- if lsp_opts[lspclient] ~= nil and lsp_opts[lspclient] ~= nil then + -- local ret = vim.tbl_extend("force", cfg, lsp_opts[lspclient]) + -- log(lsp_opts[lspclient].settings, cfg, ret) + -- end + load_cfg(ft, lspclient, cfg, loaded) + ::continue:: + end + if not retry or ft == nil then + return end - if not retry or ft == nil then return end -- local timer = vim.loop.new_timer() local i = 0 @@ -251,7 +278,7 @@ local function wait_lsp_startup(ft, retry) i = i + 1 if i > 5 or #clients > 0 then timer:close() -- Always close handles to avoid leaks. - verbose("active", #clients, i) + trace("active", #clients, i) _Loading = false return true end @@ -260,18 +287,19 @@ local function wait_lsp_startup(ft, retry) end end -vim.cmd([[autocmd FileType * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ? - local function setup(user_opts) - verbose(debug.traceback()) - if lspconfig == nil then - error("lsp-config need installed and enabled") + + log(user_opts) + trace(debug.traceback()) + user_opts = user_opts or _NgConfigValues -- incase setup was triggered from autocmd + + if _Loading == true then return end - - if _Loading == true then return end local ft = vim.bo.filetype - if ft == nil then ft = vim.api.nvim_buf_get_option(0, "filetype") end + if ft == nil then + ft = vim.api.nvim_buf_get_option(0, "filetype") + end if ft == nil or ft == "" then log("nil filetype") @@ -284,24 +312,32 @@ local function setup(user_opts) } for i = 1, #disable_ft do if ft == disable_ft[i] then - log("navigator disabled for ft", ft) + trace("navigator disabled for ft", ft) return end end + local bufnr = vim.fn.bufnr() local uri = vim.uri_from_bufnr(bufnr) - log("loading for ft ", ft, uri) if uri == 'file://' or uri == 'file:///' then log("skip loading for ft ", ft, uri) return end + log('setup', user_opts) + log("loading for ft ", ft, uri) highlight.diagnositc_config_sign() highlight.add_highlight() - + local lsp_opts = user_opts.lsp _Loading = true - wait_lsp_startup(ft, retry) + wait_lsp_startup(ft, retry, lsp_opts) + _Loading = false + + -- if not _NgConfigValues.loaded then + -- vim.cmd([[autocmd FileType * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ? + -- _NgConfigValues.loaded = true + -- end end return {setup = setup, cap = cap} diff --git a/lua/navigator/lspclient/init.lua b/lua/navigator/lspclient/init.lua deleted file mode 100644 index 9b25568..0000000 --- a/lua/navigator/lspclient/init.lua +++ /dev/null @@ -1,9 +0,0 @@ --- local log = require "navigator.util".log -local M = {} -M.setup = function(cfg) - cfg = cfg or {} - require('navigator.lspclient.clients').setup(cfg) - require("navigator.lspclient.mapping").setup(cfg) -end - -return M diff --git a/lua/navigator/lspclient/mapping.lua b/lua/navigator/lspclient/mapping.lua index 96fdd11..89ad603 100644 --- a/lua/navigator/lspclient/mapping.lua +++ b/lua/navigator/lspclient/mapping.lua @@ -1,5 +1,7 @@ local log = require"navigator.util".log -local function set_keymap(...) vim.api.nvim_set_keymap(...) end +local function set_keymap(...) + vim.api.nvim_set_keymap(...) +end local event_hdlrs = { {ev = "BufWritePre", func = "diagnostic.set_loclist({open_loclist = false})"}, @@ -9,23 +11,31 @@ local event_hdlrs = { } local key_maps = { - {key = "gr", func = "references()"}, {mode = "i", key = "", func = "signature_help()"}, - {key = "gs", func = "signature_help()"}, {key = "g0", func = "document_symbol()"}, - {key = "gW", func = "workspace_symbol()"}, {key = "", func = "definition()"}, - {key = "gD", func = "declaration()"}, + {key = "gr", func = "references()"}, + {mode = "i", key = "", func = "signature_help()"}, + {key = "gs", func = "signature_help()"}, + {key = "g0", func = "document_symbol()"}, + {key = "gW", func = "workspace_symbol()"}, + {key = "", func = "definition()"}, {key = "gD", func = "declaration()"}, {key = "gp", func = "require('navigator.definition').definition_preview()"}, {key = "gT", func = "require('navigator.treesitter').buf_ts()"}, - {key = "GT", func = "require('navigator.treesitter').bufs_ts()"}, {key = "K", func = "hover()"}, + {key = "GT", func = "require('navigator.treesitter').bufs_ts()"}, + {key = "K", func = "hover()"}, {key = "ga", mode = "n", func = "code_action()"}, - {key = "ga", mode = "v", func = "range_code_action()"}, {key = "re", func = "rename()"}, - {key = "gi", func = "incoming_calls()"}, {key = "go", func = "outgoing_calls()"}, - {key = "gi", func = "implementation()"}, {key = "gt", func = "type_definition()"}, + {key = "ga", mode = "v", func = "range_code_action()"}, + {key = "re", func = "rename()"}, + {key = "gi", func = "incoming_calls()"}, + {key = "go", func = "outgoing_calls()"}, + {key = "gi", func = "implementation()"}, + {key = "gt", func = "type_definition()"}, {key = "gL", func = "diagnostic.show_line_diagnostics()"}, {key = "gG", func = "require('navigator.diagnostics').show_diagnostic()"}, - {key = "]d", func = "diagnostic.goto_next()"}, {key = "[d", func = "diagnostic.goto_prev()"}, + {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()"} + {key = "", func = "definition()"}, + {key = "g", func = "implementation()"} } local function set_mapping(user_opts) @@ -35,7 +45,9 @@ local function set_mapping(user_opts) local user_key = user_opts.keymaps or {} local bufnr = user_opts.bufnr or 0 - local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end + local function buf_set_keymap(...) + vim.api.nvim_buf_set_keymap(bufnr, ...) + end -- local function buf_set_option(...) -- vim.api.nvim_buf_set_option(bufnr, ...) @@ -71,23 +83,30 @@ local function set_mapping(user_opts) for _, value in pairs(vim.lsp.buf_get_clients(0)) do if value == nil or value.resolved_capabilities == nil then return end if value.resolved_capabilities.document_formatting then doc_fmt = true end - if value.resolved_capabilities.document_range_formatting then range_fmt = true end + if value.resolved_capabilities.document_range_formatting then + range_fmt = true + end end -- if user_opts.cap.document_formatting then if doc_fmt then - buf_set_keymap("n", "ff", "lua vim.lsp.buf.formatting()", opts) - vim.cmd([[autocmd BufWritePre lua vim.lsp.buf.formatting()]]) + buf_set_keymap("n", "ff", "lua vim.lsp.buf.formatting()", + opts) + if _NgConfigValues.lsp.format_on_save then + vim.cmd([[autocmd BufWritePre lua vim.lsp.buf.formatting()]]) + end end -- if user_opts.cap.document_range_formatting then if range_fmt then - buf_set_keymap("v", "ff", "lua vim.lsp.buf.range_formatting()", opts) + buf_set_keymap("v", "ff", + "lua vim.lsp.buf.range_formatting()", opts) end end local function set_event_handler(user_opts) user_opts = user_opts or {} - local file_types = "c,cpp,h,go,python,vim,sh,javascript,html,css,lua,typescript,rust" + local file_types = + "c,cpp,h,go,python,vim,sh,javascript,html,css,lua,typescript,rust,javascriptreact,typescriptreact,json,yaml,kotlin,php,dart,nim,terraform" -- local format_files = "c,cpp,h,go,python,vim,javascript,typescript" --html,css, vim.api.nvim_command [[augroup nvim_lsp_autos]] vim.api.nvim_command [[autocmd!]] @@ -99,8 +118,9 @@ local function set_event_handler(user_opts) else f = "lua vim.lsp.buf." .. value.func end - local cmd = "autocmd FileType " .. file_types .. " autocmd nvim_lsp_autos " .. value.ev .. - " silent! " .. f + local cmd = + "autocmd FileType " .. file_types .. " autocmd nvim_lsp_autos " .. + value.ev .. " silent! " .. f vim.api.nvim_command(cmd) end vim.api.nvim_command([[augroup END]]) @@ -109,12 +129,11 @@ end local M = {} function M.setup(user_opts) - user_opts = user_opts or {} - user_opts.cap = vim.lsp.protocol.make_client_capabilities() set_mapping(user_opts) set_event_handler(user_opts) - local cap = user_opts.cap or {} + + local cap = user_opts.cap or vim.lsp.protocol.make_client_capabilities() if cap.call_hierarchy or cap.callHierarchy then vim.lsp.handlers["callHierarchy/incomingCalls"] = require"navigator.hierarchy".incoming_calls_handler @@ -122,12 +141,16 @@ function M.setup(user_opts) require"navigator.hierarchy".outgoing_calls_handler end - vim.lsp.handlers["textDocument/references"] = require"navigator.reference".reference_handler - vim.lsp.handlers["textDocument/codeAction"] = require"navigator.codeAction".code_action_handler - vim.lsp.handlers["textDocument/definition"] = require"navigator.definition".definition_handler + vim.lsp.handlers["textDocument/references"] = + require"navigator.reference".reference_handler + vim.lsp.handlers["textDocument/codeAction"] = + require"navigator.codeAction".code_action_handler + vim.lsp.handlers["textDocument/definition"] = + require"navigator.definition".definition_handler if cap.declaration then - vim.lsp.handlers["textDocument/declaration"] = require"navigator.definition".declaration_handler + vim.lsp.handlers["textDocument/declaration"] = + require"navigator.definition".declaration_handler end vim.lsp.handlers["textDocument/typeDefinition"] = @@ -137,13 +160,15 @@ function M.setup(user_opts) vim.lsp.handlers["textDocument/documentSymbol"] = require"navigator.symbols".document_symbol_handler - vim.lsp.handlers["workspace/symbol"] = require"navigator.symbols".workspace_symbol_handler + vim.lsp.handlers["workspace/symbol"] = + require"navigator.symbols".workspace_symbol_handler vim.lsp.handlers["textDocument/publishDiagnostics"] = require"navigator.diagnostics".diagnostic_handler local hassig, sig = pcall(require, "lsp_signature") if not hassig then - vim.lsp.handlers["textDocument/signatureHelp"] = require"navigator.signature".signature_handler + vim.lsp.handlers["textDocument/signatureHelp"] = + require"navigator.signature".signature_handler end -- vim.lsp.handlers["textDocument/hover"] = require 'navigator.hover'.hover_handler diff --git a/lua/navigator/lspwrapper.lua b/lua/navigator/lspwrapper.lua index c8d5088..8843841 100644 --- a/lua/navigator/lspwrapper.lua +++ b/lua/navigator/lspwrapper.lua @@ -4,16 +4,30 @@ local gutil = require "guihua.util" local lsp = require "vim.lsp" local api = vim.api local log = require"navigator.util".log -local verbose = require"navigator.util".verbose +local trace = require"navigator.util".trace local symbol_kind = require"navigator.lspclient.lspkind".symbol_kind local cwd = vim.fn.getcwd(0) cwd = gutil.add_pec(cwd) ts_nodes = {} ts_nodes_time = {} -local ts_enabled, ts_locals = pcall(require, "nvim-treesitter.locals") +local ts_enabled, _ = pcall(require, "nvim-treesitter.locals") local calltree_enabled = require"navigator".config_values().treesitter_call_tree +-- extract symbol from range +local function get_symbol(text, range) + if range == nil then + return "" + end + return string.sub(text, range.start.character + 1, range['end'].character) +end + +local function check_lhs(text, symbol) + local s = string.find(text, symbol) + local eq = string.find(text, '=') or 0 + return s < eq +end + function M.lines_from_locations(locations, include_filename) local fnamemodify = (function(filename) if include_filename then @@ -43,7 +57,9 @@ function M.symbols_to_items(result) local kind = symbol_kind(item.kind) item.name = result[i].name -- symbol name item.text = result[i].name - if kind ~= nil then item.text = kind .. ": " .. item.text end + if kind ~= nil then + item.text = kind .. ": " .. item.text + end item.filename = vim.uri_to_fname(item.uri) item.display_filename = item.filename:gsub(cwd .. "/", "./", 1) @@ -52,7 +68,9 @@ function M.symbols_to_items(result) end item.lnum = item.range.start.line + 1 - if item.containerName ~= nil then item.text = " " .. item.containerName .. item.text end + if item.containerName ~= nil then + item.text = " " .. item.containerName .. item.text + end table.insert(locations, item) end end @@ -65,9 +83,10 @@ local function extract_result(results_lsp) if results_lsp then local results = {} for _, server_results in pairs(results_lsp) do - if server_results.result then vim.list_extend(results, server_results.result) end + if server_results.result then + vim.list_extend(results, server_results.result) + end end - return results end end @@ -78,7 +97,9 @@ function M.check_capabilities(feature, client_id) local supported_client = false for _, client in pairs(clients) do supported_client = client.resolved_capabilities[feature] - if supported_client then goto continue end + if supported_client then + goto continue + end end ::continue:: @@ -114,15 +135,21 @@ function M.call_async(method, params, handler) end local function ts_functions(uri) - if not ts_enabled or not calltree_enabled then return nil end + if not ts_enabled or not calltree_enabled then + return nil + end local ts_func = require"navigator.treesitter".buf_func local bufnr = vim.uri_to_bufnr(uri) local x = os.clock() + trace(ts_nodes) if ts_nodes[uri] ~= nil then local t = ts_nodes_time[uri] local fname = vim.uri_to_fname(uri) local modified = vim.fn.getftime(fname) - if modified < t then return ts_nodes[uri] end + if modified <= t then + trace(t, modified) + return ts_nodes[uri] + end end local unload = false if not api.nvim_buf_is_loaded(bufnr) then @@ -139,20 +166,24 @@ local function ts_functions(uri) end ts_nodes[uri] = funcs ts_nodes_time[uri] = os.time() - verbose(funcs) + trace(funcs, ts_nodes) log(string.format("elapsed time: %.4f\n", os.clock() - x)) return funcs end local function find_ts_func_by_range(funcs, range) - if funcs == nil or range == nil then return nil end + if funcs == nil or range == nil then + return nil + end local result = {} - verbose(funcs, range) + trace(funcs, range) for _, value in pairs(funcs) do local func_range = value.node_scope -- note treesitter is C style - if func_range and func_range.start.line + 1 <= range.start.line and func_range['end'].line + 1 >= - range['end'].line then table.insert(result, value) end + if func_range and func_range.start.line <= range.start.line and func_range['end'].line >= + range['end'].line then + table.insert(result, value) + end end return result end @@ -168,7 +199,9 @@ function M.locations_to_items(locations) -- items and locations may not matching table.sort(locations, function(i, j) if i.uri == j.uri then - if i.range and i.range.start then return i.range.start.line < j.range.start.line end + if i.range and i.range.start then + return i.range.start.line < j.range.start.line + end return false else return i.uri < j.uri @@ -186,9 +219,10 @@ function M.locations_to_items(locations) item.rpath = util.get_relative_path(cwd, item.filename) table.insert(items, item) width = math.max(width, #item.text) + item.symbol_name = get_symbol(item.text, item.range) + item.lhs = check_lhs(item.text, item.symbol_name) end - - return items, width + 15 + return items, width + 24 -- TODO handle long line? end function M.symbol_to_items(locations) @@ -201,7 +235,9 @@ function M.symbol_to_items(locations) -- items and locations may not matching table.sort(locations, function(i, j) if i.uri == j.uri then - if i.range and i.range.start then return i.range.start.line < j.range.start.line end + if i.range and i.range.start then + return i.range.start.line < j.range.start.line + end return false else return i.uri < j.uri diff --git a/lua/navigator/reference.lua b/lua/navigator/reference.lua index 9c57b02..cbf36e7 100644 --- a/lua/navigator/reference.lua +++ b/lua/navigator/reference.lua @@ -3,7 +3,7 @@ local log = util.log local lsphelper = require "navigator.lspwrapper" local gui = require "navigator.gui" local lsp = require "navigator.lspwrapper" -local verbose = require"navigator.util".verbose +local trace = require"navigator.util".trace -- local log = util.log -- local partial = util.partial -- local cwd = vim.fn.getcwd(0) @@ -16,7 +16,7 @@ local function ref_hdlr(err, api, locations, num, bufnr) local opts = {} -- log("arg1", arg1) -- log(api) - -- log(locations) + trace(locations) -- log("num", num) -- log("bfnr", bufnr) if err ~= nil then @@ -24,7 +24,6 @@ local function ref_hdlr(err, api, locations, num, bufnr) return end if type(locations) ~= 'table' then - log("arg1", arg1) log(api) log(locations) log("num", num) @@ -36,10 +35,11 @@ local function ref_hdlr(err, api, locations, num, bufnr) return end local items, width = locations_to_items(locations) + local ft = vim.api.nvim_buf_get_option(bufnr, "ft") local wwidth = vim.api.nvim_get_option("columns") - width = math.min(width + 24 or 120, math.floor(wwidth * 0.8)) + width = math.min(width + 30, 120, math.floor(wwidth * 0.8)) gui.new_list_view({items = items, ft = ft, width = width, api = "Reference"}) end diff --git a/lua/navigator/render.lua b/lua/navigator/render.lua new file mode 100644 index 0000000..0336497 --- /dev/null +++ b/lua/navigator/render.lua @@ -0,0 +1,149 @@ +local log = require"guihua.log".info +local trace = require"guihua.log".trace +local M = {} +local clone = require'guihua.util'.clone +local function filename(url) + return url:match("^.+/(.+)$") or url +end + +local function extension(url) + local ext = url:match("^.+(%..+)$") or "txt" + return string.sub(ext, 2) +end + +local function get_pads(win_width, text, postfix) + local margin = win_width - #text - #postfix + if margin < 0 then + if #postfix > 1 then + text = text:sub(1, #text - 20) + end + end + local sz = #text + if sz < 30 then + sz = 30 + end + local space = '' + local i = math.floor((sz + 10) / 10) + i = i * 10 - #text + space = string.rep(' ', i) + trace(text, i, postfix, win_width) + return space +end + +function M.prepare_for_render(items, opts) + opts = opts or {} + if items == nil or #items < 1 then + print("no item found or empty fields") + return + end + local item = clone(items[1]) + local display_items = {item} + local last_summary_idx = 1 + local total_ref_in_file = 1 + local icon = " " + local lspapi = opts.api or "∑" + + local ok, devicons = pcall(require, "nvim-web-devicons") + if ok then + local fn = filename(items[1].filename) + local ext = extension(fn) + icon = devicons.get_icon(fn, ext) or icon + end + local call_by_presented = false + + opts.width = opts.width or 100 + local win_width = opts.width - 2 -- buf + + for i = 1, #items do + if items[i].call_by and #items[i].call_by > 0 then + call_by_presented = true + end + end + + for i = 1, #items do + local space = '' + local lspapi_display = lspapi + items[i].symbol_name = items[i].symbol_name or "" -- some LSP API does not have range for this + if last_summary_idx == 1 then + lspapi_display = items[i].symbol_name .. ' ' .. lspapi_display + trace(items[1], lspapi_display) + end + + -- trace(items[i], items[i].filename, last_summary_idx, display_items[last_summary_idx].filename) + if items[i].filename == display_items[last_summary_idx].filename then + space = get_pads(opts.width, icon .. ' ' .. display_items[last_summary_idx].display_filename, + lspapi_display .. ' 12') + display_items[last_summary_idx].text = string.format("%s %s%s%s %i", icon, + display_items[last_summary_idx] + .display_filename, space, + lspapi_display, total_ref_in_file) + total_ref_in_file = total_ref_in_file + 1 + else + + lspapi_display = lspapi + item = clone(items[i]) + + space = get_pads(opts.width, icon .. ' ' .. item.display_filename, lspapi_display .. ' 12') + item.text = string.format("%s %s%s%s 1", icon, item.display_filename, space, lspapi_display) + + trace(item.text) + table.insert(display_items, item) + total_ref_in_file = 1 + last_summary_idx = #display_items + end + -- content of code lines + item = clone(items[i]) + item.text = require'navigator.util'.trim_and_pad(item.text) + item.text = string.format("%4i: %s", item.lnum, item.text) + local call_by = "" + if item.lhs then + call_by = '📝 ' + end + + item.text = item.text:gsub('%s*[%[%(%{]*%s*$', '') + if item.call_by ~= nil and #item.call_by > 0 then + trace("call_by:", #item.call_by) + for _, value in pairs(item.call_by) do + if value.node_text then + local txt = value.node_text:gsub('%s*[%[%(%{]*%s*$', '') + local endwise = '{}' + if value.type == 'method' or value.type == 'function' then + endwise = '()' + call_by = ' ' + end + if #call_by > 8 then + call_by = call_by .. '  ' + end + call_by = call_by .. value.kind .. txt .. endwise + trace(item) + end + end + end + if #call_by > 1 then + space = get_pads(win_width, item.text, call_by) + if #space + #item.text + #call_by >= win_width then + if #item.text + #call_by > win_width then + log("exceeding", #item.text, #call_by, win_width) + space = ' ' + else + local remain = win_width - #item.text - #call_by + log("remain", remain) + space = string.rep(' ', remain) + end + end + item.text = item.text .. space .. call_by + end + local tail = display_items[#display_items].text + if tail ~= item.text then -- deduplicate + trace(item.text) + trace(item.call_by) + table.insert(display_items, item) + end + end + + -- display_items[last_summary_idx].text=string.format("%s [%i]", display_items[last_summary_idx].filename, + -- total_ref_in_file) + return display_items +end + +return M diff --git a/lua/navigator/symbols.lua b/lua/navigator/symbols.lua index 2934071..03bc7a2 100644 --- a/lua/navigator/symbols.lua +++ b/lua/navigator/symbols.lua @@ -55,8 +55,9 @@ function M.workspace_symbols(opts) -- result_lsp local result = {} for i = 1, #results_lsp do - if results_lsp[i] ~= nil and results_lsp[i].result ~= nil and - #results_lsp[i].result > 0 then result = results_lsp[i].result end + if results_lsp[i] ~= nil and results_lsp[i].result ~= nil and #results_lsp[i].result > 0 then + result = results_lsp[i].result + end end local items = symbols_to_items(result) @@ -115,23 +116,16 @@ function M.document_symbol_handler(err, _, result, _, bufnr) child.uri = uri child.lnum = c.range.start.line + 1 child.detail = c.detail or "" - child.text = "  [" .. ckind .. "] " .. child.detail .. " " .. - child.name + child.text = "  [" .. ckind .. "] " .. child.detail .. " " .. child.name table.insert(locations, child) end end end local ft = vim.api.nvim_buf_get_option(bufnr, "ft") - -- verbose(locations) + -- trace(locations) -- local items = locations_to_items(locations) - gui.new_list_view({ - items = locations, - prompt = true, - rawdata = true, - ft = ft, - api = " " - }) + gui.new_list_view({items = locations, prompt = true, rawdata = true, ft = ft, api = " "}) -- if locations == nil or vim.tbl_isempty(locations) then -- print "References not found" @@ -178,13 +172,7 @@ function M.workspace_symbol_handler(err, _, result, _, bufnr) -- local items = locations_to_items(locations) local ft = vim.api.nvim_buf_get_option(bufnr, "ft") - gui.new_list_view({ - items = items, - prompt = true, - ft = ft, - rowdata = true, - api = " " - }) + gui.new_list_view({items = items, prompt = true, ft = ft, rowdata = true, api = " "}) -- if locations == nil or vim.tbl_isempty(locations) then -- print "References not found" diff --git a/lua/navigator/treesitter.lua b/lua/navigator/treesitter.lua index f087996..a135ecc 100644 --- a/lua/navigator/treesitter.lua +++ b/lua/navigator/treesitter.lua @@ -2,7 +2,9 @@ local gui = require "navigator.gui" local ok, ts_locals = pcall(require, "nvim-treesitter.locals") -if not ok then error("treesitter not installed") end +if not ok then + error("treesitter not installed") +end local parsers = require "nvim-treesitter.parsers" local ts_utils = require "nvim-treesitter.ts_utils" @@ -13,7 +15,7 @@ local M = {} local cwd = vim.fn.getcwd(0) local log = require"navigator.util".log -local verbose = require"navigator.util".verbose +local trace = require"navigator.util".trace local match_kinds = { var = " ", -- "👹", -- Vampaire @@ -69,44 +71,78 @@ local function prepare_node(node, kind) if node.node then table.insert(matches, {kind = get_icon(kind), def = node.node, type = kind}) else - for name, item in pairs(node) do vim.list_extend(matches, prepare_node(item, name)) end + for name, item in pairs(node) do + vim.list_extend(matches, prepare_node(item, name)) + end end return matches end -local function get_var_context(source) +local function get_scope(type, source) local sbl, sbc, sel, sec = source:range() local current = source local result = current local next = ts_utils.get_next_node(source) local parent = current:parent() + trace(source:type(), source:range(), parent) + + if type == 'method' or type == 'function' and parent ~= nil then + trace(parent:type(), parent:range()) + -- a function name + if parent:type() == 'function_name' then + -- up one level + return parent:parent(), true + end + if parent:type() == 'function_name_field' then + return parent:parent():parent(), true + end + return parent, true + end - if next == nil or parent == nil then return end - if next:type() == "function" or next:type() == "arrow_function" then - log(current:type(), current:range()) - return parent - else - return source + if type == "var" and next ~= nil then + if next:type() == "function" or next:type() == "arrow_function" or next:type() == + "function_definition" then + trace(current:type(), current:range()) + return next, true + elseif parent:type() == 'function_declaration' then + return parent, true + else + trace(source, source:type()) + return source, false + end + else -- M.fun1 = function() end + -- lets work up and see next node + local n = source + for i = 1, 4, 1 do + if n == nil or n:parent() == nil then + break + end + n = n:parent() + next = ts_utils.get_next_node(n) + if next ~= nil and next:type() == 'function_definition' then + return next, true + end + end + end + + if source:type() == "type_identifier" then + return source:parent(), true end - -- while current ~= nil do - -- log(current:type(), current:range()) - -- if current:type() == "variable_declarator" or current:type() == "function_declaration" then - -- return current - -- end - -- -- local bl, bc, el, ec = current:range() - -- -- if bl == sbl and bc == sbc and el >= sel and ec >= sec then result = current end - -- current = current:parent() - -- end - -- log(current) + end local function get_smallest_context(source) local scopes = ts_locals.get_scopes() + for key, value in pairs(scopes) do + trace(key, value) + end local current = source - while current ~= nil and not vim.tbl_contains(scopes, current) do current = current:parent() end - log(current) - if current ~= nil then return current end - return get_var_context(source) + while current ~= nil and not vim.tbl_contains(scopes, current) do + current = current:parent() + end + if current ~= nil then + return current, true + end -- if source:type() == "identifier" then return get_var_context(source) end end @@ -115,7 +151,9 @@ 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 + if delta < 0 then + opt = {forward = false} + end bufnr = bufnr or api.nvim_get_current_buf() local node_at_point = ts_utils.get_node_at_cursor() if not node_at_point then @@ -136,13 +174,20 @@ function M.goto_adjacent_usage(bufnr, delta) 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 +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, filter, summary) + trace(bufnr, filter, summary) bufnr = bufnr or 0 summary = summary or false - if not parsers.has_parser() then print("ts not loaded") end + if not parsers.has_parser() then + print("ts not loaded") + end local fname = vim.fn.expand("%:p:f") local uri = vim.uri_from_fname(fname) if bufnr ~= 0 then @@ -186,27 +231,56 @@ local function get_all_nodes(bufnr, filter, summary) for _, node in ipairs(nodes) do item.kind = node.kind item.type = node.type + + if filter ~= nil and not filter[item.type] then + trace(item.type, item.kind) + goto continue + end local tsdata = node.def - log(item.type, tsdata:type()) - if node.def == nil then goto continue end + if node.def == nil then + goto continue + end item.node_text = ts_utils.get_node_text(tsdata, bufnr)[1] + local scope, is_func - local scope = get_smallest_context(tsdata) + if summary then + scope, is_func = get_scope(item.type, tsdata) + else + scope, is_func = get_smallest_context(tsdata) + end + if is_func then + -- hack for lua and maybe other language aswell + local parent = tsdata:parent() + if parent ~= nil and parent:type() == 'function_name' or parent:type() == + 'function_name_field' then + item.node_text = ts_utils.get_node_text(parent, bufnr)[1] + log(parent:type(), item.node_text) + end + end + + log(item.node_text, item.kind, item.type) if scope ~= nil then -- it is strange.. - log(item.node_text, item.kind, item.type) + if not is_func and summary then + goto continue + end item.node_scope = ts_utils.node_to_lsp_range(scope) end - if filter ~= nil and not filter[item.type] then goto continue end if summary then - if item.node_scope ~= nil then table.insert(all_nodes, item) end + if item.node_scope ~= nil then + table.insert(all_nodes, item) + end + + log(item.type, tsdata:type(), item.node_text, item.kind, item.node_text, item.node_scope) goto continue end item.range = ts_utils.node_to_lsp_range(tsdata) local start_line_node, _, _ = tsdata:start() - if item.node_text == "_" then goto continue end + 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.uri = uri @@ -217,16 +291,20 @@ local function get_all_nodes(bufnr, filter, summary) item.lnum = item.lnum + 1 item.col = item.col + 1 local indent = "" - if #parents > 1 then indent = string.rep(" ", #parents - 1) .. " " end + 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 length = #item.text end + if #item.text > length then + length = #item.text + end table.insert(all_nodes, item) ::continue:: end end - verbose(all_nodes) + trace(all_nodes) return all_nodes, length end @@ -244,20 +322,36 @@ function M.buf_func(bufnr) ["class"] = true, ["type"] = true }, true) - table.sort(all_nodes, function(i, j) - if i.range and j.range then - if i.range.start.line == j.range.start.line then - return i.range['end'].line < j.range['end'].line - else - return i.range.start.line < j.range.start.line - end - end - return false - end) + if #all_nodes < 1 then + log("no node found for ", bufnr) + return + end - verbose(all_nodes, width) + if all_nodes[1].node_scope then + table.sort(all_nodes, function(i, j) + if i.node_scope and j.node_scope then + if i.node_scope['end'].line == j.node_scope['end'].line then + return i.node_scope.start.line > j.node_scope.start.line + else + return i.node_scope['end'].line < j.node_scope['end'].line + end + end + return false + end) + else + table.sort(all_nodes, function(i, j) + if i.range and j.range then + if i.range['end'].line == j.range['end'].line then + return i.range.start.line > j.range.start.line + else + return i.range['end'].line < j.range['end'].line + end + end + return false + end) + end - return all_nodes + return all_nodes, width end @@ -295,14 +389,16 @@ function M.bufs_ts() if vim.api.nvim_buf_is_loaded(buf) then local all_nodes, length = get_all_nodes(buf) if all_nodes ~= nil then - if length > max_length then max_length = length end + if length > max_length then + max_length = length + end vim.list_extend(ts_opened, all_nodes) end end end end if #ts_opened > 1 then - verbose(ts_opened) + trace(ts_opened) local ft = vim.api.nvim_buf_get_option(0, "ft") gui.new_list_view({ diff --git a/lua/navigator/util.lua b/lua/navigator/util.lua index 824fca4..11546f5 100644 --- a/lua/navigator/util.lua +++ b/lua/navigator/util.lua @@ -41,7 +41,9 @@ end local function getDir(path) local data = {} local len = #path - if len <= 1 then return nil end + if len <= 1 then + return nil + end local last_index = 1 for i = 2, len do local cur_char = path:sub(i, i) @@ -60,17 +62,25 @@ function M.get_relative_path(base_path, my_path) local base_len = #base_data local my_len = #my_data - if base_len > my_len then return my_path end + if base_len > my_len then + return my_path + end - if base_data[1] ~= my_data[1] then return my_path end + if base_data[1] ~= my_data[1] then + return my_path + end local cur = 0 for i = 1, base_len do - if base_data[i] ~= my_data[i] then break end + if base_data[i] ~= my_data[i] then + break + end cur = i end local data = "" - for i = cur + 1, my_len do data = data .. my_data[i] .. "/" end + for i = cur + 1, my_len do + data = data .. my_data[i] .. "/" + end data = data .. M.get_base(my_path) return data end @@ -81,23 +91,28 @@ M._log = require("guihua.log").new({level = default_config.level}, true) -- add log to you lsp.log M.log = M._log.info -M.verbose = M._log.debug +M.trace = M._log.trace +M.error = M._log.error -function M.fmt(...) M._log.fmt_info(...) end +function M.fmt(...) + M._log.fmt_info(...) +end function M.split(inputstr, sep) - if sep == nil then sep = "%s" end + if sep == nil then + sep = "%s" + end local t = {} - for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do table.insert(t, str) end + for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do + table.insert(t, str) + end return t end -function M.trim_space(s) return s:match("^%s*(.-)%s*$") end - function M.quickfix_extract(line) -- check if it is a line of file pos been selected local split = M.split - line = M.trim_space(line) + line = vim.trim(line) local sep = split(line, " ") if #sep < 2 then M.log(line) @@ -130,7 +145,9 @@ function M.getArgs(inputstr) return cmd, t end -function M.p(t) print(vim.inspect(t)) end +function M.p(t) + print(vim.inspect(t)) +end function M.printError(msg) vim.cmd("echohl ErrorMsg") @@ -148,14 +165,18 @@ function M.open_log() vim.cmd("edit " .. path) end -function table.pack(...) return {n = select("#", ...), ...} end +function table.pack(...) + return {n = select("#", ...), ...} +end function M.show(...) local string = "" local args = table.pack(...) - for i = 1, args.n do string = string .. tostring(args[i]) .. "\t" end + for i = 1, args.n do + string = string .. tostring(args[i]) .. "\t" + end return string .. "\n" end @@ -165,12 +186,35 @@ function M.split2(s, sep) sep = sep or " " local pattern = string.format("([^%s]+)", sep) - string.gsub(s, pattern, function(c) fields[#fields + 1] = c end) + string.gsub(s, pattern, function(c) + fields[#fields + 1] = c + end) return fields end -M.open_file = function(filename) vim.api.nvim_command(string.format("e! %s", filename)) end +function M.trim_and_pad(txt) + local len = #txt + if len <= 1 then + return + end + local tab_en = txt[1] == '\t' or false + txt = vim.trim(txt) + if tab_en then + if len - txt > 2 then + return ' ' .. txt + end + if len - txt > 0 then + return ' ' .. txt + end + end + local rep = math.min(12, len - #txt) + return string.rep(' ', rep / 4) .. txt +end + +M.open_file = function(filename) + vim.api.nvim_command(string.format("e! %s", filename)) +end M.open_file_at = function(filename, line, col) vim.api.nvim_command(string.format("e! +%s %s", line, filename)) @@ -178,13 +222,27 @@ M.open_file_at = function(filename, line, col) vim.api.nvim_command(string.format("normal! %dl", col - 1)) end -function M.exists(var) for k, _ in pairs(_G) do if k == var then return true end end end +function M.exists(var) + for k, _ in pairs(_G) do + if k == var then + return true + end + end +end -function M.partial(func, arg) return (function(...) return func(arg, ...) end) end +function M.partial(func, arg) + return (function(...) + return func(arg, ...) + end) +end local exclude_ft = {"scrollbar", "help", "NvimTree"} function M.exclude(fname) - for i = 1, #exclude_ft do if string.find(fname, exclude_ft[i]) then return true end end + for i = 1, #exclude_ft do + if string.find(fname, exclude_ft[i]) then + return true + end + end return false end @@ -196,7 +254,9 @@ 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 + 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 @@ -205,7 +265,9 @@ function M.set_virt_eol(bufnr, lnum, chunks, priority, id) end function M.clear_buf(bufnr) - if not bufnr then return end + 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 @@ -217,7 +279,9 @@ function M.clear_buf(bufnr) end function M.clear_all_buf() - for bufnr in pairs(bufs) do M.clear_buf(bufnr) end + for bufnr in pairs(bufs) do + M.clear_buf(bufnr) + end bufs = {} end