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
pull/63/head
rayx 3 years ago committed by GitHub
parent 507ad0b146
commit 91e22f5e71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 all features(handler) provided by LSP from commonly used search reference, to less commonly used search for interface
implementation. implementation.
- Luv async job - Luv async thread and tasks
- Edit your code in preview window - 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?) - 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 - 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 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 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. together. If you have multiple similar LSP installed and have trouble with the plugin, please enable only one at a time.

@ -27,7 +27,8 @@ _NgConfigValues = {
code_lens = false, code_lens = false,
-- only want to enable one lsp server -- only want to enable one lsp server
disply_diagnostic_qf = true, -- always show quickfix if there are diagnostic errors 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_virtual_text = true, -- show virtual for diagnostic message
diagnostic_update_in_insert = false, -- update diagnostic message in insert mode 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 diagnostic_scrollbar_sign = {'', ''}, -- set to nil to disable, set to {'╍', 'ﮆ'} to enable diagnostic status in scroll bar area

@ -69,7 +69,7 @@ function M.setup()
local on_codelens = vim.lsp.handlers["textDocument/codeLens"] local on_codelens = vim.lsp.handlers["textDocument/codeLens"]
vim.lsp.handlers["textDocument/codeLens"] = mk_handler( vim.lsp.handlers["textDocument/codeLens"] = mk_handler(
function(err, result, ctx, cfg) 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 if nvim_0_6() then
on_codelens(err, result, ctx, cfg) on_codelens(err, result, ctx, cfg)
codelens_hdlr(err, result, ctx, cfg) codelens_hdlr(err, result, ctx, cfg)

@ -18,7 +18,8 @@ local function error_marker(result, client_id)
return return
end end
local first_line = vim.fn.line('w0') 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) local bufnr = vim.uri_to_bufnr(result.uri)
if bufnr ~= vim.api.nvim_get_current_buf() then 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) log(err, config)
return return
end 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 cwd = vim.loop.cwd()
local ft = vim.bo.filetype local ft = vim.bo.filetype
if diagnostic_list[ft] == nil then if diagnostic_list[ft] == nil then
diagnostic_list[vim.bo.filetype] = {} diagnostic_list[vim.bo.filetype] = {}
end end
-- vim.lsp.diagnostic.clear(vim.fn.bufnr(), client.id, nil, nil) -- vim.lsp.diagnostic.clear(vim.fn.bufnr(), client.id, nil, nil)
if util.nvim_0_6() then if util.nvim_0_6() then
vim.lsp.diagnostic.on_publish_diagnostics(err, result, ctx, config) vim.lsp.diagnostic.on_publish_diagnostics(err, result, ctx, config)
else else
log("old version of lsp nvim 050")
vim.lsp.diagnostic.on_publish_diagnostics(err, _, result, ctx.client_id, _, config) vim.lsp.diagnostic.on_publish_diagnostics(err, _, result, ctx.client_id, _, config)
end end
local uri = result.uri local uri = result.uri
@ -123,8 +128,11 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config)
log("diag", err, result) log("diag", err, result)
return return
end end
if vim.fn.mode() ~= 'n' and config.update_in_insert == false then
-- log("diag: ", result, client_id) log("skip in insert mode")
return
end
trace("diag: ", vim.fn.mode(), result, ctx, config)
if result and result.diagnostics then if result and result.diagnostics then
local item_list = {} local item_list = {}
for _, v in ipairs(result.diagnostics) do for _, v in ipairs(result.diagnostics) do
@ -146,21 +154,30 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config)
end end
local bufnr1 = vim.uri_to_bufnr(uri) local bufnr1 = vim.uri_to_bufnr(uri)
if not vim.api.nvim_buf_is_loaded(bufnr1) then if not vim.api.nvim_buf_is_loaded(bufnr1) then
vim.fn.bufload(bufnr1) if _NgConfigValues.diagnostic_load_files then
end vim.fn.bufload(bufnr1) -- this may slow down the neovim
local pos = v.range.start local pos = v.range.start
local row = pos.line local row = pos.line
local line = (vim.api.nvim_buf_get_lines(bufnr1, row, row + 1, false) or {""})[1] local line = (vim.api.nvim_buf_get_lines(bufnr1, row, row + 1, false) or {""})[1]
if line ~= nil then if line ~= nil then
item.text = head .. line .. _NgConfigValues.icons.diagnostic_head_description .. v.message item.text = head .. line .. _NgConfigValues.icons.diagnostic_head_description
table.insert(item_list, item) .. v.message
else table.insert(item_list, item)
error("diagnostic result empty line", v, row, bufnr1) 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
end end
-- local old_items = vim.fn.getqflist() -- local old_items = vim.fn.getqflist()
diagnostic_list[ft][uri] = item_list diagnostic_list[ft][uri] = item_list
result.uri = uri if not result.uri then
result.uri = uri
end
error_marker(result, ctx.client_id) error_marker(result, ctx.client_id)
else else
vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1) vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1)
@ -173,18 +190,21 @@ local M = {}
local diagnostic_cfg = { local diagnostic_cfg = {
-- Enable underline, use default values -- Enable underline, use default values
underline = true, underline = true,
-- Enable virtual text, override spacing to 0 -- Enable virtual text, override spacing to 3 (prevent overlap)
virtual_text = {spacing = 0, prefix = _NgConfigValues.icons.diagnostic_virtual_text}, virtual_text = {spacing = 3, prefix = _NgConfigValues.icons.diagnostic_virtual_text},
-- Use a function to dynamically turn signs off -- Use a function to dynamically turn signs off
-- and on, using buffer local variables -- and on, using buffer local variables
signs = true, 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 if _NgConfigValues.lsp.diagnostic_virtual_text == false then
diagnostic_cfg.virtual_text = false diagnostic_cfg.virtual_text = false
end end
-- vim.lsp.handlers["textDocument/publishDiagnostics"] -- vim.lsp.handlers["textDocument/publishDiagnostics"]
M.diagnostic_handler = vim.lsp.with(diag_hdlr, diagnostic_cfg) M.diagnostic_handler = vim.lsp.with(diag_hdlr, diagnostic_cfg)
@ -252,7 +272,7 @@ function M.update_err_marker()
-- nothing to update -- nothing to update
return return
end end
local bufnr = vim.fn.bufnr() local bufnr = vim.api.nvim_get_current_buf()
local diag_cnt = vim.lsp.diagnostic.get_count(bufnr, [[Error]]) local diag_cnt = vim.lsp.diagnostic.get_count(bufnr, [[Error]])
+ vim.lsp.diagnostic.get_count(bufnr, [[Warning]]) + vim.lsp.diagnostic.get_count(bufnr, [[Warning]])

@ -57,7 +57,7 @@ function M.setup_plugin()
M.active_folding_clients[client_id] = server_supports_folding M.active_folding_clients[client_id] = server_supports_folding
end end
end end
print(vim.inspect(M.active_folding_clients)) -- print(vim.inspect(M.active_folding_clients))
end end
function M.update_folds() function M.update_folds()

@ -159,6 +159,17 @@ end)
function M.get_fold_indic(lnum) function M.get_fold_indic(lnum)
local buf = api.nvim_get_current_buf() 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 {} local levels = folds_levels(buf) or {}
-- log(lnum, levels[lnum]) -- TODO: comment it out in master -- log(lnum, levels[lnum]) -- TODO: comment it out in master

@ -140,80 +140,83 @@ function M.new_list_view(opts)
local border = _NgConfigValues.border or 'shadow' local border = _NgConfigValues.border or 'shadow'
if data and not vim.tbl_isempty(data) then if not data or vim.tbl_isempty(data) then
-- replace return
-- TODO: 10 vimrc opt end
if #data > 10 and opts.prompt == nil then
loc = "top_center" -- replace
prompt = true -- TODO: 10 vimrc opt
end 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 local offset_y = r + lheight
-- style shadow took 1 lines -- style shadow took 1 lines
if border ~= 'none' then if border ~= 'none' then
if border == 'shadow' then if border == 'shadow' then
offset_y = offset_y + 1 offset_y = offset_y + 1
else else
offset_y = offset_y + 1 -- single? 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
end 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 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 end
return M return M

@ -94,7 +94,7 @@ local setups = {
-- "-rpc.trace", -- "-rpc.trace",
}, },
flags = {allow_incremental_sync = true, debounce_text_changes = 500}, flags = {allow_incremental_sync = true, debounce_text_changes = 1000},
settings = { settings = {
gopls = { gopls = {
-- more settings: https://github.com/golang/tools/blob/master/gopls/doc/settings.md -- more settings: https://github.com/golang/tools/blob/master/gopls/doc/settings.md
@ -112,7 +112,7 @@ local setups = {
staticcheck = true, staticcheck = true,
matcher = "fuzzy", matcher = "fuzzy",
diagnosticsDelay = "500ms", diagnosticsDelay = "500ms",
experimentalWatchedFileDelay = "100ms", experimentalWatchedFileDelay = "1000ms",
symbolMatcher = "fuzzy", symbolMatcher = "fuzzy",
gofumpt = false, -- true, -- turn on for new repos, gofmpt is good but also create code turmoils gofumpt = false, -- true, -- turn on for new repos, gofmpt is good but also create code turmoils
buildFlags = {"-tags", "integration"} buildFlags = {"-tags", "integration"}
@ -258,7 +258,7 @@ end
local default_cfg = { local default_cfg = {
on_attach = on_attach, 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 -- check and load based on file type

@ -123,18 +123,17 @@ function M.check_capabilities(feature, client_id)
for _, client in pairs(clients) do for _, client in pairs(clients) do
supported_client = client.resolved_capabilities[feature] supported_client = client.resolved_capabilities[feature]
if supported_client then if supported_client then
goto continue break
end end
end end
::continue::
if supported_client then if supported_client then
return true return true
else else
if #clients == 0 then if #clients == 0 then
print("LSP: no client attached") log("LSP: no client attached")
else else
log("LSP: server does not support " .. feature) trace("LSP: server does not support " .. feature)
end end
return false return false
end end
@ -164,6 +163,7 @@ function M.call_async(method, params, handler)
end end
local function ts_functions(uri) local function ts_functions(uri)
local unload_bufnr
local ts_enabled, _ = pcall(require, "nvim-treesitter.locals") local ts_enabled, _ = pcall(require, "nvim-treesitter.locals")
if not ts_enabled or not TS_analysis_enabled then if not ts_enabled or not TS_analysis_enabled then
lerr("ts not enabled") lerr("ts not enabled")
@ -175,7 +175,7 @@ local function ts_functions(uri)
trace(ts_nodes) trace(ts_nodes)
local tsnodes = ts_nodes:get(uri) local tsnodes = ts_nodes:get(uri)
if tsnodes ~= nil then if tsnodes ~= nil then
log("get data from cache") trace("get data from cache")
local t = ts_nodes_time:get(uri) or 0 local t = ts_nodes_time:get(uri) or 0
local fname = vim.uri_to_fname(uri) local fname = vim.uri_to_fname(uri)
local modified = vim.fn.getftime(fname) local modified = vim.fn.getftime(fname)
@ -191,28 +191,40 @@ local function ts_functions(uri)
if not api.nvim_buf_is_loaded(bufnr) then if not api.nvim_buf_is_loaded(bufnr) then
trace("! load buf !", uri, bufnr) trace("! load buf !", uri, bufnr)
vim.fn.bufload(bufnr) vim.fn.bufload(bufnr)
-- vim.api.nvim_buf_detach(bufnr) -- if user opens the buffer later, it prevents user attach event
unload = true unload = true
end end
local funcs = ts_func(bufnr) local funcs = ts_func(bufnr)
if unload then if unload then
local cmd = string.format("bd %d", bufnr) unload_bufnr = bufnr
trace(cmd)
-- vim.cmd(cmd) -- todo: not sure if it is needed
end end
ts_nodes:set(uri, funcs) ts_nodes:set(uri, funcs)
ts_nodes_time:set(uri, os.time()) ts_nodes_time:set(uri, os.time())
trace(funcs, ts_nodes:get(uri)) trace(funcs, ts_nodes:get(uri))
trace(string.format("elapsed time: %.4f\n", os.clock() - x)) -- how long it tooks trace(string.format("elapsed time: %.4f\n", os.clock() - x)) -- how long it tooks
return funcs return funcs, unload_bufnr
end end
local function ts_defination(uri, range) local function ts_definition(uri, range)
local unload_bufnr
local ts_enabled, _ = pcall(require, "nvim-treesitter.locals") local ts_enabled, _ = pcall(require, "nvim-treesitter.locals")
if not ts_enabled or not TS_analysis_enabled then if not ts_enabled or not TS_analysis_enabled then
lerr("ts not enabled") lerr("ts not enabled")
return nil return nil
end 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 ts_def = require"navigator.treesitter".find_definition
local bufnr = vim.uri_to_bufnr(uri) local bufnr = vim.uri_to_bufnr(uri)
local x = os.clock() local x = os.clock()
@ -224,14 +236,14 @@ local function ts_defination(uri, range)
unload = true unload = true
end end
local def_range = ts_def(range, bufnr) local def_range = ts_def(range, bufnr) or {}
if unload then if unload then
local cmd = string.format("bd %d", bufnr) unload_bufnr = bufnr
log(cmd)
-- vim.cmd(cmd) -- todo: not sure if it is needed
end end
trace(string.format(" ts def elapsed time: %.4f\n", os.clock() - x), def_range) -- how long it takes 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 end
local function find_ts_func_by_range(funcs, range) local function find_ts_func_by_range(funcs, range)
@ -251,15 +263,7 @@ local function find_ts_func_by_range(funcs, range)
return result return result
end end
function M.locations_to_items(locations) local function order_locations(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
table.sort(locations, function(i, j) table.sort(locations, function(i, j)
if i.uri == j.uri then if i.uri == j.uri then
if i.range and i.range.start then if i.range and i.range.start then
@ -270,9 +274,63 @@ function M.locations_to_items(locations)
return i.uri < j.uri return i.uri < j.uri
end end
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 = {} local uri_def = {}
order_locations(locations)
local second_part
locations, second_part = slice_locations(locations, max_items)
trace(locations) trace(locations)
local cut = -1
local unload_bufnrs = {}
for i, loc in ipairs(locations) do for i, loc in ipairs(locations) do
local funcs = nil local funcs = nil
local item = lsp.util.locations_to_items({loc})[1] local item = lsp.util.locations_to_items({loc})[1]
@ -286,12 +344,16 @@ function M.locations_to_items(locations)
end end
-- only load top 30 file. -- only load top 30 file.
local proj_file = item.uri:find(cwd) or is_win or i < 30 local proj_file = item.uri:find(cwd) or is_win or i < 30
local unload, def
if TS_analysis_enabled and proj_file then 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 -- 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 if def and def.start then
uri_def[item.uri] = def uri_def[item.uri] = def
if def.start then -- find for the 1st time if def.start then -- find for the 1st time
@ -301,6 +363,16 @@ function M.locations_to_items(locations)
end end
end 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
end end
trace(uri_def[item.uri], item.range) -- set to log if need to get all in rnge 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) table.insert(items, item)
end end
trace(uri_def) 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 end
function M.apply_action(action_chosen) function M.apply_action(action_chosen)

@ -5,15 +5,17 @@ local lsphelper = require "navigator.lspwrapper"
local gui = require "navigator.gui" local gui = require "navigator.gui"
local lsp = require "navigator.lspwrapper" local lsp = require "navigator.lspwrapper"
local trace = require"navigator.util".trace local trace = require"navigator.util".trace
ListViewCtrl = ListViewCtrl or require('guihua.listviewctrl').ListViewCtrl
-- local partial = util.partial -- local partial = util.partial
-- local cwd = vim.loop.cwd() -- local cwd = vim.loop.cwd()
-- local lsphelper = require "navigator.lspwrapper" -- local lsphelper = require "navigator.lspwrapper"
local locations_to_items = lsphelper.locations_to_items local locations_to_items = lsphelper.locations_to_items
local M = {}
local ref_view = function(err, locations, ctx, cfg) local ref_view = function(err, locations, ctx, cfg)
local truncate = cfg and cfg.truncate or 20
local opts = {} local opts = {}
trace("arg1", err, ctx, locations) trace("arg1", err, ctx, locations)
log(ctx.api)
trace(locations) trace(locations)
-- log("num", num) -- log("num", num)
-- log("bfnr", bufnr) -- log("bfnr", bufnr)
@ -25,14 +27,17 @@ local ref_view = function(err, locations, ctx, cfg)
if type(locations) ~= 'table' then if type(locations) ~= 'table' then
log(locations) log(locations)
log("ctx", ctx) log("ctx", ctx)
print("incorrect setup", location) print("incorrect setup", locations)
return return
end end
if locations == nil or vim.tbl_isempty(locations) then if locations == nil or vim.tbl_isempty(locations) then
print "References not found" print "References not found"
return return
end 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") 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)) width = math.min(width + 30, 120, math.floor(wwidth * mwidth))
-- log(items) -- log(items)
-- log(width) -- log(width)
local listview = gui.new_list_view({ opts = {
total = #locations,
items = items, items = items,
ft = ft, ft = ft,
width = width, width = width,
api = "Reference", api = "Reference",
enable_preview_edit = true 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 return listview, items, width
end 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) trace(err, locations, ctx, cfg)
M.async_hdlr = vim.loop.new_async(vim.schedule_wrap(function() M.async_hdlr = vim.loop.new_async(vim.schedule_wrap(function()
ref_view(err, locations, ctx, cfg) ref_view(err, locations, ctx, cfg)
M.async_hdlr:close() M.async_hdlr:close()
end)) end))
M.async_hdlr:send() M.async_hdlr:send()

@ -56,6 +56,7 @@ function M.prepare_for_render(items, opts)
local display_items = {item} local display_items = {item}
local last_summary_idx = 1 local last_summary_idx = 1
local total_ref_in_file = 1 local total_ref_in_file = 1
local total = opts.total
local icon = "" local icon = ""
local lspapi = opts.api or "" local lspapi = opts.api or ""
@ -87,6 +88,7 @@ function M.prepare_for_render(items, opts)
local dfn = items[i].display_filename local dfn = items[i].display_filename
if last_summary_idx == 1 then if last_summary_idx == 1 then
lspapi_display = items[i].symbol_name .. ' ' .. lspapi_display lspapi_display = items[i].symbol_name .. ' ' .. lspapi_display
trace(items[1], lspapi_display, display_items[last_summary_idx]) trace(items[1], lspapi_display, display_items[last_summary_idx])
end 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) -- trace(items[i], items[i].filename, last_summary_idx, display_items[last_summary_idx].filename)
-- TODO refact display_filename generate part -- TODO refact display_filename generate part
if items[i].filename == fn then 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 if trim and opts.width > 50 and #dfn > opts.width - 20 then
local fn1 = string.sub(dfn, 1, opts.width - 50) local fn1 = string.sub(dfn, 1, opts.width - 50)
local fn2 = string.sub(dfn, #dfn - 10, #dfn) local fn2 = string.sub(dfn, #dfn - 10, #dfn)
@ -102,10 +104,15 @@ function M.prepare_for_render(items, opts)
space = ' ' space = ' '
-- log("trim", fn1, fn2) -- log("trim", fn1, fn2)
end end
display_items[last_summary_idx].text = string.format("%s %s%s%s %i", icon, local api_disp = string.format("%s %s%s%s %i", icon,
display_items[last_summary_idx] display_items[last_summary_idx].display_filename, space,
.display_filename, space, lspapi_display, total_ref_in_file)
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 total_ref_in_file = total_ref_in_file + 1
else else

@ -47,6 +47,30 @@ function M.goto_definition(bufnr)
end end
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 -- use lsp range to find def
function M.find_definition(range, bufnr) function M.find_definition(range, bufnr)
if not range or not range.start then if not range or not range.start then
@ -68,13 +92,19 @@ function M.find_definition(range, bufnr)
end end
local definition = locals.find_definition(node_at_point, bufnr) local definition = locals.find_definition(node_at_point, bufnr)
if definition ~= node_at_point then -- NOTE: it may not worksfor some of languages. if def not found, ts
if definition ~= node_at_point then -- returns current node. if your node is def, then it also return self... then I have no idea weather it is
trace("err: def found:", definition:range(), definition:type()) -- 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() local r, c = definition:range()
return {start = {line = r, character = c}} return {start = {line = r, character = c}}
else else
trace("err: def not found in ", bufnr) trace("error: def not found in ", bufnr, definition:range(), definition:type(),
definition:parent():type())
end end
end end
@ -283,11 +313,21 @@ local function get_all_nodes(bufnr, filter, summary)
-- instead of neighbors of the next nodes. -- instead of neighbors of the next nodes.
local containers = { local containers = {
["function"] = true, ["function"] = true,
["local_function"] = true,
["arrow_function"] = true, ["arrow_function"] = true,
["type"] = true, ["type"] = true,
["class"] = true, ["class"] = true,
["method"] = 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 -- Step 2 find correct completions
local length = 10 local length = 10
local parents = {} -- stack of nodes a clever algorithm from treesiter refactor @Santos Gallegos 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) trace(all_nodes)
local nd = {nodes = all_nodes, ftime = vim.fn.getftime(fname), length = length} local nd = {nodes = all_nodes, ftime = vim.fn.getftime(fname), length = length}
lru:set(hash, nd) lru:set(hash, nd)
if should_unload then
vim.api.nvim_buf_delete(bufnr, {unload = true})
end
return all_nodes, length return all_nodes, length
end end
@ -401,6 +444,7 @@ function M.buf_func(bufnr)
end end
bufnr = bufnr or api.nvim_get_current_buf() bufnr = bufnr or api.nvim_get_current_buf()
local all_nodes, width = get_all_nodes(bufnr, { local all_nodes, width = get_all_nodes(bufnr, {
["function"] = true, ["function"] = true,
["var"] = true, ["var"] = true,
@ -532,7 +576,7 @@ function M.get_node_at_line(lnum)
local node = tree:root():named_descendant_for_range(unpack(range)) 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 return node
end end

@ -13,6 +13,32 @@ describe("should run lsp reference", function()
if debug.getinfo(vim.lsp.handlers.signature_help).nparams > 4 then if debug.getinfo(vim.lsp.handlers.signature_help).nparams > 4 then
nvim_6 = false nvim_6 = false
end 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() it("should show references", function()
local status = require("plenary.reload").reload_module("navigator") local status = require("plenary.reload").reload_module("navigator")
@ -96,31 +122,6 @@ describe("should run lsp reference", function()
-- allow gopls start -- allow gopls start
vim.wait(200, function() vim.wait(200, function()
end) 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 local win, items, width
@ -144,6 +145,75 @@ describe("should run lsp reference", function()
eq(win.ctrl.data[2].range.start.line, 14) eq(win.ctrl.data[2].range.start.line, 14)
eq(items[1].display_filename, "./interface.go") 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) -- eq(width, 60)
end) end)
end) end)

Loading…
Cancel
Save