diff --git a/lua/symbols-outline.lua b/lua/symbols-outline.lua index b23867e..c99c07a 100644 --- a/lua/symbols-outline.lua +++ b/lua/symbols-outline.lua @@ -1,6 +1,8 @@ local vim = vim -local util = vim.lsp.util -local symbols = require('symbols') + +local parser = require('symbols-outline.parser') +local ui = require('symbols-outline.ui') +local writer = require('symbols-outline.writer') local D = {} @@ -33,17 +35,6 @@ local function getParams() return {textDocument = vim.lsp.util.make_text_document_params()} end -local function get_hover_params(node, winnr) - local bufnr = vim.api.nvim_win_get_buf(winnr) - local fn = "file://" .. vim.api.nvim_buf_get_name(bufnr) - - return { - textDocument = {uri = fn}, - position = {line = node.line, character = node.character}, - bufnr = bufnr - } -end - ------------------------- -- STATE ------------------------- @@ -59,200 +50,23 @@ local function wipe_state() D.state = {outline_items = {}, flattened_outline_items = {}} end -------------------------- --- UI STUFF -------------------------- -local markers = { - bottom = "└", - middle = "├", - vertical = "│", - horizontal = "─" -} - -local hovered_hl_ns = vim.api.nvim_create_namespace("hovered_item") - -local function highlight_text(name, text, hl_group) - vim.cmd(string.format("syn match %s /%s/", name, text)) - vim.cmd(string.format("hi def link %s %s", name, hl_group)) -end - -local function setup_highlights() - -- markers - -- - highlight_text("marker_middle", markers.middle, "Comment") - highlight_text("marker_vertical", markers.vertical, "Comment") - highlight_text("markers_horizontal", markers.horizontal, "Comment") - highlight_text("markers_bottom", markers.bottom, "Comment") - - for _, value in ipairs(symbols.kinds) do - local symbol = symbols[value] - highlight_text(value, symbol.icon, symbol.hl) - end - vim.cmd('hi FocusedSymbol guibg=#e50050') -end - ---------------------------- -- PARSING AND WRITING STUFF ---------------------------- --- copies an array and returns it because lua usually does references -local function array_copy(t) - local ret = {} - for _, value in ipairs(t) do table.insert(ret, value) end - return ret -end - --- parses result into a neat table -local function parse(result, depth, heirarchy) - local ret = {} - - for index, value in pairs(result) do - -- the heirarchy is basically a table of booleans which tells whether - -- the parent was the last in its group or not - local hir = heirarchy or {} - -- how many parents this node has, 1 is the lowest value because its - -- easier to work it - local level = depth or 1 - -- whether this node is the last in its group - local isLast = index == #result - - local children = nil - if value.children ~= nil then - -- copy by value because we dont want it messing with the hir table - local child_hir = array_copy(hir) - table.insert(child_hir, isLast) - children = parse(value.children, level + 1, child_hir) - end - - table.insert(ret, { - deprecated = value.deprecated, - kind = value.kind, - icon = symbols.icon_from_kind(value.kind), - name = value.name, - detail = value.detail, - line = value.selectionRange.start.line, - range_start = value.range.start.line, - range_end = value.range["end"].line, - character = value.selectionRange.start.character, - children = children, - depth = level, - isLast = isLast, - heirarchy = hir - }); - end - return ret -end - -local function flatten(outline_items) - local ret = {} - for _, value in ipairs(outline_items) do - table.insert(ret, value) - if value.children ~= nil then - local inner = flatten(value.children) - for _, value_inner in ipairs(inner) do - table.insert(ret, value_inner) - end - end - end - return ret -end - -local function table_to_str(t) - local ret = "" - for _, value in ipairs(t) do ret = ret .. tostring(value) end - return ret -end - -local function str_to_table(str) - local t = {} - for i = 1, #str do t[i] = str:sub(i, i) end - return t -end - -local function get_lines(flattened_outline_items) - local lines = {} - for _, value in ipairs(flattened_outline_items) do - local line = str_to_table(string.rep(" ", value.depth)) - - -- makes the guides - for index, _ in ipairs(line) do - -- all items start with a space (or two) - if index == 1 then - line[index] = " " - -- if index is last, add a bottom marker if current item is last, - -- else add a middle marker - elseif index == #line then - if value.isLast then - line[index] = markers.bottom - else - line[index] = markers.middle - end - -- else if the parent was not the last in its group, add a - -- vertical marker because there are items under us and we need - -- to point to those - elseif not value.heirarchy[index] then - line[index] = markers.vertical - end - end - - local final_prefix = {} - -- Add 1 space between the guides - for _, v in ipairs(line) do - table.insert(final_prefix, v) - table.insert(final_prefix, " ") - end - - table.insert(lines, table_to_str(final_prefix) .. value.icon .. " " .. - value.name) - end - return lines -end - -local function get_details(outline_items, bufnr, winnr, lines) - lines = lines or {} - for _, value in ipairs(outline_items) do - table.insert(lines, value.detail or "") - - if value.children ~= nil then - get_details(value.children, bufnr, winnr, lines) - end - end - return lines -end - -local function write_outline(bufnr, lines) - vim.api.nvim_buf_set_option(bufnr, "modifiable", true) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - vim.api.nvim_buf_set_option(bufnr, "modifiable", false) -end - -local function write_details(bufnr, lines) - for index, value in ipairs(lines) do - vim.api.nvim_buf_set_virtual_text(bufnr, -1, index - 1, - {{value, "Comment"}}, {}) - end -end - -local function clear_virt_text(bufnr) - vim.api.nvim_buf_clear_namespace(bufnr, -1, 0, -1) -end - function D._refresh() if D.state.outline_buf ~= nil then vim.lsp.buf_request(0, "textDocument/documentSymbol", getParams(), function(_, _, result) D.state.code_win = vim.api.nvim_get_current_win() - D.state.outline_items = parse(result) - D.state.flattened_outline_items = flatten(parse(result)) + D.state.outline_items = parser.parse(result) + D.state.flattened_outline_items = + parser.flatten(parser.parse(result)) - local lines = get_lines(D.state.flattened_outline_items) - write_outline(D.state.outline_buf, lines) - - clear_virt_text(D.state.outline_buf) - local details = get_details(D.state.outline_items, - D.state.outline_buf, D.state.outline_win) - write_details(D.state.outline_buf, details) + writer.parse_and_write(D.state.outline_buf, D.state.outline_win, + D.state.outline_items, + D.state.flattened_outline_items) end) end end @@ -264,46 +78,6 @@ function D._goto_location() vim.fn.cursor(node.line + 1, node.character + 1) end --- handler yoinked from the default implementation -function D._hover() - local current_line = vim.api.nvim_win_get_cursor(D.state.outline_win)[1] - local node = D.state.flattened_outline_items[current_line] - - local hover_params = get_hover_params(node, D.state.code_win) - - vim.lsp.buf_request(hover_params.bufnr, "textDocument/hover", hover_params, - function(_, method, result) - - util.focusable_float(method, function() - if not (result and result.contents) then - -- return { 'No information available' } - return - end - - local markdown_lines = util.convert_input_to_markdown_lines( - result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - -- return { 'No information available' } - return - end - local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, { - border = { - {"┌", "FloatBorder"}, {"─", "FloatBorder"}, - {"┐", "FloatBorder"}, {"│", "FloatBorder"}, - {"┘", "FloatBorder"}, {"─", "FloatBorder"}, - {"└", "FloatBorder"}, {"│", "FloatBorder"} - } - }) - util.close_preview_autocmd({ - "CursorMoved", "BufHidden", "InsertCharPre" - }, winnr) - - return bufnr, winnr - end) - end) -end - function D._highlight_current_item() if D.state.outline_buf == nil or vim.api.nvim_get_current_buf() == D.state.outline_buf then return end @@ -321,12 +95,10 @@ function D._highlight_current_item() end -- clear old highlight - vim.api.nvim_buf_clear_namespace(D.state.outline_buf, hovered_hl_ns, 0, -1) + ui.clear_hover_highlight(D.state.outline_buf) for _, value in ipairs(nodes) do - vim.api.nvim_buf_add_highlight(D.state.outline_buf, hovered_hl_ns, - "FocusedSymbol", - value.line_in_outline - 1, - value.depth * 2, -1) + ui.add_hover_highlight(D.state.outline_buf, value.line_in_outline - 1, + value.depth * 2) vim.api.nvim_win_set_cursor(D.state.outline_win, {value.line_in_outline, 1}) end @@ -366,7 +138,7 @@ local function setup_keymaps(bufnr) {}) -- hover symbol vim.api.nvim_buf_set_keymap(bufnr, "n", "", - ":lua require('symbols-outline')._hover()", + ":lua require('symbols-outline.hover').show_hover()", {}) -- close outline when escape is pressed vim.api.nvim_buf_set_keymap(bufnr, "n", "", ":bw!", {}) @@ -402,16 +174,13 @@ local function handler(_, _, result) D.state.code_win = vim.api.nvim_get_current_win() setup_buffer() - D.state.outline_items = parse(result) - D.state.flattened_outline_items = flatten(parse(result)) + D.state.outline_items = parser.parse(result) + D.state.flattened_outline_items = parser.flatten(parser.parse(result)) - local lines = get_lines(D.state.flattened_outline_items) - write_outline(D.state.outline_buf, lines) - - local details = get_details(D.state.outline_items, D.state.outline_buf, - D.state.outline_win) - write_details(D.state.outline_buf, details) - setup_highlights() + writer.parse_and_write(D.state.outline_buf, D.state.outline_win, + D.state.outline_items, + D.state.flattened_outline_items) + ui.setup_highlights() end function D.toggle_outline() diff --git a/lua/symbols-outline/hover.lua b/lua/symbols-outline/hover.lua new file mode 100644 index 0000000..0931f0a --- /dev/null +++ b/lua/symbols-outline/hover.lua @@ -0,0 +1,59 @@ +local vim = vim + +local state = require('symbols-outline').state +local util = vim.lsp.util + +local M = {} + +local function get_hover_params(node, winnr) + local bufnr = vim.api.nvim_win_get_buf(winnr) + local fn = "file://" .. vim.api.nvim_buf_get_name(bufnr) + + return { + textDocument = {uri = fn}, + position = {line = node.line, character = node.character}, + bufnr = bufnr + } +end + +-- handler yoinked from the default implementation +function M.show_hover() + local current_line = vim.api.nvim_win_get_cursor(state.outline_win)[1] + local node = state.flattened_outline_items[current_line] + + local hover_params = get_hover_params(node, state.code_win) + + vim.lsp.buf_request(hover_params.bufnr, "textDocument/hover", hover_params, + function(_, method, result) + + util.focusable_float(method, function() + if not (result and result.contents) then + -- return { 'No information available' } + return + end + + local markdown_lines = util.convert_input_to_markdown_lines( + result.contents) + markdown_lines = util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + -- return { 'No information available' } + return + end + local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, { + border = { + {"┌", "FloatBorder"}, {"─", "FloatBorder"}, + {"┐", "FloatBorder"}, {"│", "FloatBorder"}, + {"┘", "FloatBorder"}, {"─", "FloatBorder"}, + {"└", "FloatBorder"}, {"│", "FloatBorder"} + } + }) + util.close_preview_autocmd({ + "CursorMoved", "BufHidden", "InsertCharPre" + }, winnr) + + return bufnr, winnr + end) + end) +end + +return M diff --git a/lua/symbols-outline/parser.lua b/lua/symbols-outline/parser.lua new file mode 100644 index 0000000..7c84840 --- /dev/null +++ b/lua/symbols-outline/parser.lua @@ -0,0 +1,131 @@ +local symbols = require('symbols-outline.symbols') +local ui = require('symbols-outline.ui') + +local M = {} + +-- copies an array and returns it because lua usually does references +local function array_copy(t) + local ret = {} + for _, value in ipairs(t) do table.insert(ret, value) end + return ret +end + +-- parses result into a neat table +function M.parse(result, depth, heirarchy) + local ret = {} + + for index, value in pairs(result) do + -- the heirarchy is basically a table of booleans which tells whether + -- the parent was the last in its group or not + local hir = heirarchy or {} + -- how many parents this node has, 1 is the lowest value because its + -- easier to work it + local level = depth or 1 + -- whether this node is the last in its group + local isLast = index == #result + + local children = nil + if value.children ~= nil then + -- copy by value because we dont want it messing with the hir table + local child_hir = array_copy(hir) + table.insert(child_hir, isLast) + children = M.parse(value.children, level + 1, child_hir) + end + + table.insert(ret, { + deprecated = value.deprecated, + kind = value.kind, + icon = symbols.icon_from_kind(value.kind), + name = value.name, + detail = value.detail, + line = value.selectionRange.start.line, + range_start = value.range.start.line, + range_end = value.range["end"].line, + character = value.selectionRange.start.character, + children = children, + depth = level, + isLast = isLast, + heirarchy = hir + }); + end + return ret +end + +function M.flatten(outline_items) + local ret = {} + for _, value in ipairs(outline_items) do + table.insert(ret, value) + if value.children ~= nil then + local inner = M.flatten(value.children) + for _, value_inner in ipairs(inner) do + table.insert(ret, value_inner) + end + end + end + return ret +end + +local function table_to_str(t) + local ret = "" + for _, value in ipairs(t) do ret = ret .. tostring(value) end + return ret +end + +local function str_to_table(str) + local t = {} + for i = 1, #str do t[i] = str:sub(i, i) end + return t +end + +function M.get_lines(flattened_outline_items) + local lines = {} + for _, value in ipairs(flattened_outline_items) do + local line = str_to_table(string.rep(" ", value.depth)) + + -- makes the guides + for index, _ in ipairs(line) do + -- all items start with a space (or two) + if index == 1 then + line[index] = " " + -- if index is last, add a bottom marker if current item is last, + -- else add a middle marker + elseif index == #line then + if value.isLast then + line[index] = ui.markers.bottom + else + line[index] = ui.markers.middle + end + -- else if the parent was not the last in its group, add a + -- vertical marker because there are items under us and we need + -- to point to those + elseif not value.heirarchy[index] then + line[index] = ui.markers.vertical + end + end + + local final_prefix = {} + -- Add 1 space between the guides + for _, v in ipairs(line) do + table.insert(final_prefix, v) + table.insert(final_prefix, " ") + end + + table.insert(lines, table_to_str(final_prefix) .. value.icon .. " " .. + value.name) + end + return lines +end + +function M.get_details(outline_items, bufnr, winnr, lines) + lines = lines or {} + for _, value in ipairs(outline_items) do + table.insert(lines, value.detail or "") + + if value.children ~= nil then + M.get_details(value.children, bufnr, winnr, lines) + end + end + return lines +end + +return M diff --git a/lua/symbols.lua b/lua/symbols-outline/symbols.lua similarity index 100% rename from lua/symbols.lua rename to lua/symbols-outline/symbols.lua diff --git a/lua/symbols-outline/ui.lua b/lua/symbols-outline/ui.lua new file mode 100644 index 0000000..baec8d5 --- /dev/null +++ b/lua/symbols-outline/ui.lua @@ -0,0 +1,43 @@ +local vim = vim +local symbols = require('symbols-outline.symbols') +local M = {} + +M.markers = { + bottom = "└", + middle = "├", + vertical = "│", + horizontal = "─" +} + +M.hovered_hl_ns = vim.api.nvim_create_namespace("hovered_item") + +function M.clear_hover_highlight(bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, M.hovered_hl_ns, 0, -1) +end + +function M.add_hover_highlight(bufnr, line, col_start) + vim.api.nvim_buf_add_highlight(bufnr, M.hovered_hl_ns, "FocusedSymbol", + line, col_start, -1) + +end + +local function highlight_text(name, text, hl_group) + vim.cmd(string.format("syn match %s /%s/", name, text)) + vim.cmd(string.format("hi def link %s %s", name, hl_group)) +end + +function M.setup_highlights() + -- markers + highlight_text("marker_middle", M.markers.middle, "Comment") + highlight_text("marker_vertical", M.markers.vertical, "Comment") + highlight_text("markers_horizontal", M.markers.horizontal, "Comment") + highlight_text("markers_bottom", M.markers.bottom, "Comment") + + for _, value in ipairs(symbols.kinds) do + local symbol = symbols[value] + highlight_text(value, symbol.icon, symbol.hl) + end + vim.cmd('hi FocusedSymbol guibg=#e50050') +end + +return M diff --git a/lua/symbols-outline/writer.lua b/lua/symbols-outline/writer.lua new file mode 100644 index 0000000..b5670c0 --- /dev/null +++ b/lua/symbols-outline/writer.lua @@ -0,0 +1,35 @@ +local vim = vim + +local parser = require('symbols-outline.parser') + +local M = {} + +function M.write_outline(bufnr, lines) + vim.api.nvim_buf_set_option(bufnr, "modifiable", true) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.api.nvim_buf_set_option(bufnr, "modifiable", false) +end + +function M.write_details(bufnr, lines) + for index, value in ipairs(lines) do + vim.api.nvim_buf_set_virtual_text(bufnr, -1, index - 1, + {{value, "Comment"}}, {}) + end +end + +local function clear_virt_text(bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, -1, 0, -1) +end + +-- runs the whole writing routine where the text is cleared, new data is parsed +-- and then written +function M.parse_and_write(bufnr, winnr, outline_items, flattened_outline_items) + local lines = parser.get_lines(flattened_outline_items) + M.write_outline(bufnr, lines) + + clear_virt_text(bufnr) + local details = parser.get_details(outline_items, bufnr, winnr) + M.write_details(bufnr, details) +end + +return M