From 91e22f5e719d1d805d6b00a5863b6d81bdcabdd8 Mon Sep 17 00:00:00 2001 From: rayx Date: Mon, 13 Sep 2021 23:18:54 +1000 Subject: [PATCH] Lv thread (#60) * update tests, update total display * not run ts def multiple time * Thread enable for display and backend * remove ::continue:: * README updates * skip diagnostic in edit mode * error marker uri nil handling * disable debug * debounce text change to 1s * severity sort * diagnostic skip loading files --- README.md | 35 ++++++- lua/navigator.lua | 3 +- lua/navigator/codelens.lua | 2 +- lua/navigator/diagnostics.lua | 60 ++++++++---- lua/navigator/foldlsp.lua | 2 +- lua/navigator/foldts.lua | 11 +++ lua/navigator/gui.lua | 141 ++++++++++++++-------------- lua/navigator/lspclient/clients.lua | 6 +- lua/navigator/lspwrapper.lua | 140 +++++++++++++++++++++------ lua/navigator/reference.lua | 49 ++++++++-- lua/navigator/render.lua | 17 +++- lua/navigator/treesitter.lua | 54 ++++++++++- tests/reference_spec.lua | 120 ++++++++++++++++++----- 13 files changed, 472 insertions(+), 168 deletions(-) diff --git a/README.md b/README.md index 363264e..d06604c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ included for LSP clients. all features(handler) provided by LSP from commonly used search reference, to less commonly used search for interface implementation. -- Luv async job +- Luv async thread and tasks - Edit your code in preview window @@ -62,7 +62,7 @@ implementation. - Treesitter symbol search. It is handy for large files (Some of LSP e.g. sumneko_lua, there is a 100kb file size limitation?) -- FZY search with Lua-JIT +- FZY search with either native C (if gcc installed) or Lua-JIT - LSP multiple symbol highlight/marker and hop between document references @@ -265,6 +265,37 @@ require'navigator'.setup({ ``` +### LSP clients + +Built clients: +```lua +local servers = { + "angularls", "gopls", "tsserver", "flow", "bashls", "dockerls", "julials", "pylsp", "pyright", + "jedi_language_server", "jdtls", "sumneko_lua", "vimls", "html", "jsonls", "solargraph", "cssls", + "yamlls", "clangd", "ccls", "sqls", "denols", "graphql", "dartls", "dotls", + "kotlin_language_server", "nimls", "intelephense", "vuels", "phpactor", "omnisharp", + "r_language_server", "rust_analyzer", "terraformls" +} + +``` + +Navigator will try to load avalible lsp server/client based on filetype. The clients has none default on_attach. +incremental sync and debounce is enabled by navigator. And the lsp +snippet will be enabled. So you could use COQ and nvim-cmp snippet expand. + + +Other than above setup, additional none default setup are used for following lsp: + +* gopls +* clangd +* rust_analyzer +* sqls +* sumneko_lua +* pyright +* ccls + +Please check [client setup](https://github.com/ray-x/navigator.lua/blob/26012cf9c172aa788a2e53018d94b32c5c75af75/lua/navigator/lspclient/clients.lua#L98-L234) + The plugin can work with multiple LSP, e.g sqls+gopls+efm. But there are cases you may need to disable some of the servers. (Prevent loading multiple LSP for same source code.) e.g. I saw strange behaviours when I use pyls+pyright+pyls_ms together. If you have multiple similar LSP installed and have trouble with the plugin, please enable only one at a time. diff --git a/lua/navigator.lua b/lua/navigator.lua index 1bef8c3..ea9eabc 100644 --- a/lua/navigator.lua +++ b/lua/navigator.lua @@ -27,7 +27,8 @@ _NgConfigValues = { code_lens = false, -- only want to enable one lsp server disply_diagnostic_qf = true, -- always show quickfix if there are diagnostic errors - + diagnostic_load_files = false, -- lsp diagnostic errors list may contains uri that not opened yet set to true + -- to load those files diagnostic_virtual_text = true, -- show virtual for diagnostic message diagnostic_update_in_insert = false, -- update diagnostic message in insert mode diagnostic_scrollbar_sign = {'▃', '█'}, -- set to nil to disable, set to {'╍', 'ﮆ'} to enable diagnostic status in scroll bar area diff --git a/lua/navigator/codelens.lua b/lua/navigator/codelens.lua index fc2a16c..8a364f3 100644 --- a/lua/navigator/codelens.lua +++ b/lua/navigator/codelens.lua @@ -69,7 +69,7 @@ function M.setup() local on_codelens = vim.lsp.handlers["textDocument/codeLens"] vim.lsp.handlers["textDocument/codeLens"] = mk_handler( function(err, result, ctx, cfg) - log(err, result, ctx.client_id, ctx.bufnr, cfg) + trace(err, result, ctx.client_id, ctx.bufnr, cfg) if nvim_0_6() then on_codelens(err, result, ctx, cfg) codelens_hdlr(err, result, ctx, cfg) diff --git a/lua/navigator/diagnostics.lua b/lua/navigator/diagnostics.lua index 8791a99..be657f9 100644 --- a/lua/navigator/diagnostics.lua +++ b/lua/navigator/diagnostics.lua @@ -18,7 +18,8 @@ local function error_marker(result, client_id) return end local first_line = vim.fn.line('w0') - local ft = vim.fn.expand('%:h:t') -- get the current file extension + -- local rootfolder = vim.fn.expand('%:h:t') -- get the current file root folder + trace(result) local bufnr = vim.uri_to_bufnr(result.uri) if bufnr ~= vim.api.nvim_get_current_buf() then @@ -106,16 +107,20 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config) log(err, config) return end + if vim.fn.mode() ~= 'n' and config.update_in_insert == false then + log("skip in insert mode") + return + end local cwd = vim.loop.cwd() local ft = vim.bo.filetype if diagnostic_list[ft] == nil then diagnostic_list[vim.bo.filetype] = {} end -- vim.lsp.diagnostic.clear(vim.fn.bufnr(), client.id, nil, nil) - if util.nvim_0_6() then vim.lsp.diagnostic.on_publish_diagnostics(err, result, ctx, config) else + log("old version of lsp nvim 050") vim.lsp.diagnostic.on_publish_diagnostics(err, _, result, ctx.client_id, _, config) end local uri = result.uri @@ -123,8 +128,11 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config) log("diag", err, result) return end - - -- log("diag: ", result, client_id) + if vim.fn.mode() ~= 'n' and config.update_in_insert == false then + log("skip in insert mode") + return + end + trace("diag: ", vim.fn.mode(), result, ctx, config) if result and result.diagnostics then local item_list = {} for _, v in ipairs(result.diagnostics) do @@ -146,21 +154,30 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config) end local bufnr1 = vim.uri_to_bufnr(uri) if not vim.api.nvim_buf_is_loaded(bufnr1) then - vim.fn.bufload(bufnr1) - end - local pos = v.range.start - local row = pos.line - local line = (vim.api.nvim_buf_get_lines(bufnr1, row, row + 1, false) or {""})[1] - if line ~= nil then - item.text = head .. line .. _NgConfigValues.icons.diagnostic_head_description .. v.message - table.insert(item_list, item) - else - error("diagnostic result empty line", v, row, bufnr1) + if _NgConfigValues.diagnostic_load_files then + vim.fn.bufload(bufnr1) -- this may slow down the neovim + local pos = v.range.start + local row = pos.line + local line = (vim.api.nvim_buf_get_lines(bufnr1, row, row + 1, false) or {""})[1] + if line ~= nil then + item.text = head .. line .. _NgConfigValues.icons.diagnostic_head_description + .. v.message + table.insert(item_list, item) + else + error("diagnostic result empty line", v, row, bufnr1) + end + else + item.text = head .. _NgConfigValues.icons.diagnostic_head_description .. v.message + table.insert(item_list, item) + end end end -- local old_items = vim.fn.getqflist() diagnostic_list[ft][uri] = item_list - result.uri = uri + if not result.uri then + result.uri = uri + end + error_marker(result, ctx.client_id) else vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1) @@ -173,18 +190,21 @@ local M = {} local diagnostic_cfg = { -- Enable underline, use default values underline = true, - -- Enable virtual text, override spacing to 0 - virtual_text = {spacing = 0, prefix = _NgConfigValues.icons.diagnostic_virtual_text}, + -- Enable virtual text, override spacing to 3 (prevent overlap) + virtual_text = {spacing = 3, prefix = _NgConfigValues.icons.diagnostic_virtual_text}, -- Use a function to dynamically turn signs off -- and on, using buffer local variables signs = true, - -- Disable a feature - update_in_insert = _NgConfigValues.lsp.diagnostic_update_in_insert or false + update_in_insert = _NgConfigValues.lsp.diagnostic_update_in_insert or false, + severity_sort = function(a, b) + return a.severity < b.severity + end } if _NgConfigValues.lsp.diagnostic_virtual_text == false then diagnostic_cfg.virtual_text = false end + -- vim.lsp.handlers["textDocument/publishDiagnostics"] M.diagnostic_handler = vim.lsp.with(diag_hdlr, diagnostic_cfg) @@ -252,7 +272,7 @@ function M.update_err_marker() -- nothing to update return end - local bufnr = vim.fn.bufnr() + local bufnr = vim.api.nvim_get_current_buf() local diag_cnt = vim.lsp.diagnostic.get_count(bufnr, [[Error]]) + vim.lsp.diagnostic.get_count(bufnr, [[Warning]]) diff --git a/lua/navigator/foldlsp.lua b/lua/navigator/foldlsp.lua index 78e41f9..544204c 100644 --- a/lua/navigator/foldlsp.lua +++ b/lua/navigator/foldlsp.lua @@ -57,7 +57,7 @@ function M.setup_plugin() M.active_folding_clients[client_id] = server_supports_folding end end - print(vim.inspect(M.active_folding_clients)) + -- print(vim.inspect(M.active_folding_clients)) end function M.update_folds() diff --git a/lua/navigator/foldts.lua b/lua/navigator/foldts.lua index 6eff20e..4c89d8f 100644 --- a/lua/navigator/foldts.lua +++ b/lua/navigator/foldts.lua @@ -159,6 +159,17 @@ end) function M.get_fold_indic(lnum) local buf = api.nvim_get_current_buf() + local shown = false + for i = 1, vim.fn.tabpagenr('$') do + for key, value in pairs(vim.fn.tabpagebuflist(i)) do + if value == buf then + shown = true + end + end + end + if not shown then + return "0" + end local levels = folds_levels(buf) or {} -- log(lnum, levels[lnum]) -- TODO: comment it out in master diff --git a/lua/navigator/gui.lua b/lua/navigator/gui.lua index ec62b70..9cd3972 100644 --- a/lua/navigator/gui.lua +++ b/lua/navigator/gui.lua @@ -140,80 +140,83 @@ function M.new_list_view(opts) 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 - loc = "top_center" - prompt = true - end + if not data or vim.tbl_isempty(data) then + return + end + + -- replace + -- TODO: 10 vimrc opt + if #data > 10 and opts.prompt == nil then + loc = "top_center" + prompt = true + end - local lheight = math.min(#data, math.floor(wheight * _NgConfigValues.height)) + local lheight = math.min(#data, math.floor(wheight * _NgConfigValues.height)) - local r, _ = top_center(lheight, width) + 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 idx = require"guihua.util".fzy_idx - local transparency = _NgConfigValues.transparency - if transparency == 100 then - transparency = nil + 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 - return ListView:new({ - loc = loc, - prompt = prompt, - relative = opts.relative, - style = opts.style, - api = opts.api, - rect = {height = lheight, width = width, pos_x = 0, pos_y = 0}, - -- preview_height = pheight, - ft = opts.ft or 'guihua', - -- data = display_data, - data = data, - border = border, - on_confirm = opts.on_confirm or function(item, split_opts) - log(split_opts) - split_opts = split_opts or {} - if item.filename ~= nil then - log("openfile ", item.filename, item.lnum, item.col) - util.open_file_at(item.filename, item.lnum, item.col, split_opts.split) - end - end, - transparency = transparency, - on_move = opts.on_move or function(item) - trace("on move", pos, item) - trace("on move", pos, item.text or item, item.uri, item.filename) - -- todo fix - if item.uri == nil then - item.uri = "file:///" .. item.filename - end - return M.preview_uri({ - uri = item.uri, - width = width, - height = lheight, -- this is to cal offset - preview_height = pheight, - lnum = item.lnum, - col = item.col, - range = item.range, - offset_x = 0, - offset_y = offset_y, - border = border, - enable_edit = opts.enable_preview_edit or false - }) - 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 idx = require"guihua.util".fzy_idx + local transparency = _NgConfigValues.transparency + if transparency == 100 then + transparency = nil + end + return ListView:new({ + loc = loc, + prompt = prompt, + relative = opts.relative, + style = opts.style, + api = opts.api, + total = opts.total, + rect = {height = lheight, width = width, pos_x = 0, pos_y = 0}, + -- preview_height = pheight, + ft = opts.ft or 'guihua', + -- data = display_data, + data = data, + border = border, + on_confirm = opts.on_confirm or function(item, split_opts) + log(split_opts) + split_opts = split_opts or {} + if item.filename ~= nil then + log("openfile ", item.filename, item.lnum, item.col) + util.open_file_at(item.filename, item.lnum, item.col, split_opts.split) + end + end, + transparency = transparency, + on_move = opts.on_move or function(item) + trace("on move", item) + trace("on move", item.text or item, item.uri, item.filename) + -- todo fix + if item.uri == nil then + item.uri = "file:///" .. item.filename + end + return M.preview_uri({ + uri = item.uri, + width = width, + height = lheight, -- this is to cal offset + preview_height = pheight, + lnum = item.lnum, + col = item.col, + range = item.range, + offset_x = 0, + offset_y = offset_y, + border = border, + enable_edit = opts.enable_preview_edit or false + }) + end + }) end return M diff --git a/lua/navigator/lspclient/clients.lua b/lua/navigator/lspclient/clients.lua index 3c7c074..00bcbfd 100644 --- a/lua/navigator/lspclient/clients.lua +++ b/lua/navigator/lspclient/clients.lua @@ -94,7 +94,7 @@ local setups = { -- "-rpc.trace", }, - flags = {allow_incremental_sync = true, debounce_text_changes = 500}, + flags = {allow_incremental_sync = true, debounce_text_changes = 1000}, settings = { gopls = { -- more settings: https://github.com/golang/tools/blob/master/gopls/doc/settings.md @@ -112,7 +112,7 @@ local setups = { staticcheck = true, matcher = "fuzzy", diagnosticsDelay = "500ms", - experimentalWatchedFileDelay = "100ms", + experimentalWatchedFileDelay = "1000ms", symbolMatcher = "fuzzy", gofumpt = false, -- true, -- turn on for new repos, gofmpt is good but also create code turmoils buildFlags = {"-tags", "integration"} @@ -258,7 +258,7 @@ end local default_cfg = { on_attach = on_attach, - flags = {allow_incremental_sync = true, debounce_text_changes = 500} + flags = {allow_incremental_sync = true, debounce_text_changes = 1000} } -- check and load based on file type diff --git a/lua/navigator/lspwrapper.lua b/lua/navigator/lspwrapper.lua index 3595e48..7f73ce4 100644 --- a/lua/navigator/lspwrapper.lua +++ b/lua/navigator/lspwrapper.lua @@ -123,18 +123,17 @@ function M.check_capabilities(feature, client_id) for _, client in pairs(clients) do supported_client = client.resolved_capabilities[feature] if supported_client then - goto continue + break end end - ::continue:: if supported_client then return true else if #clients == 0 then - print("LSP: no client attached") + log("LSP: no client attached") else - log("LSP: server does not support " .. feature) + trace("LSP: server does not support " .. feature) end return false end @@ -164,6 +163,7 @@ function M.call_async(method, params, handler) end local function ts_functions(uri) + local unload_bufnr local ts_enabled, _ = pcall(require, "nvim-treesitter.locals") if not ts_enabled or not TS_analysis_enabled then lerr("ts not enabled") @@ -175,7 +175,7 @@ local function ts_functions(uri) trace(ts_nodes) local tsnodes = ts_nodes:get(uri) if tsnodes ~= nil then - log("get data from cache") + trace("get data from cache") local t = ts_nodes_time:get(uri) or 0 local fname = vim.uri_to_fname(uri) local modified = vim.fn.getftime(fname) @@ -191,28 +191,40 @@ local function ts_functions(uri) if not api.nvim_buf_is_loaded(bufnr) then trace("! load buf !", uri, bufnr) vim.fn.bufload(bufnr) + -- vim.api.nvim_buf_detach(bufnr) -- if user opens the buffer later, it prevents user attach event unload = true end local funcs = ts_func(bufnr) if unload then - local cmd = string.format("bd %d", bufnr) - trace(cmd) - -- vim.cmd(cmd) -- todo: not sure if it is needed + unload_bufnr = bufnr end ts_nodes:set(uri, funcs) ts_nodes_time:set(uri, os.time()) trace(funcs, ts_nodes:get(uri)) trace(string.format("elapsed time: %.4f\n", os.clock() - x)) -- how long it tooks - return funcs + return funcs, unload_bufnr end -local function ts_defination(uri, range) +local function ts_definition(uri, range) + local unload_bufnr local ts_enabled, _ = pcall(require, "nvim-treesitter.locals") if not ts_enabled or not TS_analysis_enabled then lerr("ts not enabled") return nil end + + local key = string.format('%s_%d_%d_%d', uri, range.start.line, range.start.character, + range['end'].line) + local tsnode = ts_nodes:get(key) + local ftime = ts_nodes_time:get(key) + + local fname = vim.uri_to_fname(uri) + local modified = vim.fn.getftime(fname) + if tsnodes and modified <= ftime then + log('ts def from cache') + return tsnode + end local ts_def = require"navigator.treesitter".find_definition local bufnr = vim.uri_to_bufnr(uri) local x = os.clock() @@ -224,14 +236,14 @@ local function ts_defination(uri, range) unload = true end - local def_range = ts_def(range, bufnr) + local def_range = ts_def(range, bufnr) or {} if unload then - local cmd = string.format("bd %d", bufnr) - log(cmd) - -- vim.cmd(cmd) -- todo: not sure if it is needed + unload_bufnr = bufnr end trace(string.format(" ts def elapsed time: %.4f\n", os.clock() - x), def_range) -- how long it takes - return def_range + ts_nodes:set(key, def_range) + ts_nodes_time:set(key, x) + return def_range, unload_bufnr end local function find_ts_func_by_range(funcs, range) @@ -251,15 +263,7 @@ local function find_ts_func_by_range(funcs, range) return result end -function M.locations_to_items(locations) - if not locations or vim.tbl_isempty(locations) then - print("list not avalible") - return - end - local width = 4 - - local items = {} -- lsp.util.locations_to_items(locations) - -- items and locations may not matching +local function order_locations(locations) table.sort(locations, function(i, j) if i.uri == j.uri then if i.range and i.range.start then @@ -270,9 +274,63 @@ function M.locations_to_items(locations) return i.uri < j.uri end end) + return locations +end + +local function slice_locations(locations, max_items) + local cut = -1 + if #locations > max_items then + local uri = locations[max_items] + for i = max_items + 1, #locations do + if uri ~= locations[i] and not brk then + cut = i + break + end + end + end + local first_part, second_part = locations, {} + if cut > 1 and cut < #locations then + first_part = vim.list_slice(locations, 1, cut) + second_part = vim.list_slice(locations, cut + 1, #locations) + end + return first_part, second_part + +end + +local function test_locations() + local locations = { + {uri = '1', range = {start = {line = 1}}}, {uri = '2', range = {start = {line = 2}}}, + {uri = '2', range = {start = {line = 3}}}, {uri = '1', range = {start = {line = 3}}}, + {uri = '1', range = {start = {line = 4}}}, {uri = '3', range = {start = {line = 4}}}, + {uri = '3', range = {start = {line = 4}}} + } + local second_part + order_locations(locations) + local locations, second_part = slice_locations(locations, 3) + log(locations, second_part) +end + +function M.locations_to_items(locations, max_items) + max_items = max_items or 100000 -- + if not locations or vim.tbl_isempty(locations) then + print("list not avalible") + return + end + local width = 4 + + local items = {} -- lsp.util.locations_to_items(locations) + -- items and locations may not matching + local uri_def = {} + order_locations(locations) + local second_part + locations, second_part = slice_locations(locations, max_items) trace(locations) + + local cut = -1 + + local unload_bufnrs = {} for i, loc in ipairs(locations) do local funcs = nil local item = lsp.util.locations_to_items({loc})[1] @@ -286,12 +344,16 @@ function M.locations_to_items(locations) end -- only load top 30 file. local proj_file = item.uri:find(cwd) or is_win or i < 30 + local unload, def if TS_analysis_enabled and proj_file then - funcs = ts_functions(item.uri) + funcs, unload = ts_functions(item.uri) - if uri_def[item.uri] == nil or uri_def[item.uri] == {} then + if unload then + table.insert(unload_bufnrs, unload) + end + if not uri_def[item.uri] then -- find def in file - local def = ts_defination(item.uri, item.range) + def, unload = ts_definition(item.uri, item.range) if def and def.start then uri_def[item.uri] = def if def.start then -- find for the 1st time @@ -301,6 +363,16 @@ function M.locations_to_items(locations) end end end + else + if uri_def[item.uri] == false then + uri_def[item.uri] = {} -- no def in file, TODO: it is tricky the definition is in another file and it is the + -- only occurrence + else + uri_def[item.uri] = false -- no def in file + end + end + if unload then + table.insert(unload_bufnrs, unload) end end trace(uri_def[item.uri], item.range) -- set to log if need to get all in rnge @@ -325,7 +397,19 @@ function M.locations_to_items(locations) table.insert(items, item) end trace(uri_def) - return items, width + 24 -- TODO handle long line? + + -- defer release new open buffer + if #unload_bufnrs > 10 then -- load too many? + vim.defer_fn(function() + for i, bufnr_unload in ipairs(unload_bufnrs) do + if api.nvim_buf_is_loaded(bufnr_unload) and i > 10 then + api.nvim_buf_delete(bufnr_unload, {unload = true}) + end + end + end, 100) + end + + return items, width + 24, second_part -- TODO handle long line? end function M.apply_action(action_chosen) diff --git a/lua/navigator/reference.lua b/lua/navigator/reference.lua index 5536501..4055d4c 100644 --- a/lua/navigator/reference.lua +++ b/lua/navigator/reference.lua @@ -5,15 +5,17 @@ local lsphelper = require "navigator.lspwrapper" local gui = require "navigator.gui" local lsp = require "navigator.lspwrapper" local trace = require"navigator.util".trace +ListViewCtrl = ListViewCtrl or require('guihua.listviewctrl').ListViewCtrl -- local partial = util.partial -- local cwd = vim.loop.cwd() -- local lsphelper = require "navigator.lspwrapper" local locations_to_items = lsphelper.locations_to_items +local M = {} local ref_view = function(err, locations, ctx, cfg) + local truncate = cfg and cfg.truncate or 20 local opts = {} trace("arg1", err, ctx, locations) - log(ctx.api) trace(locations) -- log("num", num) -- log("bfnr", bufnr) @@ -25,14 +27,17 @@ local ref_view = function(err, locations, ctx, cfg) if type(locations) ~= 'table' then log(locations) log("ctx", ctx) - print("incorrect setup", location) + print("incorrect setup", locations) return end if locations == nil or vim.tbl_isempty(locations) then print "References not found" return end - local items, width = locations_to_items(locations) + + local items, width, second_part = locations_to_items(locations, truncate) + local thread_items = vim.deepcopy(items) + log("splits: ", #items, #second_part) local ft = vim.api.nvim_buf_get_option(ctx.bufnr, "ft") @@ -41,22 +46,50 @@ local ref_view = function(err, locations, ctx, cfg) width = math.min(width + 30, 120, math.floor(wwidth * mwidth)) -- log(items) -- log(width) - local listview = gui.new_list_view({ + opts = { + total = #locations, items = items, ft = ft, width = width, api = "Reference", enable_preview_edit = true - }) + } + local listview = gui.new_list_view(opts) + + trace("update items", listview.ctrl.class) + local nv_ref_async + nv_ref_async = vim.loop.new_async(vim.schedule_wrap(function() + log('$$$$$$$$ seperate thread... $$$$$$$$') + if vim.tbl_isempty(second_part) then + return + end + local items2 = locations_to_items(second_part) + + vim.list_extend(thread_items, items2) + + local data = require"navigator.render".prepare_for_render(thread_items, opts) + listview.ctrl:on_data_update(data) + if nv_ref_async then + vim.loop.close(nv_ref_async) + else + log("invalid asy", nv_ref_async) + end + end)) + + vim.defer_fn(function() + vim.loop.new_thread(function(asy) + asy:send() + end, nv_ref_async) + + end, 100) + return listview, items, width end -local M = {} -local ref_hdlr = mk_handler(function(err, locations, ctx, cfg) +local ref_hdlr = mk_handler(function(err, locations, ctx, cfg) trace(err, locations, ctx, cfg) M.async_hdlr = vim.loop.new_async(vim.schedule_wrap(function() ref_view(err, locations, ctx, cfg) - M.async_hdlr:close() end)) M.async_hdlr:send() diff --git a/lua/navigator/render.lua b/lua/navigator/render.lua index c3cdde1..ae8276e 100644 --- a/lua/navigator/render.lua +++ b/lua/navigator/render.lua @@ -56,6 +56,7 @@ function M.prepare_for_render(items, opts) local display_items = {item} local last_summary_idx = 1 local total_ref_in_file = 1 + local total = opts.total local icon = " " local lspapi = opts.api or "∑" @@ -87,6 +88,7 @@ function M.prepare_for_render(items, opts) local dfn = items[i].display_filename if last_summary_idx == 1 then lspapi_display = items[i].symbol_name .. ' ' .. lspapi_display + trace(items[1], lspapi_display, display_items[last_summary_idx]) end @@ -94,7 +96,7 @@ function M.prepare_for_render(items, opts) -- trace(items[i], items[i].filename, last_summary_idx, display_items[last_summary_idx].filename) -- TODO refact display_filename generate part if items[i].filename == fn then - space, trim = get_pads(opts.width, icon .. ' ' .. dfn, lspapi_display .. ' 12') + space, trim = get_pads(opts.width, icon .. ' ' .. dfn, lspapi_display .. ' 14') if trim and opts.width > 50 and #dfn > opts.width - 20 then local fn1 = string.sub(dfn, 1, opts.width - 50) local fn2 = string.sub(dfn, #dfn - 10, #dfn) @@ -102,10 +104,15 @@ function M.prepare_for_render(items, opts) space = ' ' -- log("trim", fn1, fn2) end - 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) + local api_disp = string.format("%s %s%s%s %i", icon, + display_items[last_summary_idx].display_filename, space, + lspapi_display, total_ref_in_file) + + if total then + api_disp = api_disp .. ' of: ' .. tostring(total) + end + + display_items[last_summary_idx].text = api_disp total_ref_in_file = total_ref_in_file + 1 else diff --git a/lua/navigator/treesitter.lua b/lua/navigator/treesitter.lua index 4f19b07..7bd752f 100644 --- a/lua/navigator/treesitter.lua +++ b/lua/navigator/treesitter.lua @@ -47,6 +47,30 @@ function M.goto_definition(bufnr) end end +local function node_is_definination(node) + if node:parent() == nil then + return false + end + local nd_type = node:parent():type() + local decl = {'short_var_declaration', 'short_var_declaration', 'declaration'} + + if vim.tbl_contains(decl, nd_type) then + return true + end + + if node:parent():parent() == nil then + return false + end + + nd_type = node:parent():parent():type() + if vim.tbl_contains(decl, nd_type) then + return true + end + + return false + +end + -- use lsp range to find def function M.find_definition(range, bufnr) if not range or not range.start then @@ -68,13 +92,19 @@ function M.find_definition(range, bufnr) end local definition = locals.find_definition(node_at_point, bufnr) - - if definition ~= node_at_point then - trace("err: def found:", definition:range(), definition:type()) + if definition ~= node_at_point then -- NOTE: it may not worksfor some of languages. if def not found, ts + -- returns current node. if your node is def, then it also return self... then I have no idea weather it is + -- def or not + trace("info: def found:", definition:range(), definition:type()) + local r, c = definition:range() + return {start = {line = r, character = c}} + elseif node_is_definination(node_at_point) then + trace("declaraction here ", definition:type()) local r, c = definition:range() return {start = {line = r, character = c}} else - trace("err: def not found in ", bufnr) + trace("error: def not found in ", bufnr, definition:range(), definition:type(), + definition:parent():type()) end end @@ -283,11 +313,21 @@ local function get_all_nodes(bufnr, filter, summary) -- instead of neighbors of the next nodes. local containers = { ["function"] = true, + ["local_function"] = true, ["arrow_function"] = true, ["type"] = true, ["class"] = true, ["method"] = true } + + -- check and load buff + + local should_unload = false + if not vim.api.nvim_buf_is_loaded(bufnr) then + should_unload = true + vim.fn.bufload(bufnr) + end + -- Step 2 find correct completions local length = 10 local parents = {} -- stack of nodes a clever algorithm from treesiter refactor @Santos Gallegos @@ -391,6 +431,9 @@ local function get_all_nodes(bufnr, filter, summary) trace(all_nodes) local nd = {nodes = all_nodes, ftime = vim.fn.getftime(fname), length = length} lru:set(hash, nd) + if should_unload then + vim.api.nvim_buf_delete(bufnr, {unload = true}) + end return all_nodes, length end @@ -401,6 +444,7 @@ function M.buf_func(bufnr) end bufnr = bufnr or api.nvim_get_current_buf() + local all_nodes, width = get_all_nodes(bufnr, { ["function"] = true, ["var"] = true, @@ -532,7 +576,7 @@ function M.get_node_at_line(lnum) local node = tree:root():named_descendant_for_range(unpack(range)) - trace(node, node:type()) + -- trace(node, node:type()) -- log all lines and all nodes return node end diff --git a/tests/reference_spec.lua b/tests/reference_spec.lua index 86c01c6..9773a93 100644 --- a/tests/reference_spec.lua +++ b/tests/reference_spec.lua @@ -13,6 +13,32 @@ describe("should run lsp reference", function() if debug.getinfo(vim.lsp.handlers.signature_help).nparams > 4 then nvim_6 = false end + local result = { + { + range = {['end'] = {character = 6, line = 14}, start = {character = 1, line = 14}}, + uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" + }, { + range = {['end'] = {character = 15, line = 24}, start = {character = 10, line = 24}}, + uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" + }, { + range = {['end'] = {character = 17, line = 28}, start = {character = 12, line = 28}}, + uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" + }, { + range = {['end'] = {character = 19, line = 51}, start = {character = 14, line = 51}}, + uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" + }, { + range = {['end'] = {character = 19, line = 55}, start = {character = 14, line = 55}}, + uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" + }, { + range = {['end'] = {character = 16, line = 59}, start = {character = 11, line = 59}}, + + uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" + }, { + range = {['end'] = {character = 16, line = 5}, start = {character = 11, line = 5}}, + uri = "file://" .. cur_dir .. "/tests/fixtures/interface_test.go" + } + } + it("should show references", function() local status = require("plenary.reload").reload_module("navigator") @@ -96,31 +122,6 @@ describe("should run lsp reference", function() -- allow gopls start vim.wait(200, function() end) - local result = { - { - range = {['end'] = {character = 6, line = 14}, start = {character = 1, line = 14}}, - uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" - }, { - range = {['end'] = {character = 15, line = 24}, start = {character = 10, line = 24}}, - uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" - }, { - range = {['end'] = {character = 17, line = 28}, start = {character = 12, line = 28}}, - uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" - }, { - range = {['end'] = {character = 19, line = 51}, start = {character = 14, line = 51}}, - uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" - }, { - range = {['end'] = {character = 19, line = 55}, start = {character = 14, line = 55}}, - uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" - }, { - range = {['end'] = {character = 16, line = 59}, start = {character = 11, line = 59}}, - - uri = "file://" .. cur_dir .. "/tests/fixtures/interface.go" - }, { - range = {['end'] = {character = 16, line = 5}, start = {character = 11, line = 5}}, - uri = "file://" .. cur_dir .. "/tests/fixtures/interface_test.go" - } - } local win, items, width @@ -144,6 +145,75 @@ describe("should run lsp reference", function() eq(win.ctrl.data[2].range.start.line, 14) eq(items[1].display_filename, "./interface.go") + -- eq(width, 60) + end) + it("reference handler should return items with thread", function() + + local status = require("plenary.reload").reload_module("navigator") + local status = require("plenary.reload").reload_module("guihua") + vim.cmd([[packadd navigator.lua]]) + vim.cmd([[packadd guihua.lua]]) + local path = cur_dir .. "/tests/fixtures/interface.go" -- %:p:h ? %:p + print(path) + local cmd = " silent exe 'e " .. path .. "'" + vim.cmd(cmd) + vim.cmd([[cd %:p:h]]) + local bufn = vim.fn.bufnr("") + + vim.fn.setpos(".", {bufn, 15, 4, 0}) -- width + + vim.bo.filetype = "go" + require'navigator'.setup({ + debug = true, -- log output, set to true and log path: ~/.local/share/nvim/gh.log + code_action_icon = "A ", + width = 0.75, -- max width ratio (number of cols for the floating window) / (window width) + height = 0.3, -- max list window height, 0.3 by default + preview_height = 0.35, -- max height of preview windows + debug_console_output = true, + border = 'none' + }) + + _NgConfigValues.debug_console_output = true + + vim.bo.filetype = "go" + -- allow gopls start + for i = 1, 10 do + vim.wait(400, function() + end) + local clients = vim.lsp.get_active_clients() + print("clients ", #clients) + if #clients > 0 then + break + end + end + + -- allow gopls start + vim.wait(200, function() + end) + + local win, items, width + + if nvim_6 then + win, items, width = require('navigator.reference').ref_view(nil, result, { + method = 'textDocument/references', + bufnr = 1, + client_id = 1 + }, {truncate = 2}) + + else + win, items, width = require('navigator.reference').reference_handler(nil, + "textDocument/references", + result, 1, 1) + + end + + print("win", vim.inspect(win)) + print("items", vim.inspect(items)) + -- eq(win.ctrl.data, "./interface.go") + eq(win.ctrl.data[1].display_filename, "./interface.go") + eq(win.ctrl.data[2].range.start.line, 14) + -- eq(items[1].display_filename, "./interface.go") + -- eq(width, 60) end) end)