merge treesitter branch. Improve reference search with treesitter scope. Better highlight in reference search result

neovim_0_5
ray-x 3 years ago
parent 7351a65917
commit 777acc4b92

@ -1,27 +1,67 @@
# Navigator
Easy code navigation through LSP and 🌲🏡Treesitter symbols, diagnostic errors.
- Easy code navigation through LSP and 🌲🏡Treesitter symbols; view diagnostic errors.
![document symbol](https://github.com/ray-x/files/blob/master/img/navigator/doc_symbol.gif?raw=true)
- Put language server and tree sitter's parser together. Not only for better highlight but also display symbol context
and scope.
Here is an example
Show javascript call tree 🌲 of a variable inside a closure. Similar to incoming&outgoing calls from LSP. This feature
is designed for the symbol analysis.
![js_closure_call_tree](https://user-images.githubusercontent.com/1681295/119120589-cee23700-ba6f-11eb-95c5-b9ac8d445c31.jpg)
Explains:
- There are 3 references for the symbol <span style="color:red"> *browser* </span> in closure.js
- The first reference of browser is an assigement, an emoji of 📝 indicates the value changed in this line. In many
cases, we search for reference to find out where the value changed.
- The second reference of `browser` is inside function `displayName` and `displayName` sit inside `makeFunc`, So you
will see ` displayName{} <- makeFunc{}`
- The third similar to the second, as var browser is on the right side of '=', the value not changed in this line
and emoji is not shown.
Struct type references in multiple Go ﳑ files
![go_reference](https://user-images.githubusercontent.com/1681295/119123823-54b3b180-ba73-11eb-8790-097601e10f6a.gif)
This feature can provide you info in which function/class/method the variable was referenced. It is handy for large
project where class/function defination is too long to fit into preview window. Also provides a birdview of where the
variable is referenced.
# Features:
- LSP easy setup. Support the most commonly used lsp clients setup. Dynamic lsp activation based on buffer type.
- LSP easy setup. Support the most commonly used lsp clients setup. Dynamic lsp activation based on buffer type. This
also enable you handle workspace combine mix types of codes (e.g. Go + javascript + yml)
- Out of box experience. 10 lines of minimum vimrc can turn your neovim into a full-featured LSP & Treesitter powered IDE
- Unorthodox UI with floating windows
- Unorthodox UI with floating windows, navigator provides a visual way to manage and navigate through symbols, diagnostic errors, reference etc. Is covers
all features(handler) provided by LSP from commenly used search reference, to less commenly used search for interface
implementation.
- Async request with lsp.buf_request for reference search
- Treesitter symbol search. It is handy for large filas (Some of LSP e.g. sumneko_lua, there is a 100kb file size limition?)
- FZY search with Lua-JIT
- Better navigation for diagnostic errors, Navigate through all files/buffers that contain errors/warnings
- Grouping references/implementation/incomming/outgoing based on file names.
- Grouping references/implementation/incoming/outgoing based on file names.
- Treesitter based variable/function context analysis. It is 10x times faster compared to purely rely on LSP. In most
of the case, it takes treesitter less than 4 ms to read and render all nodes for a file of 1,000 LOC.
- The first plugin, IMO, that allows you to search in all treesitter symbols in the workspace.
- Nerdfont, emoji for LSP and Treesitter kind
- Optimize display (remove trailing bracket/space), display the caller of reference, de-duplicate lsp results (e.g reference
in the same line). Using treesitter for file preview highlighter etc
# Why a new plugin
After installed a handful of lsp plugins, I still got ~800 loc for lsp and treesitter and still increasing because I need
to tune the lsp plugins to fit my requirements. Navigator.lua help user setup lspconfig with only a few lines of codes.
This plugin provides a visual way to manage and navigate through symbols, diagnostic errors, reference etc.
It also the first plugin, IMO, that allows you to search in all treesitter symbols in the workspace.
I'd like to go beyond what the system is providing.
# Similar projects / special mentions:
@ -30,11 +70,14 @@ It also the first plugin, IMO, that allows you to search in all treesitter symbo
- [fuzzy](https://github.com/amirrezaask/fuzzy.nvim)
- [lspsaga](https://github.com/glepnir/lspsaga.nvim)
- [fzf-lsp lsp with fzf as gui backend](https://github.com/gfanto/fzf-lsp.nvim)
- [nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects)
# Install
Require nvim-0.5.0 (a.k.a nightly)
You can remove your lspconfig setup and use this plugin.
The plugin depends on lspconfig and [guihua.lua](https://github.com/ray-x/guihua.lua), which provides GUI and fzy support(thanks [romgrk](romgrk/fzy-lua-native)).
The plugin depends on lspconfig and [guihua.lua](https://github.com/ray-x/guihua.lua), which provides GUI and fzy support(migrate from [romgrk's project](romgrk/fzy-lua-native)).
```vim
Plug 'neovim/nvim-lspconfig'
@ -42,6 +85,8 @@ Plug 'ray-x/guihua.lua', {'do': 'cd lua/fzy && make' }
Plug 'ray-x/navigator.lua'
```
Note: Highly recommened: 'nvim-treesitter/nvim-treesitter'
Packer
```lua
@ -79,17 +124,56 @@ lua <<EOF
require'navigator'.setup()
EOF
```
You can remove your lspconfig.lua and use the hooks of navigator.lua. As the
navigator will bind keys and handler for you. The LSP will be loaded lazily based on filetype.
Nondefault configuration example:
```lua
require.'navigator'.setup({
debug = false, -- log output not implemented
code_action_icon = " ",
width = 0.75, -- number of cols for the floating window
height = 0.3, -- preview window size, 0.3 by default
on_attach = nil,
-- put a on_attach of your own here, e.g
-- function(client, bufnr)
-- -- the on_attach will be called at end of navigator on_attach
-- end,
treesitter_call_tree = true, -- treesitter variable context
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",
code_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true},
lsp = {
format_on_save = true, -- set to false to disasble lsp code format on save (if you are using prettier/efm/formater etc)
tsserver = {
filetypes = {'typescript'} -- disable javascript etc,
-- set to {} to disable the lspclient for all filetype
},
gopls = { -- gopls setting
settings = {
gopls = {gofumpt = false} -- disable gofumpt etc,
}
}
}
})
```
Generally speaking, you could remove most part of your lspconfig.lua and use the hooks in navigator.lua. As the
navigator will bind keys and handler for you. The lsp will be loaded lazily based on filetype.
## Dependency
- lspconfig
- guihua.lua (provides floating window, FZY)
- Optional:
- treesitter (list treesitter symbols)
- treesitter (list treesitter symbols, object analysis)
- lsp-signature (better signature help)
The plugin can be loaded lazily (packer `opt = true` ), And it will check if optional plugins existance and load those plugins only if they existed.
@ -102,7 +186,7 @@ Please refer to lua/navigator/lspclient/mapping.lua on key mappings. Should be a
- Use \<c-e\> or `:q!` to kill the floating window
- <up/down> (or \<c-n\>, \<c-p\>) to move
- \<c-o\> to open location or apply code actions
- \<c-o\> or \<CR\> to open location or apply code actions. Note: \<CR\> might be binded in insert mode by other plugins
## Configuration
@ -121,8 +205,7 @@ colorscheme: [aurora](https://github.com/ray-x/aurora)
### Reference
![reference](https://github.com/ray-x/files/blob/master/img/navigator/ref.gif?raw=true)
Pls check first part of README
### Document Symbol
@ -132,7 +215,7 @@ colorscheme: [aurora](https://github.com/ray-x/aurora)
![workspace symbol](https://github.com/ray-x/files/blob/master/img/navigator/workspace_symbol.gif?raw=true)
# Current symbol highlight and jump backword/forward between symbols
# Current symbol highlight and jump backward/forward between symbols
Document highlight provided by LSP.
Jump between symbols between symbols with treesitter (with `]r` and `[r`)
@ -150,7 +233,7 @@ Show diagnostic in all buffers
### Implementation
![implementation](https://github.com/ray-x/files/blob/master/img/navigator/implemention.jpg?raw=true)
![implementation](https://user-images.githubusercontent.com/1681295/118735346-967e0580-b883-11eb-8c1e-88c5810f7e05.jpg?raw=true)
### Fzy search in reference
@ -160,17 +243,18 @@ Show diagnostic in all buffers
![code actions](https://github.com/ray-x/files/blob/master/img/navigator/codeaction.jpg?raw=true)
Fill struct with gopls
#### Fill struct with gopls
![code actions fill struct](https://github.com/ray-x/files/blob/master/img/navigator/fill_struct.gif?raw=true)
### Code preview with highlight
![code preview](https://github.com/ray-x/files/blob/master/img/navigator/preview_with_hl.jpg?raw=true)
![treesitter_preview](https://user-images.githubusercontent.com/1681295/118900852-4bccbe00-b955-11eb-82f6-0747b1b64e7c.jpg)
### Treesitter symbol
Treetsitter symbols in all buffers
![treesitter](https://github.com/ray-x/files/blob/master/img/navigator/treesitter.jpg?raw=true)
![treesitter](https://user-images.githubusercontent.com/1681295/118734953-cc6eba00-b882-11eb-9db8-0a052630d57e.jpg?raw=true)
### Signature help
@ -184,7 +268,7 @@ Improved signature help with current parameter highlighted
![incomming](https://github.com/ray-x/files/blob/master/img/navigator/incomming.jpg?raw=true)
### Light bulb when codeAction avalible
### Light bulb if codeAction available
![lightbulb](https://github.com/ray-x/files/blob/master/img/navigator/lightbulb.jpg?raw=true)
@ -192,6 +276,9 @@ Improved signature help with current parameter highlighted
![nerdfont](https://github.com/ray-x/files/blob/master/img/navigator/icon_nerd.jpg?raw=true)
# Break changes and known issues
[known issues I am working on](https://github.com/ray-x/navigator.lua/issues/1)
# Todo
- Early phase, bugs expected, PR and suggestions are welcome

@ -2,7 +2,8 @@ local M = {}
_NgConfigValues = {
debug = false, -- log output not implemented
code_action_icon = "",
width = nil, -- valeu of cols TODO allow float e.g. 0.6
width = 0.6, -- valeu of cols TODO allow float e.g. 0.6
preview_height = 0.35,
height = nil,
on_attach = nil,
-- function(client, bufnr)
@ -11,8 +12,15 @@ _NgConfigValues = {
sumneko_root_path = vim.fn.expand("$HOME") .. "/github/sumneko/lua-language-server",
sumneko_binary = vim.fn.expand("$HOME") ..
"/github/sumneko/lua-language-server/bin/macOS/lua-language-server",
treesitter_call_tree = true,
code_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true}
code_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true},
treesitter_call_tree = true, -- treesitter variable context
lsp = {
format_on_save = true, -- set to false to disasble lsp code format on save (if you are using prettier/efm/formater etc)
tsserver = {
filetypes = {'typescript'} -- disable javascript etc,
-- set to {} to disable the lspclient for all filetype
}
}
}
vim.cmd("command! -nargs=0 LspLog call v:lua.open_lsp_log()")
@ -20,26 +28,36 @@ vim.cmd("command! -nargs=0 LspRestart call v:lua.reload_lsp()")
local extend_config = function(opts)
opts = opts or {}
if next(opts) == nil then return end
if next(opts) == nil then
return
end
for key, value in pairs(opts) do
if _NgConfigValues[key] == nil then
error(string.format("[] Key %s not valid", key))
return
end
if type(M.config_values[key]) == "table" then
for k, v in pairs(value) do _NgConfigValues[key][k] = v end
-- if _NgConfigValues[key] == nil then
-- error(string.format("[] Key %s not valid", key))
-- return
-- end
if type(_NgConfigValues[key]) == "table" then
for k, v in pairs(value) do
_NgConfigValues[key][k] = v
end
else
_NgConfigValues[key] = value
end
end
end
M.config_values = function() return _NgConfigValues end
M.config_values = function()
return _NgConfigValues
end
M.setup = function(cfg)
extend_config(cfg)
-- local log = require"navigator.util".log
-- log(debug.traceback())
-- log(cfg, _NgConfigValues)
-- print("loading navigator")
require("navigator.lspclient").setup(_NgConfigValues)
require('navigator.lspclient.clients').setup(_NgConfigValues)
require("navigator.lspclient.mapping").setup(_NgConfigValues)
require("navigator.reference")
require("navigator.definition")
require("navigator.hierarchy")
@ -49,6 +67,10 @@ M.setup = function(cfg)
vim.cmd [[autocmd CursorHold,CursorHoldI * lua require'navigator.codeAction'.code_action_prompt()]]
end
-- vim.cmd("autocmd BufNewFile,BufRead *.go setlocal noexpandtab tabstop=4 shiftwidth=4")
if not _NgConfigValues.loaded then
vim.cmd([[autocmd FileType * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ?
_NgConfigValues.loaded = true
end
end
return M

@ -1,6 +1,6 @@
local util = require "navigator.util"
local log = util.log
local verbose = util.verbose
local trace = util.trace
local code_action = {}
local gui = require "navigator.gui"
local config = require("navigator").config_values()
@ -42,7 +42,7 @@ function code_action.code_action_handler(err, _, actions, cid, bufnr, _, customS
vim.lsp.buf.execute_command(action_chosen)
end
verbose(action_chosen)
trace(action_chosen)
end
gui.new_list_view {

@ -3,29 +3,10 @@ local ListView = require "guihua.listview"
local TextView = require "guihua.textview"
local util = require "navigator.util"
local log = require"navigator.util".log
local verbose = require"navigator.util".verbose
local trace = require"navigator.util".trace
local api = vim.api
function M.new_preview(opts)
return TextView:new({
loc = "top_center",
rect = {
height = opts.height, -- opts.preview_heigh or 12, -- TODO 12
width = opts.width or 100,
pos_x = opts.pos_x or 0,
pos_y = opts.pos_y or 4
},
-- data = display_data,
relative = opts.relative,
-- data = opts.items, -- either items or uri
uri = opts.uri,
syntax = opts.syntax,
enter = opts.enter or false,
range = opts.range,
display_range = opts.display_range,
hl_line = opts.hl_line
})
end
local top_center = require"guihua.location".top_center
function M._preview_location(opts) -- location, width, pos_x, pos_y
local uri = opts.location.uri
@ -34,7 +15,9 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y
return
end
local bufnr = vim.uri_to_bufnr(uri)
if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) end
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
--
local display_range = opts.location.range
@ -56,15 +39,18 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y
-- local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line, false)
--
local syntax = api.nvim_buf_get_option(bufnr, "ft")
if syntax == nil or #syntax < 1 then syntax = "c" end
if syntax == nil or #syntax < 1 then
syntax = "c"
end
-- verbose(syntax, contents)
-- trace(syntax, contents)
local win_opts = {
syntax = syntax,
width = opts.width,
height = display_range['end'].line - display_range.start.line + 1,
pos_x = opts.offset_x or 0,
pos_y = opts.offset_y or 10,
preview_height = opts.preview_height,
pos_x = opts.offset_x,
pos_y = opts.offset_y,
range = opts.range,
display_range = display_range,
uri = uri,
@ -72,23 +58,44 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y
}
-- win_opts.items = contents
win_opts.hl_line = opts.lnum - display_range.start.line
if win_opts.hl_line < 0 then win_opts.hl_line = 1 end
verbose(opts.lnum, opts.range.start.line, win_opts.hl_line)
local w = M.new_preview(win_opts)
if win_opts.hl_line < 0 then
win_opts.hl_line = 1
end
trace(opts.lnum, opts.range.start.line, win_opts.hl_line)
local w = TextView:new({
loc = "offset_center",
rect = {
height = win_opts.height, -- opts.preview_heigh or 12, -- TODO 12
width = win_opts.width,
pos_x = win_opts.pos_x,
pos_y = win_opts.pos_y
},
list_view_height = win_opts.height,
-- data = display_data,
relative = win_opts.relative,
-- data = opts.items, -- either items or uri
uri = win_opts.uri,
syntax = win_opts.syntax,
enter = win_opts.enter or false,
range = win_opts.range,
display_range = win_opts.display_range,
hl_line = win_opts.hl_line
})
return w
end
function M.preview_uri(opts) -- uri, width, line, col, offset_x, offset_y
local line_beg = opts.lnum - 1
if line_beg >= 2 then line_beg = line_beg - 2 end
if line_beg >= 2 then
line_beg = line_beg - 2
end
local loc = {uri = opts.uri, range = {start = {line = line_beg}}}
-- TODO: preview height
loc.range["end"] = {line = opts.lnum + 12}
loc.range["end"] = {line = opts.lnum + opts.preview_height}
opts.location = loc
log("uri", opts.uri, opts.lnum, opts.location)
trace("uri", opts.uri, opts.lnum, opts.location.range.start.line, opts.location.range['end'].line)
return M._preview_location(opts)
end
@ -99,58 +106,94 @@ function M.new_list_view(opts)
local data = {}
local wwidth = api.nvim_get_option("columns")
local width = math.min(opts.width or config.width or 120, math.floor(wwidth * 0.8))
local loc = "top_center"
local width = math.floor(wwidth * 0.75)
if config.width ~= nil and config.width > 0.3 and config.width < 0.99 then
width = math.floor(wwidth * config.width)
end
width = math.min(opts.width or 120, width)
opts.width = width
local wheight = config.height or math.floor(api.nvim_get_option("lines") * 0.8)
local prompt = opts.prompt or false
opts.width = width
if opts.rawdata then
data = items
else
data = require"guihua.util".prepare_for_render(items, opts)
data = require"navigator.render".prepare_for_render(items, opts)
end
local border = _NgConfigValues.border or 'shadow'
if data and not vim.tbl_isempty(data) then
-- replace
-- TODO: 10 vimrc opt
if #data > 10 and opts.prompt == nil then prompt = true end
if #data > 10 and opts.prompt == nil then
loc = "top_center"
prompt = true
end
local lheight = math.min(#data, math.floor(wheight / 2))
local pheight = math.min(wheight - lheight, math.floor(wheight / 2))
local r, _ = top_center(lheight, width)
local offset_y = r + lheight
-- style shadow took 1 lines
if border ~= 'none' then
if border == 'shadow' then
offset_y = offset_y + 1
else
offset_y = offset_y + 1 -- single?
end
end
-- if border is not set, this should be r+lheigh
if prompt then
offset_y = offset_y + 1 -- need to check this out
end
local height = math.min(#data, math.floor(wheight / 2))
local offset_y = height + 2 -- style shadow took 2 lines
if prompt then offset_y = offset_y + 1 end
return ListView:new({
loc = "top_center",
loc = loc,
prompt = prompt,
relative = opts.relative,
style = opts.style,
api = opts.api,
rect = {height = height, width = width, pos_x = 0, pos_y = 0},
rect = {height = lheight, width = width, pos_x = 0, pos_y = 0},
-- preview_height = pheight,
ft = opts.ft or 'guihua',
-- data = display_data,
data = data,
on_confirm = opts.on_confirm or function(pos)
if pos == 0 then pos = 1 end
if pos == 0 then
pos = 1
end
local l = data[pos]
if l.filename ~= nil then
verbose("openfile ", l.filename, l.lnum, l.col)
trace("openfile ", l.filename, l.lnum, l.col)
util.open_file_at(l.filename, l.lnum, l.col)
end
end,
on_move = opts.on_move or function(pos)
if pos == 0 then pos = 1 end
if pos == 0 then
pos = 1
end
local l = data[pos]
verbose("on move", pos, l)
verbose("on move", pos, l.text or l, l.uri, l.filename)
trace("on move", pos, l)
trace("on move", pos, l.text or l, l.uri, l.filename)
-- todo fix
if l.uri == nil then l.uri = "file:///" .. l.filename end
if l.uri == nil then
l.uri = "file:///" .. l.filename
end
return M.preview_uri({
uri = l.uri,
width = width,
height = lheight, -- this is to cal offset
preview_height = pheight,
lnum = l.lnum,
col = l.col,
range = l.range,
offset_x = 0,
offset_y = offset_y,
border = "double"
border = border
})
end
})

@ -6,25 +6,22 @@ local lsphelper = require "navigator.lspwrapper"
local cwd = vim.fn.getcwd(0)
local M = {}
local function call_hierarchy_handler(direction, err, _, result, _, _,
error_message)
local function call_hierarchy_handler(direction, err, _, result, _, _, error_message)
-- log('call_hierarchy')
assert(#vim.lsp.buf_get_clients() > 0,
"Must have a client running to use lsp_tags")
assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags")
if err ~= nil then
log("dir", direction, "result", result, "err", err)
print("ERROR: " .. error_message)
return
end
-- log("dir", direction, "result", result)
local items = {}
for _, call_hierarchy_call in pairs(result) do
local call_hierarchy_item = call_hierarchy_call[direction]
local kind = ''
if call_hierarchy_item.kind then
kind = require'navigator.lspclient.lspkind'.symbol_kind(
call_hierarchy_item.kind) .. ' '
kind = require'navigator.lspclient.lspkind'.symbol_kind(call_hierarchy_item.kind) .. ' '
end
for _, range in pairs(call_hierarchy_call.fromRanges) do
local filename = assert(vim.uri_to_fname(call_hierarchy_item.uri))
@ -47,21 +44,18 @@ end
local call_hierarchy_handler_from = partial(call_hierarchy_handler, "from")
local call_hierarchy_handler_to = partial(call_hierarchy_handler, "to")
local function incoming_calls_handler(bang, err, method, result, client_id,
bufnr)
assert(#vim.lsp.buf_get_clients() > 0,
"Must have a client running to use lsp_tags")
local results = call_hierarchy_handler_from(err, method, result, client_id,
bufnr, "Incoming calls not found")
local function incoming_calls_handler(bang, err, method, result, client_id, bufnr)
assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags")
local results = call_hierarchy_handler_from(err, method, result, client_id, bufnr,
"Incoming calls not found")
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
gui.new_list_view({items = results, ft = ft, api = ''})
end
local function outgoing_calls_handler(bang, err, method, result, client_id,
bufnr)
local results = call_hierarchy_handler_to(err, method, result, client_id,
bufnr, "Outgoing calls not found")
local function outgoing_calls_handler(bang, err, method, result, client_id, bufnr)
local results = call_hierarchy_handler_to(err, method, result, client_id, bufnr,
"Outgoing calls not found")
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
gui.new_list_view({items = results, ft = ft, api = ''})
@ -69,23 +63,23 @@ local function outgoing_calls_handler(bang, err, method, result, client_id,
end
function M.incoming_calls(bang, opts)
assert(#vim.lsp.buf_get_clients() > 0,
"Must have a client running to use lsp_tags")
if not lsphelper.check_capabilities("call_hierarchy") then return end
assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags")
if not lsphelper.check_capabilities("call_hierarchy") then
return
end
local params = vim.lsp.util.make_position_params()
util.call_sync("callHierarchy/incomingCalls", params, opts,
partial(incoming_calls_handler, bang))
util.call_sync("callHierarchy/incomingCalls", params, opts, partial(incoming_calls_handler, bang))
end
function M.outgoing_calls(bang, opts)
assert(#vim.lsp.buf_get_clients() > 0,
"Must have a client running to use lsp_tags")
if not lsphelper.check_capabilities("call_hierarchy") then return end
assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp_tags")
if not lsphelper.check_capabilities("call_hierarchy") then
return
end
local params = vim.lsp.util.make_position_params()
util.call_sync("callHierarchy/outgoingCalls", params, opts,
partial(outgoing_calls_handler, bang))
util.call_sync("callHierarchy/outgoingCalls", params, opts, partial(outgoing_calls_handler, bang))
end
M.incoming_calls_call = partial(M.incoming_calls, 0)

@ -3,7 +3,7 @@ local lsp = require("vim.lsp")
local util = require "navigator.util"
local log = util.log
local verbose = util.verbose
local trace = util.trace
local diagnostic_map = function(bufnr)
local opts = {noremap = true, silent = true}
@ -20,7 +20,7 @@ M.on_attach = function(client, bufnr)
return
end
log("attaching", bufnr)
verbose(client)
trace(client)
local hassig, sig = pcall(require, "lsp_signature")
if hassig then sig.on_attach() end
diagnostic_map(bufnr)

@ -1,6 +1,6 @@
-- todo allow config passed in
local log = require"navigator.util".log
local verbose = require"navigator.util".verbose
local trace = require"navigator.util".trace
_Loading = false
@ -8,7 +8,9 @@ if packer_plugins ~= nil then
-- packer installed
local loader = require"packer".loader
if not packer_plugins["neovim/nvim-lspconfig"] or
not packer_plugins["neovim/nvim-lspconfig"].loaded then loader("nvim-lspconfig") end
not packer_plugins["neovim/nvim-lspconfig"].loaded then
loader("nvim-lspconfig")
end
if not packer_plugins["ray-x/guihua.lua"] or not packer_plugins["guihua.lua"].loaded then
loader("guihua.lua")
-- if lazyloading
@ -16,13 +18,15 @@ if packer_plugins ~= nil then
end
local has_lsp, lspconfig = pcall(require, "lspconfig")
if not has_lsp then error("loading lsp config") end
if not has_lsp then
error("loading lsp config")
end
local highlight = require "navigator.lspclient.highlight"
local util = lspconfig.util
local config = require"navigator".config_values()
local cap = vim.lsp.protocol.make_client_capabilities()
-- local cap = vim.lsp.protocol.make_client_capabilities()
local on_attach = require("navigator.lspclient.attach").on_attach
-- gopls["ui.completion.usePlaceholders"] = true
@ -48,7 +52,9 @@ add("$VIMRUNTIME")
-- add your config
local home = vim.fn.expand("$HOME")
if vim.fn.isdirectory(home .. "/.config/nvim") then add(home .. "/.config/nvim") end
if vim.fn.isdirectory(home .. "/.config/nvim") then
add(home .. "/.config/nvim")
end
-- add plugins it may be very slow to add all in path
-- if vim.fn.isdirectory(home .. "/.config/share/nvim/site/pack/packer") then
@ -64,7 +70,7 @@ library[vim.fn.expand("$VIMRUNTIME/lua/vim/lsp")] = true
local setups = {
gopls = {
on_attach = on_attach,
capabilities = cap,
-- capabilities = cap,
filetypes = {"go", "gomod"},
message_level = vim.lsp.protocol.MessageType.Error,
cmd = {
@ -86,8 +92,9 @@ local setups = {
completeUnimported = true,
staticcheck = true,
matcher = "fuzzy",
experimentalDiagnosticsDelay = "500ms",
symbolMatcher = "fuzzy",
gofumpt = true,
gofumpt = false, -- true, -- turn on for new repos, gofmpt is good but also create code turmoils
buildFlags = {"-tags", "integration"}
-- buildFlags = {"-tags", "functional"}
}
@ -204,22 +211,25 @@ local default_cfg = {on_attach = on_attach}
-- check and load based on file type
local function load_cfg(ft, client, cfg, loaded)
-- log("trying", client)
if lspconfig[client] == nil then
log("not supported", client)
log("not supported by nvim", client)
return
end
local lspft = lspconfig[client].document_config.default_config.filetypes
local should_load = false
if lspft ~= nil and #lspft > 0 then
for _, value in ipairs(lspft) do if ft == value then should_load = true end end
for _, value in ipairs(lspft) do
if ft == value then
should_load = true
end
end
if should_load then
for _, c in pairs(loaded) do
if client == c then
-- loaded
verbose(client, "already been loaded for", ft, loaded)
trace(client, "already been loaded for", ft, loaded)
return
end
end
@ -230,19 +240,36 @@ local function load_cfg(ft, client, cfg, loaded)
-- need to verify the lsp server is up
end
local function wait_lsp_startup(ft, retry)
local function wait_lsp_startup(ft, retry, lsp_opts)
retry = retry or false
local clients = vim.lsp.get_active_clients() or {}
local loaded = {}
for i = 1, 2 do
for _ = 1, 2 do
for _, client in ipairs(clients) do
if client ~= nil then table.insert(loaded, client.name) end
if client ~= nil then
table.insert(loaded, client.name)
end
end
for _, lspclient in ipairs(servers) do
if lsp_opts[lspclient] ~= nil and lsp_opts[lspclient].filetypes ~= nil then
if not vim.tbl_contains(lsp_opts[lspclient].filetypes, ft) then
trace("ft", ft, "disabled for", lspclient)
goto continue
end
end
local cfg = setups[lspclient] or default_cfg
-- if user provides override values
-- if lsp_opts[lspclient] ~= nil and lsp_opts[lspclient] ~= nil then
-- local ret = vim.tbl_extend("force", cfg, lsp_opts[lspclient])
-- log(lsp_opts[lspclient].settings, cfg, ret)
-- end
load_cfg(ft, lspclient, cfg, loaded)
::continue::
end
if not retry or ft == nil then
return
end
if not retry or ft == nil then return end
--
local timer = vim.loop.new_timer()
local i = 0
@ -251,7 +278,7 @@ local function wait_lsp_startup(ft, retry)
i = i + 1
if i > 5 or #clients > 0 then
timer:close() -- Always close handles to avoid leaks.
verbose("active", #clients, i)
trace("active", #clients, i)
_Loading = false
return true
end
@ -260,18 +287,19 @@ local function wait_lsp_startup(ft, retry)
end
end
vim.cmd([[autocmd FileType * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ?
local function setup(user_opts)
verbose(debug.traceback())
if lspconfig == nil then
error("lsp-config need installed and enabled")
log(user_opts)
trace(debug.traceback())
user_opts = user_opts or _NgConfigValues -- incase setup was triggered from autocmd
if _Loading == true then
return
end
if _Loading == true then return end
local ft = vim.bo.filetype
if ft == nil then ft = vim.api.nvim_buf_get_option(0, "filetype") end
if ft == nil then
ft = vim.api.nvim_buf_get_option(0, "filetype")
end
if ft == nil or ft == "" then
log("nil filetype")
@ -284,24 +312,32 @@ local function setup(user_opts)
}
for i = 1, #disable_ft do
if ft == disable_ft[i] then
log("navigator disabled for ft", ft)
trace("navigator disabled for ft", ft)
return
end
end
local bufnr = vim.fn.bufnr()
local uri = vim.uri_from_bufnr(bufnr)
log("loading for ft ", ft, uri)
if uri == 'file://' or uri == 'file:///' then
log("skip loading for ft ", ft, uri)
return
end
log('setup', user_opts)
log("loading for ft ", ft, uri)
highlight.diagnositc_config_sign()
highlight.add_highlight()
local lsp_opts = user_opts.lsp
_Loading = true
wait_lsp_startup(ft, retry)
wait_lsp_startup(ft, retry, lsp_opts)
_Loading = false
-- if not _NgConfigValues.loaded then
-- vim.cmd([[autocmd FileType * lua require'navigator.lspclient.clients'.setup()]]) -- BufWinEnter BufNewFile,BufRead ?
-- _NgConfigValues.loaded = true
-- end
end
return {setup = setup, cap = cap}

@ -1,9 +0,0 @@
-- local log = require "navigator.util".log
local M = {}
M.setup = function(cfg)
cfg = cfg or {}
require('navigator.lspclient.clients').setup(cfg)
require("navigator.lspclient.mapping").setup(cfg)
end
return M

@ -1,5 +1,7 @@
local log = require"navigator.util".log
local function set_keymap(...) vim.api.nvim_set_keymap(...) end
local function set_keymap(...)
vim.api.nvim_set_keymap(...)
end
local event_hdlrs = {
{ev = "BufWritePre", func = "diagnostic.set_loclist({open_loclist = false})"},
@ -9,23 +11,31 @@ local event_hdlrs = {
}
local key_maps = {
{key = "gr", func = "references()"}, {mode = "i", key = "<M-k>", func = "signature_help()"},
{key = "gs", func = "signature_help()"}, {key = "g0", func = "document_symbol()"},
{key = "gW", func = "workspace_symbol()"}, {key = "<c-]>", func = "definition()"},
{key = "gD", func = "declaration()"},
{key = "gr", func = "references()"},
{mode = "i", key = "<M-k>", func = "signature_help()"},
{key = "gs", func = "signature_help()"},
{key = "g0", func = "document_symbol()"},
{key = "gW", func = "workspace_symbol()"},
{key = "<c-]>", func = "definition()"}, {key = "gD", func = "declaration()"},
{key = "gp", func = "require('navigator.definition').definition_preview()"},
{key = "gT", func = "require('navigator.treesitter').buf_ts()"},
{key = "GT", func = "require('navigator.treesitter').bufs_ts()"}, {key = "K", func = "hover()"},
{key = "GT", func = "require('navigator.treesitter').bufs_ts()"},
{key = "K", func = "hover()"},
{key = "ga", mode = "n", func = "code_action()"},
{key = "ga", mode = "v", func = "range_code_action()"}, {key = "<Leader>re", func = "rename()"},
{key = "<Leader>gi", func = "incoming_calls()"}, {key = "<Leader>go", func = "outgoing_calls()"},
{key = "gi", func = "implementation()"}, {key = "gt", func = "type_definition()"},
{key = "ga", mode = "v", func = "range_code_action()"},
{key = "<Leader>re", func = "rename()"},
{key = "<Leader>gi", func = "incoming_calls()"},
{key = "<Leader>go", func = "outgoing_calls()"},
{key = "gi", func = "implementation()"},
{key = "gt", func = "type_definition()"},
{key = "gL", func = "diagnostic.show_line_diagnostics()"},
{key = "gG", func = "require('navigator.diagnostics').show_diagnostic()"},
{key = "]d", func = "diagnostic.goto_next()"}, {key = "[d", func = "diagnostic.goto_prev()"},
{key = "]d", func = "diagnostic.goto_next()"},
{key = "[d", func = "diagnostic.goto_prev()"},
{key = "]r", func = "require('navigator.treesitter').goto_next_usage()"},
{key = "[r", func = "require('navigator.treesitter').goto_previous_usage()"},
{key = "<C-LeftMouse>", func = "definition()"}, {key = "g<LeftMouse>", func = "implementation()"}
{key = "<C-LeftMouse>", func = "definition()"},
{key = "g<LeftMouse>", func = "implementation()"}
}
local function set_mapping(user_opts)
@ -35,7 +45,9 @@ local function set_mapping(user_opts)
local user_key = user_opts.keymaps or {}
local bufnr = user_opts.bufnr or 0
local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
local function buf_set_keymap(...)
vim.api.nvim_buf_set_keymap(bufnr, ...)
end
-- local function buf_set_option(...)
-- vim.api.nvim_buf_set_option(bufnr, ...)
@ -71,23 +83,30 @@ local function set_mapping(user_opts)
for _, value in pairs(vim.lsp.buf_get_clients(0)) do
if value == nil or value.resolved_capabilities == nil then return end
if value.resolved_capabilities.document_formatting then doc_fmt = true end
if value.resolved_capabilities.document_range_formatting then range_fmt = true end
if value.resolved_capabilities.document_range_formatting then
range_fmt = true
end
end
-- if user_opts.cap.document_formatting then
if doc_fmt then
buf_set_keymap("n", "<space>ff", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)
vim.cmd([[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting()]])
buf_set_keymap("n", "<space>ff", "<cmd>lua vim.lsp.buf.formatting()<CR>",
opts)
if _NgConfigValues.lsp.format_on_save then
vim.cmd([[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting()]])
end
end
-- if user_opts.cap.document_range_formatting then
if range_fmt then
buf_set_keymap("v", "<space>ff", "<cmd>lua vim.lsp.buf.range_formatting()<CR>", opts)
buf_set_keymap("v", "<space>ff",
"<cmd>lua vim.lsp.buf.range_formatting()<CR>", opts)
end
end
local function set_event_handler(user_opts)
user_opts = user_opts or {}
local file_types = "c,cpp,h,go,python,vim,sh,javascript,html,css,lua,typescript,rust"
local file_types =
"c,cpp,h,go,python,vim,sh,javascript,html,css,lua,typescript,rust,javascriptreact,typescriptreact,json,yaml,kotlin,php,dart,nim,terraform"
-- local format_files = "c,cpp,h,go,python,vim,javascript,typescript" --html,css,
vim.api.nvim_command [[augroup nvim_lsp_autos]]
vim.api.nvim_command [[autocmd!]]
@ -99,8 +118,9 @@ local function set_event_handler(user_opts)
else
f = "lua vim.lsp.buf." .. value.func
end
local cmd = "autocmd FileType " .. file_types .. " autocmd nvim_lsp_autos " .. value.ev ..
" <buffer> silent! " .. f
local cmd =
"autocmd FileType " .. file_types .. " autocmd nvim_lsp_autos " ..
value.ev .. " <buffer> silent! " .. f
vim.api.nvim_command(cmd)
end
vim.api.nvim_command([[augroup END]])
@ -109,12 +129,11 @@ end
local M = {}
function M.setup(user_opts)
user_opts = user_opts or {}
user_opts.cap = vim.lsp.protocol.make_client_capabilities()
set_mapping(user_opts)
set_event_handler(user_opts)
local cap = user_opts.cap or {}
local cap = user_opts.cap or vim.lsp.protocol.make_client_capabilities()
if cap.call_hierarchy or cap.callHierarchy then
vim.lsp.handlers["callHierarchy/incomingCalls"] =
require"navigator.hierarchy".incoming_calls_handler
@ -122,12 +141,16 @@ function M.setup(user_opts)
require"navigator.hierarchy".outgoing_calls_handler
end
vim.lsp.handlers["textDocument/references"] = require"navigator.reference".reference_handler
vim.lsp.handlers["textDocument/codeAction"] = require"navigator.codeAction".code_action_handler
vim.lsp.handlers["textDocument/definition"] = require"navigator.definition".definition_handler
vim.lsp.handlers["textDocument/references"] =
require"navigator.reference".reference_handler
vim.lsp.handlers["textDocument/codeAction"] =
require"navigator.codeAction".code_action_handler
vim.lsp.handlers["textDocument/definition"] =
require"navigator.definition".definition_handler
if cap.declaration then
vim.lsp.handlers["textDocument/declaration"] = require"navigator.definition".declaration_handler
vim.lsp.handlers["textDocument/declaration"] =
require"navigator.definition".declaration_handler
end
vim.lsp.handlers["textDocument/typeDefinition"] =
@ -137,13 +160,15 @@ function M.setup(user_opts)
vim.lsp.handlers["textDocument/documentSymbol"] =
require"navigator.symbols".document_symbol_handler
vim.lsp.handlers["workspace/symbol"] = require"navigator.symbols".workspace_symbol_handler
vim.lsp.handlers["workspace/symbol"] =
require"navigator.symbols".workspace_symbol_handler
vim.lsp.handlers["textDocument/publishDiagnostics"] =
require"navigator.diagnostics".diagnostic_handler
local hassig, sig = pcall(require, "lsp_signature")
if not hassig then
vim.lsp.handlers["textDocument/signatureHelp"] = require"navigator.signature".signature_handler
vim.lsp.handlers["textDocument/signatureHelp"] =
require"navigator.signature".signature_handler
end
-- vim.lsp.handlers["textDocument/hover"] = require 'navigator.hover'.hover_handler

@ -4,16 +4,30 @@ local gutil = require "guihua.util"
local lsp = require "vim.lsp"
local api = vim.api
local log = require"navigator.util".log
local verbose = require"navigator.util".verbose
local trace = require"navigator.util".trace
local symbol_kind = require"navigator.lspclient.lspkind".symbol_kind
local cwd = vim.fn.getcwd(0)
cwd = gutil.add_pec(cwd)
ts_nodes = {}
ts_nodes_time = {}
local ts_enabled, ts_locals = pcall(require, "nvim-treesitter.locals")
local ts_enabled, _ = pcall(require, "nvim-treesitter.locals")
local calltree_enabled = require"navigator".config_values().treesitter_call_tree
-- extract symbol from range
local function get_symbol(text, range)
if range == nil then
return ""
end
return string.sub(text, range.start.character + 1, range['end'].character)
end
local function check_lhs(text, symbol)
local s = string.find(text, symbol)
local eq = string.find(text, '=') or 0
return s < eq
end
function M.lines_from_locations(locations, include_filename)
local fnamemodify = (function(filename)
if include_filename then
@ -43,7 +57,9 @@ function M.symbols_to_items(result)
local kind = symbol_kind(item.kind)
item.name = result[i].name -- symbol name
item.text = result[i].name
if kind ~= nil then item.text = kind .. ": " .. item.text end
if kind ~= nil then
item.text = kind .. ": " .. item.text
end
item.filename = vim.uri_to_fname(item.uri)
item.display_filename = item.filename:gsub(cwd .. "/", "./", 1)
@ -52,7 +68,9 @@ function M.symbols_to_items(result)
end
item.lnum = item.range.start.line + 1
if item.containerName ~= nil then item.text = "" .. item.containerName .. item.text end
if item.containerName ~= nil then
item.text = "" .. item.containerName .. item.text
end
table.insert(locations, item)
end
end
@ -65,9 +83,10 @@ local function extract_result(results_lsp)
if results_lsp then
local results = {}
for _, server_results in pairs(results_lsp) do
if server_results.result then vim.list_extend(results, server_results.result) end
if server_results.result then
vim.list_extend(results, server_results.result)
end
end
return results
end
end
@ -78,7 +97,9 @@ function M.check_capabilities(feature, client_id)
local supported_client = false
for _, client in pairs(clients) do
supported_client = client.resolved_capabilities[feature]
if supported_client then goto continue end
if supported_client then
goto continue
end
end
::continue::
@ -114,15 +135,21 @@ function M.call_async(method, params, handler)
end
local function ts_functions(uri)
if not ts_enabled or not calltree_enabled then return nil end
if not ts_enabled or not calltree_enabled then
return nil
end
local ts_func = require"navigator.treesitter".buf_func
local bufnr = vim.uri_to_bufnr(uri)
local x = os.clock()
trace(ts_nodes)
if ts_nodes[uri] ~= nil then
local t = ts_nodes_time[uri]
local fname = vim.uri_to_fname(uri)
local modified = vim.fn.getftime(fname)
if modified < t then return ts_nodes[uri] end
if modified <= t then
trace(t, modified)
return ts_nodes[uri]
end
end
local unload = false
if not api.nvim_buf_is_loaded(bufnr) then
@ -139,20 +166,24 @@ local function ts_functions(uri)
end
ts_nodes[uri] = funcs
ts_nodes_time[uri] = os.time()
verbose(funcs)
trace(funcs, ts_nodes)
log(string.format("elapsed time: %.4f\n", os.clock() - x))
return funcs
end
local function find_ts_func_by_range(funcs, range)
if funcs == nil or range == nil then return nil end
if funcs == nil or range == nil then
return nil
end
local result = {}
verbose(funcs, range)
trace(funcs, range)
for _, value in pairs(funcs) do
local func_range = value.node_scope
-- note treesitter is C style
if func_range and func_range.start.line + 1 <= range.start.line and func_range['end'].line + 1 >=
range['end'].line then table.insert(result, value) end
if func_range and func_range.start.line <= range.start.line and func_range['end'].line >=
range['end'].line then
table.insert(result, value)
end
end
return result
end
@ -168,7 +199,9 @@ function M.locations_to_items(locations)
-- items and locations may not matching
table.sort(locations, function(i, j)
if i.uri == j.uri then
if i.range and i.range.start then return i.range.start.line < j.range.start.line end
if i.range and i.range.start then
return i.range.start.line < j.range.start.line
end
return false
else
return i.uri < j.uri
@ -186,9 +219,10 @@ function M.locations_to_items(locations)
item.rpath = util.get_relative_path(cwd, item.filename)
table.insert(items, item)
width = math.max(width, #item.text)
item.symbol_name = get_symbol(item.text, item.range)
item.lhs = check_lhs(item.text, item.symbol_name)
end
return items, width + 15
return items, width + 24 -- TODO handle long line?
end
function M.symbol_to_items(locations)
@ -201,7 +235,9 @@ function M.symbol_to_items(locations)
-- items and locations may not matching
table.sort(locations, function(i, j)
if i.uri == j.uri then
if i.range and i.range.start then return i.range.start.line < j.range.start.line end
if i.range and i.range.start then
return i.range.start.line < j.range.start.line
end
return false
else
return i.uri < j.uri

@ -3,7 +3,7 @@ local log = util.log
local lsphelper = require "navigator.lspwrapper"
local gui = require "navigator.gui"
local lsp = require "navigator.lspwrapper"
local verbose = require"navigator.util".verbose
local trace = require"navigator.util".trace
-- local log = util.log
-- local partial = util.partial
-- local cwd = vim.fn.getcwd(0)
@ -16,7 +16,7 @@ local function ref_hdlr(err, api, locations, num, bufnr)
local opts = {}
-- log("arg1", arg1)
-- log(api)
-- log(locations)
trace(locations)
-- log("num", num)
-- log("bfnr", bufnr)
if err ~= nil then
@ -24,7 +24,6 @@ local function ref_hdlr(err, api, locations, num, bufnr)
return
end
if type(locations) ~= 'table' then
log("arg1", arg1)
log(api)
log(locations)
log("num", num)
@ -36,10 +35,11 @@ local function ref_hdlr(err, api, locations, num, bufnr)
return
end
local items, width = locations_to_items(locations)
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
local wwidth = vim.api.nvim_get_option("columns")
width = math.min(width + 24 or 120, math.floor(wwidth * 0.8))
width = math.min(width + 30, 120, math.floor(wwidth * 0.8))
gui.new_list_view({items = items, ft = ft, width = width, api = "Reference"})
end

@ -0,0 +1,149 @@
local log = require"guihua.log".info
local trace = require"guihua.log".trace
local M = {}
local clone = require'guihua.util'.clone
local function filename(url)
return url:match("^.+/(.+)$") or url
end
local function extension(url)
local ext = url:match("^.+(%..+)$") or "txt"
return string.sub(ext, 2)
end
local function get_pads(win_width, text, postfix)
local margin = win_width - #text - #postfix
if margin < 0 then
if #postfix > 1 then
text = text:sub(1, #text - 20)
end
end
local sz = #text
if sz < 30 then
sz = 30
end
local space = ''
local i = math.floor((sz + 10) / 10)
i = i * 10 - #text
space = string.rep(' ', i)
trace(text, i, postfix, win_width)
return space
end
function M.prepare_for_render(items, opts)
opts = opts or {}
if items == nil or #items < 1 then
print("no item found or empty fields")
return
end
local item = clone(items[1])
local display_items = {item}
local last_summary_idx = 1
local total_ref_in_file = 1
local icon = ""
local lspapi = opts.api or ""
local ok, devicons = pcall(require, "nvim-web-devicons")
if ok then
local fn = filename(items[1].filename)
local ext = extension(fn)
icon = devicons.get_icon(fn, ext) or icon
end
local call_by_presented = false
opts.width = opts.width or 100
local win_width = opts.width - 2 -- buf
for i = 1, #items do
if items[i].call_by and #items[i].call_by > 0 then
call_by_presented = true
end
end
for i = 1, #items do
local space = ''
local lspapi_display = lspapi
items[i].symbol_name = items[i].symbol_name or "" -- some LSP API does not have range for this
if last_summary_idx == 1 then
lspapi_display = items[i].symbol_name .. ' ' .. lspapi_display
trace(items[1], lspapi_display)
end
-- trace(items[i], items[i].filename, last_summary_idx, display_items[last_summary_idx].filename)
if items[i].filename == display_items[last_summary_idx].filename then
space = get_pads(opts.width, icon .. ' ' .. display_items[last_summary_idx].display_filename,
lspapi_display .. ' 12')
display_items[last_summary_idx].text = string.format("%s %s%s%s %i", icon,
display_items[last_summary_idx]
.display_filename, space,
lspapi_display, total_ref_in_file)
total_ref_in_file = total_ref_in_file + 1
else
lspapi_display = lspapi
item = clone(items[i])
space = get_pads(opts.width, icon .. ' ' .. item.display_filename, lspapi_display .. ' 12')
item.text = string.format("%s %s%s%s 1", icon, item.display_filename, space, lspapi_display)
trace(item.text)
table.insert(display_items, item)
total_ref_in_file = 1
last_summary_idx = #display_items
end
-- content of code lines
item = clone(items[i])
item.text = require'navigator.util'.trim_and_pad(item.text)
item.text = string.format("%4i: %s", item.lnum, item.text)
local call_by = ""
if item.lhs then
call_by = '📝 '
end
item.text = item.text:gsub('%s*[%[%(%{]*%s*$', '')
if item.call_by ~= nil and #item.call_by > 0 then
trace("call_by:", #item.call_by)
for _, value in pairs(item.call_by) do
if value.node_text then
local txt = value.node_text:gsub('%s*[%[%(%{]*%s*$', '')
local endwise = '{}'
if value.type == 'method' or value.type == 'function' then
endwise = '()'
call_by = ''
end
if #call_by > 8 then
call_by = call_by .. ''
end
call_by = call_by .. value.kind .. txt .. endwise
trace(item)
end
end
end
if #call_by > 1 then
space = get_pads(win_width, item.text, call_by)
if #space + #item.text + #call_by >= win_width then
if #item.text + #call_by > win_width then
log("exceeding", #item.text, #call_by, win_width)
space = ' '
else
local remain = win_width - #item.text - #call_by
log("remain", remain)
space = string.rep(' ', remain)
end
end
item.text = item.text .. space .. call_by
end
local tail = display_items[#display_items].text
if tail ~= item.text then -- deduplicate
trace(item.text)
trace(item.call_by)
table.insert(display_items, item)
end
end
-- display_items[last_summary_idx].text=string.format("%s [%i]", display_items[last_summary_idx].filename,
-- total_ref_in_file)
return display_items
end
return M

@ -55,8 +55,9 @@ function M.workspace_symbols(opts)
-- result_lsp
local result = {}
for i = 1, #results_lsp do
if results_lsp[i] ~= nil and results_lsp[i].result ~= nil and
#results_lsp[i].result > 0 then result = results_lsp[i].result end
if results_lsp[i] ~= nil and results_lsp[i].result ~= nil and #results_lsp[i].result > 0 then
result = results_lsp[i].result
end
end
local items = symbols_to_items(result)
@ -115,23 +116,16 @@ function M.document_symbol_handler(err, _, result, _, bufnr)
child.uri = uri
child.lnum = c.range.start.line + 1
child.detail = c.detail or ""
child.text = "  [" .. ckind .. "] " .. child.detail .. " " ..
child.name
child.text = "  [" .. ckind .. "] " .. child.detail .. " " .. child.name
table.insert(locations, child)
end
end
end
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
-- verbose(locations)
-- trace(locations)
-- local items = locations_to_items(locations)
gui.new_list_view({
items = locations,
prompt = true,
rawdata = true,
ft = ft,
api = ""
})
gui.new_list_view({items = locations, prompt = true, rawdata = true, ft = ft, api = ""})
-- if locations == nil or vim.tbl_isempty(locations) then
-- print "References not found"
@ -178,13 +172,7 @@ function M.workspace_symbol_handler(err, _, result, _, bufnr)
-- local items = locations_to_items(locations)
local ft = vim.api.nvim_buf_get_option(bufnr, "ft")
gui.new_list_view({
items = items,
prompt = true,
ft = ft,
rowdata = true,
api = ""
})
gui.new_list_view({items = items, prompt = true, ft = ft, rowdata = true, api = ""})
-- if locations == nil or vim.tbl_isempty(locations) then
-- print "References not found"

@ -2,7 +2,9 @@ local gui = require "navigator.gui"
local ok, ts_locals = pcall(require, "nvim-treesitter.locals")
if not ok then error("treesitter not installed") end
if not ok then
error("treesitter not installed")
end
local parsers = require "nvim-treesitter.parsers"
local ts_utils = require "nvim-treesitter.ts_utils"
@ -13,7 +15,7 @@ local M = {}
local cwd = vim.fn.getcwd(0)
local log = require"navigator.util".log
local verbose = require"navigator.util".verbose
local trace = require"navigator.util".trace
local match_kinds = {
var = "", -- "👹", -- Vampaire
@ -69,44 +71,78 @@ local function prepare_node(node, kind)
if node.node then
table.insert(matches, {kind = get_icon(kind), def = node.node, type = kind})
else
for name, item in pairs(node) do vim.list_extend(matches, prepare_node(item, name)) end
for name, item in pairs(node) do
vim.list_extend(matches, prepare_node(item, name))
end
end
return matches
end
local function get_var_context(source)
local function get_scope(type, source)
local sbl, sbc, sel, sec = source:range()
local current = source
local result = current
local next = ts_utils.get_next_node(source)
local parent = current:parent()
trace(source:type(), source:range(), parent)
if type == 'method' or type == 'function' and parent ~= nil then
trace(parent:type(), parent:range())
-- a function name
if parent:type() == 'function_name' then
-- up one level
return parent:parent(), true
end
if parent:type() == 'function_name_field' then
return parent:parent():parent(), true
end
return parent, true
end
if next == nil or parent == nil then return end
if next:type() == "function" or next:type() == "arrow_function" then
log(current:type(), current:range())
return parent
else
return source
if type == "var" and next ~= nil then
if next:type() == "function" or next:type() == "arrow_function" or next:type() ==
"function_definition" then
trace(current:type(), current:range())
return next, true
elseif parent:type() == 'function_declaration' then
return parent, true
else
trace(source, source:type())
return source, false
end
else -- M.fun1 = function() end
-- lets work up and see next node
local n = source
for i = 1, 4, 1 do
if n == nil or n:parent() == nil then
break
end
n = n:parent()
next = ts_utils.get_next_node(n)
if next ~= nil and next:type() == 'function_definition' then
return next, true
end
end
end
if source:type() == "type_identifier" then
return source:parent(), true
end
-- while current ~= nil do
-- log(current:type(), current:range())
-- if current:type() == "variable_declarator" or current:type() == "function_declaration" then
-- return current
-- end
-- -- local bl, bc, el, ec = current:range()
-- -- if bl == sbl and bc == sbc and el >= sel and ec >= sec then result = current end
-- current = current:parent()
-- end
-- log(current)
end
local function get_smallest_context(source)
local scopes = ts_locals.get_scopes()
for key, value in pairs(scopes) do
trace(key, value)
end
local current = source
while current ~= nil and not vim.tbl_contains(scopes, current) do current = current:parent() end
log(current)
if current ~= nil then return current end
return get_var_context(source)
while current ~= nil and not vim.tbl_contains(scopes, current) do
current = current:parent()
end
if current ~= nil then
return current, true
end
-- if source:type() == "identifier" then return get_var_context(source) end
end
@ -115,7 +151,9 @@ local lsp_reference = require"navigator.dochighlight".goto_adjent_reference
function M.goto_adjacent_usage(bufnr, delta)
local opt = {forward = true}
-- log(delta)
if delta < 0 then opt = {forward = false} end
if delta < 0 then
opt = {forward = false}
end
bufnr = bufnr or api.nvim_get_current_buf()
local node_at_point = ts_utils.get_node_at_cursor()
if not node_at_point then
@ -136,13 +174,20 @@ function M.goto_adjacent_usage(bufnr, delta)
ts_utils.goto_node(usages[target_index])
end
function M.goto_next_usage(bufnr) return M.goto_adjacent_usage(bufnr, 1) end
function M.goto_previous_usage(bufnr) return M.goto_adjacent_usage(bufnr, -1) end
function M.goto_next_usage(bufnr)
return M.goto_adjacent_usage(bufnr, 1)
end
function M.goto_previous_usage(bufnr)
return M.goto_adjacent_usage(bufnr, -1)
end
local function get_all_nodes(bufnr, filter, summary)
trace(bufnr, filter, summary)
bufnr = bufnr or 0
summary = summary or false
if not parsers.has_parser() then print("ts not loaded") end
if not parsers.has_parser() then
print("ts not loaded")
end
local fname = vim.fn.expand("%:p:f")
local uri = vim.uri_from_fname(fname)
if bufnr ~= 0 then
@ -186,27 +231,56 @@ local function get_all_nodes(bufnr, filter, summary)
for _, node in ipairs(nodes) do
item.kind = node.kind
item.type = node.type
if filter ~= nil and not filter[item.type] then
trace(item.type, item.kind)
goto continue
end
local tsdata = node.def
log(item.type, tsdata:type())
if node.def == nil then goto continue end
if node.def == nil then
goto continue
end
item.node_text = ts_utils.get_node_text(tsdata, bufnr)[1]
local scope, is_func
local scope = get_smallest_context(tsdata)
if summary then
scope, is_func = get_scope(item.type, tsdata)
else
scope, is_func = get_smallest_context(tsdata)
end
if is_func then
-- hack for lua and maybe other language aswell
local parent = tsdata:parent()
if parent ~= nil and parent:type() == 'function_name' or parent:type() ==
'function_name_field' then
item.node_text = ts_utils.get_node_text(parent, bufnr)[1]
log(parent:type(), item.node_text)
end
end
log(item.node_text, item.kind, item.type)
if scope ~= nil then
-- it is strange..
log(item.node_text, item.kind, item.type)
if not is_func and summary then
goto continue
end
item.node_scope = ts_utils.node_to_lsp_range(scope)
end
if filter ~= nil and not filter[item.type] then goto continue end
if summary then
if item.node_scope ~= nil then table.insert(all_nodes, item) end
if item.node_scope ~= nil then
table.insert(all_nodes, item)
end
log(item.type, tsdata:type(), item.node_text, item.kind, item.node_text, item.node_scope)
goto continue
end
item.range = ts_utils.node_to_lsp_range(tsdata)
local start_line_node, _, _ = tsdata:start()
if item.node_text == "_" then goto continue end
if item.node_text == "_" then
goto continue
end
item.full_text = vim.trim(api.nvim_buf_get_lines(bufnr, start_line_node, start_line_node + 1,
false)[1] or "")
item.uri = uri
@ -217,16 +291,20 @@ local function get_all_nodes(bufnr, filter, summary)
item.lnum = item.lnum + 1
item.col = item.col + 1
local indent = ""
if #parents > 1 then indent = string.rep(" ", #parents - 1) .. "" end
if #parents > 1 then
indent = string.rep(" ", #parents - 1) .. ""
end
item.text = string.format(" %s %s%-10s\t %s", item.kind, indent, item.node_text,
item.full_text)
if #item.text > length then length = #item.text end
if #item.text > length then
length = #item.text
end
table.insert(all_nodes, item)
::continue::
end
end
verbose(all_nodes)
trace(all_nodes)
return all_nodes, length
end
@ -244,20 +322,36 @@ function M.buf_func(bufnr)
["class"] = true,
["type"] = true
}, true)
table.sort(all_nodes, function(i, j)
if i.range and j.range then
if i.range.start.line == j.range.start.line then
return i.range['end'].line < j.range['end'].line
else
return i.range.start.line < j.range.start.line
end
end
return false
end)
if #all_nodes < 1 then
log("no node found for ", bufnr)
return
end
verbose(all_nodes, width)
if all_nodes[1].node_scope then
table.sort(all_nodes, function(i, j)
if i.node_scope and j.node_scope then
if i.node_scope['end'].line == j.node_scope['end'].line then
return i.node_scope.start.line > j.node_scope.start.line
else
return i.node_scope['end'].line < j.node_scope['end'].line
end
end
return false
end)
else
table.sort(all_nodes, function(i, j)
if i.range and j.range then
if i.range['end'].line == j.range['end'].line then
return i.range.start.line > j.range.start.line
else
return i.range['end'].line < j.range['end'].line
end
end
return false
end)
end
return all_nodes
return all_nodes, width
end
@ -295,14 +389,16 @@ function M.bufs_ts()
if vim.api.nvim_buf_is_loaded(buf) then
local all_nodes, length = get_all_nodes(buf)
if all_nodes ~= nil then
if length > max_length then max_length = length end
if length > max_length then
max_length = length
end
vim.list_extend(ts_opened, all_nodes)
end
end
end
end
if #ts_opened > 1 then
verbose(ts_opened)
trace(ts_opened)
local ft = vim.api.nvim_buf_get_option(0, "ft")
gui.new_list_view({

@ -41,7 +41,9 @@ end
local function getDir(path)
local data = {}
local len = #path
if len <= 1 then return nil end
if len <= 1 then
return nil
end
local last_index = 1
for i = 2, len do
local cur_char = path:sub(i, i)
@ -60,17 +62,25 @@ function M.get_relative_path(base_path, my_path)
local base_len = #base_data
local my_len = #my_data
if base_len > my_len then return my_path end
if base_len > my_len then
return my_path
end
if base_data[1] ~= my_data[1] then return my_path end
if base_data[1] ~= my_data[1] then
return my_path
end
local cur = 0
for i = 1, base_len do
if base_data[i] ~= my_data[i] then break end
if base_data[i] ~= my_data[i] then
break
end
cur = i
end
local data = ""
for i = cur + 1, my_len do data = data .. my_data[i] .. "/" end
for i = cur + 1, my_len do
data = data .. my_data[i] .. "/"
end
data = data .. M.get_base(my_path)
return data
end
@ -81,23 +91,28 @@ M._log = require("guihua.log").new({level = default_config.level}, true)
-- add log to you lsp.log
M.log = M._log.info
M.verbose = M._log.debug
M.trace = M._log.trace
M.error = M._log.error
function M.fmt(...) M._log.fmt_info(...) end
function M.fmt(...)
M._log.fmt_info(...)
end
function M.split(inputstr, sep)
if sep == nil then sep = "%s" end
if sep == nil then
sep = "%s"
end
local t = {}
for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do table.insert(t, str) end
for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
table.insert(t, str)
end
return t
end
function M.trim_space(s) return s:match("^%s*(.-)%s*$") end
function M.quickfix_extract(line)
-- check if it is a line of file pos been selected
local split = M.split
line = M.trim_space(line)
line = vim.trim(line)
local sep = split(line, " ")
if #sep < 2 then
M.log(line)
@ -130,7 +145,9 @@ function M.getArgs(inputstr)
return cmd, t
end
function M.p(t) print(vim.inspect(t)) end
function M.p(t)
print(vim.inspect(t))
end
function M.printError(msg)
vim.cmd("echohl ErrorMsg")
@ -148,14 +165,18 @@ function M.open_log()
vim.cmd("edit " .. path)
end
function table.pack(...) return {n = select("#", ...), ...} end
function table.pack(...)
return {n = select("#", ...), ...}
end
function M.show(...)
local string = ""
local args = table.pack(...)
for i = 1, args.n do string = string .. tostring(args[i]) .. "\t" end
for i = 1, args.n do
string = string .. tostring(args[i]) .. "\t"
end
return string .. "\n"
end
@ -165,12 +186,35 @@ function M.split2(s, sep)
sep = sep or " "
local pattern = string.format("([^%s]+)", sep)
string.gsub(s, pattern, function(c) fields[#fields + 1] = c end)
string.gsub(s, pattern, function(c)
fields[#fields + 1] = c
end)
return fields
end
M.open_file = function(filename) vim.api.nvim_command(string.format("e! %s", filename)) end
function M.trim_and_pad(txt)
local len = #txt
if len <= 1 then
return
end
local tab_en = txt[1] == '\t' or false
txt = vim.trim(txt)
if tab_en then
if len - txt > 2 then
return ' ' .. txt
end
if len - txt > 0 then
return ' ' .. txt
end
end
local rep = math.min(12, len - #txt)
return string.rep(' ', rep / 4) .. txt
end
M.open_file = function(filename)
vim.api.nvim_command(string.format("e! %s", filename))
end
M.open_file_at = function(filename, line, col)
vim.api.nvim_command(string.format("e! +%s %s", line, filename))
@ -178,13 +222,27 @@ M.open_file_at = function(filename, line, col)
vim.api.nvim_command(string.format("normal! %dl", col - 1))
end
function M.exists(var) for k, _ in pairs(_G) do if k == var then return true end end end
function M.exists(var)
for k, _ in pairs(_G) do
if k == var then
return true
end
end
end
function M.partial(func, arg) return (function(...) return func(arg, ...) end) end
function M.partial(func, arg)
return (function(...)
return func(arg, ...)
end)
end
local exclude_ft = {"scrollbar", "help", "NvimTree"}
function M.exclude(fname)
for i = 1, #exclude_ft do if string.find(fname, exclude_ft[i]) then return true end end
for i = 1, #exclude_ft do
if string.find(fname, exclude_ft[i]) then
return true
end
end
return false
end
@ -196,7 +254,9 @@ local api = vim.api
local bufs
function M.set_virt_eol(bufnr, lnum, chunks, priority, id)
if nss == nil then nss = api.nvim_create_namespace("navigator_search") end
if nss == nil then
nss = api.nvim_create_namespace("navigator_search")
end
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
bufs[bufnr] = true
-- id may be nil
@ -205,7 +265,9 @@ function M.set_virt_eol(bufnr, lnum, chunks, priority, id)
end
function M.clear_buf(bufnr)
if not bufnr then return end
if not bufnr then
return
end
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
if bufs[bufnr] then
if api.nvim_buf_is_valid(bufnr) then
@ -217,7 +279,9 @@ function M.clear_buf(bufnr)
end
function M.clear_all_buf()
for bufnr in pairs(bufs) do M.clear_buf(bufnr) end
for bufnr in pairs(bufs) do
M.clear_buf(bufnr)
end
bufs = {}
end

Loading…
Cancel
Save