From 9f7bd6ebff4c9e8eb4e77713cecee41617f27660 Mon Sep 17 00:00:00 2001 From: rayx Date: Tue, 28 Jun 2022 14:40:19 +1000 Subject: [PATCH] Feature/198 calltree (#199) * refactor hierarchy.lua * show side panel for hierarchy * allow call hierarchy to fold and expand to show call tree * update command maps --- README.md | 12 ++ doc/navigator.txt | 262 +++++++++++++++++++++---- lua/navigator/hierarchy.lua | 293 ++++++++++++++++++++-------- lua/navigator/lspclient/mapping.lua | 1 + lua/navigator/util.lua | 18 ++ 5 files changed, 469 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 13070bc..173096c 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ variable is: - Treesitter symbols sidebar, LSP document symbole sidebar. Both with preview and folding +- Calltree: Display and expand Lsp incoming/outgoing calls hierarchy-tree with sidebar + - Fully support LSP CodeAction, CodeLens, CodeLens action. Help you improve code quality. - LRU cache for treesitter nodes @@ -108,6 +110,8 @@ variable is: - Multigrid support (different font and detachable) +- Side panel (sidebar) and floating windows + # Why a new plugin I'd like to go beyond what the system is offering. @@ -671,6 +675,12 @@ You can override the above highlight to fit your current colorscheme | ------------ | ------------------------- | | LspToggleFmt | toggle lsp auto format | | LspKeymaps | show LSP releated keymaps | +| Nctags {args} | show ctags symbols, args: -g regen ctags | +| LspRestart | reload lsp | +| LspToggleFmt | toggle lsp format | +| LspSymbols | document symbol in side panel | +| TSymobls | treesitter symbol in side panel | +| Calltree {args} | lsp call hierarchy call tree, args: -i (incomming default), -o (outgoing) | ## Screenshots @@ -691,6 +701,8 @@ Treesitter outline and Diagnostics image image +Calltree (LSP call hierarchy) +image ### GUI and multigrid support diff --git a/doc/navigator.txt b/doc/navigator.txt index c34347b..60e1661 100644 --- a/doc/navigator.txt +++ b/doc/navigator.txt @@ -18,10 +18,12 @@ CONTENTS *navigator-content 5.4.1. LSP clients.................................|navigator-lsp_clients| 5.4.1.1. Add your own servers.........|navigator-add_your_own_servers| 5.4.2. Disable a lsp client loading from navigator.|navigator-disable_a_lsp_client_loading_from_navigator| - 5.4.3. Default keymaps.........................|navigator-default_keymaps| - 5.4.4. Colors/Highlight:.....................|navigator-colors/highlight:| + 5.4.3. Try it your self.......................|navigator-try_it_your_self| + 5.4.4. Default keymaps.........................|navigator-default_keymaps| + 5.4.5. Colors/Highlight:.....................|navigator-colors/highlight:| 5.5. Dependency.........................................|navigator-dependency| - 5.6. Integration with lsp_installer (williamboman/nvim-lsp-installer).|navigator-integration_with_lsp_installer_(williamboman/nvim-lsp-installer)| + 5.6. Integrat with lsp_installer (williamboman/nvim-lsp-installer).|navigator-integrat_with_lsp_installer_(williamboman/nvim-lsp-installer)| + 5.6.1. Integration with other lsp plugins (e.g. rust-tools, go.nvim, clangd extension).|navigator-integration_with_other_lsp_plugins_(e.g._rust-tools,_go.nvim,_clangd_extension)| 5.7. Usage...................................................|navigator-usage| 5.8. Configuration...................................|navigator-configuration| 5.9. Highlight...........................................|navigator-highlight| @@ -29,10 +31,11 @@ CONTENTS *navigator-content 5.11. Screenshots......................................|navigator-screenshots| 5.11.1. Reference....................................|navigator-reference| 5.11.2. Definition preview..................|navigator-definition_preview| - 5.11.3. GUI and multigrid support....|navigator-gui_and_multigrid_support| - 5.11.4. Document Symbol........................|navigator-document_symbol| - 5.11.5. Workspace Symbol......................|navigator-workspace_symbol| - 5.11.6. highlight document symbol and jump between reference.|navigator-highlight_document_symbol_and_jump_between_reference| + 5.11.3. Sidebar, folding, outline....|navigator-sidebar,_folding,_outline| + 5.11.4. GUI and multigrid support....|navigator-gui_and_multigrid_support| + 5.11.5. Document Symbol and navigate through the list.|navigator-document_symbol_and_navigate_through_the_list| + 5.11.6. Workspace Symbol......................|navigator-workspace_symbol| + 5.11.7. highlight document symbol and jump between reference.|navigator-highlight_document_symbol_and_jump_between_reference| 6. Current symbol highlight and jump backward/forward between symbols.|navigator-current_symbol_highlight_and_jump_backward/forward_between_symbols| 6.1. Diagnostic.....................................|navigator-diagnostic| 6.2. Edit in preview window.............|navigator-edit_in_preview_window| @@ -48,6 +51,9 @@ CONTENTS *navigator-content 6.11. Light bulb if codeAction available.|navigator-light_bulb_if_codeaction_available| 6.12. Codelens........................................|navigator-codelens| 6.13. Predefined LSP symbol nerdfont/emoji.|navigator-predefined_lsp_symbol_nerdfont/emoji| + 6.14. VS-code style folding with treesitter.|navigator-vs-code_style_folding_with_treesitter| + 6.14.1. folding function..................|navigator-folding_function| + 6.14.2. folding comments..................|navigator-folding_comments| 7. Debug the plugin...................................|navigator-debug_the_plugin| 8. Break changes and known issues.......|navigator-break_changes_and_known_issues| 9. Todo...........................................................|navigator-todo| @@ -56,8 +62,14 @@ CONTENTS *navigator-content ================================================================================ NAVIGATOR *navigator-navigator* +* Source code analysis and navigate tool * Easy code navigation, view diagnostic errors, see relationships of functions, variables * A plugin combines the power of LSP and 🌲🏡 Treesitter together. Not only provids a better highlight but also help you analyse symbol context effectively. +* ctags fuzzy search & build ctags symbols + +- + +* [](https://youtu.be/P1kd7Y8AatE) Here are some examples @@ -127,7 +139,8 @@ FEATURES: *navigator-features * 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 * ccls call hierarchy (Non-standard `ccls/call` API) supports -* Syntax folding based on treesitter folding algorithm. (It behaves similar to vs-code) +* Syntax folding based on treesitter or LSP_fold folding algorithm. (It behaves similar to vs-code); comment folding +* Treesitter symbols sidebar, LSP document symbole sidebar. Both with preview and folding * Fully support LSP CodeAction, CodeLens, CodeLens action. Help you improve code quality. * LRU cache for treesitter nodes * Lazy loader friendly @@ -151,7 +164,7 @@ SIMILAR PROJECTS / SPECIAL MENTIONS: *navigator-similar_projects_/_special_menti ================================================================================ INSTALL *navigator-install* -Require nvim-0.6.1 or nightly +Require nvim-0.6.1 or above, nightly (0.8) prefered 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(migrate from romgrk's project (romgrk/fzy-lua-native)). @@ -161,11 +174,17 @@ The plugin depends on lspconfig and guihua.lua (https://github.com/ray-x/guihua. Plug 'ray-x/navigator.lua' < -Note: Highly recommened: 'nvim-treesitter/nvim-treesitter' +Note: Highly recommend: 'nvim-treesitter/nvim-treesitter' Packer > - use {'ray-x/navigator.lua', requires = {'ray-x/guihua.lua', run = 'cd lua/fzy && make'}} + use({ + 'ray-x/navigator.lua', + requires = { + { 'ray-x/guihua.lua', run = 'cd lua/fzy && make' }, + { 'neovim/nvim-lspconfig' }, + }, + }) < -------------------------------------------------------------------------------- @@ -183,11 +202,11 @@ SAMPLE VIMRC TURNING YOUR NEOVIM INTO A FULL-FEATURED IDE *navigator-sample_vimr Plug 'neovim/nvim-lspconfig' Plug 'ray-x/guihua.lua', {'do': 'cd lua/fzy && make' } Plug 'ray-x/navigator.lua' - " Plug 'hrsh7th/nvim-compe' and other plugins you commenly use... + " Plug 'hrsh7th/nvim-cmp' and other plugins you commenly use... " optional, if you need treesitter symbol support Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'} call plug#end() - " No need for rquire('lspconfig'), navigator will configure it for you + " No need for require('lspconfig'), navigator will configure it for you lua < require'navigator'.setup({ - debug = false, -- log output, set to true and log path: ~/.local/share/nvim/gh.log + debug = false, -- log output, set to true and log path: ~/.cache/nvim/gh.log 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 @@ -258,6 +277,9 @@ Nondefault configuration example: -- please check mapping.lua for all keymaps treesitter_analysis = true, -- treesitter variable context transparency = 50, -- 0 ~ 100 blur the main window, 100: fully transparent, 0: opaque, set to nil or 100 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 = nil, -- if you would like to init ray-x/lsp_signature plugin in navigator, and pass in your own config to signature help icons = { -- Code action code_action_icon = "🏏", @@ -268,23 +290,37 @@ Nondefault configuration example: }, lsp_installer = false, -- set to true if you would like use the lsp installed by williamboman/nvim-lsp-installer lsp = { + enable = true, -- skip lsp setup if disabled make sure add require('navigator.lspclient.mapping').setup() in you + -- own on_attach code_action = {enable = true, sign = true, sign_priority = 40, virtual_text = true}, code_lens_action = {enable = true, sign = true, sign_priority = 40, virtual_text = true}, - format_on_save = true, -- set to false to disasble lsp code format on save (if you are using prettier/efm/formater etc) + format_on_save = true, -- set to false to disable lsp code format on save (if you are using prettier/efm/formater etc) disable_format_cap = {"sqls", "sumneko_lua", "gopls"}, -- a list of lsp disable format capacity (e.g. if you using efm or vim-codeformat etc), empty {} by default disable_lsp = {'pylsd', 'sqlls'}, -- a list of lsp server disabled for your project, e.g. denols and tsserver you may -- only want to enable one lsp server -- to disable all default config and use your own lsp setup set -- disable_lsp = 'all' -- Default {} + diagnostic = { + underline = true, + virtual_text = true, -- show virtual for diagnostic message + update_in_insert = false, -- update diagnostic message in insert mode + }, diagnostic_scrollbar_sign = {'▃', '▆', '█'}, -- experimental: diagnostic status in scroll bar area; set to false to disable the diagnostic sign, -- for other style, set to {'╍', 'ﮆ'} or {'-', '='} + diagnostic_virtual_text = true, -- show virtual for diagnostic message + diagnostic_update_in_insert = false, -- update diagnostic message in insert mode disply_diagnostic_qf = true, -- always show quickfix if there are diagnostic errors, set to false if you want to ignore it tsserver = { filetypes = {'typescript'} -- disable javascript etc, -- set to {} to disable the lspclient for all filetypes }, + ctags ={ + cmd = 'ctags', + tagfile = 'tags' + options = '-R --exclude=.git --exclude=node_modules --exclude=test --exclude=vendor --excmd=number' + } gopls = { -- gopls setting on_attach = function(client, bufnr) -- on_attach for gopls -- your special on attach here @@ -300,7 +336,8 @@ Nondefault configuration example: 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", }, - servers = {'cmake', 'ltex'}, -- by default empty, but if you whant navigator load e.g. `cmake` and `ltex` for you , you + servers = {'cmake', 'ltex'}, -- by default empty, and it should load all LSP clients avalible based on filetype + -- but if you whant navigator load e.g. `cmake` and `ltex` for you , you -- can put them in the `servers` list and navigator will auto load them. -- you could still specify the custom config like this -- cmake = {filetypes = {'cmake', 'makefile'}, single_file_support = false}, @@ -317,7 +354,8 @@ Built clients: "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", "svelte", "texlab", "clojure_lsp" + "r_language_server", "rust_analyzer", "terraformls", "svelte", "texlab", "clojure_lsp", "elixirls", + "sourcekit", "fsautocomplete", "vls", "hls" } < @@ -347,12 +385,12 @@ ADD YOUR OWN SERVERS *navigator-add_your_own_server Above servers covered a small part neovim lspconfig support, You can still use lspconfig to add and config servers not in the list. If you would like to add a server not in the list, you can check this PR https://github.com/ray-x/navigator.lua/pull/107 -Also, an option in setup: +Alternatively, update following option in setup(if you do not want a PR): > require'navigator'setup{lsp={servers={'cmake', 'lexls'}}} < -Above example add cmake and lexls to the default server list +Above option add cmake and lexls to the default server list DISABLE A LSP CLIENT LOADING FROM NAVIGATOR *navigator-disable_a_lsp_client_loading_from_navigator* @@ -375,11 +413,16 @@ Or: }) < +TRY IT YOUR SELF *navigator-try_it_your_self* + +In `playground` folder, there is a `init.lua` and source code for you to play with. Check playground/README.md (https://github.com/ray-x/navigator.lua/blob/master/playground/README.md) for more details + DEFAULT KEYMAPS *navigator-default_keymaps* | mode | key | function | | ---- | --------------- | ---------------------------------------------------------- | -| n | gr | show reference and context | +| n | gr | async references, definitions and context | +| n | gr | show reference and context | | i | | signature help | | n | | signature help | | n | gW | workspace symbol | @@ -390,18 +433,20 @@ DEFAULT KEYMAPS *navigator-default_keymap | n | gp | definition preview (Go to Preview) | | n | | definition | | n | g | implementation | -| n | gT | treesitter document symbol | +| n | gt | treesitter document symbol | | n | gT | treesitter symbol for all open buffers | +| n | ct | ctags symbol search | +| n | cg | ctags symbol generate | | n | K | hover doc | | n | ca | code action (when you see 🏏 ) | | n | la | code lens action (when you see a codelens indicator) | -| v | cA | range code action (when you see 🏏 ) | +| v | ca | range code action (when you see 🏏 ) | | n | rn | rename with floating window | | n | re | rename (lsp default) | | n | gi | hierarchy incoming calls | | n | go | hierarchy outgoing calls | | n | gi | implementation | -| n | D | type definition | +| n | D | type definition | | n | gL | show line diagnostic | | n | gG | show diagnostic for all buffers | | n | ]d | next diagnostic | @@ -409,9 +454,9 @@ DEFAULT KEYMAPS *navigator-default_keymap | n | dt | diagnostic toggle(enable/disable) | | n | ]r | next treesitter reference/usage | | n | [r | previous treesitter reference/usage | -| n | wa | add workspace folder | -| n | wr | remove workspace folder | -| n | wl | print workspace folder | +| n | wa | add workspace folder | +| n | wr | remove workspace folder | +| n | wl | print workspace folder | | n | k | toggle reference highlight | | i/n | | previous item in list | | i/n | | next item in list | @@ -458,19 +503,43 @@ The plugin can be loaded lazily (packer `opt = true` ), And it will check if opt The terminal will need to be able to output nerdfont and emoji correctly. I am using Kitty with nerdfont (Victor Mono). -------------------------------------------------------------------------------- -INTEGRATION WITH LSP_INSTALLER (WILLIAMBOMAN/NVIM-LSP-INSTALLER) *navigator-integration_with_lsp_installer_(williamboman/nvim-lsp-installer)* +INTEGRAT WITH LSP_INSTALLER (WILLIAMBOMAN/NVIM-LSP-INSTALLER) *navigator-integrat_with_lsp_installer_(williamboman/nvim-lsp-installer)* -If you'd like to only use the lsp servers installed by lsp_installer. Please set +If you are using lsp_installer and would like to use the lsp servers installed by lsp_installer. Please set > lsp_installer = true < -In the config. +In the config. Also please setup the lsp server from installer setup with `server:setup{opts}` + +example: +> + use({ + 'williamboman/nvim-lsp-installer', + config = function() + local lsp_installer = require('nvim-lsp-installer') + lsp_installer.setup{} + end, + }) + use({ + 'ray-x/navigator.lua', + config = function() + require('navigator').setup({ + debug = true, + lsp_installer = true, + keymaps = { { key = 'gR', func = "require('navigator.reference').async_ref()" } }, + }) + end, + }) +< -Navigator will startup the server installed by lsp-installer. Please do not call `server:setup{opts}` from lsp installer +Please refer to lsp_installer_config (https://github.com/ray-x/navigator.lua/blob/master/playground/init_lsp_installer.lua) +for more info + +Alternatively, Navigator can be used to startup the server installed by lsp-installer. as it will override the navigator setup -Also, could use following setups +To start LSP installed by lsp_installer, please use following setups > require'navigator'.setup({ -- lsp_installer = false -- default value is false @@ -482,7 +551,70 @@ Also, could use following setups example cmd setup (mac) for pyright : > - cmd = { "/Users/username/.local/share/nvim/lsp_servers/python/node_modules/.bin/pyright-langserver", "--stdio" } + require'navigator'.setup({ + -- lsp_installer = false -- default value is false + lsp = { + tsserver = { + cmd = { "/Users/username/.local/share/nvim/lsp_servers/python/node_modules/.bin/pyright-langserver", "--stdio" } + } + } + } +< + +The lsp servers installed by nvim-lsp-installer is in following dir +> + local path = require 'nvim-lsp-installer.path' + local install_root_dir = path.concat {vim.fn.stdpath 'data', 'lsp_servers'} +< + +And you can setup binary full path to this: (e.g. with gopls) +`install_root_dir .. '/go/gopls'` So the config is +> + local path = require 'nvim-lsp-installer.path' + local install_root_dir = path.concat {vim.fn.stdpath 'data', 'lsp_servers'} + require'navigator'.setup({ + -- lsp_installer = false -- default value is false + lsp = { + gopls = { + cmd = { install_root_dir .. '/go/gopls' } + } + } + } +< + +Use lsp_installer configs +You can delegate the lsp server setup to lsp_installer with `server:setup{opts}` +Here is an example init_lsp_installer.lua (https://github.com/ray-x/navigator.lua/blob/master/playground/init_lsp_installer.lua) + +INTEGRATION WITH OTHER LSP PLUGINS (E.G. RUST-TOOLS, GO.NVIM, CLANGD EXTENSION) *navigator-integration_with_other_lsp_plugins_(e.g._rust-tools,_go.nvim,_clangd_extension)* + +There are lots of plugins provides lsp support +go.nvim allow you either hook gopls from go.nvim or from navigator and it can export the lsp setup from go.nvim. + +rust-tools and clangd allow you to setup on_attach from config server +Here is an example to setup rust with rust-tools +> + require'navigator'.setup({ + lsp = { + disable_lsp = { "rust_analyzer", "clangd" }, -- will not run rust_analyzer setup from navigator + } + }) + require('rust-tools').setup({ + server = { + on_attach = function(_, _) + require('navigator.lspclient.mapping').setup() -- setup navigator keymaps here, + -- otherwise, you can define your own commands to call navigator functions + end, + } + }) + require("clangd_extensions").setup { + server = { + on_attach = function(_, _) + require('navigator.lspclient.mapping').setup() -- setup navigator keymaps here, + -- otherwise, you can define your own commands to call navigator functions + end, + } + } < -------------------------------------------------------------------------------- @@ -521,10 +653,41 @@ You can override the above highlight to fit your current colorscheme -------------------------------------------------------------------------------- COMMANDS *navigator-commands* -| command | function | -| ------------ | ---------------------- | -| LspToggleFmt | toggle lsp auto format | - +| command | function | +| ------------ | ------------------------- | +| LspToggleFmt | toggle lsp auto format | +| LspKeymaps | show LSP releated keymaps | +| Nctags {args} | show ctags symbols, args: -g regen ctags | +| LspRestart | reload lsp | +| LspSymbols | document symbol in side panel | +| TSymobls | treesitter symbol in side panel | +| CallTree {args} | lsp call hierarchy call tree, args: -i (incomming default), -o (outgoing) | + +:LspToggleFmt *:LspToggleFmt* + Toggle lsp auto format. + +:LspKeymaps *:LspKeymaps* + Show Lsp keymaps. + +:Nctags [flags] *:Nctags* + Show ctags symbols. + [flags]: + -g regen ctags + +:LspRestart *:LspRestart* + Restart Lsp. + +:LspSymbols *:LspSymbols* + Lsp document symbol in side panel. + +:TSSymbols *:TSSymbols* + Treesitter symbol in side panel. + +:Calltree [flags] *:Calltree* + Lsp call hierarchy call tree. + [flags]: + -i: incomming default + -o: outgoing -------------------------------------------------------------------------------- SCREENSHOTS *navigator-screenshots* @@ -540,15 +703,27 @@ Using treesitter and LSP to view the symbol definition +SIDEBAR, FOLDING, OUTLINE *navigator-sidebar,_folding,_outline* + +Treesitter outline and Diagnostics + + + GUI AND MULTIGRID SUPPORT *navigator-gui_and_multigrid_support* You can load a different font size for floating win -DOCUMENT SYMBOL *navigator-document_symbol* +DOCUMENT SYMBOL AND NAVIGATE THROUGH THE LIST *navigator-document_symbol_and_navigate_through_the_list* + +The key binding to navigate in the list. +* up and down key +* `` for page up and down +* number key 1~9 go to the ith item. +* If there are loads of results, would be good to use fzy search prompt to filter out the result you are interested. WORKSPACE SYMBOL *navigator-workspace_symbol* @@ -647,6 +822,16 @@ PREDEFINED LSP SYMBOL NERDFONT/EMOJI *navigator-predefined_lsp_symbol_nerdfont/e +VS-CODE STYLE FOLDING WITH TREESITTER *navigator-vs-code_style_folding_with_treesitter* + +FOLDING FUNCTION *navigator-folding_function* + + + +FOLDING COMMENTS *navigator-folding_comments* + + + ================================================================================ DEBUG THE PLUGIN *navigator-debug_the_plugin* @@ -689,3 +874,6 @@ ERRORS AND BUG REPORTING *navigator-errors_and_bug_reportin * Check console output * Check `LspInfo` and treesitter status with `checkhealth` * Turn on log and attach the log to your issue if possible you can remove any personal/company info in the log +* Submit Issue with minium vimrc. Please check playground/init.lua as a vimrc template. !!!Please DONOT use a packer vimrc + +that installs everything to default folder!!! Also check this repo navigator bug report (https://github.com/fky2015/navigator.nvim-bug-report) diff --git a/lua/navigator/hierarchy.lua b/lua/navigator/hierarchy.lua index 98d2536..cfa9d81 100644 --- a/lua/navigator/hierarchy.lua +++ b/lua/navigator/hierarchy.lua @@ -1,27 +1,58 @@ local gui = require('navigator.gui') local util = require('navigator.util') local log = util.log -local trace = util.log +local trace = util.trace local partial = util.partial local lsphelper = require('navigator.lspwrapper') local path_sep = require('navigator.util').path_sep() local path_cur = require('navigator.util').path_cur() local cwd = vim.loop.cwd() +local in_method = 'callHierarchy/incomingCalls' +local out_method = 'callHierarchy/outgoingCalls' + +local lsp_method = { to = out_method, from = in_method } +local panel_method = { to = out_method, from = in_method } + local M = {} local outgoing_calls_handler local incoming_calls_handler +local hierarchy_handler + +local call_hierarchy + +local function pick_call_hierarchy_item(call_hierarchy_items) + if not call_hierarchy_items then + return + end + if #call_hierarchy_items == 1 then + return call_hierarchy_items[1] + end + local items = {} + for i, item in pairs(call_hierarchy_items) do + local entry = item.detail or item.name + table.insert(items, string.format('%d. %s', i, entry)) + end + local choice = vim.fn.inputlist(items) + if choice < 1 or choice > #items then + return + end + return choice +end -local function call_hierarchy_handler(direction, err, result, ctx, config) - log(direction, err, result, ctx, config) +-- convert lsp result to navigator items +local function call_hierarchy_result_procesor(direction, err, result, ctx, config) + math.randomseed(os.clock() * 100000000000) + trace(direction, err, ctx, config) + trace(result) if not result then vim.notify('No call hierarchy items found', vim.lsp.log_levels.WARN) return end -- trace('call_hierarchy', result) - local bufnr = vim.api.nvim_get_current_buf() - assert(next(vim.lsp.buf_get_clients(bufnr)), 'Must have a client running to use lsp_tags') + local bufnr = ctx.bufnr or vim.api.nvim_get_current_buf() + assert(next(vim.lsp.buf_get_clients(bufnr)), 'Must have a client running to use call hierarchy') if err ~= nil then log('dir', direction, 'result', result, 'err', err, ctx) vim.notify('ERROR: ' .. err, vim.lsp.log_levels.WARN) @@ -30,9 +61,9 @@ local function call_hierarchy_handler(direction, err, result, ctx, config) local items = ctx.items or {} + local kind = ' ' for _, call_hierarchy_result in pairs(result) do local call_hierarchy_item = call_hierarchy_result[direction] - local kind = ' ' if call_hierarchy_item.kind then kind = require('navigator.lspclient.lspkind').symbol_kind(call_hierarchy_item.kind) .. ' ' end @@ -40,103 +71,190 @@ local function call_hierarchy_handler(direction, err, result, ctx, config) local display_filename = filename:gsub(cwd .. path_sep, path_cur, 1) call_hierarchy_item.detail = call_hierarchy_item.detail or '' call_hierarchy_item.detail = string.gsub(call_hierarchy_item.detail, '\n', ' ↳ ') - trace(result, call_hierarchy_item) + trace(call_hierarchy_item) local disp_item = vim.tbl_deep_extend('force', {}, call_hierarchy_item) disp_item = vim.tbl_deep_extend('force', disp_item, { filename = filename, display_filename = display_filename, - indent = ctx.depth, + indent_level = ctx.depth or 1, + method = lsp_method[direction], + node_text = call_hierarchy_item.name, + type = kind, + id = math.random(1, 100000), text = kind .. call_hierarchy_item.name .. ' ﰲ ' .. call_hierarchy_item.detail, lnum = call_hierarchy_item.selectionRange.start.line + 1, col = call_hierarchy_item.selectionRange.start.character, }) table.insert(items, disp_item) - if ctx.depth or 0 > 0 then - local params = { - position = { - character = disp_item.selectionRange.start.character, - line = disp_item.selectionRange.start.line, - }, - textDocument = { - uri = disp_item.uri, - }, - } - local api = 'callHierarchy/outgoingCalls' - local handler = outgoing_calls_handler - if direction == 'incoming' then - api = 'callHierarchy/incomingCalls' - handler = incoming_calls_handler - end - lsphelper.call_sync( - api, - params, - ctx, - vim.lsp.with( - partial(handler, 0), - { depth = ctx.depth - 1, direction = 'to', items = ctx.items, no_show = true } - ) - ) - end end - log(items) + trace(items) return items end -local call_hierarchy_handler_from = partial(call_hierarchy_handler, 'from') -local call_hierarchy_handler_to = partial(call_hierarchy_handler, 'to') +local call_hierarchy_handler_from = partial(call_hierarchy_result_procesor, 'from') +local call_hierarchy_handler_to = partial(call_hierarchy_result_procesor, 'to') -incoming_calls_handler = function(_, err, result, ctx, cfg) - local bufnr = vim.api.nvim_get_current_buf() +-- the handler that deal all lsp request +hierarchy_handler = function(dir, handler, show, api, err, result, ctx, cfg) + trace(dir, handler, api, show, err, result, ctx, cfg) + ctx = ctx or {} -- can be nil if it is async call + cfg = cfg or {} + opts = ctx.opts or {} + vim.validate({ handler = { handler, 'function' }, show = { show, 'function' }, api = { api, 'string' } }) + local bufnr = ctx.bufnr or vim.api.nvim_get_current_buf() assert(next(vim.lsp.buf_get_clients(bufnr)), 'Must have a client running to use lsp hierarchy') - local results = call_hierarchy_handler_from(err, result, ctx, cfg, 'Incoming calls not found') + + local results = handler(err, result, ctx, cfg, 'Incoming calls not found') local ft = vim.api.nvim_buf_get_option(ctx.bufnr or vim.api.nvim_get_current_buf(), 'ft') if ctx.no_show then return results end - local win = gui.new_list_view({ items = results, ft = ft, api = ' ' }) + -- local panel = args.panel + -- local items = args.items + -- local parent_node = args.node + -- local section_id = args.section_id or 1 + local show_args = { + items = results, + ft = ft, + api = api, + bufnr = bufnr, + panel = opts.panel, + parent_node = opts.parent_node, + } + local win = show(show_args) return results, win end -outgoing_calls_handler = function(_, err, result, ctx, cfg) - local results = call_hierarchy_handler_to(err, result, ctx, cfg, 'Outgoing calls not found') - - local ft = vim.api.nvim_buf_get_option(ctx.bufnr, 'ft') - if ctx.no_show then - return results - end - local win = gui.new_list_view({ items = results, ft = ft, api = ' ' }) - return result, win +local make_params = function(uri, pos) + return { + textDocument = { + uri = uri, + }, + position = pos, + } end -local function request(method, params, handler) - return vim.lsp.buf_request(0, method, params, handler) +local function display_panel(args) + -- args = {items=results, ft=ft, api=api} + log(args) + + local Panel = require('guihua.panel') + local bufnr = args.bufnr or vim.api.nvim_get_current_buf() + local ft = args.ft or vim.api.nvim_buf_get_option(bufnr, 'buftype') + local items = args.items + local p = Panel:new({ + header = args.header or 'Call Hierarchy', + render = function(bufnr) + return items + end, + fold = function(panel, node) + if node.expanded ~= nil then + node.expanded = not node.expanded + vim.cmd('normal! za') + else + expand(panel, node) + node.expanded = true + end + log('fold') + return node + end, + }) + p:open(true) end -local function pick_call_hierarchy_item(call_hierarchy_items) - if not call_hierarchy_items then - return - end - if #call_hierarchy_items == 1 then - return call_hierarchy_items[1] +local function expand_item(args) + -- args = {items=results, ft=ft, api=api} + print('dispaly panel') + trace(args, args.parent_node) + local panel = args.panel + local items = args.items + local parent_node = args.parent_node + local section_id = args.section_id or 1 + + local sect + local sectid = 1 + for i, s in pairs(panel.sections) do + if s.id == section_id then + sectid = i + break + end end - local items = {} - for i, item in pairs(call_hierarchy_items) do - local entry = item.detail or item.name - table.insert(items, string.format('%d. %s', i, entry)) + sect = panel.sections[sectid] + for i, node in pairs(sect.nodes) do + if node.id == parent_node.id then + for j in ipairs(items) do + items[j].indent_level = parent_node.indent_level + 1 + table.insert(sect.nodes, i + j, args.items[j]) + end + sect.nodes[i].expanded = true + sect.nodes[i].expandable = false + break + end end - local choice = vim.fn.inputlist(items) - if choice < 1 or choice > #items then - return + trace(panel.sections[sectid]) + -- render the panel again + panel:redraw(false) +end + +incoming_calls_handler = util.partial4( + hierarchy_handler, + 'from', + call_hierarchy_handler_from, + gui.new_list_view, + ' ' +) +outgoing_calls_handler = util.partial4(hierarchy_handler, 'to', call_hierarchy_handler_to, gui.new_list_view, ' ') + +local incoming_calls_panel = util.partial4( + hierarchy_handler, + 'from', + call_hierarchy_handler_from, + display_panel, + ' ' +) +local outgoing_calls_panel = util.partial4(hierarchy_handler, 'to', call_hierarchy_handler_to, display_panel, ' ') + +local incoming_calls_expand = util.partial4(hierarchy_handler, 'from', call_hierarchy_handler_from, expand_item, ' ') +local outgoing_calls_expand = util.partial4(hierarchy_handler, 'to', call_hierarchy_handler_to, expand_item, ' ') + +function expand(panel, node) + trace(panel, node) + local params = make_params(node.uri, { + line = node.range.start.line, + character = node.range.start.character, + }) + local handler = incoming_calls_expand + if node.api == out_method then + handler = outgoing_calls_expand end - return choice + + local bufnr = vim.uri_to_bufnr(node.uri) + call_hierarchy(node.method, { + params = params, + panel = panel, + parent_node = node, + handler = handler, + bufnr = bufnr, + }) end -local function call_hierarchy(method, opts) - local params = vim.lsp.util.make_position_params() +local request = vim.lsp.buf_request + +-- call_hierarchy with floating window +call_hierarchy = function(method, opts) + trace(method, opts) opts = opts or {} + local params = opts.params or vim.lsp.util.make_position_params() + local bufnr = opts.bufnr + local handler = function(err, result, ctx, cfg) + ctx.opts = opts + return opts.handler(err, result, ctx, cfg) + end + -- log(opts, params) request( + bufnr, 'textDocument/prepareCallHierarchy', params, vim.lsp.with(function(err, result, ctx) @@ -145,32 +263,47 @@ local function call_hierarchy(method, opts) return end local call_hierarchy_item = pick_call_hierarchy_item(result) - log('result', result, 'items', call_hierarchy_item) local client = vim.lsp.get_client_by_id(ctx.client_id) if client then - client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr) + trace('result', result, 'items', call_hierarchy_item, method, ctx, client.name) + client.request(method, { + item = call_hierarchy_item, + args = { + method = method, + }, + }, handler, ctx.bufnr) else - vim.notify( - string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id), - vim.log.levels.WARN - ) + vim.notify(string.format('Client with id=%d stopped', ctx.client_id), vim.log.levels.WARN) end end, { direction = method, depth = opts.depth }) ) end function M.incoming_calls(opts) - call_hierarchy('callHierarchy/incomingCalls', opts) + call_hierarchy(in_method, opts) end function M.outgoing_calls(opts) - call_hierarchy('callHierarchy/outgoingCalls', opts) + call_hierarchy(out_method, opts) end -M.incoming_calls_call = partial(M.incoming_calls, 0) -M.outgoing_calls_call = partial(M.outgoing_calls, 0) +function M.incoming_calls_panel(opts) + opts = vim.tbl_extend('force', { handler = incoming_calls_panel }, opts or {}) + call_hierarchy(in_method, opts) +end -M.incoming_calls_handler = partial(incoming_calls_handler, 0) -M.outgoing_calls_handler = partial(outgoing_calls_handler, 0) +function M.outgoing_calls_panel(opts) + opts = vim.tbl_extend('force', { handler = outgoing_calls_panel }, opts or {}) + call_hierarchy(out_method, opts) +end +M.incoming_calls_handler = incoming_calls_handler +M.outgoing_calls_handler = outgoing_calls_handler + +function M.calltree(args) + if args == '-o' then + return M.outgoing_calls_panel() + end + M.incoming_calls_panel() +end return M diff --git a/lua/navigator/lspclient/mapping.lua b/lua/navigator/lspclient/mapping.lua index 85eb09c..51240cd 100644 --- a/lua/navigator/lspclient/mapping.lua +++ b/lua/navigator/lspclient/mapping.lua @@ -70,6 +70,7 @@ local commands = { "command! -nargs=0 LspKeymaps lua require'navigator.lspclient.mapping'.get_keymaps_help()", "command! -nargs=0 LspSymbols lua require'navigator.symbols'.side_panel()", "command! -nargs=0 TSymbols lua require'navigator.treesitter'.side_panel()", + "command! -nargs=* Calltree lua require'navigator.hierarchy'.calltree()", } local key_maps_help = {} diff --git a/lua/navigator/util.lua b/lua/navigator/util.lua index e0e09a6..63ed191 100644 --- a/lua/navigator/util.lua +++ b/lua/navigator/util.lua @@ -426,6 +426,24 @@ function M.partial(func, arg) end end +function M.partial2(func, arg1, arg2) + return function(...) + return func(arg1, arg2, ...) + end +end + +function M.partial3(func, arg1, arg2, arg3) + return function(...) + return func(arg1, arg2, arg3, ...) + end +end + +function M.partial4(func, arg1, arg2, arg3, arg4) + return function(...) + return func(arg1, arg2, arg3, arg4, ...) + end +end + function M.empty(t) if t == nil then return true