codelens support, foldelsp

pull/43/head
ray-x 3 years ago
parent 48faeb69ab
commit 58ac955777

@ -23,9 +23,11 @@ and emoji is not shown.
#### Example: C++ defination
Another example for C++
C++ example: search reference and defination
![cpp_ref](https://user-images.githubusercontent.com/1681295/119215215-8bd7a080-bb0f-11eb-82fc-8cdf1955e6e7.jpg)
You may find that a 🦕 dinosaur(d) on the line of `Rectangle rect;` which means there is a defination (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 defination is inside function main().
#### Golang struct type
Struct type references in multiple Go ﳑ files
@ -33,8 +35,8 @@ 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 a large
project where class/function definition is too long to fit into the preview window. Also provides a birdview of where the
variable is
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:
- Referenced
- Modified
- Defined
@ -78,7 +80,9 @@ in the same line). Using treesitter for file preview highlighter etc
- ccls call hierarchy (Non-standard `ccls/call` API) supports
- Syntax folding based on treesitter folding algorithm
- Syntax folding based on treesitter folding algorithm. (It behaves similar to vs-code)
- LSP Code Action, Code Lens, Code lens action
# Why a new plugin
@ -219,6 +223,9 @@ require'navigator'.setup({
lsp = {
format_on_save = true, -- set to false to disasble lsp code format on save (if you are using prettier/efm/formater etc)
disable_format_ft = {"sqls", "sumneko_lua", "gopls"}, -- a list of lsp not enable auto-format (e.g. if you using efm or vim-codeformat etc), empty by default
disable_lsp = {'pylsd', 'sqlls'}, -- a list of lsp server disabled for your project, e.g. denols and tsserver you may
-- only want to enable one lsp server
diag_scroll_bar_sign = {'▃', '█'}, -- experimental: diagnostic status in scroll bar area; set to nil to disable the diagnostic sign,
-- for other style, set to {'╍', 'ﮆ'} or {'-', '='}

@ -0,0 +1,3 @@
function! foldlsp#foldexpr()
return luaeval(printf('require"navigator.foldlsp".get_fold_indic(%d)', v:lnum))
endfunction

@ -15,10 +15,15 @@ _NgConfigValues = {
-- end,
ts_fold = false,
code_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true},
code_lens_action_prompt = {enable = true, sign = true, sign_priority = 40, virtual_text = true},
treesitter_analysis = true, -- treesitter variable context
transparency = 50, -- 0 ~ 100 blur the main window, 100: fully transparent, 0: opaque, set to nil to disable it
lsp = {
format_on_save = true, -- set to false to disasble lsp code format on save (if you are using prettier/efm/formater etc)
disable_format_ft = {}, -- a list of lsp not enable auto-format (e.g. if you using efm or vim-codeformat etc), empty by default
disable_lsp = {}, -- a list of lsp server disabled for your project, e.g. denols and tsserver you may
code_lens = false,
-- only want to enable one lsp server
disply_diagnostic_qf = true, -- always show quickfix if there are diagnostic errors
diag_scroll_bar_sign = {'', ''}, -- set to nil to disable, set to {'╍', 'ﮆ'} to enable diagnostic status in scroll bar area
tsserver = {
@ -35,6 +40,8 @@ _NgConfigValues = {
icons = {
-- Code action
code_action_icon = "",
-- code lens
code_lens_action_icon = "",
-- Diagnostics
diagnostic_head = '🐛',
diagnostic_head_severity_1 = "🈲",
@ -119,6 +126,12 @@ M.setup = function(cfg)
if _NgConfigValues.ts_fold == true then
require('navigator.foldts').on_attach()
end
--- if code line enabled
if _NgConfigValues.lsp.code_lens then
require("navigator.codelens").setup()
end
end
return M

@ -26,6 +26,7 @@ function code_action.code_action_handler(err, _, actions, cid, bufnr, _, customS
end
end
local apply = require('navigator.lspwrapper').apply_action
local function apply_action(action)
local action_chosen = nil
for key, value in pairs(actions) do
@ -33,26 +34,12 @@ function code_action.code_action_handler(err, _, actions, cid, bufnr, _, customS
action_chosen = value
end
end
if action_chosen == nil then
log("no match for ", action, actions)
return
end
local switch = string.format("silent b %d", bufnr)
if action_chosen.edit or type(action_chosen.command) == "table" then
if action_chosen.edit then
vim.lsp.util.apply_workspace_edit(action_chosen.edit)
end
if type(action_chosen.command) == "table" then
-- switch buff
vim.cmd(switch)
vim.lsp.buf.execute_command(action_chosen.command)
end
else
vim.cmd(switch)
vim.lsp.buf.execute_command(action_chosen)
end
trace(action_chosen)
apply(action_chosen)
end
gui.new_list_view {

@ -0,0 +1,145 @@
-- codelenses
-- https://github.com/josa42/nvim-lsp-codelenses/blob/master/lua/jg/lsp/codelenses.lua
-- https://github.com/neovim/neovim/blob/master/runtime/lua/vim/lsp/codelens.lua
local codelens = require('vim.lsp.codelens')
local log = require"navigator.util".log
local lsphelper = require "navigator.lspwrapper"
local api = vim.api
local gui = require "navigator.gui"
local M = {}
local config = require("navigator").config_values()
local sign_name = "NavigatorCodeLensLightBulb"
if vim.tbl_isempty(vim.fn.sign_getdefined(sign_name)) then
vim.fn.sign_define(sign_name,
{text = config.icons.code_lens_action_icon, texthl = "LspDiagnosticsSignHint"})
end
local sign_group = "nvcodelensaction"
local get_current_winid = require('navigator.util').get_current_winid
local code_lens_action = {}
local function _update_sign(line)
log("update sign at line ", line)
local winid = get_current_winid()
if code_lens_action[winid] == nil then
code_lens_action[winid] = {}
end
if code_lens_action[winid].lightbulb_line ~= 0 then
vim.fn.sign_unplace(sign_group, {id = code_lens_action[winid].lightbulb_line, buffer = "%"})
end
if line then
-- log("updatasign", line, sign_group, sign_name)
vim.fn.sign_place(line, sign_group, sign_name, "%",
{lnum = line + 1, priority = config.code_lens_action_prompt.sign_priority})
code_lens_action[winid].lightbulb_line = line
end
end
local function codelens_hdlr(err, _, result, client_id, bufnr)
if err then
warn("lsp code lens", vim.inspect(err))
return
end
log("codelenes result", result)
for _, v in pairs(result) do
_update_sign(v.range.start.line)
end
end
function M.setup()
vim.cmd('highlight! link LspCodeLens LspDiagnosticsHint')
vim.cmd('highlight! link LspCodeLensText LspDiagnosticsInformation')
vim.cmd('highlight! link LspCodeLensTextSign LspDiagnosticsSignInformation')
vim.cmd('highlight! link LspCodeLensTextSeparator Boolean')
vim.cmd('augroup navigator.codelenses')
vim.cmd(' autocmd!')
vim.cmd(
"autocmd BufEnter,CursorHold,InsertLeave <buffer> lua require('navigator.codelens').refresh()")
vim.cmd('augroup end')
local on_codelens = vim.lsp.handlers["textDocument/codeLens"]
vim.lsp.handlers["textDocument/codeLens"] = function(err, _, result, client_id, bufnr)
on_codelens(err, _, result, client_id, bufnr)
codelens_hdlr(err, _, result, client_id, bufnr)
end
end
M.lsp_clients = {}
function M.refresh()
assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp code action")
if not lsphelper.check_capabilities("code_lens") then
return
end
vim.lsp.codelens.refresh()
end
function M.run_action()
log("run code len action")
assert(#vim.lsp.buf_get_clients() > 0, "Must have a client running to use lsp code action")
if not lsphelper.check_capabilities("code_lens") then
return
end
local line = api.nvim_win_get_cursor(0)[1]
local bufnr = api.nvim_get_current_buf()
local lenses = codelens.get(bufnr)
if lenses == nil or #lenses == 0 then
return
end
local width = 40
local data = {"  Auto Fix <C-o> Apply <C-e> Exit"}
for i, lens in pairs(lenses) do
if lens.range.start.line == (line - 1) then
local title = lens.command.title:gsub("\r\n", "\\r\\n")
title = title:gsub("\n", "\\n")
title = string.format("[%d] %s", i, title)
table.insert(data, title)
lenses[i].display_title = title
width = math.max(width, #lens.command.title)
end
end
local apply = require('navigator.lspwrapper').apply_action
local function apply_action(action)
local action_chosen = nil
for key, value in pairs(lenses) do
if value.display_title == action then
action_chosen = value
end
end
if action_chosen == nil then
log("no match for ", action, lenses)
return
end
apply(action_chosen)
end
gui.new_list_view {
items = data,
width = width + 4,
loc = "top_center",
relative = "cursor",
rawdata = true,
data = data,
on_confirm = function(pos)
log(pos)
apply_action(pos)
end,
on_move = function(pos)
log(pos)
return pos
end
}
end
return M

@ -0,0 +1,171 @@
local log = require"navigator.util".log
local lsp = vim.lsp
local api = vim.api
local M = {}
-- TODO: per-buffer fold table?
M.current_buf_folds = {}
-- Informative table keeping track of language servers that implement textDocument/foldingRange.
-- Not used at runtime (capability is resolved dynamically)
M.servers_supporting_folding = {
pylsp = true,
pyright = false,
sumneko_lua = true,
texlab = true,
clangd = false,
gopls = true,
julials = false
}
M.active_folding_clients = {}
function M.on_attach()
M.setup_plugin()
M.update_folds()
end
function M.setup_plugin()
api.nvim_command("augroup FoldingCommand")
api.nvim_command("autocmd! * <buffer>")
api.nvim_command("autocmd BufEnter <buffer> lua require'navigator.foldlsp'.update_folds()")
api.nvim_command("autocmd BufWritePost <buffer> lua require'navigator.foldlsp'.update_folds()")
api.nvim_command("augroup end")
-- vim.cmd([[
--
-- function! folding_nvim#foldexpr()
-- return luaeval(printf('require"navigator.foldlsp".get_fold_indic(%d)', v:lnum))
-- endfunction
--
-- ]])
local clients = vim.lsp.buf_get_clients()
for _, client in pairs(clients) do
local client_id = client['id']
if M.active_folding_clients[client_id] == nil then
local server_supports_folding = client['server_capabilities']['foldingRangeProvider'] or false
-- if not server_supports_folding then
-- api.nvim_command(string.format('echom "lsp-folding: %s does not provide folding requests"',
-- client['name']))
-- end
M.active_folding_clients[client_id] = server_supports_folding
end
end
print(vim.inspect(M.active_folding_clients))
end
function M.update_folds()
local current_window = api.nvim_get_current_win()
local in_diff_mode = api.nvim_win_get_option(current_window, 'diff')
if in_diff_mode then
-- In diff mode, use diff folding.
api.nvim_win_set_option(current_window, 'foldmethod', 'diff')
else
local clients = lsp.buf_get_clients(0)
for client_id, client in pairs(clients) do
if M.active_folding_clients[client_id] then
-- XXX: better to pass callback in this method or add it directly in the config?
-- client.config.callbacks['textDocument/foldingRange'] = M.fold_handler
local current_bufnr = api.nvim_get_current_buf()
local params = {uri = vim.uri_from_bufnr(current_bufnr)}
client.request('textDocument/foldingRange', {textDocument = params}, M.fold_handler,
current_bufnr)
end
end
end
end
function M.debug_folds()
for _, table in ipairs(M.current_buf_folds) do
local start_line = table['startLine']
local end_line = table['endLine']
log('startline', start_line, 'endline', end_line)
end
end
function M.fold_handler(err, method, result, client, bufnr)
-- params: err, method, result, client_id, bufnr
-- XXX: handle err?
if err or result == nil or #result == 0 then
print(err, method, client)
return
end
M.debug_folds()
local current_bufnr = api.nvim_get_current_buf()
-- Discard the folding result if buffer focus has changed since the request was
-- done.
if current_bufnr == bufnr then
for _, fold in ipairs(result) do
fold['startLine'] = M.adjust_foldstart(fold['startLine'])
fold['endLine'] = M.adjust_foldend(fold['endLine'])
end
table.sort(result, function(a, b)
return a['startLine'] < b['startLine']
end)
M.current_buf_folds = result
local current_window = api.nvim_get_current_win()
api.nvim_win_set_option(current_window, 'foldmethod', 'expr')
api.nvim_win_set_option(current_window, 'foldexpr', 'foldlsp#foldexpr()')
end
end
function M.adjust_foldstart(line_no)
return line_no + 1
end
function M.adjust_foldend(line_no)
local bufnr = api.nvim_get_current_buf()
local filetype = api.nvim_buf_get_option(bufnr, 'filetype')
if filetype == 'lua' then
return line_no + 2
else
return line_no + 1
end
end
function M.get_fold_indic(lnum)
local fold_level = 0
local is_foldstart = false
local is_foldend = false
for _, table in ipairs(M.current_buf_folds) do
local start_line = table['startLine']
local end_line = table['endLine']
-- can exit early b/c folds get pre-orderered manually
if lnum < start_line then
break
end
if lnum >= start_line and lnum <= end_line then
fold_level = fold_level + 1
if lnum == start_line then
is_foldstart = true
end
if lnum == end_line then
is_foldend = true
end
end
end
if is_foldend and is_foldstart then
-- If line marks both start and end of folds (like ``else`` statement),
-- merge the two folds into one by returning the current foldlevel
-- without any marker.
return fold_level
elseif is_foldstart then
return string.format(">%d", fold_level)
elseif is_foldend then
return string.format("<%d", fold_level)
else
return fold_level
end
end
return M

@ -161,7 +161,7 @@ function M.get_fold_indic(lnum)
local buf = api.nvim_get_current_buf()
local levels = folds_levels(buf) or {}
log(lnum, levels[lnum])
-- log(lnum, levels[lnum]) -- TODO: comment it out in master
return levels[lnum] or "0"
end

@ -303,7 +303,7 @@ local function load_cfg(ft, client, cfg, loaded)
-- need to verify the lsp server is up
end
local function wait_lsp_startup(ft, retry, lsp_opts)
local function wait_lsp_startup(ft, retry, user_lsp_opts)
retry = retry or false
local clients = vim.lsp.get_active_clients() or {}
local loaded = {}
@ -316,13 +316,18 @@ local function wait_lsp_startup(ft, retry, lsp_opts)
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
if user_lsp_opts[lspclient] ~= nil and user_lsp_opts[lspclient].filetypes ~= nil then
if not vim.tbl_contains(user_lsp_opts[lspclient].filetypes, ft) then
trace("ft", ft, "disabled for", lspclient)
goto continue
end
end
if vim.tbl_contains(_NgConfigValues.lsp.disable_lsp or {}, lspclient) then
log("disable lsp", lspconfig)
goto continue
end
local default_config = {}
if lspconfig[lspclient] == nil then
print("lspclient", lspclient, "no longer support by lspconfig, please submit an issue")
@ -344,21 +349,36 @@ local function wait_lsp_startup(ft, retry, lsp_opts)
goto continue
end
log("cfg", lspclient, cfg)
-- if user provides override values
cfg.capabilities = capabilities
if lsp_opts[lspclient] ~= nil then
if user_lsp_opts[lspclient] ~= nil then
-- log(lsp_opts[lspclient], cfg)
cfg = vim.tbl_deep_extend("force", cfg, lsp_opts[lspclient])
local disable_fmt = false
log(lspclient, _NgConfigValues.lsp.disable_format_ft)
if vim.tbl_contains(_NgConfigValues.lsp.disable_format_ft or {}, lspclient) then
log("fileformat disabled for ", lspclient)
disable_fmt = true
end
cfg = vim.tbl_deep_extend("force", cfg, user_lsp_opts[lspclient])
if _NgConfigValues.combined_attach == nil then
cfg.on_attach = on_attach
cfg.on_attach = function(client, bufnr)
on_attach(client, bufnr)
if disable_fmt then
client.resolved_capabilities.document_formatting = false
end
end
end
if _NgConfigValues.combined_attach == "mine" then
if _NgConfigValues.on_attach == nil then
error("on attach not provided")
end
cfg.on_attach = _NgConfigValues.on_attach
cfg.on_attach = function(client, bufnr)
_NgConfigValues.on_attach(client, bufnr)
if disable_fmt then
client.resolved_capabilities.document_formatting = false
end
end
end
if _NgConfigValues.combined_attach == "both" then
cfg.on_attach = function(client, bufnr)
@ -370,6 +390,9 @@ local function wait_lsp_startup(ft, retry, lsp_opts)
else
on_attach(client, bufnr)
end
if disable_fmt then
client.resolved_capabilities.document_formatting = false
end
end
end
end
@ -378,7 +401,7 @@ local function wait_lsp_startup(ft, retry, lsp_opts)
::continue::
end
local efm_cfg = lsp_opts['efm']
local efm_cfg = user_lsp_opts['efm']
if efm_cfg then
lspconfig.efm.setup(efm_cfg)
end

@ -45,6 +45,8 @@ local key_maps = {
{key = '<Space>wa', func = 'vim.lsp.buf.add_workspace_folder()'},
{key = '<Space>wr', func = 'vim.lsp.buf.remove_workspace_folder()'},
{key = '<Space>wl', func = 'print(vim.inspect(vim.lsp.buf.list_workspace_folders()))'},
{key = "<Space>la", mode = "n", func = "require('navigator.codelens').run_action()"},
}
-- LuaFormatter on
local M = {}
@ -125,7 +127,7 @@ local function set_mapping(user_opts)
if ccls then
-- log("override ccls", ccls_mappings)
for _, value in pairs(ccls_mappings) do
f = "<Cmd>lua " .. value.func .. "<CR>"
local f = "<Cmd>lua " .. value.func .. "<CR>"
local k = value.key
local m = value.mode or "n"
set_keymap(m, k, f, opts)

@ -317,6 +317,31 @@ function M.locations_to_items(locations)
return items, width + 24 -- TODO handle long line?
end
function M.apply_action(action_chosen)
assert(action_chosen ~= nil, "action must not be nil")
if action_chosen == nil then
log("no match for ", action, lenses)
return
end
local switch = string.format("silent b %d", bufnr)
if action_chosen.edit or type(action_chosen.command) == "table" then
if action_chosen.edit then
vim.lsp.util.apply_workspace_edit(action_chosen.edit)
end
if type(action_chosen.command) == "table" then
-- switch buff
vim.cmd(switch)
vim.lsp.buf.execute_command(action_chosen.command)
end
else
vim.cmd(switch)
vim.lsp.buf.execute_command(action_chosen)
end
log(action_chosen)
end
function M.symbol_to_items(locations)
if not locations or vim.tbl_isempty(locations) then
print("list not avalible")

@ -1025,3 +1025,102 @@ definition.lua:9: { {
text = "🈲func (c circle) area() float64 {📛undeclared name: circle",
uri = "file:///Users/username/lsp_test/go/interface.go"
} }
-- code lens
{ {
command = {
arguments = { {
URIs = { "file:///Users/username/lsp_test/go/go.mod" }
} },
command = "gopls.tidy",
title = "Run go mod tidy"
},
range = {
end = {
character = 13,
line = 0
},
start = {
character = 0,
line = 0
}
}
}, {
command = {
arguments = { {
URI = "file:///Users/username/lsp_test/go/go.mod"
} },
command = "gopls.vendor",
title = "Create vendor directory"
},
range = {
end = {
character = 13,
line = 0
},
start = {
character = 0,
line = 0
}
}
}, {
command = {
arguments = { {
Modules = { "github.com/containerd/containerd", "github.com/docker/docker", "github.com/docker/go-connections", "github.com/fatih/gomodifytags", "github.com/google/go-cmp", "github.com/moby/term", "github.com/morikuni/aec", "github.com/sirupsen/logrus", "github.com/sourcegraph/jsonrpc2", "golang.org/x/net", "golang.org/x/sys", "golang.org/x/text", "golang.org/x/time", "golang.org/x/tools", "google.golang.org/grpc" },
URI = "file:///Users/username/lsp_test/go/go.mod"
} },
command = "gopls.check_upgrades",
title = "Check for upgrades"
},
range = {
end = {
character = 1,
line = 20
},
start = {
character = 0,
line = 4
}
}
}, {
command = {
arguments = { {
AddRequire = false,
GoCmdArgs = { "-d", "-u", "-t", "./..." },
URI = "file:///Users/username/lsp_test/go/go.mod"
} },
command = "gopls.upgrade_dependency",
title = "Upgrade transitive dependencies"
},
range = {
end = {
character = 1,
line = 20
},
start = {
character = 0,
line = 4
}
}
}, {
command = {
arguments = { {
AddRequire = false,
GoCmdArgs = { "-d", "github.com/containerd/containerd", "github.com/docker/docker", "github.com/docker/go-connections", "github.com/fatih/gomodifytags", "github.com/google/go-cmp", "github.com/moby/term", "github.com/morikuni/aec", "github.com/sirupsen/logrus", "github.com/sourcegraph/jsonrpc2", "golang.org/x/net", "golang.org/x/sys", "golang.org/x/text", "golang.org/x/time", "golang.org/x/tools", "google.golang.org/grpc" },
URI = "file:///Users/username/lsp_test/go/go.mod"
} },
command = "gopls.upgrade_dependency",
title = "Upgrade direct dependencies"
},
range = {
end = {
character = 1,
line = 20
},
start = {
character = 0,
line = 4
}
}
} }

@ -502,12 +502,12 @@ function M.get_node_at_line(lnum)
-- Get the language tree with nodes inside the given range
local root = parsers.get_parser()
local ts_tree = node_in_range(root, range)
log(ts_tree:trees())
-- log(ts_tree:trees())
local tree = ts_tree:trees()[1]
local node = tree:root():named_descendant_for_range(unpack(range))
log(node, node:type())
trace(node, node:type())
return node
end

@ -345,4 +345,8 @@ function M.clear_all_buf()
bufs = {}
end
function M.get_current_winid()
return api.nvim_get_current_win()
end
return M

Loading…
Cancel
Save