Skip to content

Commit

Permalink
ui,feature: implement an outline view similar to vscode
Browse files Browse the repository at this point in the history
this feature implements a persistent and live outline view similar to
vscode's.
  • Loading branch information
ldelossa committed Nov 23, 2021
1 parent 50342f5 commit c0b89e6
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 106 deletions.
15 changes: 13 additions & 2 deletions lua/calltree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ M.nerd = {
Class = "",
Interface = "",
Module = "",
Namespace = "",
Object = "",
Property = "",
Unit = "",
Value = "",
Expand All @@ -33,13 +35,15 @@ M.nerd = {
M.codicons = {
Text = "",
Method = "",
Function = "",
Function = "",
Constructor = "",
Field = "",
Variable = "",
Class = "",
Interface = "",
Module = "",
Namespace = "",
Object = "",
Property = "",
Unit = "",
Value = "",
Expand Down Expand Up @@ -79,6 +83,12 @@ function M.setup(user_config)
vim.lsp.handlers['callHierarchy/outgoingCalls'] = vim.lsp.with(
require('calltree.lsp.handlers').ch_lsp_handler("to"), {}
)
vim.lsp.handlers['textDocument/documentSymbol'] = vim.lsp.with(
require('calltree.lsp.handlers').ws_lsp_handler(), {}
)

-- autocommand for updating outline view
-- vim.cmd([[au TextChanged,BufEnter,BufWritePost * lua require('calltree.ui').refresh_symbol_tree()]])

-- merge config
if user_config ~= nil then
Expand Down Expand Up @@ -115,7 +125,8 @@ function M.setup(user_config)
-- setup commands
vim.cmd("command! CTOpen lua require('calltree.ui').open_calltree()")
vim.cmd("command! STOpen lua require('calltree.ui').open_symboltree()")
vim.cmd("command! CTClose lua require('calltree.ui').close()")
vim.cmd("command! CTClose lua require('calltree.ui').close_calltree()")
vim.cmd("command! STClose lua require('calltree.ui').close_symboltree()")
vim.cmd("command! CTExpand lua require('calltree.ui').expand()")
vim.cmd("command! CTCollapse lua require('calltree.ui').collapse()")
vim.cmd("command! CTSwitch lua require('calltree.ui').switch_direction()")
Expand Down
46 changes: 43 additions & 3 deletions lua/calltree/lsp/handlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ M.ch_lsp_handler = function(direction)

-- store the window invoking the call tree, jumps will
-- occur here.
ui.invoking_win = vim.api.nvim_get_current_win()
ui.invoking_calltree_win = vim.api.nvim_get_current_win()

-- create a new tree
ui.calltree_handle = tree.new_tree("calltree")
Expand All @@ -41,7 +41,7 @@ M.ch_lsp_handler = function(direction)
nil)

-- try to resolve the workspace symbol for root.
root.symbol = lsp_util.symbol_from_node(ui.active_lsp_clients, root, ui.invoking_win_handle)
root.symbol = lsp_util.symbol_from_node(ui.active_lsp_clients, root, ui.invoking_calltree_win)

-- create the root's children nodes via the response array.
local children = {}
Expand All @@ -53,7 +53,7 @@ M.ch_lsp_handler = function(direction)
call_hierarchy_call.fromRanges
)
-- try to resolve the workspace symbol for child
child.symbol = lsp_util.symbol_from_node(ui.active_lsp_clients, child, ui.invoking_win_handle)
child.symbol = lsp_util.symbol_from_node(ui.active_lsp_clients, child, ui.invoking_calltree_win)
table.insert(children, child)
end

Expand All @@ -62,4 +62,44 @@ M.ch_lsp_handler = function(direction)
end
end

M.ws_lsp_handler = function()
return function(err, result, ctx, _)
if err ~= nil then
return
end
if result == nil then
return
end
-- snag the lsp clients from the buffer issuing the
-- call hierarchy request
ui.active_lsp_clients = vim.lsp.get_active_clients()

local prev_depth_table = nil
-- grab the previous depth table if it exists
local prev_tree = tree.get_tree(ui.symboltree_handle)
if prev_tree ~= nil then
prev_depth_table = prev_tree.depth_table
end

ui.invoking_symboltree_win = vim.api.nvim_get_current_win()
print(ui.invoking_symboltree_win)

-- create a new tree
ui.symboltree_handle = tree.new_tree("symboltree")

-- create a synthetic document symbol to act as a root
local synthetic_root_ds = {
name = lsp_util.relative_path_from_uri(ctx.params.textDocument.uri),
kind = 1,
range = {start = {line = -1}}, -- provide this so keyify works in tree_node.add
selectionRange = {start = {line = -1}}, -- provide this so keyify works in tree_node.add
children = result,
uri = ctx.params.textDocument.uri
}
local root = lsp_util.build_recursive_symbol_tree(0, synthetic_root_ds, nil, prev_depth_table)
tree.add_node(ui.symboltree_handle, root, nil, true)
ui.open_symboltree()
end
end

return M
127 changes: 127 additions & 0 deletions lua/calltree/lsp/util.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

local tree_node = require('calltree.tree.node')
local M = {}

M.multi_client_request = function(clients, method, params, handler, bufnr)
Expand All @@ -22,6 +24,96 @@ function M.relative_path_from_uri(uri)
return vim.fn.substitute(uri_path, cwd .. "/", "", ""), true
end

function M.resolve_file_path(node)
if node.symbol ~= nil then
uri = node.symbol.location.uri
return M.relative_path_from_uri(uri)
elseif node.call_hierarchy_item ~= nil then
uri = node.call_hierarchy_item.uri
return M.relative_path_from_uri(uri)
elseif node.document_symbol ~= nil then
uri = node.uri
return M.relative_path_from_uri(uri)
else
return nil
end

end

function M.resolve_location(node)
local location = nil
if node.symbol ~= nil then
location = node.symbol.location
elseif node.call_hierarchy_item ~= nil then
location = {
uri = node.call_hierarchy_item.uri,
range = node.call_hierarchy_item.range
}
elseif node.document_symbol ~= nil then
location = {
uri = node.uri,
range = node.document_symbol.selectionRange
}
end
return location
end

function M.resolve_hover_params(node)
local params = {}
if node.symbol ~= nil then
params.textDocument = {
uri = node.symbol.location.uri
}
params.position = {
line = node.symbol.location.range.start.line,
character = node.symbol.location.range.start.character
}
elseif node.call_hierarchy_item ~= nil then
params.textDocument = {
uri = node.call_hierarchy_item.uri
}
params.position = {
line = node.call_hierarchy_item.range.start.line,
character = node.call_hierarchy_item.range.start.character
}
elseif node.document_symbol ~= nil then
params.textDocument = {
uri = node.uri
}
params.position = {
line = node.document_symbol.selectionRange.start.line,
character = node.document_symbol.selectionRange.start.character
}
else
return nil
end
return params
end

function M.resolve_symbol_kind(node)
if node.symbol ~= nil then
return vim.lsp.protocol.SymbolKind[node.symbol.kind]
elseif node.call_hierarchy_item ~= nil then
return vim.lsp.protocol.SymbolKind[node.call_hierarchy_item.kind]
elseif node.document_symbol ~= nil then
return vim.lsp.protocol.SymbolKind[node.document_symbol.kind]
else
return nil
end
end

function M.resolve_detail(node)
if node.symbol ~= nil then
return node.symbol.detail
elseif node.call_hierarchy_item ~= nil then
return node.call_hierarchy_item.detail
elseif node.document_symbol ~= nil then
return node.document_symbol.detail
else
return nil
end
end

-- symbol_from_node attempts to extract the workspace
-- symbol the node represents.
--
Expand Down Expand Up @@ -66,4 +158,39 @@ function M.symbol_from_node(clients, node, bufnr)
return nil
end

function M.build_recursive_symbol_tree(depth, document_symbol, parent, prev_depth_table)
local node = tree_node.new(
document_symbol.name,
depth,
nil,
nil,
document_symbol
)
if parent == nil then
-- if no parent the document_symbol is our synthetic one and carries the uri
-- which will be recursively added to all children nodes in the document symboltree.
node.uri = document_symbol.uri
end
-- if we have a previous depth table search it for an old reference of self
-- and set expanded state correctly.
if prev_depth_table ~= nil and prev_depth_table[depth] ~= nil then
for _, child in ipairs(prev_depth_table[depth]) do
if child.key == node.key then
node.expanded = child.expanded
end
end
end
if parent ~= nil then
-- the parent will be carrying the uri for the document symbol tree we are building.
node.uri = parent.uri
table.insert(parent.children, node)
end
if document_symbol.children ~= nil then
for _, child_document_symbol in ipairs(document_symbol.children) do
M.build_recursive_symbol_tree(depth+1, child_document_symbol, node, prev_depth_table)
end
end
return node
end

return M
31 changes: 25 additions & 6 deletions lua/calltree/tree/node.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@ local M = {}
-- returns:
-- string - the key
function M.keyify(node)
local key = node.name .. ":"
.. node.call_hierarchy_item.uri .. ":"
.. node.call_hierarchy_item.range.start.line
return key
local key = ""
if node.document_symbol ~= nil then
key = node.document_symbol.name .. ":" ..
node.document_symbol.kind .. ":" ..
node.document_symbol.range.start.line
return key
end
if node.symbol ~= nil then
key = node.symbol.name .. ":" ..
node.symbol.location.uri .. ":" ..
node.symbol.location.range.start.line
return key
end
if node.call_hierarchy_item ~= nil then
key = node.call_hierarchy_item.name .. ":" ..
node.call_hierarchy_item.uri .. ":" ..
node.call_hierarchy_item.range.start.line
return key
end
end

-- new construct a new Node.
Expand All @@ -28,15 +43,19 @@ end
-- kind : string - the kind of symbol this node represents.
--
-- references : array of references of the given symbol
function M.new(name, depth, call_hierarchy_item, references)
function M.new(name, depth, call_hierarchy_item, references, document_symbol)
local node = {
name=name,
depth=depth,
call_hierarchy_item=call_hierarchy_item,
references=references,
children={},
expanded=false,
symbol=nil
symbol=nil,
document_symbol=document_symbol,
-- if the node is a document_symbol this field will be present
-- containing the document uri the symbol belongs to.
uri=""
}
node.key = M.keyify(node)
return node
Expand Down
15 changes: 12 additions & 3 deletions lua/calltree/tree/tree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function M.new_tree(kind)
end

function M.get_tree(handle)
return reg[handle].root
return reg[handle]
end

-- recursive_dpt_compute traverses the tree
Expand All @@ -51,7 +51,16 @@ local function _refresh_dpt(tree)
_recursive_dpt_compute(tree, reg[tree].root)
end

function M.add_node(tree, parent, children)
function M.add_node(tree, parent, children, external)
-- external nodes are roots of trees built externally
-- if this is true set the tree's root to the incoming parent
-- and immediately return
if external then
reg[tree].root = parent
_refresh_dpt(tree)
return
end

-- if depth is 0 we are creating a new call tree.
if parent.depth == 0 then
reg[tree].root = parent
Expand Down Expand Up @@ -92,7 +101,7 @@ function M.add_node(tree, parent, children)
end

M.write_tree = function(tree, buf)
marshal.marshal_tree(buf, {}, reg[tree].root)
marshal.marshal_tree(buf, {}, reg[tree].root, tree)
return buf
end

Expand Down
Loading

0 comments on commit c0b89e6

Please sign in to comment.