Vim diagnostic refactor (#63)

* multigrid support

* Tuning diagnostic performance, add codelens inline hint function

* add ctx to error marker handler

* setup lsp_signature from navigator

* diagnostic refact PR https://github.com/neovim/neovim/pull/15585

* diagnostic api changes

* allow disable emoji/nerdfont icons setup

* improve diagnostic/codeaction/codelens preview popup; add seperate line

* severity_sort set to reverse

* prettier for markdown. code action virtual text show title
neovim_0.6
rayx 3 years ago committed by GitHub
parent 90039247b4
commit 79fee5dda8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,23 +13,25 @@ The following screenshot shows javascript call tree 🌲 of variable `browser` i
![navigator](https://user-images.githubusercontent.com/1681295/126022829-291a7a2e-4d24-4fde-8293-5ae61562e67d.jpg) ![navigator](https://user-images.githubusercontent.com/1681295/126022829-291a7a2e-4d24-4fde-8293-5ae61562e67d.jpg)
Explanation: Explanation:
- The first line of floating windows shows there are 3 references for the symbol <span style="color:red"> *browser* </span> in closure.js
- The first reference of browser is an assignment, an emoji 📝 indicates the value is changed in this line. In many - The first line of floating windows shows there are 3 references for the symbol <span style="color:red"> _browser_ </span> in closure.js
cases, we search for references to find out when the value changed. - The first reference of browser is an assignment, an emoji 📝 indicates the value is changed in this line. In many
cases, we search for references to find out when the value changed.
- The second reference of `browser` is inside function `displayName` and `displayName` sit inside `makeFunc`, So you - The second reference of `browser` is inside function `displayName` and `displayName` sit inside `makeFunc`, So you
will see ` displayName{} <- makeFunc{}` 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 - 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. and emoji is not shown.
#### Example: C++ definition #### Example: C++ definition
C++ example: search reference and definition C++ example: search reference and definition
![cpp_ref](https://user-images.githubusercontent.com/1681295/119215215-8bd7a080-bb0f-11eb-82fc-8cdf1955e6e7.jpg) ![cpp_ref](https://user-images.githubusercontent.com/1681295/119215215-8bd7a080-bb0f-11eb-82fc-8cdf1955e6e7.jpg)
You may find a 🦕 dinosaur(d) on the line of `Rectangle rect,` which means there is a definition (d for def) of rect in this line. You may find a 🦕 dinosaur(d) on the line of `Rectangle rect,` which means there is a definition (d for def) of rect in this line.
``<- f main()`` means the definition is inside function main(). `<- f main()` means the definition is inside function main().
#### Golang struct type #### Golang struct type
Struct type references in multiple Go ﳑ files Struct type references in multiple Go ﳑ files
![go_reference](https://user-images.githubusercontent.com/1681295/119123823-54b3b180-ba73-11eb-8790-097601e10f6a.gif) ![go_reference](https://user-images.githubusercontent.com/1681295/119123823-54b3b180-ba73-11eb-8790-097601e10f6a.gif)
@ -37,6 +39,7 @@ Struct type references in multiple Go ﳑ files
This feature can provide you info in which function/class/method the variable was referenced. It is handy for a large This feature can provide you info in which function/class/method the variable was referenced. It is handy for a large
project where class/function definition is too long to fit into the preview window. Also provides a bird's eye view of where the project where class/function definition is too long to fit into the preview window. Also provides a bird's eye view of where the
variable is: variable is:
- Referenced - Referenced
- Modified - Modified
- Defined - Defined
@ -45,14 +48,14 @@ variable is:
# Features: # Features:
- LSP easy setup. Support the most commonly used lsp clients setup. Dynamic lsp activation based on buffer type. This - LSP easy setup. Support the most commonly used lsp clients setup. Dynamic lsp activation based on buffer type. This
also enables you to handle workspace with mixed types of codes (e.g. Go + javascript + yml). A better default setup is also enables you to handle workspace with mixed types of codes (e.g. Go + javascript + yml). A better default setup is
included for LSP clients. included for LSP clients.
- Out of box experience. 10 lines of minimum vimrc can turn your neovim into a full-featured LSP & Treesitter powered IDE - 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, navigator provides a visual way to manage and navigate through symbols, diagnostic errors, reference etc. It covers - Unorthodox UI with floating windows, navigator provides a visual way to manage and navigate through symbols, diagnostic errors, reference etc. It covers
all features(handler) provided by LSP from commonly used search reference, to less commonly used search for interface all features(handler) provided by LSP from commonly used search reference, to less commonly used search for interface
implementation. implementation.
- Luv async thread and tasks - Luv async thread and tasks
@ -71,14 +74,14 @@ implementation.
- Grouping references/implementation/incoming/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 - 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. 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, allows you to search in all treesitter symbols in the workspace. - The first plugin, IMO, allows you to search in all treesitter symbols in the workspace.
- Nerdfont, emoji for LSP and treesitter kind - 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 - 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 in the same line). Using treesitter for file preview highlighter etc
- ccls call hierarchy (Non-standard `ccls/call` API) supports - ccls call hierarchy (Non-standard `ccls/call` API) supports
@ -186,9 +189,6 @@ EOF
``` ```
Nondefault configuration example: Nondefault configuration example:
```lua ```lua
@ -219,7 +219,7 @@ require'navigator'.setup({
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},
icons = { icons = {
-- Code action -- Code action
code_action_icon = "", code_action_icon = "🏏",
-- Diagnostics -- Diagnostics
diagnostic_head = '🐛', diagnostic_head = '🐛',
diagnostic_head_severity_1 = "🈲", diagnostic_head_severity_1 = "🈲",
@ -268,6 +268,7 @@ require'navigator'.setup({
### LSP clients ### LSP clients
Built clients: Built clients:
```lua ```lua
local servers = { local servers = {
"angularls", "gopls", "tsserver", "flow", "bashls", "dockerls", "julials", "pylsp", "pyright", "angularls", "gopls", "tsserver", "flow", "bashls", "dockerls", "julials", "pylsp", "pyright",
@ -283,24 +284,24 @@ Navigator will try to load avalible lsp server/client based on filetype. The cli
incremental sync and debounce is enabled by navigator. And the lsp incremental sync and debounce is enabled by navigator. And the lsp
snippet will be enabled. So you could use COQ and nvim-cmp snippet expand. snippet will be enabled. So you could use COQ and nvim-cmp snippet expand.
Other than above setup, additional none default setup are used for following lsp: Other than above setup, additional none default setup are used for following lsp:
* gopls - gopls
* clangd - clangd
* rust_analyzer - rust_analyzer
* sqls - sqls
* sumneko_lua - sumneko_lua
* pyright - pyright
* ccls - ccls
Please check [client setup](https://github.com/ray-x/navigator.lua/blob/26012cf9c172aa788a2e53018d94b32c5c75af75/lua/navigator/lspclient/clients.lua#L98-L234) Please check [client setup](https://github.com/ray-x/navigator.lua/blob/26012cf9c172aa788a2e53018d94b32c5c75af75/lua/navigator/lspclient/clients.lua#L98-L234)
The plugin can work with multiple LSP, e.g sqls+gopls+efm. But there are cases you may need to disable some of the The plugin can work with multiple LSP, e.g sqls+gopls+efm. But there are cases you may need to disable some of the
servers. (Prevent loading multiple LSP for same source code.) e.g. I saw strange behaviours when I use pyls+pyright+pyls_ms servers. (Prevent loading multiple LSP for same source code.) e.g. I saw strange behaviours when I use pyls+pyright+pyls_ms
together. If you have multiple similar LSP installed and have trouble with the plugin, please enable only one at a time. together. If you have multiple similar LSP installed and have trouble with the plugin, please enable only one at a time.
### Disable a lsp client loading from navigator ### Disable a lsp client loading from navigator
To disable a specific LSP, set `filetypes` to {} e.g. To disable a specific LSP, set `filetypes` to {} e.g.
```lua ```lua
@ -320,56 +321,54 @@ require'navigator'.setup({
### Default keymaps ### Default keymaps
| mode | key | function | | mode | key | function |
|--- |--- |--- | | ---- | --------------- | ---------------------------------------------------------- |
| n | gr | show reference and context | | n | gr | show reference and context |
| i | \<m-k\> | signature help | | i | \<m-k\> | signature help |
| n | \<c-k\> | signature help | | n | \<c-k\> | signature help |
| n | gW | workspace symbol | | n | gW | workspace symbol |
| n | gD | declaration | | n | gD | declaration |
| n | g0 | document symbol | | n | g0 | document symbol |
| n | \<C-]\> | go to definition (if multiple show listview) | | n | \<C-]\> | go to definition (if multiple show listview) |
| n | gp | definition | | n | gp | definition |
| n | \<C-LeftMouse\> | definition| | n | \<C-LeftMouse\> | definition |
| n | g\<LeftMouse\> | implementation| | n | g\<LeftMouse\> | implementation |
| n | gT | treesitter document symbol | | n | gT | treesitter document symbol |
| n | \<Leader\>gT | treesitter symbol for all open buffers | | n | \<Leader\>gT | treesitter symbol for all open buffers |
| n | K | hover doc | | n | K | hover doc |
| n | \<Space\>ca | code action (when you see 💡 ) | | n | \<Space\>ca | code action (when you see 💡 ) |
| n | \<Space\>la | code lens action (when you see a codelens indicator) | | n | \<Space\>la | code lens action (when you see a codelens indicator) |
| v | \<Space\>cA | range code action (when you see 💡 ) | | v | \<Space\>cA | range code action (when you see 💡 ) |
| n | \<Space\>rn | rename with floating window| | n | \<Space\>rn | rename with floating window |
| n | \<Leader\>re | rename (lsp default)| | n | \<Leader\>re | rename (lsp default) |
| n | \<Leader\>gi | incoming calls| | n | \<Leader\>gi | incoming calls |
| n | \<Leader\>go | outgoing calls| | n | \<Leader\>go | outgoing calls |
| n | gi | implementation | | n | gi | implementation |
| n | \<Sapce\> D | type definition | | n | \<Sapce\> D | type definition |
| n | gL | show line diagnostic | | n | gL | show line diagnostic |
| n | gG | show diagnostic for all buffers | | n | gG | show diagnostic for all buffers |
| n | ]d | next diagnostic| | n | ]d | next diagnostic |
| n | [d | previous diagnostic| | n | [d | previous diagnostic |
| n | ]r | next treesitter reference/usage| | n | ]r | next treesitter reference/usage |
| n | [r | previous treesitter reference/usage| | n | [r | previous treesitter reference/usage |
| n | \<Sapce\> wa | add workspace folder| | n | \<Sapce\> wa | add workspace folder |
| n | \<Sapce\> wr | remove workspace folder| | n | \<Sapce\> wr | remove workspace folder |
| n | \<Sapce\> wl | print workspace folder| | n | \<Sapce\> wl | print workspace folder |
| n | \<Leader\>k | toggle reference highlight | | n | \<Leader\>k | toggle reference highlight |
| i/n | \<C-p\> | previous item in list| | i/n | \<C-p\> | previous item in list |
| i/n | \<C-n\> | next item in list| | i/n | \<C-n\> | next item in list |
| i/n | number 1~9 | move to ith row/item in the list| | i/n | number 1~9 | move to ith row/item in the list |
| i/n | \<Up\> | previous item in list| | i/n | \<Up\> | previous item in list |
| i/n | \<Down\> | next item in list| | i/n | \<Down\> | next item in list |
| n | \<Ctrl-w\>j | move cursor to preview (windows move to bottom view point)| | n | \<Ctrl-w\>j | move cursor to preview (windows move to bottom view point) |
| n | \<Ctrl-w\>k | move cursor to list (windows move to up view point)| | n | \<Ctrl-w\>k | move cursor to list (windows move to up view point) |
| i/n | \<C-o\> | open preview file in nvim/Apply action| | i/n | \<C-o\> | open preview file in nvim/Apply action |
| n | \<C-v\> | open preview file in nvim with vsplit| | n | \<C-v\> | open preview file in nvim with vsplit |
| n | \<C-s\> | open preview file in nvim with split| | n | \<C-s\> | open preview file in nvim with split |
| n | \<Enter\> | open preview file in nvim/Apply action| | n | \<Enter\> | open preview file in nvim/Apply action |
| i/n | \<C-b\> | previous page in listview| | i/n | \<C-b\> | previous page in listview |
| i/n | \<C-f\> | next page in listview| | i/n | \<C-f\> | next page in listview |
| i/n | \<C-s\> | save the modification to preview window to file| | i/n | \<C-s\> | save the modification to preview window to file |
### Colors/Highlight: ### Colors/Highlight:
@ -382,11 +381,9 @@ hi default GHTextViewDark guifg=#e0d8f4 guibg=#332e55
hi default GHListDark guifg=#e0d8f4 guibg=#103234 hi default GHListDark guifg=#e0d8f4 guibg=#103234
``` ```
There are other Lsp highlight been used in this plugin, e.g LspReferenceRead/Text/Write are used for document highlight, There are other Lsp highlight been used in this plugin, e.g LspReferenceRead/Text/Write are used for document highlight,
LspDiagnosticsXXX are used for diagnostic. Please check highlight.lua and dochighlight.lua for more info. LspDiagnosticsXXX are used for diagnostic. Please check highlight.lua and dochighlight.lua for more info.
## Dependency ## Dependency
- lspconfig - lspconfig
@ -399,13 +396,14 @@ 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). The terminal will need to be able to output nerdfont and emoji correctly. I am using Kitty with nerdfont (Victor Mono).
## Integration with lspinstall ## Integration with lspinstall
If you'd like to only use the lsp servers installed by lspinstall. Please set If you'd like to only use the lsp servers installed by lspinstall. Please set
```lua ```lua
lspinstall = false lspinstall = false
``` ```
In the config In the config
## Usage ## Usage
@ -431,22 +429,21 @@ require'navigator'.setup({on_attach = function(client, bufnr) require 'illuminat
Highlight I am using: Highlight I am using:
* LspReferenceRead, LspReferenceText and LspReferenceWrite are used for `autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()` - LspReferenceRead, LspReferenceText and LspReferenceWrite are used for `autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()`
That is where you saw the current symbol been highlighted. That is where you saw the current symbol been highlighted.
* GHListDark and GHTextViewDark is used for floating listvew and TextView. They are be based on current background - GHListDark and GHTextViewDark is used for floating listvew and TextView. They are be based on current background
(Normal) and PmenuSel (Normal) and PmenuSel
* In future, I will use NormalFloat for floating view. But ATM, most of colorscheme does not define NormalFloat - In future, I will use NormalFloat for floating view. But ATM, most of colorscheme does not define NormalFloat
You can override the above highlight to fit your current colorscheme You can override the above highlight to fit your current colorscheme
## commands ## commands
| command | function |
|--- |--- |
| LspToggleFmt | toggle lsp auto format |
| command | function |
| ------------ | ---------------------- |
| LspToggleFmt | toggle lsp auto format |
## Screenshots ## Screenshots
@ -465,6 +462,7 @@ Pls check the first part of README
![workspace symbol](https://github.com/ray-x/files/blob/master/img/navigator/workspace_symbol.gif?raw=true) ![workspace symbol](https://github.com/ray-x/files/blob/master/img/navigator/workspace_symbol.gif?raw=true)
### highlight document symbol and jump between reference ### highlight document symbol and jump between reference
![multiple_symbol_hi3](https://user-images.githubusercontent.com/1681295/120067627-f9f80680-c0bf-11eb-9216-18e5c8547f59.gif) ![multiple_symbol_hi3](https://user-images.githubusercontent.com/1681295/120067627-f9f80680-c0bf-11eb-9216-18e5c8547f59.gif)
# Current symbol highlight and jump backward/forward between symbols # Current symbol highlight and jump backward/forward between symbols
@ -480,7 +478,6 @@ Visual studio code style show errors minimap in scroll bar area
![diagnostic_scroll_bar](https://user-images.githubusercontent.com/1681295/128736430-e365523d-810c-4c16-a3b4-c74969f45f0b.jpg) ![diagnostic_scroll_bar](https://user-images.githubusercontent.com/1681295/128736430-e365523d-810c-4c16-a3b4-c74969f45f0b.jpg)
Diagnostic in single bufer Diagnostic in single bufer
![diagnostic](https://github.com/ray-x/files/blob/master/img/navigator/diag.jpg?raw=true) ![diagnostic](https://github.com/ray-x/files/blob/master/img/navigator/diag.jpg?raw=true)
@ -495,7 +492,6 @@ You can in place edit your code in floating window
https://user-images.githubusercontent.com/1681295/121832919-89cbc080-cd0e-11eb-9778-11d0f356b38d.mov https://user-images.githubusercontent.com/1681295/121832919-89cbc080-cd0e-11eb-9778-11d0f356b38d.mov
(Note: This feature only avalible in `find reference` and `find diagnostic`, You can not add/remove lines in floating window) (Note: This feature only avalible in `find reference` and `find diagnostic`, You can not add/remove lines in floating window)
### Implementation ### Implementation
@ -549,14 +545,12 @@ Codelens for C++/ccls. Symbol reference
![codelens_cpp_ccls](https://user-images.githubusercontent.com/1681295/132429134-abc6547e-79cc-44a4-b7a9-23550b895e51.jpg) ![codelens_cpp_ccls](https://user-images.githubusercontent.com/1681295/132429134-abc6547e-79cc-44a4-b7a9-23550b895e51.jpg)
### Predefined LSP symbol nerdfont/emoji ### Predefined LSP symbol nerdfont/emoji
![nerdfont](https://github.com/ray-x/files/blob/master/img/navigator/icon_nerd.jpg?raw=true) ![nerdfont](https://github.com/ray-x/files/blob/master/img/navigator/icon_nerd.jpg?raw=true)
# Break changes and known issues # Break changes and known issues
[known issues I am working on](https://github.com/ray-x/navigator.lua/issues/1) [known issues I am working on](https://github.com/ray-x/navigator.lua/issues/1)
# Todo # Todo

@ -8,6 +8,7 @@ _NgConfigValues = {
preview_lines_before = 5, -- lines before the highlight line preview_lines_before = 5, -- lines before the highlight line
default_mapping = true, default_mapping = true,
keymaps = {}, -- e.g keymaps={{key = "GR", func = "references()"}, } this replace gr default mapping keymaps = {}, -- e.g keymaps={{key = "GR", func = "references()"}, } this replace gr default mapping
external = nil, -- true: enable for goneovim multigrid otherwise false
border = "single", -- border style, can be one of 'none', 'single', 'double', "shadow" border = "single", -- border style, can be one of 'none', 'single', 'double', "shadow"
combined_attach = "both", -- both: use both customized attach and navigator default attach, mine: only use my attach defined in vimrc combined_attach = "both", -- both: use both customized attach and navigator default attach, mine: only use my attach defined in vimrc
@ -33,7 +34,7 @@ _NgConfigValues = {
-- to load those files -- to load those files
diagnostic_virtual_text = true, -- show virtual for diagnostic message diagnostic_virtual_text = true, -- show virtual for diagnostic message
diagnostic_update_in_insert = false, -- update diagnostic message in insert mode diagnostic_update_in_insert = false, -- update diagnostic message in insert mode
diagnostic_scrollbar_sign = {'', ''}, -- set to nil to disable, set to {'╍', 'ﮆ'} to enable diagnostic status in scroll bar area diagnostic_scrollbar_sign = {'', '', ''}, -- set to nil to disable, set to {'╍', 'ﮆ'} to enable diagnostic status in scroll bar area
tsserver = { tsserver = {
-- filetypes = {'typescript'} -- disable javascript etc, -- filetypes = {'typescript'} -- disable javascript etc,
-- set to {} to disable the lspclient for all filetype -- set to {} to disable the lspclient for all filetype
@ -46,16 +47,22 @@ _NgConfigValues = {
}, },
lspinstall = false, -- set to true if you would like use the lsp installed by lspinstall lspinstall = false, -- set to true if you would like use the lsp installed by lspinstall
icons = { icons = {
icons = true, -- set to false to use system default ( if you using a terminal does not have nerd/icon)
-- Code action -- Code action
code_action_icon = "", code_action_icon = "🏏", -- "",
-- code lens -- code lens
code_lens_action_icon = "", code_lens_action_icon = "",
-- Diagnostics -- Diagnostics
diagnostic_head = '🐛', diagnostic_head = '🐛',
diagnostic_err = "📛",
diagnostic_warn = "👎",
diagnostic_info = [[👩]],
diagnostic_hint = [[💁]],
diagnostic_head_severity_1 = "🈲", diagnostic_head_severity_1 = "🈲",
diagnostic_head_severity_2 = "☣️", diagnostic_head_severity_2 = "☣️",
diagnostic_head_severity_3 = "👎", diagnostic_head_severity_3 = "👎",
diagnostic_head_description = "📛", diagnostic_head_description = "👹",
diagnostic_virtual_text = "🦊", diagnostic_virtual_text = "🦊",
diagnostic_file = "🚑", diagnostic_file = "🚑",
-- Values -- Values
@ -131,7 +138,7 @@ M.setup = function(cfg)
-- log("navigator loader") -- log("navigator loader")
if _NgConfigValues.code_action_prompt.enable then if _NgConfigValues.code_action_prompt.enable then
vim.cmd [[autocmd CursorHold * lua require'navigator.codeAction'.code_action_prompt()]] vim.cmd [[autocmd CursorHold,CursorHoldI * lua require'navigator.codeAction'.code_action_prompt()]]
end end
-- vim.cmd("autocmd BufNewFile,BufRead *.go setlocal noexpandtab tabstop=4 shiftwidth=4") -- vim.cmd("autocmd BufNewFile,BufRead *.go setlocal noexpandtab tabstop=4 shiftwidth=4")
if not _NgConfigValues.loaded then if not _NgConfigValues.loaded then
@ -141,12 +148,6 @@ M.setup = function(cfg)
if _NgConfigValues.ts_fold == true then if _NgConfigValues.ts_fold == true then
require('navigator.foldts').on_attach() require('navigator.foldts').on_attach()
end end
--- if code line enabled
if _NgConfigValues.lsp.code_lens then
require("navigator.codelens").setup()
end
end end
return M return M

@ -5,9 +5,13 @@ local code_action = {}
local gui = require "navigator.gui" local gui = require "navigator.gui"
local config = require("navigator").config_values() local config = require("navigator").config_values()
local api = vim.api local api = vim.api
local sign_name = "NavigatorLightBulb"
local diagnostic = vim.diagnostic or vim.lsp.diagnostic
code_action.code_action_handler = util.mk_handler(function(err, actions, ctx, cfg) code_action.code_action_handler = util.mk_handler(function(err, actions, ctx, cfg)
log(actions, ctx) log(actions, ctx)
if actions == nil or vim.tbl_isempty(actions) then if actions == nil or vim.tbl_isempty(actions) or err then
print("No code actions available") print("No code actions available")
return return
end end
@ -19,13 +23,16 @@ code_action.code_action_handler = util.mk_handler(function(err, actions, ctx, cf
table.insert(data, title) table.insert(data, title)
actions[i].display_title = title actions[i].display_title = title
end end
local width = 0 local width = 42
for _, str in ipairs(data) do for _, str in ipairs(data) do
if #str > width then if #str > width then
width = #str width = #str
end end
end end
local divider = string.rep('', width + 2)
table.insert(data, 2, divider)
local apply = require('navigator.lspwrapper').apply_action local apply = require('navigator.lspwrapper').apply_action
local function apply_action(action) local function apply_action(action)
local action_chosen = nil local action_chosen = nil
@ -42,7 +49,7 @@ code_action.code_action_handler = util.mk_handler(function(err, actions, ctx, cf
apply(action_chosen) apply(action_chosen)
end end
gui.new_list_view { local listview = gui.new_list_view {
items = data, items = data,
width = width + 4, width = width + 4,
loc = "top_center", loc = "top_center",
@ -58,6 +65,9 @@ code_action.code_action_handler = util.mk_handler(function(err, actions, ctx, cf
return pos return pos
end end
} }
log("new buffer", listview.bufnr)
vim.api.nvim_buf_add_highlight(listview.bufnr, -1, 'Title', 0, 0, -1)
end) end)
-- https://github.com/glepnir/lspsaga.nvim/blob/main/lua/lspsaga/codeaction.lua -- https://github.com/glepnir/lspsaga.nvim/blob/main/lua/lspsaga/codeaction.lua
@ -71,22 +81,17 @@ local get_current_winid = function()
return api.nvim_get_current_win() return api.nvim_get_current_win()
end end
local sign_name = "NavigatorLightBulb" local function _update_virtual_text(line, actions)
if vim.tbl_isempty(vim.fn.sign_getdefined(sign_name)) then
vim.fn.sign_define(sign_name,
{text = config.icons.code_action_icon, texthl = "LspDiagnosticsSignHint"})
end
local function _update_virtual_text(line)
local namespace = get_namespace() local namespace = get_namespace()
pcall(api.nvim_buf_clear_namespace, 0, namespace, 0, -1) pcall(api.nvim_buf_clear_namespace, 0, namespace, 0, -1)
if line then if line then
log(line, actions)
local icon_with_indent = " " .. config.icons.code_action_icon local icon_with_indent = " " .. config.icons.code_action_icon
local title = actions[1].title
pcall(api.nvim_buf_set_extmark, 0, namespace, line, -1, { pcall(api.nvim_buf_set_extmark, 0, namespace, line, -1, {
virt_text = {{icon_with_indent, "LspDiagnosticsSignHint"}}, virt_text = {{icon_with_indent .. title, "LspDiagnosticsSignHint"}},
virt_text_pos = "overlay", virt_text_pos = "overlay",
hl_mode = "combine" hl_mode = "combine"
}) })
@ -94,6 +99,11 @@ local function _update_virtual_text(line)
end end
local function _update_sign(line) local function _update_sign(line)
if vim.tbl_isempty(vim.fn.sign_getdefined(sign_name)) then
vim.fn.sign_define(sign_name,
{text = config.icons.code_action_icon, texthl = "LspDiagnosticsSignHint"})
end
local winid = get_current_winid() local winid = get_current_winid()
if code_action[winid] == nil then if code_action[winid] == nil then
code_action[winid] = {} code_action[winid] = {}
@ -110,11 +120,14 @@ local function _update_sign(line)
end end
end end
local need_check_diagnostic = {["go"] = true, ["python"] = true} -- local need_check_diagnostic = {["go"] = true, ["python"] = true}
local need_check_diagnostic = {['python'] = true}
function code_action:render_action_virtual_text(line, diagnostics) function code_action:render_action_virtual_text(line, diagnostics)
return function(_, _, actions) return function(err, actions, context)
log(err, line, diagnostics, actions, context)
if actions == nil or type(actions) ~= "table" or vim.tbl_isempty(actions) then if actions == nil or type(actions) ~= "table" or vim.tbl_isempty(actions) then
-- no actions cleanup
if config.code_action_prompt.virtual_text then if config.code_action_prompt.virtual_text then
_update_virtual_text(nil) _update_virtual_text(nil)
end end
@ -127,6 +140,7 @@ function code_action:render_action_virtual_text(line, diagnostics)
if next(diagnostics) == nil then if next(diagnostics) == nil then
_update_sign(nil) _update_sign(nil)
else else
-- no diagnostic, no code action sign..
_update_sign(line) _update_sign(line)
end end
else else
@ -139,10 +153,10 @@ function code_action:render_action_virtual_text(line, diagnostics)
if next(diagnostics) == nil then if next(diagnostics) == nil then
_update_virtual_text(nil) _update_virtual_text(nil)
else else
_update_virtual_text(line) _update_virtual_text(line, actions)
end end
else else
_update_virtual_text(line) _update_virtual_text(line, actions)
end end
end end
end end
@ -186,7 +200,15 @@ code_action.code_action_prompt = function()
return return
end end
local diagnostics = vim.lsp.diagnostic.get_line_diagnostics() local diagnostics
if diagnostic.get_line_diagnostics then
-- old version
diagnostics = diagnostic.get_line_diagnostics()
else
local lnum = vim.api.nvim_win_get_cursor(0)[1] - 1
diagnostics = diagnostic.get(vim.api.nvim_get_current_buf(), {lnum = lnum})
end
local winid = get_current_winid() local winid = get_current_winid()
code_action[winid] = code_action[winid] or {} code_action[winid] = code_action[winid] or {}
code_action[winid].lightbulb_line = code_action[winid].lightbulb_line or 0 code_action[winid].lightbulb_line = code_action[winid].lightbulb_line or 0

@ -69,7 +69,9 @@ function M.setup()
local on_codelens = vim.lsp.handlers["textDocument/codeLens"] local on_codelens = vim.lsp.handlers["textDocument/codeLens"]
vim.lsp.handlers["textDocument/codeLens"] = mk_handler( vim.lsp.handlers["textDocument/codeLens"] = mk_handler(
function(err, result, ctx, cfg) function(err, result, ctx, cfg)
trace(err, result, ctx.client_id, ctx.bufnr, cfg) -- trace(err, result, ctx.client_id, ctx.bufnr, cfg or {})
cfg = cfg or {}
ctx = ctx or {bufnr = vim.api.nvim_get_current_buf()}
if nvim_0_6() then if nvim_0_6() then
on_codelens(err, result, ctx, cfg) on_codelens(err, result, ctx, cfg)
codelens_hdlr(err, result, ctx, cfg) codelens_hdlr(err, result, ctx, cfg)
@ -139,8 +141,12 @@ function M.run_action()
end end
apply(action_chosen) apply(action_chosen)
end end
local divider = string.rep('', width + 2)
table.insert(data, 2, divider)
if #data > 0 then if #data > 0 then
gui.new_list_view { local lv = gui.new_list_view {
items = data, items = data,
width = width + 4, width = width + 4,
loc = "top_center", loc = "top_center",
@ -156,7 +162,70 @@ function M.run_action()
return pos return pos
end end
} }
vim.api.nvim_buf_add_highlight(lv.bufnr, -1, 'Title', 0, 0, -1)
end
end
local virtual_types_ns = api.nvim_create_namespace("ng_virtual_types");
function M.disable()
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_clear_namespace(bufnr, virtual_types_ns, 0, -1)
is_enabled = false
end
M.inline = function()
local lsp = vim.lsp
if is_enabled == false then
return
end
if vim.fn.getcmdwintype() == ':' then
return
end end
if #vim.lsp.buf_get_clients() == 0 then
return
end
local bufnr = api.nvim_get_current_buf()
local parameter = lsp.util.make_position_params()
local response = lsp.buf_request_sync(bufnr, "textDocument/codeLens", parameter)
-- Clear previous highlighting
api.nvim_buf_clear_namespace(bufnr, virtual_types_ns, 0, -1)
if response then
log(response)
for _, v in ipairs(response) do
if v == nil or v.result == nil then
return
end -- no response
for _, vv in pairs(v.result) do
local start_line = -1
for _, vvv in pairs(vv.range) do
start_line = tonumber(vvv.line)
end
local cmd = vv.command
local msg = _NgConfigValues.icons.code_action_icon .. ' '
if cmd then
local txt = cmd.title or ''
txt = txt .. ' ' .. (cmd.command or '') .. ' '
msg = msg .. txt .. ' '
end
log(msg)
api.nvim_buf_set_extmark(bufnr, virtual_types_ns, start_line, -1, {
virt_text = {{msg, "LspCodeLensText"}},
virt_text_pos = 'overlay',
hl_mode = 'combine'
})
end
end
-- else
-- api.nvim_command("echohl WarningMsg | echo 'VirtualTypes: No response' | echohl None")
end
end end
return M return M

@ -0,0 +1,29 @@
local M = {}
function M.debounce_trailing(ms, fn)
local timer = vim.loop.new_timer()
return function(...)
local argv = {...}
timer:start(ms, 0, function()
timer:stop()
fn(unpack(argv))
end)
end
end
function M.throttle_leading(ms, fn)
local timer = vim.loop.new_timer()
local running = false
return function(...)
if not running then
timer:start(ms, 0, function()
running = false
timer:stop()
end)
running = true
fn(...)
end
end
end
return M

@ -1,11 +1,11 @@
local gui = require "navigator.gui" local gui = require "navigator.gui"
local diagnostic_list = {} local diagnostic_list = {}
local diagnostic = vim.diagnostic or vim.lsp.diagnostic
_NG_VT_NS = vim.api.nvim_create_namespace("navigator_lua") -- local hide = diagnostic.hide or diagnostic.clear
_NG_VT_DIAG_NS = vim.api.nvim_create_namespace("navigator_lua_diag")
local util = require "navigator.util" local util = require "navigator.util"
local log = util.log local log = util.log
local trace = require"guihua.log".trace local trace = require"guihua.log".trace
-- trace = log
local error = util.error local error = util.error
local path_sep = require"navigator.util".path_sep() local path_sep = require"navigator.util".path_sep()
@ -13,101 +13,160 @@ local mk_handler = require"navigator.util".mk_handler
local path_cur = require"navigator.util".path_cur() local path_cur = require"navigator.util".path_cur()
diagnostic_list[vim.bo.filetype] = {} diagnostic_list[vim.bo.filetype] = {}
local function error_marker(result, client_id) local function clear_diag_VT(bufnr) -- important for clearing out when no more errors
if _NgConfigValues.lsp.diagnostic_scrollbar_sign == nil then -- not enabled or already shown log(bufnr, _NG_VT_DIAG_NS)
if bufnr == nil or _NG_VT_DIAG_NS == nil then
return return
end end
local first_line = vim.fn.line('w0')
-- local rootfolder = vim.fn.expand('%:h:t') -- get the current file root folder
trace(result)
local bufnr = vim.uri_to_bufnr(result.uri) vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_DIAG_NS, 0, -1)
if bufnr ~= vim.api.nvim_get_current_buf() then _NG_VT_DIAG_NS = nil
-- log("not same buf", client_id, result.uri, bufnr, vim.fn.bufnr()) end
return
local function get_count(bufnr, level)
if vim.diagnostic ~= nil then
return #diagnostic.get(bufnr, {severity = level})
else
return diagnostic.get_count(bufnr, level)
end end
trace(result, bufnr) end
if result == nil or result.diagnostics == nil or #result.diagnostics == 0 then local function error_marker(result, ctx, config)
local diag_cnt = vim.lsp.diagnostic.get_count(bufnr, [[Error]]) vim.defer_fn(function()
+ vim.lsp.diagnostic.get_count(bufnr, [[Warning]]) if vim.tbl_isempty(result.diagnostics) then
if diag_cnt == 0 and _NG_VT_NS ~= nil then return
vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_NS, 0, -1)
end end
return if _NgConfigValues.lsp.diagnostic_scrollbar_sign == nil then -- not enabled or already shown
end return
end
local first_line = vim.fn.line('w0')
-- local rootfolder = vim.fn.expand('%:h:t') -- get the current file root folder
-- total line num of current buffer local bufnr = ctx.bufnr
if bufnr == nil then
bufnr = vim.uri_to_bufnr(result.uri)
end
local fname = vim.api.nvim_buf_get_name(bufnr)
local uri = vim.uri_from_fname(fname)
if uri ~= result.uri then
log("not same buf", ctx, result.uri, bufnr, vim.fn.bufnr())
return
end
-- local winid = vim.fn.win_getid(vim.fn.winnr()) if not vim.api.nvim_buf_is_loaded(bufnr) then
-- local winid = vim.api.nvim_get_current_win() log("buf not loaded")
bufnr = vim.api.nvim_get_current_buf() return
local total_num = vim.fn.getbufinfo(bufnr)[1].linecount
-- local total_num = vim.fn.getbufinfo(vim.fn.winbufnr(winid))[1].linecount
-- window size of current buffer
local stats = vim.api.nvim_list_uis()[1] end
local wwidth = stats.width;
local wheight = stats.height;
-- local wwidth = vim.fn.winwidth(winid) trace('schedule callback', result, ctx, config)
-- local wheight = vim.fn.winheight(winid) -- trace(result, bufnr)
if total_num <= wheight then
return
end
if _NG_VT_NS == nil then
_NG_VT_NS = vim.api.nvim_create_namespace("navigator_lua")
end
vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1) if result == nil or result.diagnostics == nil or #result.diagnostics == 0 then
if total_num <= wheight then local diag_cnt = get_count(bufnr, [[Error]]) + get_count(bufnr, [[Warning]])
first_line = 0 if diag_cnt == 0 and _NG_VT_DIAG_NS ~= nil then
end log("great no errors")
local pos = {} vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_DIAG_NS, 0, -1)
-- pos of virtual text
for _, diag in pairs(result.diagnostics) do
if diag.range and diag.range.start and diag.range.start.line then
local p = diag.range.start.line
p = util.round(p * wheight / math.max(wheight, total_num))
if pos[#pos] and pos[#pos].line == p then
pos[#pos] = {
line = p,
sign = _NgConfigValues.lsp.diagnostic_scrollbar_sign[2],
severity = diag.severity
}
else
table.insert(pos, {
line = p,
sign = _NgConfigValues.lsp.diagnostic_scrollbar_sign[1],
severity = diag.severity
})
end end
return
end end
trace("pos", pos, diag.range.start)
end
for i, s in pairs(pos) do -- total line num of current buffer
local hl = 'ErrorMsg'
if s.severity > 1 then -- local winid = vim.fn.win_getid(vim.fn.winnr())
hl = 'WarningMsg' -- local winid = vim.api.nvim_get_current_win()
local total_num = vim.api.nvim_buf_line_count(bufnr)
-- local total_num = vim.fn.getbufinfo(vim.fn.winbufnr(winid))[1].linecount
-- window size of current buffer
local stats = vim.api.nvim_list_uis()[1]
-- local wwidth = stats.width;
local wheight = stats.height;
if total_num <= wheight then
return
end end
local l = s.line + first_line if _NG_VT_DIAG_NS == nil then
if l > total_num then _NG_VT_DIAG_NS = vim.api.nvim_create_namespace("navigator_lua_diag")
l = total_num
end end
vim.api.nvim_buf_set_extmark(bufnr, _NG_VT_NS, l, -1,
{virt_text = {{s.sign, hl}}, virt_text_pos = 'right_align'}) local pos = {}
end local diags = result.diagnostics
for i, _ in ipairs(diags) do
if not diags[i].range then
diags[i].range = {start = {line = diags[i].lnum}}
end
end
table.sort(diags, function(a, b)
return a.range.start.line < b.range.start.line
end)
-- pos of virtual text
for _, diag in pairs(result.diagnostics) do
local p
if not diag.range then
diag.range = {start = {line = diag.lnum}}
end
if diag.range and diag.range.start and diag.range.start.line then
p = diag.range.start.line
p = util.round(p * wheight / math.max(wheight, total_num))
if pos[#pos] and pos[#pos].line == p then
local bar = ''
if pos[#pos] == _NgConfigValues.lsp.diagnostic_scrollbar_sign[2] then
bar = ''
end
pos[#pos] = {line = p, sign = bar, severity = math.min(diag.severity, pos[#pos].severity)}
else
table.insert(pos, {
line = p,
sign = _NgConfigValues.lsp.diagnostic_scrollbar_sign[1],
severity = diag.severity
})
end
end
trace("pos, line:", p, diag.severity, diag.range)
end
if not vim.tbl_isempty(pos) then
vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_DIAG_NS, 0, -1)
end
for i, s in pairs(pos) do
local hl = 'ErrorMsg'
if s.severity == 2 then
hl = 'WarningMsg'
else
hl = 'DiagnosticInfo'
end
local l = s.line + first_line
if l > total_num then
l = total_num
end
trace("add pos", s, bufnr)
vim.api.nvim_buf_set_extmark(bufnr, _NG_VT_DIAG_NS, l, -1,
{virt_text = {{s.sign, hl}}, virt_text_pos = 'right_align'})
end
end, 10) -- defer in 10ms
end
local update_err_marker_async = function()
local debounce = require'navigator.debounce'.debounce_trailing
return debounce(20, error_marker)
end end
local diag_hdlr = mk_handler(function(err, result, ctx, config) local diag_hdlr = mk_handler(function(err, result, ctx, config)
trace(result)
require"navigator.lspclient.highlight".diagnositc_config_sign()
if err ~= nil then if err ~= nil then
log(err, config) log(err, config, result)
return return
end end
if vim.fn.mode() ~= 'n' and config.update_in_insert == false then
local mode = vim.api.nvim_get_mode().mode
if mode ~= 'n' and config.update_in_insert == false then
log("skip in insert mode") log("skip in insert mode")
return return
end end
@ -116,23 +175,26 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config)
if diagnostic_list[ft] == nil then if diagnostic_list[ft] == nil then
diagnostic_list[vim.bo.filetype] = {} diagnostic_list[vim.bo.filetype] = {}
end end
-- vim.lsp.diagnostic.clear(vim.fn.bufnr(), client.id, nil, nil)
local client_id = ctx.client_id
-- not sure if I should do this hack
if vim.tbl_isempty(result.diagnostics) then
if vim.api.nvim_buf_is_loaded(ctx.bufnr) then
-- diagnostic.reset(ctx.client_id)
-- clear_diag_VT(ctx.bufnr)
end
return
end
if util.nvim_0_6() then if util.nvim_0_6() then
trace(err, result, ctx, config)
vim.lsp.diagnostic.on_publish_diagnostics(err, result, ctx, config) vim.lsp.diagnostic.on_publish_diagnostics(err, result, ctx, config)
else else
log("old version of lsp nvim 050") log("old version of lsp nvim 050")
vim.lsp.diagnostic.on_publish_diagnostics(err, _, result, ctx.client_id, _, config) vim.lsp.diagnostic.on_publish_diagnostics(err, _, result, ctx.client_id, _, config)
end end
local uri = result.uri local uri = result.uri
if err then -- trace("diag: ", mode, result, ctx, config)
log("diag", err, result)
return
end
if vim.fn.mode() ~= 'n' and config.update_in_insert == false then
log("skip in insert mode")
return
end
trace("diag: ", vim.fn.mode(), result, ctx, config)
if result and result.diagnostics then if result and result.diagnostics then
local item_list = {} local item_list = {}
for _, v in ipairs(result.diagnostics) do for _, v in ipairs(result.diagnostics) do
@ -155,6 +217,7 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config)
local bufnr1 = vim.uri_to_bufnr(uri) local bufnr1 = vim.uri_to_bufnr(uri)
if not vim.api.nvim_buf_is_loaded(bufnr1) then if not vim.api.nvim_buf_is_loaded(bufnr1) then
if _NgConfigValues.diagnostic_load_files then if _NgConfigValues.diagnostic_load_files then
-- print('load buffers')
vim.fn.bufload(bufnr1) -- this may slow down the neovim vim.fn.bufload(bufnr1) -- this may slow down the neovim
local pos = v.range.start local pos = v.range.start
local row = pos.line local row = pos.line
@ -178,10 +241,12 @@ local diag_hdlr = mk_handler(function(err, result, ctx, config)
result.uri = uri result.uri = uri
end end
error_marker(result, ctx.client_id) local marker = update_err_marker_async()
marker(result, ctx, config)
else else
vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1) trace("great, no diag errors")
_NG_VT_NS = nil vim.api.nvim_buf_clear_namespace(0, _NG_VT_DIAG_NS, 0, -1)
_NG_VT_DIAG_NS = nil
end end
end) end)
@ -196,9 +261,7 @@ local diagnostic_cfg = {
-- and on, using buffer local variables -- and on, using buffer local variables
signs = true, signs = true,
update_in_insert = _NgConfigValues.lsp.diagnostic_update_in_insert or false, update_in_insert = _NgConfigValues.lsp.diagnostic_update_in_insert or false,
severity_sort = function(a, b) severity_sort = {reverse = true}
return a.severity < b.severity
end
} }
if _NgConfigValues.lsp.diagnostic_virtual_text == false then if _NgConfigValues.lsp.diagnostic_virtual_text == false then
@ -209,24 +272,13 @@ end
M.diagnostic_handler = vim.lsp.with(diag_hdlr, diagnostic_cfg) M.diagnostic_handler = vim.lsp.with(diag_hdlr, diagnostic_cfg)
M.hide_diagnostic = function() M.hide_diagnostic = function()
if _NG_VT_NS then if _NG_VT_DIAG_NS then
vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1) vim.api.nvim_buf_clear_namespace(0, _NG_VT_DIAG_NS, 0, -1)
_NG_VT_NS = nil _NG_VT_DIAG_NS = nil
end end
end end
M.show_diagnostic = function() M.show_diagnostic = function()
vim.lsp.diagnostic.get_all()
local bufs = vim.api.nvim_list_bufs()
for _, buf in ipairs(bufs) do
local bname = vim.fn.bufname(buf)
if #bname > 0 and not util.exclude(bname) then
if vim.api.nvim_buf_is_loaded(buf) then
vim.lsp.diagnostic.get(buf, nil)
end
end
end
if diagnostic_list[vim.bo.filetype] ~= nil then if diagnostic_list[vim.bo.filetype] ~= nil then
-- log(diagnostic_list[vim.bo.filetype]) -- log(diagnostic_list[vim.bo.filetype])
-- vim.fn.setqflist({}, " ", {title = "LSP", items = diagnostic_list[vim.bo.filetype]}) -- vim.fn.setqflist({}, " ", {title = "LSP", items = diagnostic_list[vim.bo.filetype]})
@ -239,56 +291,81 @@ M.show_diagnostic = function()
end end
-- log(display_items) -- log(display_items)
if #display_items > 0 then if #display_items > 0 then
gui.new_list_view({ local listview = gui.new_list_view({
items = display_items, items = display_items,
api = _NgConfigValues.icons.diagnostic_file .. _NgConfigValues.icons.diagnostic_head api = _NgConfigValues.icons.diagnostic_file .. _NgConfigValues.icons.diagnostic_head
.. " Diagnostic ", .. " Diagnostic ",
enable_preview_edit = true enable_preview_edit = true
}) })
trace("new buffer", listview.bufnr)
vim.api.nvim_buf_add_highlight(listview.bufnr, -1, 'Title', 0, 0, -1)
end end
end end
end end
-- set quickfix win -- set loc list win
M.set_diag_loclist = function() M.set_diag_loclist = function()
local bufnr = vim.api.nvim_get_current_buf()
local diag_cnt = get_count(bufnr, [[Error]]) + get_count(bufnr, [[Warning]])
if diag_cnt == 0 then
log("great, no errors!")
return
end
local clients = vim.lsp.buf_get_clients(0)
local cfg = {open = diag_cnt > 0}
for _, client in pairs(clients) do
cfg.client_id = client['id']
break
end
if not vim.tbl_isempty(vim.lsp.buf_get_clients(0)) then if not vim.tbl_isempty(vim.lsp.buf_get_clients(0)) then
local err_cnt = vim.lsp.diagnostic.get_count(0, [[Error]]) local err_cnt = get_count(0, [[Error]])
if err_cnt > 0 and _NgConfigValues.lsp.disply_diagnostic_qf then if err_cnt > 0 and _NgConfigValues.lsp.disply_diagnostic_qf then
vim.lsp.diagnostic.set_loclist() if diagnostic.set_loclist then
diagnostic.set_loclist(cfg)
else
cfg.namespaces = diagnostic.get_namespace(nil)
diagnostic.setloclist(cfg)
end
else else
vim.cmd("lclose") vim.cmd("lclose")
end end
end end
end end
local function clear_diag_VT() -- important for clearing out when no more errors
vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1)
_NG_VT_NS = nil
end
-- TODO: callback when scroll -- TODO: callback when scroll
function M.update_err_marker() function M.update_err_marker()
if _NG_VT_NS == nil then trace("update err marker", _NG_VT_DIAG_NS)
if _NG_VT_DIAG_NS == nil then
-- nothing to update -- nothing to update
return return
end end
local bufnr = vim.api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local diag_cnt = vim.lsp.diagnostic.get_count(bufnr, [[Error]]) local diag_cnt = get_count(bufnr, [[Error]]) + get_count(bufnr, [[Warning]])
+ vim.lsp.diagnostic.get_count(bufnr, [[Warning]]) + get_count(bufnr, [[Info]]) + get_count(bufnr, [[Hint]])
if diag_cnt == 0 and _NG_VT_NS ~= nil then
vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_NS, 0, -1) -- redraw
if diag_cnt == 0 and _NG_VT_DIAG_NS ~= nil then
vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_DIAG_NS, 0, -1)
trace("no errors")
return return
end end
-- redraw vim.api.nvim_buf_clear_namespace(bufnr, _NG_VT_DIAG_NS, 0, -1)
vim.api.nvim_buf_clear_namespace(0, _NG_VT_NS, 0, -1) local errors = diagnostic.get(bufnr)
local errors = vim.lsp.diagnostic.get(bufnr)
if #errors == 0 then if #errors == 0 then
trace("errors", errors)
return return
end end
local result = {diagnostics = errors, uri = errors[1].uri} local uri = vim.uri_from_bufnr(bufnr)
error_marker(result) local result = {diagnostics = errors, uri = errors[1].uri or uri}
trace(result)
local marker = update_err_marker_async()
marker(result, {bufnr = bufnr, method = 'textDocument/publishDiagnostics'})
end end
-- TODO: update the marker -- TODO: update the marker
@ -300,4 +377,14 @@ if _NgConfigValues.lsp.diagnostic_scrollbar_sign then
vim.cmd [[autocmd WinScrolled * lua require'navigator.diagnostics'.update_err_marker()]] vim.cmd [[autocmd WinScrolled * lua require'navigator.diagnostics'.update_err_marker()]]
end end
function M.get_line_diagnostic()
local lnum = vim.api.nvim_win_get_cursor(0)[1] - 1
return diagnostic.get(vim.api.nvim_get_current_buf(), {lnum = lnum})
end
function M.show_line_diagnostics()
local lnum = vim.api.nvim_win_get_cursor(0)[1] - 1
diagnostic.show_line_diagnostics({border = 'single'}, vim.api.nvim_get_current_buf(), lnum)
end
return M return M

@ -58,6 +58,11 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y
uri = uri, uri = uri,
allow_edit = opts.enable_edit allow_edit = opts.enable_edit
} }
if _NgConfigValues.external then
win_opts.external = true
win_opts.relative = nil
end
-- win_opts.items = contents -- win_opts.items = contents
win_opts.hl_line = opts.lnum - display_range.start.line win_opts.hl_line = opts.lnum - display_range.start.line
if win_opts.hl_line < 0 then if win_opts.hl_line < 0 then
@ -84,7 +89,8 @@ function M._preview_location(opts) -- location, width, pos_x, pos_y
border = opts.border, border = opts.border,
display_range = win_opts.display_range, display_range = win_opts.display_range,
hl_line = win_opts.hl_line, hl_line = win_opts.hl_line,
allow_edit = win_opts.allow_edit allow_edit = win_opts.allow_edit,
external = win_opts.external
}) })
return w return w
end end
@ -125,7 +131,7 @@ function M.new_list_view(opts)
if config.width ~= nil and config.width > 0.3 and config.width < 0.99 then if config.width ~= nil and config.width > 0.3 and config.width < 0.99 then
width = math.floor(wwidth * config.width) width = math.floor(wwidth * config.width)
end end
width = math.min(120, width) width = math.min(120, width, opts.width or 120)
local wheight = math.floor(1 + api.nvim_get_option("lines") local wheight = math.floor(1 + api.nvim_get_option("lines")
* (_NgConfigValues.height + _NgConfigValues.preview_height)) * (_NgConfigValues.height + _NgConfigValues.preview_height))
local pheight = math.max(_NgConfigValues.preview_lines, math.floor( local pheight = math.max(_NgConfigValues.preview_lines, math.floor(
@ -173,6 +179,10 @@ function M.new_list_view(opts)
if transparency == 100 then if transparency == 100 then
transparency = nil transparency = nil
end end
local ext = _NgConfigValues.external
if ext then
opts.relative = nil
end
return ListView:new({ return ListView:new({
loc = loc, loc = loc,
prompt = prompt, prompt = prompt,
@ -186,6 +196,7 @@ function M.new_list_view(opts)
-- data = display_data, -- data = display_data,
data = data, data = data,
border = border, border = border,
external = ext,
on_confirm = opts.on_confirm or function(item, split_opts) on_confirm = opts.on_confirm or function(item, split_opts)
log(split_opts) log(split_opts)
split_opts = split_opts or {} split_opts = split_opts or {}
@ -213,6 +224,7 @@ function M.new_list_view(opts)
offset_x = 0, offset_x = 0,
offset_y = offset_y, offset_y = offset_y,
border = border, border = border,
external = ext,
enable_edit = opts.enable_preview_edit or false enable_edit = opts.enable_preview_edit or false
}) })
end end

@ -1,7 +1,7 @@
local log = require"navigator.util".log local log = require"navigator.util".log
_LoadedClients = {} _LoadedClients = {}
local loader = nil local loader = nil
packer_plugins = packer_plugins or nil -- suppress warnings local packer_plugins = packer_plugins or nil -- suppress warnings
-- packer only -- packer only
if packer_plugins ~= nil then -- packer install if packer_plugins ~= nil then -- packer install

@ -14,6 +14,7 @@ local M = {}
M.on_attach = function(client, bufnr) M.on_attach = function(client, bufnr)
local uri = vim.uri_from_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr)
if uri == "file://" or uri == "file:///" or #uri < 11 then if uri == "file://" or uri == "file:///" or #uri < 11 then
log("skip for float buffer", uri) log("skip for float buffer", uri)
return {error = "invalid file", result = nil} return {error = "invalid file", result = nil}
@ -24,7 +25,7 @@ M.on_attach = function(client, bufnr)
diagnostic_map(bufnr) diagnostic_map(bufnr)
-- add highlight for Lspxxx -- add highlight for Lspxxx
require"navigator.lspclient.highlight".add_highlight() require"navigator.lspclient.highlight".add_highlight()
require"navigator.lspclient.highlight".diagnositc_config_sign()
api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc") api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc")
require("navigator.lspclient.mapping").setup({ require("navigator.lspclient.mapping").setup({

@ -518,6 +518,11 @@ local function setup(user_opts)
wait_lsp_startup(ft, retry, lsp_opts) wait_lsp_startup(ft, retry, lsp_opts)
--- if code line enabled
if _NgConfigValues.lsp.code_lens then
require("navigator.codelens").setup()
end
_LoadedClients[ft] = true _LoadedClients[ft] = true
-- _LoadedClients[ft] = vim.tbl_extend("keep", _LoadedClients[ft] or {}, {ft}) -- _LoadedClients[ft] = vim.tbl_extend("keep", _LoadedClients[ft] or {}, {ft})

@ -1,20 +1,62 @@
local M = {} local M = {}
local log = require"navigator.util".log
local api = vim.api local api = vim.api
-- lsp sign          ﮻         ﯭ        ﳀ   -- lsp sign          ﮻         ﯭ        ﳀ  
function M.diagnositc_config_sign() function M.diagnositc_config_sign()
vim.fn.sign_define('LspDiagnosticsSignError', if M.configed then
{text = '', texthl = 'LspDiagnosticsSignError', linehl = '', numhl = ''}) return
vim.fn.sign_define('LspDiagnosticsSignWarning', end
{text = '', texthl = 'LspDiagnosticsSignWarning', linehl = '', numhl = ''}) local icons = _NgConfigValues.icons
vim.fn.sign_define('LspDiagnosticsSignInformation', {
text = '', local sign_name = "NavigatorLightBulb"
texthl = 'LspDiagnosticsSignInformation', if vim.fn.sign_getdefined(sign_name).text == nil then
linehl = '',
numhl = '' vim.fn
}) .sign_define(sign_name, {text = icons.code_action_icon, texthl = "LspDiagnosticsSignHint"})
vim.fn.sign_define('LspDiagnosticsSignHint',
{text = '💡', texthl = 'LspDiagnosticsSignHint', linehl = '', numhl = ''}) sign_name = "NavigatorCodeLensLightBulb"
vim.fn.sign_define(sign_name,
{text = icons.code_lens_action_icon, texthl = "LspDiagnosticsSignHint"})
end
local e, w, i, h = icons.diagnostic_err, icons.diagnostic_warn, icons.diagnostic_info,
icons.diagnostic_hint
if vim.diagnostic ~= nil then
local t = vim.fn.sign_getdefined('DiagnosticSignWarn')
if vim.tbl_isempty(t) or t[1].text == "W " and icons.icons == true then
vim.fn.sign_define('DiagnosticSignError',
{text = e, texthl = 'DiagnosticError', linehl = '', numhl = ''})
vim.fn.sign_define('DiagnosticSignWarn',
{text = w, texthl = 'DiagnosticWarn', linehl = '', numhl = ''})
vim.fn.sign_define('DiagnosticSignInfo',
{text = i, texthl = 'DiagnosticInfo', linehl = '', numhl = ''})
vim.fn.sign_define('DiagnosticSignHint',
{text = h, texthl = 'DiagnosticHint', linehl = '', numhl = ''})
t = vim.fn.sign_getdefined('DiagnosticSignWarn')
log('*** t ', t, "diagnostic add sign")
end
else
local t = vim.fn.sign_getdefined('LspDiagnosticSignWarn')
if vim.tbl_isempty(t) or t[1].text == "W " and icons.icons == true then
vim.fn.sign_define('LspDiagnosticsSignError',
{text = e, texthl = 'LspDiagnosticsSignError', linehl = '', numhl = ''})
vim.fn.sign_define('LspDiagnosticsSignWarning',
{text = w, texthl = 'LspDiagnosticsSignWarning', linehl = '', numhl = ''})
vim.fn.sign_define('LspDiagnosticsSignInformation', {
text = i,
texthl = 'LspDiagnosticsSignInformation',
linehl = '',
numhl = ''
})
vim.fn.sign_define('LspDiagnosticsSignHint',
{text = h, texthl = 'LspDiagnosticsSignHint', linehl = '', numhl = ''})
end
end
M.configed = true
end end
function M.add_highlight() function M.add_highlight()
@ -24,8 +66,12 @@ function M.add_highlight()
api.nvim_command("hi! link LspDiagnosticsUnderlineWarning SpellRare") api.nvim_command("hi! link LspDiagnosticsUnderlineWarning SpellRare")
api.nvim_command("hi! link LspDiagnosticsUnderlineInformation SpellRare") api.nvim_command("hi! link LspDiagnosticsUnderlineInformation SpellRare")
api.nvim_command("hi! link LspDiagnosticsUnderlineHint SpellRare") api.nvim_command("hi! link LspDiagnosticsUnderlineHint SpellRare")
api.nvim_command("hi def link NGPreviewTitle Title")
api.nvim_command("hi! link DiagnosticUnderlineError SpellBad")
api.nvim_command("hi! link DiagnosticUnderlineWarning SpellRare")
api.nvim_command("hi! link DiagnosticUnderlineInformation SpellRare")
api.nvim_command("hi! link DiagnosticUnderlineHint SpellRare")
api.nvim_command("hi def link NGPreviewTitle Title")
local colors = { local colors = {
{'#aefe00', '#aede00', '#aebe00', '#4e7efe'}, {'#ff00e0', '#df00e0', '#af00e0', '#fedefe'}, {'#aefe00', '#aede00', '#aebe00', '#4e7efe'}, {'#ff00e0', '#df00e0', '#af00e0', '#fedefe'},
{'#1000ef', '#2000df', '#2000cf', '#f0f040'}, {'#d8a8a3', '#c8a8a3', '#b8a8a3', '#4e2c33'}, {'#1000ef', '#2000df', '#2000cf', '#f0f040'}, {'#d8a8a3', '#c8a8a3', '#b8a8a3', '#4e2c33'},

@ -31,7 +31,7 @@ local key_maps = {
{key = "<Leader>go", func = "outgoing_calls()"}, {key = "<Leader>go", func = "outgoing_calls()"},
{key = "gi", func = "implementation()"}, {key = "gi", func = "implementation()"},
{key = "<Space>D", func = "type_definition()"}, {key = "<Space>D", func = "type_definition()"},
{key = "gL", func = "diagnostic.show_line_diagnostics( { border = 'single' })"}, {key = "gL", func = "require('navigator.diagnostics').show_line_diagnostics()"},
{key = "gG", func = "require('navigator.diagnostics').show_diagnostic()"}, {key = "gG", func = "require('navigator.diagnostics').show_diagnostic()"},
{key = "]d", func = "diagnostic.goto_next({ border = 'single' })"}, {key = "]d", func = "diagnostic.goto_next({ border = 'single' })"},
{key = "[d", func = "diagnostic.goto_prev({ border = 'single' })"}, {key = "[d", func = "diagnostic.goto_prev({ border = 'single' })"},
@ -119,7 +119,11 @@ local function set_mapping(user_opts)
if string.find(value.func, "require") then if string.find(value.func, "require") then
f = "<Cmd>lua " .. value.func .. "<CR>" f = "<Cmd>lua " .. value.func .. "<CR>"
elseif string.find(value.func, "diagnostic") then elseif string.find(value.func, "diagnostic") then
f = "<Cmd>lua vim.lsp." .. value.func .. "<CR>" local diagnostic = '<Cmd>lua vim.'
if vim.lsp.diagnostic ~= nil then
diagnostic = '<Cmd>lua vim.lsp.'
end
f = diagnostic .. value.func .. "<CR>"
elseif string.find(value.func, "vim.") then elseif string.find(value.func, "vim.") then
f = "<Cmd>lua " .. value.func .. "<CR>" f = "<Cmd>lua " .. value.func .. "<CR>"
end end
@ -241,9 +245,11 @@ function M.setup(user_opts)
require"navigator.diagnostics".diagnostic_handler require"navigator.diagnostics".diagnostic_handler
-- TODO: when active signature merge to neovim, remove this setup: -- TODO: when active signature merge to neovim, remove this setup:
local hassig, sig = pcall(require, "lsp_signature")
if hassig then if _NgConfigValues.signature_help_cfg then
if _NgConfigValues.signature_help_cfg then log("setup signature from navigator")
local hassig, sig = pcall(require, "lsp_signature")
if hassig then
sig.setup(_NgConfigValues.signature_help_cfg) sig.setup(_NgConfigValues.signature_help_cfg)
end end
else else

@ -40,7 +40,7 @@ local function check_lhs(text, symbol)
return false return false
end end
if s < eq and eq ~= eq2 then if s < eq and eq ~= eq2 then
log(symbol, "modified") trace(symbol, "modified")
end end
if eq == eq3 + 1 then if eq == eq3 + 1 then
return false return false

@ -1,4 +1,4 @@
parameter -- [[ -- parameter
{ {
position = { position = {
character = 6, character = 6,
@ -7,8 +7,7 @@ parameter
textDocument = { textDocument = {
uri = "file:///Users/username/lsp_test/go/interface.go" uri = "file:///Users/username/lsp_test/go/interface.go"
} }
} } ]]
--[[ -- incomming/outgoing --[[ -- incomming/outgoing
@ -103,8 +102,8 @@ dir from result { {
-- locations/reference from lsp
-- [[ locations/reference from lsp -- [[
{ { { {
range = { range = {
["end"] = { ["end"] = {
@ -130,7 +129,7 @@ dir from result { {
}, },
uri = "file:///Users/username/lsp-test/go/interface.go" uri = "file:///Users/username/lsp-test/go/interface.go"
} } } }
--]] ]] --
-- definition -- definition
@ -1124,3 +1123,173 @@ definition.lua:9: { {
} }
} }
} } } }
-- rust code lens
{ {
result = { {
command = {
arguments = { {
args = {
cargoArgs = { "run", "--package", "hello", "--bin", "hello" },
cargoExtraArgs = {},
executableArgs = {},
workspaceRoot = "/Users/ray.xu/lsp_test/rust"
},
kind = "cargo",
label = "run hello",
location = {
targetRange = {
end = {
character = 1,
line = 68
},
start = {
character = 0,
line = 45
}
},
targetSelectionRange = {
end = {
character = 7,
line = 45
},
start = {
character = 3,
line = 45
}
},
targetUri = "file:///Users/ray.xu/lsp_test/rust/src/main.rs"
}
} },
command = "rust-analyzer.runSingle",
title = "▶︎ Run "
},
range = {
end = {
character = 7,
line = 45
},
start = {
character = 3,
line = 45
}
}
}, {
command = {
arguments = { {
args = {
cargoArgs = { "run", "--package", "hello", "--bin", "hello" },
cargoExtraArgs = {},
executableArgs = {},
workspaceRoot = "/Users/ray.xu/lsp_test/rust"
},
kind = "cargo",
label = "run hello",
location = {
targetRange = {
end = {
character = 1,
line = 68
},
start = {
character = 0,
line = 45
}
},
targetSelectionRange = {
end = {
character = 7,
line = 45
},
start = {
character = 3,
line = 45
}
},
targetUri = "file:///Users/ray.xu/lsp_test/rust/src/main.rs"
}
} },
command = "rust-analyzer.debugSingle",
title = "Debug"
},
range = {
end = {
character = 7,
line = 45
},
start = {
character = 3,
line = 45
}
}
}, {
data = {
impls = {
position = {
character = 6,
line = 2
},
textDocument = {
uri = "file:///Users/ray.xu/lsp_test/rust/src/main.rs"
}
}
},
range = {
end = {
character = 10,
line = 2
},
start = {
character = 6,
line = 2
}
}
}, {
data = {
impls = {
position = {
character = 7,
line = 28
},
textDocument = {
uri = "file:///Users/ray.xu/lsp_test/rust/src/main.rs"
}
}
},
range = {
end = {
character = 10,
line = 28
},
start = {
character = 7,
line = 28
}
}
}, {
data = {
impls = {
position = {
character = 7,
line = 31
},
textDocument = {
uri = "file:///Users/ray.xu/lsp_test/rust/src/main.rs"
}
}
},
range = {
end = {
character = 10,
line = 31
},
start = {
character = 7,
line = 31
}
}
} }
} }

@ -3,6 +3,9 @@ local trace = require"guihua.log".trace
local M = {} local M = {}
local clone = require'guihua.util'.clone local clone = require'guihua.util'.clone
local function filename(url) local function filename(url)
if url == nil then
return ''
end
return url:match("^.+/(.+)$") or url return url:match("^.+/(.+)$") or url
end end

@ -393,4 +393,18 @@ function M.partial(func, arg)
end)) end))
end end
-- alternatively: use vim.notify("namespace does not exist or is anonymous", vim.log.levels.ERROR)
function M.warn(msg)
vim.api.nvim_echo({{"WRN: " .. msg, "WarningMsg"}}, true, {})
end
function M.error(msg)
vim.api.nvim_echo({{"ERR: " .. msg, "ErrorMsg"}}, true, {})
end
function M.info(msg)
vim.api.nvim_echo({{"Info: " .. msg}}, true, {})
end
return M return M

Loading…
Cancel
Save