From edee3e39c6b8232ea88f8759965bf6dcabc10b83 Mon Sep 17 00:00:00 2001 From: ray-x Date: Sat, 27 Aug 2022 11:49:08 +1000 Subject: [PATCH] merge changes for treesitter symbole context --- README.md | 1 + lua/navigator.lua | 6 +- lua/navigator/ctags.lua | 2 +- lua/navigator/definition.lua | 3 +- lua/navigator/lspwrapper.lua | 26 +++++-- lua/navigator/render.lua | 33 ++------ lua/navigator/treesitter.lua | 142 ++++++++++++++++++++++++++++++----- lua/navigator/workspace.lua | 2 +- 8 files changed, 160 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 774acb9..f0a78bd 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ require'navigator'.setup({ -- please check mapping.lua for all keymaps treesitter_analysis = true, -- treesitter variable context treesitter_analysis_max_num = 100, -- how many items to run treesitter analysis + treesitter_analysis_condense = true, -- condense form for treesitter analysis -- this value prevent slow in large projects, e.g. found 100000 reference in a project transparency = 50, -- 0 ~ 100 blur the main window, 100: fully transparent, 0: opaque, set to nil or 100 to disable it diff --git a/lua/navigator.lua b/lua/navigator.lua index 93e0143..eb43869 100755 --- a/lua/navigator.lua +++ b/lua/navigator.lua @@ -32,11 +32,15 @@ _NgConfigValues = { ts_fold = false, treesitter_analysis = true, -- treesitter variable context treesitter_analysis_max_num = 100, -- how many items to run treesitter analysis + treesitter_analysis_condense = true, -- short format of function transparency = 50, -- 0 ~ 100 blur the main window, 100: fully transparent, 0: opaque, set to nil to disable it lsp_signature_help = true, -- if you would like to hook ray-x/lsp_signature plugin in navigator -- setup here. if it is nil, navigator will not init signature help signature_help_cfg = { debug = false }, -- if you would like to init ray-x/lsp_signature plugin in navigator, pass in signature help - ctags = { cmd = 'ctags', tagfile = '.tags', options = '-R --exclude=.git --exclude=node_modules --exclude=test --exclude=vendor --excmd=number', + ctags = { + cmd = 'ctags', + tagfile = '.tags', + options = '-R --exclude=.git --exclude=node_modules --exclude=test --exclude=vendor --excmd=number', }, lsp = { enable = true, -- if disabled make sure add require('navigator.lspclient.mapping').setup() in you on_attach diff --git a/lua/navigator/ctags.lua b/lua/navigator/ctags.lua index eb4eb23..eeaec6d 100644 --- a/lua/navigator/ctags.lua +++ b/lua/navigator/ctags.lua @@ -89,7 +89,7 @@ local function ctags_symbols() local height = _NgConfigValues.height or 0.4 local width = _NgConfigValues.width or 0.7 height = math.floor(height * vfn.winheight('%')) - width = math.floor(width * vfn.winwidth('%')) + width = math.floor(vim.api.nvim_get_option('columns') * width) local items = {} local ctags_file = _NgConfigValues.ctags.tagfile if not util.file_exists(ctags_file) then diff --git a/lua/navigator/definition.lua b/lua/navigator/definition.lua index 8c3b941..f3692b8 100644 --- a/lua/navigator/definition.lua +++ b/lua/navigator/definition.lua @@ -117,7 +117,8 @@ local function def_preview(timeout_ms) end end local width = 40 - local maxwidth = math.floor(vim.fn.winwidth(0) * 4 / 5) + + local maxwidth = math.floor( vim.api.nvim_get_option('columns') * 0.8) for _, value in pairs(definition) do -- log(key, value, width) width = math.max(width, #value + 4) diff --git a/lua/navigator/lspwrapper.lua b/lua/navigator/lspwrapper.lua index 0c27e34..8179e62 100644 --- a/lua/navigator/lspwrapper.lua +++ b/lua/navigator/lspwrapper.lua @@ -19,7 +19,7 @@ cwd = gutil.add_pec(cwd) local ts_nodes = require('navigator.lru').new(1000, 1024 * 1024) local ts_nodes_time = require('navigator.lru').new(1000) local TS_analysis_enabled = require('navigator').config_values().treesitter_analysis - +local nts = require('navigator.treesitter') -- extract symbol from range function M.get_symbol(text, range) if range == nil then @@ -169,7 +169,7 @@ local function ts_functions(uri, optional) lerr('ts not enabled') return nil end - local ts_func = require('navigator.treesitter').buf_func + local ts_func = nts.buf_func local bufnr = vim.uri_to_bufnr(uri) local x = os.clock() trace(ts_nodes) @@ -230,7 +230,7 @@ local function ts_definition(uri, range, optional) if optional then return end - local ts_def = require('navigator.treesitter').find_definition + local ts_def = nts.find_definition local bufnr = vim.uri_to_bufnr(uri) local x = os.clock() trace(ts_nodes) @@ -252,6 +252,7 @@ local function ts_definition(uri, range, optional) end local function find_ts_func_by_range(funcs, range) + log(funcs, range) if funcs == nil or range == nil then return nil end @@ -352,7 +353,6 @@ function M.locations_to_items(locations, ctx) local unload_bufnrs = {} for i, loc in ipairs(locations) do - local funcs = nil local item = lsp.util.locations_to_items({ loc }, enc)[1] item.range = locations[i].range or locations[i].targetRange item.uri = locations[i].uri or locations[i].targetUri @@ -363,13 +363,25 @@ function M.locations_to_items(locations, ctx) log(cwd) end -- 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 < _NgConfigValues.treesitter_analysis_max_num local unload, def + local context = '' if TS_analysis_enabled and proj_file then - funcs, unload = ts_functions(item.uri, ts_optional(i, #unload_bufnrs)) + local ts_context = nts.ref_context + + local bufnr = vim.uri_to_bufnr(item.uri) + if not api.nvim_buf_is_loaded(bufnr) then + log('! load buf !', item.uri, bufnr) + vim.fn.bufload(bufnr) + unload = bufnr + end + context = ts_context({ bufnr = bufnr, pos = item.range }) or '' + log(context) + -- TODO: unload buffers if unload then table.insert(unload_bufnrs, unload) + unload = nil end if not uri_def[item.uri] then -- find def in file @@ -410,7 +422,7 @@ function M.locations_to_items(locations, ctx) item.filename = assert(vim.uri_to_fname(item.uri)) local filename = item.filename:gsub(cwd .. path_sep, path_cur, 1) item.display_filename = filename or item.filename - item.call_by = find_ts_func_by_range(funcs, item.range) + item.call_by = context -- find_ts_func_by_range(funcs, item.range) item.rpath = util.get_relative_path(cwd, item.filename) width = math.max(width, #item.text) item.symbol_name = M.get_symbol(item.text, item.range) diff --git a/lua/navigator/render.lua b/lua/navigator/render.lua index 8259eed..8c96d16 100644 --- a/lua/navigator/render.lua +++ b/lua/navigator/render.lua @@ -69,7 +69,7 @@ function M.prepare_for_render(items, opts) icon = devicons.get_icon(fn, ext) or icon end -- local call_by_presented = false - opts.width = opts.width or 100 + opts.width = opts.width or math.floor( vim.api.nvim_get_option('columns') * 0.8) local win_width = opts.width -- buf for i = 1, #items do @@ -150,31 +150,8 @@ function M.prepare_for_render(items, opts) trace(ts_report, header_len) 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 = '()' - local syb = items[i].symbol_name - if txt == items[i].symbol_name or (#txt > #syb and txt:sub(#txt - #syb + 1) == syb) then - if ts_report ~= _NgConfigValues.icons.value_definition .. ' ' then - ts_report = ts_report .. _NgConfigValues.icons.value_definition .. ' ' - end - header_len = #ts_report + 1 - else - ts_report = ts_report .. ' ' - end - end - if #ts_report > header_len then - ts_report = ts_report .. '  ' - end - ts_report = ts_report .. value.kind .. txt .. endwise - trace(item) - end - end + if item.call_by ~= nil and item.call_by ~= '' then + ts_report = ts_report .. ' ' .. item.call_by end if #ts_report > 1 then space, trim = get_pads(win_width, item.text, ts_report) @@ -195,7 +172,9 @@ function M.prepare_for_render(items, opts) if #space + #item.text + #ts_report >= win_width then if #item.text + #ts_report > win_width then trace('exceeding', #item.text, #ts_report, win_width) - space = ' ' + space = ' ' + local len = math.max(win_width - #item.text - 4, 16) + ts_report = ts_report:sub(1, len) else local remain = win_width - #item.text - #ts_report trace('remain', remain) diff --git a/lua/navigator/treesitter.lua b/lua/navigator/treesitter.lua index 9a31052..bff5561 100644 --- a/lua/navigator/treesitter.lua +++ b/lua/navigator/treesitter.lua @@ -23,6 +23,7 @@ local cwd = vim.loop.cwd() local log = require('navigator.util').log local lerr = require('navigator.util').error local trace = function(...) end +trace = log if vim.fn.has('nvim-0.7') == 1 then local trace = require('navigator.util').trace end @@ -109,6 +110,95 @@ function M.find_definition(range, bufnr) end end +function M.get_tsnode_at_pos(pos, bufnr, ignore_injected_langs) + if not pos or not pos.start then + return + end + local cursor_range = { pos.start.line, pos.start.character } + + local buf = bufnr + local root_lang_tree = parsers.get_parser(buf) + if not root_lang_tree then + return + end + + local root + if ignore_injected_langs then + for _, tree in ipairs(root_lang_tree:trees()) do + local tree_root = tree:root() + if tree_root and ts_utils.is_in_node_range(tree_root, cursor_range[1], cursor_range[2]) then + root = tree_root + break + end + end + else + root = ts_utils.get_root_for_position(cursor_range[1], cursor_range[2], root_lang_tree) + end + + if not root then + return + end + + return root:named_descendant_for_range(cursor_range[1], cursor_range[2], cursor_range[1], cursor_range[2]) +end + +-- Trim spaces and opening brackets from end +local transform_line = function(line) + line = line:gsub("%s*[%[%(%{]*%s*$", "") + line = line:gsub("function", "") + line = line:gsub("func%w*%s+", "") + if _NgConfigValues.treesitter_analysis_condense then + line = line:gsub("%([%a,%s]+%)", "()") + end + return line +end + +function M.ref_context(opts) + if not parsers.has_parser() then + return + end + local options = opts or {} + -- if type(opts) == "number" then + -- options = { indicator_size = opts } + -- end + + local bufnr = options.bufnr or 0 + local pos = options.pos + if not pos then + pos = {start = vim.lsp.util.make_position_params().position} + end + local indicator_size = options.indicator_size or 100 + local type_patterns = options.type_patterns or { "class", "function", "method" } + local transform_fn = options.transform_fn or transform_line + local separator = options.separator or "  " + + local current_node = M.get_tsnode_at_pos(pos, bufnr) + if not current_node then + log('no node at pos', bufnr, pos) + return "" + end + + local lines = {} + local expr = current_node + + while expr do + local line = ts_utils._get_line_for_node(expr, type_patterns, transform_fn, bufnr) + log(line) + if line ~= "" and not vim.tbl_contains(lines, line) then + table.insert(lines, 1, line) + end + expr = expr:parent() + end + + local text = table.concat(lines, separator) + local text_len = #text + if text_len > indicator_size then + return "..." .. text:sub(text_len - indicator_size, text_len) + end + + return text +end + --- Get definitions of bufnr (unique and sorted by order of appearance). --- This function copy from treesitter/refactor/navigation.lua local function get_definitions(bufnr) @@ -121,23 +211,23 @@ local function get_definitions(bufnr) ts_locals.recurse_local_nodes(loc.definition, function(_, node, _, match) -- lua doesn't compare tables by value, -- use the value from byte count instead. - local k, l, start = node:start() + local row, col, offset = node:start() + local erow, ecol, end_ = node:end_() trace(node, match) - trace(k, l, start, node:parent(), node:parent():start(), node:parent():type()) + trace(row, col, erow, offset, node:parent(), node:parent():start(), node:parent():type()) if node and node:parent() and string.find(node:parent():type(), 'parameter_declaration') then log('parameter_declaration skip') return end - nodes_set[start] = { node = node, type = match or '' } + nodes_set[offset] = { node = node, type = match or '' } end) end if loc.method then -- for go ts_locals.recurse_local_nodes(loc.method, function(def, node, full_match, match) - local k, l, start = node:start() - - trace(k, l, start, def, node, full_match, match, node:parent(), node:parent():start(), node:parent():type()) + local row, col, start = node:start() + trace(row, col, start, def, node, full_match, match, node:parent(), node:parent():start(), node:parent():type()) if node:type() == 'field_identifier' and nodes_set[start] == nil then nodes_set[start] = { node = node, type = 'method' } end @@ -154,16 +244,32 @@ local function get_definitions(bufnr) end if loc.reference then -- for go ts_locals.recurse_local_nodes(loc.reference, function(def, node, full_match, match) - local k, l, start = node:start() + local row, col, start = node:start() local p1, p1t = '', '' local p2, p2t = '', '' + local p3, p3t = '', '' if node:parent() and node:parent():parent() then p1 = node:parent() p1t = node:parent():type() p2 = node:parent():parent() p2t = node:parent():parent():type() end - trace(k, l, start, def, node, full_match, match, p1t, p1, node:parent():start(), node:parent():type(), p2, p2t) + if p2 and p2:parent() then + p3 = p2:parent() + p3t = p2:parent():type() + end + trace(row, col, start, def, node, full_match, match, p1t, p1, node:parent():start(), node:parent():type(), p2, p2t, p3, p3t) + if p1t == 'arrow_function' then + row, col, start = p1:start() + trace('arrow_function 1', row, col) + nodes_set[start] = { node = p1, type = p1t } + end + + if p2t == 'arrow_function' then + row, col, start = p2:start() + trace('arrow_function 2', row, col) + nodes_set[start] = { node = p2, type = p2t } + end if nodes_set[start] == nil then if -- qualified_type : e.g. io.Reader inside interface node:parent() @@ -210,7 +316,7 @@ local function get_scope(type, source) local parent = current:parent() trace(source:type(), source:range(), parent) - if type == 'method' or type == 'function' and parent ~= nil then + if type == 'method' or type:find('function') and parent ~= nil then trace(parent:type(), parent:range()) -- a function name if parent:type() == 'function_name' then @@ -345,7 +451,7 @@ local function get_all_nodes(bufnr, filter, summary) trace(bufnr, filter, summary) if not bufnr then - vim.notify('get_all_node invalide bufnr', vim.lsp.log_levels.WARN) + vim.notify('get_all_node invalid bufnr', vim.lsp.log_levels.WARN) end summary = summary or false local ft = vim.api.nvim_buf_get_option(bufnr, 'filetype') @@ -362,17 +468,13 @@ local function get_all_nodes(bufnr, filter, summary) local display_filename = fname:gsub(cwd .. path_sep, path_cur, 1) local all_nodes = {} - -- Support completion-nvim customized label map - -- local customized_labels = vim.g.completion_customize_lsp_label or {} - - -- Force some types to act like they are parents - -- instead of neighbors of the next nodes. - local containers = { + local containers = filter or { ['function'] = true, ['local_function'] = true, ['arrow_function'] = true, ['type'] = true, ['class'] = true, + ['call_expression'] = true, -- ['var'] = true, ['struct'] = true, ['method'] = true, @@ -440,7 +542,10 @@ local function get_all_nodes(bufnr, filter, summary) trace('skipped', item.type, item.kind) goto continue end - item.node_text = vim.treesitter.get_node_text(tsdata, bufnr) or '' + local text = vim.treesitter.get_node_text(tsdata, bufnr) or '' + text = vim.split(text, '\n')[1] or '' + item.node_text = text + log(item.node_text) local scope, is_func if summary then @@ -448,6 +553,7 @@ local function get_all_nodes(bufnr, filter, summary) else scope, is_func = get_smallest_context(tsdata) end + log(item, scope, is_func) if is_func then -- hack for lua and maybe other language aswell local parent = tsdata:parent() @@ -581,6 +687,7 @@ function M.buf_func(bufnr) local all_nodes, width = get_all_nodes(bufnr, { ['function'] = true, + ['arrow_function'] = true, ['var'] = true, ['method'] = true, ['class'] = true, @@ -614,6 +721,7 @@ function M.buf_func(bufnr) return false end) end + log(all_nodes) return all_nodes, width end diff --git a/lua/navigator/workspace.lua b/lua/navigator/workspace.lua index 3a6704a..3928ff6 100644 --- a/lua/navigator/workspace.lua +++ b/lua/navigator/workspace.lua @@ -37,7 +37,7 @@ function M.workspace_symbol_live() local height = _NgConfigValues.height or 0.4 height = math.floor(height * vfn.winheight('%')) local width = _NgConfigValues.width or 0.7 - width = math.floor(width * vfn.winwidth('%')) + width = math.floor(vim.api.nvim_get_option('columns') * width) local bufnr = vim.api.nvim_get_current_buf() local ft = vim.o.ft local data = { { text = 'input the symbol name to start fuzzy search' } }